/* Copyright 2013, 2015 SINTEF ICT, Applied Mathematics. Copyright 2014, 2015 Dr. Blatt - HPC-Simulation-Software & Services Copyright 2014, 2015 Statoil ASA. Copyright 2015 NTNU Copyright 2015 IRIS AS This file is part of the Open Porous Media project (OPM). OPM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OPM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OPM. If not, see . */ #ifndef OPM_BLACKOILMODELBASE_IMPL_HEADER_INCLUDED #define OPM_BLACKOILMODELBASE_IMPL_HEADER_INCLUDED #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include // A debugging utility. #define OPM_AD_DUMP(foo) \ do { \ std::cout << "==========================================\n" \ << #foo ":\n" \ << collapseJacs(foo) << std::endl; \ } while (0) #define OPM_AD_DUMPVAL(foo) \ do { \ std::cout << "==========================================\n" \ << #foo ":\n" \ << foo.value() << std::endl; \ } while (0) #define OPM_AD_DISKVAL(foo) \ do { \ std::ofstream os(#foo); \ os.precision(16); \ os << foo.value() << std::endl; \ } while (0) namespace Opm { typedef AutoDiffBlock ADB; typedef ADB::V V; typedef ADB::M M; typedef Eigen::Array DataBlock; template BlackoilModelBase:: BlackoilModelBase(const ModelParameters& param, const Grid& grid , const BlackoilPropsAdFromDeck& fluid, const DerivedGeology& geo , const RockCompressibility* rock_comp_props, const WellModel& well_model, const NewtonIterationBlackoilInterface& linsolver, std::shared_ptr< const Opm::EclipseState > eclState, std::shared_ptr schedule, std::shared_ptr summary_config, const bool has_disgas, const bool has_vapoil, const bool terminal_output) : grid_ (grid) , fluid_ (fluid) , geo_ (geo) , rock_comp_props_(rock_comp_props) , vfp_properties_( eclState->getTableManager().getVFPInjTables(), eclState->getTableManager().getVFPProdTables()) , linsolver_ (linsolver) , active_(detail::activePhases(fluid.phaseUsage())) , canph_ (detail::active2Canonical(fluid.phaseUsage())) , cells_ (detail::buildAllCells(Opm::AutoDiffGrid::numCells(grid))) , ops_ (grid, geo.nnc()) , has_disgas_(has_disgas) , has_vapoil_(has_vapoil) , param_( param ) , use_threshold_pressure_(false) , sd_ (fluid.numPhases()) , phaseCondition_(AutoDiffGrid::numCells(grid)) , well_model_ (well_model) , isRs_(V::Zero(AutoDiffGrid::numCells(grid))) , isRv_(V::Zero(AutoDiffGrid::numCells(grid))) , isSg_(V::Zero(AutoDiffGrid::numCells(grid))) , residual_ ( { std::vector(fluid.numPhases(), ADB::null()), ADB::null(), ADB::null(), { 1.1169, 1.0031, 0.0031 }, // the default magic numbers false } ) , terminal_output_ (terminal_output) , material_name_(0) , current_relaxation_(1.0) // only one region 0 used, which means average reservoir hydrocarbon conditions in // the field will be calculated. // TODO: more delicate implementation will be required if we want to handle different // FIP regions specified from the well specifications. , rate_converter_(fluid_.phaseUsage(), std::vector(AutoDiffGrid::numCells(grid_),0)) { if (active_[Water]) { material_name_.push_back("Water"); } if (active_[Oil]) { material_name_.push_back("Oil"); } if (active_[Gas]) { material_name_.push_back("Gas"); } assert(numMaterials() == std::accumulate(active_.begin(), active_.end(), 0)); // Due to the material_name_ init above. const double gravity = detail::getGravity(geo_.gravity(), UgGridHelpers::dimensions(grid_)); const V depth = Opm::AutoDiffGrid::cellCentroidsZToEigen(grid_); well_model_.init(&fluid_, &active_, &phaseCondition_, &vfp_properties_, gravity, depth); // TODO: put this for now to avoid modify the following code. // TODO: this code can be fragile. #if HAVE_MPI const Wells* wells_arg = asImpl().well_model_.wellsPointer(); if ( linsolver_.parallelInformation().type() == typeid(ParallelISTLInformation) ) { const ParallelISTLInformation& info = boost::any_cast(linsolver_.parallelInformation()); if ( terminal_output_ ) { // Only rank 0 does print to std::cout if terminal_output is enabled terminal_output_ = (info.communicator().rank()==0); } int local_number_of_wells = localWellsActive() ? wells().number_of_wells : 0; int global_number_of_wells = info.communicator().sum(local_number_of_wells); const bool wells_active = ( wells_arg && global_number_of_wells > 0 ); wellModel().setWellsActive(wells_active); // Compute the global number of cells std::vector v( Opm::AutoDiffGrid::numCells(grid_), 1); global_nc_ = 0; info.computeReduction(v, Opm::Reduction::makeGlobalSumFunctor(), global_nc_); }else #endif { wellModel().setWellsActive( localWellsActive() ); global_nc_ = Opm::AutoDiffGrid::numCells(grid_); } } template bool BlackoilModelBase:: isParallel() const { #if HAVE_MPI if ( linsolver_.parallelInformation().type() != typeid(ParallelISTLInformation) ) { return false; } else { const auto& comm =boost::any_cast(linsolver_.parallelInformation()).communicator(); return comm.size() > 1; } #else return false; #endif } template void BlackoilModelBase:: prepareStep(const SimulatorTimerInterface& timer, const ReservoirState& reservoir_state, const WellState& /* well_state */) { const double dt = timer.currentStepLength(); pvdt_ = geo_.poreVolume() / dt; if (active_[Gas]) { updatePrimalVariableFromState(reservoir_state); } } template template SimulatorReport BlackoilModelBase:: nonlinearIteration(const int iteration, const SimulatorTimerInterface& timer, NonlinearSolverType& nonlinear_solver, ReservoirState& reservoir_state, WellState& well_state) { SimulatorReport report; Dune::Timer perfTimer; perfTimer.start(); const double dt = timer.currentStepLength(); if (iteration == 0) { // For each iteration we store in a vector the norms of the residual of // the mass balance for each active phase, the well flux and the well equations. residual_norms_history_.clear(); current_relaxation_ = 1.0; dx_old_ = V::Zero(sizeNonLinear()); } try { report += asImpl().assemble(reservoir_state, well_state, iteration == 0); report.assemble_time += perfTimer.stop(); } catch (...) { report.assemble_time += perfTimer.stop(); throw; } report.total_linearizations = 1; perfTimer.reset(); perfTimer.start(); report.converged = asImpl().getConvergence(timer, iteration); residual_norms_history_.push_back(asImpl().computeResidualNorms()); report.update_time += perfTimer.stop(); const bool must_solve = (iteration < nonlinear_solver.minIter()) || (!report.converged); if (must_solve) { perfTimer.reset(); perfTimer.start(); report.total_newton_iterations = 1; // enable single precision for solvers when dt is smaller then maximal time step for single precision residual_.singlePrecision = ( dt < param_.maxSinglePrecisionTimeStep_ ); // Compute the nonlinear update. V dx; try { dx = asImpl().solveJacobianSystem(); report.linear_solve_time += perfTimer.stop(); report.total_linear_iterations += linearIterationsLastSolve(); } catch (...) { report.linear_solve_time += perfTimer.stop(); report.total_linear_iterations += linearIterationsLastSolve(); throw; } perfTimer.reset(); perfTimer.start(); if (param_.use_update_stabilization_) { // Stabilize the nonlinear update. bool isOscillate = false; bool isStagnate = false; nonlinear_solver.detectOscillations(residual_norms_history_, iteration, isOscillate, isStagnate); if (isOscillate) { current_relaxation_ -= nonlinear_solver.relaxIncrement(); current_relaxation_ = std::max(current_relaxation_, nonlinear_solver.relaxMax()); if (terminalOutputEnabled()) { std::string msg = " Oscillating behavior detected: Relaxation set to " + std::to_string(current_relaxation_); OpmLog::info(msg); } } nonlinear_solver.stabilizeNonlinearUpdate(dx, dx_old_, current_relaxation_); } // Apply the update, applying model-dependent // limitations and chopping of the update. asImpl().updateState(dx, reservoir_state, well_state); report.update_time += perfTimer.stop(); } return report; } template void BlackoilModelBase:: afterStep(const SimulatorTimerInterface& /*timer*/, ReservoirState& /* reservoir_state */, WellState& /* well_state */) { // Does nothing in this model. } template int BlackoilModelBase:: sizeNonLinear() const { return residual_.sizeNonLinear(); } template int BlackoilModelBase:: linearIterationsLastSolve() const { return linsolver_.iterations(); } template bool BlackoilModelBase:: terminalOutputEnabled() const { return terminal_output_; } template int BlackoilModelBase:: numPhases() const { return fluid_.numPhases(); } template int BlackoilModelBase:: numMaterials() const { return material_name_.size(); } template const std::string& BlackoilModelBase:: materialName(int material_index) const { assert(material_index < numMaterials()); return material_name_[material_index]; } template void BlackoilModelBase:: setThresholdPressures(const std::vector& threshold_pressures) { const int num_faces = AutoDiffGrid::numFaces(grid_); const int num_nnc = geo_.nnc().numNNC(); const int num_connections = num_faces + num_nnc; if (int(threshold_pressures.size()) != num_connections) { OPM_THROW(std::runtime_error, "Illegal size of threshold_pressures input ( " << threshold_pressures.size() << " ), must be equal to number of faces + nncs ( " << num_faces << " + " << num_nnc << " )."); } use_threshold_pressure_ = true; // Map to interior faces. const int num_ifaces = ops_.internal_faces.size(); threshold_pressures_by_connection_.resize(num_ifaces + num_nnc); for (int ii = 0; ii < num_ifaces; ++ii) { threshold_pressures_by_connection_[ii] = threshold_pressures[ops_.internal_faces[ii]]; } // Handle NNCs // Note: the nnc threshold pressures is appended after the face threshold pressures for (int ii = 0; ii < num_nnc; ++ii) { threshold_pressures_by_connection_[ii + num_ifaces] = threshold_pressures[ii + num_faces]; } } template BlackoilModelBase:: ReservoirResidualQuant::ReservoirResidualQuant() : accum(2, ADB::null()) , mflux( ADB::null()) , b ( ADB::null()) , mu ( ADB::null()) , rho ( ADB::null()) , kr ( ADB::null()) , dh ( ADB::null()) , mob ( ADB::null()) { } template BlackoilModelBase:: SimulatorData::SimulatorData(int num_phases) : rq(num_phases) , rsSat(ADB::null()) , rvSat(ADB::null()) , soMax() , Pb() , Pd() , krnswdc_ow() , krnswdc_go() , pcswmdc_ow() , pcswmdc_go() , fip() { } template void BlackoilModelBase:: makeConstantState(SolutionState& state) const { // HACK: throw away the derivatives. this may not be the most // performant way to do things, but it will make the state // automatically consistent with variableState() (and doing // things automatically is all the rage in this module ;) state.pressure = ADB::constant(state.pressure.value()); state.temperature = ADB::constant(state.temperature.value()); state.rs = ADB::constant(state.rs.value()); state.rv = ADB::constant(state.rv.value()); const int num_phases = state.saturation.size(); for (int phaseIdx = 0; phaseIdx < num_phases; ++ phaseIdx) { state.saturation[phaseIdx] = ADB::constant(state.saturation[phaseIdx].value()); } state.qs = ADB::constant(state.qs.value()); state.bhp = ADB::constant(state.bhp.value()); assert(state.canonical_phase_pressures.size() == static_cast(Opm::BlackoilPhases::MaxNumPhases)); for (int canphase = 0; canphase < Opm::BlackoilPhases::MaxNumPhases; ++canphase) { ADB& pp = state.canonical_phase_pressures[canphase]; pp = ADB::constant(pp.value()); } } template typename BlackoilModelBase::SolutionState BlackoilModelBase:: variableState(const ReservoirState& x, const WellState& xw) const { std::vector vars0 = asImpl().variableStateInitials(x, xw); std::vector vars = ADB::variables(vars0); return asImpl().variableStateExtractVars(x, asImpl().variableStateIndices(), vars); } template std::vector BlackoilModelBase:: variableStateInitials(const ReservoirState& x, const WellState& xw) const { assert(active_[ Oil ]); const int np = x.numPhases(); std::vector vars0; // p, Sw and Rs, Rv or Sg is used as primary depending on solution conditions // and bhp and Q for the wells vars0.reserve(np + 1); variableReservoirStateInitials(x, vars0); asImpl().wellModel().variableWellStateInitials(xw, vars0); return vars0; } template void BlackoilModelBase:: variableReservoirStateInitials(const ReservoirState& x, std::vector& vars0) const { using namespace Opm::AutoDiffGrid; const int nc = numCells(grid_); const int np = x.numPhases(); // Initial pressure. assert (not x.pressure().empty()); const V p = Eigen::Map(& x.pressure()[0], nc, 1); vars0.push_back(p); // Initial saturation. assert (not x.saturation().empty()); const DataBlock s = Eigen::Map(& x.saturation()[0], nc, np); const Opm::PhaseUsage pu = fluid_.phaseUsage(); // We do not handle a Water/Gas situation correctly, guard against it. assert (active_[ Oil]); if (active_[ Water ]) { const V sw = s.col(pu.phase_pos[ Water ]); vars0.push_back(sw); } if (active_[ Gas ]) { // define new primary variable xvar depending on solution condition V xvar(nc); const V sg = s.col(pu.phase_pos[ Gas ]); const V rs = Eigen::Map(& x.gasoilratio()[0], x.gasoilratio().size()); const V rv = Eigen::Map(& x.rv()[0], x.rv().size()); xvar = isRs_*rs + isRv_*rv + isSg_*sg; vars0.push_back(xvar); } } template std::vector BlackoilModelBase:: variableStateIndices() const { assert(active_[Oil]); std::vector indices(5, -1); int next = 0; indices[Pressure] = next++; if (active_[Water]) { indices[Sw] = next++; } if (active_[Gas]) { indices[Xvar] = next++; } asImpl().wellModel().variableStateWellIndices(indices, next); assert(next == fluid_.numPhases() + 2); return indices; } template typename BlackoilModelBase::SolutionState BlackoilModelBase:: variableStateExtractVars(const ReservoirState& x, const std::vector& indices, std::vector& vars) const { //using namespace Opm::AutoDiffGrid; const int nc = Opm::AutoDiffGrid::numCells(grid_); const Opm::PhaseUsage pu = fluid_.phaseUsage(); SolutionState state(fluid_.numPhases()); // Pressure. state.pressure = std::move(vars[indices[Pressure]]); // Temperature cannot be a variable at this time (only constant). const V temp = Eigen::Map(& x.temperature()[0], x.temperature().size()); state.temperature = ADB::constant(temp); // Saturations { ADB so = ADB::constant(V::Ones(nc, 1)); if (active_[ Water ]) { state.saturation[pu.phase_pos[ Water ]] = std::move(vars[indices[Sw]]); const ADB& sw = state.saturation[pu.phase_pos[ Water ]]; so -= sw; } if (active_[ Gas ]) { // Define Sg Rs and Rv in terms of xvar. // Xvar is only defined if gas phase is active const ADB& xvar = vars[indices[Xvar]]; ADB& sg = state.saturation[ pu.phase_pos[ Gas ] ]; sg = isSg_*xvar + isRv_*so; so -= sg; //Compute the phase pressures before computing RS/RV { const ADB& sw = (active_[ Water ] ? state.saturation[ pu.phase_pos[ Water ] ] : ADB::null()); state.canonical_phase_pressures = computePressures(state.pressure, sw, so, sg); } if (active_[ Oil ]) { // RS and RV is only defined if both oil and gas phase are active. sd_.rsSat = fluidRsSat(state.canonical_phase_pressures[ Oil ], so , cells_); if (has_disgas_) { state.rs = (1-isRs_)*sd_.rsSat + isRs_*xvar; } else { state.rs = sd_.rsSat; } sd_.rvSat = fluidRvSat(state.canonical_phase_pressures[ Gas ], so , cells_); if (has_vapoil_) { state.rv = (1-isRv_)*sd_.rvSat + isRv_*xvar; } else { state.rv = sd_.rvSat; } sd_.soMax = fluid_.satOilMax(); fluid_.getGasOilHystParams(sd_.krnswdc_go, sd_.pcswmdc_go, cells_); fluid_.getOilWaterHystParams(sd_.krnswdc_ow, sd_.pcswmdc_ow, cells_); sd_.Pb = fluid_.bubblePointPressure(cells_, state.temperature.value(), state.rs.value()); sd_.Pd = fluid_.dewPointPressure(cells_, state.temperature.value(), state.rv.value()); } } else { // Compute phase pressures also if gas phase is not active const ADB& sw = (active_[ Water ] ? state.saturation[ pu.phase_pos[ Water ] ] : ADB::null()); const ADB& sg = ADB::null(); state.canonical_phase_pressures = computePressures(state.pressure, sw, so, sg); } if (active_[ Oil ]) { // Note that so is never a primary variable. state.saturation[pu.phase_pos[ Oil ]] = std::move(so); } } // wells asImpl().wellModel().variableStateExtractWellsVars(indices, vars, state); return state; } template void BlackoilModelBase:: computeAccum(const SolutionState& state, const int aix ) { const Opm::PhaseUsage& pu = fluid_.phaseUsage(); const ADB& press = state.pressure; const ADB& temp = state.temperature; const std::vector& sat = state.saturation; const ADB& rs = state.rs; const ADB& rv = state.rv; const std::vector cond = phaseCondition(); const ADB pv_mult = poroMult(press); const int maxnp = Opm::BlackoilPhases::MaxNumPhases; for (int phase = 0; phase < maxnp; ++phase) { if (active_[ phase ]) { const int pos = pu.phase_pos[ phase ]; sd_.rq[pos].b = asImpl().fluidReciprocFVF(phase, state.canonical_phase_pressures[phase], temp, rs, rv, cond); sd_.rq[pos].accum[aix] = pv_mult * sd_.rq[pos].b * sat[pos]; // OPM_AD_DUMP(sd_.rq[pos].b); // OPM_AD_DUMP(sd_.rq[pos].accum[aix]); } } if (active_[ Oil ] && active_[ Gas ]) { // Account for gas dissolved in oil and vaporized oil const int po = pu.phase_pos[ Oil ]; const int pg = pu.phase_pos[ Gas ]; // Temporary copy to avoid contribution of dissolved gas in the vaporized oil // when both dissolved gas and vaporized oil are present. const ADB accum_gas_copy =sd_.rq[pg].accum[aix]; sd_.rq[pg].accum[aix] += state.rs * sd_.rq[po].accum[aix]; sd_.rq[po].accum[aix] += state.rv * accum_gas_copy; // OPM_AD_DUMP(sd_.rq[pg].accum[aix]); } } template SimulatorReport BlackoilModelBase:: assemble(const ReservoirState& reservoir_state, WellState& well_state, const bool initial_assembly) { using namespace Opm::AutoDiffGrid; SimulatorReport report; // If we have VFP tables, we need the well connection // pressures for the "simple" hydrostatic correction // between well depth and vfp table depth. if (isVFPActive()) { SolutionState state = asImpl().variableState(reservoir_state, well_state); SolutionState state0 = state; asImpl().makeConstantState(state0); asImpl().wellModel().computeWellConnectionPressures(state0, well_state); } // set up the guide rate and group control if (asImpl().wellModel().wellCollection()->groupControlActive() && initial_assembly) { setupGroupControl(reservoir_state, well_state); } // Possibly switch well controls and updating well state to // get reasonable initial conditions for the wells asImpl().wellModel().updateWellControls(well_state); if (asImpl().wellModel().wellCollection()->groupControlActive()) { // enforce VREP control when necessary. applyVREPGroupControl(reservoir_state, well_state); asImpl().wellModel().wellCollection()->updateWellTargets(well_state.wellRates()); } // Create the primary variables. SolutionState state = asImpl().variableState(reservoir_state, well_state); if (initial_assembly) { // Create the (constant, derivativeless) initial state. SolutionState state0 = state; asImpl().makeConstantState(state0); // Compute initial accumulation contributions // and well connection pressures. asImpl().computeAccum(state0, 0); asImpl().wellModel().computeWellConnectionPressures(state0, well_state); } // OPM_AD_DISKVAL(state.pressure); // OPM_AD_DISKVAL(state.saturation[0]); // OPM_AD_DISKVAL(state.saturation[1]); // OPM_AD_DISKVAL(state.saturation[2]); // OPM_AD_DISKVAL(state.rs); // OPM_AD_DISKVAL(state.rv); // OPM_AD_DISKVAL(state.qs); // OPM_AD_DISKVAL(state.bhp); // -------- Mass balance equations -------- asImpl().assembleMassBalanceEq(state); // -------- Well equations ---------- if ( ! wellsActive() ) { return report; } std::vector mob_perfcells; std::vector b_perfcells; asImpl().wellModel().extractWellPerfProperties(state, sd_.rq, mob_perfcells, b_perfcells); if (param_.solve_welleq_initially_ && initial_assembly) { // solve the well equations as a pre-processing step report += asImpl().solveWellEq(mob_perfcells, b_perfcells, reservoir_state, state, well_state); } V aliveWells; std::vector cq_s; asImpl().wellModel().computeWellFlux(state, mob_perfcells, b_perfcells, aliveWells, cq_s); asImpl().wellModel().updatePerfPhaseRatesAndPressures(cq_s, state, well_state); asImpl().wellModel().addWellFluxEq(cq_s, state, residual_); asImpl().addWellContributionToMassBalanceEq(cq_s, state, well_state); asImpl().wellModel().addWellControlEq(state, well_state, aliveWells, residual_); return report; } template void BlackoilModelBase:: assembleMassBalanceEq(const SolutionState& state) { // Compute b_p and the accumulation term b_p*s_p for each phase, // except gas. For gas, we compute b_g*s_g + Rs*b_o*s_o. // These quantities are stored in sd_.rq[phase].accum[1]. // The corresponding accumulation terms from the start of // the timestep (b^0_p*s^0_p etc.) were already computed // on the initial call to assemble() and stored in sd_.rq[phase].accum[0]. asImpl().computeAccum(state, 1); // Set up the common parts of the mass balance equations // for each active phase. const V transi = subset(geo_.transmissibility(), ops_.internal_faces); const V trans_nnc = ops_.nnc_trans; V trans_all(transi.size() + trans_nnc.size()); trans_all << transi, trans_nnc; { const std::vector kr = asImpl().computeRelPerm(state); for (int phaseIdx = 0; phaseIdx < fluid_.numPhases(); ++phaseIdx) { sd_.rq[phaseIdx].kr = kr[canph_[phaseIdx]]; } } #pragma omp parallel for schedule(static) for (int phaseIdx = 0; phaseIdx < fluid_.numPhases(); ++phaseIdx) { const std::vector& cond = phaseCondition(); sd_.rq[phaseIdx].mu = asImpl().fluidViscosity(canph_[phaseIdx], state.canonical_phase_pressures[canph_[phaseIdx]], state.temperature, state.rs, state.rv, cond); sd_.rq[phaseIdx].rho = asImpl().fluidDensity(canph_[phaseIdx], sd_.rq[phaseIdx].b, state.rs, state.rv); asImpl().computeMassFlux(phaseIdx, trans_all, sd_.rq[phaseIdx].kr, sd_.rq[phaseIdx].mu, sd_.rq[phaseIdx].rho, state.canonical_phase_pressures[canph_[phaseIdx]], state); residual_.material_balance_eq[ phaseIdx ] = pvdt_ * (sd_.rq[phaseIdx].accum[1] - sd_.rq[phaseIdx].accum[0]) + ops_.div*sd_.rq[phaseIdx].mflux; } // -------- Extra (optional) rs and rv contributions to the mass balance equations -------- // Add the extra (flux) terms to the mass balance equations // From gas dissolved in the oil phase (rs) and oil vaporized in the gas phase (rv) // The extra terms in the accumulation part of the equation are already handled. if (active_[ Oil ] && active_[ Gas ]) { const int po = fluid_.phaseUsage().phase_pos[ Oil ]; const int pg = fluid_.phaseUsage().phase_pos[ Gas ]; const UpwindSelector upwindOil(grid_, ops_, sd_.rq[po].dh.value()); const ADB rs_face = upwindOil.select(state.rs); const UpwindSelector upwindGas(grid_, ops_, sd_.rq[pg].dh.value()); const ADB rv_face = upwindGas.select(state.rv); residual_.material_balance_eq[ pg ] += ops_.div * (rs_face * sd_.rq[po].mflux); residual_.material_balance_eq[ po ] += ops_.div * (rv_face * sd_.rq[pg].mflux); // OPM_AD_DUMP(residual_.material_balance_eq[ Gas ]); } if (param_.update_equations_scaling_) { asImpl().updateEquationsScaling(); } } template void BlackoilModelBase:: updateEquationsScaling() { ADB::V B; const Opm::PhaseUsage& pu = fluid_.phaseUsage(); for ( int idx=0; idx(linsolver_.parallelInformation()); double B_global_sum = 0; real_info.computeReduction(B, Reduction::makeGlobalSumFunctor(), B_global_sum); residual_.matbalscale[idx] = B_global_sum / global_nc_; } else #endif { residual_.matbalscale[idx] = B.mean(); } } } } template void BlackoilModelBase:: addWellContributionToMassBalanceEq(const std::vector& cq_s, const SolutionState&, const WellState&) { if ( !asImpl().localWellsActive() ) { // If there are no wells in the subdomain of the proces then // cq_s has zero size and will cause a segmentation fault below. return; } // Add well contributions to mass balance equations const int nc = Opm::AutoDiffGrid::numCells(grid_); const int np = asImpl().numPhases(); const V& efficiency_factors = wellModel().wellPerfEfficiencyFactors(); for (int phase = 0; phase < np; ++phase) { residual_.material_balance_eq[phase] -= superset(efficiency_factors * cq_s[phase], wellModel().wellOps().well_cells, nc); } } template bool BlackoilModelBase:: isVFPActive() const { if( ! localWellsActive() ) { return false; } if ( vfp_properties_.getProd()->empty() && vfp_properties_.getInj()->empty() ) { return false; } const int nw = wells().number_of_wells; //Loop over all wells for (int w = 0; w < nw; ++w) { const WellControls* wc = wells().ctrls[w]; const int nwc = well_controls_get_num(wc); //Loop over all controls for (int c=0; c < nwc; ++c) { const WellControlType ctrl_type = well_controls_iget_type(wc, c); if (ctrl_type == THP) { return true; } } } return false; } template SimulatorReport BlackoilModelBase:: solveWellEq(const std::vector& mob_perfcells, const std::vector& b_perfcells, const ReservoirState& reservoir_state, SolutionState& state, WellState& well_state) { V aliveWells; const int np = wells().number_of_phases; std::vector cq_s(np, ADB::null()); std::vector indices = asImpl().wellModel().variableWellStateIndices(); SolutionState state0 = state; WellState well_state0 = well_state; asImpl().makeConstantState(state0); std::vector mob_perfcells_const(np, ADB::null()); std::vector b_perfcells_const(np, ADB::null()); if (asImpl().localWellsActive() ){ // If there are non well in the sudomain of the process // thene mob_perfcells_const and b_perfcells_const would be empty for (int phase = 0; phase < np; ++phase) { mob_perfcells_const[phase] = ADB::constant(mob_perfcells[phase].value()); b_perfcells_const[phase] = ADB::constant(b_perfcells[phase].value()); } } int it = 0; bool converged; do { // bhp and Q for the wells std::vector vars0; vars0.reserve(2); asImpl().wellModel().variableWellStateInitials(well_state, vars0); std::vector vars = ADB::variables(vars0); SolutionState wellSolutionState = state0; asImpl().wellModel().variableStateExtractWellsVars(indices, vars, wellSolutionState); asImpl().wellModel().computeWellFlux(wellSolutionState, mob_perfcells_const, b_perfcells_const, aliveWells, cq_s); asImpl().wellModel().updatePerfPhaseRatesAndPressures(cq_s, wellSolutionState, well_state); asImpl().wellModel().addWellFluxEq(cq_s, wellSolutionState, residual_); asImpl().wellModel().addWellControlEq(wellSolutionState, well_state, aliveWells, residual_); converged = getWellConvergence(it); if (converged) { break; } ++it; if( localWellsActive() ) { std::vector eqs; eqs.reserve(2); eqs.push_back(residual_.well_flux_eq); eqs.push_back(residual_.well_eq); ADB total_residual = vertcatCollapseJacs(eqs); const std::vector& Jn = total_residual.derivative(); typedef Eigen::SparseMatrix Sp; Sp Jn0; Jn[0].toSparse(Jn0); const Eigen::SparseLU< Sp > solver(Jn0); ADB::V total_residual_v = total_residual.value(); const Eigen::VectorXd& dx = solver.solve(total_residual_v.matrix()); assert(dx.size() == total_residual_v.size()); asImpl().wellModel().updateWellState(dx.array(), dbhpMaxRel(), well_state); } // We have to update the well controls regardless whether there are local // wells active or not as parallel logging will take place that needs to // communicate with all processes. asImpl().wellModel().updateWellControls(well_state); if (asImpl().wellModel().wellCollection()->groupControlActive()) { // Enforce the VREP control applyVREPGroupControl(reservoir_state, well_state); asImpl().wellModel().wellCollection()->updateWellTargets(well_state.wellRates()); } } while (it < 15); if (converged) { if (terminalOutputEnabled()) { OpmLog::note("well converged iter: " + std::to_string(it)); } const int nw = wells().number_of_wells; { // We will set the bhp primary variable to the new ones, // but we do not change the derivatives here. ADB::V new_bhp = Eigen::Map(well_state.bhp().data(), nw); // Avoiding the copy below would require a value setter method // in AutoDiffBlock. std::vector old_derivs = state.bhp.derivative(); state.bhp = ADB::function(std::move(new_bhp), std::move(old_derivs)); } { // Need to reshuffle well rates, from phase running fastest // to wells running fastest. // The transpose() below switches the ordering. const DataBlock wrates = Eigen::Map(well_state.wellRates().data(), nw, np).transpose(); ADB::V new_qs = Eigen::Map(wrates.data(), nw*np); std::vector old_derivs = state.qs.derivative(); state.qs = ADB::function(std::move(new_qs), std::move(old_derivs)); } asImpl().computeWellConnectionPressures(state, well_state); } if (!converged) { well_state = well_state0; } SimulatorReport report; report.total_well_iterations = it; report.converged = converged; return report; } template V BlackoilModelBase:: solveJacobianSystem() const { return linsolver_.computeNewtonIncrement(residual_); } template void BlackoilModelBase:: updateState(const V& dx, ReservoirState& reservoir_state, WellState& well_state) { using namespace Opm::AutoDiffGrid; const int np = fluid_.numPhases(); const int nc = numCells(grid_); const V null; assert(null.size() == 0); const V zero = V::Zero(nc); const V ones = V::Constant(nc,1.0); // Extract parts of dx corresponding to each part. const V dp = subset(dx, Span(nc)); int varstart = nc; const V dsw = active_[Water] ? subset(dx, Span(nc, 1, varstart)) : null; varstart += dsw.size(); const V dxvar = active_[Gas] ? subset(dx, Span(nc, 1, varstart)): null; varstart += dxvar.size(); // Extract well parts np phase rates + bhp const V dwells = subset(dx, Span(asImpl().wellModel().numWellVars(), 1, varstart)); varstart += dwells.size(); assert(varstart == dx.size()); // Pressure update. const double dpmaxrel = dpMaxRel(); const V p_old = Eigen::Map(&reservoir_state.pressure()[0], nc, 1); const V absdpmax = dpmaxrel*p_old.abs(); const V dp_limited = sign(dp) * dp.abs().min(absdpmax); const V p = (p_old - dp_limited).max(zero); std::copy(&p[0], &p[0] + nc, reservoir_state.pressure().begin()); // Saturation updates. const Opm::PhaseUsage& pu = fluid_.phaseUsage(); const DataBlock s_old = Eigen::Map(& reservoir_state.saturation()[0], nc, np); const double dsmax = dsMax(); V so; V sw; V sg; { V maxVal = zero; V dso = zero; if (active_[Water]){ maxVal = dsw.abs().max(maxVal); dso = dso - dsw; } V dsg; if (active_[Gas]){ dsg = isSg_ * dxvar - isRv_ * dsw; maxVal = dsg.abs().max(maxVal); dso = dso - dsg; } maxVal = dso.abs().max(maxVal); V step = dsmax/maxVal; step = step.min(1.); if (active_[Water]) { const int pos = pu.phase_pos[ Water ]; const V sw_old = s_old.col(pos); sw = sw_old - step * dsw; } if (active_[Gas]) { const int pos = pu.phase_pos[ Gas ]; const V sg_old = s_old.col(pos); sg = sg_old - step * dsg; } assert(active_[Oil]); const int pos = pu.phase_pos[ Oil ]; const V so_old = s_old.col(pos); so = so_old - step * dso; } if (active_[Gas]) { auto ixg = sg < 0; for (int c = 0; c < nc; ++c) { if (ixg[c]) { if (active_[Water]) { sw[c] = sw[c] / (1-sg[c]); } so[c] = so[c] / (1-sg[c]); sg[c] = 0; } } } if (active_[Oil]) { auto ixo = so < 0; for (int c = 0; c < nc; ++c) { if (ixo[c]) { if (active_[Water]) { sw[c] = sw[c] / (1-so[c]); } if (active_[Gas]) { sg[c] = sg[c] / (1-so[c]); } so[c] = 0; } } } if (active_[Water]) { auto ixw = sw < 0; for (int c = 0; c < nc; ++c) { if (ixw[c]) { so[c] = so[c] / (1-sw[c]); if (active_[Gas]) { sg[c] = sg[c] / (1-sw[c]); } sw[c] = 0; } } } // Update rs and rv const double drmaxrel = drMaxRel(); V rs; if (has_disgas_) { const V rs_old = Eigen::Map(&reservoir_state.gasoilratio()[0], nc); const V drs = isRs_ * dxvar; const V drs_limited = sign(drs) * drs.abs().min( (rs_old.abs()*drmaxrel).max( ones*1.0)); rs = rs_old - drs_limited; rs = rs.max(zero); } V rv; if (has_vapoil_) { const V rv_old = Eigen::Map(&reservoir_state.rv()[0], nc); const V drv = isRv_ * dxvar; const V drv_limited = sign(drv) * drv.abs().min( (rv_old.abs()*drmaxrel).max( ones*1e-3)); rv = rv_old - drv_limited; rv = rv.max(zero); } // Sg is used as primal variable for water only cells. const double epsilon = std::sqrt(std::numeric_limits::epsilon()); auto watOnly = sw > (1 - epsilon); // phase translation sg <-> rs std::vector& hydroCarbonState = reservoir_state.hydroCarbonState(); std::fill(hydroCarbonState.begin(), hydroCarbonState.end(), HydroCarbonState::GasAndOil); if (has_disgas_) { const V rsSat0 = fluidRsSat(p_old, s_old.col(pu.phase_pos[Oil]), cells_); const V rsSat = fluidRsSat(p, so, cells_); sd_.rsSat = ADB::constant(rsSat); // The obvious case auto hasGas = (sg > 0 && isRs_ == 0); // Set oil saturated if previous rs is sufficiently large const V rs_old = Eigen::Map(&reservoir_state.gasoilratio()[0], nc); auto gasVaporized = ( (rs > rsSat * (1+epsilon) && isRs_ == 1 ) && (rs_old > rsSat0 * (1-epsilon)) ); auto useSg = watOnly || hasGas || gasVaporized; for (int c = 0; c < nc; ++c) { if (useSg[c]) { rs[c] = rsSat[c]; if (watOnly[c]) { so[c] = 0; sg[c] = 0; rs[c] = 0; } } else { hydroCarbonState[c] = HydroCarbonState::OilOnly; } } //rs = rs.min(rsSat); } // phase transitions so <-> rv if (has_vapoil_) { // The gas pressure is needed for the rvSat calculations const V gaspress_old = computeGasPressure(p_old, s_old.col(Water), s_old.col(Oil), s_old.col(Gas)); const V gaspress = computeGasPressure(p, sw, so, sg); const V rvSat0 = fluidRvSat(gaspress_old, s_old.col(pu.phase_pos[Oil]), cells_); const V rvSat = fluidRvSat(gaspress, so, cells_); sd_.rvSat = ADB::constant(rvSat); // The obvious case auto hasOil = (so > 0 && isRv_ == 0); // Set oil saturated if previous rv is sufficiently large const V rv_old = Eigen::Map(&reservoir_state.rv()[0], nc); auto oilCondensed = ( (rv > rvSat * (1+epsilon) && isRv_ == 1) && (rv_old > rvSat0 * (1-epsilon)) ); auto useSg = watOnly || hasOil || oilCondensed; for (int c = 0; c < nc; ++c) { if (useSg[c]) { rv[c] = rvSat[c]; if (watOnly[c]) { so[c] = 0; sg[c] = 0; rv[c] = 0; } } else { hydroCarbonState[c] = HydroCarbonState::GasOnly; } } //rv = rv.min(rvSat); } // Update the reservoir_state if (active_[Water]) { for (int c = 0; c < nc; ++c) { reservoir_state.saturation()[c*np + pu.phase_pos[ Water ]] = sw[c]; } } if (active_[Gas]) { for (int c = 0; c < nc; ++c) { reservoir_state.saturation()[c*np + pu.phase_pos[ Gas ]] = sg[c]; } } if (active_[ Oil ]) { for (int c = 0; c < nc; ++c) { reservoir_state.saturation()[c*np + pu.phase_pos[ Oil ]] = so[c]; } } if (has_disgas_) { std::copy(&rs[0], &rs[0] + nc, reservoir_state.gasoilratio().begin()); } if (has_vapoil_) { std::copy(&rv[0], &rv[0] + nc, reservoir_state.rv().begin()); } asImpl().wellModel().updateWellState(dwells, dbhpMaxRel(), well_state); // Update phase conditions used for property calculations. updatePhaseCondFromPrimalVariable(reservoir_state); } template std::vector BlackoilModelBase:: computeRelPerm(const SolutionState& state) const { using namespace Opm::AutoDiffGrid; const int nc = numCells(grid_); const ADB zero = ADB::constant(V::Zero(nc)); const Opm::PhaseUsage& pu = fluid_.phaseUsage(); const ADB& sw = (active_[ Water ] ? state.saturation[ pu.phase_pos[ Water ] ] : zero); const ADB& so = (active_[ Oil ] ? state.saturation[ pu.phase_pos[ Oil ] ] : zero); const ADB& sg = (active_[ Gas ] ? state.saturation[ pu.phase_pos[ Gas ] ] : zero); return fluid_.relperm(sw, so, sg, cells_); } template std::vector BlackoilModelBase:: computePressures(const ADB& po, const ADB& sw, const ADB& so, const ADB& sg) const { // convert the pressure offsets to the capillary pressures std::vector pressure = fluid_.capPress(sw, so, sg, cells_); for (int phaseIdx = 0; phaseIdx < BlackoilPhases::MaxNumPhases; ++phaseIdx) { // The reference pressure is always the liquid phase (oil) pressure. if (phaseIdx == BlackoilPhases::Liquid) continue; if (active_[phaseIdx]) { pressure[phaseIdx] = pressure[phaseIdx] - pressure[BlackoilPhases::Liquid]; } } // Since pcow = po - pw, but pcog = pg - po, // we have // pw = po - pcow // pg = po + pcgo // This is an unfortunate inconsistency, but a convention we must handle. for (int phaseIdx = 0; phaseIdx < BlackoilPhases::MaxNumPhases; ++phaseIdx) { if (active_[phaseIdx]) { if (phaseIdx == BlackoilPhases::Aqua) { pressure[phaseIdx] = po - pressure[phaseIdx]; } else { pressure[phaseIdx] += po; } } } return pressure; } template V BlackoilModelBase:: computeGasPressure(const V& po, const V& sw, const V& so, const V& sg) const { assert (active_[Gas]); std::vector cp = fluid_.capPress(ADB::constant(sw), ADB::constant(so), ADB::constant(sg), cells_); return cp[Gas].value() + po; } template void BlackoilModelBase:: computeMassFlux(const int actph , const V& transi, const ADB& kr , const ADB& mu , const ADB& rho , const ADB& phasePressure, const SolutionState& state) { // Compute and store mobilities. const ADB tr_mult = transMult(state.pressure); sd_.rq[ actph ].mob = tr_mult * kr / mu; // Compute head differentials. Gravity potential is done using the face average as in eclipse and MRST. const ADB rhoavg = ops_.caver * rho; sd_.rq[ actph ].dh = ops_.ngrad * phasePressure - geo_.gravity()[2] * (rhoavg * (ops_.ngrad * geo_.z().matrix())); if (use_threshold_pressure_) { applyThresholdPressures(sd_.rq[ actph ].dh); } // Compute phase fluxes with upwinding of formation value factor and mobility. const ADB& b = sd_.rq[ actph ].b; const ADB& mob = sd_.rq[ actph ].mob; const ADB& dh = sd_.rq[ actph ].dh; UpwindSelector upwind(grid_, ops_, dh.value()); sd_.rq[ actph ].mflux = upwind.select(b * mob) * (transi * dh); } template void BlackoilModelBase:: applyThresholdPressures(ADB& dp) { // We support reversible threshold pressures only. // Method: if the potential difference is lower (in absolute // value) than the threshold for any face, then the potential // (and derivatives) is set to zero. If it is above the // threshold, the threshold pressure is subtracted from the // absolute potential (the potential is moved towards zero). // Identify the set of faces where the potential is under the // threshold, that shall have zero flow. Storing the bool // Array as a V (a double Array) with 1 and 0 elements, a // 1 where flow is allowed, a 0 where it is not. const V high_potential = (dp.value().abs() >= threshold_pressures_by_connection_).template cast(); // Create a sparse vector that nullifies the low potential elements. const M keep_high_potential(high_potential.matrix().asDiagonal()); // Find the current sign for the threshold modification const V sign_dp = sign(dp.value()); const V threshold_modification = sign_dp * threshold_pressures_by_connection_; // Modify potential and nullify where appropriate. dp = keep_high_potential * (dp - threshold_modification); } template std::vector BlackoilModelBase:: computeResidualNorms() const { std::vector residualNorms; std::vector::const_iterator massBalanceIt = residual_.material_balance_eq.begin(); const std::vector::const_iterator endMassBalanceIt = residual_.material_balance_eq.end(); for (; massBalanceIt != endMassBalanceIt; ++massBalanceIt) { const double massBalanceResid = detail::infinityNorm( (*massBalanceIt), linsolver_.parallelInformation() ); if (!std::isfinite(massBalanceResid)) { OPM_THROW(Opm::NumericalProblem, "Encountered a non-finite residual"); } residualNorms.push_back(massBalanceResid); } // the following residuals are not used in the oscillation detection now const double wellFluxResid = detail::infinityNormWell( residual_.well_flux_eq, linsolver_.parallelInformation() ); if (!std::isfinite(wellFluxResid)) { OPM_THROW(Opm::NumericalProblem, "Encountered a non-finite residual"); } residualNorms.push_back(wellFluxResid); const double wellResid = detail::infinityNormWell( residual_.well_eq, linsolver_.parallelInformation() ); if (!std::isfinite(wellResid)) { OPM_THROW(Opm::NumericalProblem, "Encountered a non-finite residual"); } residualNorms.push_back(wellResid); return residualNorms; } template double BlackoilModelBase:: relativeChange(const SimulationDataContainer& previous, const SimulationDataContainer& current ) const { std::vector< double > p0 ( previous.pressure() ); std::vector< double > sat0( previous.saturation() ); const std::size_t pSize = p0.size(); const std::size_t satSize = sat0.size(); // compute u^n - u^n+1 for( std::size_t i=0; i 0.0 ) { return stateOld / stateNew ; } else { return 0.0; } } template double BlackoilModelBase:: convergenceReduction(const Eigen::Array& B, const Eigen::Array& tempV, const Eigen::Array& R, std::vector& R_sum, std::vector& maxCoeff, std::vector& B_avg, std::vector& maxNormWell, int nc) const { const int np = asImpl().numPhases(); const int nm = asImpl().numMaterials(); const int nw = residual_.well_flux_eq.size() / np; assert(nw * np == int(residual_.well_flux_eq.size())); // Do the global reductions #if HAVE_MPI if ( linsolver_.parallelInformation().type() == typeid(ParallelISTLInformation) ) { const ParallelISTLInformation& info = boost::any_cast(linsolver_.parallelInformation()); // Compute the global number of cells and porevolume std::vector v(nc, 1); auto nc_and_pv = std::tuple(0, 0.0); auto nc_and_pv_operators = std::make_tuple(Opm::Reduction::makeGlobalSumFunctor(), Opm::Reduction::makeGlobalSumFunctor()); auto nc_and_pv_containers = std::make_tuple(v, geo_.poreVolume()); info.computeReduction(nc_and_pv_containers, nc_and_pv_operators, nc_and_pv); for ( int idx = 0; idx < nm; ++idx ) { auto values = std::tuple(0.0 ,0.0 ,0.0); auto containers = std::make_tuple(B.col(idx), tempV.col(idx), R.col(idx)); auto operators = std::make_tuple(Opm::Reduction::makeGlobalSumFunctor(), Opm::Reduction::makeGlobalMaxFunctor(), Opm::Reduction::makeGlobalSumFunctor()); info.computeReduction(containers, operators, values); B_avg[idx] = std::get<0>(values)/std::get<0>(nc_and_pv); maxCoeff[idx] = std::get<1>(values); R_sum[idx] = std::get<2>(values); assert(nm >= np); if (idx < np) { maxNormWell[idx] = 0.0; for ( int w = 0; w < nw; ++w ) { maxNormWell[idx] = std::max(maxNormWell[idx], std::abs(residual_.well_flux_eq.value()[nw*idx + w])); } } } info.communicator().max(maxNormWell.data(), np); // Compute pore volume return std::get<1>(nc_and_pv); } else #endif { B_avg.resize(nm); maxCoeff.resize(nm); R_sum.resize(nm); maxNormWell.resize(np); for ( int idx = 0; idx < nm; ++idx ) { B_avg[idx] = B.col(idx).sum()/nc; maxCoeff[idx] = tempV.col(idx).maxCoeff(); R_sum[idx] = R.col(idx).sum(); assert(nm >= np); if (idx < np) { maxNormWell[idx] = 0.0; for ( int w = 0; w < nw; ++w ) { maxNormWell[idx] = std::max(maxNormWell[idx], std::abs(residual_.well_flux_eq.value()[nw*idx + w])); } } } // Compute total pore volume return geo_.poreVolume().sum(); } } template bool BlackoilModelBase:: getConvergence(const SimulatorTimerInterface& timer, const int iteration) { const double dt = timer.currentStepLength(); const double tol_mb = param_.tolerance_mb_; const double tol_cnv = param_.tolerance_cnv_; const double tol_wells = param_.tolerance_wells_; const double tol_well_control = param_.tolerance_well_control_; const int nc = Opm::AutoDiffGrid::numCells(grid_); const int np = asImpl().numPhases(); const int nm = asImpl().numMaterials(); assert(int(sd_.rq.size()) == nm); const V& pv = geo_.poreVolume(); std::vector R_sum(nm); std::vector B_avg(nm); std::vector maxCoeff(nm); std::vector maxNormWell(np); Eigen::Array B(nc, nm); Eigen::Array R(nc, nm); Eigen::Array tempV(nc, nm); for ( int idx = 0; idx < nm; ++idx ) { const ADB& tempB = sd_.rq[idx].b; B.col(idx) = 1./tempB.value(); R.col(idx) = residual_.material_balance_eq[idx].value(); tempV.col(idx) = R.col(idx).abs()/pv; } const double pvSum = convergenceReduction(B, tempV, R, R_sum, maxCoeff, B_avg, maxNormWell, nc); std::vector CNV(nm); std::vector mass_balance_residual(nm); std::vector well_flux_residual(np); bool converged_MB = true; bool converged_CNV = true; bool converged_Well = true; // Finish computation for ( int idx = 0; idx < nm; ++idx ) { CNV[idx] = B_avg[idx] * dt * maxCoeff[idx]; mass_balance_residual[idx] = std::abs(B_avg[idx]*R_sum[idx]) * dt / pvSum; converged_MB = converged_MB && (mass_balance_residual[idx] < tol_mb); converged_CNV = converged_CNV && (CNV[idx] < tol_cnv); // Well flux convergence is only for fluid phases, not other materials // in our current implementation. assert(nm >= np); if (idx < np) { well_flux_residual[idx] = B_avg[idx] * maxNormWell[idx]; converged_Well = converged_Well && (well_flux_residual[idx] < tol_wells); } } const double residualWell = detail::infinityNormWell(residual_.well_eq, linsolver_.parallelInformation()); converged_Well = converged_Well && (residualWell < tol_well_control); const bool converged = converged_MB && converged_CNV && converged_Well; // Residual in Pascal can have high values and still be ok. const double maxWellResidualAllowed = 1000.0 * maxResidualAllowed(); if ( terminal_output_ ) { // Only rank 0 does print to std::cout if (iteration == 0) { std::string msg = "Iter"; for (int idx = 0; idx < nm; ++idx) { msg += " MB(" + materialName(idx).substr(0, 3) + ") "; } for (int idx = 0; idx < nm; ++idx) { msg += " CNV(" + materialName(idx).substr(0, 1) + ") "; } for (int idx = 0; idx < np; ++idx) { msg += " W-FLUX(" + materialName(idx).substr(0, 1) + ")"; } msg += " WELL-CONT"; // std::cout << " WELL-CONT "; OpmLog::debug(msg); } std::ostringstream ss; const std::streamsize oprec = ss.precision(3); const std::ios::fmtflags oflags = ss.setf(std::ios::scientific); ss << std::setw(4) << iteration; for (int idx = 0; idx < nm; ++idx) { ss << std::setw(11) << mass_balance_residual[idx]; } for (int idx = 0; idx < nm; ++idx) { ss << std::setw(11) << CNV[idx]; } for (int idx = 0; idx < np; ++idx) { ss << std::setw(11) << well_flux_residual[idx]; } ss << std::setw(11) << residualWell; // std::cout << std::setw(11) << residualWell; ss.precision(oprec); ss.flags(oflags); OpmLog::debug(ss.str()); } for (int idx = 0; idx < nm; ++idx) { if (std::isnan(mass_balance_residual[idx]) || std::isnan(CNV[idx]) || (idx < np && std::isnan(well_flux_residual[idx]))) { const auto msg = std::string("NaN residual for phase ") + materialName(idx); if (terminal_output_) { OpmLog::bug(msg); } OPM_THROW_NOLOG(Opm::NumericalProblem, msg); } if (mass_balance_residual[idx] > maxResidualAllowed() || CNV[idx] > maxResidualAllowed() || (idx < np && well_flux_residual[idx] > maxResidualAllowed())) { const auto msg = std::string("Too large residual for phase ") + materialName(idx); if (terminal_output_) { OpmLog::problem(msg); } OPM_THROW_NOLOG(Opm::NumericalProblem, msg); } } if (std::isnan(residualWell) || residualWell > maxWellResidualAllowed) { const auto msg = std::string("NaN or too large residual for well control equation"); if (terminal_output_) { OpmLog::problem(msg); } OPM_THROW_NOLOG(Opm::NumericalProblem, msg); } return converged; } template bool BlackoilModelBase:: getWellConvergence(const int iteration) { const double tol_wells = param_.tolerance_wells_; const double tol_well_control = param_.tolerance_well_control_; const int nc = Opm::AutoDiffGrid::numCells(grid_); const int np = asImpl().numPhases(); const int nm = asImpl().numMaterials(); const V& pv = geo_.poreVolume(); std::vector R_sum(nm); std::vector B_avg(nm); std::vector maxCoeff(nm); std::vector maxNormWell(np); Eigen::Array B(nc, nm); Eigen::Array R(nc, nm); Eigen::Array tempV(nc, nm); for ( int idx = 0; idx < nm; ++idx ) { const ADB& tempB = sd_.rq[idx].b; B.col(idx) = 1./tempB.value(); R.col(idx) = residual_.material_balance_eq[idx].value(); tempV.col(idx) = R.col(idx).abs()/pv; } convergenceReduction(B, tempV, R, R_sum, maxCoeff, B_avg, maxNormWell, nc); std::vector well_flux_residual(np); bool converged_Well = true; // Finish computation for ( int idx = 0; idx < np; ++idx ) { well_flux_residual[idx] = B_avg[idx] * maxNormWell[idx]; converged_Well = converged_Well && (well_flux_residual[idx] < tol_wells); } const double residualWell = detail::infinityNormWell(residual_.well_eq, linsolver_.parallelInformation()); converged_Well = converged_Well && (residualWell < tol_well_control); const bool converged = converged_Well; // if one of the residuals is NaN, throw exception, so that the solver can be restarted for (int idx = 0; idx < np; ++idx) { if (std::isnan(well_flux_residual[idx])) { const auto msg = std::string("NaN residual for phase ") + materialName(idx); if (terminal_output_) { OpmLog::bug(msg); } OPM_THROW_NOLOG(Opm::NumericalProblem, msg); } if (well_flux_residual[idx] > maxResidualAllowed()) { const auto msg = std::string("Too large residual for phase ") + materialName(idx); if (terminal_output_) { OpmLog::problem(msg); } OPM_THROW_NOLOG(Opm::NumericalProblem, msg); } } if ( terminal_output_ ) { // Only rank 0 does print to std::cout if (iteration == 0) { std::string msg; msg = "Iter"; for (int idx = 0; idx < np; ++idx) { msg += " W-FLUX(" + materialName(idx).substr(0, 1) + ")"; } msg += " WELL-CONT"; OpmLog::debug(msg); } std::ostringstream ss; const std::streamsize oprec = ss.precision(3); const std::ios::fmtflags oflags = ss.setf(std::ios::scientific); ss << std::setw(4) << iteration; for (int idx = 0; idx < np; ++idx) { ss << std::setw(11) << well_flux_residual[idx]; } ss << std::setw(11) << residualWell; ss.precision(oprec); ss.flags(oflags); OpmLog::debug(ss.str()); } return converged; } template ADB BlackoilModelBase:: fluidViscosity(const int phase, const ADB& p , const ADB& temp , const ADB& rs , const ADB& rv , const std::vector& cond) const { switch (phase) { case Water: return fluid_.muWat(p, temp, cells_); case Oil: return fluid_.muOil(p, temp, rs, cond, cells_); case Gas: return fluid_.muGas(p, temp, rv, cond, cells_); default: OPM_THROW(std::runtime_error, "Unknown phase index " << phase); } } template ADB BlackoilModelBase:: fluidReciprocFVF(const int phase, const ADB& p , const ADB& temp , const ADB& rs , const ADB& rv , const std::vector& cond) const { switch (phase) { case Water: return fluid_.bWat(p, temp, cells_); case Oil: return fluid_.bOil(p, temp, rs, cond, cells_); case Gas: return fluid_.bGas(p, temp, rv, cond, cells_); default: OPM_THROW(std::runtime_error, "Unknown phase index " << phase); } } template ADB BlackoilModelBase:: fluidDensity(const int phase, const ADB& b, const ADB& rs, const ADB& rv) const { const V& rhos = fluid_.surfaceDensity(phase, cells_); const Opm::PhaseUsage& pu = fluid_.phaseUsage(); ADB rho = rhos * b; if (phase == Oil && active_[Gas]) { rho += fluid_.surfaceDensity(pu.phase_pos[ Gas ], cells_) * rs * b; } if (phase == Gas && active_[Oil]) { rho += fluid_.surfaceDensity(pu.phase_pos[ Oil ], cells_) * rv * b; } return rho; } template V BlackoilModelBase:: fluidRsSat(const V& p, const V& satOil, const std::vector& cells) const { return fluid_.rsSat(ADB::constant(p), ADB::constant(satOil), cells).value(); } template ADB BlackoilModelBase:: fluidRsSat(const ADB& p, const ADB& satOil, const std::vector& cells) const { return fluid_.rsSat(p, satOil, cells); } template V BlackoilModelBase:: fluidRvSat(const V& p, const V& satOil, const std::vector& cells) const { return fluid_.rvSat(ADB::constant(p), ADB::constant(satOil), cells).value(); } template ADB BlackoilModelBase:: fluidRvSat(const ADB& p, const ADB& satOil, const std::vector& cells) const { return fluid_.rvSat(p, satOil, cells); } template ADB BlackoilModelBase:: poroMult(const ADB& p) const { const int n = p.size(); if (rock_comp_props_ && rock_comp_props_->isActive()) { V pm(n); V dpm(n); #pragma omp parallel for schedule(static) for (int i = 0; i < n; ++i) { pm[i] = rock_comp_props_->poroMult(p.value()[i]); dpm[i] = rock_comp_props_->poroMultDeriv(p.value()[i]); } ADB::M dpm_diag(dpm.matrix().asDiagonal()); const int num_blocks = p.numBlocks(); std::vector jacs(num_blocks); #pragma omp parallel for schedule(dynamic) for (int block = 0; block < num_blocks; ++block) { fastSparseProduct(dpm_diag, p.derivative()[block], jacs[block]); } return ADB::function(std::move(pm), std::move(jacs)); } else { return ADB::constant(V::Constant(n, 1.0)); } } template ADB BlackoilModelBase:: transMult(const ADB& p) const { const int n = p.size(); if (rock_comp_props_ && rock_comp_props_->isActive()) { V tm(n); V dtm(n); #pragma omp parallel for schedule(static) for (int i = 0; i < n; ++i) { tm[i] = rock_comp_props_->transMult(p.value()[i]); dtm[i] = rock_comp_props_->transMultDeriv(p.value()[i]); } ADB::M dtm_diag(dtm.matrix().asDiagonal()); const int num_blocks = p.numBlocks(); std::vector jacs(num_blocks); #pragma omp parallel for schedule(dynamic) for (int block = 0; block < num_blocks; ++block) { fastSparseProduct(dtm_diag, p.derivative()[block], jacs[block]); } return ADB::function(std::move(tm), std::move(jacs)); } else { return ADB::constant(V::Constant(n, 1.0)); } } template void BlackoilModelBase:: classifyCondition(const ReservoirState& state) { using namespace Opm::AutoDiffGrid; const int nc = numCells(grid_); const int np = state.numPhases(); const PhaseUsage& pu = fluid_.phaseUsage(); const DataBlock s = Eigen::Map(& state.saturation()[0], nc, np); if (active_[ Gas ]) { // Oil/Gas or Water/Oil/Gas system const V so = s.col(pu.phase_pos[ Oil ]); const V sg = s.col(pu.phase_pos[ Gas ]); for (V::Index c = 0, e = sg.size(); c != e; ++c) { if (so[c] > 0) { phaseCondition_[c].setFreeOil (); } if (sg[c] > 0) { phaseCondition_[c].setFreeGas (); } if (active_[ Water ]) { phaseCondition_[c].setFreeWater(); } } } else { // Water/Oil system assert (active_[ Water ]); const V so = s.col(pu.phase_pos[ Oil ]); for (V::Index c = 0, e = so.size(); c != e; ++c) { phaseCondition_[c].setFreeWater(); if (so[c] > 0) { phaseCondition_[c].setFreeOil(); } } } } template void BlackoilModelBase:: updatePrimalVariableFromState(const ReservoirState& state) { updatePhaseCondFromPrimalVariable(state); } /// Update the phaseCondition_ member based on the primalVariable_ member. template void BlackoilModelBase:: updatePhaseCondFromPrimalVariable(const ReservoirState& state) { const int nc = Opm::AutoDiffGrid::numCells(grid_); isRs_ = V::Zero(nc); isRv_ = V::Zero(nc); isSg_ = V::Zero(nc); if (! (active_[Gas] && active_[Oil])) { // updatePhaseCondFromPrimarVariable() logic requires active gas and oil phase. phaseCondition_.assign(nc, PhasePresence()); return; } for (int c = 0; c < nc; ++c) { phaseCondition_[c] = PhasePresence(); // No free phases. phaseCondition_[c].setFreeWater(); // Not necessary for property calculation usage. switch (state.hydroCarbonState()[c]) { case HydroCarbonState::GasAndOil: phaseCondition_[c].setFreeOil(); phaseCondition_[c].setFreeGas(); isSg_[c] = 1; break; case HydroCarbonState::OilOnly: phaseCondition_[c].setFreeOil(); isRs_[c] = 1; break; case HydroCarbonState::GasOnly: phaseCondition_[c].setFreeGas(); isRv_[c] = 1; break; default: OPM_THROW(std::logic_error, "Unknown primary variable enum value in cell " << c << ": " << state.hydroCarbonState()[c]); } } } template void BlackoilModelBase:: computeWellConnectionPressures(const SolutionState& state, const WellState& well_state) { asImpl().wellModel().computeWellConnectionPressures(state, well_state); } template std::vector > BlackoilModelBase:: computeFluidInPlace(const ReservoirState& x, const std::vector& fipnum) { using namespace Opm::AutoDiffGrid; const int nc = numCells(grid_); std::vector saturation(3, ADB::null()); const DataBlock s = Eigen::Map(& x.saturation()[0], nc, x.numPhases()); const ADB pressure = ADB::constant(Eigen::Map(& x.pressure()[0], nc, 1)); const ADB temperature = ADB::constant(Eigen::Map(& x.temperature()[0], nc, 1)); saturation[Water] = active_[Water] ? ADB::constant(s.col(Water)) : ADB::null(); saturation[Oil] = active_[Oil] ? ADB::constant(s.col(Oil)) : ADB::constant(V::Zero(nc)); saturation[Gas] = active_[Gas] ? ADB::constant(s.col(Gas)) : ADB::constant(V::Zero(nc)); const ADB rs = ADB::constant(Eigen::Map(& x.gasoilratio()[0], nc, 1)); const ADB rv = ADB::constant(Eigen::Map(& x.rv()[0], nc, 1)); const auto canonical_phase_pressures = computePressures(pressure, saturation[Water], saturation[Oil], saturation[Gas]); const Opm::PhaseUsage& pu = fluid_.phaseUsage(); const std::vector cond = phaseCondition(); const ADB pv_mult = poroMult(pressure); const V& pv = geo_.poreVolume(); const int maxnp = Opm::BlackoilPhases::MaxNumPhases; for (int phase = 0; phase < maxnp; ++phase) { if (active_[ phase ]) { const int pos = pu.phase_pos[ phase ]; const auto& b = asImpl().fluidReciprocFVF(phase, canonical_phase_pressures[phase], temperature, rs, rv, cond); sd_.fip[phase] = ((pv_mult * b * saturation[pos] * pv).value()); } } if (active_[ Oil ] && active_[ Gas ]) { // Account for gas dissolved in oil and vaporized oil sd_.fip[SimulatorData::FIP_DISSOLVED_GAS] = rs.value() * sd_.fip[SimulatorData::FIP_LIQUID]; sd_.fip[SimulatorData::FIP_VAPORIZED_OIL] = rv.value() * sd_.fip[SimulatorData::FIP_VAPOUR]; } // For a parallel run this is just a local maximum and needs to be updated later int dims = *std::max_element(fipnum.begin(), fipnum.end()); std::vector > values(dims); for (int i=0; i < dims; ++i) { values[i].resize(7, 0.0); } const V hydrocarbon = saturation[Oil].value() + saturation[Gas].value(); V hcpv; V pres; if ( !isParallel() ) { //Accumulate phases for each region for (int phase = 0; phase < maxnp; ++phase) { if (active_[ phase ]) { for (int c = 0; c < nc; ++c) { const int region = fipnum[c] - 1; if (region != -1) { values[region][phase] += sd_.fip[phase][c]; } } } } //Accumulate RS and RV-volumes for each region if (active_[ Oil ] && active_[ Gas ]) { for (int c = 0; c < nc; ++c) { const int region = fipnum[c] - 1; if (region != -1) { values[region][SimulatorData::FIP_DISSOLVED_GAS] += sd_.fip[SimulatorData::FIP_DISSOLVED_GAS][c]; values[region][SimulatorData::FIP_VAPORIZED_OIL] += sd_.fip[SimulatorData::FIP_VAPORIZED_OIL][c]; } } } hcpv = V::Zero(dims); pres = V::Zero(dims); for (int c = 0; c < nc; ++c) { const int region = fipnum[c] - 1; if (region != -1) { hcpv[region] += pv[c] * hydrocarbon[c]; pres[region] += pv[c] * pressure.value()[c]; } } sd_.fip[SimulatorData::FIP_PV] = V::Zero(nc); sd_.fip[SimulatorData::FIP_WEIGHTED_PRESSURE] = V::Zero(nc); for (int c = 0; c < nc; ++c) { const int region = fipnum[c] - 1; if (region != -1) { sd_.fip[SimulatorData::FIP_PV][c] = pv[c]; //Compute hydrocarbon pore volume weighted average pressure. //If we have no hydrocarbon in region, use pore volume weighted average pressure instead if (hcpv[region] != 0) { sd_.fip[SimulatorData::FIP_WEIGHTED_PRESSURE][c] = pv[c] * pressure.value()[c] * hydrocarbon[c] / hcpv[region]; } else { sd_.fip[SimulatorData::FIP_WEIGHTED_PRESSURE][c] = pres[region] / pv[c]; } values[region][SimulatorData::FIP_PV] += sd_.fip[SimulatorData::FIP_PV][c]; values[region][SimulatorData::FIP_WEIGHTED_PRESSURE] += sd_.fip[SimulatorData::FIP_WEIGHTED_PRESSURE][c]; } } } else { #if HAVE_MPI // mask[c] is 1 if we need to compute something in parallel const auto & pinfo = boost::any_cast(linsolver_.parallelInformation()); const auto& mask = pinfo.getOwnerMask(); auto comm = pinfo.communicator(); // Compute the global dims value and resize values accordingly. dims = comm.max(dims); values.resize(dims); for (int i=0; i < dims; ++i) { values[i].resize(7); std::fill(values[i].begin(), values[i].end(), 0.0); } //Accumulate phases for each region for (int phase = 0; phase < maxnp; ++phase) { for (int c = 0; c < nc; ++c) { const int region = fipnum[c] - 1; if (region != -1 && mask[c]) { values[region][phase] += sd_.fip[phase][c]; } } } //Accumulate RS and RV-volumes for each region if (active_[ Oil ] && active_[ Gas ]) { for (int c = 0; c < nc; ++c) { const int region = fipnum[c] - 1; if (region != -1 && mask[c]) { values[region][SimulatorData::FIP_DISSOLVED_GAS] += sd_.fip[SimulatorData::FIP_DISSOLVED_GAS][c]; values[region][SimulatorData::FIP_VAPORIZED_OIL] += sd_.fip[SimulatorData::FIP_VAPORIZED_OIL][c]; } } } hcpv = V::Zero(dims); pres = V::Zero(dims); for (int c = 0; c < nc; ++c) { const int region = fipnum[c] - 1; if (region != -1 && mask[c]) { hcpv[region] += pv[c] * hydrocarbon[c]; pres[region] += pv[c] * pressure.value()[c]; } } comm.sum(hcpv.data(), hcpv.size()); comm.sum(pres.data(), pres.size()); sd_.fip[SimulatorData::FIP_PV] = V::Zero(nc); sd_.fip[SimulatorData::FIP_WEIGHTED_PRESSURE] = V::Zero(nc); for (int c = 0; c < nc; ++c) { const int region = fipnum[c] - 1; if (region != -1 && mask[c]) { sd_.fip[SimulatorData::FIP_PV][c] = pv[c]; if (hcpv[region] != 0) { sd_.fip[SimulatorData::FIP_WEIGHTED_PRESSURE][c] = pv[c] * pressure.value()[c] * hydrocarbon[c] / hcpv[region]; } else { sd_.fip[SimulatorData::FIP_WEIGHTED_PRESSURE][c] = pres[region] / pv[c]; } values[region][SimulatorData::FIP_PV] += sd_.fip[SimulatorData::FIP_PV][c]; values[region][SimulatorData::FIP_WEIGHTED_PRESSURE] += sd_.fip[SimulatorData::FIP_WEIGHTED_PRESSURE][c]; } } // For the frankenstein branch we hopefully can turn values into a vanilla // std::vector, use some index magic above, use one communication // to sum up the vector entries instead of looping over the regions. for(int reg=0; reg < dims; ++reg) { comm.sum(values[reg].data(), values[reg].size()); } #else // This should never happen! OPM_THROW(std::logic_error, "HAVE_MPI should be defined if we are running in parallel"); #endif } return values; } template void BlackoilModelBase:: computeWellVoidageRates(const ReservoirState& reservoir_state, const WellState& well_state, std::vector& well_voidage_rates, std::vector& voidage_conversion_coeffs) { // TODO: for now, we store the voidage rates for all the production wells. // For injection wells, the rates are stored as zero. // We only store the conversion coefficients for all the injection wells. // Later, more delicate model will be implemented here. // And for the moment, group control can only work for serial running. const int nw = well_state.numWells(); const int np = numPhases(); const Wells* wells = asImpl().wellModel().wellsPointer(); // we calculate the voidage rate for each well, that means the sum of all the phases. well_voidage_rates.resize(nw, 0); // store the conversion coefficients, while only for the use of injection wells. voidage_conversion_coeffs.resize(nw * np, 1.0); int global_number_wells = nw; #if HAVE_MPI if ( linsolver_.parallelInformation().type() == typeid(ParallelISTLInformation) ) { const auto& info = boost::any_cast(linsolver_.parallelInformation()); global_number_wells = info.communicator().sum(global_number_wells); if ( global_number_wells ) { rate_converter_.defineState(reservoir_state, boost::any_cast(linsolver_.parallelInformation())); } } else #endif { if ( global_number_wells ) { rate_converter_.defineState(reservoir_state); } } std::vector well_rates(np, 0.0); std::vector convert_coeff(np, 1.0); if ( !well_voidage_rates.empty() ) { for (int w = 0; w < nw; ++w) { const bool is_producer = wells->type[w] == PRODUCER; // not sure necessary to change all the value to be positive if (is_producer) { std::transform(well_state.wellRates().begin() + np * w, well_state.wellRates().begin() + np * (w + 1), well_rates.begin(), std::negate()); // the average hydrocarbon conditions of the whole field will be used const int fipreg = 0; // Not considering FIP for the moment. const int well_cell_top = wells->well_cells[wells->well_connpos[w]]; const int pvtreg = fluid_.cellPvtRegionIndex()[well_cell_top]; rate_converter_.calcCoeff(fipreg, pvtreg, convert_coeff); well_voidage_rates[w] = std::inner_product(well_rates.begin(), well_rates.end(), convert_coeff.begin(), 0.0); } else { // TODO: Not sure whether will encounter situation with all zero rates // and whether it will cause problem here. std::copy(well_state.wellRates().begin() + np * w, well_state.wellRates().begin() + np * (w + 1), well_rates.begin()); // the average hydrocarbon conditions of the whole field will be used const int fipreg = 0; // Not considering FIP for the moment. const int well_cell_top = wells->well_cells[wells->well_connpos[w]]; const int pvtreg = fluid_.cellPvtRegionIndex()[well_cell_top]; rate_converter_.calcCoeff(fipreg, pvtreg, convert_coeff); std::copy(convert_coeff.begin(), convert_coeff.end(), voidage_conversion_coeffs.begin() + np * w); } } } } template void BlackoilModelBase:: applyVREPGroupControl(const ReservoirState& reservoir_state, WellState& well_state) { if (asImpl().wellModel().wellCollection()->havingVREPGroups()) { std::vector well_voidage_rates; std::vector voidage_conversion_coeffs; computeWellVoidageRates(reservoir_state, well_state, well_voidage_rates, voidage_conversion_coeffs); asImpl().wellModel().wellCollection()->applyVREPGroupControls(well_voidage_rates, voidage_conversion_coeffs); // for the wells under group control, update the currentControls for the well_state for (const WellNode* well_node : asImpl().wellModel().wellCollection()->getLeafNodes()) { if (well_node->isInjector() && !well_node->individualControl()) { const int well_index = well_node->selfIndex(); well_state.currentControls()[well_index] = well_node->groupControlIndex(); } } } } template void BlackoilModelBase:: setupGroupControl(const ReservoirState& reservoir_state, WellState& well_state) { if (asImpl().wellModel().wellCollection()->requireWellPotentials()) { SolutionState state = asImpl().variableState(reservoir_state, well_state); asImpl().makeConstantState(state); asImpl().wellModel().computeWellConnectionPressures(state, well_state); const int np = numPhases(); std::vector b(np, ADB::null()); std::vector mob(np, ADB::null()); const ADB& press = state.pressure; const ADB& temp = state.temperature; const ADB& rs = state.rs; const ADB& rv = state.rv; const std::vector cond = phaseCondition(); const ADB pv_mult = poroMult(press); const ADB tr_mult = transMult(press); const std::vector kr = asImpl().computeRelPerm(state); std::vector mob_perfcells(np, ADB::null()); std::vector b_perfcells(np, ADB::null()); for (int phase = 0; phase < np; ++phase) { if (active_[phase]) { const std::vector& well_cells = asImpl().wellModel().wellOps().well_cells; const ADB mu = asImpl().fluidViscosity(canph_[phase], state.canonical_phase_pressures[canph_[phase]], temp, rs, rv, cond); mob[phase] = tr_mult * kr[canph_[phase]] / mu; mob_perfcells[phase] = subset(mob[phase], well_cells); b[phase] = asImpl().fluidReciprocFVF(phase, state.canonical_phase_pressures[phase], temp, rs, rv, cond); b_perfcells[phase] = subset(b[phase], well_cells); } } // well potentials for each well std::vector well_potentials; asImpl().wellModel().computeWellPotentials(mob_perfcells, b_perfcells, well_state, state, well_potentials); asImpl().wellModel().wellCollection()->setGuideRatesWithPotentials(asImpl().wellModel().wellsPointer(), fluid_.phaseUsage(), well_potentials); } // end of if applyVREPGroupControl(reservoir_state, well_state); if (asImpl().wellModel().wellCollection()->groupControlApplied()) { asImpl().wellModel().wellCollection()->updateWellTargets(well_state.wellRates()); } else { asImpl().wellModel().wellCollection()->applyGroupControls(); // the well collections do not have access to Well State, so the currentControls() of Well State need to // be updated based on the group control setup const int nw = wells().number_of_wells; for (int w = 0; w < nw; ++w) { const WellNode& well_node = asImpl().wellModel().wellCollection()->findWellNode(wells().name[w]); if (!well_node.individualControl()) { well_state.currentControls()[w] = well_node.groupControlIndex(); } } } // end of else } } // namespace Opm #endif // OPM_BLACKOILMODELBASE_IMPL_HEADER_INCLUDED