// -*- 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_HH
#define EWOMS_ECL_PROBLEM_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
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;
};
// 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;
};
// disable thermal flux boundaries by default
template
struct EnableThermalFluxBoundaries {
static constexpr bool value = false;
};
template
struct EnableTracerModel {
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 EclProblem : 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 { enablePolymerMolarWeight = getPropValue() };
enum { enableFoam = getPropValue() };
enum { enableExtbo = getPropValue() };
enum { enableTemperature = getPropValue() };
enum { enableEnergy = getPropValue() };
enum { enableDiffusion = getPropValue() };
enum { enableThermalFluxBoundaries = getPropValue() };
enum { enableApiTracking = 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 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, EnableTracerModel,
"Transport tracers found in the deck.");
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
*/
EclProblem(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());
// 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();
if (initconfig.restartRequested())
readEclRestartSolution_();
else
readInitialCondition_();
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);
}
tracerModel_.init();
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);
}
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();
eclState.apply_geo_keywords( miniDeck );
// 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);
}
/*!
* \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(),
ecl_state,
schedule,
simulator.vanguard().actionState(),
simulator.vanguard().summaryState());
}
/*!
* \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;
}
void applyActions(int reportStep,
double sim_time,
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();
}
for (const auto& pyaction : actions.pending_python()) {
pyaction->run(ecl_state, schedule, reportStep, summaryState);
}
bool commit_wellstate = false;
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 affected_wells = schedule.applyAction(reportStep, TimeService::from_time_t(simTime), *action, actionResult, wellpi);
actionState.add_run(*action, simTime);
this->wellModel_.updateEclWells(reportStep, affected_wells);
if (!affected_wells.empty())
commit_wellstate = true;
} 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;
}
/*!
* \copydoc EclTransmissiblity::diffusivity
*/
template