/// @brief Computes saturation from surface volume void computeSaturation(const BlackoilPropertiesInterface& props, BlackoilState& state) { const int np = props.numPhases(); const int nc = props.numCells(); std::vector<double> allA(nc*np*np); std::vector<int> allcells(nc); for (int c = 0; c < nc; ++c) { allcells[c] = c; } //std::vector<double> res_vol(np); const std::vector<double>& z = state.surfacevol(); props.matrix(nc, &state.pressure()[0], &z[0], &allcells[0], &allA[0], 0); // Linear solver. MAT_SIZE_T n = np; MAT_SIZE_T nrhs = 1; MAT_SIZE_T lda = np; std::vector<MAT_SIZE_T> piv(np); MAT_SIZE_T ldb = np; MAT_SIZE_T info = 0; //double res_vol; double tot_sat; const double epsilon = std::sqrt(std::numeric_limits<double>::epsilon()); for (int c = 0; c < nc; ++c) { double* A = &allA[c*np*np]; const double* z_loc = &z[c*np]; double* s = &state.saturation()[c*np]; for (int p = 0; p < np; ++p){ s[p] = z_loc[p]; } dgesv_(&n, &nrhs, &A[0], &lda, &piv[0], &s[0], &ldb, &info); tot_sat = 0; for (int p = 0; p < np; ++p){ if (s[p] < epsilon) // saturation may be less then zero due to round of errors s[p] = 0; tot_sat += s[p]; } for (int p = 0; p < np; ++p){ s[p] = s[p]/tot_sat; } } }
/// Construct solver. /// \param[in] grid A 2d or 3d grid. /// \param[in] props Rock and fluid properties. /// \param[in] linsolver Linear solver to use. /// \param[in] residual_tol Solution accepted if inf-norm of residual is smaller. /// \param[in] change_tol Solution accepted if inf-norm of change in pressure is smaller. /// \param[in] maxiter Maximum acceptable number of iterations. /// \param[in] gravity Gravity vector. If non-null, the array should /// have D elements. /// \param[in] wells The wells argument. Will be used in solution, /// is ignored if NULL. /// Note: this class observes the well object, and /// makes the assumption that the well topology /// and completions does not change during the /// run. However, controls (only) are allowed /// to change. CompressibleTpfa::CompressibleTpfa(const UnstructuredGrid& grid, const BlackoilPropertiesInterface& props, const RockCompressibility* rock_comp_props, const LinearSolverInterface& linsolver, const double residual_tol, const double change_tol, const int maxiter, const double* gravity, const struct Wells* wells) : grid_(grid), props_(props), rock_comp_props_(rock_comp_props), linsolver_(linsolver), residual_tol_(residual_tol), change_tol_(change_tol), maxiter_(maxiter), gravity_(gravity), wells_(wells), htrans_(grid.cell_facepos[ grid.number_of_cells ]), trans_ (grid.number_of_faces), allcells_(grid.number_of_cells), singular_(false) { if (wells_ && (wells_->number_of_phases != props.numPhases())) { OPM_THROW(std::runtime_error, "Inconsistent number of phases specified (wells vs. props): " << wells_->number_of_phases << " != " << props.numPhases()); } const int num_dofs = grid.number_of_cells + (wells ? wells->number_of_wells : 0); pressure_increment_.resize(num_dofs); UnstructuredGrid* gg = const_cast<UnstructuredGrid*>(&grid_); tpfa_htrans_compute(gg, props.permeability(), &htrans_[0]); tpfa_trans_compute(gg, &htrans_[0], &trans_[0]); // If we have rock compressibility, pore volumes are updated // in the compute*() methods, otherwise they are constant and // hence may be computed here. if (rock_comp_props_ == NULL || !rock_comp_props_->isActive()) { computePorevolume(grid_, props.porosity(), porevol_); } for (int c = 0; c < grid.number_of_cells; ++c) { allcells_[c] = c; } cfs_tpfa_res_wells w; w.W = const_cast<struct Wells*>(wells_); w.data = NULL; h_ = cfs_tpfa_res_construct(gg, &w, props.numPhases()); }
/// @brief Computes injected and produced surface volumes of all phases. /// Note 1: assumes that only the first phase is injected. /// Note 2: assumes that transport has been done with an /// implicit method, i.e. that the current state /// gives the mobilities used for the preceding timestep. /// Note 3: Gives surface volume values, not reservoir volumes /// (as the incompressible version of the function does). /// Also, assumes that transport_src is given in surface volumes /// for injector terms! /// @param[in] props fluid and rock properties. /// @param[in] state state variables (pressure, sat, surfvol) /// @param[in] transport_src if < 0: total resv outflow, if > 0: first phase surfv inflow /// @param[in] dt timestep used /// @param[out] injected must point to a valid array with P elements, /// where P = s.size()/src.size(). /// @param[out] produced must also point to a valid array with P elements. void computeInjectedProduced(const BlackoilPropertiesInterface& props, const BlackoilState& state, const std::vector<double>& transport_src, const double dt, double* injected, double* produced) { const int num_cells = transport_src.size(); if (props.numCells() != num_cells) { OPM_THROW(std::runtime_error, "Size of transport_src vector does not match number of cells in props."); } const int np = props.numPhases(); if (int(state.saturation().size()) != num_cells*np) { OPM_THROW(std::runtime_error, "Sizes of state vectors do not match number of cells."); } const std::vector<double>& press = state.pressure(); const std::vector<double>& temp = state.temperature(); const std::vector<double>& s = state.saturation(); const std::vector<double>& z = state.surfacevol(); std::fill(injected, injected + np, 0.0); std::fill(produced, produced + np, 0.0); std::vector<double> visc(np); std::vector<double> mob(np); std::vector<double> A(np*np); std::vector<double> prod_resv_phase(np); std::vector<double> prod_surfvol(np); for (int c = 0; c < num_cells; ++c) { if (transport_src[c] > 0.0) { // Inflowing transport source is a surface volume flux // for the first phase. injected[0] += transport_src[c]*dt; } else if (transport_src[c] < 0.0) { // Outflowing transport source is a total reservoir // volume flux. const double flux = -transport_src[c]*dt; const double* sat = &s[np*c]; props.relperm(1, sat, &c, &mob[0], 0); props.viscosity(1, &press[c], &temp[c], &z[np*c], &c, &visc[0], 0); props.matrix(1, &press[c], &temp[c], &z[np*c], &c, &A[0], 0); double totmob = 0.0; for (int p = 0; p < np; ++p) { mob[p] /= visc[p]; totmob += mob[p]; } std::fill(prod_surfvol.begin(), prod_surfvol.end(), 0.0); for (int p = 0; p < np; ++p) { prod_resv_phase[p] = (mob[p]/totmob)*flux; for (int q = 0; q < np; ++q) { prod_surfvol[q] += prod_resv_phase[p]*A[q + np*p]; } } for (int p = 0; p < np; ++p) { produced[p] += prod_surfvol[p]; } } } }
void WellReport::push(const BlackoilPropertiesInterface& props, const Wells& wells, const std::vector<double>& p, const std::vector<double>& z, const std::vector<double>& s, const double time, const std::vector<double>& well_bhp, const std::vector<double>& well_perfrates) { // TODO: refactor, since this is almost identical to the other push(). int nw = well_bhp.size(); assert(nw == wells.number_of_wells); int np = props.numPhases(); const int max_np = 3; if (np > max_np) { OPM_THROW(std::runtime_error, "WellReport for now assumes #phases <= " << max_np); } std::vector<double> data_now; data_now.reserve(1 + 3*nw); data_now.push_back(time/unit::day); for (int w = 0; w < nw; ++w) { data_now.push_back(well_bhp[w]/(unit::barsa)); double well_rate_total = 0.0; double well_rate_water = 0.0; for (int perf = wells.well_connpos[w]; perf < wells.well_connpos[w + 1]; ++perf) { const double perf_rate = unit::convert::to(well_perfrates[perf], unit::cubic(unit::meter)/unit::day); well_rate_total += perf_rate; if (perf_rate > 0.0) { // Injection. well_rate_water += perf_rate*wells.comp_frac[0]; } else { // Production. const int cell = wells.well_cells[perf]; double mob[max_np]; props.relperm(1, &s[np*cell], &cell, mob, 0); double visc[max_np]; props.viscosity(1, &p[cell], 0, &z[np*cell], &cell, visc, 0); double tmob = 0; for(int i = 0; i < np; ++i) { mob[i] /= visc[i]; tmob += mob[i]; } const double fracflow = mob[0]/(tmob); well_rate_water += perf_rate*fracflow; } } data_now.push_back(well_rate_total); if (well_rate_total == 0.0) { data_now.push_back(0.0); } else { data_now.push_back(well_rate_water/well_rate_total); } } data_.push_back(data_now); }
void initStateBasic(const UnstructuredGrid& grid, const BlackoilPropertiesInterface& props, const parameter::ParameterGroup& param, const double gravity, State& state) { // TODO: Refactor to exploit similarity with IncompProp* case. const int num_phases = props.numPhases(); if (num_phases != 2) { THROW("initStateTwophaseBasic(): currently handling only two-phase scenarios."); } state.init(grid, num_phases); const int num_cells = props.numCells(); // By default: initialise water saturation to minimum everywhere. std::vector<int> all_cells(num_cells); for (int i = 0; i < num_cells; ++i) { all_cells[i] = i; } state.setFirstSat(all_cells, props, State::MinSat); const bool convection_testcase = param.getDefault("convection_testcase", false); if (convection_testcase) { // Initialise water saturation to max in the 'left' part. std::vector<int> left_cells; left_cells.reserve(num_cells/2); const int *glob_cell = grid.global_cell; const int* cd = grid.cartdims; for (int cell = 0; cell < num_cells; ++cell) { const int gc = glob_cell == 0 ? cell : glob_cell[cell]; bool left = (gc % cd[0]) < cd[0]/2; if (left) { left_cells.push_back(cell); } } state.setFirstSat(left_cells, props, State::MaxSat); const double init_p = param.getDefault("ref_pressure", 100.0)*unit::barsa; std::fill(state.pressure().begin(), state.pressure().end(), init_p); } else if (param.has("water_oil_contact")) { // Warn against error-prone usage. if (gravity == 0.0) { std::cout << "**** Warning: running gravity convection scenario, but gravity is zero." << std::endl; } if (grid.cartdims[2] <= 1) { std::cout << "**** Warning: running gravity convection scenario, which expects nz > 1." << std::endl; } // Initialise water saturation to max below water-oil contact. const double woc = param.get<double>("water_oil_contact"); initWaterOilContact(grid, props, woc, WaterBelow, state); // Initialise pressure to hydrostatic state. const double ref_p = param.getDefault("ref_pressure", 100.0)*unit::barsa; initHydrostaticPressure(grid, props, woc, gravity, woc, ref_p, state); } else { // Use default: water saturation is minimum everywhere. // Initialise pressure to hydrostatic state. const double ref_p = param.getDefault("ref_pressure", 100.0)*unit::barsa; const double ref_z = grid.cell_centroids[0 + grid.dimensions - 1]; const double woc = -1e100; initHydrostaticPressure(grid, props, woc, gravity, ref_z, ref_p, state); } // Finally, init face pressures. initFacePressure(grid, state); }
/// @brief Computes injected and produced volumes of all phases, /// and injected and produced polymer mass - in the compressible case. /// Note 1: assumes that only the first phase is injected. /// Note 2: assumes that transport has been done with an /// implicit method, i.e. that the current state /// gives the mobilities used for the preceding timestep. /// @param[in] props fluid and rock properties. /// @param[in] polyprops polymer properties /// @param[in] state state variables (pressure, fluxes etc.) /// @param[in] transport_src if < 0: total reservoir volume outflow, /// if > 0: first phase *surface volume* inflow. /// @param[in] inj_c injected concentration by cell /// @param[in] dt timestep used /// @param[out] injected must point to a valid array with P elements, /// where P = s.size()/transport_src.size(). /// @param[out] produced must also point to a valid array with P elements. /// @param[out] polyinj injected mass of polymer /// @param[out] polyprod produced mass of polymer void computeInjectedProduced(const BlackoilPropertiesInterface& props, const Opm::PolymerProperties& polyprops, const PolymerBlackoilState& state, const std::vector<double>& transport_src, const std::vector<double>& inj_c, const double dt, double* injected, double* produced, double& polyinj, double& polyprod) { const int num_cells = transport_src.size(); if (props.numCells() != num_cells) { OPM_THROW(std::runtime_error, "Size of transport_src vector does not match number of cells in props."); } const int np = props.numPhases(); if (int(state.saturation().size()) != num_cells*np) { OPM_THROW(std::runtime_error, "Sizes of state vectors do not match number of cells."); } const std::vector<double>& press = state.pressure(); const std::vector<double>& temp = state.temperature(); const std::vector<double>& s = state.saturation(); const std::vector<double>& z = state.surfacevol(); const std::vector<double>& c = state.getCellData( state.CONCENTRATION ); const std::vector<double>& cmax = state.getCellData( state.CMAX ); std::fill(injected, injected + np, 0.0); std::fill(produced, produced + np, 0.0); polyinj = 0.0; polyprod = 0.0; std::vector<double> visc(np); std::vector<double> kr_cell(np); std::vector<double> mob(np); std::vector<double> A(np*np); std::vector<double> prod_resv_phase(np); std::vector<double> prod_surfvol(np); double mc; for (int cell = 0; cell < num_cells; ++cell) { if (transport_src[cell] > 0.0) { // Inflowing transport source is a surface volume flux // for the first phase. injected[0] += transport_src[cell]*dt; polyinj += transport_src[cell]*dt*inj_c[cell]; } else if (transport_src[cell] < 0.0) { // Outflowing transport source is a total reservoir // volume flux. const double flux = -transport_src[cell]*dt; const double* sat = &s[np*cell]; props.relperm(1, sat, &cell, &kr_cell[0], 0); props.viscosity(1, &press[cell], &temp[cell], &z[np*cell], &cell, &visc[0], 0); props.matrix(1, &press[cell], &temp[cell], &z[np*cell], &cell, &A[0], 0); polyprops.effectiveMobilities(c[cell], cmax[cell], &visc[0], &kr_cell[0], &mob[0]); double totmob = 0.0; for (int p = 0; p < np; ++p) { totmob += mob[p]; } std::fill(prod_surfvol.begin(), prod_surfvol.end(), 0.0); for (int p = 0; p < np; ++p) { prod_resv_phase[p] = (mob[p]/totmob)*flux; for (int q = 0; q < np; ++q) { prod_surfvol[q] += prod_resv_phase[p]*A[q + np*p]; } } for (int p = 0; p < np; ++p) { produced[p] += prod_surfvol[p]; } polyprops.computeMc(c[cell], mc); polyprod += produced[0]*mc; } } }