mirror of
https://github.com/OPM/opm-simulators.git
synced 2026-10-24 22:57:42 -05:00
2105 lines
97 KiB
C++
2105 lines
97 KiB
C++
// -*- 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 <http://www.gnu.org/licenses/>.
|
|
|
|
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::OutputBlackOilModule
|
|
*/
|
|
#ifndef OPM_OUTPUT_BLACK_OIL_MODULE_HPP
|
|
#define OPM_OUTPUT_BLACK_OIL_MODULE_HPP
|
|
|
|
#include <dune/common/fvector.hh>
|
|
|
|
#include <opm/simulators/utils/moduleVersion.hpp>
|
|
|
|
#include <opm/common/Exceptions.hpp>
|
|
#include <opm/common/TimingMacros.hpp>
|
|
#include <opm/common/OpmLog/OpmLog.hpp>
|
|
#include <opm/common/utility/Visitor.hpp>
|
|
|
|
#include <opm/input/eclipse/EclipseState/SummaryConfig/SummaryConfig.hpp>
|
|
|
|
#include <opm/material/common/Valgrind.hpp>
|
|
#include <opm/material/fluidmatrixinteractions/EclEpsScalingPoints.hpp>
|
|
#include <opm/material/fluidstates/BlackOilFluidState.hpp>
|
|
#include <opm/material/fluidsystems/BlackOilFluidSystem.hpp>
|
|
|
|
#include <opm/models/blackoil/blackoilproperties.hh>
|
|
#include <opm/models/discretization/common/fvbaseproperties.hh>
|
|
#include <opm/models/utils/parametersystem.hpp>
|
|
#include <opm/models/utils/propertysystem.hh>
|
|
|
|
#include <opm/output/data/Cells.hpp>
|
|
#include <opm/output/eclipse/EclipseIO.hpp>
|
|
#include <opm/output/eclipse/Inplace.hpp>
|
|
|
|
#include <opm/simulators/flow/CollectDataOnIORank.hpp>
|
|
#include <opm/simulators/flow/FlowBaseVanguard.hpp>
|
|
#include <opm/simulators/flow/GenericOutputBlackoilModule.hpp>
|
|
#include <opm/simulators/flow/OutputExtractor.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <cstddef>
|
|
#include <functional>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace Opm {
|
|
|
|
// forward declaration
|
|
template <class TypeTag>
|
|
class EcfvDiscretization;
|
|
|
|
/*!
|
|
* \ingroup BlackOilSimulator
|
|
*
|
|
* \brief Output module for the results black oil model writing in
|
|
* ECL binary format.
|
|
*/
|
|
template <class TypeTag>
|
|
class OutputBlackOilModule : public GenericOutputBlackoilModule<GetPropType<TypeTag, Properties::FluidSystem>>
|
|
{
|
|
using Simulator = GetPropType<TypeTag, Properties::Simulator>;
|
|
using Discretization = GetPropType<TypeTag, Properties::Discretization>;
|
|
using Scalar = GetPropType<TypeTag, Properties::Scalar>;
|
|
using Evaluation = GetPropType<TypeTag, Properties::Evaluation>;
|
|
using ElementContext = GetPropType<TypeTag, Properties::ElementContext>;
|
|
using MaterialLaw = GetPropType<TypeTag, Properties::MaterialLaw>;
|
|
using MaterialLawParams = GetPropType<TypeTag, Properties::MaterialLawParams>;
|
|
using IntensiveQuantities = GetPropType<TypeTag, Properties::IntensiveQuantities>;
|
|
using FluidSystem = GetPropType<TypeTag, Properties::FluidSystem>;
|
|
using FluidState = typename IntensiveQuantities::FluidState;
|
|
using GridView = GetPropType<TypeTag, Properties::GridView>;
|
|
using Element = typename GridView::template Codim<0>::Entity;
|
|
using ElementIterator = typename GridView::template Codim<0>::Iterator;
|
|
using BaseType = GenericOutputBlackoilModule<FluidSystem>;
|
|
using Indices = GetPropType<TypeTag, Properties::Indices>;
|
|
using Dir = FaceDir::DirEnum;
|
|
using BlockExtractor = detail::BlockExtractor<TypeTag>;
|
|
using Extractor = detail::Extractor<TypeTag>;
|
|
using Grid = GetPropType<TypeTag, Properties::Grid>;
|
|
using EquilGrid = GetPropType<TypeTag, Properties::EquilGrid>;
|
|
using CollectDataOnIORankType = CollectDataOnIORank<Grid,EquilGrid,GridView>;
|
|
|
|
static constexpr int conti0EqIdx = Indices::conti0EqIdx;
|
|
static constexpr int numPhases = FluidSystem::numPhases;
|
|
static constexpr int oilPhaseIdx = FluidSystem::oilPhaseIdx;
|
|
static constexpr int gasPhaseIdx = FluidSystem::gasPhaseIdx;
|
|
static constexpr int waterPhaseIdx = FluidSystem::waterPhaseIdx;
|
|
static constexpr int gasCompIdx = FluidSystem::gasCompIdx;
|
|
static constexpr int oilCompIdx = FluidSystem::oilCompIdx;
|
|
static constexpr int waterCompIdx = FluidSystem::waterCompIdx;
|
|
enum { enableEnergy = getPropValue<TypeTag, Properties::EnableEnergy>() };
|
|
enum { enableMICP = getPropValue<TypeTag, Properties::EnableMICP>() };
|
|
|
|
template<int idx, class VectorType>
|
|
static Scalar value_or_zero(const VectorType& v)
|
|
{
|
|
if constexpr (idx == -1) {
|
|
return 0.0;
|
|
} else {
|
|
return v.empty() ? 0.0 : v[idx];
|
|
}
|
|
}
|
|
|
|
public:
|
|
OutputBlackOilModule(const Simulator& simulator,
|
|
const SummaryConfig& smryCfg,
|
|
const CollectDataOnIORankType& collectOnIORank)
|
|
: BaseType(simulator.vanguard().eclState(),
|
|
simulator.vanguard().schedule(),
|
|
smryCfg,
|
|
simulator.vanguard().summaryState(),
|
|
moduleVersionName(),
|
|
[this](const int idx)
|
|
{ return simulator_.problem().eclWriter().collectOnIORank().localIdxToGlobalIdx(idx); },
|
|
simulator.vanguard().grid().comm(),
|
|
getPropValue<TypeTag, Properties::EnableEnergy>(),
|
|
getPropValue<TypeTag, Properties::EnableTemperature>(),
|
|
getPropValue<TypeTag, Properties::EnableMech>(),
|
|
getPropValue<TypeTag, Properties::EnableSolvent>(),
|
|
getPropValue<TypeTag, Properties::EnablePolymer>(),
|
|
getPropValue<TypeTag, Properties::EnableFoam>(),
|
|
getPropValue<TypeTag, Properties::EnableBrine>(),
|
|
getPropValue<TypeTag, Properties::EnableSaltPrecipitation>(),
|
|
getPropValue<TypeTag, Properties::EnableExtbo>(),
|
|
getPropValue<TypeTag, Properties::EnableMICP>())
|
|
, simulator_(simulator)
|
|
, collectOnIORank_(collectOnIORank)
|
|
{
|
|
for (auto& region_pair : this->regions_) {
|
|
this->createLocalRegion_(region_pair.second);
|
|
}
|
|
|
|
auto isCartIdxOnThisRank = [&collectOnIORank](const int idx) {
|
|
return collectOnIORank.isCartIdxOnThisRank(idx);
|
|
};
|
|
|
|
this->setupBlockData(isCartIdxOnThisRank);
|
|
|
|
if (! Parameters::Get<Parameters::OwnerCellsFirst>()) {
|
|
const std::string msg = "The output code does not support --owner-cells-first=false.";
|
|
if (collectOnIORank.isIORank()) {
|
|
OpmLog::error(msg);
|
|
}
|
|
OPM_THROW_NOLOG(std::runtime_error, msg);
|
|
}
|
|
|
|
if (smryCfg.match("[FB]PP[OGW]") || smryCfg.match("RPP[OGW]*")) {
|
|
auto rset = this->eclState_.fieldProps().fip_regions();
|
|
rset.push_back("PVTNUM");
|
|
|
|
// Note: We explicitly use decltype(auto) here because the
|
|
// default scheme (-> auto) will deduce an undesirable type. We
|
|
// need the "reference to vector" semantics in this instance.
|
|
this->regionAvgDensity_
|
|
.emplace(this->simulator_.gridView().comm(),
|
|
FluidSystem::numPhases, rset,
|
|
[fp = std::cref(this->eclState_.fieldProps())]
|
|
(const std::string& rsetName) -> decltype(auto)
|
|
{ return fp.get().get_int(rsetName); });
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Allocate memory for the scalar fields we would like to
|
|
* write to ECL output files
|
|
*/
|
|
void
|
|
allocBuffers(const unsigned bufferSize,
|
|
const unsigned reportStepNum,
|
|
const bool substep,
|
|
const bool log,
|
|
const bool isRestart)
|
|
{
|
|
if (! std::is_same<Discretization, EcfvDiscretization<TypeTag>>::value) {
|
|
return;
|
|
}
|
|
|
|
const auto& problem = this->simulator_.problem();
|
|
|
|
this->doAllocBuffers(bufferSize,
|
|
reportStepNum,
|
|
substep,
|
|
log,
|
|
isRestart,
|
|
&problem.materialLawManager()->hysteresisConfig(),
|
|
problem.eclWriter().getOutputNnc().size());
|
|
}
|
|
|
|
//! \brief Setup list of active element-level data extractors
|
|
void setupExtractors(const bool isSubStep,
|
|
const int reportStepNum)
|
|
{
|
|
this->setupElementExtractors_();
|
|
this->setupBlockExtractors_(isSubStep, reportStepNum);
|
|
}
|
|
|
|
//! \brief Clear list of active element-level data extractors
|
|
void clearExtractors()
|
|
{
|
|
this->extractors_.clear();
|
|
this->blockExtractors_.clear();
|
|
this->extraBlockExtractors_.clear();
|
|
}
|
|
|
|
/*!
|
|
* \brief Modify the internal buffers according to the intensive
|
|
* quanties relevant for an element
|
|
*/
|
|
void processElement(const ElementContext& elemCtx)
|
|
{
|
|
OPM_TIMEBLOCK_LOCAL(processElement);
|
|
if (!std::is_same<Discretization, EcfvDiscretization<TypeTag>>::value) {
|
|
return;
|
|
}
|
|
|
|
if (this->extractors_.empty()) {
|
|
assert(0);
|
|
}
|
|
|
|
const auto& matLawManager = simulator_.problem().materialLawManager();
|
|
|
|
typename Extractor::HysteresisParams hysterParams;
|
|
for (unsigned dofIdx = 0; dofIdx < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++dofIdx) {
|
|
const auto& intQuants = elemCtx.intensiveQuantities(dofIdx, /*timeIdx=*/0);
|
|
const auto& fs = intQuants.fluidState();
|
|
|
|
const typename Extractor::Context ectx{
|
|
elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0),
|
|
elemCtx.primaryVars(dofIdx, /*timeIdx=*/0).pvtRegionIndex(),
|
|
elemCtx.simulator().episodeIndex(),
|
|
fs,
|
|
intQuants,
|
|
hysterParams
|
|
};
|
|
|
|
if (matLawManager->enableHysteresis()) {
|
|
if (FluidSystem::phaseIsActive(oilPhaseIdx) && FluidSystem::phaseIsActive(waterPhaseIdx)) {
|
|
matLawManager->oilWaterHysteresisParams(hysterParams.somax,
|
|
hysterParams.swmax,
|
|
hysterParams.swmin,
|
|
ectx.globalDofIdx);
|
|
}
|
|
if (FluidSystem::phaseIsActive(oilPhaseIdx) && FluidSystem::phaseIsActive(gasPhaseIdx)) {
|
|
matLawManager->gasOilHysteresisParams(hysterParams.sgmax,
|
|
hysterParams.shmax,
|
|
hysterParams.somin,
|
|
ectx.globalDofIdx);
|
|
}
|
|
}
|
|
|
|
Extractor::process(ectx, extractors_);
|
|
}
|
|
}
|
|
|
|
void processElementBlockData(const ElementContext& elemCtx)
|
|
{
|
|
OPM_TIMEBLOCK_LOCAL(processElementBlockData);
|
|
if (!std::is_same<Discretization, EcfvDiscretization<TypeTag>>::value) {
|
|
return;
|
|
}
|
|
|
|
if (this->blockExtractors_.empty() && this->extraBlockExtractors_.empty()) {
|
|
return;
|
|
}
|
|
|
|
for (unsigned dofIdx = 0; dofIdx < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++dofIdx) {
|
|
// Adding block data
|
|
const auto globalDofIdx = elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0);
|
|
const auto cartesianIdx = elemCtx.simulator().vanguard().cartesianIndex(globalDofIdx);
|
|
|
|
const auto be_it = this->blockExtractors_.find(cartesianIdx);
|
|
const auto bee_it = this->extraBlockExtractors_.find(cartesianIdx);
|
|
if (be_it == this->blockExtractors_.end() &&
|
|
bee_it == this->extraBlockExtractors_.end())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const auto& intQuants = elemCtx.intensiveQuantities(dofIdx, /*timeIdx=*/0);
|
|
const auto& fs = intQuants.fluidState();
|
|
|
|
const typename BlockExtractor::Context ectx{
|
|
globalDofIdx,
|
|
dofIdx,
|
|
fs,
|
|
intQuants,
|
|
elemCtx,
|
|
};
|
|
|
|
if (be_it != this->blockExtractors_.end()) {
|
|
BlockExtractor::process(be_it->second, ectx);
|
|
}
|
|
if (bee_it != this->extraBlockExtractors_.end()) {
|
|
BlockExtractor::process(bee_it->second, ectx);
|
|
}
|
|
}
|
|
}
|
|
|
|
void outputFipAndResvLog(const Inplace& inplace,
|
|
const std::size_t reportStepNum,
|
|
double elapsed,
|
|
boost::posix_time::ptime currentDate,
|
|
const bool substep,
|
|
const Parallel::Communication& comm)
|
|
{
|
|
|
|
if (comm.rank() != 0) {
|
|
return;
|
|
}
|
|
|
|
// For report step 0 we use the RPTSOL config, else derive from RPTSCHED
|
|
std::unique_ptr<FIPConfig> fipSched;
|
|
if (reportStepNum > 0) {
|
|
const auto& rpt = this->schedule_[reportStepNum-1].rpt_config.get();
|
|
fipSched = std::make_unique<FIPConfig>(rpt);
|
|
}
|
|
const FIPConfig& fipc = reportStepNum == 0 ? this->eclState_.getEclipseConfig().fip()
|
|
: *fipSched;
|
|
|
|
if (!substep && !this->forceDisableFipOutput_ && fipc.output(FIPConfig::OutputField::FIELD)) {
|
|
|
|
this->logOutput_.timeStamp("BALANCE", elapsed, reportStepNum, currentDate);
|
|
|
|
const auto& initial_inplace = this->initialInplace().value();
|
|
this->logOutput_.fip(inplace, initial_inplace, "");
|
|
|
|
if (fipc.output(FIPConfig::OutputField::FIPNUM)) {
|
|
this->logOutput_.fip(inplace, initial_inplace, "FIPNUM");
|
|
|
|
if (fipc.output(FIPConfig::OutputField::RESV))
|
|
this->logOutput_.fipResv(inplace, "FIPNUM");
|
|
}
|
|
|
|
if (fipc.output(FIPConfig::OutputField::FIP)) {
|
|
for (const auto& reg : this->regions_) {
|
|
if (reg.first != "FIPNUM") {
|
|
std::ostringstream ss;
|
|
ss << "BAL" << reg.first.substr(3);
|
|
this->logOutput_.timeStamp(ss.str(), elapsed, reportStepNum, currentDate);
|
|
this->logOutput_.fip(inplace, initial_inplace, reg.first);
|
|
|
|
if (fipc.output(FIPConfig::OutputField::RESV))
|
|
this->logOutput_.fipResv(inplace, reg.first);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Capture connection fluxes, particularly to account for inter-region flows.
|
|
*
|
|
* \tparam ActiveIndex Callable type, typically a lambda, that enables
|
|
* retrieving the active index, on the local MPI rank, of a
|
|
* particular cell/element. Must support a function call operator of
|
|
* the form
|
|
\code
|
|
int operator()(const Element& elem) const
|
|
\endcode
|
|
*
|
|
* \tparam CartesianIndex Callable type, typically a lambda, that
|
|
* enables retrieving the globally unique Cartesian index of a
|
|
* particular cell/element given its active index on the local MPI
|
|
* rank. Must support a function call operator of the form
|
|
\code
|
|
int operator()(const int activeIndex) const
|
|
\endcode
|
|
*
|
|
* \param[in] elemCtx Primary lookup structure for per-cell/element
|
|
* dynamic information.
|
|
*
|
|
* \param[in] activeIndex Mapping from cell/elements to linear indices
|
|
* on local MPI rank.
|
|
*
|
|
* \param[in] cartesianIndex Mapping from active index on local MPI rank
|
|
* to globally unique Cartesian cell/element index.
|
|
*/
|
|
template <class ActiveIndex, class CartesianIndex>
|
|
void processFluxes(const ElementContext& elemCtx,
|
|
ActiveIndex&& activeIndex,
|
|
CartesianIndex&& cartesianIndex)
|
|
{
|
|
OPM_TIMEBLOCK_LOCAL(processFluxes);
|
|
const auto identifyCell = [&activeIndex, &cartesianIndex](const Element& elem)
|
|
-> InterRegFlowMap::Cell
|
|
{
|
|
const auto cellIndex = activeIndex(elem);
|
|
|
|
return {
|
|
static_cast<int>(cellIndex),
|
|
cartesianIndex(cellIndex),
|
|
elem.partitionType() == Dune::InteriorEntity
|
|
};
|
|
};
|
|
|
|
const auto timeIdx = 0u;
|
|
const auto& stencil = elemCtx.stencil(timeIdx);
|
|
const auto numInteriorFaces = elemCtx.numInteriorFaces(timeIdx);
|
|
|
|
for (auto scvfIdx = 0 * numInteriorFaces; scvfIdx < numInteriorFaces; ++scvfIdx) {
|
|
const auto& face = stencil.interiorFace(scvfIdx);
|
|
const auto left = identifyCell(stencil.element(face.interiorIndex()));
|
|
const auto right = identifyCell(stencil.element(face.exteriorIndex()));
|
|
|
|
const auto rates = this->
|
|
getComponentSurfaceRates(elemCtx, face.area(), scvfIdx, timeIdx);
|
|
|
|
this->interRegionFlows_.addConnection(left, right, rates);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Prepare for capturing connection fluxes, particularly to
|
|
* account for inter-region flows.
|
|
*/
|
|
void initializeFluxData()
|
|
{
|
|
// Inter-region flow rates. Note: ".clear()" prepares to accumulate
|
|
// contributions per bulk connection between FIP regions.
|
|
this->interRegionFlows_.clear();
|
|
}
|
|
|
|
/*!
|
|
* \brief Finalize capturing connection fluxes.
|
|
*/
|
|
void finalizeFluxData()
|
|
{
|
|
this->interRegionFlows_.compress();
|
|
}
|
|
|
|
/*!
|
|
* \brief Get read-only access to collection of inter-region flows.
|
|
*/
|
|
const InterRegFlowMap& getInterRegFlows() const
|
|
{
|
|
return this->interRegionFlows_;
|
|
}
|
|
|
|
template <class FluidState>
|
|
void assignToFluidState(FluidState& fs, unsigned elemIdx) const
|
|
{
|
|
for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) {
|
|
if (this->saturation_[phaseIdx].empty())
|
|
continue;
|
|
|
|
fs.setSaturation(phaseIdx, this->saturation_[phaseIdx][elemIdx]);
|
|
}
|
|
|
|
if (!this->fluidPressure_.empty()) {
|
|
// this assumes that capillary pressures only depend on the phase saturations
|
|
// and possibly on temperature. (this is always the case for ECL problems.)
|
|
std::array<Scalar, numPhases> pc = {0};
|
|
const MaterialLawParams& matParams = simulator_.problem().materialLawParams(elemIdx);
|
|
MaterialLaw::capillaryPressures(pc, matParams, fs);
|
|
Valgrind::CheckDefined(this->fluidPressure_[elemIdx]);
|
|
Valgrind::CheckDefined(pc);
|
|
const auto& pressure = this->fluidPressure_[elemIdx];
|
|
for (unsigned phaseIdx = 0; phaseIdx < numPhases; ++phaseIdx) {
|
|
if (!FluidSystem::phaseIsActive(phaseIdx))
|
|
continue;
|
|
|
|
if (Indices::oilEnabled)
|
|
fs.setPressure(phaseIdx, pressure + (pc[phaseIdx] - pc[oilPhaseIdx]));
|
|
else if (Indices::gasEnabled)
|
|
fs.setPressure(phaseIdx, pressure + (pc[phaseIdx] - pc[gasPhaseIdx]));
|
|
else if (Indices::waterEnabled)
|
|
//single (water) phase
|
|
fs.setPressure(phaseIdx, pressure);
|
|
}
|
|
}
|
|
|
|
if (!this->temperature_.empty())
|
|
fs.setTemperature(this->temperature_[elemIdx]);
|
|
if (!this->rs_.empty())
|
|
fs.setRs(this->rs_[elemIdx]);
|
|
if (!this->rsw_.empty())
|
|
fs.setRsw(this->rsw_[elemIdx]);
|
|
if (!this->rv_.empty())
|
|
fs.setRv(this->rv_[elemIdx]);
|
|
if (!this->rvw_.empty())
|
|
fs.setRvw(this->rvw_[elemIdx]);
|
|
}
|
|
|
|
void initHysteresisParams(Simulator& simulator, unsigned elemIdx) const
|
|
{
|
|
if (!this->soMax_.empty())
|
|
simulator.problem().setMaxOilSaturation(elemIdx, this->soMax_[elemIdx]);
|
|
|
|
if (simulator.problem().materialLawManager()->enableHysteresis()) {
|
|
auto matLawManager = simulator.problem().materialLawManager();
|
|
|
|
if (FluidSystem::phaseIsActive(oilPhaseIdx)
|
|
&& FluidSystem::phaseIsActive(waterPhaseIdx)) {
|
|
Scalar somax = 2.0;
|
|
Scalar swmax = -2.0;
|
|
Scalar swmin = 2.0;
|
|
|
|
if (matLawManager->enableNonWettingHysteresis()) {
|
|
if (!this->soMax_.empty()) {
|
|
somax = this->soMax_[elemIdx];
|
|
}
|
|
}
|
|
if (matLawManager->enableWettingHysteresis()) {
|
|
if (!this->swMax_.empty()) {
|
|
swmax = this->swMax_[elemIdx];
|
|
}
|
|
}
|
|
if (matLawManager->enablePCHysteresis()) {
|
|
if (!this->swmin_.empty()) {
|
|
swmin = this->swmin_[elemIdx];
|
|
}
|
|
}
|
|
matLawManager->setOilWaterHysteresisParams(
|
|
somax, swmax, swmin, elemIdx);
|
|
}
|
|
if (FluidSystem::phaseIsActive(oilPhaseIdx)
|
|
&& FluidSystem::phaseIsActive(gasPhaseIdx)) {
|
|
Scalar sgmax = 2.0;
|
|
Scalar shmax = -2.0;
|
|
Scalar somin = 2.0;
|
|
|
|
if (matLawManager->enableNonWettingHysteresis()) {
|
|
if (!this->sgmax_.empty()) {
|
|
sgmax = this->sgmax_[elemIdx];
|
|
}
|
|
}
|
|
if (matLawManager->enableWettingHysteresis()) {
|
|
if (!this->shmax_.empty()) {
|
|
shmax = this->shmax_[elemIdx];
|
|
}
|
|
}
|
|
if (matLawManager->enablePCHysteresis()) {
|
|
if (!this->somin_.empty()) {
|
|
somin = this->somin_[elemIdx];
|
|
}
|
|
}
|
|
matLawManager->setGasOilHysteresisParams(
|
|
sgmax, shmax, somin, elemIdx);
|
|
}
|
|
|
|
}
|
|
|
|
if (simulator_.vanguard().eclState().fieldProps().has_double("SWATINIT")) {
|
|
simulator.problem().materialLawManager()
|
|
->applyRestartSwatInit(elemIdx, this->ppcw_[elemIdx]);
|
|
}
|
|
}
|
|
|
|
void updateFluidInPlace(const ElementContext& elemCtx)
|
|
{
|
|
for (unsigned dofIdx = 0; dofIdx < elemCtx.numPrimaryDof(/*timeIdx=*/0); ++dofIdx) {
|
|
updateFluidInPlace_(elemCtx, dofIdx);
|
|
}
|
|
}
|
|
|
|
void updateFluidInPlace(const unsigned globalDofIdx,
|
|
const IntensiveQuantities& intQuants,
|
|
const double totVolume)
|
|
{
|
|
this->updateFluidInPlace_(globalDofIdx, intQuants, totVolume);
|
|
}
|
|
|
|
private:
|
|
template <typename T>
|
|
using RemoveCVR = std::remove_cv_t<std::remove_reference_t<T>>;
|
|
|
|
template <typename, class = void>
|
|
struct HasGeoMech : public std::false_type {};
|
|
|
|
template <typename Problem>
|
|
struct HasGeoMech<
|
|
Problem, std::void_t<decltype(std::declval<Problem>().geoMechModel())>
|
|
> : public std::true_type {};
|
|
|
|
bool isDefunctParallelWell(std::string wname) const override
|
|
{
|
|
if (simulator_.gridView().comm().size() == 1)
|
|
return false;
|
|
const auto& parallelWells = simulator_.vanguard().parallelWells();
|
|
std::pair<std::string, bool> value {wname, true};
|
|
auto candidate = std::lower_bound(parallelWells.begin(), parallelWells.end(), value);
|
|
return candidate == parallelWells.end() || *candidate != value;
|
|
}
|
|
|
|
void updateFluidInPlace_(const ElementContext& elemCtx, const unsigned dofIdx)
|
|
{
|
|
const auto& intQuants = elemCtx.intensiveQuantities(dofIdx, /*timeIdx=*/0);
|
|
const unsigned globalDofIdx = elemCtx.globalSpaceIndex(dofIdx, /*timeIdx=*/0);
|
|
const auto totVolume = elemCtx.simulator().model().dofTotalVolume(globalDofIdx);
|
|
|
|
this->updateFluidInPlace_(globalDofIdx, intQuants, totVolume);
|
|
}
|
|
|
|
void updateFluidInPlace_(const unsigned globalDofIdx,
|
|
const IntensiveQuantities& intQuants,
|
|
const double totVolume)
|
|
{
|
|
OPM_TIMEBLOCK_LOCAL(updateFluidInPlace);
|
|
|
|
this->updateTotalVolumesAndPressures_(globalDofIdx, intQuants, totVolume);
|
|
|
|
if (this->computeFip_) {
|
|
this->updatePhaseInplaceVolumes_(globalDofIdx, intQuants, totVolume);
|
|
}
|
|
}
|
|
|
|
void createLocalRegion_(std::vector<int>& region)
|
|
{
|
|
std::size_t elemIdx = 0;
|
|
for (const auto& elem : elements(simulator_.gridView())) {
|
|
if (elem.partitionType() != Dune::InteriorEntity) {
|
|
region[elemIdx] = 0;
|
|
}
|
|
|
|
++elemIdx;
|
|
}
|
|
}
|
|
|
|
template <typename FluidState>
|
|
void aggregateAverageDensityContributions_(const FluidState& fs,
|
|
const unsigned int globalDofIdx,
|
|
const double porv)
|
|
{
|
|
auto pvCellValue = RegionPhasePoreVolAverage::CellValue{};
|
|
pvCellValue.porv = porv;
|
|
|
|
for (auto phaseIdx = 0*FluidSystem::numPhases;
|
|
phaseIdx < FluidSystem::numPhases; ++phaseIdx)
|
|
{
|
|
if (! FluidSystem::phaseIsActive(phaseIdx)) {
|
|
continue;
|
|
}
|
|
|
|
pvCellValue.value = getValue(fs.density(phaseIdx));
|
|
pvCellValue.sat = getValue(fs.saturation(phaseIdx));
|
|
|
|
this->regionAvgDensity_
|
|
->addCell(globalDofIdx,
|
|
RegionPhasePoreVolAverage::Phase { phaseIdx },
|
|
pvCellValue);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Compute surface level component flow rates across a single
|
|
* intersection.
|
|
*
|
|
* \param[in] elemCtx Primary lookup structure for per-cell/element
|
|
* dynamic information.
|
|
*
|
|
* \param[in] scvfIdx Linear index of current interior bulk connection.
|
|
*
|
|
* \param[in] timeIdx Historical time-point at which to evaluate dynamic
|
|
* quantities (e.g., reciprocal FVF or dissolved gas concentration).
|
|
* Zero for the current time.
|
|
*
|
|
* \return Surface level component flow rates.
|
|
*/
|
|
data::InterRegFlowMap::FlowRates
|
|
getComponentSurfaceRates(const ElementContext& elemCtx,
|
|
const Scalar faceArea,
|
|
const std::size_t scvfIdx,
|
|
const std::size_t timeIdx) const
|
|
{
|
|
using Component = data::InterRegFlowMap::Component;
|
|
|
|
auto rates = data::InterRegFlowMap::FlowRates {};
|
|
|
|
const auto& extQuant = elemCtx.extensiveQuantities(scvfIdx, timeIdx);
|
|
|
|
const auto alpha = getValue(extQuant.extrusionFactor()) * faceArea;
|
|
|
|
if (FluidSystem::phaseIsActive(oilPhaseIdx)) {
|
|
const auto& up = elemCtx
|
|
.intensiveQuantities(extQuant.upstreamIndex(oilPhaseIdx), timeIdx);
|
|
|
|
const auto pvtReg = up.pvtRegionIndex();
|
|
|
|
const auto bO = getValue(getInvB_<FluidSystem, FluidState, Scalar>
|
|
(up.fluidState(), oilPhaseIdx, pvtReg));
|
|
|
|
const auto qO = alpha * bO * getValue(extQuant.volumeFlux(oilPhaseIdx));
|
|
|
|
rates[Component::Oil] += qO;
|
|
|
|
if (FluidSystem::phaseIsActive(gasPhaseIdx)) {
|
|
const auto Rs = getValue(
|
|
BlackOil::getRs_<FluidSystem, FluidState, Scalar>
|
|
(up.fluidState(), pvtReg));
|
|
|
|
rates[Component::Gas] += qO * Rs;
|
|
rates[Component::Disgas] += qO * Rs;
|
|
}
|
|
}
|
|
|
|
if (FluidSystem::phaseIsActive(gasPhaseIdx)) {
|
|
const auto& up = elemCtx
|
|
.intensiveQuantities(extQuant.upstreamIndex(gasPhaseIdx), timeIdx);
|
|
|
|
const auto pvtReg = up.pvtRegionIndex();
|
|
|
|
const auto bG = getValue(getInvB_<FluidSystem, FluidState, Scalar>
|
|
(up.fluidState(), gasPhaseIdx, pvtReg));
|
|
|
|
const auto qG = alpha * bG * getValue(extQuant.volumeFlux(gasPhaseIdx));
|
|
|
|
rates[Component::Gas] += qG;
|
|
|
|
if (FluidSystem::phaseIsActive(oilPhaseIdx)) {
|
|
const auto Rv = getValue(
|
|
BlackOil::getRv_<FluidSystem, FluidState, Scalar>
|
|
(up.fluidState(), pvtReg));
|
|
|
|
rates[Component::Oil] += qG * Rv;
|
|
rates[Component::Vapoil] += qG * Rv;
|
|
}
|
|
}
|
|
|
|
if (FluidSystem::phaseIsActive(waterPhaseIdx)) {
|
|
const auto& up = elemCtx
|
|
.intensiveQuantities(extQuant.upstreamIndex(waterPhaseIdx), timeIdx);
|
|
|
|
const auto pvtReg = up.pvtRegionIndex();
|
|
|
|
const auto bW = getValue(getInvB_<FluidSystem, FluidState, Scalar>
|
|
(up.fluidState(), waterPhaseIdx, pvtReg));
|
|
|
|
rates[Component::Water] +=
|
|
alpha * bW * getValue(extQuant.volumeFlux(waterPhaseIdx));
|
|
}
|
|
|
|
return rates;
|
|
}
|
|
|
|
template <typename FluidState>
|
|
Scalar hydroCarbonFraction(const FluidState& fs) const
|
|
{
|
|
if (this->eclState_.runspec().co2Storage()) {
|
|
// CO2 storage: Hydrocarbon volume is full pore-volume.
|
|
return 1.0;
|
|
}
|
|
|
|
// Common case. Hydrocarbon volume is fraction occupied by actual
|
|
// hydrocarbons.
|
|
auto hydrocarbon = Scalar {0};
|
|
if (FluidSystem::phaseIsActive(oilPhaseIdx)) {
|
|
hydrocarbon += getValue(fs.saturation(oilPhaseIdx));
|
|
}
|
|
|
|
if (FluidSystem::phaseIsActive(gasPhaseIdx)) {
|
|
hydrocarbon += getValue(fs.saturation(gasPhaseIdx));
|
|
}
|
|
|
|
return hydrocarbon;
|
|
}
|
|
|
|
void updateTotalVolumesAndPressures_(const unsigned globalDofIdx,
|
|
const IntensiveQuantities& intQuants,
|
|
const double totVolume)
|
|
{
|
|
const auto& fs = intQuants.fluidState();
|
|
|
|
const double pv = totVolume * intQuants.porosity().value();
|
|
const auto hydrocarbon = this->hydroCarbonFraction(fs);
|
|
|
|
if (! this->hydrocarbonPoreVolume_.empty()) {
|
|
this->fipC_.assignPoreVolume(globalDofIdx,
|
|
totVolume * intQuants.referencePorosity());
|
|
|
|
this->dynamicPoreVolume_[globalDofIdx] = pv;
|
|
this->hydrocarbonPoreVolume_[globalDofIdx] = pv * hydrocarbon;
|
|
}
|
|
|
|
if (!this->pressureTimesHydrocarbonVolume_.empty() &&
|
|
!this->pressureTimesPoreVolume_.empty())
|
|
{
|
|
assert(this->hydrocarbonPoreVolume_.size() == this->pressureTimesHydrocarbonVolume_.size());
|
|
assert(this->fipC_.get(Inplace::Phase::PoreVolume).size() == this->pressureTimesPoreVolume_.size());
|
|
|
|
if (FluidSystem::phaseIsActive(oilPhaseIdx)) {
|
|
this->pressureTimesPoreVolume_[globalDofIdx] =
|
|
getValue(fs.pressure(oilPhaseIdx)) * pv;
|
|
|
|
this->pressureTimesHydrocarbonVolume_[globalDofIdx] =
|
|
this->pressureTimesPoreVolume_[globalDofIdx] * hydrocarbon;
|
|
}
|
|
else if (FluidSystem::phaseIsActive(gasPhaseIdx)) {
|
|
this->pressureTimesPoreVolume_[globalDofIdx] =
|
|
getValue(fs.pressure(gasPhaseIdx)) * pv;
|
|
|
|
this->pressureTimesHydrocarbonVolume_[globalDofIdx] =
|
|
this->pressureTimesPoreVolume_[globalDofIdx] * hydrocarbon;
|
|
}
|
|
else if (FluidSystem::phaseIsActive(waterPhaseIdx)) {
|
|
this->pressureTimesPoreVolume_[globalDofIdx] =
|
|
getValue(fs.pressure(waterPhaseIdx)) * pv;
|
|
}
|
|
}
|
|
}
|
|
|
|
void updatePhaseInplaceVolumes_(const unsigned globalDofIdx,
|
|
const IntensiveQuantities& intQuants,
|
|
const double totVolume)
|
|
{
|
|
std::array<Scalar, FluidSystem::numPhases> fip {};
|
|
std::array<Scalar, FluidSystem::numPhases> fipr{}; // at reservoir condition
|
|
|
|
const auto& fs = intQuants.fluidState();
|
|
const auto pv = totVolume * intQuants.porosity().value();
|
|
|
|
for (unsigned phaseIdx = 0; phaseIdx < FluidSystem::numPhases; ++phaseIdx) {
|
|
if (!FluidSystem::phaseIsActive(phaseIdx)) {
|
|
continue;
|
|
}
|
|
|
|
const auto b = getValue(fs.invB(phaseIdx));
|
|
const auto s = getValue(fs.saturation(phaseIdx));
|
|
|
|
fipr[phaseIdx] = s * pv;
|
|
fip [phaseIdx] = b * fipr[phaseIdx];
|
|
}
|
|
|
|
this->fipC_.assignVolumesSurface(globalDofIdx, fip);
|
|
this->fipC_.assignVolumesReservoir(globalDofIdx,
|
|
fs.saltConcentration().value(),
|
|
fipr);
|
|
|
|
if (FluidSystem::phaseIsActive(oilPhaseIdx) &&
|
|
FluidSystem::phaseIsActive(gasPhaseIdx))
|
|
{
|
|
this->updateOilGasDistribution(globalDofIdx, fs, fip);
|
|
}
|
|
|
|
if (FluidSystem::phaseIsActive(waterPhaseIdx) &&
|
|
FluidSystem::phaseIsActive(gasPhaseIdx))
|
|
{
|
|
this->updateGasWaterDistribution(globalDofIdx, fs, fip);
|
|
}
|
|
|
|
if (FluidSystem::phaseIsActive(gasPhaseIdx) &&
|
|
this->fipC_.hasCo2InGas())
|
|
{
|
|
this->updateCO2InGas(globalDofIdx, pv, intQuants);
|
|
}
|
|
|
|
if (this->fipC_.hasCo2InWater() &&
|
|
(FluidSystem::phaseIsActive(waterPhaseIdx) ||
|
|
FluidSystem::phaseIsActive(oilPhaseIdx)))
|
|
{
|
|
this->updateCO2InWater(globalDofIdx, pv, fs);
|
|
}
|
|
|
|
if constexpr(enableMICP) {
|
|
const auto surfVolWat = pv * getValue(fs.invB(waterPhaseIdx));
|
|
if (this->fipC_.hasMicrobialMass()) {
|
|
this->updateMicrobialMass(globalDofIdx, intQuants, surfVolWat);
|
|
}
|
|
if (this->fipC_.hasOxygenMass()) {
|
|
this->updateOxygenMass(globalDofIdx, intQuants, surfVolWat);
|
|
}
|
|
if (this->fipC_.hasUreaMass()) {
|
|
this->updateUreaMass(globalDofIdx, intQuants, surfVolWat);
|
|
}
|
|
if (this->fipC_.hasBiofilmMass()) {
|
|
this->updateBiofilmMass(globalDofIdx, intQuants, totVolume);
|
|
}
|
|
if (this->fipC_.hasCalciteMass()) {
|
|
this->updateCalciteMass(globalDofIdx, intQuants, totVolume);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename FluidState, typename FIPArray>
|
|
void updateOilGasDistribution(const unsigned globalDofIdx,
|
|
const FluidState& fs,
|
|
const FIPArray& fip)
|
|
{
|
|
// Gas dissolved in oil and vaporized oil
|
|
const auto gasInPlaceLiquid = getValue(fs.Rs()) * fip[oilPhaseIdx];
|
|
const auto oilInPlaceGas = getValue(fs.Rv()) * fip[gasPhaseIdx];
|
|
|
|
this->fipC_.assignOilGasDistribution(globalDofIdx, gasInPlaceLiquid, oilInPlaceGas);
|
|
}
|
|
|
|
template <typename FluidState, typename FIPArray>
|
|
void updateGasWaterDistribution(const unsigned globalDofIdx,
|
|
const FluidState& fs,
|
|
const FIPArray& fip)
|
|
{
|
|
// Gas dissolved in water and vaporized water
|
|
const auto gasInPlaceWater = getValue(fs.Rsw()) * fip[waterPhaseIdx];
|
|
const auto waterInPlaceGas = getValue(fs.Rvw()) * fip[gasPhaseIdx];
|
|
|
|
this->fipC_.assignGasWater(globalDofIdx, fip, gasInPlaceWater, waterInPlaceGas);
|
|
}
|
|
|
|
template <typename IntensiveQuantities>
|
|
void updateCO2InGas(const unsigned globalDofIdx,
|
|
const double pv,
|
|
const IntensiveQuantities& intQuants)
|
|
{
|
|
const auto& scaledDrainageInfo = this->simulator_.problem().materialLawManager()
|
|
->oilWaterScaledEpsInfoDrainage(globalDofIdx);
|
|
|
|
const auto& fs = intQuants.fluidState();
|
|
Scalar sgcr = scaledDrainageInfo.Sgcr;
|
|
if (this->simulator_.problem().materialLawManager()->enableHysteresis()) {
|
|
const auto& matParams = simulator_.problem().materialLawParams(globalDofIdx);
|
|
sgcr = MaterialLaw::trappedGasSaturation(matParams, /*maximumTrapping*/false);
|
|
}
|
|
|
|
Scalar trappedGasSaturation = scaledDrainageInfo.Sgcr;
|
|
if (this->fipC_.has(Inplace::Phase::CO2MassInGasPhaseMaximumTrapped) ||
|
|
this->fipC_.has(Inplace::Phase::CO2MassInGasPhaseMaximumUnTrapped))
|
|
{
|
|
if (this->simulator_.problem().materialLawManager()->enableHysteresis()) {
|
|
const auto& matParams = simulator_.problem().materialLawParams(globalDofIdx);
|
|
// Get the maximum trapped gas saturation
|
|
trappedGasSaturation = MaterialLaw::trappedGasSaturation(matParams, /*maximumTrapping*/true);
|
|
}
|
|
}
|
|
|
|
const Scalar sg = getValue(fs.saturation(gasPhaseIdx));
|
|
Scalar strandedGasSaturation = scaledDrainageInfo.Sgcr;
|
|
if (this->fipC_.has(Inplace::Phase::CO2MassInGasPhaseEffectiveTrapped) ||
|
|
this->fipC_.has(Inplace::Phase::CO2MassInGasPhaseEffectiveUnTrapped))
|
|
{
|
|
if (this->simulator_.problem().materialLawManager()->enableHysteresis()) {
|
|
const auto& matParams = simulator_.problem().materialLawParams(globalDofIdx);
|
|
const double krg = getValue(intQuants.relativePermeability(gasPhaseIdx));
|
|
strandedGasSaturation = MaterialLaw::strandedGasSaturation(matParams, sg, krg);
|
|
}
|
|
}
|
|
|
|
const typename FIPContainer<FluidSystem>::Co2InGasInput v{
|
|
pv,
|
|
sg,
|
|
sgcr,
|
|
getValue(fs.density(gasPhaseIdx)),
|
|
FluidSystem::phaseIsActive(waterPhaseIdx)
|
|
? FluidSystem::convertRvwToXgW(getValue(fs.Rvw()), fs.pvtRegionIndex())
|
|
: FluidSystem::convertRvToXgO(getValue(fs.Rv()), fs.pvtRegionIndex()),
|
|
FluidSystem::molarMass(gasCompIdx, fs.pvtRegionIndex()),
|
|
trappedGasSaturation,
|
|
strandedGasSaturation,
|
|
};
|
|
|
|
this->fipC_.assignCo2InGas(globalDofIdx, v);
|
|
}
|
|
|
|
template <typename FluidState>
|
|
void updateCO2InWater(const unsigned globalDofIdx,
|
|
const double pv,
|
|
const FluidState& fs)
|
|
{
|
|
const auto co2InWater = FluidSystem::phaseIsActive(oilPhaseIdx)
|
|
? this->co2InWaterFromOil(fs, pv)
|
|
: this->co2InWaterFromWater(fs, pv);
|
|
|
|
const Scalar mM = FluidSystem::molarMass(gasCompIdx, fs.pvtRegionIndex());
|
|
|
|
this->fipC_.assignCo2InWater(globalDofIdx, co2InWater, mM);
|
|
}
|
|
|
|
template <typename FluidState>
|
|
Scalar co2InWaterFromWater(const FluidState& fs, const double pv) const
|
|
{
|
|
const double rhow = getValue(fs.density(waterPhaseIdx));
|
|
const double sw = getValue(fs.saturation(waterPhaseIdx));
|
|
const double xwG = FluidSystem::convertRswToXwG(getValue(fs.Rsw()), fs.pvtRegionIndex());
|
|
|
|
const Scalar mM = FluidSystem::molarMass(gasCompIdx, fs.pvtRegionIndex());
|
|
|
|
return xwG * pv * rhow * sw / mM;
|
|
}
|
|
|
|
template <typename FluidState>
|
|
Scalar co2InWaterFromOil(const FluidState& fs, const double pv) const
|
|
{
|
|
const double rhoo = getValue(fs.density(oilPhaseIdx));
|
|
const double so = getValue(fs.saturation(oilPhaseIdx));
|
|
const double xoG = FluidSystem::convertRsToXoG(getValue(fs.Rs()), fs.pvtRegionIndex());
|
|
|
|
const Scalar mM = FluidSystem::molarMass(gasCompIdx, fs.pvtRegionIndex());
|
|
|
|
return xoG * pv * rhoo * so / mM;
|
|
}
|
|
|
|
template <typename IntensiveQuantities>
|
|
void updateMicrobialMass(const unsigned globalDofIdx,
|
|
const IntensiveQuantities& intQuants,
|
|
const double surfVolWat)
|
|
{
|
|
const Scalar mass = surfVolWat * intQuants.microbialConcentration().value();
|
|
|
|
this->fipC_.assignMicrobialMass(globalDofIdx, mass);
|
|
}
|
|
|
|
template <typename IntensiveQuantities>
|
|
void updateOxygenMass(const unsigned globalDofIdx,
|
|
const IntensiveQuantities& intQuants,
|
|
const double surfVolWat)
|
|
{
|
|
const Scalar mass = surfVolWat * intQuants.oxygenConcentration().value();
|
|
|
|
this->fipC_.assignOxygenMass(globalDofIdx, mass);
|
|
}
|
|
|
|
template <typename IntensiveQuantities>
|
|
void updateUreaMass(const unsigned globalDofIdx,
|
|
const IntensiveQuantities& intQuants,
|
|
const double surfVolWat)
|
|
{
|
|
const Scalar mass = surfVolWat * intQuants.ureaConcentration().value();
|
|
|
|
this->fipC_.assignUreaMass(globalDofIdx, mass);
|
|
}
|
|
|
|
template <typename IntensiveQuantities>
|
|
void updateBiofilmMass(const unsigned globalDofIdx,
|
|
const IntensiveQuantities& intQuants,
|
|
const double totVolume)
|
|
{
|
|
const Scalar mass = totVolume * intQuants.biofilmMass().value();
|
|
|
|
this->fipC_.assignBiofilmMass(globalDofIdx, mass);
|
|
}
|
|
|
|
template <typename IntensiveQuantities>
|
|
void updateCalciteMass(const unsigned globalDofIdx,
|
|
const IntensiveQuantities& intQuants,
|
|
const double totVolume)
|
|
{
|
|
const Scalar mass = totVolume * intQuants.calciteMass().value();
|
|
|
|
this->fipC_.assignCalciteMass(globalDofIdx, mass);
|
|
}
|
|
|
|
//! \brief Setup extractors for element-level data.
|
|
void setupElementExtractors_()
|
|
{
|
|
using Entry = typename Extractor::Entry;
|
|
using Context = typename Extractor::Context;
|
|
using ScalarEntry = typename Extractor::ScalarEntry;
|
|
using PhaseEntry = typename Extractor::PhaseEntry;
|
|
|
|
const bool hasResidual = simulator_.model().linearizer().residual().size() > 0;
|
|
const auto& hysteresisConfig = simulator_.problem().materialLawManager()->hysteresisConfig();
|
|
|
|
auto extractors = std::array{
|
|
Entry{PhaseEntry{&this->saturation_,
|
|
[](const unsigned phase, const Context& ectx)
|
|
{ return getValue(ectx.fs.saturation(phase)); }
|
|
}
|
|
},
|
|
Entry{PhaseEntry{&this->invB_,
|
|
[](const unsigned phase, const Context& ectx)
|
|
{ return getValue(ectx.fs.invB(phase)); }
|
|
}
|
|
},
|
|
Entry{PhaseEntry{&this->density_,
|
|
[](const unsigned phase, const Context& ectx)
|
|
{ return getValue(ectx.fs.density(phase)); }
|
|
}
|
|
},
|
|
Entry{PhaseEntry{&this->relativePermeability_,
|
|
[](const unsigned phase, const Context& ectx)
|
|
{ return getValue(ectx.intQuants.relativePermeability(phase)); }
|
|
}
|
|
},
|
|
Entry{PhaseEntry{&this->viscosity_,
|
|
[this](const unsigned phaseIdx, const Context& ectx)
|
|
{
|
|
if (this->extboC_.allocated() && phaseIdx == oilPhaseIdx) {
|
|
return getValue(ectx.intQuants.oilViscosity());
|
|
}
|
|
else if (this->extboC_.allocated() && phaseIdx == gasPhaseIdx) {
|
|
return getValue(ectx.intQuants.gasViscosity());
|
|
}
|
|
else {
|
|
return getValue(ectx.fs.viscosity(phaseIdx));
|
|
}
|
|
}
|
|
}
|
|
},
|
|
Entry{PhaseEntry{&this->residual_,
|
|
[&modelResid = this->simulator_.model().linearizer().residual()]
|
|
(const unsigned phaseIdx, const Context& ectx)
|
|
{
|
|
const unsigned sIdx = FluidSystem::solventComponentIndex(phaseIdx);
|
|
const unsigned activeCompIdx = Indices::canonicalToActiveComponentIndex(sIdx);
|
|
return modelResid[ectx.globalDofIdx][activeCompIdx];
|
|
}
|
|
},
|
|
hasResidual
|
|
},
|
|
Entry{ScalarEntry{&this->rockCompPorvMultiplier_,
|
|
[&problem = this->simulator_.problem()](const Context& ectx)
|
|
{
|
|
return problem.template
|
|
rockCompPoroMultiplier<Scalar>(ectx.intQuants,
|
|
ectx.globalDofIdx);
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->rockCompTransMultiplier_,
|
|
[&problem = this->simulator_.problem()](const Context& ectx)
|
|
{
|
|
return problem.
|
|
template rockCompTransMultiplier<Scalar>(ectx.intQuants,
|
|
ectx.globalDofIdx);
|
|
}}
|
|
},
|
|
Entry{ScalarEntry{&this->minimumOilPressure_,
|
|
[&problem = this->simulator_.problem()](const Context& ectx)
|
|
{
|
|
return std::min(getValue(ectx.fs.pressure(oilPhaseIdx)),
|
|
problem.minOilPressure(ectx.globalDofIdx));
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->bubblePointPressure_,
|
|
[&failedCells = this->failedCellsPb_,
|
|
&vanguard = this->simulator_.vanguard()](const Context& ectx)
|
|
{
|
|
try {
|
|
return getValue(
|
|
FluidSystem::bubblePointPressure(ectx.fs,
|
|
ectx.intQuants.pvtRegionIndex())
|
|
);
|
|
} catch (const NumericalProblem&) {
|
|
const auto cartesianIdx = vanguard.cartesianIndex(ectx.globalDofIdx);
|
|
failedCells.push_back(cartesianIdx);
|
|
return Scalar{0};
|
|
}
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->dewPointPressure_,
|
|
[&failedCells = this->failedCellsPd_,
|
|
&vanguard = this->simulator_.vanguard()](const Context& ectx)
|
|
{
|
|
try {
|
|
return getValue(
|
|
FluidSystem::dewPointPressure(ectx.fs,
|
|
ectx.intQuants.pvtRegionIndex())
|
|
);
|
|
} catch (const NumericalProblem&) {
|
|
const auto cartesianIdx = vanguard.cartesianIndex(ectx.globalDofIdx);
|
|
failedCells.push_back(cartesianIdx);
|
|
return Scalar{0};
|
|
}
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->overburdenPressure_,
|
|
[&problem = simulator_.problem()](const Context& ectx)
|
|
{ return problem.overburdenPressure(ectx.globalDofIdx); }
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->temperature_,
|
|
[](const Context& ectx)
|
|
{ return getValue(ectx.fs.temperature(oilPhaseIdx)); }
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->sSol_,
|
|
[](const Context& ectx)
|
|
{ return getValue(ectx.intQuants.solventSaturation()); }
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->rswSol_,
|
|
[](const Context& ectx)
|
|
{ return getValue(ectx.intQuants.rsSolw()); }
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->cPolymer_,
|
|
[](const Context& ectx)
|
|
{ return getValue(ectx.intQuants.polymerConcentration()); }
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->cFoam_,
|
|
[](const Context& ectx)
|
|
{ return getValue(ectx.intQuants.foamConcentration()); }
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->cSalt_,
|
|
[](const Context& ectx)
|
|
{ return getValue(ectx.fs.saltConcentration()); }
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->pSalt_,
|
|
[](const Context& ectx)
|
|
{ return getValue(ectx.fs.saltSaturation()); }
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->permFact_,
|
|
[](const Context& ectx)
|
|
{ return getValue(ectx.intQuants.permFactor()); }
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->rPorV_,
|
|
[&model = this->simulator_.model()](const Context& ectx)
|
|
{
|
|
const auto totVolume = model.dofTotalVolume(ectx.globalDofIdx);
|
|
return totVolume * getValue(ectx.intQuants.porosity());
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->rs_,
|
|
[](const Context& ectx)
|
|
{ return getValue(ectx.fs.Rs()); }
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->rv_,
|
|
[](const Context& ectx)
|
|
{ return getValue(ectx.fs.Rv()); }
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->rsw_,
|
|
[](const Context& ectx)
|
|
{ return getValue(ectx.fs.Rsw()); }
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->rvw_,
|
|
[](const Context& ectx)
|
|
{ return getValue(ectx.fs.Rvw()); }
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->ppcw_,
|
|
[&matLawManager = *this->simulator_.problem().materialLawManager()]
|
|
(const Context& ectx)
|
|
{
|
|
return matLawManager.
|
|
oilWaterScaledEpsInfoDrainage(ectx.globalDofIdx).maxPcow;
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->drsdtcon_,
|
|
[&problem = this->simulator_.problem()](const Context& ectx)
|
|
{
|
|
return problem.drsdtcon(ectx.globalDofIdx,
|
|
ectx.episodeIndex);
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->pcgw_,
|
|
[](const Context& ectx)
|
|
{
|
|
return getValue(ectx.fs.pressure(gasPhaseIdx)) -
|
|
getValue(ectx.fs.pressure(waterPhaseIdx));
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->pcow_,
|
|
[](const Context& ectx)
|
|
{
|
|
return getValue(ectx.fs.pressure(oilPhaseIdx)) -
|
|
getValue(ectx.fs.pressure(waterPhaseIdx));
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->pcog_,
|
|
[](const Context& ectx)
|
|
{
|
|
return getValue(ectx.fs.pressure(gasPhaseIdx)) -
|
|
getValue(ectx.fs.pressure(oilPhaseIdx));
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->fluidPressure_,
|
|
[](const Context& ectx)
|
|
{
|
|
if (FluidSystem::phaseIsActive(oilPhaseIdx)) {
|
|
// Output oil pressure as default
|
|
return getValue(ectx.fs.pressure(oilPhaseIdx));
|
|
}
|
|
else if (FluidSystem::phaseIsActive(gasPhaseIdx)) {
|
|
// Output gas if oil is not present
|
|
return getValue(ectx.fs.pressure(gasPhaseIdx));
|
|
}
|
|
else {
|
|
// Output water if neither oil nor gas is present
|
|
return getValue(ectx.fs.pressure(waterPhaseIdx));
|
|
}
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->gasDissolutionFactor_,
|
|
[&problem = this->simulator_.problem()](const Context& ectx)
|
|
{
|
|
const Scalar SoMax = problem.maxOilSaturation(ectx.globalDofIdx);
|
|
return FluidSystem::template
|
|
saturatedDissolutionFactor<FluidState, Scalar>(ectx.fs,
|
|
oilPhaseIdx,
|
|
ectx.pvtRegionIdx,
|
|
SoMax);
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->oilVaporizationFactor_,
|
|
[&problem = this->simulator_.problem()](const Context& ectx)
|
|
{
|
|
const Scalar SoMax = problem.maxOilSaturation(ectx.globalDofIdx);
|
|
return FluidSystem::template
|
|
saturatedDissolutionFactor<FluidState, Scalar>(ectx.fs,
|
|
gasPhaseIdx,
|
|
ectx.pvtRegionIdx,
|
|
SoMax);
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->gasDissolutionFactorInWater_,
|
|
[&problem = this->simulator_.problem()](const Context& ectx)
|
|
{
|
|
const Scalar SwMax = problem.maxWaterSaturation(ectx.globalDofIdx);
|
|
return FluidSystem::template
|
|
saturatedDissolutionFactor<FluidState, Scalar>(ectx.fs,
|
|
waterPhaseIdx,
|
|
ectx.pvtRegionIdx,
|
|
SwMax);
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->waterVaporizationFactor_,
|
|
[](const Context& ectx)
|
|
{
|
|
return FluidSystem::template
|
|
saturatedVaporizationFactor<FluidState, Scalar>(ectx.fs,
|
|
gasPhaseIdx,
|
|
ectx.pvtRegionIdx);
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->gasFormationVolumeFactor_,
|
|
[](const Context& ectx)
|
|
{
|
|
return 1.0 / FluidSystem::template
|
|
inverseFormationVolumeFactor<FluidState, Scalar>(ectx.fs,
|
|
gasPhaseIdx,
|
|
ectx.pvtRegionIdx);
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->saturatedOilFormationVolumeFactor_,
|
|
[](const Context& ectx)
|
|
{
|
|
return 1.0 / FluidSystem::template
|
|
saturatedInverseFormationVolumeFactor<FluidState, Scalar>(ectx.fs,
|
|
oilPhaseIdx,
|
|
ectx.pvtRegionIdx);
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->oilSaturationPressure_,
|
|
[](const Context& ectx)
|
|
{
|
|
return FluidSystem::template
|
|
saturationPressure<FluidState, Scalar>(ectx.fs,
|
|
oilPhaseIdx,
|
|
ectx.pvtRegionIdx);
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{&this->soMax_,
|
|
[&problem = this->simulator_.problem()](const Context& ectx)
|
|
{
|
|
return std::max(getValue(ectx.fs.saturation(oilPhaseIdx)),
|
|
problem.maxOilSaturation(ectx.globalDofIdx));
|
|
}
|
|
},
|
|
!hysteresisConfig.enableHysteresis()
|
|
},
|
|
Entry{ScalarEntry{&this->swMax_,
|
|
[&problem = this->simulator_.problem()](const Context& ectx)
|
|
{
|
|
return std::max(getValue(ectx.fs.saturation(waterPhaseIdx)),
|
|
problem.maxWaterSaturation(ectx.globalDofIdx));
|
|
}
|
|
},
|
|
!hysteresisConfig.enableHysteresis()
|
|
},
|
|
Entry{ScalarEntry{&this->soMax_,
|
|
[](const Context& ectx)
|
|
{ return ectx.hParams.somax; }
|
|
},
|
|
hysteresisConfig.enableHysteresis() &&
|
|
hysteresisConfig.enableNonWettingHysteresis() &&
|
|
FluidSystem::phaseIsActive(oilPhaseIdx) &&
|
|
FluidSystem::phaseIsActive(waterPhaseIdx)
|
|
},
|
|
Entry{ScalarEntry{&this->swMax_,
|
|
[](const Context& ectx)
|
|
{ return ectx.hParams.swmax; }
|
|
},
|
|
hysteresisConfig.enableHysteresis() &&
|
|
hysteresisConfig.enableWettingHysteresis() &&
|
|
FluidSystem::phaseIsActive(oilPhaseIdx) &&
|
|
FluidSystem::phaseIsActive(waterPhaseIdx)
|
|
},
|
|
Entry{ScalarEntry{&this->swmin_,
|
|
[](const Context& ectx)
|
|
{ return ectx.hParams.swmin; }
|
|
},
|
|
hysteresisConfig.enableHysteresis() &&
|
|
hysteresisConfig.enablePCHysteresis() &&
|
|
FluidSystem::phaseIsActive(oilPhaseIdx) &&
|
|
FluidSystem::phaseIsActive(waterPhaseIdx)
|
|
},
|
|
Entry{ScalarEntry{&this->sgmax_,
|
|
[](const Context& ectx)
|
|
{ return ectx.hParams.sgmax; }
|
|
},
|
|
hysteresisConfig.enableHysteresis() &&
|
|
hysteresisConfig.enableNonWettingHysteresis() &&
|
|
FluidSystem::phaseIsActive(oilPhaseIdx) &&
|
|
FluidSystem::phaseIsActive(gasPhaseIdx)
|
|
},
|
|
Entry{ScalarEntry{&this->shmax_,
|
|
[](const Context& ectx)
|
|
{ return ectx.hParams.shmax; }
|
|
},
|
|
hysteresisConfig.enableHysteresis() &&
|
|
hysteresisConfig.enableWettingHysteresis() &&
|
|
FluidSystem::phaseIsActive(oilPhaseIdx) &&
|
|
FluidSystem::phaseIsActive(gasPhaseIdx)
|
|
},
|
|
Entry{ScalarEntry{&this->somin_,
|
|
[](const Context& ectx)
|
|
{ return ectx.hParams.somin; }
|
|
},
|
|
hysteresisConfig.enableHysteresis() &&
|
|
hysteresisConfig.enablePCHysteresis() &&
|
|
FluidSystem::phaseIsActive(oilPhaseIdx) &&
|
|
FluidSystem::phaseIsActive(gasPhaseIdx)
|
|
},
|
|
Entry{[&model = this->simulator_.model(), this](const Context& ectx)
|
|
{
|
|
// Note: We intentionally exclude effects of rock
|
|
// compressibility by using referencePorosity() here.
|
|
const auto porv = ectx.intQuants.referencePorosity()
|
|
* model.dofTotalVolume(ectx.globalDofIdx);
|
|
|
|
this->aggregateAverageDensityContributions_(ectx.fs, ectx.globalDofIdx,
|
|
static_cast<double>(porv));
|
|
}, this->regionAvgDensity_.has_value()
|
|
},
|
|
Entry{[&extboC = this->extboC_](const Context& ectx)
|
|
{
|
|
extboC.assignVolumes(ectx.globalDofIdx,
|
|
ectx.intQuants.xVolume().value(),
|
|
ectx.intQuants.yVolume().value());
|
|
extboC.assignZFraction(ectx.globalDofIdx,
|
|
ectx.intQuants.zFraction().value());
|
|
|
|
const Scalar stdVolOil = getValue(ectx.fs.saturation(oilPhaseIdx)) *
|
|
getValue(ectx.fs.invB(oilPhaseIdx)) +
|
|
getValue(ectx.fs.saturation(gasPhaseIdx)) *
|
|
getValue(ectx.fs.invB(gasPhaseIdx)) *
|
|
getValue(ectx.fs.Rv());
|
|
const Scalar stdVolGas = getValue(ectx.fs.saturation(gasPhaseIdx)) *
|
|
getValue(ectx.fs.invB(gasPhaseIdx)) *
|
|
(1.0 - ectx.intQuants.yVolume().value()) +
|
|
getValue(ectx.fs.saturation(oilPhaseIdx)) *
|
|
getValue(ectx.fs.invB(oilPhaseIdx)) *
|
|
getValue(ectx.fs.Rs()) *
|
|
(1.0 - ectx.intQuants.xVolume().value());
|
|
const Scalar stdVolCo2 = getValue(ectx.fs.saturation(gasPhaseIdx)) *
|
|
getValue(ectx.fs.invB(gasPhaseIdx)) *
|
|
ectx.intQuants.yVolume().value() +
|
|
getValue(ectx.fs.saturation(oilPhaseIdx)) *
|
|
getValue(ectx.fs.invB(oilPhaseIdx)) *
|
|
getValue(ectx.fs.Rs()) *
|
|
ectx.intQuants.xVolume().value();
|
|
const Scalar rhoO = FluidSystem::referenceDensity(gasPhaseIdx, ectx.pvtRegionIdx);
|
|
const Scalar rhoG = FluidSystem::referenceDensity(gasPhaseIdx, ectx.pvtRegionIdx);
|
|
const Scalar rhoCO2 = ectx.intQuants.zRefDensity();
|
|
const Scalar stdMassTotal = 1.0e-10 + stdVolOil * rhoO + stdVolGas * rhoG + stdVolCo2 * rhoCO2;
|
|
extboC.assignMassFractions(ectx.globalDofIdx,
|
|
stdVolGas * rhoG / stdMassTotal,
|
|
stdVolOil * rhoO / stdMassTotal,
|
|
stdVolCo2 * rhoCO2 / stdMassTotal);
|
|
}, this->extboC_.allocated()
|
|
},
|
|
Entry{[&micpC = this->micpC_](const Context& ectx)
|
|
{
|
|
micpC.assign(ectx.globalDofIdx,
|
|
ectx.intQuants.microbialConcentration().value(),
|
|
ectx.intQuants.oxygenConcentration().value(),
|
|
ectx.intQuants.ureaConcentration().value(),
|
|
ectx.intQuants.biofilmConcentration().value(),
|
|
ectx.intQuants.calciteConcentration().value());
|
|
}, this->micpC_.allocated()
|
|
},
|
|
Entry{[&rftC = this->rftC_,
|
|
&vanguard = this->simulator_.vanguard()](const Context& ectx)
|
|
{
|
|
const auto cartesianIdx = vanguard.cartesianIndex(ectx.globalDofIdx);
|
|
rftC.assign(cartesianIdx,
|
|
[&fs = ectx.fs]() { return getValue(fs.pressure(oilPhaseIdx)); },
|
|
[&fs = ectx.fs]() { return getValue(fs.saturation(waterPhaseIdx)); },
|
|
[&fs = ectx.fs]() { return getValue(fs.saturation(gasPhaseIdx)); });
|
|
}
|
|
},
|
|
Entry{[&tC = this->tracerC_,
|
|
&tM = this->simulator_.problem().tracerModel()](const Context& ectx)
|
|
{
|
|
tC.assignFreeConcentrations(ectx.globalDofIdx,
|
|
[gIdx = ectx.globalDofIdx, &tM](const unsigned tracerIdx)
|
|
{ return tM.freeTracerConcentration(tracerIdx, gIdx); });
|
|
tC.assignSolConcentrations(ectx.globalDofIdx,
|
|
[gIdx = ectx.globalDofIdx, &tM](const unsigned tracerIdx)
|
|
{ return tM.solTracerConcentration(tracerIdx, gIdx); });
|
|
}
|
|
},
|
|
Entry{[&flowsInf = this->simulator_.problem().model().linearizer().getFlowsInfo(),
|
|
&flowsC = this->flowsC_](const Context& ectx)
|
|
{
|
|
constexpr auto gas_idx = Indices::gasEnabled ?
|
|
conti0EqIdx + Indices::canonicalToActiveComponentIndex(gasCompIdx) : -1;
|
|
constexpr auto oil_idx = Indices::oilEnabled ?
|
|
conti0EqIdx + Indices::canonicalToActiveComponentIndex(oilCompIdx) : -1;
|
|
constexpr auto water_idx = Indices::waterEnabled ?
|
|
conti0EqIdx + Indices::canonicalToActiveComponentIndex(waterCompIdx) : -1;
|
|
const auto& flowsInfos = flowsInf[ectx.globalDofIdx];
|
|
for (const auto& flowsInfo : flowsInfos) {
|
|
flowsC.assignFlows(ectx.globalDofIdx,
|
|
flowsInfo.faceId,
|
|
flowsInfo.nncId,
|
|
value_or_zero<gas_idx>(flowsInfo.flow),
|
|
value_or_zero<oil_idx>(flowsInfo.flow),
|
|
value_or_zero<water_idx>(flowsInfo.flow));
|
|
}
|
|
}, !this->simulator_.problem().model().linearizer().getFlowsInfo().empty()
|
|
},
|
|
Entry{[&floresInf = this->simulator_.problem().model().linearizer().getFloresInfo(),
|
|
&flowsC = this->flowsC_](const Context& ectx)
|
|
{
|
|
constexpr auto gas_idx = Indices::gasEnabled ?
|
|
conti0EqIdx + Indices::canonicalToActiveComponentIndex(gasCompIdx) : -1;
|
|
constexpr auto oil_idx = Indices::oilEnabled ?
|
|
conti0EqIdx + Indices::canonicalToActiveComponentIndex(oilCompIdx) : -1;
|
|
constexpr auto water_idx = Indices::waterEnabled ?
|
|
conti0EqIdx + Indices::canonicalToActiveComponentIndex(waterCompIdx) : -1;
|
|
const auto& floresInfos = floresInf[ectx.globalDofIdx];
|
|
for (const auto& floresInfo : floresInfos) {
|
|
flowsC.assignFlores(ectx.globalDofIdx,
|
|
floresInfo.faceId,
|
|
floresInfo.nncId,
|
|
value_or_zero<gas_idx>(floresInfo.flow),
|
|
value_or_zero<oil_idx>(floresInfo.flow),
|
|
value_or_zero<water_idx>(floresInfo.flow));
|
|
}
|
|
}, !this->simulator_.problem().model().linearizer().getFloresInfo().empty()
|
|
},
|
|
// hack to make the intial output of rs and rv Ecl compatible.
|
|
// For cells with swat == 1 Ecl outputs; rs = rsSat and rv=rvSat, in all but the initial step
|
|
// where it outputs rs and rv values calculated by the initialization. To be compatible we overwrite
|
|
// rs and rv with the values computed in the initially.
|
|
// Volume factors, densities and viscosities need to be recalculated with the updated rs and rv values.
|
|
Entry{ScalarEntry{&this->rv_,
|
|
[&problem = this->simulator_.problem()](const Context& ectx)
|
|
{ return problem.initialFluidState(ectx.globalDofIdx).Rv(); }
|
|
},
|
|
simulator_.episodeIndex() < 0 &&
|
|
FluidSystem::phaseIsActive(oilPhaseIdx) &&
|
|
FluidSystem::phaseIsActive(gasPhaseIdx)
|
|
},
|
|
Entry{ScalarEntry{&this->rs_,
|
|
[&problem = this->simulator_.problem()](const Context& ectx)
|
|
{ return problem.initialFluidState(ectx.globalDofIdx).Rs(); }
|
|
},
|
|
simulator_.episodeIndex() < 0 &&
|
|
FluidSystem::phaseIsActive(oilPhaseIdx) &&
|
|
FluidSystem::phaseIsActive(gasPhaseIdx)
|
|
},
|
|
Entry{ScalarEntry{&this->rsw_,
|
|
[&problem = this->simulator_.problem()](const Context& ectx)
|
|
{ return problem.initialFluidState(ectx.globalDofIdx).Rsw(); }
|
|
},
|
|
simulator_.episodeIndex() < 0 &&
|
|
FluidSystem::phaseIsActive(oilPhaseIdx) &&
|
|
FluidSystem::phaseIsActive(gasPhaseIdx)
|
|
},
|
|
Entry{ScalarEntry{&this->rvw_,
|
|
[&problem = this->simulator_.problem()](const Context& ectx)
|
|
{ return problem.initialFluidState(ectx.globalDofIdx).Rvw(); }
|
|
},
|
|
simulator_.episodeIndex() < 0 &&
|
|
FluidSystem::phaseIsActive(oilPhaseIdx) &&
|
|
FluidSystem::phaseIsActive(gasPhaseIdx)
|
|
},
|
|
// re-compute the volume factors, viscosities and densities if asked for
|
|
Entry{PhaseEntry{&this->density_,
|
|
[&problem = this->simulator_.problem()](const unsigned phase,
|
|
const Context& ectx)
|
|
{
|
|
const auto& fsInitial = problem.initialFluidState(ectx.globalDofIdx);
|
|
return FluidSystem::density(fsInitial,
|
|
phase,
|
|
ectx.intQuants.pvtRegionIndex());
|
|
}
|
|
},
|
|
simulator_.episodeIndex() < 0 &&
|
|
FluidSystem::phaseIsActive(oilPhaseIdx) &&
|
|
FluidSystem::phaseIsActive(gasPhaseIdx)
|
|
},
|
|
Entry{PhaseEntry{&this->invB_,
|
|
[&problem = this->simulator_.problem()](const unsigned phase,
|
|
const Context& ectx)
|
|
{
|
|
const auto& fsInitial = problem.initialFluidState(ectx.globalDofIdx);
|
|
return FluidSystem::inverseFormationVolumeFactor(fsInitial,
|
|
phase,
|
|
ectx.intQuants.pvtRegionIndex());
|
|
}
|
|
},
|
|
simulator_.episodeIndex() < 0 &&
|
|
FluidSystem::phaseIsActive(oilPhaseIdx) &&
|
|
FluidSystem::phaseIsActive(gasPhaseIdx)
|
|
},
|
|
Entry{PhaseEntry{&this->viscosity_,
|
|
[&problem = this->simulator_.problem()](const unsigned phase,
|
|
const Context& ectx)
|
|
{
|
|
const auto& fsInitial = problem.initialFluidState(ectx.globalDofIdx);
|
|
return FluidSystem::viscosity(fsInitial,
|
|
phase,
|
|
ectx.intQuants.pvtRegionIndex());
|
|
}
|
|
},
|
|
simulator_.episodeIndex() < 0 &&
|
|
FluidSystem::phaseIsActive(oilPhaseIdx) &&
|
|
FluidSystem::phaseIsActive(gasPhaseIdx)
|
|
},
|
|
};
|
|
|
|
// Setup active extractors
|
|
this->extractors_ = Extractor::removeInactive(extractors);
|
|
|
|
if constexpr (getPropValue<TypeTag, Properties::EnableMech>()) {
|
|
if (this->mech_.allocated()) {
|
|
this->extractors_.emplace(
|
|
[&mech = this->mech_,
|
|
&model = simulator_.problem().geoMechModel()](const Context& ectx)
|
|
{
|
|
mech.assignDelStress(ectx.globalDofIdx,
|
|
model.delstress(ectx.globalDofIdx));
|
|
|
|
mech.assignDisplacement(ectx.globalDofIdx,
|
|
model.disp(ectx.globalDofIdx, /*include_fracture*/true));
|
|
|
|
// is the tresagii stress which make rock fracture
|
|
mech.assignFracStress(ectx.globalDofIdx,
|
|
model.fractureStress(ectx.globalDofIdx));
|
|
|
|
mech.assignLinStress(ectx.globalDofIdx,
|
|
model.linstress(ectx.globalDofIdx));
|
|
|
|
mech.assignPotentialForces(ectx.globalDofIdx,
|
|
model.mechPotentialForce(ectx.globalDofIdx),
|
|
model.mechPotentialPressForce(ectx.globalDofIdx),
|
|
model.mechPotentialTempForce(ectx.globalDofIdx));
|
|
|
|
mech.assignStrain(ectx.globalDofIdx,
|
|
model.strain(ectx.globalDofIdx, /*include_fracture*/true));
|
|
|
|
// Total stress is not stored but calculated result is Voigt notation
|
|
mech.assignStress(ectx.globalDofIdx,
|
|
model.stress(ectx.globalDofIdx, /*include_fracture*/true));
|
|
}, true
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
//! \brief Setup extractor execution map for block data.
|
|
void setupBlockExtractors_(const bool isSubStep,
|
|
const int reportStepNum)
|
|
{
|
|
using Entry = typename BlockExtractor::Entry;
|
|
using Context = typename BlockExtractor::Context;
|
|
using PhaseEntry = typename BlockExtractor::PhaseEntry;
|
|
using ScalarEntry = typename BlockExtractor::ScalarEntry;
|
|
|
|
using namespace std::string_view_literals;
|
|
|
|
const auto pressure_handler =
|
|
Entry{ScalarEntry{std::vector{"BPR"sv, "BPRESSUR"sv},
|
|
[](const Context& ectx)
|
|
{
|
|
if (FluidSystem::phaseIsActive(oilPhaseIdx)) {
|
|
return getValue(ectx.fs.pressure(oilPhaseIdx));
|
|
}
|
|
else if (FluidSystem::phaseIsActive(gasPhaseIdx)) {
|
|
return getValue(ectx.fs.pressure(gasPhaseIdx));
|
|
}
|
|
else { //if (FluidSystem::phaseIsActive(waterPhaseIdx))
|
|
return getValue(ectx.fs.pressure(waterPhaseIdx));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
const auto handlers = std::array{
|
|
pressure_handler,
|
|
Entry{PhaseEntry{std::array{
|
|
std::array{"BWSAT"sv, "BOSAT"sv, "BGSAT"sv},
|
|
std::array{"BSWAT"sv, "BSOIL"sv, "BSGAS"sv}
|
|
},
|
|
[](const unsigned phaseIdx, const Context& ectx)
|
|
{
|
|
return getValue(ectx.fs.saturation(phaseIdx));
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BNSAT",
|
|
[](const Context& ectx)
|
|
{
|
|
return ectx.intQuants.solventSaturation().value();
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{std::vector{"BTCNFHEA"sv, "BTEMP"sv},
|
|
[](const Context& ectx)
|
|
{
|
|
if (FluidSystem::phaseIsActive(oilPhaseIdx)) {
|
|
return getValue(ectx.fs.temperature(oilPhaseIdx));
|
|
}
|
|
else if (FluidSystem::phaseIsActive(gasPhaseIdx)) {
|
|
return getValue(ectx.fs.temperature(gasPhaseIdx));
|
|
}
|
|
else { //if (FluidSystem::phaseIsActive(waterPhaseIdx))
|
|
return getValue(ectx.fs.temperature(waterPhaseIdx));
|
|
}
|
|
}
|
|
}
|
|
},
|
|
Entry{PhaseEntry{std::array{
|
|
std::array{"BWKR"sv, "BOKR"sv, "BGKR"sv},
|
|
std::array{"BKRW"sv, "BKRO"sv, "BKRG"sv}
|
|
},
|
|
[](const unsigned phaseIdx, const Context& ectx)
|
|
{
|
|
return getValue(ectx.intQuants.relativePermeability(phaseIdx));
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BKROG",
|
|
[&problem = this->simulator_.problem()](const Context& ectx)
|
|
{
|
|
const auto& materialParams =
|
|
problem.materialLawParams(ectx.elemCtx,
|
|
ectx.dofIdx,
|
|
/* timeIdx = */ 0);
|
|
return getValue(MaterialLaw::template
|
|
relpermOilInOilGasSystem<Evaluation>(materialParams,
|
|
ectx.fs));
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BKROW",
|
|
[&problem = this->simulator_.problem()](const Context& ectx)
|
|
{
|
|
const auto& materialParams = problem.materialLawParams(ectx.elemCtx,
|
|
ectx.dofIdx,
|
|
/* timeIdx = */ 0);
|
|
return getValue(MaterialLaw::template
|
|
relpermOilInOilWaterSystem<Evaluation>(materialParams,
|
|
ectx.fs));
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BWPC",
|
|
[](const Context& ectx)
|
|
{
|
|
return getValue(ectx.fs.pressure(oilPhaseIdx)) -
|
|
getValue(ectx.fs.pressure(waterPhaseIdx));
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BGPC",
|
|
[](const Context& ectx)
|
|
{
|
|
return getValue(ectx.fs.pressure(gasPhaseIdx)) -
|
|
getValue(ectx.fs.pressure(oilPhaseIdx));
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BWPR",
|
|
[](const Context& ectx)
|
|
{
|
|
return getValue(ectx.fs.pressure(waterPhaseIdx));
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BGPR",
|
|
[](const Context& ectx)
|
|
{
|
|
return getValue(ectx.fs.pressure(gasPhaseIdx));
|
|
}
|
|
}
|
|
},
|
|
Entry{PhaseEntry{std::array{
|
|
std::array{"BVWAT"sv, "BVOIL"sv, "BVGAS"sv},
|
|
std::array{"BWVIS"sv, "BOVIS"sv, "BGVIS"sv}
|
|
},
|
|
[](const unsigned phaseIdx, const Context& ectx)
|
|
{
|
|
return getValue(ectx.fs.viscosity(phaseIdx));
|
|
}
|
|
}
|
|
},
|
|
Entry{PhaseEntry{std::array{
|
|
std::array{"BWDEN"sv, "BODEN"sv, "BGDEN"sv},
|
|
std::array{"BDENW"sv, "BDENO"sv, "BDENG"sv}
|
|
},
|
|
[](const unsigned phaseIdx, const Context& ectx)
|
|
{
|
|
return getValue(ectx.fs.density(phaseIdx));
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BFLOWI",
|
|
[&flowsC = this->flowsC_](const Context& ectx)
|
|
{
|
|
return flowsC.getFlow(ectx.globalDofIdx, Dir::XPlus, waterCompIdx);
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BFLOWJ",
|
|
[&flowsC = this->flowsC_](const Context& ectx)
|
|
{
|
|
return flowsC.getFlow(ectx.globalDofIdx, Dir::YPlus, waterCompIdx);
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BFLOWK",
|
|
[&flowsC = this->flowsC_](const Context& ectx)
|
|
{
|
|
return flowsC.getFlow(ectx.globalDofIdx, Dir::ZPlus, waterCompIdx);
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BRPV",
|
|
[&model = this->simulator_.model()](const Context& ectx)
|
|
{
|
|
return getValue(ectx.intQuants.porosity()) *
|
|
model.dofTotalVolume(ectx.globalDofIdx);
|
|
}
|
|
}
|
|
},
|
|
Entry{PhaseEntry{std::array{"BWPV"sv, "BOPV"sv, "BGPV"sv},
|
|
[&model = this->simulator_.model()](const unsigned phaseIdx,
|
|
const Context& ectx)
|
|
{
|
|
return getValue(ectx.fs.saturation(phaseIdx)) *
|
|
getValue(ectx.intQuants.porosity()) *
|
|
model.dofTotalVolume(ectx.globalDofIdx);
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BRS",
|
|
[](const Context& ectx)
|
|
{
|
|
return getValue(ectx.fs.Rs());
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BRV",
|
|
[](const Context& ectx)
|
|
{
|
|
return getValue(ectx.fs.Rv());
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BOIP",
|
|
[&model = this->simulator_.model()](const Context& ectx)
|
|
{
|
|
return (getValue(ectx.fs.invB(oilPhaseIdx)) *
|
|
getValue(ectx.fs.saturation(oilPhaseIdx)) +
|
|
getValue(ectx.fs.Rv()) *
|
|
getValue(ectx.fs.invB(gasPhaseIdx)) *
|
|
getValue(ectx.fs.saturation(gasPhaseIdx))) *
|
|
model.dofTotalVolume(ectx.globalDofIdx) *
|
|
getValue(ectx.intQuants.porosity());
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BGIP",
|
|
[&model = this->simulator_.model()](const Context& ectx)
|
|
{
|
|
Scalar result = getValue(ectx.fs.invB(gasPhaseIdx)) *
|
|
getValue(ectx.fs.saturation(gasPhaseIdx));
|
|
|
|
if (FluidSystem::phaseIsActive(oilPhaseIdx)) {
|
|
result += getValue(ectx.fs.Rs()) *
|
|
getValue(ectx.fs.invB(oilPhaseIdx)) *
|
|
getValue(ectx.fs.saturation(oilPhaseIdx));
|
|
}
|
|
else {
|
|
result += getValue(ectx.fs.Rsw()) *
|
|
getValue(ectx.fs.invB(waterPhaseIdx)) *
|
|
getValue(ectx.fs.saturation(waterPhaseIdx));
|
|
}
|
|
|
|
return result *
|
|
model.dofTotalVolume(ectx.globalDofIdx) *
|
|
getValue(ectx.intQuants.porosity());
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BWIP",
|
|
[&model = this->simulator_.model()](const Context& ectx)
|
|
{
|
|
return getValue(ectx.fs.invB(waterPhaseIdx)) *
|
|
getValue(ectx.fs.saturation(waterPhaseIdx)) *
|
|
model.dofTotalVolume(ectx.globalDofIdx) *
|
|
getValue(ectx.intQuants.porosity());
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BOIPL",
|
|
[&model = this->simulator_.model()](const Context& ectx)
|
|
{
|
|
return getValue(ectx.fs.invB(oilPhaseIdx)) *
|
|
getValue(ectx.fs.saturation(oilPhaseIdx)) *
|
|
model.dofTotalVolume(ectx.globalDofIdx) *
|
|
getValue(ectx.intQuants.porosity());
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BGIPL",
|
|
[&model = this->simulator_.model()](const Context& ectx)
|
|
{
|
|
Scalar result;
|
|
if (FluidSystem::phaseIsActive(oilPhaseIdx)) {
|
|
result = getValue(ectx.fs.Rs()) *
|
|
getValue(ectx.fs.invB(oilPhaseIdx)) *
|
|
getValue(ectx.fs.saturation(oilPhaseIdx));
|
|
}
|
|
else {
|
|
result = getValue(ectx.fs.Rsw()) *
|
|
getValue(ectx.fs.invB(waterPhaseIdx)) *
|
|
getValue(ectx.fs.saturation(waterPhaseIdx));
|
|
}
|
|
return result *
|
|
model.dofTotalVolume(ectx.globalDofIdx) *
|
|
getValue(ectx.intQuants.porosity());
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BGIPG",
|
|
[&model = this->simulator_.model()](const Context& ectx)
|
|
{
|
|
return getValue(ectx.fs.invB(gasPhaseIdx)) *
|
|
getValue(ectx.fs.saturation(gasPhaseIdx)) *
|
|
model.dofTotalVolume(ectx.globalDofIdx) *
|
|
getValue(ectx.intQuants.porosity());
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BOIPG",
|
|
[&model = this->simulator_.model()](const Context& ectx)
|
|
{
|
|
return getValue(ectx.fs.Rv()) *
|
|
getValue(ectx.fs.invB(gasPhaseIdx)) *
|
|
getValue(ectx.fs.saturation(gasPhaseIdx)) *
|
|
model.dofTotalVolume(ectx.globalDofIdx) *
|
|
getValue(ectx.intQuants.porosity());
|
|
}
|
|
}
|
|
},
|
|
Entry{PhaseEntry{std::array{"BPPW"sv, "BPPO"sv, "BPPG"sv},
|
|
[&simConfig = this->eclState_.getSimulationConfig(),
|
|
&grav = this->simulator_.problem().gravity(),
|
|
®ionAvgDensity = this->regionAvgDensity_,
|
|
&problem = this->simulator_.problem(),
|
|
®ions = this->regions_](const unsigned phaseIdx, const Context& ectx)
|
|
{
|
|
auto phase = RegionPhasePoreVolAverage::Phase{};
|
|
phase.ix = phaseIdx;
|
|
|
|
// Note different region handling here. FIPNUM is
|
|
// one-based, but we need zero-based lookup in
|
|
// DatumDepth. On the other hand, pvtRegionIndex is
|
|
// zero-based but we need one-based lookup in
|
|
// RegionPhasePoreVolAverage.
|
|
|
|
// Subtract one to convert FIPNUM to region index.
|
|
const auto datum = simConfig.datumDepths()(regions["FIPNUM"][ectx.dofIdx] - 1);
|
|
|
|
// Add one to convert region index to region ID.
|
|
const auto region = RegionPhasePoreVolAverage::Region {
|
|
ectx.elemCtx.primaryVars(ectx.dofIdx, /*timeIdx=*/0).pvtRegionIndex() + 1
|
|
};
|
|
|
|
const auto density = regionAvgDensity->value("PVTNUM", phase, region);
|
|
|
|
const auto press = getValue(ectx.fs.pressure(phase.ix));
|
|
const auto dz = problem.dofCenterDepth(ectx.globalDofIdx) - datum;
|
|
return press - density*dz*grav[GridView::dimensionworld - 1];
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BMMIP",
|
|
[&model = this->simulator_.model()](const Context& ectx)
|
|
{
|
|
return getValue(ectx.intQuants.microbialConcentration()) *
|
|
getValue(ectx.intQuants.porosity()) *
|
|
model.dofTotalVolume(ectx.globalDofIdx);
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BMOIP",
|
|
[&model = this->simulator_.model()](const Context& ectx)
|
|
{
|
|
return getValue(ectx.intQuants.oxygenConcentration()) *
|
|
getValue(ectx.intQuants.porosity()) *
|
|
model.dofTotalVolume(ectx.globalDofIdx);
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BMUIP",
|
|
[&model = this->simulator_.model()](const Context& ectx)
|
|
{
|
|
return getValue(ectx.intQuants.ureaConcentration()) *
|
|
getValue(ectx.intQuants.porosity()) *
|
|
model.dofTotalVolume(ectx.globalDofIdx) * 1;
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BMBIP",
|
|
[&model = this->simulator_.model()](const Context& ectx)
|
|
{
|
|
return model.dofTotalVolume(ectx.globalDofIdx) *
|
|
getValue(ectx.intQuants.biofilmMass());
|
|
}
|
|
}
|
|
},
|
|
Entry{ScalarEntry{"BMCIP",
|
|
[&model = this->simulator_.model()](const Context& ectx)
|
|
{
|
|
return model.dofTotalVolume(ectx.globalDofIdx) *
|
|
getValue(ectx.intQuants.calciteMass());
|
|
}
|
|
}
|
|
},
|
|
};
|
|
|
|
this->blockExtractors_ = BlockExtractor::setupExecMap(this->blockData_, handlers);
|
|
|
|
this->extraBlockData_.clear();
|
|
if (reportStepNum > 0 && !isSubStep) {
|
|
// check we need extra block pressures for RPTSCHED
|
|
const auto& rpt = this->schedule_[reportStepNum - 1].rpt_config.get();
|
|
if (rpt.contains("WELLS") && rpt.at("WELLS") > 1) {
|
|
this->setupExtraBlockData(reportStepNum,
|
|
[&c = this->collectOnIORank_](const int idx)
|
|
{ return c.isCartIdxOnThisRank(idx); });
|
|
|
|
const auto extraHandlers = std::array{
|
|
pressure_handler,
|
|
};
|
|
|
|
this->extraBlockExtractors_ = BlockExtractor::setupExecMap(this->extraBlockData_, extraHandlers);
|
|
}
|
|
}
|
|
}
|
|
|
|
const Simulator& simulator_;
|
|
const CollectDataOnIORankType& collectOnIORank_;
|
|
std::vector<typename Extractor::Entry> extractors_;
|
|
typename BlockExtractor::ExecMap blockExtractors_;
|
|
typename BlockExtractor::ExecMap extraBlockExtractors_;
|
|
};
|
|
|
|
} // namespace Opm
|
|
|
|
#endif // OPM_OUTPUT_BLACK_OIL_MODULE_HPP
|