diff --git a/CMakeLists_files.cmake b/CMakeLists_files.cmake index 990d77352..5be7b04b9 100644 --- a/CMakeLists_files.cmake +++ b/CMakeLists_files.cmake @@ -94,6 +94,8 @@ list (APPEND PUBLIC_HEADER_FILES opm/autodiff/AutoDiffHelpers.hpp opm/autodiff/AutoDiff.hpp opm/autodiff/BackupRestore.hpp + opm/autodiff/BlackoilModel.hpp + opm/autodiff/BlackoilModel_impl.hpp opm/autodiff/BlackoilPropsAdFromDeck.hpp opm/autodiff/BlackoilPropsAdInterface.hpp opm/autodiff/CPRPreconditioner.hpp @@ -108,6 +110,8 @@ list (APPEND PUBLIC_HEADER_FILES opm/autodiff/NewtonIterationBlackoilCPR.hpp opm/autodiff/NewtonIterationBlackoilInterface.hpp opm/autodiff/NewtonIterationBlackoilSimple.hpp + opm/autodiff/NewtonSolver.hpp + opm/autodiff/NewtonSolver_impl.hpp opm/autodiff/LinearisedBlackoilResidual.hpp opm/autodiff/RateConverter.hpp opm/autodiff/RedistributeDataHandles.hpp diff --git a/opm/autodiff/BlackoilModel.hpp b/opm/autodiff/BlackoilModel.hpp new file mode 100644 index 000000000..ff8317852 --- /dev/null +++ b/opm/autodiff/BlackoilModel.hpp @@ -0,0 +1,441 @@ +/* + Copyright 2013, 2015 SINTEF ICT, Applied Mathematics. + Copyright 2014, 2015 Statoil ASA. + Copyright 2014, 2015 Dr. Markus Blatt - HPC-Simulation-Software & Services + Copyright 2015 NTNU + + 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_BLACKOILMODEL_HEADER_INCLUDED +#define OPM_BLACKOILMODEL_HEADER_INCLUDED + +#include + +#include +#include +#include +#include +#include + +#include + +struct UnstructuredGrid; +struct Wells; + +namespace Opm { + + namespace parameter { class ParameterGroup; } + class DerivedGeology; + class RockCompressibility; + class NewtonIterationBlackoilInterface; + class BlackoilState; + class WellStateFullyImplicitBlackoil; + + + /// A model implementation for three-phase black oil. + /// + /// The simulator is capable of handling three-phase problems + /// where gas can be dissolved in oil and vice versa. It + /// uses an industry-standard TPFA discretization with per-phase + /// upwind weighting of mobilities. + /// + /// It uses automatic differentiation via the class AutoDiffBlock + /// to simplify assembly of the jacobian matrix. + template + class BlackoilModel + { + public: + // --------- Types and enums --------- + typedef AutoDiffBlock ADB; + typedef ADB::V V; + typedef ADB::M M; + typedef BlackoilState ReservoirState; + typedef WellStateFullyImplicitBlackoil WellState; + + /// Model-specific solver parameters. + struct ModelParameters + { + double dp_max_rel_; + double ds_max_; + double dr_max_rel_; + double max_residual_allowed_; + double tolerance_mb_; + double tolerance_cnv_; + double tolerance_wells_; + + explicit ModelParameters( const parameter::ParameterGroup& param ); + ModelParameters(); + + void reset(); + }; + + // --------- Public methods --------- + + /// Construct the model. It will retain references to the + /// arguments of this functions, and they are expected to + /// remain in scope for the lifetime of the solver. + /// \param[in] param parameters + /// \param[in] grid grid data structure + /// \param[in] fluid fluid properties + /// \param[in] geo rock properties + /// \param[in] rock_comp_props if non-null, rock compressibility properties + /// \param[in] wells well structure + /// \param[in] linsolver linear solver + /// \param[in] has_disgas turn on dissolved gas + /// \param[in] has_vapoil turn on vaporized oil feature + /// \param[in] terminal_output request output to cout/cerr + BlackoilModel(const ModelParameters& param, + const Grid& grid , + const BlackoilPropsAdInterface& fluid, + const DerivedGeology& geo , + const RockCompressibility* rock_comp_props, + const Wells* wells, + const NewtonIterationBlackoilInterface& linsolver, + const bool has_disgas, + const bool has_vapoil, + const bool terminal_output); + + /// \brief Set threshold pressures that prevent or reduce flow. + /// This prevents flow across faces if the potential + /// difference is less than the threshold. If the potential + /// difference is greater, the threshold value is subtracted + /// before calculating flow. This is treated symmetrically, so + /// flow is prevented or reduced in both directions equally. + /// \param[in] threshold_pressures_by_face array of size equal to the number of faces + /// of the grid passed in the constructor. + void setThresholdPressures(const std::vector& threshold_pressures_by_face); + + /// Called once before each time step. + /// \param[in] dt time step size + /// \param[in, out] reservoir_state reservoir state variables + /// \param[in, out] well_state well state variables + void prepareStep(const double dt, + ReservoirState& reservoir_state, + WellState& well_state); + + /// Called once after each time step. + /// In this class, this function does nothing. + /// \param[in] dt time step size + /// \param[in, out] reservoir_state reservoir state variables + /// \param[in, out] well_state well state variables + void afterStep(const double dt, + ReservoirState& reservoir_state, + WellState& well_state); + + /// Assemble the residual and Jacobian of the nonlinear system. + /// \param[in] reservoir_state reservoir state variables + /// \param[in, out] well_state well state variables + /// \param[in] initial_assembly pass true if this is the first call to assemble() in this timestep + void assemble(const BlackoilState& reservoir_state, + WellStateFullyImplicitBlackoil& well_state, + const bool initial_assembly); + + /// \brief Compute the residual norms of the mass balance for each phase, + /// the well flux, and the well equation. + /// \return a vector that contains for each phase the norm of the mass balance + /// and afterwards the norm of the residual of the well flux and the well equation. + std::vector computeResidualNorms() const; + + /// The size (number of unknowns) of the nonlinear system of equations. + int sizeNonLinear() const; + + /// Number of linear iterations used in last call to solveJacobianSystem(). + int linearIterationsLastSolve() const; + + /// Solve the Jacobian system Jx = r where J is the Jacobian and + /// r is the residual. + V solveJacobianSystem() const; + + /// Apply an update to the primary variables, chopped if appropriate. + /// \param[in] dx updates to apply to primary variables + /// \param[in, out] reservoir_state reservoir state variables + /// \param[in, out] well_state well state variables + void updateState(const V& dx, + BlackoilState& reservoir_state, + WellStateFullyImplicitBlackoil& well_state); + + /// Return true if output to cout is wanted. + bool terminalOutputEnabled() const; + + /// Compute convergence based on total mass balance (tol_mb) and maximum + /// residual mass balance (tol_cnv). + /// \param[in] dt timestep length + /// \param[in] iteration current iteration number + bool getConvergence(const double dt, const int iteration); + + /// The number of active phases in the model. + int numPhases() const; + + private: + + // --------- Types and enums --------- + + typedef Eigen::Array DataBlock; + + struct ReservoirResidualQuant { + ReservoirResidualQuant(); + std::vector accum; // Accumulations + ADB mflux; // Mass flux (surface conditions) + ADB b; // Reciprocal FVF + ADB head; // Pressure drop across int. interfaces + ADB mob; // Phase mobility (per cell) + }; + + struct SolutionState { + SolutionState(const int np); + ADB pressure; + ADB temperature; + std::vector saturation; + ADB rs; + ADB rv; + ADB qs; + ADB bhp; + // Below are quantities stored in the state for optimization purposes. + std::vector canonical_phase_pressures; // Always has 3 elements, even if only 2 phases active. + }; + + struct WellOps { + WellOps(const Wells* wells); + M w2p; // well -> perf (scatter) + M p2w; // perf -> well (gather) + }; + + enum { Water = BlackoilPropsAdInterface::Water, + Oil = BlackoilPropsAdInterface::Oil , + Gas = BlackoilPropsAdInterface::Gas , + MaxNumPhases = BlackoilPropsAdInterface::MaxNumPhases + }; + + enum PrimalVariables { Sg = 0, RS = 1, RV = 2 }; + + // --------- Data members --------- + + const Grid& grid_; + const BlackoilPropsAdInterface& fluid_; + const DerivedGeology& geo_; + const RockCompressibility* rock_comp_props_; + const Wells* wells_; + const NewtonIterationBlackoilInterface& linsolver_; + // For each canonical phase -> true if active + const std::vector active_; + // Size = # active phases. Maps active -> canonical phase indices. + const std::vector canph_; + const std::vector cells_; // All grid cells + HelperOps ops_; + const WellOps wops_; + const bool has_disgas_; + const bool has_vapoil_; + + ModelParameters param_; + bool use_threshold_pressure_; + V threshold_pressures_by_interior_face_; + + std::vector rq_; + std::vector phaseCondition_; + V well_perforation_pressure_diffs_; // Diff to bhp for each well perforation. + + LinearisedBlackoilResidual residual_; + + /// \brief Whether we print something to std::cout + bool terminal_output_; + + std::vector primalVariable_; + V pvdt_; + + // --------- Private methods --------- + + // return true if wells are available + bool wellsActive() const { return wells_ ? wells_->number_of_wells > 0 : false ; } + // return wells object + const Wells& wells () const { assert( bool(wells_ != 0) ); return *wells_; } + + SolutionState + constantState(const BlackoilState& x, + const WellStateFullyImplicitBlackoil& xw) const; + + void + makeConstantState(SolutionState& state) const; + + SolutionState + variableState(const BlackoilState& x, + const WellStateFullyImplicitBlackoil& xw) const; + + void + computeAccum(const SolutionState& state, + const int aix ); + + void computeWellConnectionPressures(const SolutionState& state, + const WellStateFullyImplicitBlackoil& xw); + + void + addWellControlEq(const SolutionState& state, + const WellStateFullyImplicitBlackoil& xw, + const V& aliveWells); + + void + addWellEq(const SolutionState& state, + WellStateFullyImplicitBlackoil& xw, + V& aliveWells); + + void updateWellControls(WellStateFullyImplicitBlackoil& xw) const; + + std::vector + computePressures(const SolutionState& state) const; + + std::vector + computePressures(const ADB& po, + const ADB& sw, + const ADB& so, + const ADB& sg) const; + + V + computeGasPressure(const V& po, + const V& sw, + const V& so, + const V& sg) const; + + std::vector + computeRelPerm(const SolutionState& state) const; + + void + computeMassFlux(const int actph , + const V& transi, + const ADB& kr , + const ADB& p , + const SolutionState& state ); + + void applyThresholdPressures(ADB& dp); + + ADB + fluidViscosity(const int phase, + const ADB& p , + const ADB& temp , + const ADB& rs , + const ADB& rv , + const std::vector& cond, + const std::vector& cells) const; + + ADB + fluidReciprocFVF(const int phase, + const ADB& p , + const ADB& temp , + const ADB& rs , + const ADB& rv , + const std::vector& cond, + const std::vector& cells) const; + + ADB + fluidDensity(const int phase, + const ADB& p , + const ADB& temp , + const ADB& rs , + const ADB& rv , + const std::vector& cond, + const std::vector& cells) const; + + V + fluidRsSat(const V& p, + const V& so, + const std::vector& cells) const; + + ADB + fluidRsSat(const ADB& p, + const ADB& so, + const std::vector& cells) const; + + V + fluidRvSat(const V& p, + const V& so, + const std::vector& cells) const; + + ADB + fluidRvSat(const ADB& p, + const ADB& so, + const std::vector& cells) const; + + ADB + poroMult(const ADB& p) const; + + ADB + transMult(const ADB& p) const; + + void + classifyCondition(const SolutionState& state, + std::vector& cond ) const; + + const std::vector + phaseCondition() const {return phaseCondition_;} + + void + classifyCondition(const BlackoilState& state); + + + /// update the primal variable for Sg, Rv or Rs. The Gas phase must + /// be active to call this method. + void + updatePrimalVariableFromState(const BlackoilState& state); + + /// Update the phaseCondition_ member based on the primalVariable_ member. + void + updatePhaseCondFromPrimalVariable(); + + /// \brief Compute the reduction within the convergence check. + /// \param[in] B A matrix with MaxNumPhases columns and the same number rows + /// as the number of cells of the grid. B.col(i) contains the values + /// for phase i. + /// \param[in] tempV A matrix with MaxNumPhases columns and the same number rows + /// as the number of cells of the grid. tempV.col(i) contains the + /// values + /// for phase i. + /// \param[in] R A matrix with MaxNumPhases columns and the same number rows + /// as the number of cells of the grid. B.col(i) contains the values + /// for phase i. + /// \param[out] R_sum An array of size MaxNumPhases where entry i contains the sum + /// of R for the phase i. + /// \param[out] maxCoeff An array of size MaxNumPhases where entry i contains the + /// maximum of tempV for the phase i. + /// \param[out] B_avg An array of size MaxNumPhases where entry i contains the average + /// of B for the phase i. + /// \param[out] maxNormWell The maximum of the well equations for each phase. + /// \param[in] nc The number of cells of the local grid. + /// \param[in] nw The number of wells on the local grid. + /// \return The total pore volume over all cells. + double + convergenceReduction(const Eigen::Array& B, + const Eigen::Array& tempV, + const Eigen::Array& R, + std::array& R_sum, + std::array& maxCoeff, + std::array& B_avg, + std::vector& maxNormWell, + int nc, + int nw) const; + + double dpMaxRel() const { return param_.dp_max_rel_; } + double dsMax() const { return param_.ds_max_; } + double drMaxRel() const { return param_.dr_max_rel_; } + double maxResidualAllowed() const { return param_.max_residual_allowed_; } + + }; +} // namespace Opm + +#include "BlackoilModel_impl.hpp" + +#endif // OPM_BLACKOILMODEL_HEADER_INCLUDED diff --git a/opm/autodiff/BlackoilModel_impl.hpp b/opm/autodiff/BlackoilModel_impl.hpp new file mode 100644 index 000000000..1f71307f3 --- /dev/null +++ b/opm/autodiff/BlackoilModel_impl.hpp @@ -0,0 +1,2309 @@ +/* + 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_BLACKOILMODEL_IMPL_HEADER_INCLUDED +#define OPM_BLACKOILMODEL_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 + +// 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; + + +namespace detail { + + + std::vector + buildAllCells(const int nc) + { + std::vector all_cells(nc); + + for (int c = 0; c < nc; ++c) { all_cells[c] = c; } + + return all_cells; + } + + + + template + std::vector + activePhases(const PU& pu) + { + const int maxnp = Opm::BlackoilPhases::MaxNumPhases; + std::vector active(maxnp, false); + + for (int p = 0; p < pu.MaxNumPhases; ++p) { + active[ p ] = pu.phase_used[ p ] != 0; + } + + return active; + } + + + + template + std::vector + active2Canonical(const PU& pu) + { + const int maxnp = Opm::BlackoilPhases::MaxNumPhases; + std::vector act2can(maxnp, -1); + + for (int phase = 0; phase < maxnp; ++phase) { + if (pu.phase_used[ phase ]) { + act2can[ pu.phase_pos[ phase ] ] = phase; + } + } + + return act2can; + } + + +} // namespace detail + + template + void BlackoilModel::ModelParameters:: + reset() + { + // default values for the solver parameters + dp_max_rel_ = 1.0e9; + ds_max_ = 0.2; + dr_max_rel_ = 1.0e9; + max_residual_allowed_ = 1e7; + tolerance_mb_ = 1.0e-5; + tolerance_cnv_ = 1.0e-2; + tolerance_wells_ = 5.0e-1; + } + + template + BlackoilModel::ModelParameters:: + ModelParameters() + { + // set default values + reset(); + } + + template + BlackoilModel::ModelParameters:: + ModelParameters( const parameter::ParameterGroup& param ) + { + // set default values + reset(); + + // overload with given parameters + dp_max_rel_ = param.getDefault("dp_max_rel", dp_max_rel_); + ds_max_ = param.getDefault("ds_max", ds_max_); + dr_max_rel_ = param.getDefault("dr_max_rel", dr_max_rel_); + max_residual_allowed_ = param.getDefault("max_residual_allowed", max_residual_allowed_); + tolerance_mb_ = param.getDefault("tolerance_mb", tolerance_mb_); + tolerance_cnv_ = param.getDefault("tolerance_cnv", tolerance_cnv_); + tolerance_wells_ = param.getDefault("tolerance_wells", tolerance_wells_ ); + } + + + template + BlackoilModel:: + BlackoilModel(const ModelParameters& param, + const Grid& grid , + const BlackoilPropsAdInterface& fluid, + const DerivedGeology& geo , + const RockCompressibility* rock_comp_props, + const Wells* wells, + const NewtonIterationBlackoilInterface& linsolver, + const bool has_disgas, + const bool has_vapoil, + const bool terminal_output) + : grid_ (grid) + , fluid_ (fluid) + , geo_ (geo) + , rock_comp_props_(rock_comp_props) + , wells_ (wells) + , linsolver_ (linsolver) + , active_(detail::activePhases(fluid.phaseUsage())) + , canph_ (detail::active2Canonical(fluid.phaseUsage())) + , cells_ (detail::buildAllCells(Opm::AutoDiffGrid::numCells(grid))) + , ops_ (grid) + , wops_ (wells_) + , has_disgas_(has_disgas) + , has_vapoil_(has_vapoil) + , param_( param ) + , use_threshold_pressure_(false) + , rq_ (fluid.numPhases()) + , phaseCondition_(AutoDiffGrid::numCells(grid)) + , residual_ ( { std::vector(fluid.numPhases(), ADB::null()), + ADB::null(), + ADB::null() } ) + , terminal_output_ (terminal_output) + { +#if HAVE_MPI + if ( terminal_output_ ) { + if ( linsolver_.parallelInformation().type() == typeid(ParallelISTLInformation) ) + { + const ParallelISTLInformation& info = + boost::any_cast(linsolver_.parallelInformation()); + // Only rank 0 does print to std::cout if terminal_output is enabled + terminal_output_ = (info.communicator().rank()==0); + } + } +#endif + } + + + + template + void + BlackoilModel:: + prepareStep(const double dt, + ReservoirState& reservoir_state, + WellState& /* well_state */) + { + pvdt_ = geo_.poreVolume() / dt; + if (active_[Gas]) { + updatePrimalVariableFromState(reservoir_state); + } + } + + + + + template + void + BlackoilModel:: + afterStep(const double /* dt */, + ReservoirState& /* reservoir_state */, + WellState& /* well_state */) + { + // Does nothing in this model. + } + + + + + template + int + BlackoilModel:: + sizeNonLinear() const + { + return residual_.sizeNonLinear(); + } + + + + + template + int + BlackoilModel:: + linearIterationsLastSolve() const + { + return linsolver_.iterations(); + } + + + + + template + bool + BlackoilModel:: + terminalOutputEnabled() const + { + return terminal_output_; + } + + + + + template + int + BlackoilModel:: + numPhases() const + { + return fluid_.numPhases(); + } + + + + + template + void + BlackoilModel:: + setThresholdPressures(const std::vector& threshold_pressures_by_face) + { + const int num_faces = AutoDiffGrid::numFaces(grid_); + if (int(threshold_pressures_by_face.size()) != num_faces) { + OPM_THROW(std::runtime_error, "Illegal size of threshold_pressures_by_face input, must be equal to number of faces."); + } + use_threshold_pressure_ = true; + // Map to interior faces. + const int num_ifaces = ops_.internal_faces.size(); + threshold_pressures_by_interior_face_.resize(num_ifaces); + for (int ii = 0; ii < num_ifaces; ++ii) { + threshold_pressures_by_interior_face_[ii] = threshold_pressures_by_face[ops_.internal_faces[ii]]; + } + } + + + + + template + BlackoilModel::ReservoirResidualQuant::ReservoirResidualQuant() + : accum(2, ADB::null()) + , mflux( ADB::null()) + , b ( ADB::null()) + , head ( ADB::null()) + , mob ( ADB::null()) + { + } + + + + + + template + BlackoilModel::SolutionState::SolutionState(const int np) + : pressure ( ADB::null()) + , temperature( ADB::null()) + , saturation(np, ADB::null()) + , rs ( ADB::null()) + , rv ( ADB::null()) + , qs ( ADB::null()) + , bhp ( ADB::null()) + , canonical_phase_pressures(3, ADB::null()) + { + } + + + + + + template + BlackoilModel:: + WellOps::WellOps(const Wells* wells) + : w2p(), + p2w() + { + if( wells ) + { + w2p = M(wells->well_connpos[ wells->number_of_wells ], wells->number_of_wells); + p2w = M(wells->number_of_wells, wells->well_connpos[ wells->number_of_wells ]); + + const int nw = wells->number_of_wells; + const int* const wpos = wells->well_connpos; + + typedef Eigen::Triplet Tri; + + std::vector scatter, gather; + scatter.reserve(wpos[nw]); + gather .reserve(wpos[nw]); + + for (int w = 0, i = 0; w < nw; ++w) { + for (; i < wpos[ w + 1 ]; ++i) { + scatter.push_back(Tri(i, w, 1.0)); + gather .push_back(Tri(w, i, 1.0)); + } + } + + w2p.setFromTriplets(scatter.begin(), scatter.end()); + p2w.setFromTriplets(gather .begin(), gather .end()); + } + } + + + + + + template + typename BlackoilModel::SolutionState + BlackoilModel::constantState(const BlackoilState& x, + const WellStateFullyImplicitBlackoil& xw) const + { + auto state = variableState(x, xw); + makeConstantState(state); + return state; + } + + + + + + template + void + BlackoilModel::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 BlackoilModel::SolutionState + BlackoilModel::variableState(const BlackoilState& x, + const WellStateFullyImplicitBlackoil& xw) const + { + using namespace Opm::AutoDiffGrid; + const int nc = numCells(grid_); + const int np = x.numPhases(); + + std::vector vars0; + // p, Sw and Rs, Rv or Sg is used as primary depending on solution conditions + vars0.reserve(np + 1); + // 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); + } + + // store cell status in vectors + V isRs = V::Zero(nc,1); + V isRv = V::Zero(nc,1); + V isSg = V::Zero(nc,1); + + if (active_[ Gas ]){ + for (int c = 0; c < nc ; c++ ) { + switch (primalVariable_[c]) { + case PrimalVariables::RS: + isRs[c] = 1; + break; + + case PrimalVariables::RV: + isRv[c] = 1; + break; + + default: + isSg[c] = 1; + break; + } + } + + + // 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); + } + + + // Initial well rates. + if( wellsActive() ) + { + // Need to reshuffle well rates, from phase running fastest + // to wells running fastest. + const int nw = wells().number_of_wells; + + // The transpose() below switches the ordering. + const DataBlock wrates = Eigen::Map(& xw.wellRates()[0], nw, np).transpose(); + const V qs = Eigen::Map(wrates.data(), nw*np); + vars0.push_back(qs); + + // Initial well bottom-hole pressure. + assert (not xw.bhp().empty()); + const V bhp = Eigen::Map(& xw.bhp()[0], xw.bhp().size()); + vars0.push_back(bhp); + } + else + { + // push null states for qs and bhp + vars0.push_back(V()); + vars0.push_back(V()); + } + + std::vector vars = ADB::variables(vars0); + + SolutionState state(np); + + // Pressure. + int nextvar = 0; + state.pressure = std::move(vars[ nextvar++ ]); + + // temperature + 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[ nextvar++ ]); + 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[ nextvar++ ]; + ADB& sg = state.saturation[ pu.phase_pos[ Gas ] ]; + sg = isSg*xvar + isRv* so; + so -= sg; + + if (active_[ Oil ]) { + // RS and RV is only defined if both oil and gas phase are active. + const ADB& sw = (active_[ Water ] + ? state.saturation[ pu.phase_pos[ Water ] ] + : ADB::constant(V::Zero(nc, 1))); + state.canonical_phase_pressures = computePressures(state.pressure, sw, so, sg); + const ADB rsSat = fluidRsSat(state.canonical_phase_pressures[ Oil ], so , cells_); + if (has_disgas_) { + state.rs = (1-isRs) * rsSat + isRs*xvar; + } else { + state.rs = rsSat; + } + const ADB rvSat = fluidRvSat(state.canonical_phase_pressures[ Gas ], so , cells_); + if (has_vapoil_) { + state.rv = (1-isRv) * rvSat + isRv*xvar; + } else { + state.rv = rvSat; + } + } + } + + if (active_[ Oil ]) { + // Note that so is never a primary variable. + state.saturation[pu.phase_pos[ Oil ]] = std::move(so); + } + } + + // Qs. + state.qs = std::move(vars[ nextvar++ ]); + + // Bhp. + state.bhp = std::move(vars[ nextvar++ ]); + + assert(nextvar == int(vars.size())); + + return state; + } + + + + + + template + void + BlackoilModel::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 ]; + rq_[pos].b = fluidReciprocFVF(phase, state.canonical_phase_pressures[phase], temp, rs, rv, cond, cells_); + rq_[pos].accum[aix] = pv_mult * rq_[pos].b * sat[pos]; + // OPM_AD_DUMP(rq_[pos].b); + // OPM_AD_DUMP(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 =rq_[pg].accum[aix]; + + rq_[pg].accum[aix] += state.rs * rq_[po].accum[aix]; + rq_[po].accum[aix] += state.rv * accum_gas_copy; + // OPM_AD_DUMP(rq_[pg].accum[aix]); + } + } + + + + + + template + void BlackoilModel::computeWellConnectionPressures(const SolutionState& state, + const WellStateFullyImplicitBlackoil& xw) + { + if( ! wellsActive() ) return ; + + using namespace Opm::AutoDiffGrid; + // 1. Compute properties required by computeConnectionPressureDelta(). + // Note that some of the complexity of this part is due to the function + // taking std::vector arguments, and not Eigen objects. + const int nperf = wells().well_connpos[wells().number_of_wells]; + const int nw = wells().number_of_wells; + const std::vector well_cells(wells().well_cells, wells().well_cells + nperf); + + // Compute the average pressure in each well block + const V perf_press = Eigen::Map(xw.perfPress().data(), nperf); + V avg_press = perf_press*0; + for (int w = 0; w < nw; ++w) { + for (int perf = wells().well_connpos[w]; perf < wells().well_connpos[w+1]; ++perf) { + const double p_above = perf == wells().well_connpos[w] ? state.bhp.value()[w] : perf_press[perf - 1]; + const double p_avg = (perf_press[perf] + p_above)/2; + avg_press[perf] = p_avg; + } + } + + // Use cell values for the temperature as the wells don't knows its temperature yet. + const ADB perf_temp = subset(state.temperature, well_cells); + + // Compute b, rsmax, rvmax values for perforations. + // Evaluate the properties using average well block pressures + // and cell values for rs, rv, phase condition and temperature. + const ADB avg_press_ad = ADB::constant(avg_press); + std::vector perf_cond(nperf); + const std::vector& pc = phaseCondition(); + for (int perf = 0; perf < nperf; ++perf) { + perf_cond[perf] = pc[well_cells[perf]]; + } + const PhaseUsage& pu = fluid_.phaseUsage(); + DataBlock b(nperf, pu.num_phases); + std::vector rsmax_perf(nperf, 0.0); + std::vector rvmax_perf(nperf, 0.0); + if (pu.phase_used[BlackoilPhases::Aqua]) { + const V bw = fluid_.bWat(avg_press_ad, perf_temp, well_cells).value(); + b.col(pu.phase_pos[BlackoilPhases::Aqua]) = bw; + } + assert(active_[Oil]); + const V perf_so = subset(state.saturation[pu.phase_pos[Oil]].value(), well_cells); + if (pu.phase_used[BlackoilPhases::Liquid]) { + const ADB perf_rs = subset(state.rs, well_cells); + const V bo = fluid_.bOil(avg_press_ad, perf_temp, perf_rs, perf_cond, well_cells).value(); + b.col(pu.phase_pos[BlackoilPhases::Liquid]) = bo; + const V rssat = fluidRsSat(avg_press, perf_so, well_cells); + rsmax_perf.assign(rssat.data(), rssat.data() + nperf); + } + if (pu.phase_used[BlackoilPhases::Vapour]) { + const ADB perf_rv = subset(state.rv, well_cells); + const V bg = fluid_.bGas(avg_press_ad, perf_temp, perf_rv, perf_cond, well_cells).value(); + b.col(pu.phase_pos[BlackoilPhases::Vapour]) = bg; + const V rvsat = fluidRvSat(avg_press, perf_so, well_cells); + rvmax_perf.assign(rvsat.data(), rvsat.data() + nperf); + } + // b is row major, so can just copy data. + std::vector b_perf(b.data(), b.data() + nperf * pu.num_phases); + // Extract well connection depths. + const V depth = cellCentroidsZToEigen(grid_); + const V pdepth = subset(depth, well_cells); + std::vector perf_depth(pdepth.data(), pdepth.data() + nperf); + // Surface density. + std::vector surf_dens(fluid_.surfaceDensity(), fluid_.surfaceDensity() + pu.num_phases); + // Gravity + double grav = 0.0; + const double* g = geo_.gravity(); + const int dim = dimensions(grid_); + if (g) { + // Guard against gravity in anything but last dimension. + for (int dd = 0; dd < dim - 1; ++dd) { + assert(g[dd] == 0.0); + } + grav = g[dim - 1]; + } + + // 2. Compute pressure deltas, and store the results. + std::vector cdp = WellDensitySegmented + ::computeConnectionPressureDelta(wells(), xw, fluid_.phaseUsage(), + b_perf, rsmax_perf, rvmax_perf, perf_depth, + surf_dens, grav); + well_perforation_pressure_diffs_ = Eigen::Map(cdp.data(), nperf); + } + + + + + + template + void + BlackoilModel:: + assemble(const BlackoilState& reservoir_state, + WellStateFullyImplicitBlackoil& well_state, + const bool initial_assembly) + { + using namespace Opm::AutoDiffGrid; + + // Possibly switch well controls and updating well state to + // get reasonable initial conditions for the wells + updateWellControls(well_state); + + // Create the primary variables. + SolutionState state = variableState(reservoir_state, well_state); + + if (initial_assembly) { + // Create the (constant, derivativeless) initial state. + SolutionState state0 = state; + makeConstantState(state0); + // Compute initial accumulation contributions + // and well connection pressures. + computeAccum(state0, 0); + 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 -------- + + // 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 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 rq_[phase].accum[0]. + 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 std::vector kr = computeRelPerm(state); + for (int phaseIdx = 0; phaseIdx < fluid_.numPhases(); ++phaseIdx) { + computeMassFlux(phaseIdx, transi, kr[canph_[phaseIdx]], state.canonical_phase_pressures[canph_[phaseIdx]], state); + + residual_.material_balance_eq[ phaseIdx ] = + pvdt_ * (rq_[phaseIdx].accum[1] - rq_[phaseIdx].accum[0]) + + ops_.div*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_, + rq_[po].head.value()); + const ADB rs_face = upwindOil.select(state.rs); + + const UpwindSelector upwindGas(grid_, ops_, + rq_[pg].head.value()); + const ADB rv_face = upwindGas.select(state.rv); + + residual_.material_balance_eq[ pg ] += ops_.div * (rs_face * rq_[po].mflux); + residual_.material_balance_eq[ po ] += ops_.div * (rv_face * rq_[pg].mflux); + + // OPM_AD_DUMP(residual_.material_balance_eq[ Gas ]); + + } + + // -------- Well equations ---------- + + // Add contribution from wells and set up the well equations. + V aliveWells; + addWellEq(state, well_state, aliveWells); + addWellControlEq(state, well_state, aliveWells); + } + + + + + + template + void BlackoilModel::addWellEq(const SolutionState& state, + WellStateFullyImplicitBlackoil& xw, + V& aliveWells) + { + if( ! wellsActive() ) return ; + + const int nc = Opm::AutoDiffGrid::numCells(grid_); + const int np = wells().number_of_phases; + const int nw = wells().number_of_wells; + const int nperf = wells().well_connpos[nw]; + const Opm::PhaseUsage& pu = fluid_.phaseUsage(); + V Tw = Eigen::Map(wells().WI, nperf); + const std::vector well_cells(wells().well_cells, wells().well_cells + nperf); + + // pressure diffs computed already (once per step, not changing per iteration) + const V& cdp = well_perforation_pressure_diffs_; + + // Extract needed quantities for the perforation cells + const ADB& p_perfcells = subset(state.pressure, well_cells); + const ADB& rv_perfcells = subset(state.rv,well_cells); + const ADB& rs_perfcells = subset(state.rs,well_cells); + std::vector mob_perfcells(np, ADB::null()); + std::vector b_perfcells(np, ADB::null()); + for (int phase = 0; phase < np; ++phase) { + mob_perfcells[phase] = subset(rq_[phase].mob,well_cells); + b_perfcells[phase] = subset(rq_[phase].b,well_cells); + } + + // Perforation pressure + const ADB perfpressure = (wops_.w2p * state.bhp) + cdp; + std::vector perfpressure_d(perfpressure.value().data(), perfpressure.value().data() + nperf); + xw.perfPress() = perfpressure_d; + + // Pressure drawdown (also used to determine direction of flow) + const ADB drawdown = p_perfcells - perfpressure; + + // Compute vectors with zero and ones that + // selects the wanted quantities. + + // selects injection perforations + V selectInjectingPerforations = V::Zero(nperf); + // selects producing perforations + V selectProducingPerforations = V::Zero(nperf); + for (int c = 0; c < nperf; ++c){ + if (drawdown.value()[c] < 0) + selectInjectingPerforations[c] = 1; + else + selectProducingPerforations[c] = 1; + } + + // HANDLE FLOW INTO WELLBORE + + // compute phase volumetric rates at standard conditions + std::vector cq_ps(np, ADB::null()); + for (int phase = 0; phase < np; ++phase) { + const ADB cq_p = -(selectProducingPerforations * Tw) * (mob_perfcells[phase] * drawdown); + cq_ps[phase] = b_perfcells[phase] * cq_p; + } + if (active_[Oil] && active_[Gas]) { + const int oilpos = pu.phase_pos[Oil]; + const int gaspos = pu.phase_pos[Gas]; + const ADB cq_psOil = cq_ps[oilpos]; + const ADB cq_psGas = cq_ps[gaspos]; + cq_ps[gaspos] += rs_perfcells * cq_psOil; + cq_ps[oilpos] += rv_perfcells * cq_psGas; + } + + // HANDLE FLOW OUT FROM WELLBORE + + // Using total mobilities + ADB total_mob = mob_perfcells[0]; + for (int phase = 1; phase < np; ++phase) { + total_mob += mob_perfcells[phase]; + } + // injection perforations total volume rates + const ADB cqt_i = -(selectInjectingPerforations * Tw) * (total_mob * drawdown); + + // compute wellbore mixture for injecting perforations + // The wellbore mixture depends on the inflow from the reservoar + // and the well injection rates. + + // compute avg. and total wellbore phase volumetric rates at standard conds + const DataBlock compi = Eigen::Map(wells().comp_frac, nw, np); + std::vector wbq(np, ADB::null()); + ADB wbqt = ADB::constant(V::Zero(nw)); + for (int phase = 0; phase < np; ++phase) { + const ADB& q_ps = wops_.p2w * cq_ps[phase]; + const ADB& q_s = subset(state.qs, Span(nw, 1, phase*nw)); + Selector injectingPhase_selector(q_s.value(), Selector::GreaterZero); + const int pos = pu.phase_pos[phase]; + wbq[phase] = (compi.col(pos) * injectingPhase_selector.select(q_s,ADB::constant(V::Zero(nw)))) - q_ps; + wbqt += wbq[phase]; + } + + // compute wellbore mixture at standard conditions. + Selector notDeadWells_selector(wbqt.value(), Selector::Zero); + std::vector cmix_s(np, ADB::null()); + for (int phase = 0; phase < np; ++phase) { + const int pos = pu.phase_pos[phase]; + cmix_s[phase] = wops_.w2p * notDeadWells_selector.select(ADB::constant(compi.col(pos)), wbq[phase]/wbqt); + } + + // compute volume ratio between connection at standard conditions + ADB volumeRatio = ADB::constant(V::Zero(nperf)); + const ADB d = V::Constant(nperf,1.0) - rv_perfcells * rs_perfcells; + for (int phase = 0; phase < np; ++phase) { + ADB tmp = cmix_s[phase]; + + if (phase == Oil && active_[Gas]) { + const int gaspos = pu.phase_pos[Gas]; + tmp = tmp - rv_perfcells * cmix_s[gaspos] / d; + } + if (phase == Gas && active_[Oil]) { + const int oilpos = pu.phase_pos[Oil]; + tmp = tmp - rs_perfcells * cmix_s[oilpos] / d; + } + volumeRatio += tmp / b_perfcells[phase]; + } + + // injecting connections total volumerates at standard conditions + ADB cqt_is = cqt_i/volumeRatio; + + // connection phase volumerates at standard conditions + std::vector cq_s(np, ADB::null()); + for (int phase = 0; phase < np; ++phase) { + cq_s[phase] = cq_ps[phase] + cmix_s[phase]*cqt_is; + } + + // Add well contributions to mass balance equations + for (int phase = 0; phase < np; ++phase) { + residual_.material_balance_eq[phase] -= superset(cq_s[phase],well_cells,nc); + } + + // WELL EQUATIONS + ADB qs = state.qs; + for (int phase = 0; phase < np; ++phase) { + qs -= superset(wops_.p2w * cq_s[phase], Span(nw, 1, phase*nw), nw*np); + + } + + // check for dead wells (used in the well controll equations) + aliveWells = V::Constant(nw, 1.0); + for (int w = 0; w < nw; ++w) { + if (wbqt.value()[w] == 0) { + aliveWells[w] = 0.0; + } + } + + // Update the perforation phase rates (used to calculate the pressure drop in the wellbore) + V cq = superset(cq_s[0].value(), Span(nperf, np, 0), nperf*np); + for (int phase = 1; phase < np; ++phase) { + cq += superset(cq_s[phase].value(), Span(nperf, np, phase), nperf*np); + } + + std::vector cq_d(cq.data(), cq.data() + nperf*np); + xw.perfPhaseRates() = cq_d; + + residual_.well_flux_eq = qs; + } + + + + + + namespace detail + { + double rateToCompare(const std::vector& well_phase_flow_rate, + const int well, + const int num_phases, + const double* distr) + { + double rate = 0.0; + for (int phase = 0; phase < num_phases; ++phase) { + // Important: well_phase_flow_rate is ordered with all phase rates for first + // well first, then all phase rates for second well etc. + rate += well_phase_flow_rate[well*num_phases + phase] * distr[phase]; + } + return rate; + } + + bool constraintBroken(const std::vector& bhp, + const std::vector& well_phase_flow_rate, + const int well, + const int num_phases, + const WellType& well_type, + const WellControls* wc, + const int ctrl_index) + { + const WellControlType ctrl_type = well_controls_iget_type(wc, ctrl_index); + const double target = well_controls_iget_target(wc, ctrl_index); + const double* distr = well_controls_iget_distr(wc, ctrl_index); + + bool broken = false; + + switch (well_type) { + case INJECTOR: + { + switch (ctrl_type) { + case BHP: + broken = bhp[well] > target; + break; + + case RESERVOIR_RATE: // Intentional fall-through + case SURFACE_RATE: + broken = rateToCompare(well_phase_flow_rate, + well, num_phases, distr) > target; + break; + } + } + break; + + case PRODUCER: + { + switch (ctrl_type) { + case BHP: + broken = bhp[well] < target; + break; + + case RESERVOIR_RATE: // Intentional fall-through + case SURFACE_RATE: + // Note that the rates compared below are negative, + // so breaking the constraints means: too high flow rate + // (as for injection). + broken = rateToCompare(well_phase_flow_rate, + well, num_phases, distr) < target; + break; + } + } + break; + + default: + OPM_THROW(std::logic_error, "Can only handle INJECTOR and PRODUCER wells."); + } + + return broken; + } + } // namespace detail + + + + + template + void BlackoilModel::updateWellControls(WellStateFullyImplicitBlackoil& xw) const + { + if( ! wellsActive() ) return ; + + std::string modestring[3] = { "BHP", "RESERVOIR_RATE", "SURFACE_RATE" }; + // Find, for each well, if any constraints are broken. If so, + // switch control to first broken constraint. + const int np = wells().number_of_phases; + const int nw = wells().number_of_wells; + for (int w = 0; w < nw; ++w) { + const WellControls* wc = wells().ctrls[w]; + // The current control in the well state overrides + // the current control set in the Wells struct, which + // is instead treated as a default. + int current = xw.currentControls()[w]; + // Loop over all controls except the current one, and also + // skip any RESERVOIR_RATE controls, since we cannot + // handle those. + const int nwc = well_controls_get_num(wc); + int ctrl_index = 0; + for (; ctrl_index < nwc; ++ctrl_index) { + if (ctrl_index == current) { + // This is the currently used control, so it is + // used as an equation. So this is not used as an + // inequality constraint, and therefore skipped. + continue; + } + if (detail::constraintBroken(xw.bhp(), xw.wellRates(), w, np, wells().type[w], wc, ctrl_index)) { + // ctrl_index will be the index of the broken constraint after the loop. + break; + } + } + if (ctrl_index != nwc) { + // Constraint number ctrl_index was broken, switch to it. + if (terminal_output_) + { + std::cout << "Switching control mode for well " << wells().name[w] + << " from " << modestring[well_controls_iget_type(wc, current)] + << " to " << modestring[well_controls_iget_type(wc, ctrl_index)] << std::endl; + } + xw.currentControls()[w] = ctrl_index; + current = xw.currentControls()[w]; + } + + // Updating well state and primary variables. + // Target values are used as initial conditions for BHP and SURFACE_RATE + const double target = well_controls_iget_target(wc, current); + const double* distr = well_controls_iget_distr(wc, current); + switch (well_controls_iget_type(wc, current)) { + case BHP: + xw.bhp()[w] = target; + break; + + case RESERVOIR_RATE: + // No direct change to any observable quantity at + // surface condition. In this case, use existing + // flow rates as initial conditions as reservoir + // rate acts only in aggregate. + break; + + case SURFACE_RATE: + for (int phase = 0; phase < np; ++phase) { + if (distr[phase] > 0.0) { + xw.wellRates()[np*w + phase] = target * distr[phase]; + } + } + break; + } + + } + } + + + + + template + void BlackoilModel::addWellControlEq(const SolutionState& state, + const WellStateFullyImplicitBlackoil& xw, + const V& aliveWells) + { + if( ! wellsActive() ) return; + + const int np = wells().number_of_phases; + const int nw = wells().number_of_wells; + + V bhp_targets = V::Zero(nw); + V rate_targets = V::Zero(nw); + M rate_distr(nw, np*nw); + for (int w = 0; w < nw; ++w) { + const WellControls* wc = wells().ctrls[w]; + // The current control in the well state overrides + // the current control set in the Wells struct, which + // is instead treated as a default. + const int current = xw.currentControls()[w]; + + switch (well_controls_iget_type(wc, current)) { + case BHP: + { + bhp_targets (w) = well_controls_iget_target(wc, current); + rate_targets(w) = -1e100; + } + break; + + case RESERVOIR_RATE: // Intentional fall-through + case SURFACE_RATE: + { + // RESERVOIR and SURFACE rates look the same, from a + // high-level point of view, in the system of + // simultaneous linear equations. + + const double* const distr = + well_controls_iget_distr(wc, current); + + for (int p = 0; p < np; ++p) { + rate_distr.insert(w, p*nw + w) = distr[p]; + } + + bhp_targets (w) = -1.0e100; + rate_targets(w) = well_controls_iget_target(wc, current); + } + break; + } + } + const ADB bhp_residual = state.bhp - bhp_targets; + const ADB rate_residual = rate_distr * state.qs - rate_targets; + // Choose bhp residual for positive bhp targets. + Selector bhp_selector(bhp_targets); + residual_.well_eq = bhp_selector.select(bhp_residual, rate_residual); + // For wells that are dead (not flowing), and therefore not communicating + // with the reservoir, we set the equation to be equal to the well's total + // flow. This will be a solution only if the target rate is also zero. + M rate_summer(nw, np*nw); + for (int w = 0; w < nw; ++w) { + for (int phase = 0; phase < np; ++phase) { + rate_summer.insert(w, phase*nw + w) = 1.0; + } + } + Selector alive_selector(aliveWells, Selector::NotEqualZero); + residual_.well_eq = alive_selector.select(residual_.well_eq, rate_summer * state.qs); + // OPM_AD_DUMP(residual_.well_eq); + } + + + + + + template + V BlackoilModel::solveJacobianSystem() const + { + return linsolver_.computeNewtonIncrement(residual_); + } + + + + + + namespace detail + { + /// \brief Compute the L-infinity norm of a vector + /// \warn This function is not suitable to compute on the well equations. + /// \param a The container to compute the infinity norm on. + /// It has to have one entry for each cell. + /// \param info In a parallel this holds the information about the data distribution. + double infinityNorm( const ADB& a, const boost::any& pinfo = boost::any() ) + { +#if HAVE_MPI + if ( pinfo.type() == typeid(ParallelISTLInformation) ) + { + const ParallelISTLInformation& real_info = + boost::any_cast(pinfo); + double result=0; + real_info.computeReduction(a.value(), Reduction::makeGlobalMaxFunctor(), result); + return result; + } + else +#endif + { + if( a.value().size() > 0 ) { + return a.value().matrix().lpNorm (); + } + else { // this situation can occur when no wells are present + return 0.0; + } + } + } + + /// \brief Compute the L-infinity norm of a vector representing a well equation. + /// \param a The container to compute the infinity norm on. + /// \param info In a parallel this holds the information about the data distribution. + double infinityNormWell( const ADB& a, const boost::any& pinfo ) + { + double result=0; + if( a.value().size() > 0 ) { + result = a.value().matrix().lpNorm (); + } +#if HAVE_MPI + if ( pinfo.type() == typeid(ParallelISTLInformation) ) + { + const ParallelISTLInformation& real_info = + boost::any_cast(pinfo); + result = real_info.communicator().max(result); + } +#endif + return result; + } + + } // namespace detail + + + + + + template + void BlackoilModel::updateState(const V& dx, + BlackoilState& reservoir_state, + WellStateFullyImplicitBlackoil& well_state) + { + using namespace Opm::AutoDiffGrid; + const int np = fluid_.numPhases(); + const int nc = numCells(grid_); + const int nw = wellsActive() ? wells().number_of_wells : 0; + const V null; + assert(null.size() == 0); + const V zero = V::Zero(nc); + + // store cell status in vectors + V isRs = V::Zero(nc,1); + V isRv = V::Zero(nc,1); + V isSg = V::Zero(nc,1); + if (active_[Gas]) { + for (int c = 0; c < nc; ++c) { + switch (primalVariable_[c]) { + case PrimalVariables::RS: + isRs[c] = 1; + break; + + case PrimalVariables::RV: + isRv[c] = 1; + break; + + default: + isSg[c] = 1; + break; + } + } + } + + // 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(); + + const V dqs = subset(dx, Span(np*nw, 1, varstart)); + varstart += dqs.size(); + const V dbhp = subset(dx, Span(nw, 1, varstart)); + varstart += dbhp.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; + } + + const int pos = pu.phase_pos[ Oil ]; + const V so_old = s_old.col(pos); + so = so_old - step * dso; + } + + // Appleyard chop process. + auto ixg = sg < 0; + for (int c = 0; c < nc; ++c) { + if (ixg[c]) { + sw[c] = sw[c] / (1-sg[c]); + so[c] = so[c] / (1-sg[c]); + sg[c] = 0; + } + } + + + auto ixo = so < 0; + for (int c = 0; c < nc; ++c) { + if (ixo[c]) { + sw[c] = sw[c] / (1-so[c]); + sg[c] = sg[c] / (1-so[c]); + so[c] = 0; + } + } + + auto ixw = sw < 0; + for (int c = 0; c < nc; ++c) { + if (ixw[c]) { + so[c] = so[c] / (1-sw[c]); + sg[c] = sg[c] / (1-sw[c]); + sw[c] = 0; + } + } + + const V sumSat = sw + so + sg; + sw = sw / sumSat; + so = so / sumSat; + sg = sg / sumSat; + + // Update the reservoir_state + for (int c = 0; c < nc; ++c) { + reservoir_state.saturation()[c*np + pu.phase_pos[ Water ]] = sw[c]; + } + + for (int c = 0; c < nc; ++c) { + reservoir_state.saturation()[c*np + pu.phase_pos[ Gas ]] = sg[c]; + } + + if (active_[ Oil ]) { + const int pos = pu.phase_pos[ Oil ]; + for (int c = 0; c < nc; ++c) { + reservoir_state.saturation()[c*np + pos] = so[c]; + } + } + + // 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); + rs = rs_old - drs_limited; + } + 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); + rv = rv_old - drv_limited; + } + + + // 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::fill(primalVariable_.begin(), primalVariable_.end(), PrimalVariables::Sg); + + if (has_disgas_) { + const V rsSat0 = fluidRsSat(p_old, s_old.col(pu.phase_pos[Oil]), cells_); + const V rsSat = fluidRsSat(p, so, cells_); + // 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]; + } else { + primalVariable_[c] = PrimalVariables::RS; + } + } + + } + + // 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_); + + // 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]; + } else { + primalVariable_[c] = PrimalVariables::RV; + } + } + + } + + // Update the reservoir_state + 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()); + } + + if( wellsActive() ) + { + // Qs update. + // Since we need to update the wellrates, that are ordered by wells, + // from dqs which are ordered by phase, the simplest is to compute + // dwr, which is the data from dqs but ordered by wells. + const DataBlock wwr = Eigen::Map(dqs.data(), np, nw).transpose(); + const V dwr = Eigen::Map(wwr.data(), nw*np); + const V wr_old = Eigen::Map(&well_state.wellRates()[0], nw*np); + const V wr = wr_old - dwr; + std::copy(&wr[0], &wr[0] + wr.size(), well_state.wellRates().begin()); + + // Bhp update. + const V bhp_old = Eigen::Map(&well_state.bhp()[0], nw, 1); + const V dbhp_limited = sign(dbhp) * dbhp.abs().min(bhp_old.abs()*dpmaxrel); + const V bhp = bhp_old - dbhp_limited; + std::copy(&bhp[0], &bhp[0] + bhp.size(), well_state.bhp().begin()); + } + + // Update phase conditions used for property calculations. + updatePhaseCondFromPrimalVariable(); + } + + + + + + template + std::vector + BlackoilModel::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 + BlackoilModel::computePressures(const SolutionState& state) const + { + using namespace Opm::AutoDiffGrid; + const int nc = numCells(grid_); + + const ADB null = ADB::constant(V::Zero(nc)); + + const Opm::PhaseUsage& pu = fluid_.phaseUsage(); + const ADB& sw = (active_[ Water ] + ? state.saturation[ pu.phase_pos[ Water ] ] + : null); + + const ADB& so = (active_[ Oil ] + ? state.saturation[ pu.phase_pos[ Oil ] ] + : null); + + const ADB& sg = (active_[ Gas ] + ? state.saturation[ pu.phase_pos[ Gas ] ] + : null); + return computePressures(state.pressure, sw, so, sg); + + + } + + + + + + template + std::vector + BlackoilModel:: + 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; + 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 (phaseIdx == BlackoilPhases::Aqua) { + pressure[phaseIdx] = po - pressure[phaseIdx]; + } else { + pressure[phaseIdx] += po; + } + } + + return pressure; + } + + + + + + template + V + BlackoilModel::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 + BlackoilModel::computeMassFlux(const int actph , + const V& transi, + const ADB& kr , + const ADB& phasePressure, + const SolutionState& state) + { + const int canonicalPhaseIdx = canph_[ actph ]; + + const std::vector cond = phaseCondition(); + + const ADB tr_mult = transMult(state.pressure); + const ADB mu = fluidViscosity(canonicalPhaseIdx, phasePressure, state.temperature, state.rs, state.rv,cond, cells_); + + rq_[ actph ].mob = tr_mult * kr / mu; + + const ADB rho = fluidDensity(canonicalPhaseIdx, phasePressure, state.temperature, state.rs, state.rv,cond, cells_); + + ADB& head = rq_[ actph ].head; + + // compute gravity potensial using the face average as in eclipse and MRST + const ADB rhoavg = ops_.caver * rho; + + ADB dp = ops_.ngrad * phasePressure - geo_.gravity()[2] * (rhoavg * (ops_.ngrad * geo_.z().matrix())); + + if (use_threshold_pressure_) { + applyThresholdPressures(dp); + } + + head = transi*dp; + //head = transi*(ops_.ngrad * phasePressure) + gflux; + + UpwindSelector upwind(grid_, ops_, head.value()); + + const ADB& b = rq_[ actph ].b; + const ADB& mob = rq_[ actph ].mob; + rq_[ actph ].mflux = upwind.select(b * mob) * head; + // OPM_AD_DUMP(rq_[ actph ].mob); + // OPM_AD_DUMP(rq_[ actph ].mflux); + } + + + + + + template + void + BlackoilModel::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_interior_face_).template cast(); + + // Create a sparse vector that nullifies the low potential elements. + const M keep_high_potential = spdiag(high_potential); + + // Find the current sign for the threshold modification + const V sign_dp = sign(dp.value()); + const V threshold_modification = sign_dp * threshold_pressures_by_interior_face_; + + // Modify potential and nullify where appropriate. + dp = keep_high_potential * (dp - threshold_modification); + } + + + + + template + std::vector + BlackoilModel::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 + BlackoilModel::convergenceReduction(const Eigen::Array& B, + const Eigen::Array& tempV, + const Eigen::Array& R, + std::array& R_sum, + std::array& maxCoeff, + std::array& B_avg, + std::vector& maxNormWell, + int nc, + int nw) const + { + // 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(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); + maxNormWell[idx] = 0.0; + for ( int w=0; w(nc_and_pv); + } + else +#endif + { + for ( int idx=0; idx + bool + BlackoilModel::getConvergence(const double dt, const int iteration) + { + const double tol_mb = param_.tolerance_mb_; + const double tol_cnv = param_.tolerance_cnv_; + const double tol_wells = param_.tolerance_wells_; + + const int nc = Opm::AutoDiffGrid::numCells(grid_); + const int nw = wellsActive() ? wells().number_of_wells : 0; + const Opm::PhaseUsage& pu = fluid_.phaseUsage(); + + const V pv = geo_.poreVolume(); + + const std::vector cond = phaseCondition(); + + std::array CNV = {{0., 0., 0.}}; + std::array R_sum = {{0., 0., 0.}}; + std::array B_avg = {{0., 0., 0.}}; + std::array maxCoeff = {{0., 0., 0.}}; + std::array mass_balance_residual = {{0., 0., 0.}}; + std::array well_flux_residual = {{0., 0., 0.}}; + std::size_t cols = MaxNumPhases; // needed to pass the correct type to Eigen + Eigen::Array B(nc, cols); + Eigen::Array R(nc, cols); + Eigen::Array tempV(nc, cols); + std::vector maxNormWell(MaxNumPhases); + + for ( int idx=0; idx maxResidualAllowed() || + std::isnan(mass_balance_residual[Oil]) || mass_balance_residual[Oil] > maxResidualAllowed() || + std::isnan(mass_balance_residual[Gas]) || mass_balance_residual[Gas] > maxResidualAllowed() || + std::isnan(CNV[Water]) || CNV[Water] > maxResidualAllowed() || + std::isnan(CNV[Oil]) || CNV[Oil] > maxResidualAllowed() || + std::isnan(CNV[Gas]) || CNV[Gas] > maxResidualAllowed() || + std::isnan(well_flux_residual[Water]) || well_flux_residual[Water] > maxResidualAllowed() || + std::isnan(well_flux_residual[Oil]) || well_flux_residual[Oil] > maxResidualAllowed() || + std::isnan(well_flux_residual[Gas]) || well_flux_residual[Gas] > maxResidualAllowed() || + std::isnan(residualWell) || residualWell > maxResidualAllowed() ) + { + OPM_THROW(Opm::NumericalProblem,"One of the residuals is NaN or to large!"); + } + + if ( terminal_output_ ) + { + // Only rank 0 does print to std::cout + if (iteration == 0) { + std::cout << "\nIter MB(WATER) MB(OIL) MB(GAS) CNVW CNVO CNVG W-FLUX(W) W-FLUX(O) W-FLUX(G)\n"; + } + const std::streamsize oprec = std::cout.precision(3); + const std::ios::fmtflags oflags = std::cout.setf(std::ios::scientific); + std::cout << std::setw(4) << iteration + << std::setw(11) << mass_balance_residual[Water] + << std::setw(11) << mass_balance_residual[Oil] + << std::setw(11) << mass_balance_residual[Gas] + << std::setw(11) << CNV[Water] + << std::setw(11) << CNV[Oil] + << std::setw(11) << CNV[Gas] + << std::setw(11) << well_flux_residual[Water] + << std::setw(11) << well_flux_residual[Oil] + << std::setw(11) << well_flux_residual[Gas] + << std::endl; + std::cout.precision(oprec); + std::cout.flags(oflags); + } + return converged; + } + + + + + template + ADB + BlackoilModel::fluidViscosity(const int phase, + const ADB& p , + const ADB& temp , + const ADB& rs , + const ADB& rv , + const std::vector& cond, + const std::vector& cells) 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 + BlackoilModel::fluidReciprocFVF(const int phase, + const ADB& p , + const ADB& temp , + const ADB& rs , + const ADB& rv , + const std::vector& cond, + const std::vector& cells) 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 + BlackoilModel::fluidDensity(const int phase, + const ADB& p , + const ADB& temp , + const ADB& rs , + const ADB& rv , + const std::vector& cond, + const std::vector& cells) const + { + const double* rhos = fluid_.surfaceDensity(); + ADB b = fluidReciprocFVF(phase, p, temp, rs, rv, cond, cells); + ADB rho = V::Constant(p.size(), 1, rhos[phase]) * b; + if (phase == Oil && active_[Gas]) { + // It is correct to index into rhos with canonical phase indices. + rho += V::Constant(p.size(), 1, rhos[Gas]) * rs * b; + } + if (phase == Gas && active_[Oil]) { + // It is correct to index into rhos with canonical phase indices. + rho += V::Constant(p.size(), 1, rhos[Oil]) * rv * b; + } + return rho; + } + + + + + + template + V + BlackoilModel::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 + BlackoilModel::fluidRsSat(const ADB& p, + const ADB& satOil, + const std::vector& cells) const + { + return fluid_.rsSat(p, satOil, cells); + } + + + template + V + BlackoilModel::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 + BlackoilModel::fluidRvSat(const ADB& p, + const ADB& satOil, + const std::vector& cells) const + { + return fluid_.rvSat(p, satOil, cells); + } + + + + template + ADB + BlackoilModel::poroMult(const ADB& p) const + { + const int n = p.size(); + if (rock_comp_props_ && rock_comp_props_->isActive()) { + V pm(n); + V dpm(n); + 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 = spdiag(dpm); + const int num_blocks = p.numBlocks(); + std::vector jacs(num_blocks); + 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 + BlackoilModel::transMult(const ADB& p) const + { + const int n = p.size(); + if (rock_comp_props_ && rock_comp_props_->isActive()) { + V tm(n); + V dtm(n); + 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 = spdiag(dtm); + const int num_blocks = p.numBlocks(); + std::vector jacs(num_blocks); + 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 + BlackoilModel:: + classifyCondition(const SolutionState& state, + std::vector& cond ) const + { + const PhaseUsage& pu = fluid_.phaseUsage(); + + if (active_[ Gas ]) { + // Oil/Gas or Water/Oil/Gas system + const int po = pu.phase_pos[ Oil ]; + const int pg = pu.phase_pos[ Gas ]; + + const V& so = state.saturation[ po ].value(); + const V& sg = state.saturation[ pg ].value(); + + cond.resize(sg.size()); + + for (V::Index c = 0, e = sg.size(); c != e; ++c) { + if (so[c] > 0) { cond[c].setFreeOil (); } + if (sg[c] > 0) { cond[c].setFreeGas (); } + if (active_[ Water ]) { cond[c].setFreeWater(); } + } + } + else { + // Water/Oil system + assert (active_[ Water ]); + + const int po = pu.phase_pos[ Oil ]; + const V& so = state.saturation[ po ].value(); + + cond.resize(so.size()); + + for (V::Index c = 0, e = so.size(); c != e; ++c) { + cond[c].setFreeWater(); + + if (so[c] > 0) { cond[c].setFreeOil(); } + } + } + } */ + + + template + void + BlackoilModel::classifyCondition(const BlackoilState& 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 + BlackoilModel::updatePrimalVariableFromState(const BlackoilState& 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); + + // Water/Oil/Gas system + assert (active_[ Gas ]); + + // reset the primary variables if RV and RS is not set Sg is used as primary variable. + primalVariable_.resize(nc); + std::fill(primalVariable_.begin(), primalVariable_.end(), PrimalVariables::Sg); + + const V sg = s.col(pu.phase_pos[ Gas ]); + const V so = s.col(pu.phase_pos[ Oil ]); + const V sw = s.col(pu.phase_pos[ Water ]); + + const double epsilon = std::sqrt(std::numeric_limits::epsilon()); + auto watOnly = sw > (1 - epsilon); + auto hasOil = so > 0; + auto hasGas = sg > 0; + + // For oil only cells Rs is used as primal variable. For cells almost full of water + // the default primal variable (Sg) is used. + if (has_disgas_) { + for (V::Index c = 0, e = sg.size(); c != e; ++c) { + if ( !watOnly[c] && hasOil[c] && !hasGas[c] ) {primalVariable_[c] = PrimalVariables::RS; } + } + } + + // For gas only cells Rv is used as primal variable. For cells almost full of water + // the default primal variable (Sg) is used. + if (has_vapoil_) { + for (V::Index c = 0, e = so.size(); c != e; ++c) { + if ( !watOnly[c] && hasGas[c] && !hasOil[c] ) {primalVariable_[c] = PrimalVariables::RV; } + } + } + updatePhaseCondFromPrimalVariable(); + } + + + + + + /// Update the phaseCondition_ member based on the primalVariable_ member. + template + void + BlackoilModel::updatePhaseCondFromPrimalVariable() + { + if (! active_[Gas]) { + OPM_THROW(std::logic_error, "updatePhaseCondFromPrimarVariable() logic requires active gas phase."); + } + const int nc = primalVariable_.size(); + for (int c = 0; c < nc; ++c) { + phaseCondition_[c] = PhasePresence(); // No free phases. + phaseCondition_[c].setFreeWater(); // Not necessary for property calculation usage. + switch (primalVariable_[c]) { + case PrimalVariables::Sg: + phaseCondition_[c].setFreeOil(); + phaseCondition_[c].setFreeGas(); + break; + case PrimalVariables::RS: + phaseCondition_[c].setFreeOil(); + break; + case PrimalVariables::RV: + phaseCondition_[c].setFreeGas(); + break; + default: + OPM_THROW(std::logic_error, "Unknown primary variable enum value in cell " << c << ": " << primalVariable_[c]); + } + } + } + + + + +} // namespace Opm + +#endif // OPM_BLACKOILMODEL_IMPL_HEADER_INCLUDED diff --git a/opm/autodiff/NewtonSolver.hpp b/opm/autodiff/NewtonSolver.hpp new file mode 100644 index 000000000..3f638c077 --- /dev/null +++ b/opm/autodiff/NewtonSolver.hpp @@ -0,0 +1,119 @@ +/* + Copyright 2015 SINTEF ICT, Applied Mathematics. + Copyright 2015 Statoil ASA. + + 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_NEWTONSOLVER_HEADER_INCLUDED +#define OPM_NEWTONSOLVER_HEADER_INCLUDED + +#include +#include + +namespace Opm { + + + /// A Newton solver class suitable for general fully-implicit models. + template + class NewtonSolver + { + public: + // --------- Types and enums --------- + typedef AutoDiffBlock ADB; + typedef ADB::V V; + typedef ADB::M M; + + // The Newton relaxation scheme type + enum RelaxType { DAMPEN, SOR }; + + // Solver parameters controlling nonlinear Newton process. + struct SolverParameters + { + enum RelaxType relax_type_; + double relax_max_; + double relax_increment_; + double relax_rel_tol_; + int max_iter_; // max newton iterations + int min_iter_; // min newton iterations + + explicit SolverParameters( const parameter::ParameterGroup& param ); + SolverParameters(); + + void reset(); + }; + + // Forwarding types from PhysicalModel. + typedef typename PhysicalModel::ReservoirState ReservoirState; + typedef typename PhysicalModel::WellState WellState; + + // --------- Public methods --------- + + /// Construct solver for a given model. + /// \param[in] param parameters controlling nonlinear Newton process + /// \param[in, out] model physical simulation model + explicit NewtonSolver(const SolverParameters& param, + PhysicalModel& model); + + /// Take a single forward step, after which the states will be modified + /// according to the physical model. + /// \param[in] dt time step size + /// \param[in] reservoir_state reservoir state variables + /// \param[in] well_state well state variables + /// \return number of linear iterations used + int + step(const double dt, + ReservoirState& reservoir_state, + WellState& well_state); + + /// Number of Newton iterations used in all calls to step(). + unsigned int newtonIterations() const; + + /// Number of linear solver iterations used in all calls to step(). + unsigned int linearIterations() const; + + /// Number of linear solver iterations used in the last call to step(). + unsigned int newtonIterationsLastStep() const; + + /// Number of linear solver iterations used in the last call to step(). + unsigned int linearIterationsLastStep() const; + + private: + // --------- Data members --------- + SolverParameters param_; + PhysicalModel& model_; + unsigned int newtonIterations_; + unsigned int linearIterations_; + unsigned int newtonIterationsLast_; + unsigned int linearIterationsLast_; + + // --------- Private methods --------- + enum RelaxType relaxType() const { return param_.relax_type_; } + double relaxMax() const { return param_.relax_max_; } + double relaxIncrement() const { return param_.relax_increment_; } + double relaxRelTol() const { return param_.relax_rel_tol_; } + double maxIter() const { return param_.max_iter_; } + double minIter() const { return param_.min_iter_; } + void detectNewtonOscillations(const std::vector>& residual_history, + const int it, const double relaxRelTol, + bool& oscillate, bool& stagnate) const; + void stabilizeNewton(V& dx, V& dxOld, const double omega, const RelaxType relax_type) const; + }; +} // namespace Opm + +#include "NewtonSolver_impl.hpp" + +#endif // OPM_NEWTONSOLVER_HEADER_INCLUDED diff --git a/opm/autodiff/NewtonSolver_impl.hpp b/opm/autodiff/NewtonSolver_impl.hpp new file mode 100644 index 000000000..1835decc9 --- /dev/null +++ b/opm/autodiff/NewtonSolver_impl.hpp @@ -0,0 +1,250 @@ +/* + Copyright 2013, 2015 SINTEF ICT, Applied Mathematics. + Copyright 2015 Dr. Blatt - HPC-Simulation-Software & Services + 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_NEWTONSOLVER_IMPL_HEADER_INCLUDED +#define OPM_NEWTONSOLVER_IMPL_HEADER_INCLUDED + +#include + +namespace Opm +{ + template + NewtonSolver::NewtonSolver(const SolverParameters& param, + PhysicalModel& model) + : param_(param), + model_(model), + newtonIterations_(0), + linearIterations_(0) + { + } + + template + unsigned int NewtonSolver::newtonIterations () const + { + return newtonIterations_; + } + + template + unsigned int NewtonSolver::linearIterations () const + { + return linearIterations_; + } + + + template + int + NewtonSolver:: + step(const double dt, + ReservoirState& reservoir_state, + WellState& well_state) + { + // Do model-specific once-per-step calculations. + model_.prepareStep(dt, reservoir_state, well_state); + + // 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. + std::vector> residual_norms_history; + + // Assemble residual and Jacobian, store residual norms. + model_.assemble(reservoir_state, well_state, true); + residual_norms_history.push_back(model_.computeResidualNorms()); + + // Set up for main Newton loop. + double omega = 1.0; + int iteration = 0; + bool converged = model_.getConvergence(dt, iteration); + const int sizeNonLinear = model_.sizeNonLinear(); + V dxOld = V::Zero(sizeNonLinear); + bool isOscillate = false; + bool isStagnate = false; + const enum RelaxType relaxtype = relaxType(); + int linearIterations = 0; + + // ---------- Main Newton loop ---------- + while ( (!converged && (iteration < maxIter())) || (minIter() > iteration)) { + // Compute the Newton update to the primary variables. + V dx = model_.solveJacobianSystem(); + + // Store number of linear iterations used. + linearIterations += model_.linearIterationsLastSolve(); + + // Stabilize the Newton update. + detectNewtonOscillations(residual_norms_history, iteration, relaxRelTol(), isOscillate, isStagnate); + if (isOscillate) { + omega -= relaxIncrement(); + omega = std::max(omega, relaxMax()); + if (model_.terminalOutputEnabled()) { + std::cout << " Oscillating behavior detected: Relaxation set to " << omega << std::endl; + } + } + stabilizeNewton(dx, dxOld, omega, relaxtype); + + // Apply the update, the model may apply model-dependent + // limitations and chopping of the update. + model_.updateState(dx, reservoir_state, well_state); + + // Assemble residual and Jacobian, store residual norms. + model_.assemble(reservoir_state, well_state, false); + residual_norms_history.push_back(model_.computeResidualNorms()); + + // increase iteration counter + ++iteration; + + converged = model_.getConvergence(dt, iteration); + } + + if (!converged) { + if (model_.terminalOutputEnabled()) { + std::cerr << "WARNING: Failed to compute converged solution in " << iteration << " iterations." << std::endl; + } + return -1; // -1 indicates that the solver has to be restarted + } + + linearIterations_ += linearIterations; + newtonIterations_ += iteration; + linearIterationsLast_ = linearIterations; + newtonIterationsLast_ = iteration; + + // Do model-specific post-step actions. + model_.afterStep(dt, reservoir_state, well_state); + + return linearIterations; + } + + + + template + void NewtonSolver::SolverParameters:: + reset() + { + // default values for the solver parameters + relax_type_ = DAMPEN; + relax_max_ = 0.5; + relax_increment_ = 0.1; + relax_rel_tol_ = 0.2; + max_iter_ = 15; + min_iter_ = 1; + } + + template + NewtonSolver::SolverParameters:: + SolverParameters() + { + // set default values + reset(); + } + + template + NewtonSolver::SolverParameters:: + SolverParameters( const parameter::ParameterGroup& param ) + { + // set default values + reset(); + + // overload with given parameters + relax_max_ = param.getDefault("relax_max", relax_max_); + max_iter_ = param.getDefault("max_iter", max_iter_); + min_iter_ = param.getDefault("min_iter", min_iter_); + + std::string relaxation_type = param.getDefault("relax_type", std::string("dampen")); + if (relaxation_type == "dampen") { + relax_type_ = DAMPEN; + } else if (relaxation_type == "sor") { + relax_type_ = SOR; + } else { + OPM_THROW(std::runtime_error, "Unknown Relaxtion Type " << relaxation_type); + } + } + + template + void + NewtonSolver::detectNewtonOscillations(const std::vector>& residual_history, + const int it, const double relaxRelTol, + bool& oscillate, bool& stagnate) const + { + // The detection of oscillation in two primary variable results in the report of the detection + // of oscillation for the solver. + // Only the saturations are used for oscillation detection for the black oil model. + // Stagnate is not used for any treatment here. + + if ( it < 2 ) { + oscillate = false; + stagnate = false; + return; + } + + stagnate = true; + int oscillatePhase = 0; + const std::vector& F0 = residual_history[it]; + const std::vector& F1 = residual_history[it - 1]; + const std::vector& F2 = residual_history[it - 2]; + for (int p= 0; p < model_.numPhases(); ++p){ + const double d1 = std::abs((F0[p] - F2[p]) / F0[p]); + const double d2 = std::abs((F0[p] - F1[p]) / F0[p]); + + oscillatePhase += (d1 < relaxRelTol) && (relaxRelTol < d2); + + // Process is 'stagnate' unless at least one phase + // exhibits significant residual change. + stagnate = (stagnate && !(std::abs((F1[p] - F2[p]) / F2[p]) > 1.0e-3)); + } + + oscillate = (oscillatePhase > 1); + } + + + template + void + NewtonSolver::stabilizeNewton(V& dx, V& dxOld, const double omega, + const RelaxType relax_type) const + { + // The dxOld is updated with dx. + // If omega is equal to 1., no relaxtion will be appiled. + + const V tempDxOld = dxOld; + dxOld = dx; + + switch (relax_type) { + case DAMPEN: + if (omega == 1.) { + return; + } + dx = dx*omega; + return; + case SOR: + if (omega == 1.) { + return; + } + dx = dx*omega + (1.-omega)*tempDxOld; + return; + default: + OPM_THROW(std::runtime_error, "Can only handle DAMPEN and SOR relaxation type."); + } + + return; + } + + +} // namespace Opm + + +#endif // OPM_FULLYIMPLICITSOLVER_IMPL_HEADER_INCLUDED diff --git a/opm/autodiff/SimulatorFullyImplicitBlackoil_impl.hpp b/opm/autodiff/SimulatorFullyImplicitBlackoil_impl.hpp index a2740789b..3274acaa2 100644 --- a/opm/autodiff/SimulatorFullyImplicitBlackoil_impl.hpp +++ b/opm/autodiff/SimulatorFullyImplicitBlackoil_impl.hpp @@ -24,7 +24,8 @@ #include #include -#include +#include +#include #include #include #include @@ -231,7 +232,13 @@ namespace Opm std::string tstep_filename = output_writer_.outputDirectory() + "/step_timing.txt"; std::ofstream tstep_os(tstep_filename.c_str()); - typename FullyImplicitBlackoilSolver::SolverParameter solverParam( param_ ); + typedef T Grid; + typedef BlackoilModel Model; + typedef typename Model::ModelParameters ModelParams; + ModelParams modelParams( param_ ); + typedef NewtonSolver Solver; + typedef typename Solver::SolverParameters SolverParams; + SolverParams solverParams( param_ ); // adaptive time stepping std::unique_ptr< AdaptiveTimeStepping > adaptiveTimeStepping; @@ -291,10 +298,11 @@ namespace Opm // Run a multiple steps of the solver depending on the time step control. solver_timer.start(); - FullyImplicitBlackoilSolver solver(solverParam, grid_, props_, geo_, rock_comp_props_, wells, solver_, has_disgas_, has_vapoil_, terminal_output_); + Model model(modelParams, grid_, props_, geo_, rock_comp_props_, wells, solver_, has_disgas_, has_vapoil_, terminal_output_); if (!threshold_pressures_by_face_.empty()) { - solver.setThresholdPressures(threshold_pressures_by_face_); + model.setThresholdPressures(threshold_pressures_by_face_); } + Solver solver(solverParams, model); // If sub stepping is enabled allow the solver to sub cycle // in case the report steps are to large for the solver to converge