/* 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_BLACKOILPOLYMERMODEL_IMPL_HEADER_INCLUDED #define OPM_BLACKOILPOLYMERMODEL_IMPL_HEADER_INCLUDED #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Opm { namespace detail { template int polymerPos(const PU& pu) { const int maxnp = Opm::BlackoilPhases::MaxNumPhases; int pos = 0; for (int phase = 0; phase < maxnp; ++phase) { if (pu.phase_used[phase]) { pos++; } } return pos; } } // namespace detail template BlackoilPolymerModel::BlackoilPolymerModel(const typename Base::ModelParameters& param, const Grid& grid, const BlackoilPropsAdInterface& fluid, const DerivedGeology& geo, const RockCompressibility* rock_comp_props, const PolymerPropsAd& polymer_props_ad, const Wells* wells, const NewtonIterationBlackoilInterface& linsolver, const bool has_disgas, const bool has_vapoil, const bool has_polymer, const bool has_plyshlog, const bool terminal_output) : Base(param, grid, fluid, geo, rock_comp_props, wells, linsolver, has_disgas, has_vapoil, terminal_output), polymer_props_ad_(polymer_props_ad), has_polymer_(has_polymer), has_plyshlog_(has_plyshlog), poly_pos_(detail::polymerPos(fluid.phaseUsage())) { if (has_polymer_) { if (!active_[Water]) { OPM_THROW(std::logic_error, "Polymer must solved in water!\n"); } // If deck has polymer, residual_ should contain polymer equation. rq_.resize(fluid_.numPhases() + 1); residual_.material_balance_eq.resize(fluid_.numPhases() + 1, ADB::null()); assert(poly_pos_ == fluid_.numPhases()); } } template void BlackoilPolymerModel:: prepareStep(const double dt, ReservoirState& reservoir_state, WellState& well_state) { Base::prepareStep(dt, reservoir_state, well_state); // Initial max concentration of this time step from PolymerBlackoilState. cmax_ = Eigen::Map(reservoir_state.maxconcentration().data(), Opm::AutoDiffGrid::numCells(grid_)); } template void BlackoilPolymerModel:: afterStep(const double /* dt */, ReservoirState& reservoir_state, WellState& /* well_state */) { computeCmax(reservoir_state); } template void BlackoilPolymerModel::makeConstantState(SolutionState& state) const { Base::makeConstantState(state); state.concentration = ADB::constant(state.concentration.value()); } template std::vector BlackoilPolymerModel::variableStateInitials(const ReservoirState& x, const WellState& xw) const { std::vector vars0 = Base::variableStateInitials(x, xw); assert(int(vars0.size()) == fluid_.numPhases() + 2); // Initial polymer concentration. if (has_polymer_) { assert (not x.concentration().empty()); const int nc = x.concentration().size(); const V c = Eigen::Map(&x.concentration()[0], nc); // Concentration belongs after other reservoir vars but before well vars. auto concentration_pos = vars0.begin() + fluid_.numPhases(); assert(concentration_pos == vars0.end() - 2); vars0.insert(concentration_pos, c); } return vars0; } template std::vector BlackoilPolymerModel::variableStateIndices() const { std::vector ind = Base::variableStateIndices(); assert(ind.size() == 5); if (has_polymer_) { ind.resize(6); // Concentration belongs after other reservoir vars but before well vars. ind[Concentration] = fluid_.numPhases(); // Concentration is pushing back the well vars. ++ind[Qs]; ++ind[Bhp]; } return ind; } template typename BlackoilPolymerModel::SolutionState BlackoilPolymerModel::variableStateExtractVars(const ReservoirState& x, const std::vector& indices, std::vector& vars) const { SolutionState state = Base::variableStateExtractVars(x, indices, vars); if (has_polymer_) { state.concentration = std::move(vars[indices[Concentration]]); } return state; } template void BlackoilPolymerModel::computeAccum(const SolutionState& state, const int aix ) { Base::computeAccum(state, aix); // Compute accumulation of polymer equation only if needed. if (has_polymer_) { const ADB& press = state.pressure; const std::vector& sat = state.saturation; const ADB& c = state.concentration; const ADB pv_mult = poroMult(press); // also computed in Base::computeAccum, could be optimized. const Opm::PhaseUsage& pu = fluid_.phaseUsage(); // compute polymer properties. const ADB cmax = ADB::constant(cmax_, state.concentration.blockPattern()); const ADB ads = polymer_props_ad_.adsorption(state.concentration, cmax); const double rho_rock = polymer_props_ad_.rockDensity(); const V phi = Eigen::Map(&fluid_.porosity()[0], AutoDiffGrid::numCells(grid_)); const double dead_pore_vol = polymer_props_ad_.deadPoreVol(); // Compute polymer accumulation term. rq_[poly_pos_].accum[aix] = pv_mult * rq_[pu.phase_pos[Water]].b * sat[pu.phase_pos[Water]] * c * (1. - dead_pore_vol) + pv_mult * rho_rock * (1. - phi) / phi * ads; } } template void BlackoilPolymerModel::computeCmax(ReservoirState& state) { const int nc = AutoDiffGrid::numCells(grid_); V tmp = V::Zero(nc); for (int i = 0; i < nc; ++i) { tmp[i] = std::max(state.maxconcentration()[i], state.concentration()[i]); } std::copy(&tmp[0], &tmp[0] + nc, state.maxconcentration().begin()); } template void BlackoilPolymerModel:: assembleMassBalanceEq(const SolutionState& state) { Base::assembleMassBalanceEq(state); // Add polymer equation. if (has_polymer_) { residual_.material_balance_eq[poly_pos_] = pvdt_ * (rq_[poly_pos_].accum[1] - rq_[poly_pos_].accum[0]) + ops_.div*rq_[poly_pos_].mflux; } } template void BlackoilPolymerModel::extraAddWellEq(const SolutionState& state, const WellState& xw, const std::vector& cq_ps, const std::vector& cmix_s, const ADB& cqt_is, const std::vector& well_cells) { // Add well contributions to polymer mass balance equation if (has_polymer_) { const ADB mc = computeMc(state); const int nc = xw.polymerInflow().size(); const V polyin = Eigen::Map(xw.polymerInflow().data(), nc); const V poly_in_perf = subset(polyin, well_cells); const V poly_mc_perf = subset(mc, well_cells).value(); const PhaseUsage& pu = fluid_.phaseUsage(); const ADB cq_s_poly = cq_ps[pu.phase_pos[Water]] * poly_mc_perf + cmix_s[pu.phase_pos[Water]] * cqt_is * poly_in_perf; residual_.material_balance_eq[poly_pos_] -= superset(cq_s_poly, well_cells, nc); } } template void BlackoilPolymerModel::updateState(const V& dx, ReservoirState& reservoir_state, WellState& well_state) { if (has_polymer_) { // Extract concentration change. const int np = fluid_.numPhases(); const int nc = Opm::AutoDiffGrid::numCells(grid_); const V zero = V::Zero(nc); const int concentration_start = nc * np; const V dc = subset(dx, Span(nc, 1, concentration_start)); // Create new dx with the dc part deleted. V modified_dx = V::Zero(dx.size() - nc); modified_dx.head(concentration_start) = dx.head(concentration_start); const int tail_len = dx.size() - concentration_start - nc; modified_dx.tail(tail_len) = dx.tail(tail_len); // Call base version. Base::updateState(modified_dx, reservoir_state, well_state); // Update concentration. const V c_old = Eigen::Map(&reservoir_state.concentration()[0], nc, 1); const V c = (c_old - dc).max(zero); std::copy(&c[0], &c[0] + nc, reservoir_state.concentration().begin()); } else { // Just forward call to base version. Base::updateState(dx, reservoir_state, well_state); } } template void BlackoilPolymerModel::computeMassFlux(const int actph , const V& transi, const ADB& kr , const ADB& phasePressure, const SolutionState& state) { Base::computeMassFlux(actph, transi, kr, phasePressure, state); // Polymer treatment. const int canonicalPhaseIdx = canph_[ actph ]; if (canonicalPhaseIdx == Water) { if (has_polymer_) { 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_); const ADB cmax = ADB::constant(cmax_, state.concentration.blockPattern()); const ADB mc = computeMc(state); const ADB krw_eff = polymer_props_ad_.effectiveRelPerm(state.concentration, cmax, kr); const ADB inv_wat_eff_visc = polymer_props_ad_.effectiveInvWaterVisc(state.concentration, mu.value().data()); // Reduce mobility of water phase by relperm reduction and effective viscosity increase. rq_[actph].mob = tr_mult * krw_eff * inv_wat_eff_visc; // Compute polymer mobility. rq_[poly_pos_].mob = tr_mult * mc * krw_eff * inv_wat_eff_visc; rq_[poly_pos_].b = rq_[actph].b; rq_[poly_pos_].dh = rq_[actph].dh; UpwindSelector upwind(grid_, ops_, rq_[poly_pos_].dh.value()); // Compute polymer flux. rq_[poly_pos_].mflux = upwind.select(rq_[poly_pos_].b * rq_[poly_pos_].mob) * (transi * rq_[poly_pos_].dh); // Must recompute water flux since we have to use modified mobilities. rq_[ actph ].mflux = upwind.select(rq_[actph].b * rq_[actph].mob) * (transi * rq_[actph].dh); } } } template double BlackoilPolymerModel::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); if (idx != MaxNumPhases) { // We do not compute a well flux residual for polymer. maxNormWell[idx] = 0.0; for ( int w=0; w(nc_and_pv); } else #endif { for ( int idx=0; idx bool BlackoilPolymerModel::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., 0.}}; std::array R_sum = {{0., 0., 0., 0.}}; std::array B_avg = {{0., 0., 0., 0.}}; std::array maxCoeff = {{0., 0., 0., 0.}}; std::array mass_balance_residual = {{0., 0., 0., 0.}}; std::array well_flux_residual = {{0., 0., 0.}}; std::size_t cols = MaxNumPhases+1; // 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(mass_balance_residual[MaxNumPhases]) || mass_balance_residual[MaxNumPhases] > maxResidualAllowed() || std::isnan(CNV[Water]) || CNV[Water] > maxResidualAllowed() || std::isnan(CNV[Oil]) || CNV[Oil] > maxResidualAllowed() || std::isnan(CNV[Gas]) || CNV[Gas] > maxResidualAllowed() || std::isnan(CNV[MaxNumPhases]) || CNV[MaxNumPhases] > 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 too large!"); } if ( terminal_output_ ) { // Only rank 0 does print to std::cout if (iteration == 0) { std::cout << "\nIter MB(WATER) MB(OIL) MB(GAS) MB(POLY) CNVW CNVO CNVG CNVP 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) << mass_balance_residual[MaxNumPhases] << std::setw(11) << CNV[Water] << std::setw(11) << CNV[Oil] << std::setw(11) << CNV[Gas] << std::setw(11) << CNV[MaxNumPhases] << 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 BlackoilPolymerModel::computeMc(const SolutionState& state) const { return polymer_props_ad_.polymerWaterVelocityRatio(state.concentration); } } // namespace Opm #endif // OPM_BLACKOILPOLYMERMODEL_IMPL_HEADER_INCLUDED