From abee2f9f8168343163c6d49f0f955cfe273f250e Mon Sep 17 00:00:00 2001 From: hnil Date: Tue, 7 Jun 2022 20:48:53 +0200 Subject: [PATCH 01/49] added fluxmodule refactured --- ebos/eclfluxmoduletpfa.hh | 551 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 551 insertions(+) create mode 100644 ebos/eclfluxmoduletpfa.hh diff --git a/ebos/eclfluxmoduletpfa.hh b/ebos/eclfluxmoduletpfa.hh new file mode 100644 index 000000000..ecd4f0399 --- /dev/null +++ b/ebos/eclfluxmoduletpfa.hh @@ -0,0 +1,551 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + 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 2 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 . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \brief This file contains the flux module which is used for ECL problems + * + * This approach to fluxes is very specific to two-point flux approximation and applies + * what the Eclipse Technical Description calls the "NEWTRAN" transmissibility approach. + */ +#ifndef EWOMS_ECL_FLUX_MODULE_HH +#define EWOMS_ECL_FLUX_MODULE_HH + +#include +#include +#include + +#include + +#include +#include + +namespace Opm { + +template +class EclTransIntensiveQuantities; + +template +class EclTransExtensiveQuantities; + +template +class EclTransBaseProblem; + +/*! + * \ingroup EclBlackOilSimulator + * \brief Specifies a flux module which uses ECL transmissibilities. + */ +template +struct EclTransFluxModule +{ + typedef EclTransIntensiveQuantities FluxIntensiveQuantities; + typedef EclTransExtensiveQuantities FluxExtensiveQuantities; + typedef EclTransBaseProblem FluxBaseProblem; + + /*! + * \brief Register all run-time parameters for the flux module. + */ + static void registerParameters() + { } +}; + +/*! + * \ingroup EclBlackOilSimulator + * \brief Provides the defaults for the parameters required by the + * transmissibility based volume flux calculation. + */ +template +class EclTransBaseProblem +{ }; + +/*! + * \ingroup EclBlackOilSimulator + * \brief Provides the intensive quantities for the ECL flux module + */ +template +class EclTransIntensiveQuantities +{ + using ElementContext = GetPropType; +protected: + void update_(const ElementContext&, unsigned, unsigned) + { } +}; + +/*! + * \ingroup EclBlackOilSimulator + * \brief Provides the ECL flux module + */ +template +class EclTransExtensiveQuantities +{ + using Implementation = GetPropType; + using IntensiveQuantities = GetPropType; + using FluidSystem = GetPropType; + using ElementContext = GetPropType; + using Scalar = GetPropType; + using Evaluation = GetPropType; + using GridView = GetPropType; + using MaterialLaw = GetPropType; + + enum { dimWorld = GridView::dimensionworld }; + enum { gasPhaseIdx = FluidSystem::gasPhaseIdx }; + enum { numPhases = FluidSystem::numPhases }; + enum { enableSolvent = getPropValue() }; + enum { enableExtbo = getPropValue() }; + enum { enableEnergy = getPropValue() }; + + typedef MathToolbox Toolbox; + typedef Dune::FieldVector DimVector; + typedef Dune::FieldVector EvalDimVector; + typedef Dune::FieldMatrix DimMatrix; + +public: + /*! + * \brief Return the intrinsic permeability tensor at a face [m^2] + */ + const DimMatrix& intrinsicPermeability() const + { + throw std::invalid_argument("The ECL transmissibility module does not provide an explicit intrinsic permeability"); + } + + /*! + * \brief Return the pressure potential gradient of a fluid phase at the + * face's integration point [Pa/m] + * + * \param phaseIdx The index of the fluid phase + */ + const EvalDimVector& potentialGrad(unsigned) const + { + throw std::invalid_argument("The ECL transmissibility module does not provide explicit potential gradients"); + } + + /*! + * \brief Return the gravity corrected pressure difference between the interior and + * the exterior of a face. + * + * \param phaseIdx The index of the fluid phase + */ + const Evaluation& pressureDifference(unsigned phaseIdx) const + { return pressureDifference_[phaseIdx]; } + + /*! + * \brief Return the filter velocity of a fluid phase at the face's integration point + * [m/s] + * + * \param phaseIdx The index of the fluid phase + */ + const EvalDimVector& filterVelocity(unsigned) const + { + throw std::invalid_argument("The ECL transmissibility module does not provide explicit filter velocities"); + } + + /*! + * \brief Return the volume flux of a fluid phase at the face's integration point + * \f$[m^3/s / m^2]\f$ + * + * This is the fluid volume of a phase per second and per square meter of face + * area. + * + * \param phaseIdx The index of the fluid phase + */ + const Evaluation& volumeFlux(unsigned phaseIdx) const + { return volumeFlux_[phaseIdx]; } + +protected: + /*! + * \brief Returns the local index of the degree of freedom in which is + * in upstream direction. + * + * i.e., the DOF which exhibits a higher effective pressure for + * the given phase. + */ + unsigned upstreamIndex_(unsigned phaseIdx) const + { + assert(phaseIdx < numPhases); + + return upIdx_[phaseIdx]; + } + + /*! + * \brief Returns the local index of the degree of freedom in which is + * in downstream direction. + * + * i.e., the DOF which exhibits a lower effective pressure for the + * given phase. + */ + unsigned downstreamIndex_(unsigned phaseIdx) const + { + assert(phaseIdx < numPhases); + + return dnIdx_[phaseIdx]; + } + + void updateSolvent(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { asImp_().updateVolumeFluxTrans(elemCtx, scvfIdx, timeIdx); } + + void updatePolymer(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { asImp_().updateShearMultipliers(elemCtx, scvfIdx, timeIdx); } + + + + template + void calculatePhasePressureDiff_(short& upIdx, + short& dnIdx, + EvalType& pressureDifference, + const IntensiveQuantities& intQuantsIn, + const IntensiveQuantities& intQuantsEx, + const unsigned scvfIdx, + const unsigned timeIdx, + const unsigned phaseIdx, + const unsigned interiorDofIdx, + const unsigned exteriorDofIdx, + const Scalar& Vin, + const Scalar& Vex, + const unsigned& globalIndexIn, + const unsigned& globalIndexEx, + const Scalar& distZg, + const Scalar& thpres + ) + { + + + // check shortcut: if the mobility of the phase is zero in the interior as + // well as the exterior DOF, we can skip looking at the phase. + if (intQuantsIn.mobility(phaseIdx) <= 0.0 && + intQuantsEx.mobility(phaseIdx) <= 0.0) + { + upIdx = interiorDofIdx; + dnIdx = exteriorDofIdx; + pressureDifference = 0.0; + return; + } + + // do the gravity correction: compute the hydrostatic pressure for the + // external at the depth of the internal one + const Evaluation& rhoIn = intQuantsIn.fluidState().density(phaseIdx); + Scalar rhoEx = Toolbox::value(intQuantsEx.fluidState().density(phaseIdx)); + Evaluation rhoAvg = (rhoIn + rhoEx)/2; + + const Evaluation& pressureInterior = intQuantsIn.fluidState().pressure(phaseIdx); + Evaluation pressureExterior = Toolbox::value(intQuantsEx.fluidState().pressure(phaseIdx)); + if (enableExtbo) // added stability; particulary useful for solvent migrating in pure water + // where the solvent fraction displays a 0/1 behaviour ... + pressureExterior += Toolbox::value(rhoAvg)*(distZg); + else + pressureExterior += rhoAvg*(distZg); + + pressureDifference = pressureExterior - pressureInterior; + + // decide the upstream index for the phase. for this we make sure that the + // degree of freedom which is regarded upstream if both pressures are equal + // is always the same: if the pressure is equal, the DOF with the lower + // global index is regarded to be the upstream one. + if (pressureDifference_[phaseIdx] > 0.0) { + upIdx = exteriorDofIdx; + dnIdx = interiorDofIdx; + } + else if (pressureDifference_[phaseIdx] < 0.0) { + upIdx = interiorDofIdx; + dnIdx = exteriorDofIdx; + } + else { + // if the pressure difference is zero, we chose the DOF which has the + // larger volume associated to it as upstream DOF + + if (Vin > Vex) { + upIdx = interiorDofIdx; + dnIdx = exteriorDofIdx; + } + else if (Vin < Vex) { + upIdx = exteriorDofIdx; + dnIdx = interiorDofIdx; + } + else { + assert(Vin == Vex); + // if the volumes are also equal, we pick the DOF which exhibits the + // smaller global index + if (globalIndexIn < globalIndexEx) { + upIdx = interiorDofIdx; + dnIdx = exteriorDofIdx; + } + else { + upIdx = exteriorDofIdx; + dnIdx = interiorDofIdx; + } + } + } + + // apply the threshold pressure for the intersection. note that the concept + // of threshold pressure is a quite big hack that only makes sense for ECL + // datasets. (and even there, its physical justification is quite + // questionable IMO.) + if (std::abs(Toolbox::value(pressureDifference)) > thpres) { + if (pressureDifference < 0.0) + pressureDifference += thpres; + else + pressureDifference -= thpres; + } + else { + pressureDifference = 0.0; + } + } + + + /*! + * \brief Update the required gradients for interior faces + */ + void calculateGradients_(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { + Valgrind::SetUndefined(*this); + + const auto& problem = elemCtx.problem(); + const auto& stencil = elemCtx.stencil(timeIdx); + const auto& scvf = stencil.interiorFace(scvfIdx); + + interiorDofIdx_ = scvf.interiorIndex(); + exteriorDofIdx_ = scvf.exteriorIndex(); + assert(interiorDofIdx_ != exteriorDofIdx_); + + //unsigned I = stencil.globalSpaceIndex(interiorDofIdx_); + //unsigned J = stencil.globalSpaceIndex(exteriorDofIdx_); + Scalar Vin = elemCtx.dofVolume(interiorDofIdx_, /*timeIdx=*/0); + Scalar Vex = elemCtx.dofVolume(exteriorDofIdx_, /*timeIdx=*/0); + const auto& globalIndexIn = stencil.globalSpaceIndex(interiorDofIdx_); + const auto& globalIndexEx = stencil.globalSpaceIndex(exteriorDofIdx_); + Scalar trans = problem.transmissibility(elemCtx, interiorDofIdx_, exteriorDofIdx_); + Scalar faceArea = scvf.area(); + Scalar thpres = problem.thresholdPressure(globalIndexIn, globalIndexEx); + + // estimate the gravity correction: for performance reasons we use a simplified + // approach for this flux module that assumes that gravity is constant and always + // acts into the downwards direction. (i.e., no centrifuge experiments, sorry.) + Scalar g = elemCtx.problem().gravity()[dimWorld - 1]; + + const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx_, timeIdx); + const auto& intQuantsEx = elemCtx.intensiveQuantities(exteriorDofIdx_, timeIdx); + + // this is quite hacky because the dune grid interface does not provide a + // cellCenterDepth() method (so we ask the problem to provide it). The "good" + // solution would be to take the Z coordinate of the element centroids, but since + // ECL seems to like to be inconsistent on that front, it needs to be done like + // here... + Scalar zIn = problem.dofCenterDepth(elemCtx, interiorDofIdx_, timeIdx); + Scalar zEx = problem.dofCenterDepth(elemCtx, exteriorDofIdx_, timeIdx); + + // the distances from the DOF's depths. (i.e., the additional depth of the + // exterior DOF) + Scalar distZ = zIn - zEx; + + for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { + if (!FluidSystem::phaseIsActive(phaseIdx)) + continue; + calculatePhasePressureDiff_(upIdx_[phaseIdx], + dnIdx_[phaseIdx], + pressureDifference_[phaseIdx], + intQuantsIn, + intQuantsEx, + scvfIdx,//input + timeIdx,//input + phaseIdx,//input + interiorDofIdx_,//input + exteriorDofIdx_,//intput + Vin, + Vex, + globalIndexIn, + globalIndexEx, + distZ*g, + thpres); + if(pressureDifference_[phaseIdx] == 0){ + volumeFlux_[phaseIdx] = 0.0; + continue; + } + IntensiveQuantities up; + unsigned globalIndex; + if(upIdx_[phaseIdx] == interiorDofIdx_){ + up = intQuantsIn; + globalIndex = globalIndexIn; + }else{ + up = intQuantsEx; + globalIndex = globalIndexEx; + } + // TODO: should the rock compaction transmissibility multiplier be upstreamed + // or averaged? all fluids should see the same compaction?! + //const auto& globalIndex = stencil.globalSpaceIndex(upstreamIdx); + const Evaluation& transMult = + problem.template rockCompTransMultiplier(up, globalIndex); + + if (upIdx_[phaseIdx] == interiorDofIdx_) + volumeFlux_[phaseIdx] = + pressureDifference_[phaseIdx]*up.mobility(phaseIdx)*transMult*(-trans/faceArea); + else + volumeFlux_[phaseIdx] = + pressureDifference_[phaseIdx]*(Toolbox::value(up.mobility(phaseIdx))*Toolbox::value(transMult)*(-trans/faceArea)); + + } + } + + + /*! + * \brief Update the required gradients for boundary faces + */ + template + void calculateBoundaryGradients_(const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx, + const FluidState& exFluidState) + { + const auto& problem = elemCtx.problem(); + + bool enableBoundaryMassFlux = problem.nonTrivialBoundaryConditions(); + if (!enableBoundaryMassFlux) + return; + + const auto& stencil = elemCtx.stencil(timeIdx); + const auto& scvf = stencil.boundaryFace(scvfIdx); + + interiorDofIdx_ = scvf.interiorIndex(); + + Scalar trans = problem.transmissibilityBoundary(elemCtx, scvfIdx); + Scalar faceArea = scvf.area(); + + // estimate the gravity correction: for performance reasons we use a simplified + // approach for this flux module that assumes that gravity is constant and always + // acts into the downwards direction. (i.e., no centrifuge experiments, sorry.) + Scalar g = elemCtx.problem().gravity()[dimWorld - 1]; + + const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx_, timeIdx); + + // this is quite hacky because the dune grid interface does not provide a + // cellCenterDepth() method (so we ask the problem to provide it). The "good" + // solution would be to take the Z coordinate of the element centroids, but since + // ECL seems to like to be inconsistent on that front, it needs to be done like + // here... + Scalar zIn = problem.dofCenterDepth(elemCtx, interiorDofIdx_, timeIdx); + Scalar zEx = scvf.integrationPos()[dimWorld - 1]; + + // the distances from the DOF's depths. (i.e., the additional depth of the + // exterior DOF) + Scalar distZ = zIn - zEx; + + for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { + if (!FluidSystem::phaseIsActive(phaseIdx)) + continue; + + // do the gravity correction: compute the hydrostatic pressure for the + // integration position + const Evaluation& rhoIn = intQuantsIn.fluidState().density(phaseIdx); + const auto& rhoEx = exFluidState.density(phaseIdx); + Evaluation rhoAvg = (rhoIn + rhoEx)/2; + + const Evaluation& pressureInterior = intQuantsIn.fluidState().pressure(phaseIdx); + Evaluation pressureExterior = exFluidState.pressure(phaseIdx); + pressureExterior += rhoAvg*(distZ*g); + + pressureDifference_[phaseIdx] = pressureExterior - pressureInterior; + + // decide the upstream index for the phase. for this we make sure that the + // degree of freedom which is regarded upstream if both pressures are equal + // is always the same: if the pressure is equal, the DOF with the lower + // global index is regarded to be the upstream one. + if (pressureDifference_[phaseIdx] > 0.0) { + upIdx_[phaseIdx] = -1; + dnIdx_[phaseIdx] = interiorDofIdx_; + } + else { + upIdx_[phaseIdx] = interiorDofIdx_; + dnIdx_[phaseIdx] = -1; + } + + Evaluation transModified = trans; + + short upstreamIdx = upstreamIndex_(phaseIdx); + if (upstreamIdx == interiorDofIdx_) { + + // this is slightly hacky because in the automatic differentiation case, it + // only works for the element centered finite volume method. for ebos this + // does not matter, though. + const auto& up = elemCtx.intensiveQuantities(upstreamIdx, timeIdx); + + // deal with water induced rock compaction + transModified *= problem.template rockCompTransMultiplier(up, stencil.globalSpaceIndex(upstreamIdx)); + + volumeFlux_[phaseIdx] = + pressureDifference_[phaseIdx]*up.mobility(phaseIdx)*(-transModified/faceArea); + + if (enableSolvent && phaseIdx == gasPhaseIdx) + asImp_().setSolventVolumeFlux( pressureDifference_[phaseIdx]*up.solventMobility()*(-transModified/faceArea)); + } + else { + // compute the phase mobility using the material law parameters of the + // interior element. TODO: this could probably be done more efficiently + const auto& matParams = + elemCtx.problem().materialLawParams(elemCtx, + interiorDofIdx_, + /*timeIdx=*/0); + typename FluidState::Scalar kr[numPhases]; + MaterialLaw::relativePermeabilities(kr, matParams, exFluidState); + + const auto& mob = kr[phaseIdx]/exFluidState.viscosity(phaseIdx); + volumeFlux_[phaseIdx] = + pressureDifference_[phaseIdx]*mob*(-transModified/faceArea); + + // Solvent inflow is not yet supported + if (enableSolvent && phaseIdx == gasPhaseIdx) + asImp_().setSolventVolumeFlux(0.0); + } + } + } + + /*! + * \brief Update the volumetric fluxes for all fluid phases on the interior faces of the context + */ + void calculateFluxes_(const ElementContext&, unsigned, unsigned) + { } + + void calculateBoundaryFluxes_(const ElementContext&, unsigned, unsigned) + {} + +private: + Implementation& asImp_() + { return *static_cast(this); } + + const Implementation& asImp_() const + { return *static_cast(this); } + + // the volumetric flux of all phases [m^3/s] + Evaluation volumeFlux_[numPhases]; + + // the difference in effective pressure between the exterior and the interior degree + // of freedom [Pa] + Evaluation pressureDifference_[numPhases]; + + // the local indices of the interior and exterior degrees of freedom + unsigned short interiorDofIdx_; + unsigned short exteriorDofIdx_; + short upIdx_[numPhases]; + short dnIdx_[numPhases]; +}; + +}// namespace Opm + +#endif From 18b03f3546062cd9ecf233ef557b918cfc743a30 Mon Sep 17 00:00:00 2001 From: hnil Date: Wed, 8 Jun 2022 09:27:51 +0200 Subject: [PATCH 02/49] working refactoring --- CMakeLists.txt | 12 +++++ ebos/eclfluxmoduletpfa.hh | 78 ++++++++++++++--------------- flow/flow_blackoil_tpfa.cpp | 97 +++++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 39 deletions(-) create mode 100644 flow/flow_blackoil_tpfa.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d4fcea02..786559c80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -478,6 +478,18 @@ opm_add_test(flow_poly $) target_compile_definitions(flow_poly PRIVATE USE_POLYHEDRALGRID) +opm_add_test(flow_blackoil_tpfa + ONLY_COMPILE + ALWAYS_ENABLE + DEFAULT_ENABLE_IF ${FLOW_POLY_ONLY_DEFAULT_ENABLE_IF} + DEPENDS opmsimulators + LIBRARIES opmsimulators + SOURCES + flow/flow_blackoil_tpfa.cpp + # $ + $) +#target_compile_definitions(flow_blackoil_tpfa PRIVATE USE_POLYHEDRALGRID) + opm_add_test(flow_distribute_z ONLY_COMPILE ALWAYS_ENABLE diff --git a/ebos/eclfluxmoduletpfa.hh b/ebos/eclfluxmoduletpfa.hh index ecd4f0399..8e03b96c3 100644 --- a/ebos/eclfluxmoduletpfa.hh +++ b/ebos/eclfluxmoduletpfa.hh @@ -28,8 +28,8 @@ * This approach to fluxes is very specific to two-point flux approximation and applies * what the Eclipse Technical Description calls the "NEWTRAN" transmissibility approach. */ -#ifndef EWOMS_ECL_FLUX_MODULE_HH -#define EWOMS_ECL_FLUX_MODULE_HH +#ifndef EWOMS_ECL_FLUX_TPFA_MODULE_HH +#define EWOMS_ECL_FLUX_TPFA_MODULE_HH #include #include @@ -51,52 +51,52 @@ class EclTransExtensiveQuantities; template class EclTransBaseProblem; -/*! - * \ingroup EclBlackOilSimulator - * \brief Specifies a flux module which uses ECL transmissibilities. - */ -template -struct EclTransFluxModule -{ - typedef EclTransIntensiveQuantities FluxIntensiveQuantities; - typedef EclTransExtensiveQuantities FluxExtensiveQuantities; - typedef EclTransBaseProblem FluxBaseProblem; +// /*! +// * \ingroup EclBlackOilSimulator +// * \brief Specifies a flux module which uses ECL transmissibilities. +// */ +// template +// struct EclTransFluxModule +// { +// typedef EclTransIntensiveQuantities FluxIntensiveQuantities; +// typedef EclTransExtensiveQuantities FluxExtensiveQuantities; +// typedef EclTransBaseProblem FluxBaseProblem; - /*! - * \brief Register all run-time parameters for the flux module. - */ - static void registerParameters() - { } -}; +// /*! +// * \brief Register all run-time parameters for the flux module. +// */ +// static void registerParameters() +// { } +// }; -/*! - * \ingroup EclBlackOilSimulator - * \brief Provides the defaults for the parameters required by the - * transmissibility based volume flux calculation. - */ -template -class EclTransBaseProblem -{ }; +// /*! +// * \ingroup EclBlackOilSimulator +// * \brief Provides the defaults for the parameters required by the +// * transmissibility based volume flux calculation. +// */ +// template +// class EclTransBaseProblem +// { }; -/*! - * \ingroup EclBlackOilSimulator - * \brief Provides the intensive quantities for the ECL flux module - */ -template -class EclTransIntensiveQuantities -{ - using ElementContext = GetPropType; -protected: - void update_(const ElementContext&, unsigned, unsigned) - { } -}; +// /*! +// * \ingroup EclBlackOilSimulator +// * \brief Provides the intensive quantities for the ECL flux module +// */ +// template +// class EclTransIntensiveQuantities +// { +// using ElementContext = GetPropType; +// protected: +// void update_(const ElementContext&, unsigned, unsigned) +// { } +// }; /*! * \ingroup EclBlackOilSimulator * \brief Provides the ECL flux module */ template -class EclTransExtensiveQuantities +class EclTransExtensiveQuantitiesTPFA { using Implementation = GetPropType; using IntensiveQuantities = GetPropType; diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp new file mode 100644 index 000000000..a79ac7363 --- /dev/null +++ b/flow/flow_blackoil_tpfa.cpp @@ -0,0 +1,97 @@ +/* + 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 . +*/ +#include "config.h" +#include +#include +#include +// modifications from standard +#include +#include +namespace Opm { + namespace Properties { + namespace TTag { + struct EclFlowProblemTPFA { + using InheritsFrom = std::tuple; + }; + } + + } +} + +namespace Opm { + namespace Properties { + template + struct LocalLinearizerSplice { + using type = TTag::AutoDiffLocalLinearizer; + }; + } +} +namespace Opm { + namespace Properties { + template + struct LocalResidual { using type = BlackOilLocalResidualTPFA; }; + } +} +namespace Opm{ + template + struct EclTransFluxModuleTPFA + { + typedef EclTransIntensiveQuantities FluxIntensiveQuantities; + typedef EclTransExtensiveQuantitiesTPFA FluxExtensiveQuantities; + typedef EclTransBaseProblem FluxBaseProblem; + + /*! + * \brief Register all run-time parameters for the flux module. + */ + static void registerParameters() + { } + }; +} +namespace Opm { + namespace Properties { + + template + struct FluxModule { + using type = EclTransFluxModuleTPFA; + }; + } +} +// namespace Opm { +// namespace Properties { +// template +// struct Linearizer { +// using type = TTag::AutoDiffLocalLinearizer; +// }; +// } +// } + + +// namespace Opm { +// namespace Properties { +// template +// struct FluxModule { +// using type = TTag::EclTransFluxMudule; +// }; +// } +// } + +int main(int argc, char** argv) +{ + using TypeTag = Opm::Properties::TTag::EclFlowProblemTPFA; + auto mainObject = Opm::Main(argc, argv); + return mainObject.runStatic(); +} From ad668081583ed3b133901c94050bc5b5dfb79945 Mon Sep 17 00:00:00 2001 From: hnil Date: Wed, 8 Jun 2022 22:39:20 +0200 Subject: [PATCH 03/49] better test --- ebos/eclfluxmoduletpfa.hh | 50 +++++++++++++++++++++---------------- flow/flow_blackoil_tpfa.cpp | 6 ++++- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/ebos/eclfluxmoduletpfa.hh b/ebos/eclfluxmoduletpfa.hh index 8e03b96c3..4d82e27dc 100644 --- a/ebos/eclfluxmoduletpfa.hh +++ b/ebos/eclfluxmoduletpfa.hh @@ -169,7 +169,10 @@ public: * \param phaseIdx The index of the fluid phase */ const Evaluation& volumeFlux(unsigned phaseIdx) const - { return volumeFlux_[phaseIdx]; } + { + throw std::invalid_argument("Should not acces volume flux for eclmoduletpfa"); + return volumeFlux_[phaseIdx]; + } protected: /*! @@ -181,6 +184,7 @@ protected: */ unsigned upstreamIndex_(unsigned phaseIdx) const { + throw std::invalid_argument("No upstreamIndex"); assert(phaseIdx < numPhases); return upIdx_[phaseIdx]; @@ -195,6 +199,7 @@ protected: */ unsigned downstreamIndex_(unsigned phaseIdx) const { + throw std::invalid_argument("No downstream index"); assert(phaseIdx < numPhases); return dnIdx_[phaseIdx]; @@ -207,24 +212,24 @@ protected: { asImp_().updateShearMultipliers(elemCtx, scvfIdx, timeIdx); } - +public: template - void calculatePhasePressureDiff_(short& upIdx, - short& dnIdx, - EvalType& pressureDifference, - const IntensiveQuantities& intQuantsIn, - const IntensiveQuantities& intQuantsEx, - const unsigned scvfIdx, - const unsigned timeIdx, - const unsigned phaseIdx, - const unsigned interiorDofIdx, - const unsigned exteriorDofIdx, - const Scalar& Vin, - const Scalar& Vex, - const unsigned& globalIndexIn, - const unsigned& globalIndexEx, - const Scalar& distZg, - const Scalar& thpres + static void calculatePhasePressureDiff_(short& upIdx, + short& dnIdx, + EvalType& pressureDifference, + const IntensiveQuantities& intQuantsIn, + const IntensiveQuantities& intQuantsEx, + const unsigned scvfIdx, + const unsigned timeIdx, + const unsigned phaseIdx, + const unsigned interiorDofIdx, + const unsigned exteriorDofIdx, + const Scalar& Vin, + const Scalar& Vex, + const unsigned& globalIndexIn, + const unsigned& globalIndexEx, + const Scalar& distZg, + const Scalar& thpres ) { @@ -260,11 +265,11 @@ protected: // degree of freedom which is regarded upstream if both pressures are equal // is always the same: if the pressure is equal, the DOF with the lower // global index is regarded to be the upstream one. - if (pressureDifference_[phaseIdx] > 0.0) { + if (pressureDifference > 0.0) { upIdx = exteriorDofIdx; dnIdx = interiorDofIdx; } - else if (pressureDifference_[phaseIdx] < 0.0) { + else if (pressureDifference < 0.0) { upIdx = interiorDofIdx; dnIdx = exteriorDofIdx; } @@ -310,12 +315,14 @@ protected: } } - +protected: /*! * \brief Update the required gradients for interior faces */ void calculateGradients_(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) { + throw std::invalid_argument("No calculateGradients_"); + Valgrind::SetUndefined(*this); const auto& problem = elemCtx.problem(); @@ -414,6 +421,7 @@ protected: unsigned timeIdx, const FluidState& exFluidState) { + throw std::invalid_argument("No calculateGradients for boundary"); const auto& problem = elemCtx.problem(); bool enableBoundaryMassFlux = problem.nonTrivialBoundaryConditions(); diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp index a79ac7363..2d9a3fbff 100644 --- a/flow/flow_blackoil_tpfa.cpp +++ b/flow/flow_blackoil_tpfa.cpp @@ -20,6 +20,8 @@ #include // modifications from standard #include +#include +#include #include namespace Opm { namespace Properties { @@ -36,7 +38,7 @@ namespace Opm { namespace Properties { template struct LocalLinearizerSplice { - using type = TTag::AutoDiffLocalLinearizer; + using type = TTag::AutoDiffLocalLinearizerTPFA; }; } } @@ -44,6 +46,8 @@ namespace Opm { namespace Properties { template struct LocalResidual { using type = BlackOilLocalResidualTPFA; }; + template + struct DiscLocalResidual { using type = FvBaseLocalResidualTPFA; }; } } namespace Opm{ From 95308d7d021e92106b7052362a18182806779459 Mon Sep 17 00:00:00 2001 From: hnil Date: Thu, 9 Jun 2022 15:08:20 +0200 Subject: [PATCH 04/49] ensure intensive quantities is updated --- opm/simulators/flow/SimulatorFullyImplicitBlackoilEbos.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/opm/simulators/flow/SimulatorFullyImplicitBlackoilEbos.hpp b/opm/simulators/flow/SimulatorFullyImplicitBlackoilEbos.hpp index de628562c..35e9fd06d 100644 --- a/opm/simulators/flow/SimulatorFullyImplicitBlackoilEbos.hpp +++ b/opm/simulators/flow/SimulatorFullyImplicitBlackoilEbos.hpp @@ -218,7 +218,8 @@ public: ebosSimulator_.setEpisodeIndex(-1); ebosSimulator_.setEpisodeLength(0.0); ebosSimulator_.setTimeStepSize(0.0); - + // make cach upto date. no nead for updating it in elementCtx + ebosSimulator_.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0); wellModel_().beginReportStep(timer.currentStepNum()); ebosSimulator_.problem().writeOutput(); From b1f1981f16967a0c3009ac5178ad60a93ab8ae28 Mon Sep 17 00:00:00 2001 From: hnil Date: Thu, 9 Jun 2022 15:08:46 +0200 Subject: [PATCH 05/49] changed flow_blackoil_tpfa with small intensive quantities --- flow/flow_blackoil_tpfa.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp index 2d9a3fbff..e013505aa 100644 --- a/flow/flow_blackoil_tpfa.cpp +++ b/flow/flow_blackoil_tpfa.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include namespace Opm { namespace Properties { @@ -48,6 +49,9 @@ namespace Opm { struct LocalResidual { using type = BlackOilLocalResidualTPFA; }; template struct DiscLocalResidual { using type = FvBaseLocalResidualTPFA; }; + template + struct ElementContext { using type = SmallElementContext; }; + //struct ElementContext { using type = FvBaseElementContext; }; } } namespace Opm{ From 0a178daaf1afb0556d8be647d15f7cefbf424576 Mon Sep 17 00:00:00 2001 From: hnil Date: Fri, 10 Jun 2022 22:52:31 +0200 Subject: [PATCH 06/49] added extra function for easier use in tracer,aquifer, threshold pressure --- ebos/eclfluxmoduletpfa.hh | 94 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/ebos/eclfluxmoduletpfa.hh b/ebos/eclfluxmoduletpfa.hh index 4d82e27dc..e4b3cc52a 100644 --- a/ebos/eclfluxmoduletpfa.hh +++ b/ebos/eclfluxmoduletpfa.hh @@ -315,6 +315,100 @@ public: } } + static void volumeAndPhasePressureDifferences(short (&upIdx)[numPhases] , + Evaluation (&volumeFlux)[numPhases], + Evaluation (&pressureDifferences)[numPhases], + const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { + + //Valgrind::SetUndefined(*this); + + const auto& problem = elemCtx.problem(); + const auto& stencil = elemCtx.stencil(timeIdx); + const auto& scvf = stencil.interiorFace(scvfIdx); + + unsigned interiorDofIdx = scvf.interiorIndex(); + unsigned exteriorDofIdx = scvf.exteriorIndex(); + assert(interiorDofIdx != exteriorDofIdx); + + //unsigned I = stencil.globalSpaceIndex(interiorDofIdx_); + //unsigned J = stencil.globalSpaceIndex(exteriorDofIdx_); + Scalar Vin = elemCtx.dofVolume(interiorDofIdx, /*timeIdx=*/0); + Scalar Vex = elemCtx.dofVolume(exteriorDofIdx, /*timeIdx=*/0); + const auto& globalIndexIn = stencil.globalSpaceIndex(interiorDofIdx); + const auto& globalIndexEx = stencil.globalSpaceIndex(exteriorDofIdx); + Scalar trans = problem.transmissibility(elemCtx, interiorDofIdx, exteriorDofIdx); + Scalar faceArea = scvf.area(); + Scalar thpres = problem.thresholdPressure(globalIndexIn, globalIndexEx); + + // estimate the gravity correction: for performance reasons we use a simplified + // approach for this flux module that assumes that gravity is constant and always + // acts into the downwards direction. (i.e., no centrifuge experiments, sorry.) + Scalar g = elemCtx.problem().gravity()[dimWorld - 1]; + + const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx, timeIdx); + const auto& intQuantsEx = elemCtx.intensiveQuantities(exteriorDofIdx, timeIdx); + + // this is quite hacky because the dune grid interface does not provide a + // cellCenterDepth() method (so we ask the problem to provide it). The "good" + // solution would be to take the Z coordinate of the element centroids, but since + // ECL seems to like to be inconsistent on that front, it needs to be done like + // here... + Scalar zIn = problem.dofCenterDepth(elemCtx, interiorDofIdx, timeIdx); + Scalar zEx = problem.dofCenterDepth(elemCtx, exteriorDofIdx, timeIdx); + + // the distances from the DOF's depths. (i.e., the additional depth of the + // exterior DOF) + Scalar distZ = zIn - zEx; + + for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { + if (!FluidSystem::phaseIsActive(phaseIdx)) + continue; + short dnIdx; + calculatePhasePressureDiff_(upIdx[phaseIdx], + dnIdx, + pressureDifferences[phaseIdx], + intQuantsIn, + intQuantsEx, + scvfIdx,//input + timeIdx,//input + phaseIdx,//input + interiorDofIdx,//input + exteriorDofIdx,//intput + Vin, + Vex, + globalIndexIn, + globalIndexEx, + distZ*g, + thpres); + if(pressureDifferences[phaseIdx] == 0){ + volumeFlux[phaseIdx] = 0.0; + continue; + } + IntensiveQuantities up; + unsigned globalIndex; + if(upIdx[phaseIdx] == interiorDofIdx){ + up = intQuantsIn; + globalIndex = globalIndexIn; + }else{ + up = intQuantsEx; + globalIndex = globalIndexEx; + } + // TODO: should the rock compaction transmissibility multiplier be upstreamed + // or averaged? all fluids should see the same compaction?! + //const auto& globalIndex = stencil.globalSpaceIndex(upstreamIdx); + const Evaluation& transMult = + problem.template rockCompTransMultiplier(up, globalIndex); + + if (upIdx[phaseIdx] == interiorDofIdx) + volumeFlux[phaseIdx] = + pressureDifferences[phaseIdx]*up.mobility(phaseIdx)*transMult*(-trans/faceArea); + else + volumeFlux[phaseIdx] = + pressureDifferences[phaseIdx]*(Toolbox::value(up.mobility(phaseIdx))*Toolbox::value(transMult)*(-trans/faceArea)); + + } + } protected: /*! * \brief Update the required gradients for interior faces From 060877a08bec25da8fb0c1062d23396f64f7ab78 Mon Sep 17 00:00:00 2001 From: hnil Date: Fri, 10 Jun 2022 22:58:32 +0200 Subject: [PATCH 07/49] runs first steps of norne --- ebos/eclthresholdpressure.hh | 126 ++++++++++++++++--- ebos/ecltracermodel.hh | 54 ++++++-- opm/simulators/aquifers/AquiferNumerical.hpp | 39 +++++- 3 files changed, 186 insertions(+), 33 deletions(-) diff --git a/ebos/eclthresholdpressure.hh b/ebos/eclthresholdpressure.hh index a3ef95e2f..d86a0c19e 100644 --- a/ebos/eclthresholdpressure.hh +++ b/ebos/eclthresholdpressure.hh @@ -39,6 +39,7 @@ #include #include +#include namespace Opm { /*! @@ -61,11 +62,15 @@ class EclThresholdPressure : public EclGenericThresholdPressure, GetPropType, GetPropType>; + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; using Simulator = GetPropType; using Scalar = GetPropType; using Evaluation = GetPropType; using ElementContext = GetPropType; using FluidSystem = GetPropType; + using GridView = GetPropType; + enum { dimWorld = GridView::dimensionworld }; enum { enableExperiments = getPropValue() }; enum { numPhases = FluidSystem::numPhases }; @@ -95,6 +100,95 @@ public: } private: + template + double calculateMaxDp(Face& face, Stencil& stencil, + ElemCtx& elemCtx,const unsigned& scvfIdx, + const unsigned& i,const unsigned& j,const unsigned& insideElemIdx,const unsigned& outsideElemIdx){ + typedef MathToolbox Toolbox; + elemCtx.updateIntensiveQuantities(/*timeIdx=*/0); + elemCtx.updateExtensiveQuantities(/*timeIdx=*/0); + // determine the maximum difference of the pressure of any phase over the + // intersection + Scalar pth = 0.0; + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, /*timeIdx=*/0); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + unsigned upIdx = extQuants.upstreamIndex(phaseIdx); + const auto& up = elemCtx.intensiveQuantities(upIdx, /*timeIdx=*/0); + + if (up.mobility(phaseIdx) > 0.0) { + Scalar phaseVal = Toolbox::value(extQuants.pressureDifference(phaseIdx)); + pth = std::max(pth, std::abs(phaseVal)); + } + } + return pth; + } + + template + double calculateMaxDp(Face& face, Stencil& stencil, + SmallElementContext& elemCtx,const unsigned& scvfIdx, + const unsigned& i,const unsigned& j, + const unsigned& insideElemIdx,const unsigned& outsideElemIdx){ + typedef MathToolbox Toolbox; + // determine the maximum difference of the pressure of any phase over the + // intersection + Scalar pth = 0.0; + //const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, /*timeIdx=*/0); + + Scalar Vin = elemCtx.dofVolume(i, /*timeIdx=*/0); + Scalar Vex = elemCtx.dofVolume(j, /*timeIdx=*/0); + + Scalar thpres = 0.0;//NB ??problem.thresholdPressure(globalIndexIn, globalIndexEx); + + // estimate the gravity correction: for performance reasons we use a simplified + // approach for this flux module that assumes that gravity is constant and always + // acts into the downwards direction. (i.e., no centrifuge experiments, sorry.) + const auto& problem = elemCtx.problem(); + Scalar g = problem.gravity()[dimWorld - 1]; + + const auto& intQuantsIn = elemCtx.intensiveQuantities(i, /*timeIdx*/0); + const auto& intQuantsEx = elemCtx.intensiveQuantities(j, /*timeIdx*/0); + + // this is quite hacky because the dune grid interface does not provide a + // cellCenterDepth() method (so we ask the problem to provide it). The "good" + // solution would be to take the Z coordinate of the element centroids, but since + // ECL seems to like to be inconsistent on that front, it needs to be done like + // here... + Scalar zIn = problem.dofCenterDepth(elemCtx, i, /*timeIdx*/0); + Scalar zEx = problem.dofCenterDepth(elemCtx, j, /*timeIdx*/0); + + // the distances from the DOF's depths. (i.e., the additional depth of the + // exterior DOF) + Scalar distZ = zIn - zEx; + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + short dnIdx; + // + short upIdx; + Evaluation pressureDifference; + ExtensiveQuantities::calculatePhasePressureDiff_(upIdx, + dnIdx, + pressureDifference, + intQuantsIn, + intQuantsEx, + scvfIdx,//input + /*timeIdx*/0,//input + phaseIdx,//input + i,//input + j,//intput + Vin, + Vex, + insideElemIdx, + outsideElemIdx, + distZ*g, + thpres); + const IntensiveQuantities& up = (upIdx == i) ? intQuantsIn : intQuantsEx; + if (up.mobility(phaseIdx) > 0.0) { + Scalar phaseVal = Toolbox::value(pressureDifference); + pth = std::max(pth, std::abs(phaseVal)); + } + } + return pth; + } // compute the defaults of the threshold pressures using the initial condition void computeDefaultThresholdPressures_() { @@ -107,16 +201,21 @@ private: auto elemIt = gridView.template begin(); const auto& elemEndIt = gridView.template end(); ElementContext elemCtx(simulator_); + simulator_.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0); for (; elemIt != elemEndIt; ++elemIt) { const auto& elem = *elemIt; if (elem.partitionType() != Dune::InteriorEntity) continue; - elemCtx.updateAll(elem); + elemCtx.updateStencil(elem); + // + const auto& stencil = elemCtx.stencil(/*timeIdx=*/0); + for (unsigned scvfIdx = 0; scvfIdx < stencil.numInteriorFaces(); ++ scvfIdx) { + const auto& face = stencil.interiorFace(scvfIdx); unsigned i = face.interiorIndex(); @@ -127,30 +226,21 @@ private: unsigned equilRegionInside = this->elemEquilRegion_[insideElemIdx]; unsigned equilRegionOutside = this->elemEquilRegion_[outsideElemIdx]; - + if (equilRegionInside == equilRegionOutside) // the current face is not at the boundary between EQUIL regions! continue; - + const auto& problem = elemCtx.problem(); // don't include connections with negligible flow - const Evaluation& trans = simulator_.problem().transmissibility(elemCtx, i, j); + const Evaluation& trans = problem.transmissibility(elemCtx, i, j); Scalar faceArea = face.area(); if (std::abs(faceArea*getValue(trans)) < 1e-18) continue; - - // determine the maximum difference of the pressure of any phase over the - // intersection - Scalar pth = 0.0; - const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, /*timeIdx=*/0); - for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { - unsigned upIdx = extQuants.upstreamIndex(phaseIdx); - const auto& up = elemCtx.intensiveQuantities(upIdx, /*timeIdx=*/0); - - if (up.mobility(phaseIdx) > 0.0) { - Scalar phaseVal = Toolbox::value(extQuants.pressureDifference(phaseIdx)); - pth = std::max(pth, std::abs(phaseVal)); - } - } + + double pth = calculateMaxDp(face, stencil, elemCtx, scvfIdx, + i, j, + insideElemIdx, outsideElemIdx); + // don't include connections with negligible flow int offset1 = equilRegionInside*this->numEquilRegions_ + equilRegionOutside; int offset2 = equilRegionOutside*this->numEquilRegions_ + equilRegionInside; diff --git a/ebos/ecltracermodel.hh b/ebos/ecltracermodel.hh index 8237cb872..4240d60c8 100644 --- a/ebos/ecltracermodel.hh +++ b/ebos/ecltracermodel.hh @@ -86,6 +86,9 @@ class EclTracerModel : public EclGenericTracerModel; + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; public: EclTracerModel(Simulator& simulator) : BaseType(simulator.vanguard().gridView(), @@ -224,7 +227,37 @@ protected: freeVolume = phaseVolume * variable(1.0, 0); } + //template + void getVolumeFlux(unsigned& upIdx, + Scalar& v, + const FvBaseElementContext& elemCtx, + const int tracerPhaseIdx, + unsigned scvfIdx + ){ + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, /*timeIdx*/ 0); + upIdx = extQuants.upstreamIndex(tracerPhaseIdx); + v = decay(extQuants.volumeFlux(tracerPhaseIdx)); + } + //template + void getVolumeFlux(unsigned& upIdx, + Scalar& v, + const SmallElementContext& elemCtx, + const int tracerPhaseIdx, + unsigned scvfIdx + ){ + short upIdxV[numPhases]; + Eval volumFlux[numPhases]; + Eval pressureDifferences[numPhases]; + ExtensiveQuantities::volumeAndPhasePressureDifferences(upIdxV , + volumFlux, + pressureDifferences, + elemCtx, + scvfIdx, + /*timeIdx*/ 0); + v = decay(volumFlux[tracerPhaseIdx]); + upIdx = upIdxV[tracerPhaseIdx] ; + } // evaluate the flux(es) over one face void computeFlux_(TracerEvaluation & freeFlux, bool & isUpFree, @@ -236,17 +269,18 @@ protected: { const auto& stencil = elemCtx.stencil(timeIdx); const auto& scvf = stencil.interiorFace(scvfIdx); - - const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); - unsigned inIdx = extQuants.interiorIndex(); - - unsigned upIdx = extQuants.upstreamIndex(tracerPhaseIdx); - + unsigned inIdx = scvf.interiorIndex(); + unsigned upIdx; + Scalar v; + getVolumeFlux(upIdx, + v, + elemCtx, + tracerPhaseIdx, + scvfIdx); const auto& intQuants = elemCtx.intensiveQuantities(upIdx, timeIdx); const auto& fs = intQuants.fluidState(); - Scalar A = scvf.area(); - Scalar v = decay(extQuants.volumeFlux(tracerPhaseIdx)); + Scalar b = decay(fs.invB(tracerPhaseIdx)); if (inIdx == upIdx) { @@ -397,7 +431,9 @@ protected: auto elemIt = simulator_.gridView().template begin(); auto elemEndIt = simulator_.gridView().template end(); for (; elemIt != elemEndIt; ++ elemIt) { - elemCtx.updateAll(*elemIt); + //elemCtx.updateAll(*elemIt); + elemCtx.updatePrimaryStencil(*elemIt); + elemCtx.updatePrimaryIntensiveQuantities(/*timIdx*/ 0.0); int globalDofIdx = elemCtx.globalSpaceIndex(0, /*timIdx=*/0); Scalar fVolume; computeVolume_(fVolume, tr.phaseIdx_, elemCtx, 0, /*timIdx=*/0); diff --git a/opm/simulators/aquifers/AquiferNumerical.hpp b/opm/simulators/aquifers/AquiferNumerical.hpp index 7f17701ff..32f2997f9 100644 --- a/opm/simulators/aquifers/AquiferNumerical.hpp +++ b/opm/simulators/aquifers/AquiferNumerical.hpp @@ -49,12 +49,14 @@ public: using MaterialLaw = GetPropType; enum { dimWorld = GridView::dimensionworld }; - + enum { numPhases = FluidSystem::numPhases }; static const int numEq = BlackoilIndices::numEq; using Eval = DenseAd::Evaluation; using Toolbox = MathToolbox; + using IntensiveQuantities = GetPropType; + using ExtensiveQuantities = GetPropType; // Constructor AquiferNumerical(const SingleNumericalAquifer& aquifer, const std::unordered_map& cartesian_to_compressed, @@ -250,6 +252,30 @@ private: return sum_pressure_watervolume / sum_watervolume; } + template + const double getWaterFlux(ElemCtx& elem_ctx,unsigned face_idx) const{ + const auto& exQuants = elem_ctx.extensiveQuantities(face_idx, /*timeIdx*/ 0); + const double water_flux = Toolbox::value(exQuants.volumeFlux(this->phaseIdx_())); + return water_flux; + } + + const double getWaterFlux(SmallElementContext& elem_ctx,unsigned face_idx) const{ + short upIdx[numPhases]; + + Eval volumFlux[numPhases]; + Eval pressureDifferences[numPhases]; + ExtensiveQuantities::volumeAndPhasePressureDifferences(upIdx , + volumFlux, + pressureDifferences, + elem_ctx, + face_idx, + /*timeIdx*/ 0); + return Toolbox::value(volumFlux[this->phaseIdx_()]); + } + + + + double calculateAquiferFluxRate() const { double aquifer_flux = 0.0; @@ -276,9 +302,7 @@ private: if (idx != 0) { continue; } - elem_ctx.updateAllIntensiveQuantities(); - elem_ctx.updateAllExtensiveQuantities(); - + const std::size_t num_interior_faces = elem_ctx.numInteriorFaces(/*timeIdx*/ 0); // const auto &problem = elem_ctx.problem(); const auto& stencil = elem_ctx.stencil(0); @@ -300,9 +324,11 @@ private: if (this->cell_to_aquifer_cell_idx_[J] > 0) { continue; } - const auto& exQuants = elem_ctx.extensiveQuantities(face_idx, /*timeIdx*/ 0); - const double water_flux = Toolbox::value(exQuants.volumeFlux(this->phaseIdx_())); + elem_ctx.updateAllIntensiveQuantities(); + elem_ctx.updateAllExtensiveQuantities(); + const double water_flux = getWaterFlux(elem_ctx,face_idx); + const std::size_t up_id = water_flux >= 0.0 ? i : j; const auto& intQuantsIn = elem_ctx.intensiveQuantities(up_id, 0); const double invB = Toolbox::value(intQuantsIn.fluidState().invB(this->phaseIdx_())); @@ -316,6 +342,7 @@ private: return aquifer_flux; } + }; } // namespace Opm #endif From d53b6ad9fa2ee289c5700ce77c1be597a2603797 Mon Sep 17 00:00:00 2001 From: hnil Date: Tue, 14 Jun 2022 21:03:09 +0200 Subject: [PATCH 08/49] added spesialisation for using simple property updating --- flow/flow_blackoil_tpfa.cpp | 6 +++++ opm/simulators/flow/BlackoilModelEbos.hpp | 30 +++++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp index e013505aa..bca00970f 100644 --- a/flow/flow_blackoil_tpfa.cpp +++ b/flow/flow_blackoil_tpfa.cpp @@ -20,6 +20,7 @@ #include // modifications from standard #include +#include #include #include #include @@ -76,6 +77,11 @@ namespace Opm { struct FluxModule { using type = EclTransFluxModuleTPFA; }; + template + struct IntensiveQuantities { + using type = BlackOilIntensiveQuantitiesSimple; + }; + } } // namespace Opm { diff --git a/opm/simulators/flow/BlackoilModelEbos.hpp b/opm/simulators/flow/BlackoilModelEbos.hpp index e0c6643cd..21d58aaf8 100644 --- a/opm/simulators/flow/BlackoilModelEbos.hpp +++ b/opm/simulators/flow/BlackoilModelEbos.hpp @@ -52,6 +52,8 @@ #include +#include + #include #if DUNE_VERSION_NEWER(DUNE_COMMON, 2, 7) #include @@ -170,6 +172,8 @@ namespace Opm { using Indices = GetPropType; using MaterialLaw = GetPropType; using MaterialLawParams = GetPropType; + using IntensiveQuantities = GetPropType; + using Problem = GetPropType; typedef double Scalar; static const int numEq = Indices::numEq; @@ -568,11 +572,28 @@ namespace Opm { ebosSolver.solve(x); } + template + void updateIntensiveQuantity(const Problem& /*problem*/, + const SolutionVector& /*solution*/, + const IntensiveQuantity*) + { + ebosSimulator_.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0); + } - + + void updateIntensiveQuantity(const Problem& problem, + const SolutionVector& solution, + const BlackOilIntensiveQuantitiesSimple* /*intensive*/) + { + ebosSimulator_.model().invalidateAndUpdateIntensiveQuantitiesSimple(problem, + solution, + /*timeIdx*/0); + } + /// Apply an update to the primary variables. void updateSolution(const BVector& dx) { + auto& problem = ebosSimulator_.problem(); auto& ebosNewtonMethod = ebosSimulator_.model().newtonMethod(); SolutionVector& solution = ebosSimulator_.model().solution(/*timeIdx=*/0); @@ -584,7 +605,12 @@ namespace Opm { // residual // if the solution is updated, the intensive quantities need to be recalculated - ebosSimulator_.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0); + //ebosSimulator_.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0); + IntensiveQuantities* dummy = NULL;//only for template spesialization + this->updateIntensiveQuantity(problem,solution, dummy); + //ebosSimulator_.model().invalidateAndUpdateIntensiveQuantitiesSimple(problem, + //solution, + // /*timeIdx=*/0); } /// Return true if output to cout is wanted. From 8f5e0940fe02b2567dead0c6e713767a890294f2 Mon Sep 17 00:00:00 2001 From: hnil Date: Fri, 17 Jun 2022 11:15:15 +0200 Subject: [PATCH 09/49] restructuring to be able to call without local indices --- ebos/eclfluxmoduletpfa.hh | 115 ++++++++++++++++-- ebos/eclthresholdpressure.hh | 1 - opm/simulators/flow/BlackoilModelEbos.hpp | 54 ++++++-- opm/simulators/wells/BlackoilWellModel.hpp | 6 + .../wells/BlackoilWellModel_impl.hpp | 16 +++ 5 files changed, 171 insertions(+), 21 deletions(-) diff --git a/ebos/eclfluxmoduletpfa.hh b/ebos/eclfluxmoduletpfa.hh index e4b3cc52a..9fbf40059 100644 --- a/ebos/eclfluxmoduletpfa.hh +++ b/ebos/eclfluxmoduletpfa.hh @@ -219,7 +219,6 @@ public: EvalType& pressureDifference, const IntensiveQuantities& intQuantsIn, const IntensiveQuantities& intQuantsEx, - const unsigned scvfIdx, const unsigned timeIdx, const unsigned phaseIdx, const unsigned interiorDofIdx, @@ -315,6 +314,106 @@ public: } } + // static void volumeAndPhasePressureDifferences(short (&upIdx)[numPhases] , + // Evaluation (&volumeFlux)[numPhases], + // Evaluation (&pressureDifferences)[numPhases], + // const Problem& problem, + // const unsigned globalIndexIn, + // const unsinged globalIndexOut, + // const IntensiveQuantities& intQuantsIn, + // const IntensiveQuantities& intQuantsIn, + // const unsinged timeIdx) + // { + + // //Valgrind::SetUndefined(*this); + + // //const auto& problem = elemCtx.problem(); + // //const auto& stencil = elemCtx.stencil(timeIdx); + // //const auto& scvf = stencil.interiorFace(scvfIdx); + // //unsigned interiorDofIdx = scvf.interiorIndex(); + // //unsigned exteriorDofIdx = scvf.exteriorIndex(); + // //const auto& globalIndexIn = stencil.globalSpaceIndex(interiorDofIdx); + // //const auto& globalIndexEx = stencil.globalSpaceIndex(exteriorDofIdx); + + + // assert(interiorDofIdx != exteriorDofIdx); + + // //unsigned I = stencil.globalSpaceIndex(interiorDofIdx_); + // //unsigned J = stencil.globalSpaceIndex(exteriorDofIdx_); + // Scalar Vin = model.dofTotalVolume(globalIndexIn, /*timeIdx=*/0); + // Scalar Vex = model.dofTotalVolume(globalIndexOut, /*timeIdx=*/0); + + + // Scalar trans = problem.transmissibility(globalIndexIn,globalIndexOut); + // Scalar faceArea = problem.area(globalIndexIn,globalIndexOut); + // Scalar thpres = problem.thresholdPressure(globalIndexIn, globalIndexEx); + + // // estimate the gravity correction: for performance reasons we use a simplified + // // approach for this flux module that assumes that gravity is constant and always + // // acts into the downwards direction. (i.e., no centrifuge experiments, sorry.) + // constexpr Scalar g = 9.8; + + + // // this is quite hacky because the dune grid interface does not provide a + // // cellCenterDepth() method (so we ask the problem to provide it). The "good" + // // solution would be to take the Z coordinate of the element centroids, but since + // // ECL seems to like to be inconsistent on that front, it needs to be done like + // // here... + // Scalar zIn = problem.dofCenterDepth(globalIndexIn, timeIdx); + // Scalar zEx = problem.dofCenterDepth(globalIndexOut, timeIdx); + + // // the distances from the DOF's depths. (i.e., the additional depth of the + // // exterior DOF) + // Scalar distZ = zIn - zEx; + + // for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { + // if (!FluidSystem::phaseIsActive(phaseIdx)) + // continue; + // short dnIdx; + // calculatePhasePressureDiff_(upIdx[phaseIdx], + // dnIdx, + // pressureDifferences[phaseIdx], + // intQuantsIn, + // intQuantsEx, + // timeIdx,//input + // phaseIdx,//input + // interiorDofIdx,//input + // exteriorDofIdx,//intput + // Vin, + // Vex, + // globalIndexIn, + // globalIndexEx, + // distZ*g, + // thpres); + // if(pressureDifferences[phaseIdx] == 0){ + // volumeFlux[phaseIdx] = 0.0; + // continue; + // } + // IntensiveQuantities up; + // unsigned globalIndex; + // if(upIdx[phaseIdx] == interiorDofIdx){ + // up = intQuantsIn; + // globalIndex = globalIndexIn; + // }else{ + // up = intQuantsEx; + // globalIndex = globalIndexEx; + // } + // // TODO: should the rock compaction transmissibility multiplier be upstreamed + // // or averaged? all fluids should see the same compaction?! + // //const auto& globalIndex = stencil.globalSpaceIndex(upstreamIdx); + // const Evaluation& transMult = + // problem.template rockCompTransMultiplier(up, globalIndex); + + // if (upIdx[phaseIdx] == interiorDofIdx) + // volumeFlux[phaseIdx] = + // pressureDifferences[phaseIdx]*up.mobility(phaseIdx)*transMult*(-trans/faceArea); + // else + // volumeFlux[phaseIdx] = + // pressureDifferences[phaseIdx]*(Toolbox::value(up.mobility(phaseIdx))*Toolbox::value(transMult)*(-trans/faceArea)); + + // } + // } + static void volumeAndPhasePressureDifferences(short (&upIdx)[numPhases] , Evaluation (&volumeFlux)[numPhases], Evaluation (&pressureDifferences)[numPhases], @@ -326,17 +425,20 @@ public: const auto& problem = elemCtx.problem(); const auto& stencil = elemCtx.stencil(timeIdx); const auto& scvf = stencil.interiorFace(scvfIdx); - unsigned interiorDofIdx = scvf.interiorIndex(); unsigned exteriorDofIdx = scvf.exteriorIndex(); + const auto& globalIndexIn = stencil.globalSpaceIndex(interiorDofIdx); + const auto& globalIndexEx = stencil.globalSpaceIndex(exteriorDofIdx); + + assert(interiorDofIdx != exteriorDofIdx); //unsigned I = stencil.globalSpaceIndex(interiorDofIdx_); //unsigned J = stencil.globalSpaceIndex(exteriorDofIdx_); Scalar Vin = elemCtx.dofVolume(interiorDofIdx, /*timeIdx=*/0); Scalar Vex = elemCtx.dofVolume(exteriorDofIdx, /*timeIdx=*/0); - const auto& globalIndexIn = stencil.globalSpaceIndex(interiorDofIdx); - const auto& globalIndexEx = stencil.globalSpaceIndex(exteriorDofIdx); + + Scalar trans = problem.transmissibility(elemCtx, interiorDofIdx, exteriorDofIdx); Scalar faceArea = scvf.area(); Scalar thpres = problem.thresholdPressure(globalIndexIn, globalIndexEx); @@ -344,7 +446,7 @@ public: // estimate the gravity correction: for performance reasons we use a simplified // approach for this flux module that assumes that gravity is constant and always // acts into the downwards direction. (i.e., no centrifuge experiments, sorry.) - Scalar g = elemCtx.problem().gravity()[dimWorld - 1]; + constexpr Scalar g = 9.8; const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx, timeIdx); const auto& intQuantsEx = elemCtx.intensiveQuantities(exteriorDofIdx, timeIdx); @@ -370,7 +472,6 @@ public: pressureDifferences[phaseIdx], intQuantsIn, intQuantsEx, - scvfIdx,//input timeIdx,//input phaseIdx,//input interiorDofIdx,//input @@ -465,7 +566,6 @@ protected: pressureDifference_[phaseIdx], intQuantsIn, intQuantsEx, - scvfIdx,//input timeIdx,//input phaseIdx,//input interiorDofIdx_,//input @@ -492,6 +592,7 @@ protected: // TODO: should the rock compaction transmissibility multiplier be upstreamed // or averaged? all fluids should see the same compaction?! //const auto& globalIndex = stencil.globalSpaceIndex(upstreamIdx); + //NB as long as this is upwinded it could be an intensive quantity const Evaluation& transMult = problem.template rockCompTransMultiplier(up, globalIndex); diff --git a/ebos/eclthresholdpressure.hh b/ebos/eclthresholdpressure.hh index d86a0c19e..8154f3d3c 100644 --- a/ebos/eclthresholdpressure.hh +++ b/ebos/eclthresholdpressure.hh @@ -170,7 +170,6 @@ private: pressureDifference, intQuantsIn, intQuantsEx, - scvfIdx,//input /*timeIdx*/0,//input phaseIdx,//input i,//input diff --git a/opm/simulators/flow/BlackoilModelEbos.hpp b/opm/simulators/flow/BlackoilModelEbos.hpp index 21d58aaf8..d01e11f65 100644 --- a/opm/simulators/flow/BlackoilModelEbos.hpp +++ b/opm/simulators/flow/BlackoilModelEbos.hpp @@ -574,20 +574,54 @@ namespace Opm { template void updateIntensiveQuantity(const Problem& /*problem*/, - const SolutionVector& /*solution*/, + SolutionVector& solution, + const BVector& dx, const IntensiveQuantity*) { + ebosSimulator_.model().newtonMethod().update_(/*nextSolution=*/solution, + /*curSolution=*/solution, + /*update=*/dx, + /*resid=*/dx); // the update routines of the black + // oil model do not care about the + // residual ebosSimulator_.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0); } void updateIntensiveQuantity(const Problem& problem, - const SolutionVector& solution, + SolutionVector& solution, + const BVector& dx, const BlackOilIntensiveQuantitiesSimple* /*intensive*/) { - ebosSimulator_.model().invalidateAndUpdateIntensiveQuantitiesSimple(problem, - solution, - /*timeIdx*/0); + auto& model = ebosSimulator_.model(); + auto& ebosNewtonMethod = model.newtonMethod(); + if(false){ + if (!std::isfinite(dx.one_norm())) + throw NumericalIssue("Non-finite update!"); + + size_t numGridDof = model.numGridDof(); + for (unsigned dofIdx = 0; dofIdx < numGridDof; ++dofIdx) { + ebosNewtonMethod.updatePrimaryVariables_(dofIdx, + solution[dofIdx], + solution[dofIdx], + dx[dofIdx], + dx[dofIdx]); + model.invalidateAndUpdateIntensiveSingleQuantitiesSimple(problem, + solution[dofIdx], + dofIdx, + /*timeIdx*/0); + } + }else{ + ebosNewtonMethod.update_(/*nextSolution*/solution, + /*curSolution=*/solution, + /*update=*/dx, + /*resid=*/dx); // the update routines of the black + // oil model do not care about the + // residual + model.invalidateAndUpdateIntensiveQuantitiesSimple(problem, + solution, + /*timeIdx*/0); + } } /// Apply an update to the primary variables. @@ -597,17 +631,11 @@ namespace Opm { auto& ebosNewtonMethod = ebosSimulator_.model().newtonMethod(); SolutionVector& solution = ebosSimulator_.model().solution(/*timeIdx=*/0); - ebosNewtonMethod.update_(/*nextSolution=*/solution, - /*curSolution=*/solution, - /*update=*/dx, - /*resid=*/dx); // the update routines of the black - // oil model do not care about the - // residual - + // if the solution is updated, the intensive quantities need to be recalculated //ebosSimulator_.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0); IntensiveQuantities* dummy = NULL;//only for template spesialization - this->updateIntensiveQuantity(problem,solution, dummy); + this->updateIntensiveQuantity(problem,solution, dx, dummy); //ebosSimulator_.model().invalidateAndUpdateIntensiveQuantitiesSimple(problem, //solution, // /*timeIdx=*/0); diff --git a/opm/simulators/wells/BlackoilWellModel.hpp b/opm/simulators/wells/BlackoilWellModel.hpp index 13cd0d42a..ed443600e 100644 --- a/opm/simulators/wells/BlackoilWellModel.hpp +++ b/opm/simulators/wells/BlackoilWellModel.hpp @@ -210,7 +210,13 @@ namespace Opm { { endReportStep(); } + + + void computeTotalRatesForDof(RateVector& rate, + unsigned globalIdx, + unsigned timeIdx) const; + template void computeTotalRatesForDof(RateVector& rate, const Context& context, diff --git a/opm/simulators/wells/BlackoilWellModel_impl.hpp b/opm/simulators/wells/BlackoilWellModel_impl.hpp index ebde65f63..b28fa609e 100644 --- a/opm/simulators/wells/BlackoilWellModel_impl.hpp +++ b/opm/simulators/wells/BlackoilWellModel_impl.hpp @@ -499,7 +499,23 @@ namespace Opm { this->computeWellTemperature(); } + template + void + BlackoilWellModel:: + computeTotalRatesForDof(RateVector& rate, + unsigned elemIdx, + unsigned timeIdx) const + { + rate = 0; + + if (!is_cell_perforated_[elemIdx]) + return; + for (const auto& well : well_container_) + well->addCellRates(rate, elemIdx); + } + + template template void From 409f60642e68eadd14dd000e781aa3d69d5698f0 Mon Sep 17 00:00:00 2001 From: hnil Date: Fri, 17 Jun 2022 12:00:12 +0200 Subject: [PATCH 10/49] new eclproblemtpfa --- ebos/eclproblem.hh | 100 +- ebos/eclproblemtpfa.hh | 3168 +++++++++++++++++++++++++++++++++++ flow/flow_blackoil_tpfa.cpp | 12 +- 3 files changed, 3278 insertions(+), 2 deletions(-) create mode 100644 ebos/eclproblemtpfa.hh diff --git a/ebos/eclproblem.hh b/ebos/eclproblem.hh index 84e27c445..d6b01599d 100644 --- a/ebos/eclproblem.hh +++ b/ebos/eclproblem.hh @@ -1429,6 +1429,11 @@ public: /*! * \copydoc EclTransmissiblity::transmissibility */ + Scalar transmissibility(unsigned globalCenterElemIdx,unsigned globalElemIdx) const + { + return transmissibilities_.transmissibility(globalCenterElemIdx, globalElemIdx); + } + template Scalar transmissibility(const Context& context, [[maybe_unused]] unsigned fromDofLocalIdx, @@ -1537,6 +1542,13 @@ public: return this->referencePorosity_[timeIdx][globalSpaceIdx]; } + Scalar porosity(unsigned globalSpaceIdx, unsigned timeIdx) const + { + //unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + return this->referencePorosity_[timeIdx][globalSpaceIdx]; + } + + /*! * \brief Returns the depth of an degree of freedom [m] * @@ -1561,6 +1573,11 @@ public: return this->simulator().vanguard().cellCenterDepth(globalSpaceIdx); } + Scalar dofCenterDepth(unsigned globalSpaceIdx, unsigned timeIdx) const + { + return this->simulator().vanguard().cellCenterDepth(globalSpaceIdx); + } + /*! * \copydoc BlackoilProblem::rockCompressibility @@ -1580,6 +1597,20 @@ public: return this->rockParams_[tableIdx].compressibility; } + + Scalar rockCompressibility(unsigned globalSpaceIdx, unsigned timeIdx) const + { + // if (this->rockParams_.empty()) + // return 0.0; + + // unsigned tableIdx = 0; + // if (!this->rockTableIdx_.empty()) { + // tableIdx = this->rockTableIdx_[globalSpaceIdx]; + // } + unsigned tableIdx = 0; + return this->rockParams_[tableIdx].compressibility; + } + /*! * \copydoc BlackoilProblem::rockReferencePressure */ @@ -1594,10 +1625,21 @@ public: unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); tableIdx = this->rockTableIdx_[globalSpaceIdx]; } - return this->rockParams_[tableIdx].referencePressure; } + Scalar rockReferencePressure(unsigned globalSpaceIdx, unsigned timeIdx) const + { + // if (this->rockParams_.empty()) + // return 1e5; + + // unsigned tableIdx = 0; + // if (!this->rockTableIdx_.empty()) { + // tableIdx = this->rockTableIdx_[globalSpaceIdx]; + // } + unsigned tableIdx = 0;//this->rockTableIdx_[globalSpaceIdx]; + return this->rockParams_[tableIdx].referencePressure; + } /*! * \copydoc FvBaseMultiPhaseProblem::materialLawParams */ @@ -1825,6 +1867,7 @@ public: */ Scalar maxGasDissolutionFactor(unsigned timeIdx, unsigned globalDofIdx) const { + int pvtRegionIdx = this->pvtRegionIndex(globalDofIdx); int episodeIdx = this->episodeIndex(); if (!this->drsdtActive_(episodeIdx) || this->maxDRs_[pvtRegionIdx] < 0.0) @@ -1943,6 +1986,61 @@ public: aquiferModel_.initialSolutionApplied(); } + template + void source(RateVector& rate, + unsigned globalSpaceIdx, + unsigned timeIdx) const + { + rate = 0.0; + + wellModel_.computeTotalRatesForDof(rate, globalSpaceIdx, timeIdx); + + // convert the source term from the total mass rate of the + // cell to the one per unit of volume as used by the model. + + for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) { + rate[eqIdx] /= this->model().dofTotalVolume(globalSpaceIdx); + + Valgrind::CheckDefined(rate[eqIdx]); + assert(isfinite(rate[eqIdx])); + } + + // if (enableAquifers_) + // aquiferModel_.addToSource(rate, context, spaceIdx, timeIdx); + + // if requested, compensate systematic mass loss for cells which were "well + // behaved" in the last time step + // if (enableDriftCompensation_) { + // const auto& intQuants = context.intensiveQuantities(spaceIdx, timeIdx); + // const auto& simulator = this->simulator(); + // const auto& model = this->model(); + + // // we need a higher maxCompensation than the Newton tolerance because the + // // current time step might be shorter than the last one + // Scalar maxCompensation = 10.0*model.newtonMethod().tolerance(); + + // Scalar poro = intQuants.referencePorosity(); + // Scalar dt = simulator.timeStepSize(); + + // EqVector dofDriftRate = drift_[globalDofIdx]; + // dofDriftRate /= dt*context.dofTotalVolume(spaceIdx, timeIdx); + + // // compute the weighted total drift rate + // Scalar totalDriftRate = 0.0; + // for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) + // totalDriftRate += + // std::abs(dofDriftRate[eqIdx])*dt*model.eqWeight(globalDofIdx, eqIdx)/poro; + + // // make sure that we do not exceed the maximum rate of drift compensation + // if (totalDriftRate > maxCompensation) + // dofDriftRate *= maxCompensation/totalDriftRate; + + // for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) + // rate[eqIdx] -= dofDriftRate[eqIdx]; + // } + } + + /*! * \copydoc FvBaseProblem::source * diff --git a/ebos/eclproblemtpfa.hh b/ebos/eclproblemtpfa.hh new file mode 100644 index 000000000..8c49b7979 --- /dev/null +++ b/ebos/eclproblemtpfa.hh @@ -0,0 +1,3168 @@ +// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- +// vi: set et ts=4 sw=4 sts=4: +/* + 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 2 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 . + + Consult the COPYING file in the top-level source directory of this + module for the precise wording of the license and the list of + copyright holders. +*/ +/*! + * \file + * + * \copydoc Opm::EclProblem + */ +#ifndef EWOMS_ECL_PROBLEM_TPFA_HH +#define EWOMS_ECL_PROBLEM_TPFA_HH + +//#define DISABLE_ALUGRID_SFC_ORDERING 1 +//#define EBOS_USE_ALUGRID 1 + +// make sure that the EBOS_USE_ALUGRID macro. using the preprocessor for this is slightly +// hacky... +// #if EBOS_USE_ALUGRID +// //#define DISABLE_ALUGRID_SFC_ORDERING 1 +// #if !HAVE_DUNE_ALUGRID +// #warning "ALUGrid was indicated to be used for the ECL black oil simulator, but this " +// #warning "requires the presence of dune-alugrid >= 2.4. Falling back to Dune::CpGrid" +// #undef EBOS_USE_ALUGRID +// #define EBOS_USE_ALUGRID 0 +// #endif +// #else +// #define EBOS_USE_ALUGRID 0 +// #endif + +// #if EBOS_USE_ALUGRID +// #include "eclalugridvanguard.hh" +// #elif USE_POLYHEDRALGRID +// #include "eclpolyhedralgridvanguard.hh" +// #else +// #include "eclcpgridvanguard.hh" +// #endif + +// #include "eclequilinitializer.hh" +// #include "eclwriter.hh" +// #include "ecloutputblackoilmodule.hh" +// #include "ecltransmissibility.hh" +// #include "eclthresholdpressure.hh" +// #include "ecldummygradientcalculator.hh" +// #include "eclfluxmodule.hh" +// #include "eclbaseaquifermodel.hh" +// #include "eclnewtonmethod.hh" +// #include "ecltracermodel.hh" +// #include "vtkecltracermodule.hh" +// #include "eclgenericproblem.hh" + +// #include + +// #include +// #include +// #include + +// #include +// #include +// #include + +// #include +// #include + +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include + +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include + +// #include +// #include +// #include + +// #include + +// #include + +// #include +// #include +// #include +// #include + +// namespace Opm { +// template +// class EclProblem; +// } + +// namespace Opm::Properties { + +// namespace TTag { + +// #if EBOS_USE_ALUGRID +// struct EclBaseProblem { +// using InheritsFrom = std::tuple; +// }; +// #elif USE_POLYHEDRALGRID +// struct EclBaseProblem { +// using InheritsFrom = std::tuple; +// }; +// #else +// struct EclBaseProblem { +// using InheritsFrom = std::tuple; +// }; +// #endif +// } + +// // The class which deals with ECL wells +// template +// struct EclWellModel { +// using type = UndefinedProperty; +// }; + +// // Write all solutions for visualization, not just the ones for the +// // report steps... +// template +// struct EnableWriteAllSolutions { +// using type = UndefinedProperty; +// }; + +// // The number of time steps skipped between writing two consequtive restart files +// template +// struct RestartWritingInterval { +// using type = UndefinedProperty; +// }; + +// // Enable partial compensation of systematic mass losses via the source term of the next time +// // step +// template +// struct EclEnableDriftCompensation { +// using type = UndefinedProperty; +// }; + +// // Enable the additional checks even if compiled in debug mode (i.e., with the NDEBUG +// // macro undefined). Next to a slightly better performance, this also eliminates some +// // print statements in debug mode. +// template +// struct EnableDebuggingChecks { +// using type = UndefinedProperty; +// }; + +// // if thermal flux boundaries are enabled an effort is made to preserve the initial +// // thermal gradient specified via the TEMPVD keyword +// template +// struct EnableThermalFluxBoundaries { +// using type = UndefinedProperty; +// }; + +// // Specify whether API tracking should be enabled (replaces PVT regions). +// // TODO: This is not yet implemented +// template +// struct EnableApiTracking { +// using type = UndefinedProperty; +// }; + +// // The class which deals with ECL aquifers +// template +// struct EclAquiferModel { +// using type = UndefinedProperty; +// }; + +// // In experimental mode, decides if the aquifer model should be enabled or not +// template +// struct EclEnableAquifers { +// using type = UndefinedProperty; +// }; + +// // time stepping parameters +// template +// struct EclMaxTimeStepSizeAfterWellEvent { +// using type = UndefinedProperty; +// }; +// template +// struct EclRestartShrinkFactor { +// using type = UndefinedProperty; +// }; +// template +// struct EclEnableTuning { +// using type = UndefinedProperty; +// }; +// template +// struct OutputMode { +// using type = UndefinedProperty; +// }; + +// // Set the problem property +// template +// struct Problem { +// using type = EclProblem; +// }; + +// // Select the element centered finite volume method as spatial discretization +// template +// struct SpatialDiscretizationSplice { +// using type = TTag::EcfvDiscretization; +// }; + +// //! for ebos, use automatic differentiation to linearize the system of PDEs +// template +// struct LocalLinearizerSplice { +// using type = TTag::AutoDiffLocalLinearizer; +// }; + +// // Set the material law for fluid fluxes +// template +// struct MaterialLaw +// { +// private: +// using Scalar = GetPropType; +// using FluidSystem = GetPropType; + +// using Traits = ThreePhaseMaterialTraits; + +// public: +// using EclMaterialLawManager = ::Opm::EclMaterialLawManager; + +// using type = typename EclMaterialLawManager::MaterialLaw; +// }; + +// // Set the material law for energy storage in rock +// template +// struct SolidEnergyLaw +// { +// private: +// using Scalar = GetPropType; +// using FluidSystem = GetPropType; + +// public: +// using EclThermalLawManager = ::Opm::EclThermalLawManager; + +// using type = typename EclThermalLawManager::SolidEnergyLaw; +// }; + +// // Set the material law for thermal conduction +// template +// struct ThermalConductionLaw +// { +// private: +// using Scalar = GetPropType; +// using FluidSystem = GetPropType; + +// public: +// using EclThermalLawManager = ::Opm::EclThermalLawManager; + +// using type = typename EclThermalLawManager::ThermalConductionLaw; +// }; + +// // ebos can use a slightly faster stencil class because it does not need the normals and +// // the integration points of intersections +// template +// struct Stencil +// { +// private: +// using Scalar = GetPropType; +// using GridView = GetPropType; + +// public: +// using type = EcfvStencil; +// }; + +// // by default use the dummy aquifer "model" +// template +// struct EclAquiferModel { +// using type = EclBaseAquiferModel; +// }; + +// // Enable aquifers by default in experimental mode +// template +// struct EclEnableAquifers { +// static constexpr bool value = true; +// }; + +// // Enable gravity +// template +// struct EnableGravity { +// static constexpr bool value = true; +// }; + +// // Enable diffusion +// template +// struct EnableDiffusion { +// static constexpr bool value = true; +// }; + +// // only write the solutions for the report steps to disk +// template +// struct EnableWriteAllSolutions { +// static constexpr bool value = false; +// }; + +// // disable API tracking +// template +// struct EnableApiTracking { +// static constexpr bool value = false; +// }; + +// // The default for the end time of the simulation [s] +// // +// // By default, stop it after the universe will probably have stopped +// // to exist. (the ECL problem will finish the simulation explicitly +// // after it simulated the last episode specified in the deck.) +// template +// struct EndTime { +// using type = GetPropType; +// static constexpr type value = 1e100; +// }; + +// // The default for the initial time step size of the simulation [s]. +// // +// // The chosen value means that the size of the first time step is the +// // one of the initial episode (if the length of the initial episode is +// // not millions of trillions of years, that is...) +// template +// struct InitialTimeStepSize { +// using type = GetPropType; +// static constexpr type value = 3600*24; +// }; + +// // the default for the allowed volumetric error for oil per second +// template +// struct NewtonTolerance { +// using type = GetPropType; +// static constexpr type value = 1e-2; +// }; + +// // the tolerated amount of "incorrect" amount of oil per time step for the complete +// // reservoir. this is scaled by the pore volume of the reservoir, i.e., larger reservoirs +// // will tolerate larger residuals. +// template +// struct EclNewtonSumTolerance { +// using type = GetPropType; +// static constexpr type value = 1e-4; +// }; + +// // set the exponent for the volume scaling of the sum tolerance: larger reservoirs can +// // tolerate a higher amount of mass lost per time step than smaller ones! since this is +// // not linear, we use the cube root of the overall pore volume by default, i.e., the +// // value specified by the NewtonSumTolerance parameter is the "incorrect" mass per +// // timestep for an reservoir that exhibits 1 m^3 of pore volume. A reservoir with a total +// // pore volume of 10^3 m^3 will tolerate 10 times as much. +// template +// struct EclNewtonSumToleranceExponent { +// using type = GetPropType; +// static constexpr type value = 1.0/3.0; +// }; + +// // set number of Newton iterations where the volumetric residual is considered for +// // convergence +// template +// struct EclNewtonStrictIterations { +// static constexpr int value = 8; +// }; + +// // set fraction of the pore volume where the volumetric residual may be violated during +// // strict Newton iterations +// template +// struct EclNewtonRelaxedVolumeFraction { +// using type = GetPropType; +// static constexpr type value = 0.03; +// }; + +// // the maximum volumetric error of a cell in the relaxed region +// template +// struct EclNewtonRelaxedTolerance { +// using type = GetPropType; +// static constexpr type value = 1e9; +// }; + +// // Ignore the maximum error mass for early termination of the newton method. +// template +// struct NewtonMaxError { +// using type = GetPropType; +// static constexpr type value = 10e9; +// }; + +// // set the maximum number of Newton iterations to 14 because the likelyhood that a time +// // step succeeds at more than 14 Newton iteration is rather small +// template +// struct NewtonMaxIterations { +// static constexpr int value = 14; +// }; + +// // also, reduce the target for the "optimum" number of Newton iterations to 6. Note that +// // this is only relevant if the time step is reduced from the report step size for some +// // reason. (because ebos first tries to do a report step using a single time step.) +// template +// struct NewtonTargetIterations { +// static constexpr int value = 6; +// }; + +// // Disable the VTK output by default for this problem ... +// template +// struct EnableVtkOutput { +// static constexpr bool value = false; +// }; + +// // ... but enable the ECL output by default +// template +// struct EnableEclOutput { +// static constexpr bool value = true; +// }; + +// // If available, write the ECL output in a non-blocking manner +// template +// struct EnableAsyncEclOutput { +// static constexpr bool value = true; +// }; +// // Write ESMRY file for fast loading of summary data +// template +// struct EnableEsmry { +// static constexpr bool value = false; +// }; + +// // By default, use single precision for the ECL formated results +// template +// struct EclOutputDoublePrecision { +// static constexpr bool value = false; +// }; + +// // The default location for the ECL output files +// template +// struct OutputDir { +// static constexpr auto value = "."; +// }; + +// // the cache for intensive quantities can be used for ECL problems and also yields a +// // decent speedup... +// template +// struct EnableIntensiveQuantityCache { +// static constexpr bool value = true; +// }; + +// // the cache for the storage term can also be used and also yields a decent speedup +// template +// struct EnableStorageCache { +// static constexpr bool value = true; +// }; + +// // Use the "velocity module" which uses the Eclipse "NEWTRAN" transmissibilities +// template +// struct FluxModule { +// using type = EclTransFluxModule; +// }; + +// // Use the dummy gradient calculator in order not to do unnecessary work. +// template +// struct GradientCalculator { +// using type = EclDummyGradientCalculator; +// }; + +// // Use a custom Newton-Raphson method class for ebos in order to attain more +// // sophisticated update and error computation mechanisms +// template +// struct NewtonMethod { +// using type = EclNewtonMethod; +// }; + +// // The frequency of writing restart (*.ers) files. This is the number of time steps +// // between writing restart files +// template +// struct RestartWritingInterval { +// static constexpr int value = 0xffffff; // disable +// }; + +// // Drift compensation is an experimental feature, i.e., systematic errors in the +// // conservation quantities are only compensated for +// // as default if experimental mode is enabled. +// template +// struct EclEnableDriftCompensation { +// static constexpr bool value = true; + +// }; + +// // By default, we enable the debugging checks if we're compiled in debug mode +// template +// struct EnableDebuggingChecks { +// static constexpr bool value = true; +// }; + +// // store temperature (but do not conserve energy, as long as EnableEnergy is false) +// template +// struct EnableTemperature { +// static constexpr bool value = true; +// }; + +// // disable all extensions supported by black oil model. this should not really be +// // necessary but it makes things a bit more explicit +// template +// struct EnablePolymer { +// static constexpr bool value = false; +// }; +// template +// struct EnableSolvent { +// static constexpr bool value = false; +// }; +// template +// struct EnableEnergy { +// static constexpr bool value = false; +// }; +// template +// struct EnableFoam { +// static constexpr bool value = false; +// }; +// template +// struct EnableExtbo { +// static constexpr bool value = false; +// }; +// template +// struct EnableMICP { +// static constexpr bool value = false; +// }; + +// // disable thermal flux boundaries by default +// template +// struct EnableThermalFluxBoundaries { +// static constexpr bool value = false; +// }; + +// // By default, simulators derived from the EclBaseProblem are production simulators, +// // i.e., experimental features must be explicitly enabled at compile time +// template +// struct EnableExperiments { +// static constexpr bool value = false; +// }; + +// // set defaults for the time stepping parameters +// template +// struct EclMaxTimeStepSizeAfterWellEvent { +// using type = GetPropType; +// static constexpr type value = 3600*24*365.25; +// }; +// template +// struct EclRestartShrinkFactor { +// using type = GetPropType; +// static constexpr type value = 3; +// }; +// template +// struct EclEnableTuning { +// static constexpr bool value = false; +// }; + +// template +// struct OutputMode { +// static constexpr auto value = "all"; +// }; + +// } // namespace Opm::Properties + + +namespace Opm { + +/*! + * \ingroup EclBlackOilSimulator + * + * \brief This problem simulates an input file given in the data format used by the + * commercial ECLiPSE simulator. + */ +template +class EclProblemTPFA : public GetPropType + , public EclGenericProblem, + GetPropType, + GetPropType> +{ + using ParentType = GetPropType; + using Implementation = GetPropType; + + using Scalar = GetPropType; + using GridView = GetPropType; + using Stencil = GetPropType; + using FluidSystem = GetPropType; + using GlobalEqVector = GetPropType; + using EqVector = GetPropType; + using Vanguard = GetPropType; + + // Grid and world dimension + enum { dim = GridView::dimension }; + enum { dimWorld = GridView::dimensionworld }; + + // copy some indices for convenience + enum { numEq = getPropValue() }; + enum { numPhases = FluidSystem::numPhases }; + enum { numComponents = FluidSystem::numComponents }; + enum { enableExperiments = getPropValue() }; + enum { enableSolvent = getPropValue() }; + enum { enablePolymer = getPropValue() }; + enum { enableBrine = getPropValue() }; + enum { enableSaltPrecipitation = getPropValue() }; + enum { enablePolymerMolarWeight = getPropValue() }; + enum { enableFoam = getPropValue() }; + enum { enableExtbo = getPropValue() }; + enum { enableTemperature = getPropValue() }; + enum { enableEnergy = getPropValue() }; + enum { enableDiffusion = getPropValue() }; + enum { enableThermalFluxBoundaries = getPropValue() }; + enum { enableApiTracking = getPropValue() }; + enum { enableMICP = getPropValue() }; + enum { gasPhaseIdx = FluidSystem::gasPhaseIdx }; + enum { oilPhaseIdx = FluidSystem::oilPhaseIdx }; + enum { waterPhaseIdx = FluidSystem::waterPhaseIdx }; + enum { gasCompIdx = FluidSystem::gasCompIdx }; + enum { oilCompIdx = FluidSystem::oilCompIdx }; + enum { waterCompIdx = FluidSystem::waterCompIdx }; + + using PrimaryVariables = GetPropType; + using RateVector = GetPropType; + using BoundaryRateVector = GetPropType; + using Simulator = GetPropType; + using Element = typename GridView::template Codim<0>::Entity; + using ElementContext = GetPropType; + using EclMaterialLawManager = typename GetProp::EclMaterialLawManager; + using EclThermalLawManager = typename GetProp::EclThermalLawManager; + using MaterialLawParams = typename EclMaterialLawManager::MaterialLawParams; + using SolidEnergyLawParams = typename EclThermalLawManager::SolidEnergyLawParams; + using ThermalConductionLawParams = typename EclThermalLawManager::ThermalConductionLawParams; + using MaterialLaw = GetPropType; + using DofMapper = GetPropType; + using Evaluation = GetPropType; + using Indices = GetPropType; + using IntensiveQuantities = GetPropType; + using EclWellModel = GetPropType; + using EclAquiferModel = GetPropType; + + using SolventModule = BlackOilSolventModule; + using PolymerModule = BlackOilPolymerModule; + using FoamModule = BlackOilFoamModule; + using BrineModule = BlackOilBrineModule; + using ExtboModule = BlackOilExtboModule; + using MICPModule= BlackOilMICPModule; + + using InitialFluidState = typename EclEquilInitializer::ScalarFluidState; + + using Toolbox = MathToolbox; + using DimMatrix = Dune::FieldMatrix; + + using EclWriterType = EclWriter; + + using TracerModel = EclTracerModel; + +public: + using EclGenericProblem::briefDescription; + using EclGenericProblem::helpPreamble; + using EclGenericProblem::shouldWriteOutput; + using EclGenericProblem::shouldWriteRestartFile; + using EclGenericProblem::maxTimeIntegrationFailures; + using EclGenericProblem::minTimeStepSize; + + /*! + * \copydoc FvBaseProblem::registerParameters + */ + static void registerParameters() + { + ParentType::registerParameters(); + EclWriterType::registerParameters(); + VtkEclTracerModule::registerParameters(); + + EWOMS_REGISTER_PARAM(TypeTag, bool, EnableWriteAllSolutions, + "Write all solutions to disk instead of only the ones for the " + "report steps"); + EWOMS_REGISTER_PARAM(TypeTag, bool, EnableEclOutput, + "Write binary output which is compatible with the commercial " + "Eclipse simulator"); + EWOMS_REGISTER_PARAM(TypeTag, bool, EclOutputDoublePrecision, + "Tell the output writer to use double precision. Useful for 'perfect' restarts"); + EWOMS_REGISTER_PARAM(TypeTag, unsigned, RestartWritingInterval, + "The frequencies of which time steps are serialized to disk"); + EWOMS_REGISTER_PARAM(TypeTag, bool, EclEnableDriftCompensation, + "Enable partial compensation of systematic mass losses via the source term of the next time step"); + if constexpr (enableExperiments) + EWOMS_REGISTER_PARAM(TypeTag, bool, EclEnableAquifers, + "Enable analytic and numeric aquifer models"); + EWOMS_REGISTER_PARAM(TypeTag, Scalar, EclMaxTimeStepSizeAfterWellEvent, + "Maximum time step size after an well event"); + EWOMS_REGISTER_PARAM(TypeTag, Scalar, EclRestartShrinkFactor, + "Factor by which the time step is reduced after convergence failure"); + EWOMS_REGISTER_PARAM(TypeTag, bool, EclEnableTuning, + "Honor some aspects of the TUNING keyword from the ECL deck."); + EWOMS_REGISTER_PARAM(TypeTag, std::string, OutputMode, + "Specify which messages are going to be printed. Valid values are: none, log, all (default)"); + + } + + /*! + * \copydoc FvBaseProblem::prepareOutputDir + */ + std::string prepareOutputDir() const + { return this->simulator().vanguard().eclState().getIOConfig().getOutputDir(); } + + /*! + * \copydoc FvBaseProblem::handlePositionalParameter + */ + static int handlePositionalParameter(std::set& seenParams, + std::string& errorMsg, + int, + const char** argv, + int paramIdx, + int) + { + using ParamsMeta = GetProp; + Dune::ParameterTree& tree = ParamsMeta::tree(); + + std::string param = argv[paramIdx]; + size_t i = param.find('='); + if (i != std::string::npos) { + std::string oldParamName = param.substr(0, i); + std::string oldParamValue = param.substr(i+1); + std::string newParamName = "--" + oldParamName; + for (size_t j = 0; j < newParamName.size(); ++j) + if (newParamName[j] == '_') + newParamName[j] = '-'; + errorMsg = + "The old syntax to specify parameters on the command line is no longer supported: " + "Try replacing '"+oldParamName+"="+oldParamValue+"' with "+ + "'"+newParamName+"="+oldParamValue+"'!"; + return 0; + } + + if (seenParams.count("EclDeckFileName") > 0) { + errorMsg = + "Parameter 'EclDeckFileName' specified multiple times" + " as a command line parameter"; + return 0; + } + + tree["EclDeckFileName"] = argv[paramIdx]; + seenParams.insert("EclDeckFileName"); + return 1; + } + + /*! + * \copydoc Doxygen::defaultProblemConstructor + */ + EclProblemTPFA(Simulator& simulator) + : ParentType(simulator) + , EclGenericProblem(simulator.vanguard().eclState(), + simulator.vanguard().schedule(), + simulator.vanguard().gridView()) + , transmissibilities_(simulator.vanguard().eclState(), + simulator.vanguard().gridView(), + simulator.vanguard().cartesianIndexMapper(), + simulator.vanguard().grid(), + simulator.vanguard().cellCentroids(), + enableEnergy, + enableDiffusion) + , thresholdPressures_(simulator) + , wellModel_(simulator) + , aquiferModel_(simulator) + , pffDofData_(simulator.gridView(), this->elementMapper()) + , tracerModel_(simulator) + { + this->model().addOutputModule(new VtkEclTracerModule(simulator)); + // Tell the black-oil extensions to initialize their internal data structures + const auto& vanguard = simulator.vanguard(); + SolventModule::initFromState(vanguard.eclState(), vanguard.schedule()); + PolymerModule::initFromState(vanguard.eclState()); + FoamModule::initFromState(vanguard.eclState()); + BrineModule::initFromState(vanguard.eclState()); + ExtboModule::initFromState(vanguard.eclState()); + MICPModule::initFromState(vanguard.eclState()); + + // create the ECL writer + eclWriter_.reset(new EclWriterType(simulator)); + + enableDriftCompensation_ = EWOMS_GET_PARAM(TypeTag, bool, EclEnableDriftCompensation); + + enableEclOutput_ = EWOMS_GET_PARAM(TypeTag, bool, EnableEclOutput); + + if constexpr (enableExperiments) + enableAquifers_ = EWOMS_GET_PARAM(TypeTag, bool, EclEnableAquifers); + else + enableAquifers_ = true; + + this->enableTuning_ = EWOMS_GET_PARAM(TypeTag, bool, EclEnableTuning); + this->initialTimeStepSize_ = EWOMS_GET_PARAM(TypeTag, Scalar, InitialTimeStepSize); + this->minTimeStepSize_ = EWOMS_GET_PARAM(TypeTag, Scalar, MinTimeStepSize); + this->maxTimeStepSize_ = EWOMS_GET_PARAM(TypeTag, Scalar, MaxTimeStepSize); + this->maxTimeStepAfterWellEvent_ = EWOMS_GET_PARAM(TypeTag, Scalar, EclMaxTimeStepSizeAfterWellEvent); + this->restartShrinkFactor_ = EWOMS_GET_PARAM(TypeTag, Scalar, EclRestartShrinkFactor); + this->maxFails_ = EWOMS_GET_PARAM(TypeTag, unsigned, MaxTimeStepDivisions); + + RelpermDiagnostics relpermDiagnostics; + relpermDiagnostics.diagnosis(vanguard.eclState(), vanguard.cartesianIndexMapper()); + } + + /*! + * \copydoc FvBaseProblem::finishInit + */ + void finishInit() + { + ParentType::finishInit(); + + auto& simulator = this->simulator(); + const auto& eclState = simulator.vanguard().eclState(); + const auto& schedule = simulator.vanguard().schedule(); + + // Set the start time of the simulation + simulator.setStartTime(schedule.getStartTime()); + simulator.setEndTime(schedule.simTime(schedule.size() - 1)); + + // We want the episode index to be the same as the report step index to make + // things simpler, so we have to set the episode index to -1 because it is + // incremented by endEpisode(). The size of the initial time step and + // length of the initial episode is set to zero for the same reason. + simulator.setEpisodeIndex(-1); + simulator.setEpisodeLength(0.0); + + // the "NOGRAV" keyword from Frontsim or setting the EnableGravity to false + // disables gravity, else the standard value of the gravity constant at sea level + // on earth is used + this->gravity_ = 0.0; + if (EWOMS_GET_PARAM(TypeTag, bool, EnableGravity)) + this->gravity_[dim - 1] = 9.80665; + if (!eclState.getInitConfig().hasGravity()) + this->gravity_[dim - 1] = 0.0; + + if (this->enableTuning_) { + // if support for the TUNING keyword is enabled, we get the initial time + // steping parameters from it instead of from command line parameters + const auto& tuning = schedule[0].tuning(); + this->initialTimeStepSize_ = tuning.TSINIT; + this->maxTimeStepAfterWellEvent_ = tuning.TMAXWC; + this->maxTimeStepSize_ = tuning.TSMAXZ; + this->restartShrinkFactor_ = 1./tuning.TSFCNV; + this->minTimeStepSize_ = tuning.TSMINZ; + } + + this->initFluidSystem_(); + + // deal with DRSDT + this->initDRSDT_(this->model().numGridDof(), this->episodeIndex()); + + this->readRockParameters_(simulator.vanguard().cellCenterDepths()); + readMaterialParameters_(); + readThermalParameters_(); + transmissibilities_.finishInit(); + + const auto& initconfig = eclState.getInitConfig(); + tracerModel_.init(initconfig.restartRequested()); + if (initconfig.restartRequested()) + readEclRestartSolution_(); + else + readInitialCondition_(); + tracerModel_.prepareTracerBatches(); + + updatePffDofData_(); + + if constexpr (getPropValue()) { + const auto& vanguard = this->simulator().vanguard(); + const auto& gridView = vanguard.gridView(); + int numElements = gridView.size(/*codim=*/0); + this->maxPolymerAdsorption_.resize(numElements, 0.0); + } + + readBoundaryConditions_(); + + if (enableDriftCompensation_) { + drift_.resize(this->model().numGridDof()); + drift_ = 0.0; + } + + if constexpr (enableExperiments) + { + int success = 1; + const auto& cc = simulator.vanguard().grid().comm(); + + try + { + // Only rank 0 has the deck and hence can do the checks! + if (cc.rank() == 0) + this->checkDeckCompatibility_(simulator.vanguard().deck(), + enableApiTracking, + enableSolvent, + enablePolymer, + enableExtbo, + enableEnergy, + Indices::numPhases, + Indices::gasEnabled, + Indices::oilEnabled, + Indices::waterEnabled, + enableMICP); + } + catch(const std::exception& e) + { + success = 0; + success = cc.min(success); + throw; + } + + success = cc.min(success); + + if (!success) + { + throw std::runtime_error("Checking deck compatibility failed"); + } + } + + // write the static output files (EGRID, INIT, SMSPEC, etc.) + if (enableEclOutput_) { + if (simulator.vanguard().grid().comm().size() > 1) { + if (simulator.vanguard().grid().comm().rank() == 0) + eclWriter_->setTransmissibilities(&simulator.vanguard().globalTransmissibility()); + } else + eclWriter_->setTransmissibilities(&simulator.problem().eclTransmissibilities()); + + eclWriter_->writeInit(); + } + + simulator.vanguard().releaseGlobalTransmissibilities(); + + // after finishing the initialization and writing the initial solution, we move + // to the first "real" episode/report step + // for restart the episode index and start is already set + if (!initconfig.restartRequested()) { + simulator.startNextEpisode(schedule.seconds(0)); + simulator.setEpisodeIndex(0); + } + } + + void prefetch(const Element& elem) const + { pffDofData_.prefetch(elem); } + + /*! + * \brief This method restores the complete state of the problem and its sub-objects + * from disk. + * + * The serialization format used by this method is ad-hoc. It is the inverse of the + * serialize() method. + * + * \tparam Restarter The deserializer type + * + * \param res The deserializer object + */ + template + void deserialize(Restarter& res) + { + // reload the current episode/report step from the deck + beginEpisode(); + + // deserialize the wells + wellModel_.deserialize(res); + + if (enableAquifers_) + // deserialize the aquifer + aquiferModel_.deserialize(res); + } + + /*! + * \brief This method writes the complete state of the problem and its subobjects to + * disk. + * + * The file format used here is ad-hoc. + */ + template + void serialize(Restarter& res) + { + wellModel_.serialize(res); + + if (enableAquifers_) + aquiferModel_.serialize(res); + } + + int episodeIndex() const + { + return std::max(this->simulator().episodeIndex(), 0); + } + + /*! + * \brief Called by the simulator before an episode begins. + */ + void beginEpisode() + { + // Proceed to the next report step + auto& simulator = this->simulator(); + int episodeIdx = simulator.episodeIndex(); + auto& eclState = simulator.vanguard().eclState(); + const auto& schedule = simulator.vanguard().schedule(); + const auto& events = schedule[episodeIdx].events(); + + if (episodeIdx >= 0 && events.hasEvent(ScheduleEvents::GEO_MODIFIER)) { + // bring the contents of the keywords to the current state of the SCHEDULE + // section. + // + // TODO (?): make grid topology changes possible (depending on what exactly + // has changed, the grid may need be re-created which has some serious + // implications on e.g., the solution of the simulation.) + const auto& miniDeck = schedule[episodeIdx].geo_keywords(); + const auto& cc = simulator.vanguard().grid().comm(); + eclState.apply_schedule_keywords( miniDeck ); + eclBroadcast(cc, eclState.getTransMult() ); + + // re-compute all quantities which may possibly be affected. + transmissibilities_.update(true); + this->referencePorosity_[1] = this->referencePorosity_[0]; + updateReferencePorosity_(); + updatePffDofData_(); + } + + bool tuningEvent = this->beginEpisode_(enableExperiments, this->episodeIndex()); + + // set up the wells for the next episode. + wellModel_.beginEpisode(); + + // set up the aquifers for the next episode. + if (enableAquifers_) + // set up the aquifers for the next episode. + aquiferModel_.beginEpisode(); + + // set the size of the initial time step of the episode + Scalar dt = limitNextTimeStepSize_(simulator.episodeLength()); + if (episodeIdx == 0 || tuningEvent) + // allow the size of the initial time step to be set via an external parameter + // if TUNING is enabled, also limit the time step size after a tuning event to TSINIT + dt = std::min(dt, this->initialTimeStepSize_); + simulator.setTimeStepSize(dt); + + // Evaluate UDQ assign statements to make sure the settings are + // available as UDA controls for the current report step. + const auto& udq = schedule[episodeIdx].udq(); + const auto& well_matcher = schedule.wellMatcher(episodeIdx); + auto& summary_state = simulator.vanguard().summaryState(); + auto& udq_state = simulator.vanguard().udqState(); + udq.eval_assign(episodeIdx, well_matcher, summary_state, udq_state); + } + + /*! + * \brief Called by the simulator before each time integration. + */ + void beginTimeStep() + { + int episodeIdx = this->episodeIndex(); + + this->beginTimeStep_(enableExperiments, + episodeIdx, + this->simulator().timeStepIndex(), + this->simulator().startTime(), + this->simulator().time(), + this->simulator().timeStepSize(), + this->simulator().endTime()); + + // update maximum water saturation and minimum pressure + // used when ROCKCOMP is activated + const bool invalidateFromMaxWaterSat = updateMaxWaterSaturation_(); + const bool invalidateFromMinPressure = updateMinPressure_(); + + // update hysteresis and max oil saturation used in vappars + const bool invalidateFromHyst = updateHysteresis_(); + const bool invalidateFromMaxOilSat = updateMaxOilSaturation_(); + + // the derivatives may have change + bool invalidateIntensiveQuantities = invalidateFromMaxWaterSat || invalidateFromMinPressure || invalidateFromHyst || invalidateFromMaxOilSat; + if (invalidateIntensiveQuantities) + this->model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0); + + if constexpr (getPropValue()) + updateMaxPolymerAdsorption_(); + + wellModel_.beginTimeStep(); + if (enableAquifers_) + aquiferModel_.beginTimeStep(); + tracerModel_.beginTimeStep(); + + } + + /*! + * \brief Called by the simulator before each Newton-Raphson iteration. + */ + void beginIteration() + { + wellModel_.beginIteration(); + if (enableAquifers_) + aquiferModel_.beginIteration(); + } + + /*! + * \brief Called by the simulator after each Newton-Raphson iteration. + */ + void endIteration() + { + wellModel_.endIteration(); + if (enableAquifers_) + aquiferModel_.endIteration(); + } + + /*! + * \brief Called by the simulator after each time integration. + */ + void endTimeStep() + { +#ifndef NDEBUG + if constexpr (getPropValue()) { + // in debug mode, we don't care about performance, so we check if the model does + // the right thing (i.e., the mass change inside the whole reservoir must be + // equivalent to the fluxes over the grid's boundaries plus the source rates + // specified by the problem) + int rank = this->simulator().gridView().comm().rank(); + if (rank == 0) + std::cout << "checking conservativeness of solution\n"; + this->model().checkConservativeness(/*tolerance=*/-1, /*verbose=*/true); + if (rank == 0) + std::cout << "solution is sufficiently conservative\n"; + } +#endif // NDEBUG + + auto& simulator = this->simulator(); + wellModel_.endTimeStep(); + if (enableAquifers_) + aquiferModel_.endTimeStep(); + tracerModel_.endTimeStep(); + + // deal with DRSDT and DRVDT + updateCompositionChangeLimits_(); + + if (enableDriftCompensation_) { + const auto& residual = this->model().linearizer().residual(); + for (unsigned globalDofIdx = 0; globalDofIdx < residual.size(); globalDofIdx ++) { + drift_[globalDofIdx] = residual[globalDofIdx]; + drift_[globalDofIdx] *= simulator.timeStepSize(); + if constexpr (getPropValue()) + drift_[globalDofIdx] *= this->model().dofTotalVolume(globalDofIdx); + } + } + + bool isSubStep = !EWOMS_GET_PARAM(TypeTag, bool, EnableWriteAllSolutions) && !this->simulator().episodeWillBeOver(); + eclWriter_->evalSummaryState(isSubStep); + + auto& schedule = simulator.vanguard().schedule(); + auto& ecl_state = simulator.vanguard().eclState(); + int episodeIdx = this->episodeIndex(); + this->applyActions(episodeIdx, + simulator.time() + simulator.timeStepSize(), + simulator.vanguard().grid().comm(), + ecl_state, + schedule, + simulator.vanguard().actionState(), + simulator.vanguard().summaryState()); + + // deal with "clogging" for the MICP model + if constexpr (enableMICP){ + auto& model = this->model(); + const auto& residual = this->model().linearizer().residual(); + for (unsigned globalDofIdx = 0; globalDofIdx < residual.size(); globalDofIdx ++) { + auto& phi = this->referencePorosity_[/*timeIdx=*/1][globalDofIdx]; + MICPModule::checkCloggingMICP(model, phi, globalDofIdx); + } + } + } + + /*! + * \brief Called by the simulator after the end of an episode. + */ + void endEpisode() + { + auto& simulator = this->simulator(); + auto& schedule = simulator.vanguard().schedule(); + + wellModel_.endEpisode(); + if (enableAquifers_) + aquiferModel_.endEpisode(); + + int episodeIdx = this->episodeIndex(); + // check if we're finished ... + if (episodeIdx + 1 >= static_cast(schedule.size() - 1)) { + simulator.setFinished(true); + return; + } + + // .. if we're not yet done, start the next episode (report step) + simulator.startNextEpisode(schedule.stepLength(episodeIdx + 1)); + } + + /*! + * \brief Write the requested quantities of the current solution into the output + * files. + */ + void writeOutput(bool verbose = true) + { + // use the generic code to prepare the output fields and to + // write the desired VTK files. + ParentType::writeOutput(verbose); + + bool isSubStep = !EWOMS_GET_PARAM(TypeTag, bool, EnableWriteAllSolutions) && !this->simulator().episodeWillBeOver(); + if (enableEclOutput_) + eclWriter_->writeOutput(isSubStep); + } + + void finalizeOutput() { + // this will write all pending output to disk + // to avoid corruption of output files + eclWriter_.reset(); + } + + + std::unordered_map fetchWellPI(int reportStep, + const Action::ActionX& action, + const Schedule& schedule, + const std::vector& matching_wells) { + + auto wellpi_wells = action.wellpi_wells(WellMatcher(schedule[reportStep].well_order(), + schedule[reportStep].wlist_manager()), + matching_wells); + + if (wellpi_wells.empty()) + return {}; + + const auto num_wells = schedule[reportStep].well_order().size(); + std::vector wellpi_vector(num_wells); + for (const auto& wname : wellpi_wells) { + if (this->wellModel_.hasWell(wname)) { + const auto& well = schedule.getWell( wname, reportStep ); + wellpi_vector[well.seqIndex()] = this->wellModel_.wellPI(wname); + } + } + + const auto& comm = this->simulator().vanguard().grid().comm(); + if (comm.size() > 1) { + std::vector wellpi_buffer(num_wells * comm.size()); + comm.gather( wellpi_vector.data(), wellpi_buffer.data(), num_wells, 0 ); + if (comm.rank() == 0) { + for (int rank=1; rank < comm.size(); rank++) { + for (std::size_t well_index=0; well_index < num_wells; well_index++) { + const auto global_index = rank*num_wells + well_index; + const auto value = wellpi_buffer[global_index]; + if (value != 0) + wellpi_vector[well_index] = value; + } + } + } + comm.broadcast(wellpi_vector.data(), wellpi_vector.size(), 0); + } + + std::unordered_map wellpi; + for (const auto& wname : wellpi_wells) { + const auto& well = schedule.getWell( wname, reportStep ); + wellpi[wname] = wellpi_vector[ well.seqIndex() ]; + } + return wellpi; + } + + + + /* + This function is run after applyAction has been completed in the Schedule + implementation. The sim_update argument should have members & flags for + the simulator properties which need to be updated. This functionality is + probably not complete. + */ + void applySimulatorUpdate(int report_step, Parallel::Communication comm, const SimulatorUpdate& sim_update, EclipseState& ecl_state, Schedule& schedule, SummaryState& summary_state, bool& commit_wellstate) { + this->wellModel_.updateEclWells(report_step, sim_update.affected_wells, summary_state); + if (!sim_update.affected_wells.empty()) + commit_wellstate = true; + + if (sim_update.tran_update) { + const auto& keywords = schedule[report_step].geo_keywords(); + ecl_state.apply_schedule_keywords( keywords ); + eclBroadcast(comm, ecl_state.getTransMult() ); + + // re-compute transmissibility + transmissibilities_.update(true); + } + + } + + + void applyActions(int reportStep, + double sim_time, + Parallel::Communication comm, + EclipseState& ecl_state, + Schedule& schedule, + Action::State& actionState, + SummaryState& summaryState) { + const auto& actions = schedule[reportStep].actions(); + if (actions.empty()) + return; + + Action::Context context( summaryState, schedule[reportStep].wlist_manager() ); + auto now = TimeStampUTC( schedule.getStartTime() ) + std::chrono::duration(sim_time); + std::string ts; + { + std::ostringstream os; + os << std::setw(4) << std::to_string(now.year()) << '/' + << std::setw(2) << std::setfill('0') << std::to_string(now.month()) << '/' + << std::setw(2) << std::setfill('0') << std::to_string(now.day()) << " report:" << std::to_string(reportStep); + + ts = os.str(); + } + + bool commit_wellstate = false; + for (const auto& pyaction : actions.pending_python(actionState)) { + auto sim_update = schedule.runPyAction(reportStep, *pyaction, actionState, ecl_state, summaryState); + this->applySimulatorUpdate(reportStep, comm, sim_update, ecl_state, schedule, summaryState, commit_wellstate); + } + + auto simTime = asTimeT(now); + for (const auto& action : actions.pending(actionState, simTime)) { + auto actionResult = action->eval(context); + if (actionResult) { + std::string wells_string; + const auto& matching_wells = actionResult.wells(); + if (!matching_wells.empty()) { + for (std::size_t iw = 0; iw < matching_wells.size() - 1; iw++) + wells_string += matching_wells[iw] + ", "; + wells_string += matching_wells.back(); + } + std::string msg = "The action: " + action->name() + " evaluated to true at " + ts + " wells: " + wells_string; + OpmLog::info(msg); + + const auto& wellpi = this->fetchWellPI(reportStep, *action, schedule, matching_wells); + + auto sim_update = schedule.applyAction(reportStep, *action, actionResult.wells(), wellpi); + this->applySimulatorUpdate(reportStep, comm, sim_update, ecl_state, schedule, summaryState, commit_wellstate); + actionState.add_run(*action, simTime, std::move(actionResult)); + } else { + std::string msg = "The action: " + action->name() + " evaluated to false at " + ts; + OpmLog::info(msg); + } + } + /* + The well state has been stored in a previous object when the time step + has completed successfully, the action process might have modified the + well state, and to be certain that is not overwritten when starting + the next timestep we must commit it. + */ + if (commit_wellstate) + this->wellModel_.commitWGState(); + } + + + /*! + * \copydoc FvBaseMultiPhaseProblem::intrinsicPermeability + */ + template + const DimMatrix& intrinsicPermeability(const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + return transmissibilities_.permeability(globalSpaceIdx); + } + + /*! + * \brief This method returns the intrinsic permeability tensor + * given a global element index. + * + * Its main (only?) usage is the ECL transmissibility calculation code... + */ + const DimMatrix& intrinsicPermeability(unsigned globalElemIdx) const + { return transmissibilities_.permeability(globalElemIdx); } + + /*! + * \copydoc EclTransmissiblity::transmissibility + */ + Scalar transmissibility(unsigned globalCenterElemIdx,unsigned globalElemIdx) const + { + return transmissibilities_.transmissibility(globalCenterElemIdx, globalElemIdx); + } + + template + Scalar transmissibility(const Context& context, + [[maybe_unused]] unsigned fromDofLocalIdx, + unsigned toDofLocalIdx) const + { + assert(fromDofLocalIdx == 0); + return pffDofData_.get(context.element(), toDofLocalIdx).transmissibility; + } + + /*! + * \copydoc EclTransmissiblity::diffusivity + */ + template + Scalar diffusivity(const Context& context, + [[maybe_unused]] unsigned fromDofLocalIdx, + unsigned toDofLocalIdx) const + { + assert(fromDofLocalIdx == 0); + return *pffDofData_.get(context.element(), toDofLocalIdx).diffusivity; + } + + /*! + * \copydoc EclTransmissiblity::transmissibilityBoundary + */ + template + Scalar transmissibilityBoundary(const Context& elemCtx, + unsigned boundaryFaceIdx) const + { + unsigned elemIdx = elemCtx.globalSpaceIndex(/*dofIdx=*/0, /*timeIdx=*/0); + return transmissibilities_.transmissibilityBoundary(elemIdx, boundaryFaceIdx); + } + + /*! + * \copydoc EclTransmissiblity::thermalHalfTransmissibility + */ + template + Scalar thermalHalfTransmissibilityIn(const Context& context, + unsigned faceIdx, + unsigned timeIdx) const + { + const auto& face = context.stencil(timeIdx).interiorFace(faceIdx); + unsigned toDofLocalIdx = face.exteriorIndex(); + return *pffDofData_.get(context.element(), toDofLocalIdx).thermalHalfTransIn; + } + + /*! + * \copydoc EclTransmissiblity::thermalHalfTransmissibility + */ + template + Scalar thermalHalfTransmissibilityOut(const Context& context, + unsigned faceIdx, + unsigned timeIdx) const + { + const auto& face = context.stencil(timeIdx).interiorFace(faceIdx); + unsigned toDofLocalIdx = face.exteriorIndex(); + return *pffDofData_.get(context.element(), toDofLocalIdx).thermalHalfTransOut; + } + + /*! + * \copydoc EclTransmissiblity::thermalHalfTransmissibility + */ + template + Scalar thermalHalfTransmissibilityBoundary(const Context& elemCtx, + unsigned boundaryFaceIdx) const + { + unsigned elemIdx = elemCtx.globalSpaceIndex(/*dofIdx=*/0, /*timeIdx=*/0); + return transmissibilities_.thermalHalfTransBoundary(elemIdx, boundaryFaceIdx); + } + + /*! + * \brief Return a reference to the object that handles the "raw" transmissibilities. + */ + const typename Vanguard::TransmissibilityType& eclTransmissibilities() const + { return transmissibilities_; } + + /*! + * \copydoc BlackOilBaseProblem::thresholdPressure + */ + Scalar thresholdPressure(unsigned elem1Idx, unsigned elem2Idx) const + { return thresholdPressures_.thresholdPressure(elem1Idx, elem2Idx); } + + const EclThresholdPressure& thresholdPressure() const + { return thresholdPressures_; } + + EclThresholdPressure& thresholdPressure() + { return thresholdPressures_; } + + const EclTracerModel& tracerModel() const + { return tracerModel_; } + + EclTracerModel& tracerModel() + { return tracerModel_; } + + /*! + * \copydoc FvBaseMultiPhaseProblem::porosity + * + * For the EclProblemTPFA, this method is identical to referencePorosity(). The intensive + * quantities object may apply various multipliers (e.g. ones which model rock + * compressibility and water induced rock compaction) to it which depend on the + * current physical conditions. + */ + template + Scalar porosity(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + return this->referencePorosity_[timeIdx][globalSpaceIdx]; + } + + Scalar porosity(unsigned globalSpaceIdx, unsigned timeIdx) const + { + //unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + return this->referencePorosity_[timeIdx][globalSpaceIdx]; + } + + + /*! + * \brief Returns the depth of an degree of freedom [m] + * + * For ECL problems this is defined as the average of the depth of an element and is + * thus slightly different from the depth of an element's centroid. + */ + template + Scalar dofCenterDepth(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + return this->simulator().vanguard().cellCenterDepth(globalSpaceIdx); + } + + Scalar dofCenterDepth(unsigned globalSpaceIdx, unsigned timeIdx) const + { + return this->simulator().vanguard().cellCenterDepth(globalSpaceIdx); + } + + + /*! + * \copydoc BlackoilProblem::rockCompressibility + */ + template + Scalar rockCompressibility(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + if (this->rockParams_.empty()) + return 0.0; + + unsigned tableIdx = 0; + if (!this->rockTableIdx_.empty()) { + unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + tableIdx = this->rockTableIdx_[globalSpaceIdx]; + } + + return this->rockParams_[tableIdx].compressibility; + } + + + Scalar rockCompressibility(unsigned globalSpaceIdx, unsigned timeIdx) const + { + // if (this->rockParams_.empty()) + // return 0.0; + + // unsigned tableIdx = 0; + // if (!this->rockTableIdx_.empty()) { + // tableIdx = this->rockTableIdx_[globalSpaceIdx]; + // } + unsigned tableIdx = 0; + return this->rockParams_[tableIdx].compressibility; + } + + /*! + * \copydoc BlackoilProblem::rockReferencePressure + */ + template + Scalar rockReferencePressure(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + if (this->rockParams_.empty()) + return 1e5; + + unsigned tableIdx = 0; + if (!this->rockTableIdx_.empty()) { + unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + tableIdx = this->rockTableIdx_[globalSpaceIdx]; + } + return this->rockParams_[tableIdx].referencePressure; + } + + Scalar rockReferencePressure(unsigned globalSpaceIdx, unsigned timeIdx) const + { + // if (this->rockParams_.empty()) + // return 1e5; + + // unsigned tableIdx = 0; + // if (!this->rockTableIdx_.empty()) { + // tableIdx = this->rockTableIdx_[globalSpaceIdx]; + // } + unsigned tableIdx = 0;//this->rockTableIdx_[globalSpaceIdx]; + return this->rockParams_[tableIdx].referencePressure; + } + /*! + * \copydoc FvBaseMultiPhaseProblem::materialLawParams + */ + template + const MaterialLawParams& materialLawParams(const Context& context, + unsigned spaceIdx, unsigned timeIdx) const + { + unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + return materialLawParams(globalSpaceIdx); + } + + const MaterialLawParams& materialLawParams(unsigned globalDofIdx) const + { return materialLawManager_->materialLawParams(globalDofIdx); } + + /*! + * \brief Return the parameters for the energy storage law of the rock + */ + template + const SolidEnergyLawParams& + solidEnergyLawParams(const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + return thermalLawManager_->solidEnergyLawParams(globalSpaceIdx); + } + + /*! + * \copydoc FvBaseMultiPhaseProblem::thermalConductionParams + */ + template + const ThermalConductionLawParams & + thermalConductionLawParams(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + return thermalLawManager_->thermalConductionLawParams(globalSpaceIdx); + } + + /*! + * \brief Returns the ECL material law manager + * + * Note that this method is *not* part of the generic eWoms problem API because it + * would force all problens use the ECL material laws. + */ + std::shared_ptr materialLawManager() const + { return materialLawManager_; } + + /*! + * \copydoc materialLawManager() + */ + std::shared_ptr materialLawManager() + { return materialLawManager_; } + + using EclGenericProblem::pvtRegionIndex; + /*! + * \brief Returns the index of the relevant region for thermodynmic properties + */ + template + unsigned pvtRegionIndex(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { return pvtRegionIndex(context.globalSpaceIndex(spaceIdx, timeIdx)); } + + using EclGenericProblem::satnumRegionIndex; + /*! + * \brief Returns the index of the relevant region for thermodynmic properties + */ + template + unsigned satnumRegionIndex(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { return this->satnumRegionIndex(context.globalSpaceIndex(spaceIdx, timeIdx)); } + + using EclGenericProblem::miscnumRegionIndex; + /*! + * \brief Returns the index of the relevant region for thermodynmic properties + */ + template + unsigned miscnumRegionIndex(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { return this->miscnumRegionIndex(context.globalSpaceIndex(spaceIdx, timeIdx)); } + + using EclGenericProblem::plmixnumRegionIndex; + /*! + * \brief Returns the index of the relevant region for thermodynmic properties + */ + template + unsigned plmixnumRegionIndex(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { return this->plmixnumRegionIndex(context.globalSpaceIndex(spaceIdx, timeIdx)); } + + using EclGenericProblem::maxPolymerAdsorption; + /*! + * \brief Returns the max polymer adsorption value + */ + template + Scalar maxPolymerAdsorption(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { return this->maxPolymerAdsorption(context.globalSpaceIndex(spaceIdx, timeIdx)); } + + /*! + * \copydoc FvBaseProblem::name + */ + std::string name() const + { return this->simulator().vanguard().caseName(); } + + /*! + * \copydoc FvBaseMultiPhaseProblem::temperature + */ + template + Scalar temperature(const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + // use the initial temperature of the DOF if temperature is not a primary + // variable + unsigned globalDofIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + return initialFluidStates_[globalDofIdx].temperature(/*phaseIdx=*/0); + } + + /*! + * \copydoc FvBaseProblem::boundary + * + * ECLiPSE uses no-flow conditions for all boundaries. \todo really? + */ + template + void boundary(BoundaryRateVector& values, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + if(!context.intersection(spaceIdx).boundary()) + return; + + if constexpr (!enableEnergy || !enableThermalFluxBoundaries) + values.setNoFlow(); + else { + // in the energy case we need to specify a non-trivial boundary condition + // because the geothermal gradient needs to be maintained. for this, we + // simply assume the initial temperature at the boundary and specify the + // thermal flow accordingly. in this context, "thermal flow" means energy + // flow due to a temerature gradient while assuming no-flow for mass + unsigned interiorDofIdx = context.interiorScvIndex(spaceIdx, timeIdx); + unsigned globalDofIdx = context.globalSpaceIndex(interiorDofIdx, timeIdx); + values.setThermalFlow(context, spaceIdx, timeIdx, initialFluidStates_[globalDofIdx]); + } + + if (nonTrivialBoundaryConditions()) { + unsigned indexInInside = context.intersection(spaceIdx).indexInInside(); + unsigned interiorDofIdx = context.interiorScvIndex(spaceIdx, timeIdx); + unsigned globalDofIdx = context.globalSpaceIndex(interiorDofIdx, timeIdx); + unsigned pvtRegionIdx = pvtRegionIndex(context, spaceIdx, timeIdx); + switch (indexInInside) { + case 0: + if (freebcXMinus_[globalDofIdx]) + values.setFreeFlow(context, spaceIdx, timeIdx, initialFluidStates_[globalDofIdx]); + else + values.setMassRate(massratebcXMinus_[globalDofIdx], pvtRegionIdx); + break; + case 1: + if (freebcX_[globalDofIdx]) + values.setFreeFlow(context, spaceIdx, timeIdx, initialFluidStates_[globalDofIdx]); + else + values.setMassRate(massratebcX_[globalDofIdx], pvtRegionIdx); + break; + case 2: + if (freebcYMinus_[globalDofIdx]) + values.setFreeFlow(context, spaceIdx, timeIdx, initialFluidStates_[globalDofIdx]); + else + values.setMassRate(massratebcYMinus_[globalDofIdx], pvtRegionIdx); + break; + case 3: + if (freebcY_[globalDofIdx]) + values.setFreeFlow(context, spaceIdx, timeIdx, initialFluidStates_[globalDofIdx]); + else + values.setMassRate(massratebcY_[globalDofIdx], pvtRegionIdx); + break; + case 4: + if (freebcZMinus_[globalDofIdx]) + values.setFreeFlow(context, spaceIdx, timeIdx, initialFluidStates_[globalDofIdx]); + else + values.setMassRate(massratebcZMinus_[globalDofIdx], pvtRegionIdx); + break; + case 5: + if (freebcZ_[globalDofIdx]) + values.setFreeFlow(context, spaceIdx, timeIdx, initialFluidStates_[globalDofIdx]); + else + values.setMassRate(massratebcZ_[globalDofIdx], pvtRegionIdx); + break; + default: + throw std::logic_error("invalid face index for boundary condition"); + + } + } + } + + /*! + * \brief Returns an element's historic maximum oil phase saturation that was + * observed during the simulation. + * + * In this context, "historic" means the the time before the current timestep began. + * + * This is a bit of a hack from the conceptional point of view, but it is required to + * match the results of the 'flow' and ECLIPSE 100 simulators. + */ + Scalar maxOilSaturation(unsigned globalDofIdx) const + { + if (!this->vapparsActive(this->episodeIndex())) + return 0.0; + + return this->maxOilSaturation_[globalDofIdx]; + } + + /*! + * \brief Sets an element's maximum oil phase saturation observed during the + * simulation. + * + * In this context, "historic" means the the time before the current timestep began. + * + * This a hack on top of the maxOilSaturation() hack but it is currently required to + * do restart externally. i.e. from the flow code. + */ + void setMaxOilSaturation(unsigned globalDofIdx, Scalar value) + { + if (!this->vapparsActive(this->episodeIndex())) + return; + + this->maxOilSaturation_[globalDofIdx] = value; + } + + /*! + * \brief Returns the maximum value of the gas dissolution factor at the current time + * for a given degree of freedom. + */ + Scalar maxGasDissolutionFactor(unsigned timeIdx, unsigned globalDofIdx) const + { + + int pvtRegionIdx = this->pvtRegionIndex(globalDofIdx); + int episodeIdx = this->episodeIndex(); + if (!this->drsdtActive_(episodeIdx) || this->maxDRs_[pvtRegionIdx] < 0.0) + return std::numeric_limits::max()/2.0; + + Scalar scaling = 1.0; + if (this->drsdtConvective_(episodeIdx)) { + scaling = this->convectiveDrs_[globalDofIdx]; + } + + // this is a bit hacky because it assumes that a time discretization with only + // two time indices is used. + if (timeIdx == 0) + return this->lastRs_[globalDofIdx] + this->maxDRs_[pvtRegionIdx] * scaling; + else + return this->lastRs_[globalDofIdx]; + } + + /*! + * \brief Returns the maximum value of the oil vaporization factor at the current + * time for a given degree of freedom. + */ + Scalar maxOilVaporizationFactor(unsigned timeIdx, unsigned globalDofIdx) const + { + int pvtRegionIdx = this->pvtRegionIndex(globalDofIdx); + int episodeIdx = this->episodeIndex(); + if (!this->drvdtActive_(episodeIdx) || this->maxDRv_[pvtRegionIdx] < 0.0) + return std::numeric_limits::max()/2.0; + + // this is a bit hacky because it assumes that a time discretization with only + // two time indices is used. + if (timeIdx == 0) + return this->lastRv_[globalDofIdx] + this->maxDRv_[pvtRegionIdx]; + else + return this->lastRv_[globalDofIdx]; + } + + /*! + * \brief Return if the storage term of the first iteration is identical to the storage + * term for the solution of the previous time step. + * + * For quite technical reasons, the storage term cannot be recycled if either DRSDT + * or DRVDT are active in ebos. Nor if the porosity is changes between timesteps + * using a pore volume multiplier (i.e., poreVolumeMultiplier() != 1.0) + */ + bool recycleFirstIterationStorage() const + { + int episodeIdx = this->episodeIndex(); + return !this->drsdtActive_(episodeIdx) && + !this->drvdtActive_(episodeIdx) && + this->rockCompPoroMultWc_.empty() && + this->rockCompPoroMult_.empty(); + } + + /*! + * \copydoc FvBaseProblem::initial + * + * The reservoir problem uses a constant boundary condition for + * the whole domain. + */ + template + void initial(PrimaryVariables& values, const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + unsigned globalDofIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + + values.setPvtRegionIndex(pvtRegionIndex(context, spaceIdx, timeIdx)); + values.assignNaive(initialFluidStates_[globalDofIdx]); + + if constexpr (enableSolvent) + values[Indices::solventSaturationIdx] = this->solventSaturation_[globalDofIdx]; + + if constexpr (enablePolymer) + values[Indices::polymerConcentrationIdx] = this->polymerConcentration_[globalDofIdx]; + + if constexpr (enablePolymerMolarWeight) + values[Indices::polymerMoleWeightIdx]= this->polymerMoleWeight_[globalDofIdx]; + + if constexpr (enableBrine) { + if (enableSaltPrecipitation && values.primaryVarsMeaningBrine() == PrimaryVariables::Sp) { + values[Indices::saltConcentrationIdx] = initialFluidStates_[globalDofIdx].saltSaturation(); + } + else { + values[Indices::saltConcentrationIdx] = initialFluidStates_[globalDofIdx].saltConcentration(); + } + } + + if constexpr (enableMICP){ + values[Indices::microbialConcentrationIdx]= this->microbialConcentration_[globalDofIdx]; + values[Indices::oxygenConcentrationIdx]= this->oxygenConcentration_[globalDofIdx]; + values[Indices::ureaConcentrationIdx]= this->ureaConcentration_[globalDofIdx]; + values[Indices::calciteConcentrationIdx]= this->calciteConcentration_[globalDofIdx]; + values[Indices::biofilmConcentrationIdx]= this->biofilmConcentration_[globalDofIdx]; + } + + values.checkDefined(); + } + + /*! + * \copydoc FvBaseProblem::initialSolutionApplied() + */ + void initialSolutionApplied() + { + // initialize the wells. Note that this needs to be done after initializing the + // intrinsic permeabilities and the after applying the initial solution because + // the well model uses these... + wellModel_.init(); + + // let the object for threshold pressures initialize itself. this is done only at + // this point, because determining the threshold pressures may require to access + // the initial solution. + thresholdPressures_.finishInit(); + + updateCompositionChangeLimits_(); + + if (enableAquifers_) + aquiferModel_.initialSolutionApplied(); + } + + template + void source(RateVector& rate, + unsigned globalSpaceIdx, + unsigned timeIdx) const + { + rate = 0.0; + + wellModel_.computeTotalRatesForDof(rate, globalSpaceIdx, timeIdx); + + // convert the source term from the total mass rate of the + // cell to the one per unit of volume as used by the model. + + for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) { + rate[eqIdx] /= this->model().dofTotalVolume(globalSpaceIdx); + + Valgrind::CheckDefined(rate[eqIdx]); + assert(isfinite(rate[eqIdx])); + } + + // if (enableAquifers_) + // aquiferModel_.addToSource(rate, context, spaceIdx, timeIdx); + + // if requested, compensate systematic mass loss for cells which were "well + // behaved" in the last time step + // if (enableDriftCompensation_) { + // const auto& intQuants = context.intensiveQuantities(spaceIdx, timeIdx); + // const auto& simulator = this->simulator(); + // const auto& model = this->model(); + + // // we need a higher maxCompensation than the Newton tolerance because the + // // current time step might be shorter than the last one + // Scalar maxCompensation = 10.0*model.newtonMethod().tolerance(); + + // Scalar poro = intQuants.referencePorosity(); + // Scalar dt = simulator.timeStepSize(); + + // EqVector dofDriftRate = drift_[globalDofIdx]; + // dofDriftRate /= dt*context.dofTotalVolume(spaceIdx, timeIdx); + + // // compute the weighted total drift rate + // Scalar totalDriftRate = 0.0; + // for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) + // totalDriftRate += + // std::abs(dofDriftRate[eqIdx])*dt*model.eqWeight(globalDofIdx, eqIdx)/poro; + + // // make sure that we do not exceed the maximum rate of drift compensation + // if (totalDriftRate > maxCompensation) + // dofDriftRate *= maxCompensation/totalDriftRate; + + // for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) + // rate[eqIdx] -= dofDriftRate[eqIdx]; + // } + } + + + /*! + * \copydoc FvBaseProblem::source + * + * For this problem, the source term of all components is 0 everywhere. + */ + template + void source(RateVector& rate, + const Context& context, + unsigned spaceIdx, + unsigned timeIdx) const + { + rate = 0.0; + + wellModel_.computeTotalRatesForDof(rate, context, spaceIdx, timeIdx); + + // convert the source term from the total mass rate of the + // cell to the one per unit of volume as used by the model. + const unsigned globalDofIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) { + rate[eqIdx] /= this->model().dofTotalVolume(globalDofIdx); + + Valgrind::CheckDefined(rate[eqIdx]); + assert(isfinite(rate[eqIdx])); + } + + if (enableAquifers_) + aquiferModel_.addToSource(rate, context, spaceIdx, timeIdx); + + // if requested, compensate systematic mass loss for cells which were "well + // behaved" in the last time step + if (enableDriftCompensation_) { + const auto& intQuants = context.intensiveQuantities(spaceIdx, timeIdx); + const auto& simulator = this->simulator(); + const auto& model = this->model(); + + // we need a higher maxCompensation than the Newton tolerance because the + // current time step might be shorter than the last one + Scalar maxCompensation = 10.0*model.newtonMethod().tolerance(); + + Scalar poro = intQuants.referencePorosity(); + Scalar dt = simulator.timeStepSize(); + + EqVector dofDriftRate = drift_[globalDofIdx]; + dofDriftRate /= dt*context.dofTotalVolume(spaceIdx, timeIdx); + + // compute the weighted total drift rate + Scalar totalDriftRate = 0.0; + for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) + totalDriftRate += + std::abs(dofDriftRate[eqIdx])*dt*model.eqWeight(globalDofIdx, eqIdx)/poro; + + // make sure that we do not exceed the maximum rate of drift compensation + if (totalDriftRate > maxCompensation) + dofDriftRate *= maxCompensation/totalDriftRate; + + for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) + rate[eqIdx] -= dofDriftRate[eqIdx]; + } + } + + /*! + * \brief Returns a reference to the ECL well manager used by the problem. + * + * This can be used for inspecting wells outside of the problem. + */ + const EclWellModel& wellModel() const + { return wellModel_; } + + EclWellModel& wellModel() + { return wellModel_; } + + const EclAquiferModel& aquiferModel() const + { return aquiferModel_; } + + EclAquiferModel& mutableAquiferModel() + { return aquiferModel_; } + + // temporary solution to facilitate output of initial state from flow + const InitialFluidState& initialFluidState(unsigned globalDofIdx) const + { return initialFluidStates_[globalDofIdx]; } + + const EclipseIO& eclIO() const + { return eclWriter_->eclIO(); } + + void setSubStepReport(const SimulatorReportSingle& report) + { return eclWriter_->setSubStepReport(report); } + + void setSimulationReport(const SimulatorReport& report) + { return eclWriter_->setSimulationReport(report); } + + bool nonTrivialBoundaryConditions() const + { return nonTrivialBoundaryConditions_; } + + /*! + * \brief Propose the size of the next time step to the simulator. + * + * This method is only called if the Newton solver does converge, the simulator + * automatically cuts the time step in half without consultating this method again. + */ + Scalar nextTimeStepSize() const + { + // allow external code to do the timestepping + if (this->nextTimeStepSize_ > 0.0) + return this->nextTimeStepSize_; + + const auto& simulator = this->simulator(); + int episodeIdx = simulator.episodeIndex(); + + // for the initial episode, we use a fixed time step size + if (episodeIdx < 0) + return this->initialTimeStepSize_; + + // ask the newton method for a suggestion. This suggestion will be based on how + // well the previous time step converged. After that, apply the runtime time + // stepping constraints. + const auto& newtonMethod = this->model().newtonMethod(); + return limitNextTimeStepSize_(newtonMethod.suggestTimeStepSize(simulator.timeStepSize())); + } + + /*! + * \brief Calculate the porosity multiplier due to water induced rock compaction. + * + * TODO: The API of this is a bit ad-hoc, it would be better to use context objects. + */ + template + LhsEval rockCompPoroMultiplier(const IntensiveQuantities& intQuants, unsigned elementIdx) const + { + + if (this->rockCompPoroMult_.empty() && this->rockCompPoroMultWc_.empty()) + return 1.0; + + unsigned tableIdx = 0; + if (!this->rockTableIdx_.empty()) + tableIdx = this->rockTableIdx_[elementIdx]; + + const auto& fs = intQuants.fluidState(); + LhsEval effectiveOilPressure = decay(fs.pressure(oilPhaseIdx)); + if (!this->minOilPressure_.empty()) + // The pore space change is irreversible + effectiveOilPressure = + min(decay(fs.pressure(oilPhaseIdx)), + this->minOilPressure_[elementIdx]); + + if (!this->overburdenPressure_.empty()) + effectiveOilPressure -= this->overburdenPressure_[elementIdx]; + + + if (!this->rockCompPoroMult_.empty()) { + return this->rockCompPoroMult_[tableIdx].eval(effectiveOilPressure, /*extrapolation=*/true); + } + + // water compaction + assert(!this->rockCompPoroMultWc_.empty()); + LhsEval SwMax = max(decay(fs.saturation(waterPhaseIdx)), this->maxWaterSaturation_[elementIdx]); + LhsEval SwDeltaMax = SwMax - initialFluidStates_[elementIdx].saturation(waterPhaseIdx); + + return this->rockCompPoroMultWc_[tableIdx].eval(effectiveOilPressure, SwDeltaMax, /*extrapolation=*/true); + } + + /*! + * \brief Calculate the transmissibility multiplier due to water induced rock compaction. + * + * TODO: The API of this is a bit ad-hoc, it would be better to use context objects. + */ + template + LhsEval rockCompTransMultiplier(const IntensiveQuantities& intQuants, unsigned elementIdx) const + { + if (this->rockCompTransMult_.empty() && this->rockCompTransMultWc_.empty()) + return 1.0; + + unsigned tableIdx = 0; + if (!this->rockTableIdx_.empty()) + tableIdx = this->rockTableIdx_[elementIdx]; + + const auto& fs = intQuants.fluidState(); + LhsEval effectiveOilPressure = decay(fs.pressure(oilPhaseIdx)); + + if (!this->minOilPressure_.empty()) + // The pore space change is irreversible + effectiveOilPressure = + min(decay(fs.pressure(oilPhaseIdx)), + this->minOilPressure_[elementIdx]); + + if (!this->overburdenPressure_.empty()) + effectiveOilPressure -= this->overburdenPressure_[elementIdx]; + + if (!this->rockCompTransMult_.empty()) + return this->rockCompTransMult_[tableIdx].eval(effectiveOilPressure, /*extrapolation=*/true); + + // water compaction + assert(!this->rockCompTransMultWc_.empty()); + LhsEval SwMax = max(decay(fs.saturation(waterPhaseIdx)), this->maxWaterSaturation_[elementIdx]); + LhsEval SwDeltaMax = SwMax - initialFluidStates_[elementIdx].saturation(waterPhaseIdx); + + return this->rockCompTransMultWc_[tableIdx].eval(effectiveOilPressure, SwDeltaMax, /*extrapolation=*/true); + } + +private: + // update the parameters needed for DRSDT and DRVDT + void updateCompositionChangeLimits_() + { + // update the "last Rs" values for all elements, including the ones in the ghost + // and overlap regions + const auto& simulator = this->simulator(); + int episodeIdx = this->episodeIndex(); + + OPM_BEGIN_PARALLEL_TRY_CATCH(); + if (this->drsdtConvective_(episodeIdx)) { + // This implements the convective DRSDT as described in + // Sandve et al. "Convective dissolution in field scale CO2 storage simulations using the OPM Flow simulator" + // Submitted to TCCS 11, 2021 + Scalar g = this->gravity_[dim - 1]; + ElementContext elemCtx(simulator); + const auto& vanguard = simulator.vanguard(); + auto elemIt = vanguard.gridView().template begin(); + const auto& elemEndIt = vanguard.gridView().template end(); + for (; elemIt != elemEndIt; ++elemIt) { + const Element& elem = *elemIt; + elemCtx.updatePrimaryStencil(elem); + elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); + unsigned compressedDofIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); + const DimMatrix& perm = intrinsicPermeability(compressedDofIdx); + const Scalar permz = perm[dim - 1][dim - 1]; // The Z permeability + Scalar distZ = vanguard.cellThickness(compressedDofIdx); + const auto& iq = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0); + const auto& fs = iq.fluidState(); + Scalar t = getValue(fs.temperature(FluidSystem::oilPhaseIdx)); + Scalar p = getValue(fs.pressure(FluidSystem::oilPhaseIdx)); + Scalar so = getValue(fs.saturation(FluidSystem::oilPhaseIdx)); + Scalar rssat = FluidSystem::oilPvt().saturatedGasDissolutionFactor(fs.pvtRegionIndex(),t,p); + Scalar saturatedInvB = FluidSystem::oilPvt().saturatedInverseFormationVolumeFactor(fs.pvtRegionIndex(),t,p); + Scalar rsZero = 0.0; + Scalar pureDensity = FluidSystem::oilPvt().inverseFormationVolumeFactor(fs.pvtRegionIndex(),t,p,rsZero) * FluidSystem::oilPvt().oilReferenceDensity(fs.pvtRegionIndex()); + Scalar saturatedDensity = saturatedInvB * (FluidSystem::oilPvt().oilReferenceDensity(fs.pvtRegionIndex()) + rssat * FluidSystem::referenceDensity(FluidSystem::gasPhaseIdx, fs.pvtRegionIndex())); + Scalar deltaDensity = saturatedDensity - pureDensity; + Scalar rs = getValue(fs.Rs()); + Scalar visc = FluidSystem::oilPvt().viscosity(fs.pvtRegionIndex(),t,p,rs); + Scalar poro = getValue(iq.porosity()); + // Note that for so = 0 this gives no limits (inf) for the dissolution rate + // Also we restrict the effect of convective mixing to positive density differences + // i.e. we only allow for fingers moving downward + this->convectiveDrs_[compressedDofIdx] = permz * rssat * max(0.0, deltaDensity) * g / ( so * visc * distZ * poro); + } + } + + if (this->drsdtActive_(episodeIdx)) { + ElementContext elemCtx(simulator); + const auto& vanguard = simulator.vanguard(); + auto elemIt = vanguard.gridView().template begin(); + const auto& elemEndIt = vanguard.gridView().template end(); + for (; elemIt != elemEndIt; ++elemIt) { + const Element& elem = *elemIt; + + elemCtx.updatePrimaryStencil(elem); + elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); + + unsigned compressedDofIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); + const auto& iq = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0); + const auto& fs = iq.fluidState(); + + using FluidState = typename std::decay::type; + + int pvtRegionIdx = this->pvtRegionIndex(compressedDofIdx); + const auto& oilVaporizationControl = simulator.vanguard().schedule()[episodeIdx].oilvap(); + if (oilVaporizationControl.getOption(pvtRegionIdx) || fs.saturation(gasPhaseIdx) > freeGasMinSaturation_) + this->lastRs_[compressedDofIdx] = + BlackOil::template getRs_(fs, iq.pvtRegionIndex()); + else + this->lastRs_[compressedDofIdx] = std::numeric_limits::infinity(); + } + } + + // update the "last Rv" values for all elements, including the ones in the ghost + // and overlap regions + if (this->drvdtActive_(episodeIdx)) { + ElementContext elemCtx(simulator); + const auto& vanguard = simulator.vanguard(); + auto elemIt = vanguard.gridView().template begin(); + const auto& elemEndIt = vanguard.gridView().template end(); + for (; elemIt != elemEndIt; ++elemIt) { + const Element& elem = *elemIt; + + elemCtx.updatePrimaryStencil(elem); + elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); + + unsigned compressedDofIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); + const auto& iq = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0); + const auto& fs = iq.fluidState(); + + using FluidState = typename std::decay::type; + + this->lastRv_[compressedDofIdx] = + BlackOil::template getRv_(fs, iq.pvtRegionIndex()); + } + } + OPM_END_PARALLEL_TRY_CATCH("EclProblemTPFA::_updateCompositionLayers() failed: ", this->simulator().vanguard().grid().comm()); + } + + bool updateMaxOilSaturation_() + { + const auto& simulator = this->simulator(); + int episodeIdx = this->episodeIndex(); + + // we use VAPPARS + if (this->vapparsActive(episodeIdx)) { + ElementContext elemCtx(simulator); + const auto& vanguard = simulator.vanguard(); + auto elemIt = vanguard.gridView().template begin(); + const auto& elemEndIt = vanguard.gridView().template end(); + OPM_BEGIN_PARALLEL_TRY_CATCH(); + for (; elemIt != elemEndIt; ++elemIt) { + const Element& elem = *elemIt; + + elemCtx.updatePrimaryStencil(elem); + elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); + + unsigned compressedDofIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); + const auto& iq = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0); + const auto& fs = iq.fluidState(); + + Scalar So = decay(fs.saturation(oilPhaseIdx)); + + this->maxOilSaturation_[compressedDofIdx] = std::max(this->maxOilSaturation_[compressedDofIdx], So); + } + OPM_END_PARALLEL_TRY_CATCH("EclProblemTPFA::updateMayOilSaturation() failed:", vanguard.grid().comm()); + // we need to invalidate the intensive quantities cache here because the + // derivatives of Rs and Rv will most likely have changed + return true; + } + + return false; + } + + bool updateMaxWaterSaturation_() + { + // water compaction is activated in ROCKCOMP + if (this->maxWaterSaturation_.empty()) + return false; + + this->maxWaterSaturation_[/*timeIdx=*/1] = this->maxWaterSaturation_[/*timeIdx=*/0]; + ElementContext elemCtx(this->simulator()); + const auto& vanguard = this->simulator().vanguard(); + auto elemIt = vanguard.gridView().template begin(); + const auto& elemEndIt = vanguard.gridView().template end(); + OPM_BEGIN_PARALLEL_TRY_CATCH(); + for (; elemIt != elemEndIt; ++elemIt) { + const Element& elem = *elemIt; + + elemCtx.updatePrimaryStencil(elem); + elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); + + unsigned compressedDofIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); + const auto& iq = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0); + const auto& fs = iq.fluidState(); + + Scalar Sw = decay(fs.saturation(waterPhaseIdx)); + this->maxWaterSaturation_[compressedDofIdx] = std::max(this->maxWaterSaturation_[compressedDofIdx], Sw); + } + OPM_END_PARALLEL_TRY_CATCH("EclProblemTPFA::updateMayWaterSaturation() failed: ", vanguard.grid().comm()); + + return true; + } + + bool updateMinPressure_() + { + // IRREVERS option is used in ROCKCOMP + if (this->minOilPressure_.empty()) + return false; + + OPM_BEGIN_PARALLEL_TRY_CATCH(); + ElementContext elemCtx(this->simulator()); + const auto& vanguard = this->simulator().vanguard(); + auto elemIt = vanguard.gridView().template begin(); + const auto& elemEndIt = vanguard.gridView().template end(); + for (; elemIt != elemEndIt; ++elemIt) { + const Element& elem = *elemIt; + + elemCtx.updatePrimaryStencil(elem); + elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); + + unsigned compressedDofIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); + const auto& iq = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0); + const auto& fs = iq.fluidState(); + + this->minOilPressure_[compressedDofIdx] = + std::min(this->minOilPressure_[compressedDofIdx], + getValue(fs.pressure(oilPhaseIdx))); + } + OPM_END_PARALLEL_TRY_CATCH("EclProblemTPFA::updateMinPressure_() failed: ", this->simulator().vanguard().grid().comm()); + return true; + } + + void readMaterialParameters_() + { + const auto& simulator = this->simulator(); + const auto& vanguard = simulator.vanguard(); + const auto& eclState = vanguard.eclState(); + + // the PVT and saturation region numbers + this->updatePvtnum_(); + this->updateSatnum_(); + + // the MISC region numbers (solvent model) + this->updateMiscnum_(); + // the PLMIX region numbers (polymer model) + this->updatePlmixnum_(); + + //////////////////////////////// + // porosity + updateReferencePorosity_(); + this->referencePorosity_[1] = this->referencePorosity_[0]; + //////////////////////////////// + + //////////////////////////////// + // fluid-matrix interactions (saturation functions; relperm/capillary pressure) + materialLawManager_ = std::make_shared(); + materialLawManager_->initFromState(eclState); + materialLawManager_->initParamsForElements(eclState, this->model().numGridDof()); + //////////////////////////////// + } + + void readThermalParameters_() + { + if constexpr (enableEnergy) + { + const auto& simulator = this->simulator(); + const auto& vanguard = simulator.vanguard(); + const auto& eclState = vanguard.eclState(); + + // fluid-matrix interactions (saturation functions; relperm/capillary pressure) + thermalLawManager_ = std::make_shared(); + thermalLawManager_->initParamsForElements(eclState, this->model().numGridDof()); + } + } + + void updateReferencePorosity_() + { + const auto& simulator = this->simulator(); + const auto& vanguard = simulator.vanguard(); + const auto& eclState = vanguard.eclState(); + + size_t numDof = this->model().numGridDof(); + + this->referencePorosity_[/*timeIdx=*/0].resize(numDof); + + const auto& fp = eclState.fieldProps(); + const std::vector porvData = fp.porv(false); + const std::vector actnumData = fp.actnum(); + for (size_t dofIdx = 0; dofIdx < numDof; ++ dofIdx) { + Scalar poreVolume = porvData[dofIdx]; + + // we define the porosity as the accumulated pore volume divided by the + // geometric volume of the element. Note that -- in pathetic cases -- it can + // be larger than 1.0! + Scalar dofVolume = simulator.model().dofTotalVolume(dofIdx); + assert(dofVolume > 0.0); + this->referencePorosity_[/*timeIdx=*/0][dofIdx] = poreVolume/dofVolume; + } + } + + void readInitialCondition_() + { + const auto& simulator = this->simulator(); + const auto& vanguard = simulator.vanguard(); + const auto& eclState = vanguard.eclState(); + + if (eclState.getInitConfig().hasEquil()) + readEquilInitialCondition_(); + else + readExplicitInitialCondition_(); + + if constexpr (enableSolvent || enablePolymer || enablePolymerMolarWeight || enableMICP) + this->readBlackoilExtentionsInitialConditions_(this->model().numGridDof(), + enableSolvent, + enablePolymer, + enablePolymerMolarWeight, + enableMICP); + + //initialize min/max values + size_t numElems = this->model().numGridDof(); + for (size_t elemIdx = 0; elemIdx < numElems; ++elemIdx) { + const auto& fs = initialFluidStates_[elemIdx]; + if (!this->maxWaterSaturation_.empty()) + this->maxWaterSaturation_[elemIdx] = std::max(this->maxWaterSaturation_[elemIdx], fs.saturation(waterPhaseIdx)); + if (!this->maxOilSaturation_.empty()) + this->maxOilSaturation_[elemIdx] = std::max(this->maxOilSaturation_[elemIdx], fs.saturation(oilPhaseIdx)); + if (!this->minOilPressure_.empty()) + this->minOilPressure_[elemIdx] = std::min(this->minOilPressure_[elemIdx], fs.pressure(oilPhaseIdx)); + } + + + } + + void readEquilInitialCondition_() + { + const auto& simulator = this->simulator(); + + // initial condition corresponds to hydrostatic conditions. + using EquilInitializer = EclEquilInitializer; + EquilInitializer equilInitializer(simulator, *materialLawManager_); + + size_t numElems = this->model().numGridDof(); + initialFluidStates_.resize(numElems); + for (size_t elemIdx = 0; elemIdx < numElems; ++elemIdx) { + auto& elemFluidState = initialFluidStates_[elemIdx]; + elemFluidState.assign(equilInitializer.initialFluidState(elemIdx)); + } + } + + void readEclRestartSolution_() + { + // Set the start time of the simulation + auto& simulator = this->simulator(); + const auto& schedule = simulator.vanguard().schedule(); + const auto& eclState = simulator.vanguard().eclState(); + const auto& initconfig = eclState.getInitConfig(); + { + int restart_step = initconfig.getRestartStep(); + + simulator.setTime(schedule.seconds(restart_step)); + + simulator.startNextEpisode(simulator.startTime() + simulator.time(), + schedule.stepLength(restart_step)); + simulator.setEpisodeIndex(restart_step); + } + eclWriter_->beginRestart(); + + Scalar dt = std::min(eclWriter_->restartTimeStepSize(), simulator.episodeLength()); + simulator.setTimeStepSize(dt); + + size_t numElems = this->model().numGridDof(); + initialFluidStates_.resize(numElems); + if constexpr (enableSolvent) + this->solventSaturation_.resize(numElems, 0.0); + + if constexpr (enablePolymer) + this->polymerConcentration_.resize(numElems, 0.0); + + if constexpr (enablePolymerMolarWeight) { + const std::string msg {"Support of the RESTART for polymer molecular weight " + "is not implemented yet. The polymer weight value will be " + "zero when RESTART begins"}; + OpmLog::warning("NO_POLYMW_RESTART", msg); + this->polymerMoleWeight_.resize(numElems, 0.0); + } + + if constexpr (enableMICP){ + this->microbialConcentration_.resize(numElems, 0.0); + this->oxygenConcentration_.resize(numElems, 0.0); + this->ureaConcentration_.resize(numElems, 0.0); + this->biofilmConcentration_.resize(numElems, 0.0); + this->calciteConcentration_.resize(numElems, 0.0); + } + + for (size_t elemIdx = 0; elemIdx < numElems; ++elemIdx) { + auto& elemFluidState = initialFluidStates_[elemIdx]; + elemFluidState.setPvtRegionIndex(pvtRegionIndex(elemIdx)); + eclWriter_->eclOutputModule().initHysteresisParams(simulator, elemIdx); + eclWriter_->eclOutputModule().assignToFluidState(elemFluidState, elemIdx); + + // Note: Function processRestartSaturations_() mutates the + // 'ssol' argument--the value from the restart file--if solvent + // is enabled. Then, store the updated solvent saturation into + // 'solventSaturation_'. Otherwise, just pass a dummy value to + // the function and discard the unchanged result. Do not index + // into 'solventSaturation_' unless solvent is enabled. + { + auto ssol = enableSolvent + ? eclWriter_->eclOutputModule().getSolventSaturation(elemIdx) + : Scalar(0); + + processRestartSaturations_(elemFluidState, ssol); + + if constexpr (enableSolvent) + this->solventSaturation_[elemIdx] = ssol; + } + + if (! this->lastRs_.empty()) { + this->lastRs_[elemIdx] = elemFluidState.Rs(); + } + + if (! this->lastRv_.empty()) { + this->lastRv_[elemIdx] = elemFluidState.Rv(); + } + + if constexpr (enablePolymer) + this->polymerConcentration_[elemIdx] = eclWriter_->eclOutputModule().getPolymerConcentration(elemIdx); + if constexpr (enableMICP){ + this->microbialConcentration_[elemIdx] = eclWriter_->eclOutputModule().getMicrobialConcentration(elemIdx); + this->oxygenConcentration_[elemIdx] = eclWriter_->eclOutputModule().getOxygenConcentration(elemIdx); + this->ureaConcentration_[elemIdx] = eclWriter_->eclOutputModule().getUreaConcentration(elemIdx); + this->biofilmConcentration_[elemIdx] = eclWriter_->eclOutputModule().getBiofilmConcentration(elemIdx); + this->calciteConcentration_[elemIdx] = eclWriter_->eclOutputModule().getCalciteConcentration(elemIdx); + } + // if we need to restart for polymer molecular weight simulation, we need to add related here + } + + const int episodeIdx = this->episodeIndex(); + const auto& oilVaporizationControl = simulator.vanguard().schedule()[episodeIdx].oilvap(); + if (this->drsdtActive_(episodeIdx)) + // DRSDT is enabled + for (size_t pvtRegionIdx = 0; pvtRegionIdx < this->maxDRs_.size(); ++pvtRegionIdx) + this->maxDRs_[pvtRegionIdx] = oilVaporizationControl.getMaxDRSDT(pvtRegionIdx)*simulator.timeStepSize(); + + if (this->drvdtActive_(episodeIdx)) + // DRVDT is enabled + for (size_t pvtRegionIdx = 0; pvtRegionIdx < this->maxDRv_.size(); ++pvtRegionIdx) + this->maxDRv_[pvtRegionIdx] = oilVaporizationControl.getMaxDRVDT(pvtRegionIdx)*simulator.timeStepSize(); + + // assign the restart solution to the current solution. note that we still need + // to compute real initial solution after this because the initial fluid states + // need to be correct for stuff like boundary conditions. + auto& sol = this->model().solution(/*timeIdx=*/0); + const auto& gridView = this->gridView(); + ElementContext elemCtx(simulator); + auto elemIt = gridView.template begin(); + const auto& elemEndIt = gridView.template end(); + for (; elemIt != elemEndIt; ++elemIt) { + const auto& elem = *elemIt; + if (elem.partitionType() != Dune::InteriorEntity) + continue; + + elemCtx.updatePrimaryStencil(elem); + int elemIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); + initial(sol[elemIdx], elemCtx, /*spaceIdx=*/0, /*timeIdx=*/0); + } + + // make sure that the ghost and overlap entities exhibit the correct + // solution. alternatively, this could be done in the loop above by also + // considering non-interior elements. Since the initial() method might not work + // 100% correctly for such elements, let's play safe and explicitly synchronize + // using message passing. + this->model().syncOverlap(); + + eclWriter_->endRestart(); + } + + void processRestartSaturations_(InitialFluidState& elemFluidState, Scalar& solventSaturation) + { + // each phase needs to be above certain value to be claimed to be existing + // this is used to recover some RESTART running with the defaulted single-precision format + const Scalar smallSaturationTolerance = 1.e-6; + Scalar sumSaturation = 0.0; + for (size_t phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (FluidSystem::phaseIsActive(phaseIdx)) { + if (elemFluidState.saturation(phaseIdx) < smallSaturationTolerance) + elemFluidState.setSaturation(phaseIdx, 0.0); + + sumSaturation += elemFluidState.saturation(phaseIdx); + } + + } + if constexpr (enableSolvent) { + if (solventSaturation < smallSaturationTolerance) + solventSaturation = 0.0; + + sumSaturation += solventSaturation; + } + + assert(sumSaturation > 0.0); + + for (size_t phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (FluidSystem::phaseIsActive(phaseIdx)) { + const Scalar saturation = elemFluidState.saturation(phaseIdx) / sumSaturation; + elemFluidState.setSaturation(phaseIdx, saturation); + } + } + if constexpr (enableSolvent) { + solventSaturation = solventSaturation / sumSaturation; + } + } + + void readExplicitInitialCondition_() + { + const auto& simulator = this->simulator(); + const auto& vanguard = simulator.vanguard(); + const auto& eclState = vanguard.eclState(); + const auto& fp = eclState.fieldProps(); + bool has_swat = fp.has_double("SWAT"); + bool has_sgas = fp.has_double("SGAS"); + bool has_rs = fp.has_double("RS"); + bool has_rv = fp.has_double("RV"); + bool has_rvw = fp.has_double("RVW"); + bool has_pressure = fp.has_double("PRESSURE"); + bool has_salt = fp.has_double("SALT"); + bool has_saltp = fp.has_double("SALTP"); + + // make sure all required quantities are enables + if (Indices::numPhases > 1) { + if (FluidSystem::phaseIsActive(waterPhaseIdx) && !has_swat) + throw std::runtime_error("The ECL input file requires the presence of the SWAT keyword if " + "the water phase is active"); + if (FluidSystem::phaseIsActive(gasPhaseIdx) && !has_sgas && FluidSystem::phaseIsActive(oilPhaseIdx)) + throw std::runtime_error("The ECL input file requires the presence of the SGAS keyword if " + "the gas phase is active"); + } + if (!has_pressure) + throw std::runtime_error("The ECL input file requires the presence of the PRESSURE " + "keyword if the model is initialized explicitly"); + if (FluidSystem::enableDissolvedGas() && !has_rs) + throw std::runtime_error("The ECL input file requires the RS keyword to be present if" + " dissolved gas is enabled"); + if (FluidSystem::enableVaporizedOil() && !has_rv) + throw std::runtime_error("The ECL input file requires the RV keyword to be present if" + " vaporized oil is enabled"); + if (FluidSystem::enableVaporizedWater() && !has_rvw) + throw std::runtime_error("The ECL input file requires the RVW keyword to be present if" + " vaporized water is enabled"); + if (enableBrine && !has_salt) + throw std::runtime_error("The ECL input file requires the SALT keyword to be present if" + " brine is enabled and the model is initialized explicitly"); + if (enableSaltPrecipitation && !has_saltp) + throw std::runtime_error("The ECL input file requires the SALTP keyword to be present if" + " salt precipitation is enabled and the model is initialized explicitly"); + + size_t numDof = this->model().numGridDof(); + + initialFluidStates_.resize(numDof); + + std::vector waterSaturationData; + std::vector gasSaturationData; + std::vector pressureData; + std::vector rsData; + std::vector rvData; + std::vector rvwData; + std::vector tempiData; + std::vector saltData; + std::vector saltpData; + + if (FluidSystem::phaseIsActive(waterPhaseIdx) && Indices::numPhases > 1) + waterSaturationData = fp.get_double("SWAT"); + else + waterSaturationData.resize(numDof); + + if (FluidSystem::phaseIsActive(gasPhaseIdx) && FluidSystem::phaseIsActive(oilPhaseIdx)) + gasSaturationData = fp.get_double("SGAS"); + else + gasSaturationData.resize(numDof); + + pressureData = fp.get_double("PRESSURE"); + if (FluidSystem::enableDissolvedGas()) + rsData = fp.get_double("RS"); + + if (FluidSystem::enableVaporizedOil()) + rvData = fp.get_double("RV"); + + if (FluidSystem::enableVaporizedWater()) + rvwData = fp.get_double("RVW"); + + // initial reservoir temperature + tempiData = fp.get_double("TEMPI"); + + // initial salt concentration data + if (enableBrine) + saltData = fp.get_double("SALT"); + + // initial precipitated salt saturation data + if (enableSaltPrecipitation) + saltpData = fp.get_double("SALTP"); + + // calculate the initial fluid states + for (size_t dofIdx = 0; dofIdx < numDof; ++dofIdx) { + auto& dofFluidState = initialFluidStates_[dofIdx]; + + dofFluidState.setPvtRegionIndex(pvtRegionIndex(dofIdx)); + + ////// + // set temperature + ////// + Scalar temperatureLoc = tempiData[dofIdx]; + if (!std::isfinite(temperatureLoc) || temperatureLoc <= 0) + temperatureLoc = FluidSystem::surfaceTemperature; + dofFluidState.setTemperature(temperatureLoc); + + ////// + // set salt concentration + ////// + if (enableBrine) + dofFluidState.setSaltConcentration(saltData[dofIdx]); + + ////// + // set precipitated salt saturation + ////// + if (enableSaltPrecipitation) + dofFluidState.setSaltSaturation(saltpData[dofIdx]); + + ////// + // set saturations + ////// + if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) + dofFluidState.setSaturation(FluidSystem::waterPhaseIdx, + waterSaturationData[dofIdx]); + + if (FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)){ + if (!FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)){ + dofFluidState.setSaturation(FluidSystem::gasPhaseIdx, + 1.0 + - waterSaturationData[dofIdx]); + } + else + dofFluidState.setSaturation(FluidSystem::gasPhaseIdx, + gasSaturationData[dofIdx]); + } + if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) + dofFluidState.setSaturation(FluidSystem::oilPhaseIdx, + 1.0 + - waterSaturationData[dofIdx] + - gasSaturationData[dofIdx]); + + ////// + // set phase pressures + ////// + Scalar pressure = pressureData[dofIdx]; // oil pressure (or gas pressure for water-gas system or water pressure for single phase) + + // this assumes that capillary pressures only depend on the phase saturations + // and possibly on temperature. (this is always the case for ECL problems.) + Dune::FieldVector pc(0.0); + const auto& matParams = materialLawParams(dofIdx); + MaterialLaw::capillaryPressures(pc, matParams, dofFluidState); + Valgrind::CheckDefined(pressure); + Valgrind::CheckDefined(pc); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) + continue; + + if (Indices::oilEnabled) + dofFluidState.setPressure(phaseIdx, pressure + (pc[phaseIdx] - pc[oilPhaseIdx])); + else if (Indices::gasEnabled) + dofFluidState.setPressure(phaseIdx, pressure + (pc[phaseIdx] - pc[gasPhaseIdx])); + else if (Indices::waterEnabled) + //single (water) phase + dofFluidState.setPressure(phaseIdx, pressure); + } + + if (FluidSystem::enableDissolvedGas()) + dofFluidState.setRs(rsData[dofIdx]); + else if (Indices::gasEnabled && Indices::oilEnabled) + dofFluidState.setRs(0.0); + + if (FluidSystem::enableVaporizedOil()) + dofFluidState.setRv(rvData[dofIdx]); + else if (Indices::gasEnabled && Indices::oilEnabled) + dofFluidState.setRv(0.0); + + if (FluidSystem::enableVaporizedWater()) + dofFluidState.setRvw(rvwData[dofIdx]); + + ////// + // set invB_ + ////// + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + if (!FluidSystem::phaseIsActive(phaseIdx)) + continue; + + const auto& b = FluidSystem::inverseFormationVolumeFactor(dofFluidState, phaseIdx, pvtRegionIndex(dofIdx)); + dofFluidState.setInvB(phaseIdx, b); + + const auto& rho = FluidSystem::density(dofFluidState, phaseIdx, pvtRegionIndex(dofIdx)); + dofFluidState.setDensity(phaseIdx, rho); + + } + } + } + + // update the hysteresis parameters of the material laws for the whole grid + bool updateHysteresis_() + { + if (!materialLawManager_->enableHysteresis()) + return false; + + // we need to update the hysteresis data for _all_ elements (i.e., not just the + // interior ones) to avoid desynchronization of the processes in the parallel case! + const auto& simulator = this->simulator(); + ElementContext elemCtx(simulator); + const auto& vanguard = simulator.vanguard(); + auto elemIt = vanguard.gridView().template begin(); + const auto& elemEndIt = vanguard.gridView().template end(); + OPM_BEGIN_PARALLEL_TRY_CATCH(); + for (; elemIt != elemEndIt; ++elemIt) { + const Element& elem = *elemIt; + + elemCtx.updatePrimaryStencil(elem); + elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); + + unsigned compressedDofIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); + const auto& intQuants = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0); + materialLawManager_->updateHysteresis(intQuants.fluidState(), compressedDofIdx); + } + OPM_END_PARALLEL_TRY_CATCH("EclProblemTPFA::updateHyteresis_(): ", vanguard.grid().comm()); + return true; + } + + void updateMaxPolymerAdsorption_() + { + // we need to update the max polymer adsoption data for all elements + const auto& simulator = this->simulator(); + ElementContext elemCtx(simulator); + const auto& vanguard = simulator.vanguard(); + auto elemIt = vanguard.gridView().template begin(); + const auto& elemEndIt = vanguard.gridView().template end(); + OPM_BEGIN_PARALLEL_TRY_CATCH(); + for (; elemIt != elemEndIt; ++elemIt) { + const Element& elem = *elemIt; + + elemCtx.updatePrimaryStencil(elem); + elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); + + unsigned compressedDofIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); + const auto& intQuants = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0); + + this->maxPolymerAdsorption_[compressedDofIdx] = std::max(this->maxPolymerAdsorption_[compressedDofIdx], + scalarValue(intQuants.polymerAdsorption())); + } + OPM_END_PARALLEL_TRY_CATCH("EclProblemTPFA::updateMaxPolymerAdsorption_(): ", vanguard.grid().comm()); + } + + struct PffDofData_ + { + ConditionalStorage thermalHalfTransIn; + ConditionalStorage thermalHalfTransOut; + ConditionalStorage diffusivity; + Scalar transmissibility; + }; + + // update the prefetch friendly data object + void updatePffDofData_() + { + const auto& distFn = + [this](PffDofData_& dofData, + const Stencil& stencil, + unsigned localDofIdx) + -> void + { + const auto& elementMapper = this->model().elementMapper(); + + unsigned globalElemIdx = elementMapper.index(stencil.entity(localDofIdx)); + if (localDofIdx != 0) { + unsigned globalCenterElemIdx = elementMapper.index(stencil.entity(/*dofIdx=*/0)); + dofData.transmissibility = transmissibilities_.transmissibility(globalCenterElemIdx, globalElemIdx); + + if constexpr (enableEnergy) { + *dofData.thermalHalfTransIn = transmissibilities_.thermalHalfTrans(globalCenterElemIdx, globalElemIdx); + *dofData.thermalHalfTransOut = transmissibilities_.thermalHalfTrans(globalElemIdx, globalCenterElemIdx); + } + if constexpr (enableDiffusion) + *dofData.diffusivity = transmissibilities_.diffusivity(globalCenterElemIdx, globalElemIdx); + } + }; + + pffDofData_.update(distFn); + } + + void readBoundaryConditions_() + { + nonTrivialBoundaryConditions_ = false; + const auto& simulator = this->simulator(); + const auto& vanguard = simulator.vanguard(); + const auto& bcconfig = vanguard.eclState().getSimulationConfig().bcconfig(); + if (bcconfig.size() > 0) { + nonTrivialBoundaryConditions_ = true; + + size_t numCartDof = vanguard.cartesianSize(); + unsigned numElems = vanguard.gridView().size(/*codim=*/0); + std::vector cartesianToCompressedElemIdx(numCartDof, -1); + + for (unsigned elemIdx = 0; elemIdx < numElems; ++elemIdx) + cartesianToCompressedElemIdx[vanguard.cartesianIndex(elemIdx)] = elemIdx; + + massratebcXMinus_.resize(numElems, 0.0); + massratebcX_.resize(numElems, 0.0); + massratebcYMinus_.resize(numElems, 0.0); + massratebcY_.resize(numElems, 0.0); + massratebcZMinus_.resize(numElems, 0.0); + massratebcZ_.resize(numElems, 0.0); + freebcX_.resize(numElems, false); + freebcXMinus_.resize(numElems, false); + freebcY_.resize(numElems, false); + freebcYMinus_.resize(numElems, false); + freebcZ_.resize(numElems, false); + freebcZMinus_.resize(numElems, false); + + for (const auto& bcface : bcconfig) { + const auto& type = bcface.bctype; + if (type == BCType::RATE) { + int compIdx = 0; // default initialize to avoid -Wmaybe-uninitialized warning + + switch (bcface.component) { + case BCComponent::OIL: + compIdx = Indices::canonicalToActiveComponentIndex(oilCompIdx); + break; + case BCComponent::GAS: + compIdx = Indices::canonicalToActiveComponentIndex(gasCompIdx); + break; + case BCComponent::WATER: + compIdx = Indices::canonicalToActiveComponentIndex(waterCompIdx); + break; + case BCComponent::SOLVENT: + if constexpr (!enableSolvent) + throw std::logic_error("solvent is disabled and you're trying to add solvent to BC"); + + compIdx = Indices::solventSaturationIdx; + break; + case BCComponent::POLYMER: + if constexpr (!enablePolymer) + throw std::logic_error("polymer is disabled and you're trying to add polymer to BC"); + + compIdx = Indices::polymerConcentrationIdx; + break; + case BCComponent::NONE: + throw std::logic_error("you need to specify the component when RATE type is set in BC"); + break; + } + + std::vector* data = nullptr; + switch (bcface.dir) { + case FaceDir::XMinus: + data = &massratebcXMinus_; + break; + case FaceDir::XPlus: + data = &massratebcX_; + break; + case FaceDir::YMinus: + data = &massratebcYMinus_; + break; + case FaceDir::YPlus: + data = &massratebcY_; + break; + case FaceDir::ZMinus: + data = &massratebcZMinus_; + break; + case FaceDir::ZPlus: + data = &massratebcZ_; + break; + } + + const Evaluation rate = bcface.rate; + for (int i = bcface.i1; i <= bcface.i2; ++i) { + for (int j = bcface.j1; j <= bcface.j2; ++j) { + for (int k = bcface.k1; k <= bcface.k2; ++k) { + std::array tmp = {i,j,k}; + auto elemIdx = cartesianToCompressedElemIdx[vanguard.cartesianIndex(tmp)]; + if (elemIdx >= 0) + (*data)[elemIdx][compIdx] = rate; + } + } + } + } else if (type == BCType::FREE) { + std::vector* data = nullptr; + switch (bcface.dir) { + case FaceDir::XMinus: + data = &freebcXMinus_; + break; + case FaceDir::XPlus: + data = &freebcX_; + break; + case FaceDir::YMinus: + data = &freebcYMinus_; + break; + case FaceDir::YPlus: + data = &freebcY_; + break; + case FaceDir::ZMinus: + data = &freebcZMinus_; + break; + case FaceDir::ZPlus: + data = &freebcZ_; + break; + } + + for (int i = bcface.i1; i <= bcface.i2; ++i) { + for (int j = bcface.j1; j <= bcface.j2; ++j) { + for (int k = bcface.k1; k <= bcface.k2; ++k) { + std::array tmp = {i,j,k}; + auto elemIdx = cartesianToCompressedElemIdx[vanguard.cartesianIndex(tmp)]; + if (elemIdx >= 0) + (*data)[elemIdx] = true; + } + } + } + + // TODO: either the real initial solution needs to be computed or read from the restart file + const auto& eclState = simulator.vanguard().eclState(); + const auto& initconfig = eclState.getInitConfig(); + if (initconfig.restartRequested()) { + throw std::logic_error("restart is not compatible with using free boundary conditions"); + } + } else { + throw std::logic_error("invalid type for BC. Use FREE or RATE"); + } + } + } + } + + // this method applies the runtime constraints specified via the deck and/or command + // line parameters for the size of the next time step. + Scalar limitNextTimeStepSize_(Scalar dtNext) const + { + if constexpr (enableExperiments) { + const auto& simulator = this->simulator(); + int episodeIdx = simulator.episodeIndex(); + + // first thing in the morning, limit the time step size to the maximum size + dtNext = std::min(dtNext, this->maxTimeStepSize_); + + Scalar remainingEpisodeTime = + simulator.episodeStartTime() + simulator.episodeLength() + - (simulator.startTime() + simulator.time()); + assert(remainingEpisodeTime >= 0.0); + + // if we would have a small amount of time left over in the current episode, make + // two equal time steps instead of a big and a small one + if (remainingEpisodeTime/2.0 < dtNext && dtNext < remainingEpisodeTime*(1.0 - 1e-5)) + // note: limiting to the maximum time step size here is probably not strictly + // necessary, but it should not hurt and is more fool-proof + dtNext = std::min(this->maxTimeStepSize_, remainingEpisodeTime/2.0); + + if (simulator.episodeStarts()) { + // if a well event occurred, respect the limit for the maximum time step after + // that, too + int reportStepIdx = std::max(episodeIdx, 0); + const auto& events = simulator.vanguard().schedule()[reportStepIdx].events(); + bool wellEventOccured = + events.hasEvent(ScheduleEvents::NEW_WELL) + || events.hasEvent(ScheduleEvents::PRODUCTION_UPDATE) + || events.hasEvent(ScheduleEvents::INJECTION_UPDATE) + || events.hasEvent(ScheduleEvents::WELL_STATUS_CHANGE); + if (episodeIdx >= 0 && wellEventOccured && this->maxTimeStepAfterWellEvent_ > 0) + dtNext = std::min(dtNext, this->maxTimeStepAfterWellEvent_); + } + } + + return dtNext; + } + + typename Vanguard::TransmissibilityType transmissibilities_; + + std::shared_ptr materialLawManager_; + std::shared_ptr thermalLawManager_; + + EclThresholdPressure thresholdPressures_; + + std::vector initialFluidStates_; + + constexpr static Scalar freeGasMinSaturation_ = 1e-7; + + bool enableDriftCompensation_; + GlobalEqVector drift_; + + EclWellModel wellModel_; + bool enableAquifers_; + EclAquiferModel aquiferModel_; + + bool enableEclOutput_; + std::unique_ptr eclWriter_; + + PffGridVector pffDofData_; + TracerModel tracerModel_; + + std::vector freebcX_; + std::vector freebcXMinus_; + std::vector freebcY_; + std::vector freebcYMinus_; + std::vector freebcZ_; + std::vector freebcZMinus_; + + bool nonTrivialBoundaryConditions_; + std::vector massratebcX_; + std::vector massratebcXMinus_; + std::vector massratebcY_; + std::vector massratebcYMinus_; + std::vector massratebcZ_; + std::vector massratebcZMinus_; +}; + +} // namespace Opm + +#endif diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp index bca00970f..b24d62f79 100644 --- a/flow/flow_blackoil_tpfa.cpp +++ b/flow/flow_blackoil_tpfa.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace Opm { namespace Properties { namespace TTag { @@ -32,10 +33,19 @@ namespace Opm { using InheritsFrom = std::tuple; }; } - + } +} + +namespace Opm { + namespace Properties { + template + struct Problem { + using type = EclProblemTPFA; + }; } } + namespace Opm { namespace Properties { template From 07a7070dc0329de45c8b3dfa9107cf3649bfd346 Mon Sep 17 00:00:00 2001 From: hnil Date: Fri, 17 Jun 2022 12:02:34 +0200 Subject: [PATCH 11/49] revert eclproblem.hh back to original --- ebos/eclproblem.hh | 100 +-------------------------------------------- 1 file changed, 1 insertion(+), 99 deletions(-) diff --git a/ebos/eclproblem.hh b/ebos/eclproblem.hh index d6b01599d..84e27c445 100644 --- a/ebos/eclproblem.hh +++ b/ebos/eclproblem.hh @@ -1429,11 +1429,6 @@ public: /*! * \copydoc EclTransmissiblity::transmissibility */ - Scalar transmissibility(unsigned globalCenterElemIdx,unsigned globalElemIdx) const - { - return transmissibilities_.transmissibility(globalCenterElemIdx, globalElemIdx); - } - template Scalar transmissibility(const Context& context, [[maybe_unused]] unsigned fromDofLocalIdx, @@ -1542,13 +1537,6 @@ public: return this->referencePorosity_[timeIdx][globalSpaceIdx]; } - Scalar porosity(unsigned globalSpaceIdx, unsigned timeIdx) const - { - //unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); - return this->referencePorosity_[timeIdx][globalSpaceIdx]; - } - - /*! * \brief Returns the depth of an degree of freedom [m] * @@ -1573,11 +1561,6 @@ public: return this->simulator().vanguard().cellCenterDepth(globalSpaceIdx); } - Scalar dofCenterDepth(unsigned globalSpaceIdx, unsigned timeIdx) const - { - return this->simulator().vanguard().cellCenterDepth(globalSpaceIdx); - } - /*! * \copydoc BlackoilProblem::rockCompressibility @@ -1597,20 +1580,6 @@ public: return this->rockParams_[tableIdx].compressibility; } - - Scalar rockCompressibility(unsigned globalSpaceIdx, unsigned timeIdx) const - { - // if (this->rockParams_.empty()) - // return 0.0; - - // unsigned tableIdx = 0; - // if (!this->rockTableIdx_.empty()) { - // tableIdx = this->rockTableIdx_[globalSpaceIdx]; - // } - unsigned tableIdx = 0; - return this->rockParams_[tableIdx].compressibility; - } - /*! * \copydoc BlackoilProblem::rockReferencePressure */ @@ -1625,21 +1594,10 @@ public: unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); tableIdx = this->rockTableIdx_[globalSpaceIdx]; } + return this->rockParams_[tableIdx].referencePressure; } - Scalar rockReferencePressure(unsigned globalSpaceIdx, unsigned timeIdx) const - { - // if (this->rockParams_.empty()) - // return 1e5; - - // unsigned tableIdx = 0; - // if (!this->rockTableIdx_.empty()) { - // tableIdx = this->rockTableIdx_[globalSpaceIdx]; - // } - unsigned tableIdx = 0;//this->rockTableIdx_[globalSpaceIdx]; - return this->rockParams_[tableIdx].referencePressure; - } /*! * \copydoc FvBaseMultiPhaseProblem::materialLawParams */ @@ -1867,7 +1825,6 @@ public: */ Scalar maxGasDissolutionFactor(unsigned timeIdx, unsigned globalDofIdx) const { - int pvtRegionIdx = this->pvtRegionIndex(globalDofIdx); int episodeIdx = this->episodeIndex(); if (!this->drsdtActive_(episodeIdx) || this->maxDRs_[pvtRegionIdx] < 0.0) @@ -1986,61 +1943,6 @@ public: aquiferModel_.initialSolutionApplied(); } - template - void source(RateVector& rate, - unsigned globalSpaceIdx, - unsigned timeIdx) const - { - rate = 0.0; - - wellModel_.computeTotalRatesForDof(rate, globalSpaceIdx, timeIdx); - - // convert the source term from the total mass rate of the - // cell to the one per unit of volume as used by the model. - - for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) { - rate[eqIdx] /= this->model().dofTotalVolume(globalSpaceIdx); - - Valgrind::CheckDefined(rate[eqIdx]); - assert(isfinite(rate[eqIdx])); - } - - // if (enableAquifers_) - // aquiferModel_.addToSource(rate, context, spaceIdx, timeIdx); - - // if requested, compensate systematic mass loss for cells which were "well - // behaved" in the last time step - // if (enableDriftCompensation_) { - // const auto& intQuants = context.intensiveQuantities(spaceIdx, timeIdx); - // const auto& simulator = this->simulator(); - // const auto& model = this->model(); - - // // we need a higher maxCompensation than the Newton tolerance because the - // // current time step might be shorter than the last one - // Scalar maxCompensation = 10.0*model.newtonMethod().tolerance(); - - // Scalar poro = intQuants.referencePorosity(); - // Scalar dt = simulator.timeStepSize(); - - // EqVector dofDriftRate = drift_[globalDofIdx]; - // dofDriftRate /= dt*context.dofTotalVolume(spaceIdx, timeIdx); - - // // compute the weighted total drift rate - // Scalar totalDriftRate = 0.0; - // for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) - // totalDriftRate += - // std::abs(dofDriftRate[eqIdx])*dt*model.eqWeight(globalDofIdx, eqIdx)/poro; - - // // make sure that we do not exceed the maximum rate of drift compensation - // if (totalDriftRate > maxCompensation) - // dofDriftRate *= maxCompensation/totalDriftRate; - - // for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) - // rate[eqIdx] -= dofDriftRate[eqIdx]; - // } - } - - /*! * \copydoc FvBaseProblem::source * From d9c59efcf1ece5e9f48bd347cd39227eae3f24ed Mon Sep 17 00:00:00 2001 From: hnil Date: Sun, 19 Jun 2022 19:50:38 +0200 Subject: [PATCH 12/49] moved transCompFactor to intensive quantities --- ebos/eclfluxmoduletpfa.hh | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ebos/eclfluxmoduletpfa.hh b/ebos/eclfluxmoduletpfa.hh index 9fbf40059..3207d2cc8 100644 --- a/ebos/eclfluxmoduletpfa.hh +++ b/ebos/eclfluxmoduletpfa.hh @@ -498,8 +498,9 @@ public: // TODO: should the rock compaction transmissibility multiplier be upstreamed // or averaged? all fluids should see the same compaction?! //const auto& globalIndex = stencil.globalSpaceIndex(upstreamIdx); - const Evaluation& transMult = - problem.template rockCompTransMultiplier(up, globalIndex); + const Evaluation& transMult = up.rockCompTransMultiplier(); + //const Evaluation& transMult = + // problem.template rockCompTransMultiplier(up, globalIndex); if (upIdx[phaseIdx] == interiorDofIdx) volumeFlux[phaseIdx] = @@ -593,8 +594,9 @@ protected: // or averaged? all fluids should see the same compaction?! //const auto& globalIndex = stencil.globalSpaceIndex(upstreamIdx); //NB as long as this is upwinded it could be an intensive quantity - const Evaluation& transMult = - problem.template rockCompTransMultiplier(up, globalIndex); + const Evaluation& transMult = up.rockCompTransMultiplier(); + // const Evaluation& transMult = + // problem.template rockCompTransMultiplier(up, globalIndex); if (upIdx_[phaseIdx] == interiorDofIdx_) volumeFlux_[phaseIdx] = @@ -690,7 +692,9 @@ protected: const auto& up = elemCtx.intensiveQuantities(upstreamIdx, timeIdx); // deal with water induced rock compaction - transModified *= problem.template rockCompTransMultiplier(up, stencil.globalSpaceIndex(upstreamIdx)); + const double transMult = Toolbox::value(up.rockCompTransMultiplier()); + transModified *= transMult; + //problem.template rockCompTransMultiplier(up, stencil.globalSpaceIndex(upstreamIdx)); volumeFlux_[phaseIdx] = pressureDifference_[phaseIdx]*up.mobility(phaseIdx)*(-transModified/faceArea); From c5d547efffcd4239a862090e9909c75881ac5103 Mon Sep 17 00:00:00 2001 From: hnil Date: Mon, 20 Jun 2022 12:41:58 +0200 Subject: [PATCH 13/49] modification for global assembly --- ebos/eclproblemtpfa.hh | 47 ++++++++++++++++++++----------------- flow/flow_blackoil_tpfa.cpp | 3 +++ 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/ebos/eclproblemtpfa.hh b/ebos/eclproblemtpfa.hh index 8c49b7979..1e6a61da3 100644 --- a/ebos/eclproblemtpfa.hh +++ b/ebos/eclproblemtpfa.hh @@ -1930,7 +1930,6 @@ public: aquiferModel_.initialSolutionApplied(); } - template void source(RateVector& rate, unsigned globalSpaceIdx, unsigned timeIdx) const @@ -2104,6 +2103,9 @@ public: return limitNextTimeStepSize_(newtonMethod.suggestTimeStepSize(simulator.timeStepSize())); } + Scalar volume(unsigned dofIdx,unsigned /*timidx*/) const{ + return this->simulator().model().dofTotalVolume(dofIdx); + } /*! * \brief Calculate the porosity multiplier due to water induced rock compaction. * @@ -2152,34 +2154,35 @@ public: template LhsEval rockCompTransMultiplier(const IntensiveQuantities& intQuants, unsigned elementIdx) const { - if (this->rockCompTransMult_.empty() && this->rockCompTransMultWc_.empty()) - return 1.0; + return 1.0; + // if (this->rockCompTransMult_.empty() && this->rockCompTransMultWc_.empty()) + // return 1.0; - unsigned tableIdx = 0; - if (!this->rockTableIdx_.empty()) - tableIdx = this->rockTableIdx_[elementIdx]; + // unsigned tableIdx = 0; + // if (!this->rockTableIdx_.empty()) + // tableIdx = this->rockTableIdx_[elementIdx]; - const auto& fs = intQuants.fluidState(); - LhsEval effectiveOilPressure = decay(fs.pressure(oilPhaseIdx)); + // const auto& fs = intQuants.fluidState(); + // LhsEval effectiveOilPressure = decay(fs.pressure(oilPhaseIdx)); - if (!this->minOilPressure_.empty()) - // The pore space change is irreversible - effectiveOilPressure = - min(decay(fs.pressure(oilPhaseIdx)), - this->minOilPressure_[elementIdx]); + // if (!this->minOilPressure_.empty()) + // // The pore space change is irreversible + // effectiveOilPressure = + // min(decay(fs.pressure(oilPhaseIdx)), + // this->minOilPressure_[elementIdx]); - if (!this->overburdenPressure_.empty()) - effectiveOilPressure -= this->overburdenPressure_[elementIdx]; + // if (!this->overburdenPressure_.empty()) + // effectiveOilPressure -= this->overburdenPressure_[elementIdx]; - if (!this->rockCompTransMult_.empty()) - return this->rockCompTransMult_[tableIdx].eval(effectiveOilPressure, /*extrapolation=*/true); + // if (!this->rockCompTransMult_.empty()) + // return this->rockCompTransMult_[tableIdx].eval(effectiveOilPressure, /*extrapolation=*/true); - // water compaction - assert(!this->rockCompTransMultWc_.empty()); - LhsEval SwMax = max(decay(fs.saturation(waterPhaseIdx)), this->maxWaterSaturation_[elementIdx]); - LhsEval SwDeltaMax = SwMax - initialFluidStates_[elementIdx].saturation(waterPhaseIdx); + // // water compaction + // assert(!this->rockCompTransMultWc_.empty()); + // LhsEval SwMax = max(decay(fs.saturation(waterPhaseIdx)), this->maxWaterSaturation_[elementIdx]); + // LhsEval SwDeltaMax = SwMax - initialFluidStates_[elementIdx].saturation(waterPhaseIdx); - return this->rockCompTransMultWc_[tableIdx].eval(effectiveOilPressure, SwDeltaMax, /*extrapolation=*/true); + // return this->rockCompTransMultWc_[tableIdx].eval(effectiveOilPressure, SwDeltaMax, /*extrapolation=*/true); } private: diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp index b24d62f79..443e8ee2a 100644 --- a/flow/flow_blackoil_tpfa.cpp +++ b/flow/flow_blackoil_tpfa.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include namespace Opm { @@ -48,6 +49,8 @@ namespace Opm { namespace Opm { namespace Properties { + template + struct Linearizer { using type = LinearizerTPFA; }; template struct LocalLinearizerSplice { using type = TTag::AutoDiffLocalLinearizerTPFA; From 5a397246067cb98ed01dec42a93d7a187bed7def Mon Sep 17 00:00:00 2001 From: hnil Date: Mon, 20 Jun 2022 14:44:15 +0200 Subject: [PATCH 14/49] added functions for direct setting of well rates contribution to reservoir --- opm/simulators/wells/BlackoilWellModel.hpp | 25 +++++++++++++++++++++- opm/simulators/wells/WellInterface.hpp | 1 + 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/opm/simulators/wells/BlackoilWellModel.hpp b/opm/simulators/wells/BlackoilWellModel.hpp index ed443600e..6c5dd0b52 100644 --- a/opm/simulators/wells/BlackoilWellModel.hpp +++ b/opm/simulators/wells/BlackoilWellModel.hpp @@ -128,7 +128,7 @@ namespace Opm { typedef Dune::FieldVector VectorBlockType; typedef Dune::BlockVector BVector; - // typedef Dune::FieldMatrix MatrixBlockType; + typedef Opm::MatrixBlock MatrixBlockType; typedef BlackOilPolymerModule PolymerModule; typedef BlackOilMICPModule MICPModule; @@ -273,6 +273,29 @@ namespace Opm { void addWellContributions(SparseMatrixAdapter& jacobian) const; + void addReseroirSourceTerms(GlobalEqVector& residual, + SparseMatrixAdapter& jacobian) const + { + for ( const auto& well: well_container_ ) { + if(!well->isOperableAndSolvable() && !well->wellIsStopped()) + return; + + const auto& cells = well->cells(); + const auto& rates = well->connectionRates(); + for (unsigned perfIdx = 0; perfIdx < rates.size(); ++perfIdx) { + unsigned cellIdx = cells[perfIdx]; + auto rate = rates[perfIdx]; + Scalar volume = ebosSimulator_.problem().volume(cellIdx,0); + rate *= -1.0; + VectorBlockType res(0.0); + MatrixBlockType bMat(0.0); + ebosSimulator_.model().linearizer().setResAndJacobi(res,bMat,rate); + residual[cellIdx] += res; + jacobian.addToBlock(cellIdx,cellIdx,bMat); + } + } + } + // called at the beginning of a report step void beginReportStep(const int time_step); diff --git a/opm/simulators/wells/WellInterface.hpp b/opm/simulators/wells/WellInterface.hpp index 0d2631263..0ce79436b 100644 --- a/opm/simulators/wells/WellInterface.hpp +++ b/opm/simulators/wells/WellInterface.hpp @@ -288,6 +288,7 @@ public: const GroupState& group_state, DeferredLogger& deferred_logger); + const std::vector& connectionRates() const {return connectionRates_;} protected: // simulation parameters From 0c03e72782cb616087a514b9e9c446c4f07e1026 Mon Sep 17 00:00:00 2001 From: hnil Date: Mon, 20 Jun 2022 18:43:43 +0200 Subject: [PATCH 15/49] fixing compilation warning --- ebos/eclfluxmoduletpfa.hh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ebos/eclfluxmoduletpfa.hh b/ebos/eclfluxmoduletpfa.hh index 3207d2cc8..484dd7d37 100644 --- a/ebos/eclfluxmoduletpfa.hh +++ b/ebos/eclfluxmoduletpfa.hh @@ -582,13 +582,13 @@ protected: continue; } IntensiveQuantities up; - unsigned globalIndex; + //unsigned globalIndex; if(upIdx_[phaseIdx] == interiorDofIdx_){ up = intQuantsIn; - globalIndex = globalIndexIn; + //globalIndex = globalIndexIn; }else{ up = intQuantsEx; - globalIndex = globalIndexEx; + //globalIndex = globalIndexEx; } // TODO: should the rock compaction transmissibility multiplier be upstreamed // or averaged? all fluids should see the same compaction?! From 6d3b0a7c1fd56741e963e121f7659468195b480c Mon Sep 17 00:00:00 2001 From: hnil Date: Tue, 21 Jun 2022 09:15:39 +0200 Subject: [PATCH 16/49] tried to make openmp work --- ebos/eclfluxmoduletpfa.hh | 6 +- ebos/eclproblemtpfa.hh | 69 +++++++++++----------- opm/simulators/wells/BlackoilWellModel.hpp | 5 +- 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/ebos/eclfluxmoduletpfa.hh b/ebos/eclfluxmoduletpfa.hh index 484dd7d37..69e8e43eb 100644 --- a/ebos/eclfluxmoduletpfa.hh +++ b/ebos/eclfluxmoduletpfa.hh @@ -487,13 +487,13 @@ public: continue; } IntensiveQuantities up; - unsigned globalIndex; + //unsigned globalIndex; if(upIdx[phaseIdx] == interiorDofIdx){ up = intQuantsIn; - globalIndex = globalIndexIn; + // globalIndex = globalIndexIn; }else{ up = intQuantsEx; - globalIndex = globalIndexEx; + //globalIndex = globalIndexEx; } // TODO: should the rock compaction transmissibility multiplier be upstreamed // or averaged? all fluids should see the same compaction?! diff --git a/ebos/eclproblemtpfa.hh b/ebos/eclproblemtpfa.hh index 1e6a61da3..8c18b31c1 100644 --- a/ebos/eclproblemtpfa.hh +++ b/ebos/eclproblemtpfa.hh @@ -1544,14 +1544,14 @@ public: Scalar rockCompressibility(unsigned globalSpaceIdx, unsigned timeIdx) const { - // if (this->rockParams_.empty()) - // return 0.0; + if (this->rockParams_.empty()) + return 0.0; - // unsigned tableIdx = 0; - // if (!this->rockTableIdx_.empty()) { - // tableIdx = this->rockTableIdx_[globalSpaceIdx]; - // } unsigned tableIdx = 0; + if (!this->rockTableIdx_.empty()) { + tableIdx = this->rockTableIdx_[globalSpaceIdx]; + } + //unsigned tableIdx = 0;// faster but not genneral return this->rockParams_[tableIdx].compressibility; } @@ -1574,14 +1574,14 @@ public: Scalar rockReferencePressure(unsigned globalSpaceIdx, unsigned timeIdx) const { - // if (this->rockParams_.empty()) - // return 1e5; + if (this->rockParams_.empty()) + return 1e5; - // unsigned tableIdx = 0; - // if (!this->rockTableIdx_.empty()) { - // tableIdx = this->rockTableIdx_[globalSpaceIdx]; - // } - unsigned tableIdx = 0;//this->rockTableIdx_[globalSpaceIdx]; + unsigned tableIdx = 0; + if (!this->rockTableIdx_.empty()) { + tableIdx = this->rockTableIdx_[globalSpaceIdx]; + } + //unsigned tableIdx = 0;//faster but not genneral this->rockTableIdx_[globalSpaceIdx]; return this->rockParams_[tableIdx].referencePressure; } /*! @@ -2154,35 +2154,34 @@ public: template LhsEval rockCompTransMultiplier(const IntensiveQuantities& intQuants, unsigned elementIdx) const { - return 1.0; - // if (this->rockCompTransMult_.empty() && this->rockCompTransMultWc_.empty()) - // return 1.0; + if (this->rockCompTransMult_.empty() && this->rockCompTransMultWc_.empty()) + return 1.0; - // unsigned tableIdx = 0; - // if (!this->rockTableIdx_.empty()) - // tableIdx = this->rockTableIdx_[elementIdx]; + unsigned tableIdx = 0; + if (!this->rockTableIdx_.empty()) + tableIdx = this->rockTableIdx_[elementIdx]; - // const auto& fs = intQuants.fluidState(); - // LhsEval effectiveOilPressure = decay(fs.pressure(oilPhaseIdx)); + const auto& fs = intQuants.fluidState(); + LhsEval effectiveOilPressure = decay(fs.pressure(oilPhaseIdx)); - // if (!this->minOilPressure_.empty()) - // // The pore space change is irreversible - // effectiveOilPressure = - // min(decay(fs.pressure(oilPhaseIdx)), - // this->minOilPressure_[elementIdx]); + if (!this->minOilPressure_.empty()) + // The pore space change is irreversible + effectiveOilPressure = + min(decay(fs.pressure(oilPhaseIdx)), + this->minOilPressure_[elementIdx]); - // if (!this->overburdenPressure_.empty()) - // effectiveOilPressure -= this->overburdenPressure_[elementIdx]; + if (!this->overburdenPressure_.empty()) + effectiveOilPressure -= this->overburdenPressure_[elementIdx]; - // if (!this->rockCompTransMult_.empty()) - // return this->rockCompTransMult_[tableIdx].eval(effectiveOilPressure, /*extrapolation=*/true); + if (!this->rockCompTransMult_.empty()) + return this->rockCompTransMult_[tableIdx].eval(effectiveOilPressure, /*extrapolation=*/true); - // // water compaction - // assert(!this->rockCompTransMultWc_.empty()); - // LhsEval SwMax = max(decay(fs.saturation(waterPhaseIdx)), this->maxWaterSaturation_[elementIdx]); - // LhsEval SwDeltaMax = SwMax - initialFluidStates_[elementIdx].saturation(waterPhaseIdx); + // water compaction + assert(!this->rockCompTransMultWc_.empty()); + LhsEval SwMax = max(decay(fs.saturation(waterPhaseIdx)), this->maxWaterSaturation_[elementIdx]); + LhsEval SwDeltaMax = SwMax - initialFluidStates_[elementIdx].saturation(waterPhaseIdx); - // return this->rockCompTransMultWc_[tableIdx].eval(effectiveOilPressure, SwDeltaMax, /*extrapolation=*/true); + return this->rockCompTransMultWc_[tableIdx].eval(effectiveOilPressure, SwDeltaMax, /*extrapolation=*/true); } private: diff --git a/opm/simulators/wells/BlackoilWellModel.hpp b/opm/simulators/wells/BlackoilWellModel.hpp index 6c5dd0b52..41c4356b5 100644 --- a/opm/simulators/wells/BlackoilWellModel.hpp +++ b/opm/simulators/wells/BlackoilWellModel.hpp @@ -276,9 +276,12 @@ namespace Opm { void addReseroirSourceTerms(GlobalEqVector& residual, SparseMatrixAdapter& jacobian) const { +#ifdef _OPENMP +#pragma omp parallel +#endif for ( const auto& well: well_container_ ) { if(!well->isOperableAndSolvable() && !well->wellIsStopped()) - return; + continue; const auto& cells = well->cells(); const auto& rates = well->connectionRates(); From 4f6755025caba31fc167c80d6d1f2b5354ee33d1 Mon Sep 17 00:00:00 2001 From: hnil Date: Wed, 22 Jun 2022 14:32:26 +0200 Subject: [PATCH 17/49] -prototype on trueimpes in new system -probably better threading --- .../linalg/getQuasiImpesWeights.hpp | 47 ++++++++++++++++++- opm/simulators/wells/BlackoilWellModel.hpp | 8 ++-- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/opm/simulators/linalg/getQuasiImpesWeights.hpp b/opm/simulators/linalg/getQuasiImpesWeights.hpp index 3797492e8..3a48cf79b 100644 --- a/opm/simulators/linalg/getQuasiImpesWeights.hpp +++ b/opm/simulators/linalg/getQuasiImpesWeights.hpp @@ -108,8 +108,8 @@ namespace Amg elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); Dune::FieldVector storage; model.localLinearizer(threadId).localResidual().computeStorage(storage,elemCtx,/*spaceIdx=*/0, /*timeIdx=*/0); - auto extrusionFactor = elemCtx.intensiveQuantities(0, /*timeIdx=*/0).extrusionFactor(); - auto scvVolume = elemCtx.stencil(/*timeIdx=*/0).subControlVolume(0).volume() * extrusionFactor; + //auto extrusionFactor = elemCtx.intensiveQuantities(0, /*timeIdx=*/0).extrusionFactor(); + auto scvVolume = elemCtx.stencil(/*timeIdx=*/0).subControlVolume(0).volume();// * extrusionFactor; auto storage_scale = scvVolume / elemCtx.simulator().timeStepSize(); MatrixBlockType block; double pressure_scale = 50e5; @@ -130,6 +130,49 @@ namespace Amg } OPM_END_PARALLEL_TRY_CATCH("getTrueImpesWeights() failed: ", elemCtx.simulator().vanguard().grid().comm()); } + + template + void getTrueImpesWeights(int pressureVarIndex, Vector& weights, const Model& model) + { + using VectorBlockType = typename Vector::block_type; + using Matrix = typename std::decay_t; + using MatrixBlockType = typename Matrix::MatrixBlock; + constexpr int numEq = VectorBlockType::size(); + unsigned numCells = model.numTotalDof(); + VectorBlockType rhs(0.0); + rhs[pressureVarIndex] = 1.0; + //NB !!OPM_BEGIN_PARALLEL_TRY_CATCH(); +#ifdef _OPENMP +#pragma omp parallel for +#endif + for(unsigned globI = 0; globI < numCells; globI++){ + Dune::FieldVector storage; + const auto* intQuantsInP = model.cachedIntensiveQuantities(globI, /*timeIdx*/0); + assert(intQuantsInP); + const auto& intQuantsIn = *intQuantsInP; + //NB !!!!! LocalResidual::computeStorage(storage,intQuantsIn, 0); + double scvVolume = model.dofTotalVolume(globI); + double dt = 3600*24; + auto storage_scale = scvVolume / dt; + MatrixBlockType block; + double pressure_scale = 50e5; + for (int ii = 0; ii < numEq; ++ii) { + for (int jj = 0; jj < numEq; ++jj) { + block[ii][jj] = storage[ii].derivative(jj)/storage_scale; + if (jj == pressureVarIndex) { + block[ii][jj] *= pressure_scale; + } + } + } + VectorBlockType bweights; + MatrixBlockType block_transpose = Details::transposeDenseMatrix(block); + block_transpose.solve(bweights, rhs); + bweights /= 1000.0; // given normal densities this scales weights to about 1. + weights[globI] = bweights; + } + //NB!! OPM_END_PARALLEL_TRY_CATCH("getTrueImpesWeights() failed: ", elemCtx.simulator().vanguard().grid().comm()); + } + } // namespace Amg } // namespace Opm diff --git a/opm/simulators/wells/BlackoilWellModel.hpp b/opm/simulators/wells/BlackoilWellModel.hpp index 41c4356b5..4e96454bb 100644 --- a/opm/simulators/wells/BlackoilWellModel.hpp +++ b/opm/simulators/wells/BlackoilWellModel.hpp @@ -276,10 +276,12 @@ namespace Opm { void addReseroirSourceTerms(GlobalEqVector& residual, SparseMatrixAdapter& jacobian) const { + // NB this loop may write to same element if a cell has more than one perforation #ifdef _OPENMP -#pragma omp parallel +#pragma omp parallel for #endif - for ( const auto& well: well_container_ ) { + for(size_t i = 0; i < well_container_.size(); i++){// to be sure open understand + const auto& well = well_container_[i]; if(!well->isOperableAndSolvable() && !well->wellIsStopped()) continue; @@ -288,7 +290,7 @@ namespace Opm { for (unsigned perfIdx = 0; perfIdx < rates.size(); ++perfIdx) { unsigned cellIdx = cells[perfIdx]; auto rate = rates[perfIdx]; - Scalar volume = ebosSimulator_.problem().volume(cellIdx,0); + // Scalar volume = ebosSimulator_.problem().volume(cellIdx,0); rate *= -1.0; VectorBlockType res(0.0); MatrixBlockType bMat(0.0); From f7512798410a4863c9c506339b4d6f3776795e36 Mon Sep 17 00:00:00 2001 From: hnil Date: Wed, 22 Jun 2022 18:27:17 +0200 Subject: [PATCH 18/49] fixed true impes for new code --- opm/simulators/linalg/ISTLSolverEbos.hpp | 16 +++++++++++++--- opm/simulators/linalg/getQuasiImpesWeights.hpp | 10 +++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/opm/simulators/linalg/ISTLSolverEbos.hpp b/opm/simulators/linalg/ISTLSolverEbos.hpp index 07248cce9..6f225a79a 100644 --- a/opm/simulators/linalg/ISTLSolverEbos.hpp +++ b/opm/simulators/linalg/ISTLSolverEbos.hpp @@ -92,6 +92,7 @@ namespace Opm using AbstractPreconditionerType = Dune::PreconditionerWithUpdate; using WellModelOperator = WellModelAsLinearOperator; using ElementMapper = GetPropType; + using Evaluation = GetPropType; constexpr static std::size_t pressureIndex = GetPropType::pressureSwitchIdx; #if HAVE_CUDA || HAVE_OPENCL || HAVE_FPGA || HAVE_AMGCL @@ -505,7 +506,8 @@ namespace Opm // assignment p = pressureIndex prevent compiler warning about // capturing variable with non-automatic storage duration weightsCalculator = [this, p = pressureIndex]() { - return this->getTrueImpesWeights(p); + ElementContext elemCtx(this->simulator_); + return this->getTrueImpesWeights(p, elemCtx); }; } else { OPM_THROW(std::invalid_argument, @@ -520,16 +522,24 @@ namespace Opm // Weights to make approximate pressure equations. // Calculated from the storage terms (only) of the // conservation equations, ignoring all other terms. - Vector getTrueImpesWeights(int pressureVarIndex) const + template + Vector getTrueImpesWeights(int pressureVarIndex,ElemCtx& elemCtx) const { Vector weights(rhs_->size()); - ElementContext elemCtx(simulator_); Amg::getTrueImpesWeights(pressureVarIndex, weights, simulator_.vanguard().gridView(), elemCtx, simulator_.model(), ThreadManager::threadId()); return weights; } + + Vector getTrueImpesWeights(int pressureVarIndex,SmallElementContext& /*elemCtx*/) const + { + Vector weights(rhs_->size()); + Amg::getTrueImpesWeights(pressureVarIndex, weights, simulator_.model()); + return weights; + } + /// Zero out off-diagonal blocks on rows corresponding to overlap cells /// Diagonal blocks on ovelap rows are set to diag(1.0). diff --git a/opm/simulators/linalg/getQuasiImpesWeights.hpp b/opm/simulators/linalg/getQuasiImpesWeights.hpp index 3a48cf79b..a73417730 100644 --- a/opm/simulators/linalg/getQuasiImpesWeights.hpp +++ b/opm/simulators/linalg/getQuasiImpesWeights.hpp @@ -87,7 +87,7 @@ namespace Amg return weights; } - template + template void getTrueImpesWeights(int pressureVarIndex, Vector& weights, const GridView& gridView, ElementContext& elemCtx, const Model& model, std::size_t threadId) { @@ -95,8 +95,8 @@ namespace Amg using Matrix = typename std::decay_t; using MatrixBlockType = typename Matrix::MatrixBlock; constexpr int numEq = VectorBlockType::size(); - using Evaluation = typename std::decay_t - ::block_type; +// using Evaluation = typename std::decay_t +// ::block_type; VectorBlockType rhs(0.0); rhs[pressureVarIndex] = 1.0; int index = 0; @@ -131,7 +131,7 @@ namespace Amg OPM_END_PARALLEL_TRY_CATCH("getTrueImpesWeights() failed: ", elemCtx.simulator().vanguard().grid().comm()); } - template + template void getTrueImpesWeights(int pressureVarIndex, Vector& weights, const Model& model) { using VectorBlockType = typename Vector::block_type; @@ -150,7 +150,7 @@ namespace Amg const auto* intQuantsInP = model.cachedIntensiveQuantities(globI, /*timeIdx*/0); assert(intQuantsInP); const auto& intQuantsIn = *intQuantsInP; - //NB !!!!! LocalResidual::computeStorage(storage,intQuantsIn, 0); + Model::LocalResidual::computeStorage(storage,intQuantsIn, 0); double scvVolume = model.dofTotalVolume(globI); double dt = 3600*24; auto storage_scale = scvVolume / dt; From 5d0ae333f4637e17617408f9dd18debcc10c9474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Fri, 24 Jun 2022 10:14:25 +0200 Subject: [PATCH 19/49] Fix: add template argument. --- opm/simulators/linalg/ISTLSolverEbos.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/opm/simulators/linalg/ISTLSolverEbos.hpp b/opm/simulators/linalg/ISTLSolverEbos.hpp index 6f225a79a..3aced34ba 100644 --- a/opm/simulators/linalg/ISTLSolverEbos.hpp +++ b/opm/simulators/linalg/ISTLSolverEbos.hpp @@ -526,9 +526,9 @@ namespace Opm Vector getTrueImpesWeights(int pressureVarIndex,ElemCtx& elemCtx) const { Vector weights(rhs_->size()); - Amg::getTrueImpesWeights(pressureVarIndex, weights, simulator_.vanguard().gridView(), - elemCtx, simulator_.model(), - ThreadManager::threadId()); + Amg::getTrueImpesWeights(pressureVarIndex, weights, simulator_.vanguard().gridView(), + elemCtx, simulator_.model(), + ThreadManager::threadId()); return weights; } From 6e46d332dec0f98e682757b13f2ef6564983ce66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Fri, 24 Jun 2022 11:31:31 +0200 Subject: [PATCH 20/49] Do not use the separate LinearizerTpfa class. --- flow/flow_blackoil_tpfa.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp index 443e8ee2a..e1e7f4907 100644 --- a/flow/flow_blackoil_tpfa.cpp +++ b/flow/flow_blackoil_tpfa.cpp @@ -49,8 +49,8 @@ namespace Opm { namespace Opm { namespace Properties { - template - struct Linearizer { using type = LinearizerTPFA; }; + // template + // struct Linearizer { using type = LinearizerTPFA; }; template struct LocalLinearizerSplice { using type = TTag::AutoDiffLocalLinearizerTPFA; From 743eff9cf653a2ffa00cb54d393ebe066fa353c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Fri, 24 Jun 2022 11:53:31 +0200 Subject: [PATCH 21/49] Set prop for using tpfa linearizer to true. --- flow/flow_blackoil_tpfa.cpp | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp index e1e7f4907..70005e275 100644 --- a/flow/flow_blackoil_tpfa.cpp +++ b/flow/flow_blackoil_tpfa.cpp @@ -14,7 +14,9 @@ You should have received a copy of the GNU General Public License along with OPM. If not, see . */ + #include "config.h" + #include #include #include @@ -24,9 +26,9 @@ #include #include #include -#include #include #include + namespace Opm { namespace Properties { namespace TTag { @@ -41,7 +43,7 @@ namespace Opm { namespace Properties { template struct Problem { - using type = EclProblemTPFA; + using type = EclProblemTPFA; }; } } @@ -51,6 +53,11 @@ namespace Opm { namespace Properties { // template // struct Linearizer { using type = LinearizerTPFA; }; + + // Override default: use the TPFA linearizer. + template + struct UseTpfaLinearizer { static constexpr bool value = true; }; + template struct LocalLinearizerSplice { using type = TTag::AutoDiffLocalLinearizerTPFA; @@ -75,7 +82,7 @@ namespace Opm{ typedef EclTransIntensiveQuantities FluxIntensiveQuantities; typedef EclTransExtensiveQuantitiesTPFA FluxExtensiveQuantities; typedef EclTransBaseProblem FluxBaseProblem; - + /*! * \brief Register all run-time parameters for the flux module. */ @@ -97,24 +104,6 @@ namespace Opm { } } -// namespace Opm { -// namespace Properties { -// template -// struct Linearizer { -// using type = TTag::AutoDiffLocalLinearizer; -// }; -// } -// } - - -// namespace Opm { -// namespace Properties { -// template -// struct FluxModule { -// using type = TTag::EclTransFluxMudule; -// }; -// } -// } int main(int argc, char** argv) { From 9dba7865e9328317af1ccbae3d7dd85109a00a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Fri, 24 Jun 2022 13:42:32 +0200 Subject: [PATCH 22/49] No need for specific variant of base ad local linearizer. --- flow/flow_blackoil_tpfa.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp index 70005e275..8735af042 100644 --- a/flow/flow_blackoil_tpfa.cpp +++ b/flow/flow_blackoil_tpfa.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +//#include #include #include #include @@ -58,10 +58,10 @@ namespace Opm { template struct UseTpfaLinearizer { static constexpr bool value = true; }; - template - struct LocalLinearizerSplice { - using type = TTag::AutoDiffLocalLinearizerTPFA; - }; + // template + // struct LocalLinearizerSplice { + // using type = TTag::AutoDiffLocalLinearizerTPFA; + // }; } } namespace Opm { From 4d0fc84b1ff012040d557fe0422c808c628af2e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Fri, 24 Jun 2022 14:01:45 +0200 Subject: [PATCH 23/49] No need for specific base local residual class. --- flow/flow_blackoil_tpfa.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp index 8735af042..a7f561d8b 100644 --- a/flow/flow_blackoil_tpfa.cpp +++ b/flow/flow_blackoil_tpfa.cpp @@ -23,7 +23,7 @@ // modifications from standard #include #include -#include +//#include //#include #include #include @@ -69,8 +69,8 @@ namespace Opm { template struct LocalResidual { using type = BlackOilLocalResidualTPFA; }; template - struct DiscLocalResidual { using type = FvBaseLocalResidualTPFA; }; - template + // struct DiscLocalResidual { using type = FvBaseLocalResidualTPFA; }; + // template struct ElementContext { using type = SmallElementContext; }; //struct ElementContext { using type = FvBaseElementContext; }; } From 657f4f5b8e20b91ebd8b9ec52fb90ac1b70c6f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Fri, 24 Jun 2022 15:56:40 +0200 Subject: [PATCH 24/49] Use separate TpfaLinearizer (again). --- flow/flow_blackoil_tpfa.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp index a7f561d8b..ce5a7f72d 100644 --- a/flow/flow_blackoil_tpfa.cpp +++ b/flow/flow_blackoil_tpfa.cpp @@ -26,6 +26,7 @@ //#include //#include #include +#include #include #include @@ -51,12 +52,12 @@ namespace Opm { namespace Opm { namespace Properties { - // template - // struct Linearizer { using type = LinearizerTPFA; }; - - // Override default: use the TPFA linearizer. template - struct UseTpfaLinearizer { static constexpr bool value = true; }; + struct Linearizer { using type = TpfaLinearizer; }; + + // // Override default: use the TPFA linearizer. + // template + // struct UseTpfaLinearizer { static constexpr bool value = true; }; // template // struct LocalLinearizerSplice { From 66a1c46413ecaf0b81e0c256a748252ec46e519a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Thu, 30 Jun 2022 10:20:18 +0200 Subject: [PATCH 25/49] Remove unused code branch. --- opm/simulators/flow/BlackoilModelEbos.hpp | 41 +++++++---------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/opm/simulators/flow/BlackoilModelEbos.hpp b/opm/simulators/flow/BlackoilModelEbos.hpp index d01e11f65..cb9013582 100644 --- a/opm/simulators/flow/BlackoilModelEbos.hpp +++ b/opm/simulators/flow/BlackoilModelEbos.hpp @@ -587,7 +587,7 @@ namespace Opm { ebosSimulator_.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0); } - + void updateIntensiveQuantity(const Problem& problem, SolutionVector& solution, const BVector& dx, @@ -595,35 +595,18 @@ namespace Opm { { auto& model = ebosSimulator_.model(); auto& ebosNewtonMethod = model.newtonMethod(); - if(false){ - if (!std::isfinite(dx.one_norm())) - throw NumericalIssue("Non-finite update!"); - - size_t numGridDof = model.numGridDof(); - for (unsigned dofIdx = 0; dofIdx < numGridDof; ++dofIdx) { - ebosNewtonMethod.updatePrimaryVariables_(dofIdx, - solution[dofIdx], - solution[dofIdx], - dx[dofIdx], - dx[dofIdx]); - model.invalidateAndUpdateIntensiveSingleQuantitiesSimple(problem, - solution[dofIdx], - dofIdx, - /*timeIdx*/0); - } - }else{ - ebosNewtonMethod.update_(/*nextSolution*/solution, - /*curSolution=*/solution, - /*update=*/dx, - /*resid=*/dx); // the update routines of the black - // oil model do not care about the - // residual - model.invalidateAndUpdateIntensiveQuantitiesSimple(problem, - solution, - /*timeIdx*/0); - } + ebosNewtonMethod.update_(/*nextSolution*/solution, + /*curSolution=*/solution, + /*update=*/dx, + /*resid=*/dx); // the update routines of the black + // oil model do not care about the + // residual + model.invalidateAndUpdateIntensiveQuantitiesSimple(problem, + solution, + /*timeIdx*/0); } - + + /// Apply an update to the primary variables. void updateSolution(const BVector& dx) { From b1bcab31b9c0c46c6fe53ad0806d4e9a1d0bc2ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Fri, 1 Jul 2022 15:36:32 +0200 Subject: [PATCH 26/49] Refactor addToSource to add interface not using element context. --- opm/simulators/aquifers/AquiferInterface.hpp | 23 ++++++++++++------- .../aquifers/BlackoilAquiferModel.hpp | 1 + .../aquifers/BlackoilAquiferModel_impl.hpp | 18 +++++++++++++++ 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/opm/simulators/aquifers/AquiferInterface.hpp b/opm/simulators/aquifers/AquiferInterface.hpp index fcc997f8c..74180a2d9 100644 --- a/opm/simulators/aquifers/AquiferInterface.hpp +++ b/opm/simulators/aquifers/AquiferInterface.hpp @@ -143,24 +143,31 @@ public: const unsigned timeIdx) { const unsigned cellIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + addToSource(rates, cellIdx, timeIdx); + } + + void addToSource(RateVector& rates, + const unsigned cellIdx, + const unsigned timeIdx) + { + const auto& model = ebos_simulator_.model(); const int idx = this->cellToConnectionIdx_[cellIdx]; if (idx < 0) return; - // We are dereferencing the value of IntensiveQuantities because - // cachedIntensiveQuantities return a const pointer to - // IntensiveQuantities of that particular cell_id - const auto& intQuants = context.intensiveQuantities(spaceIdx, timeIdx); + const auto* intQuantsPtr = model.cachedIntensiveQuantities(cellIdx, timeIdx); + if (intQuantsPtr == nullptr) { + throw std::logic_error("Invalid intensive quantities cache detected in AquiferInterface::addToSource()"); + } // This is the pressure at td + dt - this->updateCellPressure(this->pressure_current_, idx, intQuants); - this->calculateInflowRate(idx, context.simulator()); + this->updateCellPressure(this->pressure_current_, idx, *intQuantsPtr); + this->calculateInflowRate(idx, ebos_simulator_); rates[BlackoilIndices::conti0EqIdx + compIdx_()] - += this->Qai_[idx] / context.dofVolume(spaceIdx, timeIdx); + += this->Qai_[idx] / model.dofTotalVolume(cellIdx); } - std::size_t size() const { return this->connections_.size(); } diff --git a/opm/simulators/aquifers/BlackoilAquiferModel.hpp b/opm/simulators/aquifers/BlackoilAquiferModel.hpp index 1e2ae5468..342f614af 100644 --- a/opm/simulators/aquifers/BlackoilAquiferModel.hpp +++ b/opm/simulators/aquifers/BlackoilAquiferModel.hpp @@ -95,6 +95,7 @@ public: // add the water rate due to aquifers to the source term. template void addToSource(RateVector& rates, const Context& context, unsigned spaceIdx, unsigned timeIdx) const; + void addToSource(RateVector& rates, unsigned globalSpaceIdx, unsigned timeIdx) const; void endIteration(); void endTimeStep(); void endEpisode(); diff --git a/opm/simulators/aquifers/BlackoilAquiferModel_impl.hpp b/opm/simulators/aquifers/BlackoilAquiferModel_impl.hpp index 3ad985fc2..829c5835c 100644 --- a/opm/simulators/aquifers/BlackoilAquiferModel_impl.hpp +++ b/opm/simulators/aquifers/BlackoilAquiferModel_impl.hpp @@ -126,6 +126,24 @@ BlackoilAquiferModel::addToSource(RateVector& rates, } } +template +void +BlackoilAquiferModel::addToSource(RateVector& rates, + unsigned globalSpaceIdx, + unsigned timeIdx) const +{ + if (aquiferCarterTracyActive()) { + for (auto& aquifer : aquifers_CarterTracy) { + aquifer.addToSource(rates, globalSpaceIdx, timeIdx); + } + } + if (aquiferFetkovichActive()) { + for (auto& aquifer : aquifers_Fetkovich) { + aquifer.addToSource(rates, globalSpaceIdx, timeIdx); + } + } +} + template void BlackoilAquiferModel::endIteration() From f19b6f723d620f6c4bdd346d92741b6f8b00c965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Fri, 1 Jul 2022 15:37:40 +0200 Subject: [PATCH 27/49] Refactor to avoid twin codepaths most places. --- ebos/eclproblemtpfa.hh | 107 ++++++++++------------------------------- 1 file changed, 26 insertions(+), 81 deletions(-) diff --git a/ebos/eclproblemtpfa.hh b/ebos/eclproblemtpfa.hh index 8c18b31c1..9530bb672 100644 --- a/ebos/eclproblemtpfa.hh +++ b/ebos/eclproblemtpfa.hh @@ -1384,11 +1384,6 @@ public: /*! * \copydoc EclTransmissiblity::transmissibility */ - Scalar transmissibility(unsigned globalCenterElemIdx,unsigned globalElemIdx) const - { - return transmissibilities_.transmissibility(globalCenterElemIdx, globalElemIdx); - } - template Scalar transmissibility(const Context& context, [[maybe_unused]] unsigned fromDofLocalIdx, @@ -1398,6 +1393,11 @@ public: return pffDofData_.get(context.element(), toDofLocalIdx).transmissibility; } + Scalar transmissibility(unsigned globalCenterElemIdx, unsigned globalElemIdx) const + { + return transmissibilities_.transmissibility(globalCenterElemIdx, globalElemIdx); + } + /*! * \copydoc EclTransmissiblity::diffusivity */ @@ -1494,16 +1494,14 @@ public: Scalar porosity(const Context& context, unsigned spaceIdx, unsigned timeIdx) const { unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); - return this->referencePorosity_[timeIdx][globalSpaceIdx]; + return this->porosity(globalSpaceIdx, timeIdx); } Scalar porosity(unsigned globalSpaceIdx, unsigned timeIdx) const { - //unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); return this->referencePorosity_[timeIdx][globalSpaceIdx]; } - - + /*! * \brief Returns the depth of an degree of freedom [m] * @@ -1514,14 +1512,14 @@ public: Scalar dofCenterDepth(const Context& context, unsigned spaceIdx, unsigned timeIdx) const { unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); - return this->simulator().vanguard().cellCenterDepth(globalSpaceIdx); + return this->dofCenterDepth(globalSpaceIdx); } - Scalar dofCenterDepth(unsigned globalSpaceIdx, unsigned timeIdx) const + Scalar dofCenterDepth(unsigned globalSpaceIdx) const { return this->simulator().vanguard().cellCenterDepth(globalSpaceIdx); } - + /*! * \copydoc BlackoilProblem::rockCompressibility @@ -1542,7 +1540,7 @@ public: } - Scalar rockCompressibility(unsigned globalSpaceIdx, unsigned timeIdx) const + Scalar rockCompressibility(unsigned globalSpaceIdx) const { if (this->rockParams_.empty()) return 0.0; @@ -1551,7 +1549,6 @@ public: if (!this->rockTableIdx_.empty()) { tableIdx = this->rockTableIdx_[globalSpaceIdx]; } - //unsigned tableIdx = 0;// faster but not genneral return this->rockParams_[tableIdx].compressibility; } @@ -1572,7 +1569,7 @@ public: return this->rockParams_[tableIdx].referencePressure; } - Scalar rockReferencePressure(unsigned globalSpaceIdx, unsigned timeIdx) const + Scalar rockReferencePressure(unsigned globalSpaceIdx) const { if (this->rockParams_.empty()) return 1e5; @@ -1581,9 +1578,9 @@ public: if (!this->rockTableIdx_.empty()) { tableIdx = this->rockTableIdx_[globalSpaceIdx]; } - //unsigned tableIdx = 0;//faster but not genneral this->rockTableIdx_[globalSpaceIdx]; return this->rockParams_[tableIdx].referencePressure; } + /*! * \copydoc FvBaseMultiPhaseProblem::materialLawParams */ @@ -1592,7 +1589,7 @@ public: unsigned spaceIdx, unsigned timeIdx) const { unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); - return materialLawParams(globalSpaceIdx); + return this->materialLawParams(globalSpaceIdx); } const MaterialLawParams& materialLawParams(unsigned globalDofIdx) const @@ -1811,7 +1808,6 @@ public: */ Scalar maxGasDissolutionFactor(unsigned timeIdx, unsigned globalDofIdx) const { - int pvtRegionIdx = this->pvtRegionIndex(globalDofIdx); int episodeIdx = this->episodeIndex(); if (!this->drsdtActive_(episodeIdx) || this->maxDRs_[pvtRegionIdx] < 0.0) @@ -1930,60 +1926,6 @@ public: aquiferModel_.initialSolutionApplied(); } - void source(RateVector& rate, - unsigned globalSpaceIdx, - unsigned timeIdx) const - { - rate = 0.0; - - wellModel_.computeTotalRatesForDof(rate, globalSpaceIdx, timeIdx); - - // convert the source term from the total mass rate of the - // cell to the one per unit of volume as used by the model. - - for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) { - rate[eqIdx] /= this->model().dofTotalVolume(globalSpaceIdx); - - Valgrind::CheckDefined(rate[eqIdx]); - assert(isfinite(rate[eqIdx])); - } - - // if (enableAquifers_) - // aquiferModel_.addToSource(rate, context, spaceIdx, timeIdx); - - // if requested, compensate systematic mass loss for cells which were "well - // behaved" in the last time step - // if (enableDriftCompensation_) { - // const auto& intQuants = context.intensiveQuantities(spaceIdx, timeIdx); - // const auto& simulator = this->simulator(); - // const auto& model = this->model(); - - // // we need a higher maxCompensation than the Newton tolerance because the - // // current time step might be shorter than the last one - // Scalar maxCompensation = 10.0*model.newtonMethod().tolerance(); - - // Scalar poro = intQuants.referencePorosity(); - // Scalar dt = simulator.timeStepSize(); - - // EqVector dofDriftRate = drift_[globalDofIdx]; - // dofDriftRate /= dt*context.dofTotalVolume(spaceIdx, timeIdx); - - // // compute the weighted total drift rate - // Scalar totalDriftRate = 0.0; - // for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) - // totalDriftRate += - // std::abs(dofDriftRate[eqIdx])*dt*model.eqWeight(globalDofIdx, eqIdx)/poro; - - // // make sure that we do not exceed the maximum rate of drift compensation - // if (totalDriftRate > maxCompensation) - // dofDriftRate *= maxCompensation/totalDriftRate; - - // for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) - // rate[eqIdx] -= dofDriftRate[eqIdx]; - // } - } - - /*! * \copydoc FvBaseProblem::source * @@ -1994,14 +1936,21 @@ public: const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + const unsigned globalDofIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + source(rate, globalDofIdx, timeIdx); + } + + void source(RateVector& rate, + unsigned globalDofIdx, + unsigned timeIdx) const { rate = 0.0; - wellModel_.computeTotalRatesForDof(rate, context, spaceIdx, timeIdx); + wellModel_.computeTotalRatesForDof(rate, globalDofIdx, timeIdx); // convert the source term from the total mass rate of the // cell to the one per unit of volume as used by the model. - const unsigned globalDofIdx = context.globalSpaceIndex(spaceIdx, timeIdx); for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) { rate[eqIdx] /= this->model().dofTotalVolume(globalDofIdx); @@ -2010,12 +1959,11 @@ public: } if (enableAquifers_) - aquiferModel_.addToSource(rate, context, spaceIdx, timeIdx); + aquiferModel_.addToSource(rate, globalDofIdx, timeIdx); // if requested, compensate systematic mass loss for cells which were "well // behaved" in the last time step if (enableDriftCompensation_) { - const auto& intQuants = context.intensiveQuantities(spaceIdx, timeIdx); const auto& simulator = this->simulator(); const auto& model = this->model(); @@ -2023,11 +1971,11 @@ public: // current time step might be shorter than the last one Scalar maxCompensation = 10.0*model.newtonMethod().tolerance(); - Scalar poro = intQuants.referencePorosity(); + Scalar poro = this->porosity(globalDofIdx, timeIdx); Scalar dt = simulator.timeStepSize(); EqVector dofDriftRate = drift_[globalDofIdx]; - dofDriftRate /= dt*context.dofTotalVolume(spaceIdx, timeIdx); + dofDriftRate /= dt*model.dofTotalVolume(globalDofIdx); // compute the weighted total drift rate Scalar totalDriftRate = 0.0; @@ -2103,9 +2051,6 @@ public: return limitNextTimeStepSize_(newtonMethod.suggestTimeStepSize(simulator.timeStepSize())); } - Scalar volume(unsigned dofIdx,unsigned /*timidx*/) const{ - return this->simulator().model().dofTotalVolume(dofIdx); - } /*! * \brief Calculate the porosity multiplier due to water induced rock compaction. * From 64474c02671166d3b8f365457bed9b0f52bc5b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Mon, 4 Jul 2022 16:20:37 +0200 Subject: [PATCH 28/49] Remove unused code. --- flow/flow_blackoil_tpfa.cpp | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp index ce5a7f72d..42bf1f4c3 100644 --- a/flow/flow_blackoil_tpfa.cpp +++ b/flow/flow_blackoil_tpfa.cpp @@ -40,6 +40,8 @@ namespace Opm { } } + + namespace Opm { namespace Properties { template @@ -54,15 +56,6 @@ namespace Opm { namespace Properties { template struct Linearizer { using type = TpfaLinearizer; }; - - // // Override default: use the TPFA linearizer. - // template - // struct UseTpfaLinearizer { static constexpr bool value = true; }; - - // template - // struct LocalLinearizerSplice { - // using type = TTag::AutoDiffLocalLinearizerTPFA; - // }; } } namespace Opm { @@ -70,27 +63,25 @@ namespace Opm { template struct LocalResidual { using type = BlackOilLocalResidualTPFA; }; template - // struct DiscLocalResidual { using type = FvBaseLocalResidualTPFA; }; - // template - struct ElementContext { using type = SmallElementContext; }; - //struct ElementContext { using type = FvBaseElementContext; }; + //struct ElementContext { using type = SmallElementContext; }; + struct ElementContext { using type = FvBaseElementContext; }; } } namespace Opm{ template - struct EclTransFluxModuleTPFA + struct EclTransFluxModuleTPFA { typedef EclTransIntensiveQuantities FluxIntensiveQuantities; typedef EclTransExtensiveQuantitiesTPFA FluxExtensiveQuantities; typedef EclTransBaseProblem FluxBaseProblem; - /*! - * \brief Register all run-time parameters for the flux module. - */ + /// \brief Register all run-time parameters for the flux module. static void registerParameters() { } }; } + + namespace Opm { namespace Properties { From c48770dc5f2d36b3e5aff8a3504866ee74e4c6be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Tue, 5 Jul 2022 09:48:38 +0200 Subject: [PATCH 29/49] Remove EclProblemTpfa, folding all methods into EclProblem. --- ebos/eclproblem.hh | 88 +- ebos/eclproblemtpfa.hh | 3115 ----------------------------------- flow/flow_blackoil_tpfa.cpp | 14 +- 3 files changed, 78 insertions(+), 3139 deletions(-) delete mode 100644 ebos/eclproblemtpfa.hh diff --git a/ebos/eclproblem.hh b/ebos/eclproblem.hh index 84e27c445..2c259a81f 100644 --- a/ebos/eclproblem.hh +++ b/ebos/eclproblem.hh @@ -1438,6 +1438,14 @@ public: return pffDofData_.get(context.element(), toDofLocalIdx).transmissibility; } + /*! + * \brief Direct access to the transmissibility between two elements. + */ + Scalar transmissibility(unsigned globalCenterElemIdx, unsigned globalElemIdx) const + { + return transmissibilities_.transmissibility(globalCenterElemIdx, globalElemIdx); + } + /*! * \copydoc EclTransmissiblity::diffusivity */ @@ -1534,6 +1542,19 @@ public: Scalar porosity(const Context& context, unsigned spaceIdx, unsigned timeIdx) const { unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + return this->porosity(globalSpaceIdx, timeIdx); + } + + /*! + * \brief Direct indexed access to the porosity. + * + * For the EclProblem, this method is identical to referencePorosity(). The intensive + * quantities object may apply various multipliers (e.g. ones which model rock + * compressibility and water induced rock compaction) to it which depend on the + * current physical conditions. + */ + Scalar porosity(unsigned globalSpaceIdx, unsigned timeIdx) const + { return this->referencePorosity_[timeIdx][globalSpaceIdx]; } @@ -1547,11 +1568,11 @@ public: Scalar dofCenterDepth(const Context& context, unsigned spaceIdx, unsigned timeIdx) const { unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); - return dofCenterDepth(globalSpaceIdx); + return this->dofCenterDepth(globalSpaceIdx); } /*! - * \brief Returns the depth of an degree of freedom [m] + * \brief Direct indexed acces to the depth of an degree of freedom [m] * * For ECL problems this is defined as the average of the depth of an element and is * thus slightly different from the depth of an element's centroid. @@ -1561,7 +1582,6 @@ public: return this->simulator().vanguard().cellCenterDepth(globalSpaceIdx); } - /*! * \copydoc BlackoilProblem::rockCompressibility */ @@ -1580,6 +1600,25 @@ public: return this->rockParams_[tableIdx].compressibility; } + /*! + * Direct access to rock compressibility. + * + * While the above overload could be implemented in terms of this method, + * that would require always looking up the global space index, which + * is not always needed. + */ + Scalar rockCompressibility(unsigned globalSpaceIdx) const + { + if (this->rockParams_.empty()) + return 0.0; + + unsigned tableIdx = 0; + if (!this->rockTableIdx_.empty()) { + tableIdx = this->rockTableIdx_[globalSpaceIdx]; + } + return this->rockParams_[tableIdx].compressibility; + } + /*! * \copydoc BlackoilProblem::rockReferencePressure */ @@ -1598,6 +1637,25 @@ public: return this->rockParams_[tableIdx].referencePressure; } + /*! + * Direct access to rock reference pressure. + * + * While the above overload could be implemented in terms of this method, + * that would require always looking up the global space index, which + * is not always needed. + */ + Scalar rockReferencePressure(unsigned globalSpaceIdx) const + { + if (this->rockParams_.empty()) + return 1e5; + + unsigned tableIdx = 0; + if (!this->rockTableIdx_.empty()) { + tableIdx = this->rockTableIdx_[globalSpaceIdx]; + } + return this->rockParams_[tableIdx].referencePressure; + } + /*! * \copydoc FvBaseMultiPhaseProblem::materialLawParams */ @@ -1606,11 +1664,13 @@ public: unsigned spaceIdx, unsigned timeIdx) const { unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); - return materialLawParams(globalSpaceIdx); + return this->materialLawParams(globalSpaceIdx); } const MaterialLawParams& materialLawParams(unsigned globalDofIdx) const - { return materialLawManager_->materialLawParams(globalDofIdx); } + { + return materialLawManager_->materialLawParams(globalDofIdx); + } /*! * \brief Return the parameters for the energy storage law of the rock @@ -1953,14 +2013,21 @@ public: const Context& context, unsigned spaceIdx, unsigned timeIdx) const + { + const unsigned globalDofIdx = context.globalSpaceIndex(spaceIdx, timeIdx); + source(rate, globalDofIdx, timeIdx); + } + + void source(RateVector& rate, + unsigned globalDofIdx, + unsigned timeIdx) const { rate = 0.0; - wellModel_.computeTotalRatesForDof(rate, context, spaceIdx, timeIdx); + wellModel_.computeTotalRatesForDof(rate, globalDofIdx, timeIdx); // convert the source term from the total mass rate of the // cell to the one per unit of volume as used by the model. - const unsigned globalDofIdx = context.globalSpaceIndex(spaceIdx, timeIdx); for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) { rate[eqIdx] /= this->model().dofTotalVolume(globalDofIdx); @@ -1969,12 +2036,11 @@ public: } if (enableAquifers_) - aquiferModel_.addToSource(rate, context, spaceIdx, timeIdx); + aquiferModel_.addToSource(rate, globalDofIdx, timeIdx); // if requested, compensate systematic mass loss for cells which were "well // behaved" in the last time step if (enableDriftCompensation_) { - const auto& intQuants = context.intensiveQuantities(spaceIdx, timeIdx); const auto& simulator = this->simulator(); const auto& model = this->model(); @@ -1982,11 +2048,11 @@ public: // current time step might be shorter than the last one Scalar maxCompensation = 10.0*model.newtonMethod().tolerance(); - Scalar poro = intQuants.referencePorosity(); + Scalar poro = this->porosity(globalDofIdx, timeIdx); Scalar dt = simulator.timeStepSize(); EqVector dofDriftRate = drift_[globalDofIdx]; - dofDriftRate /= dt*context.dofTotalVolume(spaceIdx, timeIdx); + dofDriftRate /= dt*model.dofTotalVolume(globalDofIdx); // compute the weighted total drift rate Scalar totalDriftRate = 0.0; diff --git a/ebos/eclproblemtpfa.hh b/ebos/eclproblemtpfa.hh deleted file mode 100644 index 9530bb672..000000000 --- a/ebos/eclproblemtpfa.hh +++ /dev/null @@ -1,3115 +0,0 @@ -// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- -// vi: set et ts=4 sw=4 sts=4: -/* - 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 2 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 . - - Consult the COPYING file in the top-level source directory of this - module for the precise wording of the license and the list of - copyright holders. -*/ -/*! - * \file - * - * \copydoc Opm::EclProblem - */ -#ifndef EWOMS_ECL_PROBLEM_TPFA_HH -#define EWOMS_ECL_PROBLEM_TPFA_HH - -//#define DISABLE_ALUGRID_SFC_ORDERING 1 -//#define EBOS_USE_ALUGRID 1 - -// make sure that the EBOS_USE_ALUGRID macro. using the preprocessor for this is slightly -// hacky... -// #if EBOS_USE_ALUGRID -// //#define DISABLE_ALUGRID_SFC_ORDERING 1 -// #if !HAVE_DUNE_ALUGRID -// #warning "ALUGrid was indicated to be used for the ECL black oil simulator, but this " -// #warning "requires the presence of dune-alugrid >= 2.4. Falling back to Dune::CpGrid" -// #undef EBOS_USE_ALUGRID -// #define EBOS_USE_ALUGRID 0 -// #endif -// #else -// #define EBOS_USE_ALUGRID 0 -// #endif - -// #if EBOS_USE_ALUGRID -// #include "eclalugridvanguard.hh" -// #elif USE_POLYHEDRALGRID -// #include "eclpolyhedralgridvanguard.hh" -// #else -// #include "eclcpgridvanguard.hh" -// #endif - -// #include "eclequilinitializer.hh" -// #include "eclwriter.hh" -// #include "ecloutputblackoilmodule.hh" -// #include "ecltransmissibility.hh" -// #include "eclthresholdpressure.hh" -// #include "ecldummygradientcalculator.hh" -// #include "eclfluxmodule.hh" -// #include "eclbaseaquifermodel.hh" -// #include "eclnewtonmethod.hh" -// #include "ecltracermodel.hh" -// #include "vtkecltracermodule.hh" -// #include "eclgenericproblem.hh" - -// #include - -// #include -// #include -// #include - -// #include -// #include -// #include - -// #include -// #include - -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include - -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include - -// #include -// #include -// #include - -// #include - -// #include - -// #include -// #include -// #include -// #include - -// namespace Opm { -// template -// class EclProblem; -// } - -// namespace Opm::Properties { - -// namespace TTag { - -// #if EBOS_USE_ALUGRID -// struct EclBaseProblem { -// using InheritsFrom = std::tuple; -// }; -// #elif USE_POLYHEDRALGRID -// struct EclBaseProblem { -// using InheritsFrom = std::tuple; -// }; -// #else -// struct EclBaseProblem { -// using InheritsFrom = std::tuple; -// }; -// #endif -// } - -// // The class which deals with ECL wells -// template -// struct EclWellModel { -// using type = UndefinedProperty; -// }; - -// // Write all solutions for visualization, not just the ones for the -// // report steps... -// template -// struct EnableWriteAllSolutions { -// using type = UndefinedProperty; -// }; - -// // The number of time steps skipped between writing two consequtive restart files -// template -// struct RestartWritingInterval { -// using type = UndefinedProperty; -// }; - -// // Enable partial compensation of systematic mass losses via the source term of the next time -// // step -// template -// struct EclEnableDriftCompensation { -// using type = UndefinedProperty; -// }; - -// // Enable the additional checks even if compiled in debug mode (i.e., with the NDEBUG -// // macro undefined). Next to a slightly better performance, this also eliminates some -// // print statements in debug mode. -// template -// struct EnableDebuggingChecks { -// using type = UndefinedProperty; -// }; - -// // if thermal flux boundaries are enabled an effort is made to preserve the initial -// // thermal gradient specified via the TEMPVD keyword -// template -// struct EnableThermalFluxBoundaries { -// using type = UndefinedProperty; -// }; - -// // Specify whether API tracking should be enabled (replaces PVT regions). -// // TODO: This is not yet implemented -// template -// struct EnableApiTracking { -// using type = UndefinedProperty; -// }; - -// // The class which deals with ECL aquifers -// template -// struct EclAquiferModel { -// using type = UndefinedProperty; -// }; - -// // In experimental mode, decides if the aquifer model should be enabled or not -// template -// struct EclEnableAquifers { -// using type = UndefinedProperty; -// }; - -// // time stepping parameters -// template -// struct EclMaxTimeStepSizeAfterWellEvent { -// using type = UndefinedProperty; -// }; -// template -// struct EclRestartShrinkFactor { -// using type = UndefinedProperty; -// }; -// template -// struct EclEnableTuning { -// using type = UndefinedProperty; -// }; -// template -// struct OutputMode { -// using type = UndefinedProperty; -// }; - -// // Set the problem property -// template -// struct Problem { -// using type = EclProblem; -// }; - -// // Select the element centered finite volume method as spatial discretization -// template -// struct SpatialDiscretizationSplice { -// using type = TTag::EcfvDiscretization; -// }; - -// //! for ebos, use automatic differentiation to linearize the system of PDEs -// template -// struct LocalLinearizerSplice { -// using type = TTag::AutoDiffLocalLinearizer; -// }; - -// // Set the material law for fluid fluxes -// template -// struct MaterialLaw -// { -// private: -// using Scalar = GetPropType; -// using FluidSystem = GetPropType; - -// using Traits = ThreePhaseMaterialTraits; - -// public: -// using EclMaterialLawManager = ::Opm::EclMaterialLawManager; - -// using type = typename EclMaterialLawManager::MaterialLaw; -// }; - -// // Set the material law for energy storage in rock -// template -// struct SolidEnergyLaw -// { -// private: -// using Scalar = GetPropType; -// using FluidSystem = GetPropType; - -// public: -// using EclThermalLawManager = ::Opm::EclThermalLawManager; - -// using type = typename EclThermalLawManager::SolidEnergyLaw; -// }; - -// // Set the material law for thermal conduction -// template -// struct ThermalConductionLaw -// { -// private: -// using Scalar = GetPropType; -// using FluidSystem = GetPropType; - -// public: -// using EclThermalLawManager = ::Opm::EclThermalLawManager; - -// using type = typename EclThermalLawManager::ThermalConductionLaw; -// }; - -// // ebos can use a slightly faster stencil class because it does not need the normals and -// // the integration points of intersections -// template -// struct Stencil -// { -// private: -// using Scalar = GetPropType; -// using GridView = GetPropType; - -// public: -// using type = EcfvStencil; -// }; - -// // by default use the dummy aquifer "model" -// template -// struct EclAquiferModel { -// using type = EclBaseAquiferModel; -// }; - -// // Enable aquifers by default in experimental mode -// template -// struct EclEnableAquifers { -// static constexpr bool value = true; -// }; - -// // Enable gravity -// template -// struct EnableGravity { -// static constexpr bool value = true; -// }; - -// // Enable diffusion -// template -// struct EnableDiffusion { -// static constexpr bool value = true; -// }; - -// // only write the solutions for the report steps to disk -// template -// struct EnableWriteAllSolutions { -// static constexpr bool value = false; -// }; - -// // disable API tracking -// template -// struct EnableApiTracking { -// static constexpr bool value = false; -// }; - -// // The default for the end time of the simulation [s] -// // -// // By default, stop it after the universe will probably have stopped -// // to exist. (the ECL problem will finish the simulation explicitly -// // after it simulated the last episode specified in the deck.) -// template -// struct EndTime { -// using type = GetPropType; -// static constexpr type value = 1e100; -// }; - -// // The default for the initial time step size of the simulation [s]. -// // -// // The chosen value means that the size of the first time step is the -// // one of the initial episode (if the length of the initial episode is -// // not millions of trillions of years, that is...) -// template -// struct InitialTimeStepSize { -// using type = GetPropType; -// static constexpr type value = 3600*24; -// }; - -// // the default for the allowed volumetric error for oil per second -// template -// struct NewtonTolerance { -// using type = GetPropType; -// static constexpr type value = 1e-2; -// }; - -// // the tolerated amount of "incorrect" amount of oil per time step for the complete -// // reservoir. this is scaled by the pore volume of the reservoir, i.e., larger reservoirs -// // will tolerate larger residuals. -// template -// struct EclNewtonSumTolerance { -// using type = GetPropType; -// static constexpr type value = 1e-4; -// }; - -// // set the exponent for the volume scaling of the sum tolerance: larger reservoirs can -// // tolerate a higher amount of mass lost per time step than smaller ones! since this is -// // not linear, we use the cube root of the overall pore volume by default, i.e., the -// // value specified by the NewtonSumTolerance parameter is the "incorrect" mass per -// // timestep for an reservoir that exhibits 1 m^3 of pore volume. A reservoir with a total -// // pore volume of 10^3 m^3 will tolerate 10 times as much. -// template -// struct EclNewtonSumToleranceExponent { -// using type = GetPropType; -// static constexpr type value = 1.0/3.0; -// }; - -// // set number of Newton iterations where the volumetric residual is considered for -// // convergence -// template -// struct EclNewtonStrictIterations { -// static constexpr int value = 8; -// }; - -// // set fraction of the pore volume where the volumetric residual may be violated during -// // strict Newton iterations -// template -// struct EclNewtonRelaxedVolumeFraction { -// using type = GetPropType; -// static constexpr type value = 0.03; -// }; - -// // the maximum volumetric error of a cell in the relaxed region -// template -// struct EclNewtonRelaxedTolerance { -// using type = GetPropType; -// static constexpr type value = 1e9; -// }; - -// // Ignore the maximum error mass for early termination of the newton method. -// template -// struct NewtonMaxError { -// using type = GetPropType; -// static constexpr type value = 10e9; -// }; - -// // set the maximum number of Newton iterations to 14 because the likelyhood that a time -// // step succeeds at more than 14 Newton iteration is rather small -// template -// struct NewtonMaxIterations { -// static constexpr int value = 14; -// }; - -// // also, reduce the target for the "optimum" number of Newton iterations to 6. Note that -// // this is only relevant if the time step is reduced from the report step size for some -// // reason. (because ebos first tries to do a report step using a single time step.) -// template -// struct NewtonTargetIterations { -// static constexpr int value = 6; -// }; - -// // Disable the VTK output by default for this problem ... -// template -// struct EnableVtkOutput { -// static constexpr bool value = false; -// }; - -// // ... but enable the ECL output by default -// template -// struct EnableEclOutput { -// static constexpr bool value = true; -// }; - -// // If available, write the ECL output in a non-blocking manner -// template -// struct EnableAsyncEclOutput { -// static constexpr bool value = true; -// }; -// // Write ESMRY file for fast loading of summary data -// template -// struct EnableEsmry { -// static constexpr bool value = false; -// }; - -// // By default, use single precision for the ECL formated results -// template -// struct EclOutputDoublePrecision { -// static constexpr bool value = false; -// }; - -// // The default location for the ECL output files -// template -// struct OutputDir { -// static constexpr auto value = "."; -// }; - -// // the cache for intensive quantities can be used for ECL problems and also yields a -// // decent speedup... -// template -// struct EnableIntensiveQuantityCache { -// static constexpr bool value = true; -// }; - -// // the cache for the storage term can also be used and also yields a decent speedup -// template -// struct EnableStorageCache { -// static constexpr bool value = true; -// }; - -// // Use the "velocity module" which uses the Eclipse "NEWTRAN" transmissibilities -// template -// struct FluxModule { -// using type = EclTransFluxModule; -// }; - -// // Use the dummy gradient calculator in order not to do unnecessary work. -// template -// struct GradientCalculator { -// using type = EclDummyGradientCalculator; -// }; - -// // Use a custom Newton-Raphson method class for ebos in order to attain more -// // sophisticated update and error computation mechanisms -// template -// struct NewtonMethod { -// using type = EclNewtonMethod; -// }; - -// // The frequency of writing restart (*.ers) files. This is the number of time steps -// // between writing restart files -// template -// struct RestartWritingInterval { -// static constexpr int value = 0xffffff; // disable -// }; - -// // Drift compensation is an experimental feature, i.e., systematic errors in the -// // conservation quantities are only compensated for -// // as default if experimental mode is enabled. -// template -// struct EclEnableDriftCompensation { -// static constexpr bool value = true; - -// }; - -// // By default, we enable the debugging checks if we're compiled in debug mode -// template -// struct EnableDebuggingChecks { -// static constexpr bool value = true; -// }; - -// // store temperature (but do not conserve energy, as long as EnableEnergy is false) -// template -// struct EnableTemperature { -// static constexpr bool value = true; -// }; - -// // disable all extensions supported by black oil model. this should not really be -// // necessary but it makes things a bit more explicit -// template -// struct EnablePolymer { -// static constexpr bool value = false; -// }; -// template -// struct EnableSolvent { -// static constexpr bool value = false; -// }; -// template -// struct EnableEnergy { -// static constexpr bool value = false; -// }; -// template -// struct EnableFoam { -// static constexpr bool value = false; -// }; -// template -// struct EnableExtbo { -// static constexpr bool value = false; -// }; -// template -// struct EnableMICP { -// static constexpr bool value = false; -// }; - -// // disable thermal flux boundaries by default -// template -// struct EnableThermalFluxBoundaries { -// static constexpr bool value = false; -// }; - -// // By default, simulators derived from the EclBaseProblem are production simulators, -// // i.e., experimental features must be explicitly enabled at compile time -// template -// struct EnableExperiments { -// static constexpr bool value = false; -// }; - -// // set defaults for the time stepping parameters -// template -// struct EclMaxTimeStepSizeAfterWellEvent { -// using type = GetPropType; -// static constexpr type value = 3600*24*365.25; -// }; -// template -// struct EclRestartShrinkFactor { -// using type = GetPropType; -// static constexpr type value = 3; -// }; -// template -// struct EclEnableTuning { -// static constexpr bool value = false; -// }; - -// template -// struct OutputMode { -// static constexpr auto value = "all"; -// }; - -// } // namespace Opm::Properties - - -namespace Opm { - -/*! - * \ingroup EclBlackOilSimulator - * - * \brief This problem simulates an input file given in the data format used by the - * commercial ECLiPSE simulator. - */ -template -class EclProblemTPFA : public GetPropType - , public EclGenericProblem, - GetPropType, - GetPropType> -{ - using ParentType = GetPropType; - using Implementation = GetPropType; - - using Scalar = GetPropType; - using GridView = GetPropType; - using Stencil = GetPropType; - using FluidSystem = GetPropType; - using GlobalEqVector = GetPropType; - using EqVector = GetPropType; - using Vanguard = GetPropType; - - // Grid and world dimension - enum { dim = GridView::dimension }; - enum { dimWorld = GridView::dimensionworld }; - - // copy some indices for convenience - enum { numEq = getPropValue() }; - enum { numPhases = FluidSystem::numPhases }; - enum { numComponents = FluidSystem::numComponents }; - enum { enableExperiments = getPropValue() }; - enum { enableSolvent = getPropValue() }; - enum { enablePolymer = getPropValue() }; - enum { enableBrine = getPropValue() }; - enum { enableSaltPrecipitation = getPropValue() }; - enum { enablePolymerMolarWeight = getPropValue() }; - enum { enableFoam = getPropValue() }; - enum { enableExtbo = getPropValue() }; - enum { enableTemperature = getPropValue() }; - enum { enableEnergy = getPropValue() }; - enum { enableDiffusion = getPropValue() }; - enum { enableThermalFluxBoundaries = getPropValue() }; - enum { enableApiTracking = getPropValue() }; - enum { enableMICP = getPropValue() }; - enum { gasPhaseIdx = FluidSystem::gasPhaseIdx }; - enum { oilPhaseIdx = FluidSystem::oilPhaseIdx }; - enum { waterPhaseIdx = FluidSystem::waterPhaseIdx }; - enum { gasCompIdx = FluidSystem::gasCompIdx }; - enum { oilCompIdx = FluidSystem::oilCompIdx }; - enum { waterCompIdx = FluidSystem::waterCompIdx }; - - using PrimaryVariables = GetPropType; - using RateVector = GetPropType; - using BoundaryRateVector = GetPropType; - using Simulator = GetPropType; - using Element = typename GridView::template Codim<0>::Entity; - using ElementContext = GetPropType; - using EclMaterialLawManager = typename GetProp::EclMaterialLawManager; - using EclThermalLawManager = typename GetProp::EclThermalLawManager; - using MaterialLawParams = typename EclMaterialLawManager::MaterialLawParams; - using SolidEnergyLawParams = typename EclThermalLawManager::SolidEnergyLawParams; - using ThermalConductionLawParams = typename EclThermalLawManager::ThermalConductionLawParams; - using MaterialLaw = GetPropType; - using DofMapper = GetPropType; - using Evaluation = GetPropType; - using Indices = GetPropType; - using IntensiveQuantities = GetPropType; - using EclWellModel = GetPropType; - using EclAquiferModel = GetPropType; - - using SolventModule = BlackOilSolventModule; - using PolymerModule = BlackOilPolymerModule; - using FoamModule = BlackOilFoamModule; - using BrineModule = BlackOilBrineModule; - using ExtboModule = BlackOilExtboModule; - using MICPModule= BlackOilMICPModule; - - using InitialFluidState = typename EclEquilInitializer::ScalarFluidState; - - using Toolbox = MathToolbox; - using DimMatrix = Dune::FieldMatrix; - - using EclWriterType = EclWriter; - - using TracerModel = EclTracerModel; - -public: - using EclGenericProblem::briefDescription; - using EclGenericProblem::helpPreamble; - using EclGenericProblem::shouldWriteOutput; - using EclGenericProblem::shouldWriteRestartFile; - using EclGenericProblem::maxTimeIntegrationFailures; - using EclGenericProblem::minTimeStepSize; - - /*! - * \copydoc FvBaseProblem::registerParameters - */ - static void registerParameters() - { - ParentType::registerParameters(); - EclWriterType::registerParameters(); - VtkEclTracerModule::registerParameters(); - - EWOMS_REGISTER_PARAM(TypeTag, bool, EnableWriteAllSolutions, - "Write all solutions to disk instead of only the ones for the " - "report steps"); - EWOMS_REGISTER_PARAM(TypeTag, bool, EnableEclOutput, - "Write binary output which is compatible with the commercial " - "Eclipse simulator"); - EWOMS_REGISTER_PARAM(TypeTag, bool, EclOutputDoublePrecision, - "Tell the output writer to use double precision. Useful for 'perfect' restarts"); - EWOMS_REGISTER_PARAM(TypeTag, unsigned, RestartWritingInterval, - "The frequencies of which time steps are serialized to disk"); - EWOMS_REGISTER_PARAM(TypeTag, bool, EclEnableDriftCompensation, - "Enable partial compensation of systematic mass losses via the source term of the next time step"); - if constexpr (enableExperiments) - EWOMS_REGISTER_PARAM(TypeTag, bool, EclEnableAquifers, - "Enable analytic and numeric aquifer models"); - EWOMS_REGISTER_PARAM(TypeTag, Scalar, EclMaxTimeStepSizeAfterWellEvent, - "Maximum time step size after an well event"); - EWOMS_REGISTER_PARAM(TypeTag, Scalar, EclRestartShrinkFactor, - "Factor by which the time step is reduced after convergence failure"); - EWOMS_REGISTER_PARAM(TypeTag, bool, EclEnableTuning, - "Honor some aspects of the TUNING keyword from the ECL deck."); - EWOMS_REGISTER_PARAM(TypeTag, std::string, OutputMode, - "Specify which messages are going to be printed. Valid values are: none, log, all (default)"); - - } - - /*! - * \copydoc FvBaseProblem::prepareOutputDir - */ - std::string prepareOutputDir() const - { return this->simulator().vanguard().eclState().getIOConfig().getOutputDir(); } - - /*! - * \copydoc FvBaseProblem::handlePositionalParameter - */ - static int handlePositionalParameter(std::set& seenParams, - std::string& errorMsg, - int, - const char** argv, - int paramIdx, - int) - { - using ParamsMeta = GetProp; - Dune::ParameterTree& tree = ParamsMeta::tree(); - - std::string param = argv[paramIdx]; - size_t i = param.find('='); - if (i != std::string::npos) { - std::string oldParamName = param.substr(0, i); - std::string oldParamValue = param.substr(i+1); - std::string newParamName = "--" + oldParamName; - for (size_t j = 0; j < newParamName.size(); ++j) - if (newParamName[j] == '_') - newParamName[j] = '-'; - errorMsg = - "The old syntax to specify parameters on the command line is no longer supported: " - "Try replacing '"+oldParamName+"="+oldParamValue+"' with "+ - "'"+newParamName+"="+oldParamValue+"'!"; - return 0; - } - - if (seenParams.count("EclDeckFileName") > 0) { - errorMsg = - "Parameter 'EclDeckFileName' specified multiple times" - " as a command line parameter"; - return 0; - } - - tree["EclDeckFileName"] = argv[paramIdx]; - seenParams.insert("EclDeckFileName"); - return 1; - } - - /*! - * \copydoc Doxygen::defaultProblemConstructor - */ - EclProblemTPFA(Simulator& simulator) - : ParentType(simulator) - , EclGenericProblem(simulator.vanguard().eclState(), - simulator.vanguard().schedule(), - simulator.vanguard().gridView()) - , transmissibilities_(simulator.vanguard().eclState(), - simulator.vanguard().gridView(), - simulator.vanguard().cartesianIndexMapper(), - simulator.vanguard().grid(), - simulator.vanguard().cellCentroids(), - enableEnergy, - enableDiffusion) - , thresholdPressures_(simulator) - , wellModel_(simulator) - , aquiferModel_(simulator) - , pffDofData_(simulator.gridView(), this->elementMapper()) - , tracerModel_(simulator) - { - this->model().addOutputModule(new VtkEclTracerModule(simulator)); - // Tell the black-oil extensions to initialize their internal data structures - const auto& vanguard = simulator.vanguard(); - SolventModule::initFromState(vanguard.eclState(), vanguard.schedule()); - PolymerModule::initFromState(vanguard.eclState()); - FoamModule::initFromState(vanguard.eclState()); - BrineModule::initFromState(vanguard.eclState()); - ExtboModule::initFromState(vanguard.eclState()); - MICPModule::initFromState(vanguard.eclState()); - - // create the ECL writer - eclWriter_.reset(new EclWriterType(simulator)); - - enableDriftCompensation_ = EWOMS_GET_PARAM(TypeTag, bool, EclEnableDriftCompensation); - - enableEclOutput_ = EWOMS_GET_PARAM(TypeTag, bool, EnableEclOutput); - - if constexpr (enableExperiments) - enableAquifers_ = EWOMS_GET_PARAM(TypeTag, bool, EclEnableAquifers); - else - enableAquifers_ = true; - - this->enableTuning_ = EWOMS_GET_PARAM(TypeTag, bool, EclEnableTuning); - this->initialTimeStepSize_ = EWOMS_GET_PARAM(TypeTag, Scalar, InitialTimeStepSize); - this->minTimeStepSize_ = EWOMS_GET_PARAM(TypeTag, Scalar, MinTimeStepSize); - this->maxTimeStepSize_ = EWOMS_GET_PARAM(TypeTag, Scalar, MaxTimeStepSize); - this->maxTimeStepAfterWellEvent_ = EWOMS_GET_PARAM(TypeTag, Scalar, EclMaxTimeStepSizeAfterWellEvent); - this->restartShrinkFactor_ = EWOMS_GET_PARAM(TypeTag, Scalar, EclRestartShrinkFactor); - this->maxFails_ = EWOMS_GET_PARAM(TypeTag, unsigned, MaxTimeStepDivisions); - - RelpermDiagnostics relpermDiagnostics; - relpermDiagnostics.diagnosis(vanguard.eclState(), vanguard.cartesianIndexMapper()); - } - - /*! - * \copydoc FvBaseProblem::finishInit - */ - void finishInit() - { - ParentType::finishInit(); - - auto& simulator = this->simulator(); - const auto& eclState = simulator.vanguard().eclState(); - const auto& schedule = simulator.vanguard().schedule(); - - // Set the start time of the simulation - simulator.setStartTime(schedule.getStartTime()); - simulator.setEndTime(schedule.simTime(schedule.size() - 1)); - - // We want the episode index to be the same as the report step index to make - // things simpler, so we have to set the episode index to -1 because it is - // incremented by endEpisode(). The size of the initial time step and - // length of the initial episode is set to zero for the same reason. - simulator.setEpisodeIndex(-1); - simulator.setEpisodeLength(0.0); - - // the "NOGRAV" keyword from Frontsim or setting the EnableGravity to false - // disables gravity, else the standard value of the gravity constant at sea level - // on earth is used - this->gravity_ = 0.0; - if (EWOMS_GET_PARAM(TypeTag, bool, EnableGravity)) - this->gravity_[dim - 1] = 9.80665; - if (!eclState.getInitConfig().hasGravity()) - this->gravity_[dim - 1] = 0.0; - - if (this->enableTuning_) { - // if support for the TUNING keyword is enabled, we get the initial time - // steping parameters from it instead of from command line parameters - const auto& tuning = schedule[0].tuning(); - this->initialTimeStepSize_ = tuning.TSINIT; - this->maxTimeStepAfterWellEvent_ = tuning.TMAXWC; - this->maxTimeStepSize_ = tuning.TSMAXZ; - this->restartShrinkFactor_ = 1./tuning.TSFCNV; - this->minTimeStepSize_ = tuning.TSMINZ; - } - - this->initFluidSystem_(); - - // deal with DRSDT - this->initDRSDT_(this->model().numGridDof(), this->episodeIndex()); - - this->readRockParameters_(simulator.vanguard().cellCenterDepths()); - readMaterialParameters_(); - readThermalParameters_(); - transmissibilities_.finishInit(); - - const auto& initconfig = eclState.getInitConfig(); - tracerModel_.init(initconfig.restartRequested()); - if (initconfig.restartRequested()) - readEclRestartSolution_(); - else - readInitialCondition_(); - tracerModel_.prepareTracerBatches(); - - updatePffDofData_(); - - if constexpr (getPropValue()) { - const auto& vanguard = this->simulator().vanguard(); - const auto& gridView = vanguard.gridView(); - int numElements = gridView.size(/*codim=*/0); - this->maxPolymerAdsorption_.resize(numElements, 0.0); - } - - readBoundaryConditions_(); - - if (enableDriftCompensation_) { - drift_.resize(this->model().numGridDof()); - drift_ = 0.0; - } - - if constexpr (enableExperiments) - { - int success = 1; - const auto& cc = simulator.vanguard().grid().comm(); - - try - { - // Only rank 0 has the deck and hence can do the checks! - if (cc.rank() == 0) - this->checkDeckCompatibility_(simulator.vanguard().deck(), - enableApiTracking, - enableSolvent, - enablePolymer, - enableExtbo, - enableEnergy, - Indices::numPhases, - Indices::gasEnabled, - Indices::oilEnabled, - Indices::waterEnabled, - enableMICP); - } - catch(const std::exception& e) - { - success = 0; - success = cc.min(success); - throw; - } - - success = cc.min(success); - - if (!success) - { - throw std::runtime_error("Checking deck compatibility failed"); - } - } - - // write the static output files (EGRID, INIT, SMSPEC, etc.) - if (enableEclOutput_) { - if (simulator.vanguard().grid().comm().size() > 1) { - if (simulator.vanguard().grid().comm().rank() == 0) - eclWriter_->setTransmissibilities(&simulator.vanguard().globalTransmissibility()); - } else - eclWriter_->setTransmissibilities(&simulator.problem().eclTransmissibilities()); - - eclWriter_->writeInit(); - } - - simulator.vanguard().releaseGlobalTransmissibilities(); - - // after finishing the initialization and writing the initial solution, we move - // to the first "real" episode/report step - // for restart the episode index and start is already set - if (!initconfig.restartRequested()) { - simulator.startNextEpisode(schedule.seconds(0)); - simulator.setEpisodeIndex(0); - } - } - - void prefetch(const Element& elem) const - { pffDofData_.prefetch(elem); } - - /*! - * \brief This method restores the complete state of the problem and its sub-objects - * from disk. - * - * The serialization format used by this method is ad-hoc. It is the inverse of the - * serialize() method. - * - * \tparam Restarter The deserializer type - * - * \param res The deserializer object - */ - template - void deserialize(Restarter& res) - { - // reload the current episode/report step from the deck - beginEpisode(); - - // deserialize the wells - wellModel_.deserialize(res); - - if (enableAquifers_) - // deserialize the aquifer - aquiferModel_.deserialize(res); - } - - /*! - * \brief This method writes the complete state of the problem and its subobjects to - * disk. - * - * The file format used here is ad-hoc. - */ - template - void serialize(Restarter& res) - { - wellModel_.serialize(res); - - if (enableAquifers_) - aquiferModel_.serialize(res); - } - - int episodeIndex() const - { - return std::max(this->simulator().episodeIndex(), 0); - } - - /*! - * \brief Called by the simulator before an episode begins. - */ - void beginEpisode() - { - // Proceed to the next report step - auto& simulator = this->simulator(); - int episodeIdx = simulator.episodeIndex(); - auto& eclState = simulator.vanguard().eclState(); - const auto& schedule = simulator.vanguard().schedule(); - const auto& events = schedule[episodeIdx].events(); - - if (episodeIdx >= 0 && events.hasEvent(ScheduleEvents::GEO_MODIFIER)) { - // bring the contents of the keywords to the current state of the SCHEDULE - // section. - // - // TODO (?): make grid topology changes possible (depending on what exactly - // has changed, the grid may need be re-created which has some serious - // implications on e.g., the solution of the simulation.) - const auto& miniDeck = schedule[episodeIdx].geo_keywords(); - const auto& cc = simulator.vanguard().grid().comm(); - eclState.apply_schedule_keywords( miniDeck ); - eclBroadcast(cc, eclState.getTransMult() ); - - // re-compute all quantities which may possibly be affected. - transmissibilities_.update(true); - this->referencePorosity_[1] = this->referencePorosity_[0]; - updateReferencePorosity_(); - updatePffDofData_(); - } - - bool tuningEvent = this->beginEpisode_(enableExperiments, this->episodeIndex()); - - // set up the wells for the next episode. - wellModel_.beginEpisode(); - - // set up the aquifers for the next episode. - if (enableAquifers_) - // set up the aquifers for the next episode. - aquiferModel_.beginEpisode(); - - // set the size of the initial time step of the episode - Scalar dt = limitNextTimeStepSize_(simulator.episodeLength()); - if (episodeIdx == 0 || tuningEvent) - // allow the size of the initial time step to be set via an external parameter - // if TUNING is enabled, also limit the time step size after a tuning event to TSINIT - dt = std::min(dt, this->initialTimeStepSize_); - simulator.setTimeStepSize(dt); - - // Evaluate UDQ assign statements to make sure the settings are - // available as UDA controls for the current report step. - const auto& udq = schedule[episodeIdx].udq(); - const auto& well_matcher = schedule.wellMatcher(episodeIdx); - auto& summary_state = simulator.vanguard().summaryState(); - auto& udq_state = simulator.vanguard().udqState(); - udq.eval_assign(episodeIdx, well_matcher, summary_state, udq_state); - } - - /*! - * \brief Called by the simulator before each time integration. - */ - void beginTimeStep() - { - int episodeIdx = this->episodeIndex(); - - this->beginTimeStep_(enableExperiments, - episodeIdx, - this->simulator().timeStepIndex(), - this->simulator().startTime(), - this->simulator().time(), - this->simulator().timeStepSize(), - this->simulator().endTime()); - - // update maximum water saturation and minimum pressure - // used when ROCKCOMP is activated - const bool invalidateFromMaxWaterSat = updateMaxWaterSaturation_(); - const bool invalidateFromMinPressure = updateMinPressure_(); - - // update hysteresis and max oil saturation used in vappars - const bool invalidateFromHyst = updateHysteresis_(); - const bool invalidateFromMaxOilSat = updateMaxOilSaturation_(); - - // the derivatives may have change - bool invalidateIntensiveQuantities = invalidateFromMaxWaterSat || invalidateFromMinPressure || invalidateFromHyst || invalidateFromMaxOilSat; - if (invalidateIntensiveQuantities) - this->model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0); - - if constexpr (getPropValue()) - updateMaxPolymerAdsorption_(); - - wellModel_.beginTimeStep(); - if (enableAquifers_) - aquiferModel_.beginTimeStep(); - tracerModel_.beginTimeStep(); - - } - - /*! - * \brief Called by the simulator before each Newton-Raphson iteration. - */ - void beginIteration() - { - wellModel_.beginIteration(); - if (enableAquifers_) - aquiferModel_.beginIteration(); - } - - /*! - * \brief Called by the simulator after each Newton-Raphson iteration. - */ - void endIteration() - { - wellModel_.endIteration(); - if (enableAquifers_) - aquiferModel_.endIteration(); - } - - /*! - * \brief Called by the simulator after each time integration. - */ - void endTimeStep() - { -#ifndef NDEBUG - if constexpr (getPropValue()) { - // in debug mode, we don't care about performance, so we check if the model does - // the right thing (i.e., the mass change inside the whole reservoir must be - // equivalent to the fluxes over the grid's boundaries plus the source rates - // specified by the problem) - int rank = this->simulator().gridView().comm().rank(); - if (rank == 0) - std::cout << "checking conservativeness of solution\n"; - this->model().checkConservativeness(/*tolerance=*/-1, /*verbose=*/true); - if (rank == 0) - std::cout << "solution is sufficiently conservative\n"; - } -#endif // NDEBUG - - auto& simulator = this->simulator(); - wellModel_.endTimeStep(); - if (enableAquifers_) - aquiferModel_.endTimeStep(); - tracerModel_.endTimeStep(); - - // deal with DRSDT and DRVDT - updateCompositionChangeLimits_(); - - if (enableDriftCompensation_) { - const auto& residual = this->model().linearizer().residual(); - for (unsigned globalDofIdx = 0; globalDofIdx < residual.size(); globalDofIdx ++) { - drift_[globalDofIdx] = residual[globalDofIdx]; - drift_[globalDofIdx] *= simulator.timeStepSize(); - if constexpr (getPropValue()) - drift_[globalDofIdx] *= this->model().dofTotalVolume(globalDofIdx); - } - } - - bool isSubStep = !EWOMS_GET_PARAM(TypeTag, bool, EnableWriteAllSolutions) && !this->simulator().episodeWillBeOver(); - eclWriter_->evalSummaryState(isSubStep); - - auto& schedule = simulator.vanguard().schedule(); - auto& ecl_state = simulator.vanguard().eclState(); - int episodeIdx = this->episodeIndex(); - this->applyActions(episodeIdx, - simulator.time() + simulator.timeStepSize(), - simulator.vanguard().grid().comm(), - ecl_state, - schedule, - simulator.vanguard().actionState(), - simulator.vanguard().summaryState()); - - // deal with "clogging" for the MICP model - if constexpr (enableMICP){ - auto& model = this->model(); - const auto& residual = this->model().linearizer().residual(); - for (unsigned globalDofIdx = 0; globalDofIdx < residual.size(); globalDofIdx ++) { - auto& phi = this->referencePorosity_[/*timeIdx=*/1][globalDofIdx]; - MICPModule::checkCloggingMICP(model, phi, globalDofIdx); - } - } - } - - /*! - * \brief Called by the simulator after the end of an episode. - */ - void endEpisode() - { - auto& simulator = this->simulator(); - auto& schedule = simulator.vanguard().schedule(); - - wellModel_.endEpisode(); - if (enableAquifers_) - aquiferModel_.endEpisode(); - - int episodeIdx = this->episodeIndex(); - // check if we're finished ... - if (episodeIdx + 1 >= static_cast(schedule.size() - 1)) { - simulator.setFinished(true); - return; - } - - // .. if we're not yet done, start the next episode (report step) - simulator.startNextEpisode(schedule.stepLength(episodeIdx + 1)); - } - - /*! - * \brief Write the requested quantities of the current solution into the output - * files. - */ - void writeOutput(bool verbose = true) - { - // use the generic code to prepare the output fields and to - // write the desired VTK files. - ParentType::writeOutput(verbose); - - bool isSubStep = !EWOMS_GET_PARAM(TypeTag, bool, EnableWriteAllSolutions) && !this->simulator().episodeWillBeOver(); - if (enableEclOutput_) - eclWriter_->writeOutput(isSubStep); - } - - void finalizeOutput() { - // this will write all pending output to disk - // to avoid corruption of output files - eclWriter_.reset(); - } - - - std::unordered_map fetchWellPI(int reportStep, - const Action::ActionX& action, - const Schedule& schedule, - const std::vector& matching_wells) { - - auto wellpi_wells = action.wellpi_wells(WellMatcher(schedule[reportStep].well_order(), - schedule[reportStep].wlist_manager()), - matching_wells); - - if (wellpi_wells.empty()) - return {}; - - const auto num_wells = schedule[reportStep].well_order().size(); - std::vector wellpi_vector(num_wells); - for (const auto& wname : wellpi_wells) { - if (this->wellModel_.hasWell(wname)) { - const auto& well = schedule.getWell( wname, reportStep ); - wellpi_vector[well.seqIndex()] = this->wellModel_.wellPI(wname); - } - } - - const auto& comm = this->simulator().vanguard().grid().comm(); - if (comm.size() > 1) { - std::vector wellpi_buffer(num_wells * comm.size()); - comm.gather( wellpi_vector.data(), wellpi_buffer.data(), num_wells, 0 ); - if (comm.rank() == 0) { - for (int rank=1; rank < comm.size(); rank++) { - for (std::size_t well_index=0; well_index < num_wells; well_index++) { - const auto global_index = rank*num_wells + well_index; - const auto value = wellpi_buffer[global_index]; - if (value != 0) - wellpi_vector[well_index] = value; - } - } - } - comm.broadcast(wellpi_vector.data(), wellpi_vector.size(), 0); - } - - std::unordered_map wellpi; - for (const auto& wname : wellpi_wells) { - const auto& well = schedule.getWell( wname, reportStep ); - wellpi[wname] = wellpi_vector[ well.seqIndex() ]; - } - return wellpi; - } - - - - /* - This function is run after applyAction has been completed in the Schedule - implementation. The sim_update argument should have members & flags for - the simulator properties which need to be updated. This functionality is - probably not complete. - */ - void applySimulatorUpdate(int report_step, Parallel::Communication comm, const SimulatorUpdate& sim_update, EclipseState& ecl_state, Schedule& schedule, SummaryState& summary_state, bool& commit_wellstate) { - this->wellModel_.updateEclWells(report_step, sim_update.affected_wells, summary_state); - if (!sim_update.affected_wells.empty()) - commit_wellstate = true; - - if (sim_update.tran_update) { - const auto& keywords = schedule[report_step].geo_keywords(); - ecl_state.apply_schedule_keywords( keywords ); - eclBroadcast(comm, ecl_state.getTransMult() ); - - // re-compute transmissibility - transmissibilities_.update(true); - } - - } - - - void applyActions(int reportStep, - double sim_time, - Parallel::Communication comm, - EclipseState& ecl_state, - Schedule& schedule, - Action::State& actionState, - SummaryState& summaryState) { - const auto& actions = schedule[reportStep].actions(); - if (actions.empty()) - return; - - Action::Context context( summaryState, schedule[reportStep].wlist_manager() ); - auto now = TimeStampUTC( schedule.getStartTime() ) + std::chrono::duration(sim_time); - std::string ts; - { - std::ostringstream os; - os << std::setw(4) << std::to_string(now.year()) << '/' - << std::setw(2) << std::setfill('0') << std::to_string(now.month()) << '/' - << std::setw(2) << std::setfill('0') << std::to_string(now.day()) << " report:" << std::to_string(reportStep); - - ts = os.str(); - } - - bool commit_wellstate = false; - for (const auto& pyaction : actions.pending_python(actionState)) { - auto sim_update = schedule.runPyAction(reportStep, *pyaction, actionState, ecl_state, summaryState); - this->applySimulatorUpdate(reportStep, comm, sim_update, ecl_state, schedule, summaryState, commit_wellstate); - } - - auto simTime = asTimeT(now); - for (const auto& action : actions.pending(actionState, simTime)) { - auto actionResult = action->eval(context); - if (actionResult) { - std::string wells_string; - const auto& matching_wells = actionResult.wells(); - if (!matching_wells.empty()) { - for (std::size_t iw = 0; iw < matching_wells.size() - 1; iw++) - wells_string += matching_wells[iw] + ", "; - wells_string += matching_wells.back(); - } - std::string msg = "The action: " + action->name() + " evaluated to true at " + ts + " wells: " + wells_string; - OpmLog::info(msg); - - const auto& wellpi = this->fetchWellPI(reportStep, *action, schedule, matching_wells); - - auto sim_update = schedule.applyAction(reportStep, *action, actionResult.wells(), wellpi); - this->applySimulatorUpdate(reportStep, comm, sim_update, ecl_state, schedule, summaryState, commit_wellstate); - actionState.add_run(*action, simTime, std::move(actionResult)); - } else { - std::string msg = "The action: " + action->name() + " evaluated to false at " + ts; - OpmLog::info(msg); - } - } - /* - The well state has been stored in a previous object when the time step - has completed successfully, the action process might have modified the - well state, and to be certain that is not overwritten when starting - the next timestep we must commit it. - */ - if (commit_wellstate) - this->wellModel_.commitWGState(); - } - - - /*! - * \copydoc FvBaseMultiPhaseProblem::intrinsicPermeability - */ - template - const DimMatrix& intrinsicPermeability(const Context& context, - unsigned spaceIdx, - unsigned timeIdx) const - { - unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); - return transmissibilities_.permeability(globalSpaceIdx); - } - - /*! - * \brief This method returns the intrinsic permeability tensor - * given a global element index. - * - * Its main (only?) usage is the ECL transmissibility calculation code... - */ - const DimMatrix& intrinsicPermeability(unsigned globalElemIdx) const - { return transmissibilities_.permeability(globalElemIdx); } - - /*! - * \copydoc EclTransmissiblity::transmissibility - */ - template - Scalar transmissibility(const Context& context, - [[maybe_unused]] unsigned fromDofLocalIdx, - unsigned toDofLocalIdx) const - { - assert(fromDofLocalIdx == 0); - return pffDofData_.get(context.element(), toDofLocalIdx).transmissibility; - } - - Scalar transmissibility(unsigned globalCenterElemIdx, unsigned globalElemIdx) const - { - return transmissibilities_.transmissibility(globalCenterElemIdx, globalElemIdx); - } - - /*! - * \copydoc EclTransmissiblity::diffusivity - */ - template - Scalar diffusivity(const Context& context, - [[maybe_unused]] unsigned fromDofLocalIdx, - unsigned toDofLocalIdx) const - { - assert(fromDofLocalIdx == 0); - return *pffDofData_.get(context.element(), toDofLocalIdx).diffusivity; - } - - /*! - * \copydoc EclTransmissiblity::transmissibilityBoundary - */ - template - Scalar transmissibilityBoundary(const Context& elemCtx, - unsigned boundaryFaceIdx) const - { - unsigned elemIdx = elemCtx.globalSpaceIndex(/*dofIdx=*/0, /*timeIdx=*/0); - return transmissibilities_.transmissibilityBoundary(elemIdx, boundaryFaceIdx); - } - - /*! - * \copydoc EclTransmissiblity::thermalHalfTransmissibility - */ - template - Scalar thermalHalfTransmissibilityIn(const Context& context, - unsigned faceIdx, - unsigned timeIdx) const - { - const auto& face = context.stencil(timeIdx).interiorFace(faceIdx); - unsigned toDofLocalIdx = face.exteriorIndex(); - return *pffDofData_.get(context.element(), toDofLocalIdx).thermalHalfTransIn; - } - - /*! - * \copydoc EclTransmissiblity::thermalHalfTransmissibility - */ - template - Scalar thermalHalfTransmissibilityOut(const Context& context, - unsigned faceIdx, - unsigned timeIdx) const - { - const auto& face = context.stencil(timeIdx).interiorFace(faceIdx); - unsigned toDofLocalIdx = face.exteriorIndex(); - return *pffDofData_.get(context.element(), toDofLocalIdx).thermalHalfTransOut; - } - - /*! - * \copydoc EclTransmissiblity::thermalHalfTransmissibility - */ - template - Scalar thermalHalfTransmissibilityBoundary(const Context& elemCtx, - unsigned boundaryFaceIdx) const - { - unsigned elemIdx = elemCtx.globalSpaceIndex(/*dofIdx=*/0, /*timeIdx=*/0); - return transmissibilities_.thermalHalfTransBoundary(elemIdx, boundaryFaceIdx); - } - - /*! - * \brief Return a reference to the object that handles the "raw" transmissibilities. - */ - const typename Vanguard::TransmissibilityType& eclTransmissibilities() const - { return transmissibilities_; } - - /*! - * \copydoc BlackOilBaseProblem::thresholdPressure - */ - Scalar thresholdPressure(unsigned elem1Idx, unsigned elem2Idx) const - { return thresholdPressures_.thresholdPressure(elem1Idx, elem2Idx); } - - const EclThresholdPressure& thresholdPressure() const - { return thresholdPressures_; } - - EclThresholdPressure& thresholdPressure() - { return thresholdPressures_; } - - const EclTracerModel& tracerModel() const - { return tracerModel_; } - - EclTracerModel& tracerModel() - { return tracerModel_; } - - /*! - * \copydoc FvBaseMultiPhaseProblem::porosity - * - * For the EclProblemTPFA, this method is identical to referencePorosity(). The intensive - * quantities object may apply various multipliers (e.g. ones which model rock - * compressibility and water induced rock compaction) to it which depend on the - * current physical conditions. - */ - template - Scalar porosity(const Context& context, unsigned spaceIdx, unsigned timeIdx) const - { - unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); - return this->porosity(globalSpaceIdx, timeIdx); - } - - Scalar porosity(unsigned globalSpaceIdx, unsigned timeIdx) const - { - return this->referencePorosity_[timeIdx][globalSpaceIdx]; - } - - /*! - * \brief Returns the depth of an degree of freedom [m] - * - * For ECL problems this is defined as the average of the depth of an element and is - * thus slightly different from the depth of an element's centroid. - */ - template - Scalar dofCenterDepth(const Context& context, unsigned spaceIdx, unsigned timeIdx) const - { - unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); - return this->dofCenterDepth(globalSpaceIdx); - } - - Scalar dofCenterDepth(unsigned globalSpaceIdx) const - { - return this->simulator().vanguard().cellCenterDepth(globalSpaceIdx); - } - - - /*! - * \copydoc BlackoilProblem::rockCompressibility - */ - template - Scalar rockCompressibility(const Context& context, unsigned spaceIdx, unsigned timeIdx) const - { - if (this->rockParams_.empty()) - return 0.0; - - unsigned tableIdx = 0; - if (!this->rockTableIdx_.empty()) { - unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); - tableIdx = this->rockTableIdx_[globalSpaceIdx]; - } - - return this->rockParams_[tableIdx].compressibility; - } - - - Scalar rockCompressibility(unsigned globalSpaceIdx) const - { - if (this->rockParams_.empty()) - return 0.0; - - unsigned tableIdx = 0; - if (!this->rockTableIdx_.empty()) { - tableIdx = this->rockTableIdx_[globalSpaceIdx]; - } - return this->rockParams_[tableIdx].compressibility; - } - - /*! - * \copydoc BlackoilProblem::rockReferencePressure - */ - template - Scalar rockReferencePressure(const Context& context, unsigned spaceIdx, unsigned timeIdx) const - { - if (this->rockParams_.empty()) - return 1e5; - - unsigned tableIdx = 0; - if (!this->rockTableIdx_.empty()) { - unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); - tableIdx = this->rockTableIdx_[globalSpaceIdx]; - } - return this->rockParams_[tableIdx].referencePressure; - } - - Scalar rockReferencePressure(unsigned globalSpaceIdx) const - { - if (this->rockParams_.empty()) - return 1e5; - - unsigned tableIdx = 0; - if (!this->rockTableIdx_.empty()) { - tableIdx = this->rockTableIdx_[globalSpaceIdx]; - } - return this->rockParams_[tableIdx].referencePressure; - } - - /*! - * \copydoc FvBaseMultiPhaseProblem::materialLawParams - */ - template - const MaterialLawParams& materialLawParams(const Context& context, - unsigned spaceIdx, unsigned timeIdx) const - { - unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); - return this->materialLawParams(globalSpaceIdx); - } - - const MaterialLawParams& materialLawParams(unsigned globalDofIdx) const - { return materialLawManager_->materialLawParams(globalDofIdx); } - - /*! - * \brief Return the parameters for the energy storage law of the rock - */ - template - const SolidEnergyLawParams& - solidEnergyLawParams(const Context& context, - unsigned spaceIdx, - unsigned timeIdx) const - { - unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); - return thermalLawManager_->solidEnergyLawParams(globalSpaceIdx); - } - - /*! - * \copydoc FvBaseMultiPhaseProblem::thermalConductionParams - */ - template - const ThermalConductionLawParams & - thermalConductionLawParams(const Context& context, unsigned spaceIdx, unsigned timeIdx) const - { - unsigned globalSpaceIdx = context.globalSpaceIndex(spaceIdx, timeIdx); - return thermalLawManager_->thermalConductionLawParams(globalSpaceIdx); - } - - /*! - * \brief Returns the ECL material law manager - * - * Note that this method is *not* part of the generic eWoms problem API because it - * would force all problens use the ECL material laws. - */ - std::shared_ptr materialLawManager() const - { return materialLawManager_; } - - /*! - * \copydoc materialLawManager() - */ - std::shared_ptr materialLawManager() - { return materialLawManager_; } - - using EclGenericProblem::pvtRegionIndex; - /*! - * \brief Returns the index of the relevant region for thermodynmic properties - */ - template - unsigned pvtRegionIndex(const Context& context, unsigned spaceIdx, unsigned timeIdx) const - { return pvtRegionIndex(context.globalSpaceIndex(spaceIdx, timeIdx)); } - - using EclGenericProblem::satnumRegionIndex; - /*! - * \brief Returns the index of the relevant region for thermodynmic properties - */ - template - unsigned satnumRegionIndex(const Context& context, unsigned spaceIdx, unsigned timeIdx) const - { return this->satnumRegionIndex(context.globalSpaceIndex(spaceIdx, timeIdx)); } - - using EclGenericProblem::miscnumRegionIndex; - /*! - * \brief Returns the index of the relevant region for thermodynmic properties - */ - template - unsigned miscnumRegionIndex(const Context& context, unsigned spaceIdx, unsigned timeIdx) const - { return this->miscnumRegionIndex(context.globalSpaceIndex(spaceIdx, timeIdx)); } - - using EclGenericProblem::plmixnumRegionIndex; - /*! - * \brief Returns the index of the relevant region for thermodynmic properties - */ - template - unsigned plmixnumRegionIndex(const Context& context, unsigned spaceIdx, unsigned timeIdx) const - { return this->plmixnumRegionIndex(context.globalSpaceIndex(spaceIdx, timeIdx)); } - - using EclGenericProblem::maxPolymerAdsorption; - /*! - * \brief Returns the max polymer adsorption value - */ - template - Scalar maxPolymerAdsorption(const Context& context, unsigned spaceIdx, unsigned timeIdx) const - { return this->maxPolymerAdsorption(context.globalSpaceIndex(spaceIdx, timeIdx)); } - - /*! - * \copydoc FvBaseProblem::name - */ - std::string name() const - { return this->simulator().vanguard().caseName(); } - - /*! - * \copydoc FvBaseMultiPhaseProblem::temperature - */ - template - Scalar temperature(const Context& context, unsigned spaceIdx, unsigned timeIdx) const - { - // use the initial temperature of the DOF if temperature is not a primary - // variable - unsigned globalDofIdx = context.globalSpaceIndex(spaceIdx, timeIdx); - return initialFluidStates_[globalDofIdx].temperature(/*phaseIdx=*/0); - } - - /*! - * \copydoc FvBaseProblem::boundary - * - * ECLiPSE uses no-flow conditions for all boundaries. \todo really? - */ - template - void boundary(BoundaryRateVector& values, - const Context& context, - unsigned spaceIdx, - unsigned timeIdx) const - { - if(!context.intersection(spaceIdx).boundary()) - return; - - if constexpr (!enableEnergy || !enableThermalFluxBoundaries) - values.setNoFlow(); - else { - // in the energy case we need to specify a non-trivial boundary condition - // because the geothermal gradient needs to be maintained. for this, we - // simply assume the initial temperature at the boundary and specify the - // thermal flow accordingly. in this context, "thermal flow" means energy - // flow due to a temerature gradient while assuming no-flow for mass - unsigned interiorDofIdx = context.interiorScvIndex(spaceIdx, timeIdx); - unsigned globalDofIdx = context.globalSpaceIndex(interiorDofIdx, timeIdx); - values.setThermalFlow(context, spaceIdx, timeIdx, initialFluidStates_[globalDofIdx]); - } - - if (nonTrivialBoundaryConditions()) { - unsigned indexInInside = context.intersection(spaceIdx).indexInInside(); - unsigned interiorDofIdx = context.interiorScvIndex(spaceIdx, timeIdx); - unsigned globalDofIdx = context.globalSpaceIndex(interiorDofIdx, timeIdx); - unsigned pvtRegionIdx = pvtRegionIndex(context, spaceIdx, timeIdx); - switch (indexInInside) { - case 0: - if (freebcXMinus_[globalDofIdx]) - values.setFreeFlow(context, spaceIdx, timeIdx, initialFluidStates_[globalDofIdx]); - else - values.setMassRate(massratebcXMinus_[globalDofIdx], pvtRegionIdx); - break; - case 1: - if (freebcX_[globalDofIdx]) - values.setFreeFlow(context, spaceIdx, timeIdx, initialFluidStates_[globalDofIdx]); - else - values.setMassRate(massratebcX_[globalDofIdx], pvtRegionIdx); - break; - case 2: - if (freebcYMinus_[globalDofIdx]) - values.setFreeFlow(context, spaceIdx, timeIdx, initialFluidStates_[globalDofIdx]); - else - values.setMassRate(massratebcYMinus_[globalDofIdx], pvtRegionIdx); - break; - case 3: - if (freebcY_[globalDofIdx]) - values.setFreeFlow(context, spaceIdx, timeIdx, initialFluidStates_[globalDofIdx]); - else - values.setMassRate(massratebcY_[globalDofIdx], pvtRegionIdx); - break; - case 4: - if (freebcZMinus_[globalDofIdx]) - values.setFreeFlow(context, spaceIdx, timeIdx, initialFluidStates_[globalDofIdx]); - else - values.setMassRate(massratebcZMinus_[globalDofIdx], pvtRegionIdx); - break; - case 5: - if (freebcZ_[globalDofIdx]) - values.setFreeFlow(context, spaceIdx, timeIdx, initialFluidStates_[globalDofIdx]); - else - values.setMassRate(massratebcZ_[globalDofIdx], pvtRegionIdx); - break; - default: - throw std::logic_error("invalid face index for boundary condition"); - - } - } - } - - /*! - * \brief Returns an element's historic maximum oil phase saturation that was - * observed during the simulation. - * - * In this context, "historic" means the the time before the current timestep began. - * - * This is a bit of a hack from the conceptional point of view, but it is required to - * match the results of the 'flow' and ECLIPSE 100 simulators. - */ - Scalar maxOilSaturation(unsigned globalDofIdx) const - { - if (!this->vapparsActive(this->episodeIndex())) - return 0.0; - - return this->maxOilSaturation_[globalDofIdx]; - } - - /*! - * \brief Sets an element's maximum oil phase saturation observed during the - * simulation. - * - * In this context, "historic" means the the time before the current timestep began. - * - * This a hack on top of the maxOilSaturation() hack but it is currently required to - * do restart externally. i.e. from the flow code. - */ - void setMaxOilSaturation(unsigned globalDofIdx, Scalar value) - { - if (!this->vapparsActive(this->episodeIndex())) - return; - - this->maxOilSaturation_[globalDofIdx] = value; - } - - /*! - * \brief Returns the maximum value of the gas dissolution factor at the current time - * for a given degree of freedom. - */ - Scalar maxGasDissolutionFactor(unsigned timeIdx, unsigned globalDofIdx) const - { - int pvtRegionIdx = this->pvtRegionIndex(globalDofIdx); - int episodeIdx = this->episodeIndex(); - if (!this->drsdtActive_(episodeIdx) || this->maxDRs_[pvtRegionIdx] < 0.0) - return std::numeric_limits::max()/2.0; - - Scalar scaling = 1.0; - if (this->drsdtConvective_(episodeIdx)) { - scaling = this->convectiveDrs_[globalDofIdx]; - } - - // this is a bit hacky because it assumes that a time discretization with only - // two time indices is used. - if (timeIdx == 0) - return this->lastRs_[globalDofIdx] + this->maxDRs_[pvtRegionIdx] * scaling; - else - return this->lastRs_[globalDofIdx]; - } - - /*! - * \brief Returns the maximum value of the oil vaporization factor at the current - * time for a given degree of freedom. - */ - Scalar maxOilVaporizationFactor(unsigned timeIdx, unsigned globalDofIdx) const - { - int pvtRegionIdx = this->pvtRegionIndex(globalDofIdx); - int episodeIdx = this->episodeIndex(); - if (!this->drvdtActive_(episodeIdx) || this->maxDRv_[pvtRegionIdx] < 0.0) - return std::numeric_limits::max()/2.0; - - // this is a bit hacky because it assumes that a time discretization with only - // two time indices is used. - if (timeIdx == 0) - return this->lastRv_[globalDofIdx] + this->maxDRv_[pvtRegionIdx]; - else - return this->lastRv_[globalDofIdx]; - } - - /*! - * \brief Return if the storage term of the first iteration is identical to the storage - * term for the solution of the previous time step. - * - * For quite technical reasons, the storage term cannot be recycled if either DRSDT - * or DRVDT are active in ebos. Nor if the porosity is changes between timesteps - * using a pore volume multiplier (i.e., poreVolumeMultiplier() != 1.0) - */ - bool recycleFirstIterationStorage() const - { - int episodeIdx = this->episodeIndex(); - return !this->drsdtActive_(episodeIdx) && - !this->drvdtActive_(episodeIdx) && - this->rockCompPoroMultWc_.empty() && - this->rockCompPoroMult_.empty(); - } - - /*! - * \copydoc FvBaseProblem::initial - * - * The reservoir problem uses a constant boundary condition for - * the whole domain. - */ - template - void initial(PrimaryVariables& values, const Context& context, unsigned spaceIdx, unsigned timeIdx) const - { - unsigned globalDofIdx = context.globalSpaceIndex(spaceIdx, timeIdx); - - values.setPvtRegionIndex(pvtRegionIndex(context, spaceIdx, timeIdx)); - values.assignNaive(initialFluidStates_[globalDofIdx]); - - if constexpr (enableSolvent) - values[Indices::solventSaturationIdx] = this->solventSaturation_[globalDofIdx]; - - if constexpr (enablePolymer) - values[Indices::polymerConcentrationIdx] = this->polymerConcentration_[globalDofIdx]; - - if constexpr (enablePolymerMolarWeight) - values[Indices::polymerMoleWeightIdx]= this->polymerMoleWeight_[globalDofIdx]; - - if constexpr (enableBrine) { - if (enableSaltPrecipitation && values.primaryVarsMeaningBrine() == PrimaryVariables::Sp) { - values[Indices::saltConcentrationIdx] = initialFluidStates_[globalDofIdx].saltSaturation(); - } - else { - values[Indices::saltConcentrationIdx] = initialFluidStates_[globalDofIdx].saltConcentration(); - } - } - - if constexpr (enableMICP){ - values[Indices::microbialConcentrationIdx]= this->microbialConcentration_[globalDofIdx]; - values[Indices::oxygenConcentrationIdx]= this->oxygenConcentration_[globalDofIdx]; - values[Indices::ureaConcentrationIdx]= this->ureaConcentration_[globalDofIdx]; - values[Indices::calciteConcentrationIdx]= this->calciteConcentration_[globalDofIdx]; - values[Indices::biofilmConcentrationIdx]= this->biofilmConcentration_[globalDofIdx]; - } - - values.checkDefined(); - } - - /*! - * \copydoc FvBaseProblem::initialSolutionApplied() - */ - void initialSolutionApplied() - { - // initialize the wells. Note that this needs to be done after initializing the - // intrinsic permeabilities and the after applying the initial solution because - // the well model uses these... - wellModel_.init(); - - // let the object for threshold pressures initialize itself. this is done only at - // this point, because determining the threshold pressures may require to access - // the initial solution. - thresholdPressures_.finishInit(); - - updateCompositionChangeLimits_(); - - if (enableAquifers_) - aquiferModel_.initialSolutionApplied(); - } - - /*! - * \copydoc FvBaseProblem::source - * - * For this problem, the source term of all components is 0 everywhere. - */ - template - void source(RateVector& rate, - const Context& context, - unsigned spaceIdx, - unsigned timeIdx) const - { - const unsigned globalDofIdx = context.globalSpaceIndex(spaceIdx, timeIdx); - source(rate, globalDofIdx, timeIdx); - } - - void source(RateVector& rate, - unsigned globalDofIdx, - unsigned timeIdx) const - { - rate = 0.0; - - wellModel_.computeTotalRatesForDof(rate, globalDofIdx, timeIdx); - - // convert the source term from the total mass rate of the - // cell to the one per unit of volume as used by the model. - for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) { - rate[eqIdx] /= this->model().dofTotalVolume(globalDofIdx); - - Valgrind::CheckDefined(rate[eqIdx]); - assert(isfinite(rate[eqIdx])); - } - - if (enableAquifers_) - aquiferModel_.addToSource(rate, globalDofIdx, timeIdx); - - // if requested, compensate systematic mass loss for cells which were "well - // behaved" in the last time step - if (enableDriftCompensation_) { - const auto& simulator = this->simulator(); - const auto& model = this->model(); - - // we need a higher maxCompensation than the Newton tolerance because the - // current time step might be shorter than the last one - Scalar maxCompensation = 10.0*model.newtonMethod().tolerance(); - - Scalar poro = this->porosity(globalDofIdx, timeIdx); - Scalar dt = simulator.timeStepSize(); - - EqVector dofDriftRate = drift_[globalDofIdx]; - dofDriftRate /= dt*model.dofTotalVolume(globalDofIdx); - - // compute the weighted total drift rate - Scalar totalDriftRate = 0.0; - for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) - totalDriftRate += - std::abs(dofDriftRate[eqIdx])*dt*model.eqWeight(globalDofIdx, eqIdx)/poro; - - // make sure that we do not exceed the maximum rate of drift compensation - if (totalDriftRate > maxCompensation) - dofDriftRate *= maxCompensation/totalDriftRate; - - for (unsigned eqIdx = 0; eqIdx < numEq; ++ eqIdx) - rate[eqIdx] -= dofDriftRate[eqIdx]; - } - } - - /*! - * \brief Returns a reference to the ECL well manager used by the problem. - * - * This can be used for inspecting wells outside of the problem. - */ - const EclWellModel& wellModel() const - { return wellModel_; } - - EclWellModel& wellModel() - { return wellModel_; } - - const EclAquiferModel& aquiferModel() const - { return aquiferModel_; } - - EclAquiferModel& mutableAquiferModel() - { return aquiferModel_; } - - // temporary solution to facilitate output of initial state from flow - const InitialFluidState& initialFluidState(unsigned globalDofIdx) const - { return initialFluidStates_[globalDofIdx]; } - - const EclipseIO& eclIO() const - { return eclWriter_->eclIO(); } - - void setSubStepReport(const SimulatorReportSingle& report) - { return eclWriter_->setSubStepReport(report); } - - void setSimulationReport(const SimulatorReport& report) - { return eclWriter_->setSimulationReport(report); } - - bool nonTrivialBoundaryConditions() const - { return nonTrivialBoundaryConditions_; } - - /*! - * \brief Propose the size of the next time step to the simulator. - * - * This method is only called if the Newton solver does converge, the simulator - * automatically cuts the time step in half without consultating this method again. - */ - Scalar nextTimeStepSize() const - { - // allow external code to do the timestepping - if (this->nextTimeStepSize_ > 0.0) - return this->nextTimeStepSize_; - - const auto& simulator = this->simulator(); - int episodeIdx = simulator.episodeIndex(); - - // for the initial episode, we use a fixed time step size - if (episodeIdx < 0) - return this->initialTimeStepSize_; - - // ask the newton method for a suggestion. This suggestion will be based on how - // well the previous time step converged. After that, apply the runtime time - // stepping constraints. - const auto& newtonMethod = this->model().newtonMethod(); - return limitNextTimeStepSize_(newtonMethod.suggestTimeStepSize(simulator.timeStepSize())); - } - - /*! - * \brief Calculate the porosity multiplier due to water induced rock compaction. - * - * TODO: The API of this is a bit ad-hoc, it would be better to use context objects. - */ - template - LhsEval rockCompPoroMultiplier(const IntensiveQuantities& intQuants, unsigned elementIdx) const - { - - if (this->rockCompPoroMult_.empty() && this->rockCompPoroMultWc_.empty()) - return 1.0; - - unsigned tableIdx = 0; - if (!this->rockTableIdx_.empty()) - tableIdx = this->rockTableIdx_[elementIdx]; - - const auto& fs = intQuants.fluidState(); - LhsEval effectiveOilPressure = decay(fs.pressure(oilPhaseIdx)); - if (!this->minOilPressure_.empty()) - // The pore space change is irreversible - effectiveOilPressure = - min(decay(fs.pressure(oilPhaseIdx)), - this->minOilPressure_[elementIdx]); - - if (!this->overburdenPressure_.empty()) - effectiveOilPressure -= this->overburdenPressure_[elementIdx]; - - - if (!this->rockCompPoroMult_.empty()) { - return this->rockCompPoroMult_[tableIdx].eval(effectiveOilPressure, /*extrapolation=*/true); - } - - // water compaction - assert(!this->rockCompPoroMultWc_.empty()); - LhsEval SwMax = max(decay(fs.saturation(waterPhaseIdx)), this->maxWaterSaturation_[elementIdx]); - LhsEval SwDeltaMax = SwMax - initialFluidStates_[elementIdx].saturation(waterPhaseIdx); - - return this->rockCompPoroMultWc_[tableIdx].eval(effectiveOilPressure, SwDeltaMax, /*extrapolation=*/true); - } - - /*! - * \brief Calculate the transmissibility multiplier due to water induced rock compaction. - * - * TODO: The API of this is a bit ad-hoc, it would be better to use context objects. - */ - template - LhsEval rockCompTransMultiplier(const IntensiveQuantities& intQuants, unsigned elementIdx) const - { - if (this->rockCompTransMult_.empty() && this->rockCompTransMultWc_.empty()) - return 1.0; - - unsigned tableIdx = 0; - if (!this->rockTableIdx_.empty()) - tableIdx = this->rockTableIdx_[elementIdx]; - - const auto& fs = intQuants.fluidState(); - LhsEval effectiveOilPressure = decay(fs.pressure(oilPhaseIdx)); - - if (!this->minOilPressure_.empty()) - // The pore space change is irreversible - effectiveOilPressure = - min(decay(fs.pressure(oilPhaseIdx)), - this->minOilPressure_[elementIdx]); - - if (!this->overburdenPressure_.empty()) - effectiveOilPressure -= this->overburdenPressure_[elementIdx]; - - if (!this->rockCompTransMult_.empty()) - return this->rockCompTransMult_[tableIdx].eval(effectiveOilPressure, /*extrapolation=*/true); - - // water compaction - assert(!this->rockCompTransMultWc_.empty()); - LhsEval SwMax = max(decay(fs.saturation(waterPhaseIdx)), this->maxWaterSaturation_[elementIdx]); - LhsEval SwDeltaMax = SwMax - initialFluidStates_[elementIdx].saturation(waterPhaseIdx); - - return this->rockCompTransMultWc_[tableIdx].eval(effectiveOilPressure, SwDeltaMax, /*extrapolation=*/true); - } - -private: - // update the parameters needed for DRSDT and DRVDT - void updateCompositionChangeLimits_() - { - // update the "last Rs" values for all elements, including the ones in the ghost - // and overlap regions - const auto& simulator = this->simulator(); - int episodeIdx = this->episodeIndex(); - - OPM_BEGIN_PARALLEL_TRY_CATCH(); - if (this->drsdtConvective_(episodeIdx)) { - // This implements the convective DRSDT as described in - // Sandve et al. "Convective dissolution in field scale CO2 storage simulations using the OPM Flow simulator" - // Submitted to TCCS 11, 2021 - Scalar g = this->gravity_[dim - 1]; - ElementContext elemCtx(simulator); - const auto& vanguard = simulator.vanguard(); - auto elemIt = vanguard.gridView().template begin(); - const auto& elemEndIt = vanguard.gridView().template end(); - for (; elemIt != elemEndIt; ++elemIt) { - const Element& elem = *elemIt; - elemCtx.updatePrimaryStencil(elem); - elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); - unsigned compressedDofIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); - const DimMatrix& perm = intrinsicPermeability(compressedDofIdx); - const Scalar permz = perm[dim - 1][dim - 1]; // The Z permeability - Scalar distZ = vanguard.cellThickness(compressedDofIdx); - const auto& iq = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0); - const auto& fs = iq.fluidState(); - Scalar t = getValue(fs.temperature(FluidSystem::oilPhaseIdx)); - Scalar p = getValue(fs.pressure(FluidSystem::oilPhaseIdx)); - Scalar so = getValue(fs.saturation(FluidSystem::oilPhaseIdx)); - Scalar rssat = FluidSystem::oilPvt().saturatedGasDissolutionFactor(fs.pvtRegionIndex(),t,p); - Scalar saturatedInvB = FluidSystem::oilPvt().saturatedInverseFormationVolumeFactor(fs.pvtRegionIndex(),t,p); - Scalar rsZero = 0.0; - Scalar pureDensity = FluidSystem::oilPvt().inverseFormationVolumeFactor(fs.pvtRegionIndex(),t,p,rsZero) * FluidSystem::oilPvt().oilReferenceDensity(fs.pvtRegionIndex()); - Scalar saturatedDensity = saturatedInvB * (FluidSystem::oilPvt().oilReferenceDensity(fs.pvtRegionIndex()) + rssat * FluidSystem::referenceDensity(FluidSystem::gasPhaseIdx, fs.pvtRegionIndex())); - Scalar deltaDensity = saturatedDensity - pureDensity; - Scalar rs = getValue(fs.Rs()); - Scalar visc = FluidSystem::oilPvt().viscosity(fs.pvtRegionIndex(),t,p,rs); - Scalar poro = getValue(iq.porosity()); - // Note that for so = 0 this gives no limits (inf) for the dissolution rate - // Also we restrict the effect of convective mixing to positive density differences - // i.e. we only allow for fingers moving downward - this->convectiveDrs_[compressedDofIdx] = permz * rssat * max(0.0, deltaDensity) * g / ( so * visc * distZ * poro); - } - } - - if (this->drsdtActive_(episodeIdx)) { - ElementContext elemCtx(simulator); - const auto& vanguard = simulator.vanguard(); - auto elemIt = vanguard.gridView().template begin(); - const auto& elemEndIt = vanguard.gridView().template end(); - for (; elemIt != elemEndIt; ++elemIt) { - const Element& elem = *elemIt; - - elemCtx.updatePrimaryStencil(elem); - elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); - - unsigned compressedDofIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); - const auto& iq = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0); - const auto& fs = iq.fluidState(); - - using FluidState = typename std::decay::type; - - int pvtRegionIdx = this->pvtRegionIndex(compressedDofIdx); - const auto& oilVaporizationControl = simulator.vanguard().schedule()[episodeIdx].oilvap(); - if (oilVaporizationControl.getOption(pvtRegionIdx) || fs.saturation(gasPhaseIdx) > freeGasMinSaturation_) - this->lastRs_[compressedDofIdx] = - BlackOil::template getRs_(fs, iq.pvtRegionIndex()); - else - this->lastRs_[compressedDofIdx] = std::numeric_limits::infinity(); - } - } - - // update the "last Rv" values for all elements, including the ones in the ghost - // and overlap regions - if (this->drvdtActive_(episodeIdx)) { - ElementContext elemCtx(simulator); - const auto& vanguard = simulator.vanguard(); - auto elemIt = vanguard.gridView().template begin(); - const auto& elemEndIt = vanguard.gridView().template end(); - for (; elemIt != elemEndIt; ++elemIt) { - const Element& elem = *elemIt; - - elemCtx.updatePrimaryStencil(elem); - elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); - - unsigned compressedDofIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); - const auto& iq = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0); - const auto& fs = iq.fluidState(); - - using FluidState = typename std::decay::type; - - this->lastRv_[compressedDofIdx] = - BlackOil::template getRv_(fs, iq.pvtRegionIndex()); - } - } - OPM_END_PARALLEL_TRY_CATCH("EclProblemTPFA::_updateCompositionLayers() failed: ", this->simulator().vanguard().grid().comm()); - } - - bool updateMaxOilSaturation_() - { - const auto& simulator = this->simulator(); - int episodeIdx = this->episodeIndex(); - - // we use VAPPARS - if (this->vapparsActive(episodeIdx)) { - ElementContext elemCtx(simulator); - const auto& vanguard = simulator.vanguard(); - auto elemIt = vanguard.gridView().template begin(); - const auto& elemEndIt = vanguard.gridView().template end(); - OPM_BEGIN_PARALLEL_TRY_CATCH(); - for (; elemIt != elemEndIt; ++elemIt) { - const Element& elem = *elemIt; - - elemCtx.updatePrimaryStencil(elem); - elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); - - unsigned compressedDofIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); - const auto& iq = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0); - const auto& fs = iq.fluidState(); - - Scalar So = decay(fs.saturation(oilPhaseIdx)); - - this->maxOilSaturation_[compressedDofIdx] = std::max(this->maxOilSaturation_[compressedDofIdx], So); - } - OPM_END_PARALLEL_TRY_CATCH("EclProblemTPFA::updateMayOilSaturation() failed:", vanguard.grid().comm()); - // we need to invalidate the intensive quantities cache here because the - // derivatives of Rs and Rv will most likely have changed - return true; - } - - return false; - } - - bool updateMaxWaterSaturation_() - { - // water compaction is activated in ROCKCOMP - if (this->maxWaterSaturation_.empty()) - return false; - - this->maxWaterSaturation_[/*timeIdx=*/1] = this->maxWaterSaturation_[/*timeIdx=*/0]; - ElementContext elemCtx(this->simulator()); - const auto& vanguard = this->simulator().vanguard(); - auto elemIt = vanguard.gridView().template begin(); - const auto& elemEndIt = vanguard.gridView().template end(); - OPM_BEGIN_PARALLEL_TRY_CATCH(); - for (; elemIt != elemEndIt; ++elemIt) { - const Element& elem = *elemIt; - - elemCtx.updatePrimaryStencil(elem); - elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); - - unsigned compressedDofIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); - const auto& iq = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0); - const auto& fs = iq.fluidState(); - - Scalar Sw = decay(fs.saturation(waterPhaseIdx)); - this->maxWaterSaturation_[compressedDofIdx] = std::max(this->maxWaterSaturation_[compressedDofIdx], Sw); - } - OPM_END_PARALLEL_TRY_CATCH("EclProblemTPFA::updateMayWaterSaturation() failed: ", vanguard.grid().comm()); - - return true; - } - - bool updateMinPressure_() - { - // IRREVERS option is used in ROCKCOMP - if (this->minOilPressure_.empty()) - return false; - - OPM_BEGIN_PARALLEL_TRY_CATCH(); - ElementContext elemCtx(this->simulator()); - const auto& vanguard = this->simulator().vanguard(); - auto elemIt = vanguard.gridView().template begin(); - const auto& elemEndIt = vanguard.gridView().template end(); - for (; elemIt != elemEndIt; ++elemIt) { - const Element& elem = *elemIt; - - elemCtx.updatePrimaryStencil(elem); - elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); - - unsigned compressedDofIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); - const auto& iq = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0); - const auto& fs = iq.fluidState(); - - this->minOilPressure_[compressedDofIdx] = - std::min(this->minOilPressure_[compressedDofIdx], - getValue(fs.pressure(oilPhaseIdx))); - } - OPM_END_PARALLEL_TRY_CATCH("EclProblemTPFA::updateMinPressure_() failed: ", this->simulator().vanguard().grid().comm()); - return true; - } - - void readMaterialParameters_() - { - const auto& simulator = this->simulator(); - const auto& vanguard = simulator.vanguard(); - const auto& eclState = vanguard.eclState(); - - // the PVT and saturation region numbers - this->updatePvtnum_(); - this->updateSatnum_(); - - // the MISC region numbers (solvent model) - this->updateMiscnum_(); - // the PLMIX region numbers (polymer model) - this->updatePlmixnum_(); - - //////////////////////////////// - // porosity - updateReferencePorosity_(); - this->referencePorosity_[1] = this->referencePorosity_[0]; - //////////////////////////////// - - //////////////////////////////// - // fluid-matrix interactions (saturation functions; relperm/capillary pressure) - materialLawManager_ = std::make_shared(); - materialLawManager_->initFromState(eclState); - materialLawManager_->initParamsForElements(eclState, this->model().numGridDof()); - //////////////////////////////// - } - - void readThermalParameters_() - { - if constexpr (enableEnergy) - { - const auto& simulator = this->simulator(); - const auto& vanguard = simulator.vanguard(); - const auto& eclState = vanguard.eclState(); - - // fluid-matrix interactions (saturation functions; relperm/capillary pressure) - thermalLawManager_ = std::make_shared(); - thermalLawManager_->initParamsForElements(eclState, this->model().numGridDof()); - } - } - - void updateReferencePorosity_() - { - const auto& simulator = this->simulator(); - const auto& vanguard = simulator.vanguard(); - const auto& eclState = vanguard.eclState(); - - size_t numDof = this->model().numGridDof(); - - this->referencePorosity_[/*timeIdx=*/0].resize(numDof); - - const auto& fp = eclState.fieldProps(); - const std::vector porvData = fp.porv(false); - const std::vector actnumData = fp.actnum(); - for (size_t dofIdx = 0; dofIdx < numDof; ++ dofIdx) { - Scalar poreVolume = porvData[dofIdx]; - - // we define the porosity as the accumulated pore volume divided by the - // geometric volume of the element. Note that -- in pathetic cases -- it can - // be larger than 1.0! - Scalar dofVolume = simulator.model().dofTotalVolume(dofIdx); - assert(dofVolume > 0.0); - this->referencePorosity_[/*timeIdx=*/0][dofIdx] = poreVolume/dofVolume; - } - } - - void readInitialCondition_() - { - const auto& simulator = this->simulator(); - const auto& vanguard = simulator.vanguard(); - const auto& eclState = vanguard.eclState(); - - if (eclState.getInitConfig().hasEquil()) - readEquilInitialCondition_(); - else - readExplicitInitialCondition_(); - - if constexpr (enableSolvent || enablePolymer || enablePolymerMolarWeight || enableMICP) - this->readBlackoilExtentionsInitialConditions_(this->model().numGridDof(), - enableSolvent, - enablePolymer, - enablePolymerMolarWeight, - enableMICP); - - //initialize min/max values - size_t numElems = this->model().numGridDof(); - for (size_t elemIdx = 0; elemIdx < numElems; ++elemIdx) { - const auto& fs = initialFluidStates_[elemIdx]; - if (!this->maxWaterSaturation_.empty()) - this->maxWaterSaturation_[elemIdx] = std::max(this->maxWaterSaturation_[elemIdx], fs.saturation(waterPhaseIdx)); - if (!this->maxOilSaturation_.empty()) - this->maxOilSaturation_[elemIdx] = std::max(this->maxOilSaturation_[elemIdx], fs.saturation(oilPhaseIdx)); - if (!this->minOilPressure_.empty()) - this->minOilPressure_[elemIdx] = std::min(this->minOilPressure_[elemIdx], fs.pressure(oilPhaseIdx)); - } - - - } - - void readEquilInitialCondition_() - { - const auto& simulator = this->simulator(); - - // initial condition corresponds to hydrostatic conditions. - using EquilInitializer = EclEquilInitializer; - EquilInitializer equilInitializer(simulator, *materialLawManager_); - - size_t numElems = this->model().numGridDof(); - initialFluidStates_.resize(numElems); - for (size_t elemIdx = 0; elemIdx < numElems; ++elemIdx) { - auto& elemFluidState = initialFluidStates_[elemIdx]; - elemFluidState.assign(equilInitializer.initialFluidState(elemIdx)); - } - } - - void readEclRestartSolution_() - { - // Set the start time of the simulation - auto& simulator = this->simulator(); - const auto& schedule = simulator.vanguard().schedule(); - const auto& eclState = simulator.vanguard().eclState(); - const auto& initconfig = eclState.getInitConfig(); - { - int restart_step = initconfig.getRestartStep(); - - simulator.setTime(schedule.seconds(restart_step)); - - simulator.startNextEpisode(simulator.startTime() + simulator.time(), - schedule.stepLength(restart_step)); - simulator.setEpisodeIndex(restart_step); - } - eclWriter_->beginRestart(); - - Scalar dt = std::min(eclWriter_->restartTimeStepSize(), simulator.episodeLength()); - simulator.setTimeStepSize(dt); - - size_t numElems = this->model().numGridDof(); - initialFluidStates_.resize(numElems); - if constexpr (enableSolvent) - this->solventSaturation_.resize(numElems, 0.0); - - if constexpr (enablePolymer) - this->polymerConcentration_.resize(numElems, 0.0); - - if constexpr (enablePolymerMolarWeight) { - const std::string msg {"Support of the RESTART for polymer molecular weight " - "is not implemented yet. The polymer weight value will be " - "zero when RESTART begins"}; - OpmLog::warning("NO_POLYMW_RESTART", msg); - this->polymerMoleWeight_.resize(numElems, 0.0); - } - - if constexpr (enableMICP){ - this->microbialConcentration_.resize(numElems, 0.0); - this->oxygenConcentration_.resize(numElems, 0.0); - this->ureaConcentration_.resize(numElems, 0.0); - this->biofilmConcentration_.resize(numElems, 0.0); - this->calciteConcentration_.resize(numElems, 0.0); - } - - for (size_t elemIdx = 0; elemIdx < numElems; ++elemIdx) { - auto& elemFluidState = initialFluidStates_[elemIdx]; - elemFluidState.setPvtRegionIndex(pvtRegionIndex(elemIdx)); - eclWriter_->eclOutputModule().initHysteresisParams(simulator, elemIdx); - eclWriter_->eclOutputModule().assignToFluidState(elemFluidState, elemIdx); - - // Note: Function processRestartSaturations_() mutates the - // 'ssol' argument--the value from the restart file--if solvent - // is enabled. Then, store the updated solvent saturation into - // 'solventSaturation_'. Otherwise, just pass a dummy value to - // the function and discard the unchanged result. Do not index - // into 'solventSaturation_' unless solvent is enabled. - { - auto ssol = enableSolvent - ? eclWriter_->eclOutputModule().getSolventSaturation(elemIdx) - : Scalar(0); - - processRestartSaturations_(elemFluidState, ssol); - - if constexpr (enableSolvent) - this->solventSaturation_[elemIdx] = ssol; - } - - if (! this->lastRs_.empty()) { - this->lastRs_[elemIdx] = elemFluidState.Rs(); - } - - if (! this->lastRv_.empty()) { - this->lastRv_[elemIdx] = elemFluidState.Rv(); - } - - if constexpr (enablePolymer) - this->polymerConcentration_[elemIdx] = eclWriter_->eclOutputModule().getPolymerConcentration(elemIdx); - if constexpr (enableMICP){ - this->microbialConcentration_[elemIdx] = eclWriter_->eclOutputModule().getMicrobialConcentration(elemIdx); - this->oxygenConcentration_[elemIdx] = eclWriter_->eclOutputModule().getOxygenConcentration(elemIdx); - this->ureaConcentration_[elemIdx] = eclWriter_->eclOutputModule().getUreaConcentration(elemIdx); - this->biofilmConcentration_[elemIdx] = eclWriter_->eclOutputModule().getBiofilmConcentration(elemIdx); - this->calciteConcentration_[elemIdx] = eclWriter_->eclOutputModule().getCalciteConcentration(elemIdx); - } - // if we need to restart for polymer molecular weight simulation, we need to add related here - } - - const int episodeIdx = this->episodeIndex(); - const auto& oilVaporizationControl = simulator.vanguard().schedule()[episodeIdx].oilvap(); - if (this->drsdtActive_(episodeIdx)) - // DRSDT is enabled - for (size_t pvtRegionIdx = 0; pvtRegionIdx < this->maxDRs_.size(); ++pvtRegionIdx) - this->maxDRs_[pvtRegionIdx] = oilVaporizationControl.getMaxDRSDT(pvtRegionIdx)*simulator.timeStepSize(); - - if (this->drvdtActive_(episodeIdx)) - // DRVDT is enabled - for (size_t pvtRegionIdx = 0; pvtRegionIdx < this->maxDRv_.size(); ++pvtRegionIdx) - this->maxDRv_[pvtRegionIdx] = oilVaporizationControl.getMaxDRVDT(pvtRegionIdx)*simulator.timeStepSize(); - - // assign the restart solution to the current solution. note that we still need - // to compute real initial solution after this because the initial fluid states - // need to be correct for stuff like boundary conditions. - auto& sol = this->model().solution(/*timeIdx=*/0); - const auto& gridView = this->gridView(); - ElementContext elemCtx(simulator); - auto elemIt = gridView.template begin(); - const auto& elemEndIt = gridView.template end(); - for (; elemIt != elemEndIt; ++elemIt) { - const auto& elem = *elemIt; - if (elem.partitionType() != Dune::InteriorEntity) - continue; - - elemCtx.updatePrimaryStencil(elem); - int elemIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); - initial(sol[elemIdx], elemCtx, /*spaceIdx=*/0, /*timeIdx=*/0); - } - - // make sure that the ghost and overlap entities exhibit the correct - // solution. alternatively, this could be done in the loop above by also - // considering non-interior elements. Since the initial() method might not work - // 100% correctly for such elements, let's play safe and explicitly synchronize - // using message passing. - this->model().syncOverlap(); - - eclWriter_->endRestart(); - } - - void processRestartSaturations_(InitialFluidState& elemFluidState, Scalar& solventSaturation) - { - // each phase needs to be above certain value to be claimed to be existing - // this is used to recover some RESTART running with the defaulted single-precision format - const Scalar smallSaturationTolerance = 1.e-6; - Scalar sumSaturation = 0.0; - for (size_t phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { - if (FluidSystem::phaseIsActive(phaseIdx)) { - if (elemFluidState.saturation(phaseIdx) < smallSaturationTolerance) - elemFluidState.setSaturation(phaseIdx, 0.0); - - sumSaturation += elemFluidState.saturation(phaseIdx); - } - - } - if constexpr (enableSolvent) { - if (solventSaturation < smallSaturationTolerance) - solventSaturation = 0.0; - - sumSaturation += solventSaturation; - } - - assert(sumSaturation > 0.0); - - for (size_t phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { - if (FluidSystem::phaseIsActive(phaseIdx)) { - const Scalar saturation = elemFluidState.saturation(phaseIdx) / sumSaturation; - elemFluidState.setSaturation(phaseIdx, saturation); - } - } - if constexpr (enableSolvent) { - solventSaturation = solventSaturation / sumSaturation; - } - } - - void readExplicitInitialCondition_() - { - const auto& simulator = this->simulator(); - const auto& vanguard = simulator.vanguard(); - const auto& eclState = vanguard.eclState(); - const auto& fp = eclState.fieldProps(); - bool has_swat = fp.has_double("SWAT"); - bool has_sgas = fp.has_double("SGAS"); - bool has_rs = fp.has_double("RS"); - bool has_rv = fp.has_double("RV"); - bool has_rvw = fp.has_double("RVW"); - bool has_pressure = fp.has_double("PRESSURE"); - bool has_salt = fp.has_double("SALT"); - bool has_saltp = fp.has_double("SALTP"); - - // make sure all required quantities are enables - if (Indices::numPhases > 1) { - if (FluidSystem::phaseIsActive(waterPhaseIdx) && !has_swat) - throw std::runtime_error("The ECL input file requires the presence of the SWAT keyword if " - "the water phase is active"); - if (FluidSystem::phaseIsActive(gasPhaseIdx) && !has_sgas && FluidSystem::phaseIsActive(oilPhaseIdx)) - throw std::runtime_error("The ECL input file requires the presence of the SGAS keyword if " - "the gas phase is active"); - } - if (!has_pressure) - throw std::runtime_error("The ECL input file requires the presence of the PRESSURE " - "keyword if the model is initialized explicitly"); - if (FluidSystem::enableDissolvedGas() && !has_rs) - throw std::runtime_error("The ECL input file requires the RS keyword to be present if" - " dissolved gas is enabled"); - if (FluidSystem::enableVaporizedOil() && !has_rv) - throw std::runtime_error("The ECL input file requires the RV keyword to be present if" - " vaporized oil is enabled"); - if (FluidSystem::enableVaporizedWater() && !has_rvw) - throw std::runtime_error("The ECL input file requires the RVW keyword to be present if" - " vaporized water is enabled"); - if (enableBrine && !has_salt) - throw std::runtime_error("The ECL input file requires the SALT keyword to be present if" - " brine is enabled and the model is initialized explicitly"); - if (enableSaltPrecipitation && !has_saltp) - throw std::runtime_error("The ECL input file requires the SALTP keyword to be present if" - " salt precipitation is enabled and the model is initialized explicitly"); - - size_t numDof = this->model().numGridDof(); - - initialFluidStates_.resize(numDof); - - std::vector waterSaturationData; - std::vector gasSaturationData; - std::vector pressureData; - std::vector rsData; - std::vector rvData; - std::vector rvwData; - std::vector tempiData; - std::vector saltData; - std::vector saltpData; - - if (FluidSystem::phaseIsActive(waterPhaseIdx) && Indices::numPhases > 1) - waterSaturationData = fp.get_double("SWAT"); - else - waterSaturationData.resize(numDof); - - if (FluidSystem::phaseIsActive(gasPhaseIdx) && FluidSystem::phaseIsActive(oilPhaseIdx)) - gasSaturationData = fp.get_double("SGAS"); - else - gasSaturationData.resize(numDof); - - pressureData = fp.get_double("PRESSURE"); - if (FluidSystem::enableDissolvedGas()) - rsData = fp.get_double("RS"); - - if (FluidSystem::enableVaporizedOil()) - rvData = fp.get_double("RV"); - - if (FluidSystem::enableVaporizedWater()) - rvwData = fp.get_double("RVW"); - - // initial reservoir temperature - tempiData = fp.get_double("TEMPI"); - - // initial salt concentration data - if (enableBrine) - saltData = fp.get_double("SALT"); - - // initial precipitated salt saturation data - if (enableSaltPrecipitation) - saltpData = fp.get_double("SALTP"); - - // calculate the initial fluid states - for (size_t dofIdx = 0; dofIdx < numDof; ++dofIdx) { - auto& dofFluidState = initialFluidStates_[dofIdx]; - - dofFluidState.setPvtRegionIndex(pvtRegionIndex(dofIdx)); - - ////// - // set temperature - ////// - Scalar temperatureLoc = tempiData[dofIdx]; - if (!std::isfinite(temperatureLoc) || temperatureLoc <= 0) - temperatureLoc = FluidSystem::surfaceTemperature; - dofFluidState.setTemperature(temperatureLoc); - - ////// - // set salt concentration - ////// - if (enableBrine) - dofFluidState.setSaltConcentration(saltData[dofIdx]); - - ////// - // set precipitated salt saturation - ////// - if (enableSaltPrecipitation) - dofFluidState.setSaltSaturation(saltpData[dofIdx]); - - ////// - // set saturations - ////// - if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) - dofFluidState.setSaturation(FluidSystem::waterPhaseIdx, - waterSaturationData[dofIdx]); - - if (FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)){ - if (!FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)){ - dofFluidState.setSaturation(FluidSystem::gasPhaseIdx, - 1.0 - - waterSaturationData[dofIdx]); - } - else - dofFluidState.setSaturation(FluidSystem::gasPhaseIdx, - gasSaturationData[dofIdx]); - } - if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) - dofFluidState.setSaturation(FluidSystem::oilPhaseIdx, - 1.0 - - waterSaturationData[dofIdx] - - gasSaturationData[dofIdx]); - - ////// - // set phase pressures - ////// - Scalar pressure = pressureData[dofIdx]; // oil pressure (or gas pressure for water-gas system or water pressure for single phase) - - // this assumes that capillary pressures only depend on the phase saturations - // and possibly on temperature. (this is always the case for ECL problems.) - Dune::FieldVector pc(0.0); - const auto& matParams = materialLawParams(dofIdx); - MaterialLaw::capillaryPressures(pc, matParams, dofFluidState); - Valgrind::CheckDefined(pressure); - Valgrind::CheckDefined(pc); - for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { - if (!FluidSystem::phaseIsActive(phaseIdx)) - continue; - - if (Indices::oilEnabled) - dofFluidState.setPressure(phaseIdx, pressure + (pc[phaseIdx] - pc[oilPhaseIdx])); - else if (Indices::gasEnabled) - dofFluidState.setPressure(phaseIdx, pressure + (pc[phaseIdx] - pc[gasPhaseIdx])); - else if (Indices::waterEnabled) - //single (water) phase - dofFluidState.setPressure(phaseIdx, pressure); - } - - if (FluidSystem::enableDissolvedGas()) - dofFluidState.setRs(rsData[dofIdx]); - else if (Indices::gasEnabled && Indices::oilEnabled) - dofFluidState.setRs(0.0); - - if (FluidSystem::enableVaporizedOil()) - dofFluidState.setRv(rvData[dofIdx]); - else if (Indices::gasEnabled && Indices::oilEnabled) - dofFluidState.setRv(0.0); - - if (FluidSystem::enableVaporizedWater()) - dofFluidState.setRvw(rvwData[dofIdx]); - - ////// - // set invB_ - ////// - for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { - if (!FluidSystem::phaseIsActive(phaseIdx)) - continue; - - const auto& b = FluidSystem::inverseFormationVolumeFactor(dofFluidState, phaseIdx, pvtRegionIndex(dofIdx)); - dofFluidState.setInvB(phaseIdx, b); - - const auto& rho = FluidSystem::density(dofFluidState, phaseIdx, pvtRegionIndex(dofIdx)); - dofFluidState.setDensity(phaseIdx, rho); - - } - } - } - - // update the hysteresis parameters of the material laws for the whole grid - bool updateHysteresis_() - { - if (!materialLawManager_->enableHysteresis()) - return false; - - // we need to update the hysteresis data for _all_ elements (i.e., not just the - // interior ones) to avoid desynchronization of the processes in the parallel case! - const auto& simulator = this->simulator(); - ElementContext elemCtx(simulator); - const auto& vanguard = simulator.vanguard(); - auto elemIt = vanguard.gridView().template begin(); - const auto& elemEndIt = vanguard.gridView().template end(); - OPM_BEGIN_PARALLEL_TRY_CATCH(); - for (; elemIt != elemEndIt; ++elemIt) { - const Element& elem = *elemIt; - - elemCtx.updatePrimaryStencil(elem); - elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); - - unsigned compressedDofIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); - const auto& intQuants = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0); - materialLawManager_->updateHysteresis(intQuants.fluidState(), compressedDofIdx); - } - OPM_END_PARALLEL_TRY_CATCH("EclProblemTPFA::updateHyteresis_(): ", vanguard.grid().comm()); - return true; - } - - void updateMaxPolymerAdsorption_() - { - // we need to update the max polymer adsoption data for all elements - const auto& simulator = this->simulator(); - ElementContext elemCtx(simulator); - const auto& vanguard = simulator.vanguard(); - auto elemIt = vanguard.gridView().template begin(); - const auto& elemEndIt = vanguard.gridView().template end(); - OPM_BEGIN_PARALLEL_TRY_CATCH(); - for (; elemIt != elemEndIt; ++elemIt) { - const Element& elem = *elemIt; - - elemCtx.updatePrimaryStencil(elem); - elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); - - unsigned compressedDofIdx = elemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0); - const auto& intQuants = elemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0); - - this->maxPolymerAdsorption_[compressedDofIdx] = std::max(this->maxPolymerAdsorption_[compressedDofIdx], - scalarValue(intQuants.polymerAdsorption())); - } - OPM_END_PARALLEL_TRY_CATCH("EclProblemTPFA::updateMaxPolymerAdsorption_(): ", vanguard.grid().comm()); - } - - struct PffDofData_ - { - ConditionalStorage thermalHalfTransIn; - ConditionalStorage thermalHalfTransOut; - ConditionalStorage diffusivity; - Scalar transmissibility; - }; - - // update the prefetch friendly data object - void updatePffDofData_() - { - const auto& distFn = - [this](PffDofData_& dofData, - const Stencil& stencil, - unsigned localDofIdx) - -> void - { - const auto& elementMapper = this->model().elementMapper(); - - unsigned globalElemIdx = elementMapper.index(stencil.entity(localDofIdx)); - if (localDofIdx != 0) { - unsigned globalCenterElemIdx = elementMapper.index(stencil.entity(/*dofIdx=*/0)); - dofData.transmissibility = transmissibilities_.transmissibility(globalCenterElemIdx, globalElemIdx); - - if constexpr (enableEnergy) { - *dofData.thermalHalfTransIn = transmissibilities_.thermalHalfTrans(globalCenterElemIdx, globalElemIdx); - *dofData.thermalHalfTransOut = transmissibilities_.thermalHalfTrans(globalElemIdx, globalCenterElemIdx); - } - if constexpr (enableDiffusion) - *dofData.diffusivity = transmissibilities_.diffusivity(globalCenterElemIdx, globalElemIdx); - } - }; - - pffDofData_.update(distFn); - } - - void readBoundaryConditions_() - { - nonTrivialBoundaryConditions_ = false; - const auto& simulator = this->simulator(); - const auto& vanguard = simulator.vanguard(); - const auto& bcconfig = vanguard.eclState().getSimulationConfig().bcconfig(); - if (bcconfig.size() > 0) { - nonTrivialBoundaryConditions_ = true; - - size_t numCartDof = vanguard.cartesianSize(); - unsigned numElems = vanguard.gridView().size(/*codim=*/0); - std::vector cartesianToCompressedElemIdx(numCartDof, -1); - - for (unsigned elemIdx = 0; elemIdx < numElems; ++elemIdx) - cartesianToCompressedElemIdx[vanguard.cartesianIndex(elemIdx)] = elemIdx; - - massratebcXMinus_.resize(numElems, 0.0); - massratebcX_.resize(numElems, 0.0); - massratebcYMinus_.resize(numElems, 0.0); - massratebcY_.resize(numElems, 0.0); - massratebcZMinus_.resize(numElems, 0.0); - massratebcZ_.resize(numElems, 0.0); - freebcX_.resize(numElems, false); - freebcXMinus_.resize(numElems, false); - freebcY_.resize(numElems, false); - freebcYMinus_.resize(numElems, false); - freebcZ_.resize(numElems, false); - freebcZMinus_.resize(numElems, false); - - for (const auto& bcface : bcconfig) { - const auto& type = bcface.bctype; - if (type == BCType::RATE) { - int compIdx = 0; // default initialize to avoid -Wmaybe-uninitialized warning - - switch (bcface.component) { - case BCComponent::OIL: - compIdx = Indices::canonicalToActiveComponentIndex(oilCompIdx); - break; - case BCComponent::GAS: - compIdx = Indices::canonicalToActiveComponentIndex(gasCompIdx); - break; - case BCComponent::WATER: - compIdx = Indices::canonicalToActiveComponentIndex(waterCompIdx); - break; - case BCComponent::SOLVENT: - if constexpr (!enableSolvent) - throw std::logic_error("solvent is disabled and you're trying to add solvent to BC"); - - compIdx = Indices::solventSaturationIdx; - break; - case BCComponent::POLYMER: - if constexpr (!enablePolymer) - throw std::logic_error("polymer is disabled and you're trying to add polymer to BC"); - - compIdx = Indices::polymerConcentrationIdx; - break; - case BCComponent::NONE: - throw std::logic_error("you need to specify the component when RATE type is set in BC"); - break; - } - - std::vector* data = nullptr; - switch (bcface.dir) { - case FaceDir::XMinus: - data = &massratebcXMinus_; - break; - case FaceDir::XPlus: - data = &massratebcX_; - break; - case FaceDir::YMinus: - data = &massratebcYMinus_; - break; - case FaceDir::YPlus: - data = &massratebcY_; - break; - case FaceDir::ZMinus: - data = &massratebcZMinus_; - break; - case FaceDir::ZPlus: - data = &massratebcZ_; - break; - } - - const Evaluation rate = bcface.rate; - for (int i = bcface.i1; i <= bcface.i2; ++i) { - for (int j = bcface.j1; j <= bcface.j2; ++j) { - for (int k = bcface.k1; k <= bcface.k2; ++k) { - std::array tmp = {i,j,k}; - auto elemIdx = cartesianToCompressedElemIdx[vanguard.cartesianIndex(tmp)]; - if (elemIdx >= 0) - (*data)[elemIdx][compIdx] = rate; - } - } - } - } else if (type == BCType::FREE) { - std::vector* data = nullptr; - switch (bcface.dir) { - case FaceDir::XMinus: - data = &freebcXMinus_; - break; - case FaceDir::XPlus: - data = &freebcX_; - break; - case FaceDir::YMinus: - data = &freebcYMinus_; - break; - case FaceDir::YPlus: - data = &freebcY_; - break; - case FaceDir::ZMinus: - data = &freebcZMinus_; - break; - case FaceDir::ZPlus: - data = &freebcZ_; - break; - } - - for (int i = bcface.i1; i <= bcface.i2; ++i) { - for (int j = bcface.j1; j <= bcface.j2; ++j) { - for (int k = bcface.k1; k <= bcface.k2; ++k) { - std::array tmp = {i,j,k}; - auto elemIdx = cartesianToCompressedElemIdx[vanguard.cartesianIndex(tmp)]; - if (elemIdx >= 0) - (*data)[elemIdx] = true; - } - } - } - - // TODO: either the real initial solution needs to be computed or read from the restart file - const auto& eclState = simulator.vanguard().eclState(); - const auto& initconfig = eclState.getInitConfig(); - if (initconfig.restartRequested()) { - throw std::logic_error("restart is not compatible with using free boundary conditions"); - } - } else { - throw std::logic_error("invalid type for BC. Use FREE or RATE"); - } - } - } - } - - // this method applies the runtime constraints specified via the deck and/or command - // line parameters for the size of the next time step. - Scalar limitNextTimeStepSize_(Scalar dtNext) const - { - if constexpr (enableExperiments) { - const auto& simulator = this->simulator(); - int episodeIdx = simulator.episodeIndex(); - - // first thing in the morning, limit the time step size to the maximum size - dtNext = std::min(dtNext, this->maxTimeStepSize_); - - Scalar remainingEpisodeTime = - simulator.episodeStartTime() + simulator.episodeLength() - - (simulator.startTime() + simulator.time()); - assert(remainingEpisodeTime >= 0.0); - - // if we would have a small amount of time left over in the current episode, make - // two equal time steps instead of a big and a small one - if (remainingEpisodeTime/2.0 < dtNext && dtNext < remainingEpisodeTime*(1.0 - 1e-5)) - // note: limiting to the maximum time step size here is probably not strictly - // necessary, but it should not hurt and is more fool-proof - dtNext = std::min(this->maxTimeStepSize_, remainingEpisodeTime/2.0); - - if (simulator.episodeStarts()) { - // if a well event occurred, respect the limit for the maximum time step after - // that, too - int reportStepIdx = std::max(episodeIdx, 0); - const auto& events = simulator.vanguard().schedule()[reportStepIdx].events(); - bool wellEventOccured = - events.hasEvent(ScheduleEvents::NEW_WELL) - || events.hasEvent(ScheduleEvents::PRODUCTION_UPDATE) - || events.hasEvent(ScheduleEvents::INJECTION_UPDATE) - || events.hasEvent(ScheduleEvents::WELL_STATUS_CHANGE); - if (episodeIdx >= 0 && wellEventOccured && this->maxTimeStepAfterWellEvent_ > 0) - dtNext = std::min(dtNext, this->maxTimeStepAfterWellEvent_); - } - } - - return dtNext; - } - - typename Vanguard::TransmissibilityType transmissibilities_; - - std::shared_ptr materialLawManager_; - std::shared_ptr thermalLawManager_; - - EclThresholdPressure thresholdPressures_; - - std::vector initialFluidStates_; - - constexpr static Scalar freeGasMinSaturation_ = 1e-7; - - bool enableDriftCompensation_; - GlobalEqVector drift_; - - EclWellModel wellModel_; - bool enableAquifers_; - EclAquiferModel aquiferModel_; - - bool enableEclOutput_; - std::unique_ptr eclWriter_; - - PffGridVector pffDofData_; - TracerModel tracerModel_; - - std::vector freebcX_; - std::vector freebcXMinus_; - std::vector freebcY_; - std::vector freebcYMinus_; - std::vector freebcZ_; - std::vector freebcZMinus_; - - bool nonTrivialBoundaryConditions_; - std::vector massratebcX_; - std::vector massratebcXMinus_; - std::vector massratebcY_; - std::vector massratebcYMinus_; - std::vector massratebcZ_; - std::vector massratebcZMinus_; -}; - -} // namespace Opm - -#endif diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp index 42bf1f4c3..d3af19aac 100644 --- a/flow/flow_blackoil_tpfa.cpp +++ b/flow/flow_blackoil_tpfa.cpp @@ -23,12 +23,10 @@ // modifications from standard #include #include -//#include -//#include #include #include #include -#include +#include namespace Opm { namespace Properties { @@ -42,16 +40,6 @@ namespace Opm { -namespace Opm { - namespace Properties { - template - struct Problem { - using type = EclProblemTPFA; - }; - } -} - - namespace Opm { namespace Properties { template From fdce3e590d66375f7da1359a207e73e77b257e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Tue, 5 Jul 2022 14:28:13 +0200 Subject: [PATCH 30/49] WIP, now runs with just this flux module replacing original versions. --- ebos/eclfluxmoduletpfa.hh | 65 +++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/ebos/eclfluxmoduletpfa.hh b/ebos/eclfluxmoduletpfa.hh index 69e8e43eb..a7278f223 100644 --- a/ebos/eclfluxmoduletpfa.hh +++ b/ebos/eclfluxmoduletpfa.hh @@ -170,7 +170,7 @@ public: */ const Evaluation& volumeFlux(unsigned phaseIdx) const { - throw std::invalid_argument("Should not acces volume flux for eclmoduletpfa"); + //throw std::invalid_argument("Should not acces volume flux for eclmoduletpfa"); return volumeFlux_[phaseIdx]; } @@ -184,7 +184,7 @@ protected: */ unsigned upstreamIndex_(unsigned phaseIdx) const { - throw std::invalid_argument("No upstreamIndex"); + //throw std::invalid_argument("No upstreamIndex"); assert(phaseIdx < numPhases); return upIdx_[phaseIdx]; @@ -199,7 +199,7 @@ protected: */ unsigned downstreamIndex_(unsigned phaseIdx) const { - throw std::invalid_argument("No downstream index"); + //throw std::invalid_argument("No downstream index"); assert(phaseIdx < numPhases); return dnIdx_[phaseIdx]; @@ -517,8 +517,8 @@ protected: */ void calculateGradients_(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) { - throw std::invalid_argument("No calculateGradients_"); - + //throw std::invalid_argument("No calculateGradients_"); + Valgrind::SetUndefined(*this); const auto& problem = elemCtx.problem(); @@ -529,15 +529,12 @@ protected: exteriorDofIdx_ = scvf.exteriorIndex(); assert(interiorDofIdx_ != exteriorDofIdx_); - //unsigned I = stencil.globalSpaceIndex(interiorDofIdx_); - //unsigned J = stencil.globalSpaceIndex(exteriorDofIdx_); - Scalar Vin = elemCtx.dofVolume(interiorDofIdx_, /*timeIdx=*/0); - Scalar Vex = elemCtx.dofVolume(exteriorDofIdx_, /*timeIdx=*/0); - const auto& globalIndexIn = stencil.globalSpaceIndex(interiorDofIdx_); - const auto& globalIndexEx = stencil.globalSpaceIndex(exteriorDofIdx_); + unsigned I = stencil.globalSpaceIndex(interiorDofIdx_); + unsigned J = stencil.globalSpaceIndex(exteriorDofIdx_); + Scalar trans = problem.transmissibility(elemCtx, interiorDofIdx_, exteriorDofIdx_); Scalar faceArea = scvf.area(); - Scalar thpres = problem.thresholdPressure(globalIndexIn, globalIndexEx); + Scalar thpres = problem.thresholdPressure(I, J); // estimate the gravity correction: for performance reasons we use a simplified // approach for this flux module that assumes that gravity is constant and always @@ -559,37 +556,40 @@ protected: // exterior DOF) Scalar distZ = zIn - zEx; - for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { + Scalar Vin = elemCtx.dofVolume(interiorDofIdx_, /*timeIdx=*/0); + Scalar Vex = elemCtx.dofVolume(exteriorDofIdx_, /*timeIdx=*/0); + + for (unsigned phaseIdx = 0; phaseIdx < numPhases; phaseIdx++) { if (!FluidSystem::phaseIsActive(phaseIdx)) - continue; + continue; calculatePhasePressureDiff_(upIdx_[phaseIdx], dnIdx_[phaseIdx], pressureDifference_[phaseIdx], intQuantsIn, intQuantsEx, - timeIdx,//input - phaseIdx,//input - interiorDofIdx_,//input - exteriorDofIdx_,//intput + timeIdx, // input + phaseIdx, // input + interiorDofIdx_, // input + exteriorDofIdx_, // intput Vin, Vex, - globalIndexIn, - globalIndexEx, - distZ*g, + I, + J, + distZ * g, thpres); - if(pressureDifference_[phaseIdx] == 0){ + if (pressureDifference_[phaseIdx] == 0) { volumeFlux_[phaseIdx] = 0.0; continue; } - IntensiveQuantities up; + const IntensiveQuantities& up = (upIdx_[phaseIdx] == interiorDofIdx_) ? intQuantsIn : intQuantsEx; //unsigned globalIndex; - if(upIdx_[phaseIdx] == interiorDofIdx_){ - up = intQuantsIn; - //globalIndex = globalIndexIn; - }else{ - up = intQuantsEx; - //globalIndex = globalIndexEx; - } + // if(upIdx_[phaseIdx] == interiorDofIdx_){ + // up = intQuantsIn; + // //globalIndex = I; + // }else{ + // up = intQuantsEx; + // //globalIndex = J; + // } // TODO: should the rock compaction transmissibility multiplier be upstreamed // or averaged? all fluids should see the same compaction?! //const auto& globalIndex = stencil.globalSpaceIndex(upstreamIdx); @@ -597,18 +597,17 @@ protected: const Evaluation& transMult = up.rockCompTransMultiplier(); // const Evaluation& transMult = // problem.template rockCompTransMultiplier(up, globalIndex); - + if (upIdx_[phaseIdx] == interiorDofIdx_) volumeFlux_[phaseIdx] = pressureDifference_[phaseIdx]*up.mobility(phaseIdx)*transMult*(-trans/faceArea); else volumeFlux_[phaseIdx] = pressureDifference_[phaseIdx]*(Toolbox::value(up.mobility(phaseIdx))*Toolbox::value(transMult)*(-trans/faceArea)); - } } - + /*! * \brief Update the required gradients for boundary faces */ From fb0bc3d55adaad9ac8586c1140f2cec581c6b8b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Tue, 5 Jul 2022 14:59:28 +0200 Subject: [PATCH 31/49] Continue cleanup of flux module. --- ebos/eclfluxmoduletpfa.hh | 150 +++----------------------------------- 1 file changed, 12 insertions(+), 138 deletions(-) diff --git a/ebos/eclfluxmoduletpfa.hh b/ebos/eclfluxmoduletpfa.hh index a7278f223..61af6f368 100644 --- a/ebos/eclfluxmoduletpfa.hh +++ b/ebos/eclfluxmoduletpfa.hh @@ -232,7 +232,6 @@ public: ) { - // check shortcut: if the mobility of the phase is zero in the interior as // well as the exterior DOF, we can skip looking at the phase. if (intQuantsIn.mobility(phaseIdx) <= 0.0 && @@ -275,7 +274,6 @@ public: else { // if the pressure difference is zero, we chose the DOF which has the // larger volume associated to it as upstream DOF - if (Vin > Vex) { upIdx = interiorDofIdx; dnIdx = exteriorDofIdx; @@ -314,113 +312,14 @@ public: } } - // static void volumeAndPhasePressureDifferences(short (&upIdx)[numPhases] , - // Evaluation (&volumeFlux)[numPhases], - // Evaluation (&pressureDifferences)[numPhases], - // const Problem& problem, - // const unsigned globalIndexIn, - // const unsinged globalIndexOut, - // const IntensiveQuantities& intQuantsIn, - // const IntensiveQuantities& intQuantsIn, - // const unsinged timeIdx) - // { - - // //Valgrind::SetUndefined(*this); - // //const auto& problem = elemCtx.problem(); - // //const auto& stencil = elemCtx.stencil(timeIdx); - // //const auto& scvf = stencil.interiorFace(scvfIdx); - // //unsigned interiorDofIdx = scvf.interiorIndex(); - // //unsigned exteriorDofIdx = scvf.exteriorIndex(); - // //const auto& globalIndexIn = stencil.globalSpaceIndex(interiorDofIdx); - // //const auto& globalIndexEx = stencil.globalSpaceIndex(exteriorDofIdx); - - - // assert(interiorDofIdx != exteriorDofIdx); - - // //unsigned I = stencil.globalSpaceIndex(interiorDofIdx_); - // //unsigned J = stencil.globalSpaceIndex(exteriorDofIdx_); - // Scalar Vin = model.dofTotalVolume(globalIndexIn, /*timeIdx=*/0); - // Scalar Vex = model.dofTotalVolume(globalIndexOut, /*timeIdx=*/0); - - - // Scalar trans = problem.transmissibility(globalIndexIn,globalIndexOut); - // Scalar faceArea = problem.area(globalIndexIn,globalIndexOut); - // Scalar thpres = problem.thresholdPressure(globalIndexIn, globalIndexEx); - - // // estimate the gravity correction: for performance reasons we use a simplified - // // approach for this flux module that assumes that gravity is constant and always - // // acts into the downwards direction. (i.e., no centrifuge experiments, sorry.) - // constexpr Scalar g = 9.8; - - - // // this is quite hacky because the dune grid interface does not provide a - // // cellCenterDepth() method (so we ask the problem to provide it). The "good" - // // solution would be to take the Z coordinate of the element centroids, but since - // // ECL seems to like to be inconsistent on that front, it needs to be done like - // // here... - // Scalar zIn = problem.dofCenterDepth(globalIndexIn, timeIdx); - // Scalar zEx = problem.dofCenterDepth(globalIndexOut, timeIdx); - - // // the distances from the DOF's depths. (i.e., the additional depth of the - // // exterior DOF) - // Scalar distZ = zIn - zEx; - - // for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { - // if (!FluidSystem::phaseIsActive(phaseIdx)) - // continue; - // short dnIdx; - // calculatePhasePressureDiff_(upIdx[phaseIdx], - // dnIdx, - // pressureDifferences[phaseIdx], - // intQuantsIn, - // intQuantsEx, - // timeIdx,//input - // phaseIdx,//input - // interiorDofIdx,//input - // exteriorDofIdx,//intput - // Vin, - // Vex, - // globalIndexIn, - // globalIndexEx, - // distZ*g, - // thpres); - // if(pressureDifferences[phaseIdx] == 0){ - // volumeFlux[phaseIdx] = 0.0; - // continue; - // } - // IntensiveQuantities up; - // unsigned globalIndex; - // if(upIdx[phaseIdx] == interiorDofIdx){ - // up = intQuantsIn; - // globalIndex = globalIndexIn; - // }else{ - // up = intQuantsEx; - // globalIndex = globalIndexEx; - // } - // // TODO: should the rock compaction transmissibility multiplier be upstreamed - // // or averaged? all fluids should see the same compaction?! - // //const auto& globalIndex = stencil.globalSpaceIndex(upstreamIdx); - // const Evaluation& transMult = - // problem.template rockCompTransMultiplier(up, globalIndex); - - // if (upIdx[phaseIdx] == interiorDofIdx) - // volumeFlux[phaseIdx] = - // pressureDifferences[phaseIdx]*up.mobility(phaseIdx)*transMult*(-trans/faceArea); - // else - // volumeFlux[phaseIdx] = - // pressureDifferences[phaseIdx]*(Toolbox::value(up.mobility(phaseIdx))*Toolbox::value(transMult)*(-trans/faceArea)); - - // } - // } - static void volumeAndPhasePressureDifferences(short (&upIdx)[numPhases] , Evaluation (&volumeFlux)[numPhases], Evaluation (&pressureDifferences)[numPhases], const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) { - - //Valgrind::SetUndefined(*this); + + // Valgrind::SetUndefined(*this); const auto& problem = elemCtx.problem(); const auto& stencil = elemCtx.stencil(timeIdx); @@ -429,16 +328,14 @@ public: unsigned exteriorDofIdx = scvf.exteriorIndex(); const auto& globalIndexIn = stencil.globalSpaceIndex(interiorDofIdx); const auto& globalIndexEx = stencil.globalSpaceIndex(exteriorDofIdx); - - + assert(interiorDofIdx != exteriorDofIdx); - //unsigned I = stencil.globalSpaceIndex(interiorDofIdx_); - //unsigned J = stencil.globalSpaceIndex(exteriorDofIdx_); + // unsigned I = stencil.globalSpaceIndex(interiorDofIdx_); + // unsigned J = stencil.globalSpaceIndex(exteriorDofIdx_); Scalar Vin = elemCtx.dofVolume(interiorDofIdx, /*timeIdx=*/0); Scalar Vex = elemCtx.dofVolume(exteriorDofIdx, /*timeIdx=*/0); - - + Scalar trans = problem.transmissibility(elemCtx, interiorDofIdx, exteriorDofIdx); Scalar faceArea = scvf.area(); Scalar thpres = problem.thresholdPressure(globalIndexIn, globalIndexEx); @@ -482,35 +379,25 @@ public: globalIndexEx, distZ*g, thpres); - if(pressureDifferences[phaseIdx] == 0){ + if (pressureDifferences[phaseIdx] == 0) { volumeFlux[phaseIdx] = 0.0; continue; } - IntensiveQuantities up; - //unsigned globalIndex; - if(upIdx[phaseIdx] == interiorDofIdx){ - up = intQuantsIn; - // globalIndex = globalIndexIn; - }else{ - up = intQuantsEx; - //globalIndex = globalIndexEx; - } + + const IntensiveQuantities& up = (upIdx[phaseIdx] == interiorDofIdx) ? intQuantsIn : intQuantsEx; // TODO: should the rock compaction transmissibility multiplier be upstreamed // or averaged? all fluids should see the same compaction?! - //const auto& globalIndex = stencil.globalSpaceIndex(upstreamIdx); const Evaluation& transMult = up.rockCompTransMultiplier(); - //const Evaluation& transMult = - // problem.template rockCompTransMultiplier(up, globalIndex); - + if (upIdx[phaseIdx] == interiorDofIdx) volumeFlux[phaseIdx] = pressureDifferences[phaseIdx]*up.mobility(phaseIdx)*transMult*(-trans/faceArea); else volumeFlux[phaseIdx] = pressureDifferences[phaseIdx]*(Toolbox::value(up.mobility(phaseIdx))*Toolbox::value(transMult)*(-trans/faceArea)); - } } + protected: /*! * \brief Update the required gradients for interior faces @@ -582,21 +469,9 @@ protected: continue; } const IntensiveQuantities& up = (upIdx_[phaseIdx] == interiorDofIdx_) ? intQuantsIn : intQuantsEx; - //unsigned globalIndex; - // if(upIdx_[phaseIdx] == interiorDofIdx_){ - // up = intQuantsIn; - // //globalIndex = I; - // }else{ - // up = intQuantsEx; - // //globalIndex = J; - // } // TODO: should the rock compaction transmissibility multiplier be upstreamed // or averaged? all fluids should see the same compaction?! - //const auto& globalIndex = stencil.globalSpaceIndex(upstreamIdx); - //NB as long as this is upwinded it could be an intensive quantity const Evaluation& transMult = up.rockCompTransMultiplier(); - // const Evaluation& transMult = - // problem.template rockCompTransMultiplier(up, globalIndex); if (upIdx_[phaseIdx] == interiorDofIdx_) volumeFlux_[phaseIdx] = @@ -617,7 +492,7 @@ protected: unsigned timeIdx, const FluidState& exFluidState) { - throw std::invalid_argument("No calculateGradients for boundary"); + // throw std::invalid_argument("No calculateGradients for boundary"); const auto& problem = elemCtx.problem(); bool enableBoundaryMassFlux = problem.nonTrivialBoundaryConditions(); @@ -693,7 +568,6 @@ protected: // deal with water induced rock compaction const double transMult = Toolbox::value(up.rockCompTransMultiplier()); transModified *= transMult; - //problem.template rockCompTransMultiplier(up, stencil.globalSpaceIndex(upstreamIdx)); volumeFlux_[phaseIdx] = pressureDifference_[phaseIdx]*up.mobility(phaseIdx)*(-transModified/faceArea); From 9603a1643e31d04a2efb52426509640949eada2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Tue, 5 Jul 2022 15:26:26 +0200 Subject: [PATCH 32/49] Continue refactoring flux module. At current commit, see differences (added this commit). --- ebos/eclfluxmoduletpfa.hh | 121 ++++++++------------------------------ 1 file changed, 24 insertions(+), 97 deletions(-) diff --git a/ebos/eclfluxmoduletpfa.hh b/ebos/eclfluxmoduletpfa.hh index 61af6f368..de16f88bb 100644 --- a/ebos/eclfluxmoduletpfa.hh +++ b/ebos/eclfluxmoduletpfa.hh @@ -223,12 +223,12 @@ public: const unsigned phaseIdx, const unsigned interiorDofIdx, const unsigned exteriorDofIdx, - const Scalar& Vin, - const Scalar& Vex, - const unsigned& globalIndexIn, - const unsigned& globalIndexEx, - const Scalar& distZg, - const Scalar& thpres + const Scalar Vin, + const Scalar Vex, + const unsigned globalIndexIn, + const unsigned globalIndexEx, + const Scalar distZg, + const Scalar thpres ) { @@ -252,7 +252,7 @@ public: const Evaluation& pressureInterior = intQuantsIn.fluidState().pressure(phaseIdx); Evaluation pressureExterior = Toolbox::value(intQuantsEx.fluidState().pressure(phaseIdx)); if (enableExtbo) // added stability; particulary useful for solvent migrating in pure water - // where the solvent fraction displays a 0/1 behaviour ... + // where the solvent fraction displays a 0/1 behaviour ... pressureExterior += Toolbox::value(rhoAvg)*(distZg); else pressureExterior += rhoAvg*(distZg); @@ -313,32 +313,28 @@ public: } - static void volumeAndPhasePressureDifferences(short (&upIdx)[numPhases] , - Evaluation (&volumeFlux)[numPhases], - Evaluation (&pressureDifferences)[numPhases], - const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + static void volumeAndPhasePressureDifferences(short (&upIdx)[numPhases], + short (&dnIdx)[numPhases], + Evaluation (&volumeFlux)[numPhases], + Evaluation (&pressureDifferences)[numPhases], + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) { - - // Valgrind::SetUndefined(*this); - const auto& problem = elemCtx.problem(); const auto& stencil = elemCtx.stencil(timeIdx); const auto& scvf = stencil.interiorFace(scvfIdx); unsigned interiorDofIdx = scvf.interiorIndex(); unsigned exteriorDofIdx = scvf.exteriorIndex(); - const auto& globalIndexIn = stencil.globalSpaceIndex(interiorDofIdx); - const auto& globalIndexEx = stencil.globalSpaceIndex(exteriorDofIdx); assert(interiorDofIdx != exteriorDofIdx); - // unsigned I = stencil.globalSpaceIndex(interiorDofIdx_); - // unsigned J = stencil.globalSpaceIndex(exteriorDofIdx_); - Scalar Vin = elemCtx.dofVolume(interiorDofIdx, /*timeIdx=*/0); - Scalar Vex = elemCtx.dofVolume(exteriorDofIdx, /*timeIdx=*/0); + unsigned I = stencil.globalSpaceIndex(interiorDofIdx); + unsigned J = stencil.globalSpaceIndex(exteriorDofIdx); Scalar trans = problem.transmissibility(elemCtx, interiorDofIdx, exteriorDofIdx); Scalar faceArea = scvf.area(); - Scalar thpres = problem.thresholdPressure(globalIndexIn, globalIndexEx); + Scalar thpres = problem.thresholdPressure(I, J); // estimate the gravity correction: for performance reasons we use a simplified // approach for this flux module that assumes that gravity is constant and always @@ -360,12 +356,14 @@ public: // exterior DOF) Scalar distZ = zIn - zEx; + Scalar Vin = elemCtx.dofVolume(interiorDofIdx, /*timeIdx=*/0); + Scalar Vex = elemCtx.dofVolume(exteriorDofIdx, /*timeIdx=*/0); + for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { if (!FluidSystem::phaseIsActive(phaseIdx)) continue; - short dnIdx; calculatePhasePressureDiff_(upIdx[phaseIdx], - dnIdx, + dnIdx[phaseIdx], pressureDifferences[phaseIdx], intQuantsIn, intQuantsEx, @@ -375,8 +373,8 @@ public: exteriorDofIdx,//intput Vin, Vex, - globalIndexIn, - globalIndexEx, + I, + J, distZ*g, thpres); if (pressureDifferences[phaseIdx] == 0) { @@ -408,78 +406,7 @@ protected: Valgrind::SetUndefined(*this); - const auto& problem = elemCtx.problem(); - const auto& stencil = elemCtx.stencil(timeIdx); - const auto& scvf = stencil.interiorFace(scvfIdx); - - interiorDofIdx_ = scvf.interiorIndex(); - exteriorDofIdx_ = scvf.exteriorIndex(); - assert(interiorDofIdx_ != exteriorDofIdx_); - - unsigned I = stencil.globalSpaceIndex(interiorDofIdx_); - unsigned J = stencil.globalSpaceIndex(exteriorDofIdx_); - - Scalar trans = problem.transmissibility(elemCtx, interiorDofIdx_, exteriorDofIdx_); - Scalar faceArea = scvf.area(); - Scalar thpres = problem.thresholdPressure(I, J); - - // estimate the gravity correction: for performance reasons we use a simplified - // approach for this flux module that assumes that gravity is constant and always - // acts into the downwards direction. (i.e., no centrifuge experiments, sorry.) - Scalar g = elemCtx.problem().gravity()[dimWorld - 1]; - - const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx_, timeIdx); - const auto& intQuantsEx = elemCtx.intensiveQuantities(exteriorDofIdx_, timeIdx); - - // this is quite hacky because the dune grid interface does not provide a - // cellCenterDepth() method (so we ask the problem to provide it). The "good" - // solution would be to take the Z coordinate of the element centroids, but since - // ECL seems to like to be inconsistent on that front, it needs to be done like - // here... - Scalar zIn = problem.dofCenterDepth(elemCtx, interiorDofIdx_, timeIdx); - Scalar zEx = problem.dofCenterDepth(elemCtx, exteriorDofIdx_, timeIdx); - - // the distances from the DOF's depths. (i.e., the additional depth of the - // exterior DOF) - Scalar distZ = zIn - zEx; - - Scalar Vin = elemCtx.dofVolume(interiorDofIdx_, /*timeIdx=*/0); - Scalar Vex = elemCtx.dofVolume(exteriorDofIdx_, /*timeIdx=*/0); - - for (unsigned phaseIdx = 0; phaseIdx < numPhases; phaseIdx++) { - if (!FluidSystem::phaseIsActive(phaseIdx)) - continue; - calculatePhasePressureDiff_(upIdx_[phaseIdx], - dnIdx_[phaseIdx], - pressureDifference_[phaseIdx], - intQuantsIn, - intQuantsEx, - timeIdx, // input - phaseIdx, // input - interiorDofIdx_, // input - exteriorDofIdx_, // intput - Vin, - Vex, - I, - J, - distZ * g, - thpres); - if (pressureDifference_[phaseIdx] == 0) { - volumeFlux_[phaseIdx] = 0.0; - continue; - } - const IntensiveQuantities& up = (upIdx_[phaseIdx] == interiorDofIdx_) ? intQuantsIn : intQuantsEx; - // TODO: should the rock compaction transmissibility multiplier be upstreamed - // or averaged? all fluids should see the same compaction?! - const Evaluation& transMult = up.rockCompTransMultiplier(); - - if (upIdx_[phaseIdx] == interiorDofIdx_) - volumeFlux_[phaseIdx] = - pressureDifference_[phaseIdx]*up.mobility(phaseIdx)*transMult*(-trans/faceArea); - else - volumeFlux_[phaseIdx] = - pressureDifference_[phaseIdx]*(Toolbox::value(up.mobility(phaseIdx))*Toolbox::value(transMult)*(-trans/faceArea)); - } + volumeAndPhasePressureDifferences(upIdx_ , dnIdx_, volumeFlux_, pressureDifference_, elemCtx, scvfIdx, timeIdx); } From 8c0a8eda2c1f36d60f375cafa95beb55dfc8435d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Tue, 5 Jul 2022 15:51:23 +0200 Subject: [PATCH 33/49] Fix: Use correct gravity. --- ebos/eclfluxmoduletpfa.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebos/eclfluxmoduletpfa.hh b/ebos/eclfluxmoduletpfa.hh index de16f88bb..063c1236a 100644 --- a/ebos/eclfluxmoduletpfa.hh +++ b/ebos/eclfluxmoduletpfa.hh @@ -339,7 +339,7 @@ public: // estimate the gravity correction: for performance reasons we use a simplified // approach for this flux module that assumes that gravity is constant and always // acts into the downwards direction. (i.e., no centrifuge experiments, sorry.) - constexpr Scalar g = 9.8; + Scalar g = elemCtx.problem().gravity()[dimWorld - 1]; const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx, timeIdx); const auto& intQuantsEx = elemCtx.intensiveQuantities(exteriorDofIdx, timeIdx); From d61e5781943ee43a23650aaba281caf5fb81ec4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Tue, 5 Jul 2022 15:55:34 +0200 Subject: [PATCH 34/49] Remove unneeded data member. --- ebos/eclfluxmoduletpfa.hh | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/ebos/eclfluxmoduletpfa.hh b/ebos/eclfluxmoduletpfa.hh index 063c1236a..8896eed61 100644 --- a/ebos/eclfluxmoduletpfa.hh +++ b/ebos/eclfluxmoduletpfa.hh @@ -429,7 +429,7 @@ protected: const auto& stencil = elemCtx.stencil(timeIdx); const auto& scvf = stencil.boundaryFace(scvfIdx); - interiorDofIdx_ = scvf.interiorIndex(); + unsigned interiorDofIdx = scvf.interiorIndex(); Scalar trans = problem.transmissibilityBoundary(elemCtx, scvfIdx); Scalar faceArea = scvf.area(); @@ -439,14 +439,14 @@ protected: // acts into the downwards direction. (i.e., no centrifuge experiments, sorry.) Scalar g = elemCtx.problem().gravity()[dimWorld - 1]; - const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx_, timeIdx); + const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx, timeIdx); // this is quite hacky because the dune grid interface does not provide a // cellCenterDepth() method (so we ask the problem to provide it). The "good" // solution would be to take the Z coordinate of the element centroids, but since // ECL seems to like to be inconsistent on that front, it needs to be done like // here... - Scalar zIn = problem.dofCenterDepth(elemCtx, interiorDofIdx_, timeIdx); + Scalar zIn = problem.dofCenterDepth(elemCtx, interiorDofIdx, timeIdx); Scalar zEx = scvf.integrationPos()[dimWorld - 1]; // the distances from the DOF's depths. (i.e., the additional depth of the @@ -475,17 +475,17 @@ protected: // global index is regarded to be the upstream one. if (pressureDifference_[phaseIdx] > 0.0) { upIdx_[phaseIdx] = -1; - dnIdx_[phaseIdx] = interiorDofIdx_; + dnIdx_[phaseIdx] = interiorDofIdx; } else { - upIdx_[phaseIdx] = interiorDofIdx_; + upIdx_[phaseIdx] = interiorDofIdx; dnIdx_[phaseIdx] = -1; } Evaluation transModified = trans; short upstreamIdx = upstreamIndex_(phaseIdx); - if (upstreamIdx == interiorDofIdx_) { + if (upstreamIdx == interiorDofIdx) { // this is slightly hacky because in the automatic differentiation case, it // only works for the element centered finite volume method. for ebos this @@ -507,7 +507,7 @@ protected: // interior element. TODO: this could probably be done more efficiently const auto& matParams = elemCtx.problem().materialLawParams(elemCtx, - interiorDofIdx_, + interiorDofIdx, /*timeIdx=*/0); typename FluidState::Scalar kr[numPhases]; MaterialLaw::relativePermeabilities(kr, matParams, exFluidState); @@ -547,8 +547,6 @@ private: Evaluation pressureDifference_[numPhases]; // the local indices of the interior and exterior degrees of freedom - unsigned short interiorDofIdx_; - unsigned short exteriorDofIdx_; short upIdx_[numPhases]; short dnIdx_[numPhases]; }; From 0bf508daf779ea305362c236b2dd203fd4893e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Wed, 6 Jul 2022 10:26:31 +0200 Subject: [PATCH 35/49] Refactor EclFluxModule. This makes that class have the same methods as the EclFluxModuleTpfa, so the latter can be removed. --- ebos/eclfluxmodule.hh | 285 ++++++++++++++++++++++++------------------ 1 file changed, 164 insertions(+), 121 deletions(-) diff --git a/ebos/eclfluxmodule.hh b/ebos/eclfluxmodule.hh index f8f7b2f13..eed1de67a 100644 --- a/ebos/eclfluxmodule.hh +++ b/ebos/eclfluxmodule.hh @@ -100,6 +100,7 @@ class EclTransExtensiveQuantities { using Implementation = GetPropType; + using IntensiveQuantities = GetPropType; using FluidSystem = GetPropType; using ElementContext = GetPropType; using Scalar = GetPropType; @@ -206,25 +207,26 @@ protected: void updatePolymer(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) { asImp_().updateShearMultipliers(elemCtx, scvfIdx, timeIdx); } - /*! - * \brief Update the required gradients for interior faces - */ - void calculateGradients_(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + static void volumeAndPhasePressureDifferences(short (&upIdx)[numPhases], + short (&dnIdx)[numPhases], + Evaluation (&volumeFlux)[numPhases], + Evaluation (&pressureDifferences)[numPhases], + const ElementContext& elemCtx, + unsigned scvfIdx, + unsigned timeIdx) { - Valgrind::SetUndefined(*this); - const auto& problem = elemCtx.problem(); const auto& stencil = elemCtx.stencil(timeIdx); const auto& scvf = stencil.interiorFace(scvfIdx); + unsigned interiorDofIdx = scvf.interiorIndex(); + unsigned exteriorDofIdx = scvf.exteriorIndex(); - interiorDofIdx_ = scvf.interiorIndex(); - exteriorDofIdx_ = scvf.exteriorIndex(); - assert(interiorDofIdx_ != exteriorDofIdx_); + assert(interiorDofIdx != exteriorDofIdx); - unsigned I = stencil.globalSpaceIndex(interiorDofIdx_); - unsigned J = stencil.globalSpaceIndex(exteriorDofIdx_); + unsigned I = stencil.globalSpaceIndex(interiorDofIdx); + unsigned J = stencil.globalSpaceIndex(exteriorDofIdx); - Scalar trans = problem.transmissibility(elemCtx, interiorDofIdx_, exteriorDofIdx_); + Scalar trans = problem.transmissibility(elemCtx, interiorDofIdx, exteriorDofIdx); Scalar faceArea = scvf.area(); Scalar thpres = problem.thresholdPressure(I, J); @@ -233,129 +235,171 @@ protected: // acts into the downwards direction. (i.e., no centrifuge experiments, sorry.) Scalar g = elemCtx.problem().gravity()[dimWorld - 1]; - const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx_, timeIdx); - const auto& intQuantsEx = elemCtx.intensiveQuantities(exteriorDofIdx_, timeIdx); + const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx, timeIdx); + const auto& intQuantsEx = elemCtx.intensiveQuantities(exteriorDofIdx, timeIdx); // this is quite hacky because the dune grid interface does not provide a // cellCenterDepth() method (so we ask the problem to provide it). The "good" // solution would be to take the Z coordinate of the element centroids, but since // ECL seems to like to be inconsistent on that front, it needs to be done like // here... - Scalar zIn = problem.dofCenterDepth(elemCtx, interiorDofIdx_, timeIdx); - Scalar zEx = problem.dofCenterDepth(elemCtx, exteriorDofIdx_, timeIdx); + Scalar zIn = problem.dofCenterDepth(elemCtx, interiorDofIdx, timeIdx); + Scalar zEx = problem.dofCenterDepth(elemCtx, exteriorDofIdx, timeIdx); // the distances from the DOF's depths. (i.e., the additional depth of the // exterior DOF) Scalar distZ = zIn - zEx; + Scalar Vin = elemCtx.dofVolume(interiorDofIdx, /*timeIdx=*/0); + Scalar Vex = elemCtx.dofVolume(exteriorDofIdx, /*timeIdx=*/0); + for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { if (!FluidSystem::phaseIsActive(phaseIdx)) continue; - - // check shortcut: if the mobility of the phase is zero in the interior as - // well as the exterior DOF, we can skip looking at the phase. - if (intQuantsIn.mobility(phaseIdx) <= 0.0 && - intQuantsEx.mobility(phaseIdx) <= 0.0) - { - upIdx_[phaseIdx] = interiorDofIdx_; - dnIdx_[phaseIdx] = exteriorDofIdx_; - pressureDifference_[phaseIdx] = 0.0; - volumeFlux_[phaseIdx] = 0.0; + calculatePhasePressureDiff_(upIdx[phaseIdx], + dnIdx[phaseIdx], + pressureDifferences[phaseIdx], + intQuantsIn, + intQuantsEx, + timeIdx,//input + phaseIdx,//input + interiorDofIdx,//input + exteriorDofIdx,//intput + Vin, + Vex, + I, + J, + distZ*g, + thpres); + if (pressureDifferences[phaseIdx] == 0) { + volumeFlux[phaseIdx] = 0.0; continue; } - // do the gravity correction: compute the hydrostatic pressure for the - // external at the depth of the internal one - const Evaluation& rhoIn = intQuantsIn.fluidState().density(phaseIdx); - Scalar rhoEx = Toolbox::value(intQuantsEx.fluidState().density(phaseIdx)); - Evaluation rhoAvg = (rhoIn + rhoEx)/2; - - const Evaluation& pressureInterior = intQuantsIn.fluidState().pressure(phaseIdx); - Evaluation pressureExterior = Toolbox::value(intQuantsEx.fluidState().pressure(phaseIdx)); - if (enableExtbo) // added stability; particulary useful for solvent migrating in pure water - // where the solvent fraction displays a 0/1 behaviour ... - pressureExterior += Toolbox::value(rhoAvg)*(distZ*g); - else - pressureExterior += rhoAvg*(distZ*g); - - pressureDifference_[phaseIdx] = pressureExterior - pressureInterior; - - // decide the upstream index for the phase. for this we make sure that the - // degree of freedom which is regarded upstream if both pressures are equal - // is always the same: if the pressure is equal, the DOF with the lower - // global index is regarded to be the upstream one. - if (pressureDifference_[phaseIdx] > 0.0) { - upIdx_[phaseIdx] = exteriorDofIdx_; - dnIdx_[phaseIdx] = interiorDofIdx_; - } - else if (pressureDifference_[phaseIdx] < 0.0) { - upIdx_[phaseIdx] = interiorDofIdx_; - dnIdx_[phaseIdx] = exteriorDofIdx_; - } - else { - // if the pressure difference is zero, we chose the DOF which has the - // larger volume associated to it as upstream DOF - Scalar Vin = elemCtx.dofVolume(interiorDofIdx_, /*timeIdx=*/0); - Scalar Vex = elemCtx.dofVolume(exteriorDofIdx_, /*timeIdx=*/0); - if (Vin > Vex) { - upIdx_[phaseIdx] = interiorDofIdx_; - dnIdx_[phaseIdx] = exteriorDofIdx_; - } - else if (Vin < Vex) { - upIdx_[phaseIdx] = exteriorDofIdx_; - dnIdx_[phaseIdx] = interiorDofIdx_; - } - else { - assert(Vin == Vex); - // if the volumes are also equal, we pick the DOF which exhibits the - // smaller global index - if (I < J) { - upIdx_[phaseIdx] = interiorDofIdx_; - dnIdx_[phaseIdx] = exteriorDofIdx_; - } - else { - upIdx_[phaseIdx] = exteriorDofIdx_; - dnIdx_[phaseIdx] = interiorDofIdx_; - } - } - } - - // apply the threshold pressure for the intersection. note that the concept - // of threshold pressure is a quite big hack that only makes sense for ECL - // datasets. (and even there, its physical justification is quite - // questionable IMO.) - if (std::abs(Toolbox::value(pressureDifference_[phaseIdx])) > thpres) { - if (pressureDifference_[phaseIdx] < 0.0) - pressureDifference_[phaseIdx] += thpres; - else - pressureDifference_[phaseIdx] -= thpres; - } - else { - pressureDifference_[phaseIdx] = 0.0; - volumeFlux_[phaseIdx] = 0.0; - continue; - } - - // this is slightly hacky because in the automatic differentiation case, it - // only works for the element centered finite volume method. for ebos this - // does not matter, though. - unsigned upstreamIdx = upstreamIndex_(phaseIdx); - const auto& up = elemCtx.intensiveQuantities(upstreamIdx, timeIdx); - + const IntensiveQuantities& up = (upIdx[phaseIdx] == interiorDofIdx) ? intQuantsIn : intQuantsEx; // TODO: should the rock compaction transmissibility multiplier be upstreamed // or averaged? all fluids should see the same compaction?! - const Evaluation& transMult = - problem.template rockCompTransMultiplier(up, stencil.globalSpaceIndex(upstreamIdx)); + const Evaluation& transMult = up.rockCompTransMultiplier(); - if (upstreamIdx == interiorDofIdx_) - volumeFlux_[phaseIdx] = - pressureDifference_[phaseIdx]*up.mobility(phaseIdx)*transMult*(-trans/faceArea); + if (upIdx[phaseIdx] == interiorDofIdx) + volumeFlux[phaseIdx] = + pressureDifferences[phaseIdx]*up.mobility(phaseIdx)*transMult*(-trans/faceArea); else - volumeFlux_[phaseIdx] = - pressureDifference_[phaseIdx]*(Toolbox::value(up.mobility(phaseIdx))*Toolbox::value(transMult)*(-trans/faceArea)); + volumeFlux[phaseIdx] = + pressureDifferences[phaseIdx]*(Toolbox::value(up.mobility(phaseIdx))*Toolbox::value(transMult)*(-trans/faceArea)); } } + template + static void calculatePhasePressureDiff_(short& upIdx, + short& dnIdx, + EvalType& pressureDifference, + const IntensiveQuantities& intQuantsIn, + const IntensiveQuantities& intQuantsEx, + const unsigned timeIdx, + const unsigned phaseIdx, + const unsigned interiorDofIdx, + const unsigned exteriorDofIdx, + const Scalar Vin, + const Scalar Vex, + const unsigned globalIndexIn, + const unsigned globalIndexEx, + const Scalar distZg, + const Scalar thpres + ) + { + + // check shortcut: if the mobility of the phase is zero in the interior as + // well as the exterior DOF, we can skip looking at the phase. + if (intQuantsIn.mobility(phaseIdx) <= 0.0 && + intQuantsEx.mobility(phaseIdx) <= 0.0) + { + upIdx = interiorDofIdx; + dnIdx = exteriorDofIdx; + pressureDifference = 0.0; + return; + } + + // do the gravity correction: compute the hydrostatic pressure for the + // external at the depth of the internal one + const Evaluation& rhoIn = intQuantsIn.fluidState().density(phaseIdx); + Scalar rhoEx = Toolbox::value(intQuantsEx.fluidState().density(phaseIdx)); + Evaluation rhoAvg = (rhoIn + rhoEx)/2; + + const Evaluation& pressureInterior = intQuantsIn.fluidState().pressure(phaseIdx); + Evaluation pressureExterior = Toolbox::value(intQuantsEx.fluidState().pressure(phaseIdx)); + if (enableExtbo) // added stability; particulary useful for solvent migrating in pure water + // where the solvent fraction displays a 0/1 behaviour ... + pressureExterior += Toolbox::value(rhoAvg)*(distZg); + else + pressureExterior += rhoAvg*(distZg); + + pressureDifference = pressureExterior - pressureInterior; + + // decide the upstream index for the phase. for this we make sure that the + // degree of freedom which is regarded upstream if both pressures are equal + // is always the same: if the pressure is equal, the DOF with the lower + // global index is regarded to be the upstream one. + if (pressureDifference > 0.0) { + upIdx = exteriorDofIdx; + dnIdx = interiorDofIdx; + } + else if (pressureDifference < 0.0) { + upIdx = interiorDofIdx; + dnIdx = exteriorDofIdx; + } + else { + // if the pressure difference is zero, we chose the DOF which has the + // larger volume associated to it as upstream DOF + if (Vin > Vex) { + upIdx = interiorDofIdx; + dnIdx = exteriorDofIdx; + } + else if (Vin < Vex) { + upIdx = exteriorDofIdx; + dnIdx = interiorDofIdx; + } + else { + assert(Vin == Vex); + // if the volumes are also equal, we pick the DOF which exhibits the + // smaller global index + if (globalIndexIn < globalIndexEx) { + upIdx = interiorDofIdx; + dnIdx = exteriorDofIdx; + } + else { + upIdx = exteriorDofIdx; + dnIdx = interiorDofIdx; + } + } + } + + // apply the threshold pressure for the intersection. note that the concept + // of threshold pressure is a quite big hack that only makes sense for ECL + // datasets. (and even there, its physical justification is quite + // questionable IMO.) + if (std::abs(Toolbox::value(pressureDifference)) > thpres) { + if (pressureDifference < 0.0) + pressureDifference += thpres; + else + pressureDifference -= thpres; + } + else { + pressureDifference = 0.0; + } + } + +protected: + /*! + * \brief Update the required gradients for interior faces + */ + void calculateGradients_(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) + { + Valgrind::SetUndefined(*this); + + volumeAndPhasePressureDifferences(upIdx_ , dnIdx_, volumeFlux_, pressureDifference_, elemCtx, scvfIdx, timeIdx); + } + /*! * \brief Update the required gradients for boundary faces */ @@ -374,7 +418,7 @@ protected: const auto& stencil = elemCtx.stencil(timeIdx); const auto& scvf = stencil.boundaryFace(scvfIdx); - interiorDofIdx_ = scvf.interiorIndex(); + unsigned interiorDofIdx = scvf.interiorIndex(); Scalar trans = problem.transmissibilityBoundary(elemCtx, scvfIdx); Scalar faceArea = scvf.area(); @@ -384,14 +428,14 @@ protected: // acts into the downwards direction. (i.e., no centrifuge experiments, sorry.) Scalar g = elemCtx.problem().gravity()[dimWorld - 1]; - const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx_, timeIdx); + const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx, timeIdx); // this is quite hacky because the dune grid interface does not provide a // cellCenterDepth() method (so we ask the problem to provide it). The "good" // solution would be to take the Z coordinate of the element centroids, but since // ECL seems to like to be inconsistent on that front, it needs to be done like // here... - Scalar zIn = problem.dofCenterDepth(elemCtx, interiorDofIdx_, timeIdx); + Scalar zIn = problem.dofCenterDepth(elemCtx, interiorDofIdx, timeIdx); Scalar zEx = scvf.integrationPos()[dimWorld - 1]; // the distances from the DOF's depths. (i.e., the additional depth of the @@ -420,17 +464,17 @@ protected: // global index is regarded to be the upstream one. if (pressureDifference_[phaseIdx] > 0.0) { upIdx_[phaseIdx] = -1; - dnIdx_[phaseIdx] = interiorDofIdx_; + dnIdx_[phaseIdx] = interiorDofIdx; } else { - upIdx_[phaseIdx] = interiorDofIdx_; + upIdx_[phaseIdx] = interiorDofIdx; dnIdx_[phaseIdx] = -1; } Evaluation transModified = trans; short upstreamIdx = upstreamIndex_(phaseIdx); - if (upstreamIdx == interiorDofIdx_) { + if (upstreamIdx == interiorDofIdx) { // this is slightly hacky because in the automatic differentiation case, it // only works for the element centered finite volume method. for ebos this @@ -438,7 +482,8 @@ protected: const auto& up = elemCtx.intensiveQuantities(upstreamIdx, timeIdx); // deal with water induced rock compaction - transModified *= problem.template rockCompTransMultiplier(up, stencil.globalSpaceIndex(upstreamIdx)); + const double transMult = Toolbox::value(up.rockCompTransMultiplier()); + transModified *= transMult; volumeFlux_[phaseIdx] = pressureDifference_[phaseIdx]*up.mobility(phaseIdx)*(-transModified/faceArea); @@ -451,7 +496,7 @@ protected: // interior element. TODO: this could probably be done more efficiently const auto& matParams = elemCtx.problem().materialLawParams(elemCtx, - interiorDofIdx_, + interiorDofIdx, /*timeIdx=*/0); typename FluidState::Scalar kr[numPhases]; MaterialLaw::relativePermeabilities(kr, matParams, exFluidState); @@ -491,8 +536,6 @@ private: Evaluation pressureDifference_[numPhases]; // the local indices of the interior and exterior degrees of freedom - unsigned short interiorDofIdx_; - unsigned short exteriorDofIdx_; short upIdx_[numPhases]; short dnIdx_[numPhases]; }; From 55b637aedd6825fef04c2cc2f0843770d8735121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Wed, 6 Jul 2022 10:27:42 +0200 Subject: [PATCH 36/49] Remove unneded class. All functionality is now in EclFluxModule. --- ebos/eclfluxmodule.hh | 2 + ebos/eclfluxmoduletpfa.hh | 556 -------------------------------------- 2 files changed, 2 insertions(+), 556 deletions(-) delete mode 100644 ebos/eclfluxmoduletpfa.hh diff --git a/ebos/eclfluxmodule.hh b/ebos/eclfluxmodule.hh index eed1de67a..aa75be352 100644 --- a/ebos/eclfluxmodule.hh +++ b/ebos/eclfluxmodule.hh @@ -207,6 +207,8 @@ protected: void updatePolymer(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) { asImp_().updateShearMultipliers(elemCtx, scvfIdx, timeIdx); } +public: + static void volumeAndPhasePressureDifferences(short (&upIdx)[numPhases], short (&dnIdx)[numPhases], Evaluation (&volumeFlux)[numPhases], diff --git a/ebos/eclfluxmoduletpfa.hh b/ebos/eclfluxmoduletpfa.hh deleted file mode 100644 index 8896eed61..000000000 --- a/ebos/eclfluxmoduletpfa.hh +++ /dev/null @@ -1,556 +0,0 @@ -// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- -// vi: set et ts=4 sw=4 sts=4: -/* - 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 2 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 . - - Consult the COPYING file in the top-level source directory of this - module for the precise wording of the license and the list of - copyright holders. -*/ -/*! - * \file - * - * \brief This file contains the flux module which is used for ECL problems - * - * This approach to fluxes is very specific to two-point flux approximation and applies - * what the Eclipse Technical Description calls the "NEWTRAN" transmissibility approach. - */ -#ifndef EWOMS_ECL_FLUX_TPFA_MODULE_HH -#define EWOMS_ECL_FLUX_TPFA_MODULE_HH - -#include -#include -#include - -#include - -#include -#include - -namespace Opm { - -template -class EclTransIntensiveQuantities; - -template -class EclTransExtensiveQuantities; - -template -class EclTransBaseProblem; - -// /*! -// * \ingroup EclBlackOilSimulator -// * \brief Specifies a flux module which uses ECL transmissibilities. -// */ -// template -// struct EclTransFluxModule -// { -// typedef EclTransIntensiveQuantities FluxIntensiveQuantities; -// typedef EclTransExtensiveQuantities FluxExtensiveQuantities; -// typedef EclTransBaseProblem FluxBaseProblem; - -// /*! -// * \brief Register all run-time parameters for the flux module. -// */ -// static void registerParameters() -// { } -// }; - -// /*! -// * \ingroup EclBlackOilSimulator -// * \brief Provides the defaults for the parameters required by the -// * transmissibility based volume flux calculation. -// */ -// template -// class EclTransBaseProblem -// { }; - -// /*! -// * \ingroup EclBlackOilSimulator -// * \brief Provides the intensive quantities for the ECL flux module -// */ -// template -// class EclTransIntensiveQuantities -// { -// using ElementContext = GetPropType; -// protected: -// void update_(const ElementContext&, unsigned, unsigned) -// { } -// }; - -/*! - * \ingroup EclBlackOilSimulator - * \brief Provides the ECL flux module - */ -template -class EclTransExtensiveQuantitiesTPFA -{ - using Implementation = GetPropType; - using IntensiveQuantities = GetPropType; - using FluidSystem = GetPropType; - using ElementContext = GetPropType; - using Scalar = GetPropType; - using Evaluation = GetPropType; - using GridView = GetPropType; - using MaterialLaw = GetPropType; - - enum { dimWorld = GridView::dimensionworld }; - enum { gasPhaseIdx = FluidSystem::gasPhaseIdx }; - enum { numPhases = FluidSystem::numPhases }; - enum { enableSolvent = getPropValue() }; - enum { enableExtbo = getPropValue() }; - enum { enableEnergy = getPropValue() }; - - typedef MathToolbox Toolbox; - typedef Dune::FieldVector DimVector; - typedef Dune::FieldVector EvalDimVector; - typedef Dune::FieldMatrix DimMatrix; - -public: - /*! - * \brief Return the intrinsic permeability tensor at a face [m^2] - */ - const DimMatrix& intrinsicPermeability() const - { - throw std::invalid_argument("The ECL transmissibility module does not provide an explicit intrinsic permeability"); - } - - /*! - * \brief Return the pressure potential gradient of a fluid phase at the - * face's integration point [Pa/m] - * - * \param phaseIdx The index of the fluid phase - */ - const EvalDimVector& potentialGrad(unsigned) const - { - throw std::invalid_argument("The ECL transmissibility module does not provide explicit potential gradients"); - } - - /*! - * \brief Return the gravity corrected pressure difference between the interior and - * the exterior of a face. - * - * \param phaseIdx The index of the fluid phase - */ - const Evaluation& pressureDifference(unsigned phaseIdx) const - { return pressureDifference_[phaseIdx]; } - - /*! - * \brief Return the filter velocity of a fluid phase at the face's integration point - * [m/s] - * - * \param phaseIdx The index of the fluid phase - */ - const EvalDimVector& filterVelocity(unsigned) const - { - throw std::invalid_argument("The ECL transmissibility module does not provide explicit filter velocities"); - } - - /*! - * \brief Return the volume flux of a fluid phase at the face's integration point - * \f$[m^3/s / m^2]\f$ - * - * This is the fluid volume of a phase per second and per square meter of face - * area. - * - * \param phaseIdx The index of the fluid phase - */ - const Evaluation& volumeFlux(unsigned phaseIdx) const - { - //throw std::invalid_argument("Should not acces volume flux for eclmoduletpfa"); - return volumeFlux_[phaseIdx]; - } - -protected: - /*! - * \brief Returns the local index of the degree of freedom in which is - * in upstream direction. - * - * i.e., the DOF which exhibits a higher effective pressure for - * the given phase. - */ - unsigned upstreamIndex_(unsigned phaseIdx) const - { - //throw std::invalid_argument("No upstreamIndex"); - assert(phaseIdx < numPhases); - - return upIdx_[phaseIdx]; - } - - /*! - * \brief Returns the local index of the degree of freedom in which is - * in downstream direction. - * - * i.e., the DOF which exhibits a lower effective pressure for the - * given phase. - */ - unsigned downstreamIndex_(unsigned phaseIdx) const - { - //throw std::invalid_argument("No downstream index"); - assert(phaseIdx < numPhases); - - return dnIdx_[phaseIdx]; - } - - void updateSolvent(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) - { asImp_().updateVolumeFluxTrans(elemCtx, scvfIdx, timeIdx); } - - void updatePolymer(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) - { asImp_().updateShearMultipliers(elemCtx, scvfIdx, timeIdx); } - - -public: - template - static void calculatePhasePressureDiff_(short& upIdx, - short& dnIdx, - EvalType& pressureDifference, - const IntensiveQuantities& intQuantsIn, - const IntensiveQuantities& intQuantsEx, - const unsigned timeIdx, - const unsigned phaseIdx, - const unsigned interiorDofIdx, - const unsigned exteriorDofIdx, - const Scalar Vin, - const Scalar Vex, - const unsigned globalIndexIn, - const unsigned globalIndexEx, - const Scalar distZg, - const Scalar thpres - ) - { - - // check shortcut: if the mobility of the phase is zero in the interior as - // well as the exterior DOF, we can skip looking at the phase. - if (intQuantsIn.mobility(phaseIdx) <= 0.0 && - intQuantsEx.mobility(phaseIdx) <= 0.0) - { - upIdx = interiorDofIdx; - dnIdx = exteriorDofIdx; - pressureDifference = 0.0; - return; - } - - // do the gravity correction: compute the hydrostatic pressure for the - // external at the depth of the internal one - const Evaluation& rhoIn = intQuantsIn.fluidState().density(phaseIdx); - Scalar rhoEx = Toolbox::value(intQuantsEx.fluidState().density(phaseIdx)); - Evaluation rhoAvg = (rhoIn + rhoEx)/2; - - const Evaluation& pressureInterior = intQuantsIn.fluidState().pressure(phaseIdx); - Evaluation pressureExterior = Toolbox::value(intQuantsEx.fluidState().pressure(phaseIdx)); - if (enableExtbo) // added stability; particulary useful for solvent migrating in pure water - // where the solvent fraction displays a 0/1 behaviour ... - pressureExterior += Toolbox::value(rhoAvg)*(distZg); - else - pressureExterior += rhoAvg*(distZg); - - pressureDifference = pressureExterior - pressureInterior; - - // decide the upstream index for the phase. for this we make sure that the - // degree of freedom which is regarded upstream if both pressures are equal - // is always the same: if the pressure is equal, the DOF with the lower - // global index is regarded to be the upstream one. - if (pressureDifference > 0.0) { - upIdx = exteriorDofIdx; - dnIdx = interiorDofIdx; - } - else if (pressureDifference < 0.0) { - upIdx = interiorDofIdx; - dnIdx = exteriorDofIdx; - } - else { - // if the pressure difference is zero, we chose the DOF which has the - // larger volume associated to it as upstream DOF - if (Vin > Vex) { - upIdx = interiorDofIdx; - dnIdx = exteriorDofIdx; - } - else if (Vin < Vex) { - upIdx = exteriorDofIdx; - dnIdx = interiorDofIdx; - } - else { - assert(Vin == Vex); - // if the volumes are also equal, we pick the DOF which exhibits the - // smaller global index - if (globalIndexIn < globalIndexEx) { - upIdx = interiorDofIdx; - dnIdx = exteriorDofIdx; - } - else { - upIdx = exteriorDofIdx; - dnIdx = interiorDofIdx; - } - } - } - - // apply the threshold pressure for the intersection. note that the concept - // of threshold pressure is a quite big hack that only makes sense for ECL - // datasets. (and even there, its physical justification is quite - // questionable IMO.) - if (std::abs(Toolbox::value(pressureDifference)) > thpres) { - if (pressureDifference < 0.0) - pressureDifference += thpres; - else - pressureDifference -= thpres; - } - else { - pressureDifference = 0.0; - } - } - - - static void volumeAndPhasePressureDifferences(short (&upIdx)[numPhases], - short (&dnIdx)[numPhases], - Evaluation (&volumeFlux)[numPhases], - Evaluation (&pressureDifferences)[numPhases], - const ElementContext& elemCtx, - unsigned scvfIdx, - unsigned timeIdx) - { - const auto& problem = elemCtx.problem(); - const auto& stencil = elemCtx.stencil(timeIdx); - const auto& scvf = stencil.interiorFace(scvfIdx); - unsigned interiorDofIdx = scvf.interiorIndex(); - unsigned exteriorDofIdx = scvf.exteriorIndex(); - - assert(interiorDofIdx != exteriorDofIdx); - - unsigned I = stencil.globalSpaceIndex(interiorDofIdx); - unsigned J = stencil.globalSpaceIndex(exteriorDofIdx); - - Scalar trans = problem.transmissibility(elemCtx, interiorDofIdx, exteriorDofIdx); - Scalar faceArea = scvf.area(); - Scalar thpres = problem.thresholdPressure(I, J); - - // estimate the gravity correction: for performance reasons we use a simplified - // approach for this flux module that assumes that gravity is constant and always - // acts into the downwards direction. (i.e., no centrifuge experiments, sorry.) - Scalar g = elemCtx.problem().gravity()[dimWorld - 1]; - - const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx, timeIdx); - const auto& intQuantsEx = elemCtx.intensiveQuantities(exteriorDofIdx, timeIdx); - - // this is quite hacky because the dune grid interface does not provide a - // cellCenterDepth() method (so we ask the problem to provide it). The "good" - // solution would be to take the Z coordinate of the element centroids, but since - // ECL seems to like to be inconsistent on that front, it needs to be done like - // here... - Scalar zIn = problem.dofCenterDepth(elemCtx, interiorDofIdx, timeIdx); - Scalar zEx = problem.dofCenterDepth(elemCtx, exteriorDofIdx, timeIdx); - - // the distances from the DOF's depths. (i.e., the additional depth of the - // exterior DOF) - Scalar distZ = zIn - zEx; - - Scalar Vin = elemCtx.dofVolume(interiorDofIdx, /*timeIdx=*/0); - Scalar Vex = elemCtx.dofVolume(exteriorDofIdx, /*timeIdx=*/0); - - for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { - if (!FluidSystem::phaseIsActive(phaseIdx)) - continue; - calculatePhasePressureDiff_(upIdx[phaseIdx], - dnIdx[phaseIdx], - pressureDifferences[phaseIdx], - intQuantsIn, - intQuantsEx, - timeIdx,//input - phaseIdx,//input - interiorDofIdx,//input - exteriorDofIdx,//intput - Vin, - Vex, - I, - J, - distZ*g, - thpres); - if (pressureDifferences[phaseIdx] == 0) { - volumeFlux[phaseIdx] = 0.0; - continue; - } - - const IntensiveQuantities& up = (upIdx[phaseIdx] == interiorDofIdx) ? intQuantsIn : intQuantsEx; - // TODO: should the rock compaction transmissibility multiplier be upstreamed - // or averaged? all fluids should see the same compaction?! - const Evaluation& transMult = up.rockCompTransMultiplier(); - - if (upIdx[phaseIdx] == interiorDofIdx) - volumeFlux[phaseIdx] = - pressureDifferences[phaseIdx]*up.mobility(phaseIdx)*transMult*(-trans/faceArea); - else - volumeFlux[phaseIdx] = - pressureDifferences[phaseIdx]*(Toolbox::value(up.mobility(phaseIdx))*Toolbox::value(transMult)*(-trans/faceArea)); - } - } - -protected: - /*! - * \brief Update the required gradients for interior faces - */ - void calculateGradients_(const ElementContext& elemCtx, unsigned scvfIdx, unsigned timeIdx) - { - //throw std::invalid_argument("No calculateGradients_"); - - Valgrind::SetUndefined(*this); - - volumeAndPhasePressureDifferences(upIdx_ , dnIdx_, volumeFlux_, pressureDifference_, elemCtx, scvfIdx, timeIdx); - } - - - /*! - * \brief Update the required gradients for boundary faces - */ - template - void calculateBoundaryGradients_(const ElementContext& elemCtx, - unsigned scvfIdx, - unsigned timeIdx, - const FluidState& exFluidState) - { - // throw std::invalid_argument("No calculateGradients for boundary"); - const auto& problem = elemCtx.problem(); - - bool enableBoundaryMassFlux = problem.nonTrivialBoundaryConditions(); - if (!enableBoundaryMassFlux) - return; - - const auto& stencil = elemCtx.stencil(timeIdx); - const auto& scvf = stencil.boundaryFace(scvfIdx); - - unsigned interiorDofIdx = scvf.interiorIndex(); - - Scalar trans = problem.transmissibilityBoundary(elemCtx, scvfIdx); - Scalar faceArea = scvf.area(); - - // estimate the gravity correction: for performance reasons we use a simplified - // approach for this flux module that assumes that gravity is constant and always - // acts into the downwards direction. (i.e., no centrifuge experiments, sorry.) - Scalar g = elemCtx.problem().gravity()[dimWorld - 1]; - - const auto& intQuantsIn = elemCtx.intensiveQuantities(interiorDofIdx, timeIdx); - - // this is quite hacky because the dune grid interface does not provide a - // cellCenterDepth() method (so we ask the problem to provide it). The "good" - // solution would be to take the Z coordinate of the element centroids, but since - // ECL seems to like to be inconsistent on that front, it needs to be done like - // here... - Scalar zIn = problem.dofCenterDepth(elemCtx, interiorDofIdx, timeIdx); - Scalar zEx = scvf.integrationPos()[dimWorld - 1]; - - // the distances from the DOF's depths. (i.e., the additional depth of the - // exterior DOF) - Scalar distZ = zIn - zEx; - - for (unsigned phaseIdx=0; phaseIdx < numPhases; phaseIdx++) { - if (!FluidSystem::phaseIsActive(phaseIdx)) - continue; - - // do the gravity correction: compute the hydrostatic pressure for the - // integration position - const Evaluation& rhoIn = intQuantsIn.fluidState().density(phaseIdx); - const auto& rhoEx = exFluidState.density(phaseIdx); - Evaluation rhoAvg = (rhoIn + rhoEx)/2; - - const Evaluation& pressureInterior = intQuantsIn.fluidState().pressure(phaseIdx); - Evaluation pressureExterior = exFluidState.pressure(phaseIdx); - pressureExterior += rhoAvg*(distZ*g); - - pressureDifference_[phaseIdx] = pressureExterior - pressureInterior; - - // decide the upstream index for the phase. for this we make sure that the - // degree of freedom which is regarded upstream if both pressures are equal - // is always the same: if the pressure is equal, the DOF with the lower - // global index is regarded to be the upstream one. - if (pressureDifference_[phaseIdx] > 0.0) { - upIdx_[phaseIdx] = -1; - dnIdx_[phaseIdx] = interiorDofIdx; - } - else { - upIdx_[phaseIdx] = interiorDofIdx; - dnIdx_[phaseIdx] = -1; - } - - Evaluation transModified = trans; - - short upstreamIdx = upstreamIndex_(phaseIdx); - if (upstreamIdx == interiorDofIdx) { - - // this is slightly hacky because in the automatic differentiation case, it - // only works for the element centered finite volume method. for ebos this - // does not matter, though. - const auto& up = elemCtx.intensiveQuantities(upstreamIdx, timeIdx); - - // deal with water induced rock compaction - const double transMult = Toolbox::value(up.rockCompTransMultiplier()); - transModified *= transMult; - - volumeFlux_[phaseIdx] = - pressureDifference_[phaseIdx]*up.mobility(phaseIdx)*(-transModified/faceArea); - - if (enableSolvent && phaseIdx == gasPhaseIdx) - asImp_().setSolventVolumeFlux( pressureDifference_[phaseIdx]*up.solventMobility()*(-transModified/faceArea)); - } - else { - // compute the phase mobility using the material law parameters of the - // interior element. TODO: this could probably be done more efficiently - const auto& matParams = - elemCtx.problem().materialLawParams(elemCtx, - interiorDofIdx, - /*timeIdx=*/0); - typename FluidState::Scalar kr[numPhases]; - MaterialLaw::relativePermeabilities(kr, matParams, exFluidState); - - const auto& mob = kr[phaseIdx]/exFluidState.viscosity(phaseIdx); - volumeFlux_[phaseIdx] = - pressureDifference_[phaseIdx]*mob*(-transModified/faceArea); - - // Solvent inflow is not yet supported - if (enableSolvent && phaseIdx == gasPhaseIdx) - asImp_().setSolventVolumeFlux(0.0); - } - } - } - - /*! - * \brief Update the volumetric fluxes for all fluid phases on the interior faces of the context - */ - void calculateFluxes_(const ElementContext&, unsigned, unsigned) - { } - - void calculateBoundaryFluxes_(const ElementContext&, unsigned, unsigned) - {} - -private: - Implementation& asImp_() - { return *static_cast(this); } - - const Implementation& asImp_() const - { return *static_cast(this); } - - // the volumetric flux of all phases [m^3/s] - Evaluation volumeFlux_[numPhases]; - - // the difference in effective pressure between the exterior and the interior degree - // of freedom [Pa] - Evaluation pressureDifference_[numPhases]; - - // the local indices of the interior and exterior degrees of freedom - short upIdx_[numPhases]; - short dnIdx_[numPhases]; -}; - -}// namespace Opm - -#endif From 750e7ac5dd920f5673b1853fd2edd284e3973894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Wed, 6 Jul 2022 10:38:42 +0200 Subject: [PATCH 37/49] Clean up and remove unnecessary parts from flow_blackoil_tpfa.cpp. --- flow/flow_blackoil_tpfa.cpp | 37 +++++-------------------------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp index d3af19aac..5ec7c4f20 100644 --- a/flow/flow_blackoil_tpfa.cpp +++ b/flow/flow_blackoil_tpfa.cpp @@ -25,8 +25,6 @@ #include #include #include -#include -#include namespace Opm { namespace Properties { @@ -38,48 +36,23 @@ namespace Opm { } } - - namespace Opm { namespace Properties { + template struct Linearizer { using type = TpfaLinearizer; }; - } -} -namespace Opm { - namespace Properties { + template struct LocalResidual { using type = BlackOilLocalResidualTPFA; }; + template //struct ElementContext { using type = SmallElementContext; }; struct ElementContext { using type = FvBaseElementContext; }; - } -} -namespace Opm{ - template - struct EclTransFluxModuleTPFA - { - typedef EclTransIntensiveQuantities FluxIntensiveQuantities; - typedef EclTransExtensiveQuantitiesTPFA FluxExtensiveQuantities; - typedef EclTransBaseProblem FluxBaseProblem; - /// \brief Register all run-time parameters for the flux module. - static void registerParameters() - { } - }; -} - - -namespace Opm { - namespace Properties { - - template - struct FluxModule { - using type = EclTransFluxModuleTPFA; - }; template struct IntensiveQuantities { - using type = BlackOilIntensiveQuantitiesSimple; + //using type = BlackOilIntensiveQuantitiesSimple; + using type = BlackOilIntensiveQuantities; }; } From 0da2b68e0b12df978dd01db509fc235dec8ee28a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Wed, 6 Jul 2022 23:35:04 +0200 Subject: [PATCH 38/49] Adapt to changed interface. --- ebos/ecltracermodel.hh | 4 +++- opm/simulators/aquifers/AquiferNumerical.hpp | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ebos/ecltracermodel.hh b/ebos/ecltracermodel.hh index 4240d60c8..3dfb1c6ad 100644 --- a/ebos/ecltracermodel.hh +++ b/ebos/ecltracermodel.hh @@ -247,9 +247,11 @@ protected: unsigned scvfIdx ){ short upIdxV[numPhases]; + short dnIdxV[numPhases]; Eval volumFlux[numPhases]; Eval pressureDifferences[numPhases]; - ExtensiveQuantities::volumeAndPhasePressureDifferences(upIdxV , + ExtensiveQuantities::volumeAndPhasePressureDifferences(upIdxV, + dnIdxV, volumFlux, pressureDifferences, elemCtx, diff --git a/opm/simulators/aquifers/AquiferNumerical.hpp b/opm/simulators/aquifers/AquiferNumerical.hpp index 32f2997f9..a71c170ac 100644 --- a/opm/simulators/aquifers/AquiferNumerical.hpp +++ b/opm/simulators/aquifers/AquiferNumerical.hpp @@ -261,10 +261,11 @@ private: const double getWaterFlux(SmallElementContext& elem_ctx,unsigned face_idx) const{ short upIdx[numPhases]; - + short dnIdx[numPhases]; Eval volumFlux[numPhases]; Eval pressureDifferences[numPhases]; - ExtensiveQuantities::volumeAndPhasePressureDifferences(upIdx , + ExtensiveQuantities::volumeAndPhasePressureDifferences(upIdx, + dnIdx, volumFlux, pressureDifferences, elem_ctx, From 5042b021382fa700c8c24501f662042de0d4603b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Thu, 7 Jul 2022 13:16:29 +0200 Subject: [PATCH 39/49] Rename method and move to impl file. --- opm/simulators/wells/BlackoilWellModel.hpp | 29 ++---------------- .../wells/BlackoilWellModel_impl.hpp | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/opm/simulators/wells/BlackoilWellModel.hpp b/opm/simulators/wells/BlackoilWellModel.hpp index 4e96454bb..ae3926bad 100644 --- a/opm/simulators/wells/BlackoilWellModel.hpp +++ b/opm/simulators/wells/BlackoilWellModel.hpp @@ -273,33 +273,8 @@ namespace Opm { void addWellContributions(SparseMatrixAdapter& jacobian) const; - void addReseroirSourceTerms(GlobalEqVector& residual, - SparseMatrixAdapter& jacobian) const - { - // NB this loop may write to same element if a cell has more than one perforation -#ifdef _OPENMP -#pragma omp parallel for -#endif - for(size_t i = 0; i < well_container_.size(); i++){// to be sure open understand - const auto& well = well_container_[i]; - if(!well->isOperableAndSolvable() && !well->wellIsStopped()) - continue; - - const auto& cells = well->cells(); - const auto& rates = well->connectionRates(); - for (unsigned perfIdx = 0; perfIdx < rates.size(); ++perfIdx) { - unsigned cellIdx = cells[perfIdx]; - auto rate = rates[perfIdx]; - // Scalar volume = ebosSimulator_.problem().volume(cellIdx,0); - rate *= -1.0; - VectorBlockType res(0.0); - MatrixBlockType bMat(0.0); - ebosSimulator_.model().linearizer().setResAndJacobi(res,bMat,rate); - residual[cellIdx] += res; - jacobian.addToBlock(cellIdx,cellIdx,bMat); - } - } - } + void addReservoirSourceTerms(GlobalEqVector& residual, + SparseMatrixAdapter& jacobian) const; // called at the beginning of a report step void beginReportStep(const int time_step); diff --git a/opm/simulators/wells/BlackoilWellModel_impl.hpp b/opm/simulators/wells/BlackoilWellModel_impl.hpp index b28fa609e..da53e4782 100644 --- a/opm/simulators/wells/BlackoilWellModel_impl.hpp +++ b/opm/simulators/wells/BlackoilWellModel_impl.hpp @@ -1202,6 +1202,36 @@ namespace Opm { } } + template + void + BlackoilWellModel:: + addReservoirSourceTerms(GlobalEqVector& residual, + SparseMatrixAdapter& jacobian) const + { + // NB this loop may write to same element if a cell has more than one perforation +#ifdef _OPENMP +#pragma omp parallel for +#endif + for (size_t i = 0; i < well_container_.size(); i++) { + const auto& well = well_container_[i]; + if (!well->isOperableAndSolvable() && !well->wellIsStopped()) + continue; + const auto& cells = well->cells(); + const auto& rates = well->connectionRates(); + for (unsigned perfIdx = 0; perfIdx < rates.size(); ++perfIdx) { + unsigned cellIdx = cells[perfIdx]; + auto rate = rates[perfIdx]; + // Scalar volume = ebosSimulator_.model().dofTotalVolume(cellIdx); + rate *= -1.0; + VectorBlockType res(0.0); + MatrixBlockType bMat(0.0); + ebosSimulator_.model().linearizer().setResAndJacobi(res, bMat, rate); + residual[cellIdx] += res; + jacobian.addToBlock(cellIdx, cellIdx, bMat); + } + } + } + template void BlackoilWellModel:: From cd729e5bd3e13527d7ceec806be0489b6f1650c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Thu, 7 Jul 2022 13:17:29 +0200 Subject: [PATCH 40/49] Simplify formatting. --- flow/flow_blackoil_tpfa.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp index 5ec7c4f20..8ba3e751a 100644 --- a/flow/flow_blackoil_tpfa.cpp +++ b/flow/flow_blackoil_tpfa.cpp @@ -46,14 +46,10 @@ namespace Opm { struct LocalResidual { using type = BlackOilLocalResidualTPFA; }; template - //struct ElementContext { using type = SmallElementContext; }; - struct ElementContext { using type = FvBaseElementContext; }; + struct ElementContext { using type = SmallElementContext; }; template - struct IntensiveQuantities { - //using type = BlackOilIntensiveQuantitiesSimple; - using type = BlackOilIntensiveQuantities; - }; + struct IntensiveQuantities { using type = BlackOilIntensiveQuantitiesSimple; }; } } From 9de8d122768244c59bbccc17086889a2c8076ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Sun, 31 Jul 2022 18:49:47 +0200 Subject: [PATCH 41/49] [WIP] not using the SmallElementContext or BlackOilIntensiveQuantitiesSimple. --- flow/flow_blackoil_tpfa.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp index 8ba3e751a..d2afaa681 100644 --- a/flow/flow_blackoil_tpfa.cpp +++ b/flow/flow_blackoil_tpfa.cpp @@ -45,11 +45,11 @@ namespace Opm { template struct LocalResidual { using type = BlackOilLocalResidualTPFA; }; - template - struct ElementContext { using type = SmallElementContext; }; + // template + // struct ElementContext { using type = SmallElementContext; }; - template - struct IntensiveQuantities { using type = BlackOilIntensiveQuantitiesSimple; }; + // template + // struct IntensiveQuantities { using type = BlackOilIntensiveQuantitiesSimple; }; } } From 5fba14373b8ac251fcbc849c9b916b5468849666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Wed, 3 Aug 2022 09:52:03 +0200 Subject: [PATCH 42/49] Modification to reduce PR to minimal changes. --- ebos/eclthresholdpressure.hh | 91 +++---------------- ebos/ecltracermodel.hh | 31 +------ flow/flow_blackoil_tpfa.cpp | 9 +- opm/simulators/aquifers/AquiferNumerical.hpp | 23 +---- opm/simulators/flow/BlackoilModelEbos.hpp | 53 ++--------- opm/simulators/linalg/ISTLSolverEbos.hpp | 4 +- opm/simulators/wells/BlackoilWellModel.hpp | 4 +- .../wells/BlackoilWellModel_impl.hpp | 2 + opm/simulators/wells/WellInterface.hpp | 2 +- 9 files changed, 36 insertions(+), 183 deletions(-) diff --git a/ebos/eclthresholdpressure.hh b/ebos/eclthresholdpressure.hh index 8154f3d3c..1db743976 100644 --- a/ebos/eclthresholdpressure.hh +++ b/ebos/eclthresholdpressure.hh @@ -39,7 +39,6 @@ #include #include -#include namespace Opm { /*! @@ -100,10 +99,16 @@ public: } private: - template - double calculateMaxDp(Face& face, Stencil& stencil, - ElemCtx& elemCtx,const unsigned& scvfIdx, - const unsigned& i,const unsigned& j,const unsigned& insideElemIdx,const unsigned& outsideElemIdx){ + template + double calculateMaxDp(Face& face, + Stencil& stencil, + ElemCtx& elemCtx, + const unsigned& scvfIdx, + const unsigned& i, + const unsigned& j, + const unsigned& insideElemIdx, + const unsigned& outsideElemIdx) + { typedef MathToolbox Toolbox; elemCtx.updateIntensiveQuantities(/*timeIdx=*/0); elemCtx.updateExtensiveQuantities(/*timeIdx=*/0); @@ -114,7 +119,7 @@ private: for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { unsigned upIdx = extQuants.upstreamIndex(phaseIdx); const auto& up = elemCtx.intensiveQuantities(upIdx, /*timeIdx=*/0); - + if (up.mobility(phaseIdx) > 0.0) { Scalar phaseVal = Toolbox::value(extQuants.pressureDifference(phaseIdx)); pth = std::max(pth, std::abs(phaseVal)); @@ -122,72 +127,8 @@ private: } return pth; } - - template - double calculateMaxDp(Face& face, Stencil& stencil, - SmallElementContext& elemCtx,const unsigned& scvfIdx, - const unsigned& i,const unsigned& j, - const unsigned& insideElemIdx,const unsigned& outsideElemIdx){ - typedef MathToolbox Toolbox; - // determine the maximum difference of the pressure of any phase over the - // intersection - Scalar pth = 0.0; - //const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, /*timeIdx=*/0); - - Scalar Vin = elemCtx.dofVolume(i, /*timeIdx=*/0); - Scalar Vex = elemCtx.dofVolume(j, /*timeIdx=*/0); - - Scalar thpres = 0.0;//NB ??problem.thresholdPressure(globalIndexIn, globalIndexEx); - - // estimate the gravity correction: for performance reasons we use a simplified - // approach for this flux module that assumes that gravity is constant and always - // acts into the downwards direction. (i.e., no centrifuge experiments, sorry.) - const auto& problem = elemCtx.problem(); - Scalar g = problem.gravity()[dimWorld - 1]; - - const auto& intQuantsIn = elemCtx.intensiveQuantities(i, /*timeIdx*/0); - const auto& intQuantsEx = elemCtx.intensiveQuantities(j, /*timeIdx*/0); - - // this is quite hacky because the dune grid interface does not provide a - // cellCenterDepth() method (so we ask the problem to provide it). The "good" - // solution would be to take the Z coordinate of the element centroids, but since - // ECL seems to like to be inconsistent on that front, it needs to be done like - // here... - Scalar zIn = problem.dofCenterDepth(elemCtx, i, /*timeIdx*/0); - Scalar zEx = problem.dofCenterDepth(elemCtx, j, /*timeIdx*/0); - - // the distances from the DOF's depths. (i.e., the additional depth of the - // exterior DOF) - Scalar distZ = zIn - zEx; - - for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { - short dnIdx; - // - short upIdx; - Evaluation pressureDifference; - ExtensiveQuantities::calculatePhasePressureDiff_(upIdx, - dnIdx, - pressureDifference, - intQuantsIn, - intQuantsEx, - /*timeIdx*/0,//input - phaseIdx,//input - i,//input - j,//intput - Vin, - Vex, - insideElemIdx, - outsideElemIdx, - distZ*g, - thpres); - const IntensiveQuantities& up = (upIdx == i) ? intQuantsIn : intQuantsEx; - if (up.mobility(phaseIdx) > 0.0) { - Scalar phaseVal = Toolbox::value(pressureDifference); - pth = std::max(pth, std::abs(phaseVal)); - } - } - return pth; - } + + // compute the defaults of the threshold pressures using the initial condition void computeDefaultThresholdPressures_() { @@ -208,13 +149,9 @@ private: continue; elemCtx.updateStencil(elem); - // - const auto& stencil = elemCtx.stencil(/*timeIdx=*/0); - for (unsigned scvfIdx = 0; scvfIdx < stencil.numInteriorFaces(); ++ scvfIdx) { - const auto& face = stencil.interiorFace(scvfIdx); unsigned i = face.interiorIndex(); @@ -225,7 +162,6 @@ private: unsigned equilRegionInside = this->elemEquilRegion_[insideElemIdx]; unsigned equilRegionOutside = this->elemEquilRegion_[outsideElemIdx]; - if (equilRegionInside == equilRegionOutside) // the current face is not at the boundary between EQUIL regions! continue; @@ -235,7 +171,6 @@ private: Scalar faceArea = face.area(); if (std::abs(faceArea*getValue(trans)) < 1e-18) continue; - double pth = calculateMaxDp(face, stencil, elemCtx, scvfIdx, i, j, insideElemIdx, outsideElemIdx); diff --git a/ebos/ecltracermodel.hh b/ebos/ecltracermodel.hh index 3dfb1c6ad..38e171dc3 100644 --- a/ebos/ecltracermodel.hh +++ b/ebos/ecltracermodel.hh @@ -227,39 +227,18 @@ protected: freeVolume = phaseVolume * variable(1.0, 0); } - //template + void getVolumeFlux(unsigned& upIdx, Scalar& v, const FvBaseElementContext& elemCtx, const int tracerPhaseIdx, - unsigned scvfIdx - ){ + unsigned scvfIdx) + { const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, /*timeIdx*/ 0); upIdx = extQuants.upstreamIndex(tracerPhaseIdx); v = decay(extQuants.volumeFlux(tracerPhaseIdx)); } - //template - void getVolumeFlux(unsigned& upIdx, - Scalar& v, - const SmallElementContext& elemCtx, - const int tracerPhaseIdx, - unsigned scvfIdx - ){ - short upIdxV[numPhases]; - short dnIdxV[numPhases]; - Eval volumFlux[numPhases]; - Eval pressureDifferences[numPhases]; - ExtensiveQuantities::volumeAndPhasePressureDifferences(upIdxV, - dnIdxV, - volumFlux, - pressureDifferences, - elemCtx, - scvfIdx, - /*timeIdx*/ 0); - v = decay(volumFlux[tracerPhaseIdx]); - upIdx = upIdxV[tracerPhaseIdx] ; - } // evaluate the flux(es) over one face void computeFlux_(TracerEvaluation & freeFlux, bool & isUpFree, @@ -278,11 +257,10 @@ protected: v, elemCtx, tracerPhaseIdx, - scvfIdx); + scvfIdx); const auto& intQuants = elemCtx.intensiveQuantities(upIdx, timeIdx); const auto& fs = intQuants.fluidState(); Scalar A = scvf.area(); - Scalar b = decay(fs.invB(tracerPhaseIdx)); if (inIdx == upIdx) { @@ -433,7 +411,6 @@ protected: auto elemIt = simulator_.gridView().template begin(); auto elemEndIt = simulator_.gridView().template end(); for (; elemIt != elemEndIt; ++ elemIt) { - //elemCtx.updateAll(*elemIt); elemCtx.updatePrimaryStencil(*elemIt); elemCtx.updatePrimaryIntensiveQuantities(/*timIdx*/ 0.0); int globalDofIdx = elemCtx.globalSpaceIndex(0, /*timIdx=*/0); diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp index d2afaa681..91b69e51d 100644 --- a/flow/flow_blackoil_tpfa.cpp +++ b/flow/flow_blackoil_tpfa.cpp @@ -20,10 +20,9 @@ #include #include #include + // modifications from standard #include -#include -#include #include namespace Opm { @@ -45,12 +44,6 @@ namespace Opm { template struct LocalResidual { using type = BlackOilLocalResidualTPFA; }; - // template - // struct ElementContext { using type = SmallElementContext; }; - - // template - // struct IntensiveQuantities { using type = BlackOilIntensiveQuantitiesSimple; }; - } } diff --git a/opm/simulators/aquifers/AquiferNumerical.hpp b/opm/simulators/aquifers/AquiferNumerical.hpp index a71c170ac..0077178e0 100644 --- a/opm/simulators/aquifers/AquiferNumerical.hpp +++ b/opm/simulators/aquifers/AquiferNumerical.hpp @@ -252,31 +252,14 @@ private: return sum_pressure_watervolume / sum_watervolume; } - template - const double getWaterFlux(ElemCtx& elem_ctx,unsigned face_idx) const{ + template + const double getWaterFlux(ElemCtx& elem_ctx, unsigned face_idx) const + { const auto& exQuants = elem_ctx.extensiveQuantities(face_idx, /*timeIdx*/ 0); const double water_flux = Toolbox::value(exQuants.volumeFlux(this->phaseIdx_())); return water_flux; } - - const double getWaterFlux(SmallElementContext& elem_ctx,unsigned face_idx) const{ - short upIdx[numPhases]; - short dnIdx[numPhases]; - Eval volumFlux[numPhases]; - Eval pressureDifferences[numPhases]; - ExtensiveQuantities::volumeAndPhasePressureDifferences(upIdx, - dnIdx, - volumFlux, - pressureDifferences, - elem_ctx, - face_idx, - /*timeIdx*/ 0); - return Toolbox::value(volumFlux[this->phaseIdx_()]); - } - - - double calculateAquiferFluxRate() const { double aquifer_flux = 0.0; diff --git a/opm/simulators/flow/BlackoilModelEbos.hpp b/opm/simulators/flow/BlackoilModelEbos.hpp index cb9013582..e0c6643cd 100644 --- a/opm/simulators/flow/BlackoilModelEbos.hpp +++ b/opm/simulators/flow/BlackoilModelEbos.hpp @@ -52,8 +52,6 @@ #include -#include - #include #if DUNE_VERSION_NEWER(DUNE_COMMON, 2, 7) #include @@ -172,8 +170,6 @@ namespace Opm { using Indices = GetPropType; using MaterialLaw = GetPropType; using MaterialLawParams = GetPropType; - using IntensiveQuantities = GetPropType; - using Problem = GetPropType; typedef double Scalar; static const int numEq = Indices::numEq; @@ -572,56 +568,23 @@ namespace Opm { ebosSolver.solve(x); } - template - void updateIntensiveQuantity(const Problem& /*problem*/, - SolutionVector& solution, - const BVector& dx, - const IntensiveQuantity*) - { - ebosSimulator_.model().newtonMethod().update_(/*nextSolution=*/solution, - /*curSolution=*/solution, - /*update=*/dx, - /*resid=*/dx); // the update routines of the black - // oil model do not care about the - // residual - ebosSimulator_.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0); - } - - - void updateIntensiveQuantity(const Problem& problem, - SolutionVector& solution, - const BVector& dx, - const BlackOilIntensiveQuantitiesSimple* /*intensive*/) - { - auto& model = ebosSimulator_.model(); - auto& ebosNewtonMethod = model.newtonMethod(); - ebosNewtonMethod.update_(/*nextSolution*/solution, - /*curSolution=*/solution, - /*update=*/dx, - /*resid=*/dx); // the update routines of the black - // oil model do not care about the - // residual - model.invalidateAndUpdateIntensiveQuantitiesSimple(problem, - solution, - /*timeIdx*/0); - } /// Apply an update to the primary variables. void updateSolution(const BVector& dx) { - auto& problem = ebosSimulator_.problem(); auto& ebosNewtonMethod = ebosSimulator_.model().newtonMethod(); SolutionVector& solution = ebosSimulator_.model().solution(/*timeIdx=*/0); - + ebosNewtonMethod.update_(/*nextSolution=*/solution, + /*curSolution=*/solution, + /*update=*/dx, + /*resid=*/dx); // the update routines of the black + // oil model do not care about the + // residual + // if the solution is updated, the intensive quantities need to be recalculated - //ebosSimulator_.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0); - IntensiveQuantities* dummy = NULL;//only for template spesialization - this->updateIntensiveQuantity(problem,solution, dx, dummy); - //ebosSimulator_.model().invalidateAndUpdateIntensiveQuantitiesSimple(problem, - //solution, - // /*timeIdx=*/0); + ebosSimulator_.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0); } /// Return true if output to cout is wanted. diff --git a/opm/simulators/linalg/ISTLSolverEbos.hpp b/opm/simulators/linalg/ISTLSolverEbos.hpp index 3aced34ba..498a9c1b0 100644 --- a/opm/simulators/linalg/ISTLSolverEbos.hpp +++ b/opm/simulators/linalg/ISTLSolverEbos.hpp @@ -532,14 +532,14 @@ namespace Opm return weights; } - +#if 0 Vector getTrueImpesWeights(int pressureVarIndex,SmallElementContext& /*elemCtx*/) const { Vector weights(rhs_->size()); Amg::getTrueImpesWeights(pressureVarIndex, weights, simulator_.model()); return weights; } - +#endif /// Zero out off-diagonal blocks on rows corresponding to overlap cells /// Diagonal blocks on ovelap rows are set to diag(1.0). diff --git a/opm/simulators/wells/BlackoilWellModel.hpp b/opm/simulators/wells/BlackoilWellModel.hpp index ae3926bad..59056b99e 100644 --- a/opm/simulators/wells/BlackoilWellModel.hpp +++ b/opm/simulators/wells/BlackoilWellModel.hpp @@ -273,8 +273,8 @@ namespace Opm { void addWellContributions(SparseMatrixAdapter& jacobian) const; - void addReservoirSourceTerms(GlobalEqVector& residual, - SparseMatrixAdapter& jacobian) const; + // void addReservoirSourceTerms(GlobalEqVector& residual, + // SparseMatrixAdapter& jacobian) const; // called at the beginning of a report step void beginReportStep(const int time_step); diff --git a/opm/simulators/wells/BlackoilWellModel_impl.hpp b/opm/simulators/wells/BlackoilWellModel_impl.hpp index da53e4782..4b52cf1ba 100644 --- a/opm/simulators/wells/BlackoilWellModel_impl.hpp +++ b/opm/simulators/wells/BlackoilWellModel_impl.hpp @@ -1202,6 +1202,7 @@ namespace Opm { } } +#if 0 template void BlackoilWellModel:: @@ -1231,6 +1232,7 @@ namespace Opm { } } } +#endif template void diff --git a/opm/simulators/wells/WellInterface.hpp b/opm/simulators/wells/WellInterface.hpp index 0ce79436b..989af2ad8 100644 --- a/opm/simulators/wells/WellInterface.hpp +++ b/opm/simulators/wells/WellInterface.hpp @@ -288,7 +288,7 @@ public: const GroupState& group_state, DeferredLogger& deferred_logger); - const std::vector& connectionRates() const {return connectionRates_;} +// const std::vector& connectionRates() const {return connectionRates_;} protected: // simulation parameters From cabe64cc58fabde930da6de5dac0f58dd17d8c4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Wed, 3 Aug 2022 10:05:47 +0200 Subject: [PATCH 43/49] Further reduction of modifications. --- .../SimulatorFullyImplicitBlackoilEbos.hpp | 2 +- opm/simulators/wells/BlackoilWellModel.hpp | 9 +----- .../wells/BlackoilWellModel_impl.hpp | 32 ------------------- opm/simulators/wells/WellInterface.hpp | 1 - 4 files changed, 2 insertions(+), 42 deletions(-) diff --git a/opm/simulators/flow/SimulatorFullyImplicitBlackoilEbos.hpp b/opm/simulators/flow/SimulatorFullyImplicitBlackoilEbos.hpp index 35e9fd06d..6d3947730 100644 --- a/opm/simulators/flow/SimulatorFullyImplicitBlackoilEbos.hpp +++ b/opm/simulators/flow/SimulatorFullyImplicitBlackoilEbos.hpp @@ -218,7 +218,7 @@ public: ebosSimulator_.setEpisodeIndex(-1); ebosSimulator_.setEpisodeLength(0.0); ebosSimulator_.setTimeStepSize(0.0); - // make cach upto date. no nead for updating it in elementCtx + // Make cache up to date. No need for updating it in elementCtx. ebosSimulator_.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0); wellModel_().beginReportStep(timer.currentStepNum()); ebosSimulator_.problem().writeOutput(); diff --git a/opm/simulators/wells/BlackoilWellModel.hpp b/opm/simulators/wells/BlackoilWellModel.hpp index 59056b99e..eac9891dc 100644 --- a/opm/simulators/wells/BlackoilWellModel.hpp +++ b/opm/simulators/wells/BlackoilWellModel.hpp @@ -128,8 +128,6 @@ namespace Opm { typedef Dune::FieldVector VectorBlockType; typedef Dune::BlockVector BVector; - typedef Opm::MatrixBlock MatrixBlockType; - typedef BlackOilPolymerModule PolymerModule; typedef BlackOilMICPModule MICPModule; @@ -210,13 +208,11 @@ namespace Opm { { endReportStep(); } - - + void computeTotalRatesForDof(RateVector& rate, unsigned globalIdx, unsigned timeIdx) const; - template void computeTotalRatesForDof(RateVector& rate, const Context& context, @@ -273,9 +269,6 @@ namespace Opm { void addWellContributions(SparseMatrixAdapter& jacobian) const; - // void addReservoirSourceTerms(GlobalEqVector& residual, - // SparseMatrixAdapter& jacobian) const; - // called at the beginning of a report step void beginReportStep(const int time_step); diff --git a/opm/simulators/wells/BlackoilWellModel_impl.hpp b/opm/simulators/wells/BlackoilWellModel_impl.hpp index 4b52cf1ba..b28fa609e 100644 --- a/opm/simulators/wells/BlackoilWellModel_impl.hpp +++ b/opm/simulators/wells/BlackoilWellModel_impl.hpp @@ -1202,38 +1202,6 @@ namespace Opm { } } -#if 0 - template - void - BlackoilWellModel:: - addReservoirSourceTerms(GlobalEqVector& residual, - SparseMatrixAdapter& jacobian) const - { - // NB this loop may write to same element if a cell has more than one perforation -#ifdef _OPENMP -#pragma omp parallel for -#endif - for (size_t i = 0; i < well_container_.size(); i++) { - const auto& well = well_container_[i]; - if (!well->isOperableAndSolvable() && !well->wellIsStopped()) - continue; - const auto& cells = well->cells(); - const auto& rates = well->connectionRates(); - for (unsigned perfIdx = 0; perfIdx < rates.size(); ++perfIdx) { - unsigned cellIdx = cells[perfIdx]; - auto rate = rates[perfIdx]; - // Scalar volume = ebosSimulator_.model().dofTotalVolume(cellIdx); - rate *= -1.0; - VectorBlockType res(0.0); - MatrixBlockType bMat(0.0); - ebosSimulator_.model().linearizer().setResAndJacobi(res, bMat, rate); - residual[cellIdx] += res; - jacobian.addToBlock(cellIdx, cellIdx, bMat); - } - } - } -#endif - template void BlackoilWellModel:: diff --git a/opm/simulators/wells/WellInterface.hpp b/opm/simulators/wells/WellInterface.hpp index 989af2ad8..0d2631263 100644 --- a/opm/simulators/wells/WellInterface.hpp +++ b/opm/simulators/wells/WellInterface.hpp @@ -288,7 +288,6 @@ public: const GroupState& group_state, DeferredLogger& deferred_logger); -// const std::vector& connectionRates() const {return connectionRates_;} protected: // simulation parameters From 254330463ea3d72545403e418b2ad6fd29ae75df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Thu, 4 Aug 2022 15:16:40 +0200 Subject: [PATCH 44/49] Disable diffusion for flow_blackoil_tpfa. --- flow/flow_blackoil_tpfa.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp index 91b69e51d..7df3d10fa 100644 --- a/flow/flow_blackoil_tpfa.cpp +++ b/flow/flow_blackoil_tpfa.cpp @@ -44,6 +44,9 @@ namespace Opm { template struct LocalResidual { using type = BlackOilLocalResidualTPFA; }; + template + struct EnableDiffusion { static constexpr bool value = false; }; + } } From 322dcbfb365bce65d9e5ae7564786fd0c0d0ad21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Thu, 4 Aug 2022 15:54:32 +0200 Subject: [PATCH 45/49] Make getWaterFlux() take const ref argument. Minor formatting cleanups. --- opm/simulators/aquifers/AquiferNumerical.hpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/opm/simulators/aquifers/AquiferNumerical.hpp b/opm/simulators/aquifers/AquiferNumerical.hpp index 0077178e0..d04cf3157 100644 --- a/opm/simulators/aquifers/AquiferNumerical.hpp +++ b/opm/simulators/aquifers/AquiferNumerical.hpp @@ -253,7 +253,7 @@ private: } template - const double getWaterFlux(ElemCtx& elem_ctx, unsigned face_idx) const + const double getWaterFlux(const ElemCtx& elem_ctx, unsigned face_idx) const { const auto& exQuants = elem_ctx.extensiveQuantities(face_idx, /*timeIdx*/ 0); const double water_flux = Toolbox::value(exQuants.volumeFlux(this->phaseIdx_())); @@ -286,7 +286,7 @@ private: if (idx != 0) { continue; } - + const std::size_t num_interior_faces = elem_ctx.numInteriorFaces(/*timeIdx*/ 0); // const auto &problem = elem_ctx.problem(); const auto& stencil = elem_ctx.stencil(0); @@ -312,7 +312,6 @@ private: elem_ctx.updateAllExtensiveQuantities(); const double water_flux = getWaterFlux(elem_ctx,face_idx); - const std::size_t up_id = water_flux >= 0.0 ? i : j; const auto& intQuantsIn = elem_ctx.intensiveQuantities(up_id, 0); const double invB = Toolbox::value(intQuantsIn.fluidState().invB(this->phaseIdx_())); @@ -326,7 +325,6 @@ private: return aquifer_flux; } - }; } // namespace Opm #endif From 727bf8d01f6cf4320837a26a9fefe427d4b92d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Tue, 9 Aug 2022 09:19:26 +0200 Subject: [PATCH 46/49] Do not make changes to eclthresholdpressure.hh and ecltracermodel.hh. Not required for first version, but should be added later. --- ebos/eclthresholdpressure.hh | 60 +++++++++++------------------------- ebos/ecltracermodel.hh | 33 ++++++-------------- 2 files changed, 27 insertions(+), 66 deletions(-) diff --git a/ebos/eclthresholdpressure.hh b/ebos/eclthresholdpressure.hh index 1db743976..a3ef95e2f 100644 --- a/ebos/eclthresholdpressure.hh +++ b/ebos/eclthresholdpressure.hh @@ -61,15 +61,11 @@ class EclThresholdPressure : public EclGenericThresholdPressure, GetPropType, GetPropType>; - using IntensiveQuantities = GetPropType; - using ExtensiveQuantities = GetPropType; using Simulator = GetPropType; using Scalar = GetPropType; using Evaluation = GetPropType; using ElementContext = GetPropType; using FluidSystem = GetPropType; - using GridView = GetPropType; - enum { dimWorld = GridView::dimensionworld }; enum { enableExperiments = getPropValue() }; enum { numPhases = FluidSystem::numPhases }; @@ -99,36 +95,6 @@ public: } private: - template - double calculateMaxDp(Face& face, - Stencil& stencil, - ElemCtx& elemCtx, - const unsigned& scvfIdx, - const unsigned& i, - const unsigned& j, - const unsigned& insideElemIdx, - const unsigned& outsideElemIdx) - { - typedef MathToolbox Toolbox; - elemCtx.updateIntensiveQuantities(/*timeIdx=*/0); - elemCtx.updateExtensiveQuantities(/*timeIdx=*/0); - // determine the maximum difference of the pressure of any phase over the - // intersection - Scalar pth = 0.0; - const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, /*timeIdx=*/0); - for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { - unsigned upIdx = extQuants.upstreamIndex(phaseIdx); - const auto& up = elemCtx.intensiveQuantities(upIdx, /*timeIdx=*/0); - - if (up.mobility(phaseIdx) > 0.0) { - Scalar phaseVal = Toolbox::value(extQuants.pressureDifference(phaseIdx)); - pth = std::max(pth, std::abs(phaseVal)); - } - } - return pth; - } - - // compute the defaults of the threshold pressures using the initial condition void computeDefaultThresholdPressures_() { @@ -141,14 +107,13 @@ private: auto elemIt = gridView.template begin(); const auto& elemEndIt = gridView.template end(); ElementContext elemCtx(simulator_); - simulator_.model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0); for (; elemIt != elemEndIt; ++elemIt) { const auto& elem = *elemIt; if (elem.partitionType() != Dune::InteriorEntity) continue; - elemCtx.updateStencil(elem); + elemCtx.updateAll(elem); const auto& stencil = elemCtx.stencil(/*timeIdx=*/0); for (unsigned scvfIdx = 0; scvfIdx < stencil.numInteriorFaces(); ++ scvfIdx) { @@ -162,19 +127,30 @@ private: unsigned equilRegionInside = this->elemEquilRegion_[insideElemIdx]; unsigned equilRegionOutside = this->elemEquilRegion_[outsideElemIdx]; + if (equilRegionInside == equilRegionOutside) // the current face is not at the boundary between EQUIL regions! continue; - const auto& problem = elemCtx.problem(); + // don't include connections with negligible flow - const Evaluation& trans = problem.transmissibility(elemCtx, i, j); + const Evaluation& trans = simulator_.problem().transmissibility(elemCtx, i, j); Scalar faceArea = face.area(); if (std::abs(faceArea*getValue(trans)) < 1e-18) continue; - double pth = calculateMaxDp(face, stencil, elemCtx, scvfIdx, - i, j, - insideElemIdx, outsideElemIdx); - // don't include connections with negligible flow + + // determine the maximum difference of the pressure of any phase over the + // intersection + Scalar pth = 0.0; + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, /*timeIdx=*/0); + for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) { + unsigned upIdx = extQuants.upstreamIndex(phaseIdx); + const auto& up = elemCtx.intensiveQuantities(upIdx, /*timeIdx=*/0); + + if (up.mobility(phaseIdx) > 0.0) { + Scalar phaseVal = Toolbox::value(extQuants.pressureDifference(phaseIdx)); + pth = std::max(pth, std::abs(phaseVal)); + } + } int offset1 = equilRegionInside*this->numEquilRegions_ + equilRegionOutside; int offset2 = equilRegionOutside*this->numEquilRegions_ + equilRegionInside; diff --git a/ebos/ecltracermodel.hh b/ebos/ecltracermodel.hh index 38e171dc3..8237cb872 100644 --- a/ebos/ecltracermodel.hh +++ b/ebos/ecltracermodel.hh @@ -86,9 +86,6 @@ class EclTracerModel : public EclGenericTracerModel; - using IntensiveQuantities = GetPropType; - using ExtensiveQuantities = GetPropType; public: EclTracerModel(Simulator& simulator) : BaseType(simulator.vanguard().gridView(), @@ -228,17 +225,6 @@ protected: } - void getVolumeFlux(unsigned& upIdx, - Scalar& v, - const FvBaseElementContext& elemCtx, - const int tracerPhaseIdx, - unsigned scvfIdx) - { - const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, /*timeIdx*/ 0); - upIdx = extQuants.upstreamIndex(tracerPhaseIdx); - v = decay(extQuants.volumeFlux(tracerPhaseIdx)); - } - // evaluate the flux(es) over one face void computeFlux_(TracerEvaluation & freeFlux, bool & isUpFree, @@ -250,17 +236,17 @@ protected: { const auto& stencil = elemCtx.stencil(timeIdx); const auto& scvf = stencil.interiorFace(scvfIdx); - unsigned inIdx = scvf.interiorIndex(); - unsigned upIdx; - Scalar v; - getVolumeFlux(upIdx, - v, - elemCtx, - tracerPhaseIdx, - scvfIdx); + + const auto& extQuants = elemCtx.extensiveQuantities(scvfIdx, timeIdx); + unsigned inIdx = extQuants.interiorIndex(); + + unsigned upIdx = extQuants.upstreamIndex(tracerPhaseIdx); + const auto& intQuants = elemCtx.intensiveQuantities(upIdx, timeIdx); const auto& fs = intQuants.fluidState(); + Scalar A = scvf.area(); + Scalar v = decay(extQuants.volumeFlux(tracerPhaseIdx)); Scalar b = decay(fs.invB(tracerPhaseIdx)); if (inIdx == upIdx) { @@ -411,8 +397,7 @@ protected: auto elemIt = simulator_.gridView().template begin(); auto elemEndIt = simulator_.gridView().template end(); for (; elemIt != elemEndIt; ++ elemIt) { - elemCtx.updatePrimaryStencil(*elemIt); - elemCtx.updatePrimaryIntensiveQuantities(/*timIdx*/ 0.0); + elemCtx.updateAll(*elemIt); int globalDofIdx = elemCtx.globalSpaceIndex(0, /*timIdx=*/0); Scalar fVolume; computeVolume_(fVolume, tr.phaseIdx_, elemCtx, 0, /*timIdx=*/0); From 90e8a9af69c7914e76483f4fa7cd583dc81b0442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Tue, 9 Aug 2022 09:56:58 +0200 Subject: [PATCH 47/49] Do not change true impes weights. --- opm/simulators/linalg/ISTLSolverEbos.hpp | 22 +++----- .../linalg/getQuasiImpesWeights.hpp | 53 ++----------------- 2 files changed, 11 insertions(+), 64 deletions(-) diff --git a/opm/simulators/linalg/ISTLSolverEbos.hpp b/opm/simulators/linalg/ISTLSolverEbos.hpp index 498a9c1b0..07248cce9 100644 --- a/opm/simulators/linalg/ISTLSolverEbos.hpp +++ b/opm/simulators/linalg/ISTLSolverEbos.hpp @@ -92,7 +92,6 @@ namespace Opm using AbstractPreconditionerType = Dune::PreconditionerWithUpdate; using WellModelOperator = WellModelAsLinearOperator; using ElementMapper = GetPropType; - using Evaluation = GetPropType; constexpr static std::size_t pressureIndex = GetPropType::pressureSwitchIdx; #if HAVE_CUDA || HAVE_OPENCL || HAVE_FPGA || HAVE_AMGCL @@ -506,8 +505,7 @@ namespace Opm // assignment p = pressureIndex prevent compiler warning about // capturing variable with non-automatic storage duration weightsCalculator = [this, p = pressureIndex]() { - ElementContext elemCtx(this->simulator_); - return this->getTrueImpesWeights(p, elemCtx); + return this->getTrueImpesWeights(p); }; } else { OPM_THROW(std::invalid_argument, @@ -522,24 +520,16 @@ namespace Opm // Weights to make approximate pressure equations. // Calculated from the storage terms (only) of the // conservation equations, ignoring all other terms. - template - Vector getTrueImpesWeights(int pressureVarIndex,ElemCtx& elemCtx) const + Vector getTrueImpesWeights(int pressureVarIndex) const { Vector weights(rhs_->size()); - Amg::getTrueImpesWeights(pressureVarIndex, weights, simulator_.vanguard().gridView(), - elemCtx, simulator_.model(), - ThreadManager::threadId()); + ElementContext elemCtx(simulator_); + Amg::getTrueImpesWeights(pressureVarIndex, weights, simulator_.vanguard().gridView(), + elemCtx, simulator_.model(), + ThreadManager::threadId()); return weights; } -#if 0 - Vector getTrueImpesWeights(int pressureVarIndex,SmallElementContext& /*elemCtx*/) const - { - Vector weights(rhs_->size()); - Amg::getTrueImpesWeights(pressureVarIndex, weights, simulator_.model()); - return weights; - } -#endif /// Zero out off-diagonal blocks on rows corresponding to overlap cells /// Diagonal blocks on ovelap rows are set to diag(1.0). diff --git a/opm/simulators/linalg/getQuasiImpesWeights.hpp b/opm/simulators/linalg/getQuasiImpesWeights.hpp index a73417730..3797492e8 100644 --- a/opm/simulators/linalg/getQuasiImpesWeights.hpp +++ b/opm/simulators/linalg/getQuasiImpesWeights.hpp @@ -87,7 +87,7 @@ namespace Amg return weights; } - template + template void getTrueImpesWeights(int pressureVarIndex, Vector& weights, const GridView& gridView, ElementContext& elemCtx, const Model& model, std::size_t threadId) { @@ -95,8 +95,8 @@ namespace Amg using Matrix = typename std::decay_t; using MatrixBlockType = typename Matrix::MatrixBlock; constexpr int numEq = VectorBlockType::size(); -// using Evaluation = typename std::decay_t -// ::block_type; + using Evaluation = typename std::decay_t + ::block_type; VectorBlockType rhs(0.0); rhs[pressureVarIndex] = 1.0; int index = 0; @@ -108,8 +108,8 @@ namespace Amg elemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0); Dune::FieldVector storage; model.localLinearizer(threadId).localResidual().computeStorage(storage,elemCtx,/*spaceIdx=*/0, /*timeIdx=*/0); - //auto extrusionFactor = elemCtx.intensiveQuantities(0, /*timeIdx=*/0).extrusionFactor(); - auto scvVolume = elemCtx.stencil(/*timeIdx=*/0).subControlVolume(0).volume();// * extrusionFactor; + auto extrusionFactor = elemCtx.intensiveQuantities(0, /*timeIdx=*/0).extrusionFactor(); + auto scvVolume = elemCtx.stencil(/*timeIdx=*/0).subControlVolume(0).volume() * extrusionFactor; auto storage_scale = scvVolume / elemCtx.simulator().timeStepSize(); MatrixBlockType block; double pressure_scale = 50e5; @@ -130,49 +130,6 @@ namespace Amg } OPM_END_PARALLEL_TRY_CATCH("getTrueImpesWeights() failed: ", elemCtx.simulator().vanguard().grid().comm()); } - - template - void getTrueImpesWeights(int pressureVarIndex, Vector& weights, const Model& model) - { - using VectorBlockType = typename Vector::block_type; - using Matrix = typename std::decay_t; - using MatrixBlockType = typename Matrix::MatrixBlock; - constexpr int numEq = VectorBlockType::size(); - unsigned numCells = model.numTotalDof(); - VectorBlockType rhs(0.0); - rhs[pressureVarIndex] = 1.0; - //NB !!OPM_BEGIN_PARALLEL_TRY_CATCH(); -#ifdef _OPENMP -#pragma omp parallel for -#endif - for(unsigned globI = 0; globI < numCells; globI++){ - Dune::FieldVector storage; - const auto* intQuantsInP = model.cachedIntensiveQuantities(globI, /*timeIdx*/0); - assert(intQuantsInP); - const auto& intQuantsIn = *intQuantsInP; - Model::LocalResidual::computeStorage(storage,intQuantsIn, 0); - double scvVolume = model.dofTotalVolume(globI); - double dt = 3600*24; - auto storage_scale = scvVolume / dt; - MatrixBlockType block; - double pressure_scale = 50e5; - for (int ii = 0; ii < numEq; ++ii) { - for (int jj = 0; jj < numEq; ++jj) { - block[ii][jj] = storage[ii].derivative(jj)/storage_scale; - if (jj == pressureVarIndex) { - block[ii][jj] *= pressure_scale; - } - } - } - VectorBlockType bweights; - MatrixBlockType block_transpose = Details::transposeDenseMatrix(block); - block_transpose.solve(bweights, rhs); - bweights /= 1000.0; // given normal densities this scales weights to about 1. - weights[globI] = bweights; - } - //NB!! OPM_END_PARALLEL_TRY_CATCH("getTrueImpesWeights() failed: ", elemCtx.simulator().vanguard().grid().comm()); - } - } // namespace Amg } // namespace Opm From 923b1c555cdb7cdc88df8fde7fa5f6b64b1fa5ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Tue, 9 Aug 2022 14:53:58 +0200 Subject: [PATCH 48/49] Make flow_blackoil_tpfa.cpp an OPM Flow variant in the build system. --- CMakeLists.txt | 14 +----- flow/flow_blackoil_tpfa.cpp | 37 +-------------- flow/flow_ebos_blackoil_tpfa.cpp | 77 ++++++++++++++++++++++++++++++++ flow/flow_ebos_blackoil_tpfa.hpp | 30 +++++++++++++ 4 files changed, 110 insertions(+), 48 deletions(-) create mode 100644 flow/flow_ebos_blackoil_tpfa.cpp create mode 100644 flow/flow_ebos_blackoil_tpfa.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 786559c80..391579832 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -423,7 +423,7 @@ set(FLOW_MODELS blackoil brine energy extbo foam gasoil gaswater oilwater oilwater_brine gaswater_brine oilwater_polymer oilwater_polymer_injectivity micp polymer solvent gasoil_energy brine_saltprecipitation gaswater_saltprec_vapwat brine_precsalt_vapwat) -set(FLOW_VARIANT_MODELS brine_energy onephase onephase_energy) +set(FLOW_VARIANT_MODELS brine_energy onephase onephase_energy blackoil_tpfa) set(FLOW_TGTS) foreach(OBJ ${COMMON_MODELS} ${FLOW_MODELS} ${FLOW_VARIANT_MODELS}) @@ -478,18 +478,6 @@ opm_add_test(flow_poly $) target_compile_definitions(flow_poly PRIVATE USE_POLYHEDRALGRID) -opm_add_test(flow_blackoil_tpfa - ONLY_COMPILE - ALWAYS_ENABLE - DEFAULT_ENABLE_IF ${FLOW_POLY_ONLY_DEFAULT_ENABLE_IF} - DEPENDS opmsimulators - LIBRARIES opmsimulators - SOURCES - flow/flow_blackoil_tpfa.cpp - # $ - $) -#target_compile_definitions(flow_blackoil_tpfa PRIVATE USE_POLYHEDRALGRID) - opm_add_test(flow_distribute_z ONLY_COMPILE ALWAYS_ENABLE diff --git a/flow/flow_blackoil_tpfa.cpp b/flow/flow_blackoil_tpfa.cpp index 7df3d10fa..e644171bc 100644 --- a/flow/flow_blackoil_tpfa.cpp +++ b/flow/flow_blackoil_tpfa.cpp @@ -17,42 +17,9 @@ #include "config.h" -#include -#include -#include - -// modifications from standard -#include -#include - -namespace Opm { - namespace Properties { - namespace TTag { - struct EclFlowProblemTPFA { - using InheritsFrom = std::tuple; - }; - } - } -} - -namespace Opm { - namespace Properties { - - template - struct Linearizer { using type = TpfaLinearizer; }; - - template - struct LocalResidual { using type = BlackOilLocalResidualTPFA; }; - - template - struct EnableDiffusion { static constexpr bool value = false; }; - - } -} +#include int main(int argc, char** argv) { - using TypeTag = Opm::Properties::TTag::EclFlowProblemTPFA; - auto mainObject = Opm::Main(argc, argv); - return mainObject.runStatic(); + return Opm::flowEbosBlackoilTpfaMainStandalone(argc, argv); } diff --git a/flow/flow_ebos_blackoil_tpfa.cpp b/flow/flow_ebos_blackoil_tpfa.cpp new file mode 100644 index 000000000..5227d56f8 --- /dev/null +++ b/flow/flow_ebos_blackoil_tpfa.cpp @@ -0,0 +1,77 @@ +/* + 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 . +*/ +#include "config.h" + +#include + +#include +#include +#include +#include + +#include +#include + +namespace Opm { + namespace Properties { + namespace TTag { + struct EclFlowProblemTPFA { + using InheritsFrom = std::tuple; + }; + } + } +} + +namespace Opm { + namespace Properties { + + template + struct Linearizer { using type = TpfaLinearizer; }; + + template + struct LocalResidual { using type = BlackOilLocalResidualTPFA; }; + + template + struct EnableDiffusion { static constexpr bool value = false; }; + + } +} + + +namespace Opm +{ + +// ----------------- Main program ----------------- +int flowEbosBlackoilTpfaMain(int argc, char** argv, bool outputCout, bool outputFiles) +{ + // we always want to use the default locale, and thus spare us the trouble + // with incorrect locale settings. + resetLocale(); + + FlowMainEbos + mainfunc {argc, argv, outputCout, outputFiles}; + return mainfunc.execute(); +} + +int flowEbosBlackoilTpfaMainStandalone(int argc, char** argv) +{ + using TypeTag = Properties::TTag::EclFlowProblemTPFA; + auto mainObject = Opm::Main(argc, argv); + return mainObject.runStatic(); +} + +} // namespace Opm diff --git a/flow/flow_ebos_blackoil_tpfa.hpp b/flow/flow_ebos_blackoil_tpfa.hpp new file mode 100644 index 000000000..b262be98d --- /dev/null +++ b/flow/flow_ebos_blackoil_tpfa.hpp @@ -0,0 +1,30 @@ +/* + 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 FLOW_EBOS_BLACKOIL_TPFA_HPP +#define FLOW_EBOS_BLACKOIL_TPFA_HPP + +namespace Opm { + +//! \brief Main function used in flow binary. +int flowEbosBlackoilTpfaMain(int argc, char** argv, bool outputCout, bool outputFiles); + +//! \brief Main function used in flow_brine binary. +int flowEbosBlackoilTpfaMainStandalone(int argc, char** argv); + +} + +#endif // FLOW_EBOS_BLACKOIL_TPFA_HPP From a5c8d40cfa7808e6bb2183724719ded3552abe1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Atgeirr=20Fl=C3=B8=20Rasmussen?= Date: Wed, 10 Aug 2022 10:01:54 +0200 Subject: [PATCH 49/49] Remove unneeded timeIdx arguments, also silence other warnings. --- ebos/eclfluxmodule.hh | 9 ++++----- ebos/eclproblem.hh | 2 +- opm/simulators/aquifers/AquiferNumerical.hpp | 2 +- opm/simulators/wells/BlackoilWellModel.hpp | 3 +-- opm/simulators/wells/BlackoilWellModel_impl.hpp | 8 ++++---- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/ebos/eclfluxmodule.hh b/ebos/eclfluxmodule.hh index aa75be352..9b63bac89 100644 --- a/ebos/eclfluxmodule.hh +++ b/ebos/eclfluxmodule.hh @@ -263,7 +263,6 @@ public: pressureDifferences[phaseIdx], intQuantsIn, intQuantsEx, - timeIdx,//input phaseIdx,//input interiorDofIdx,//input exteriorDofIdx,//intput @@ -278,12 +277,13 @@ public: continue; } - const IntensiveQuantities& up = (upIdx[phaseIdx] == interiorDofIdx) ? intQuantsIn : intQuantsEx; + const bool upwindIsInterior = (static_cast(upIdx[phaseIdx]) == interiorDofIdx); + const IntensiveQuantities& up = upwindIsInterior ? intQuantsIn : intQuantsEx; // TODO: should the rock compaction transmissibility multiplier be upstreamed // or averaged? all fluids should see the same compaction?! const Evaluation& transMult = up.rockCompTransMultiplier(); - if (upIdx[phaseIdx] == interiorDofIdx) + if (upwindIsInterior) volumeFlux[phaseIdx] = pressureDifferences[phaseIdx]*up.mobility(phaseIdx)*transMult*(-trans/faceArea); else @@ -298,7 +298,6 @@ public: EvalType& pressureDifference, const IntensiveQuantities& intQuantsIn, const IntensiveQuantities& intQuantsEx, - const unsigned timeIdx, const unsigned phaseIdx, const unsigned interiorDofIdx, const unsigned exteriorDofIdx, @@ -475,7 +474,7 @@ protected: Evaluation transModified = trans; - short upstreamIdx = upstreamIndex_(phaseIdx); + unsigned upstreamIdx = upstreamIndex_(phaseIdx); if (upstreamIdx == interiorDofIdx) { // this is slightly hacky because in the automatic differentiation case, it diff --git a/ebos/eclproblem.hh b/ebos/eclproblem.hh index 2c259a81f..a94d2eabb 100644 --- a/ebos/eclproblem.hh +++ b/ebos/eclproblem.hh @@ -2024,7 +2024,7 @@ public: { rate = 0.0; - wellModel_.computeTotalRatesForDof(rate, globalDofIdx, timeIdx); + wellModel_.computeTotalRatesForDof(rate, globalDofIdx); // convert the source term from the total mass rate of the // cell to the one per unit of volume as used by the model. diff --git a/opm/simulators/aquifers/AquiferNumerical.hpp b/opm/simulators/aquifers/AquiferNumerical.hpp index d04cf3157..f06ffe75c 100644 --- a/opm/simulators/aquifers/AquiferNumerical.hpp +++ b/opm/simulators/aquifers/AquiferNumerical.hpp @@ -253,7 +253,7 @@ private: } template - const double getWaterFlux(const ElemCtx& elem_ctx, unsigned face_idx) const + double getWaterFlux(const ElemCtx& elem_ctx, unsigned face_idx) const { const auto& exQuants = elem_ctx.extensiveQuantities(face_idx, /*timeIdx*/ 0); const double water_flux = Toolbox::value(exQuants.volumeFlux(this->phaseIdx_())); diff --git a/opm/simulators/wells/BlackoilWellModel.hpp b/opm/simulators/wells/BlackoilWellModel.hpp index eac9891dc..ec36549cd 100644 --- a/opm/simulators/wells/BlackoilWellModel.hpp +++ b/opm/simulators/wells/BlackoilWellModel.hpp @@ -210,8 +210,7 @@ namespace Opm { } void computeTotalRatesForDof(RateVector& rate, - unsigned globalIdx, - unsigned timeIdx) const; + unsigned globalIdx) const; template void computeTotalRatesForDof(RateVector& rate, diff --git a/opm/simulators/wells/BlackoilWellModel_impl.hpp b/opm/simulators/wells/BlackoilWellModel_impl.hpp index b28fa609e..066da1432 100644 --- a/opm/simulators/wells/BlackoilWellModel_impl.hpp +++ b/opm/simulators/wells/BlackoilWellModel_impl.hpp @@ -499,15 +499,15 @@ namespace Opm { this->computeWellTemperature(); } + template void BlackoilWellModel:: computeTotalRatesForDof(RateVector& rate, - unsigned elemIdx, - unsigned timeIdx) const + unsigned elemIdx) const { rate = 0; - + if (!is_cell_perforated_[elemIdx]) return; @@ -515,7 +515,7 @@ namespace Opm { well->addCellRates(rate, elemIdx); } - + template template void