changed: move NLDD BlackoilWellModel code to separate class

This commit is contained in:
Arne Morten Kvarving 2025-01-07 15:03:27 +01:00
parent e288a61c59
commit 078249503f
13 changed files with 653 additions and 365 deletions

View File

@ -167,6 +167,7 @@ list (APPEND MAIN_SOURCE_FILES
opm/simulators/wells/BlackoilWellModelGasLift.cpp
opm/simulators/wells/BlackoilWellModelGeneric.cpp
opm/simulators/wells/BlackoilWellModelGuideRates.cpp
opm/simulators/wells/BlackoilWellModelNldd.cpp
opm/simulators/wells/BlackoilWellModelRestart.cpp
opm/simulators/wells/BlackoilWellModelWBP.cpp
opm/simulators/wells/ConnFiltrateData.cpp
@ -972,6 +973,8 @@ list (APPEND PUBLIC_HEADER_FILES
opm/simulators/wells/BlackoilWellModelGasLift_impl.hpp
opm/simulators/wells/BlackoilWellModelGeneric.hpp
opm/simulators/wells/BlackoilWellModelGuideRates.hpp
opm/simulators/wells/BlackoilWellModelNldd.hpp
opm/simulators/wells/BlackoilWellModelNldd_impl.hpp
opm/simulators/wells/BlackoilWellModelRestart.hpp
opm/simulators/wells/BlackoilWellModelWBP.hpp
opm/simulators/wells/ConnectionIndexMap.hpp

View File

@ -50,6 +50,8 @@
#include <opm/simulators/utils/ComponentName.hpp>
#include <opm/simulators/wells/BlackoilWellModelNldd.hpp>
#include <fmt/format.h>
#include <algorithm>
@ -64,6 +66,7 @@
#include <ios>
#include <memory>
#include <numeric>
#include <set>
#include <sstream>
#include <string>
#include <type_traits>
@ -76,7 +79,8 @@ template<class TypeTag> class BlackoilModel;
/// A NLDD implementation for three-phase black oil.
template <class TypeTag>
class BlackoilModelNldd {
class BlackoilModelNldd
{
public:
using ElementContext = GetPropType<TypeTag, Properties::ElementContext>;
using FluidSystem = GetPropType<TypeTag, Properties::FluidSystem>;
@ -98,11 +102,16 @@ public:
//! \param param param Model parameters
//! \param compNames Names of the solution components
explicit BlackoilModelNldd(BlackoilModel<TypeTag>& model)
: model_(model), rank_(model_.simulator().vanguard().grid().comm().rank())
: model_(model)
, wellModel_(model.wellModel())
, rank_(model_.simulator().vanguard().grid().comm().rank())
{
// Create partitions.
const auto& [partition_vector, num_domains] = this->partitionCells();
// Set nldd handler in main well model
model.wellModel().setNlddAdapter(&wellModel_);
// Scan through partitioning to get correct size for each.
std::vector<int> sizes(num_domains, 0);
for (const auto& p : partition_vector) {
@ -179,7 +188,7 @@ public:
void prepareStep()
{
// Setup domain->well mapping.
model_.wellModel().setupDomains(domains_);
wellModel_.setupDomains(domains_);
}
//! \brief Do one non-linear NLDD iteration.
@ -352,7 +361,6 @@ public:
}
private:
//! \brief Solve the equation system for a single domain.
std::pair<SimulatorReportSingle, ConvergenceReport>
solveDomain(const Domain& domain,
@ -380,9 +388,9 @@ private:
modelSimulator.model().newtonMethod().setIterationIndex(iter);
// TODO: we should have a beginIterationLocal function()
// only handling the well model for now
modelSimulator.problem().wellModel().assembleDomain(modelSimulator.model().newtonMethod().numIterations(),
modelSimulator.timeStepSize(),
domain);
wellModel_.assemble(modelSimulator.model().newtonMethod().numIterations(),
modelSimulator.timeStepSize(),
domain);
// Assemble reservoir locally.
report += this->assembleReservoirDomain(domain);
report.assemble_time += detailTimer.stop();
@ -445,9 +453,9 @@ private:
// TODO: we should have a beginIterationLocal function()
// only handling the well model for now
// Assemble reservoir locally.
modelSimulator.problem().wellModel().assembleDomain(modelSimulator.model().newtonMethod().numIterations(),
modelSimulator.timeStepSize(),
domain);
wellModel_.assemble(modelSimulator.model().newtonMethod().numIterations(),
modelSimulator.timeStepSize(),
domain);
report += this->assembleReservoirDomain(domain);
report.assemble_time += detailTimer.stop();
@ -736,7 +744,7 @@ private:
logger,
B_avg,
residual_norms);
report += model_.wellModel().getDomainWellConvergence(domain, B_avg, logger);
report += wellModel_.getWellConvergence(domain, B_avg, logger);
return report;
}
@ -815,7 +823,7 @@ private:
const SimulatorTimerInterface& timer,
const Domain& domain)
{
auto initial_local_well_primary_vars = model_.wellModel().getPrimaryVarsDomain(domain.index);
auto initial_local_well_primary_vars = wellModel_.getPrimaryVarsDomain(domain.index);
auto initial_local_solution = Details::extractVector(solution, domain.cells);
auto res = solveDomain(domain, timer, logger, iteration, false);
local_report = res.first;
@ -825,7 +833,7 @@ private:
Details::setGlobal(initial_local_solution, domain.cells, solution);
model_.simulator().model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0, domain);
} else {
model_.wellModel().setPrimaryVarsDomain(domain.index, initial_local_well_primary_vars);
wellModel_.setPrimaryVarsDomain(domain.index, initial_local_well_primary_vars);
Details::setGlobal(initial_local_solution, domain.cells, solution);
model_.simulator().model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0, domain);
}
@ -840,7 +848,7 @@ private:
const SimulatorTimerInterface& timer,
const Domain& domain)
{
auto initial_local_well_primary_vars = model_.wellModel().getPrimaryVarsDomain(domain.index);
auto initial_local_well_primary_vars = wellModel_.getPrimaryVarsDomain(domain.index);
auto initial_local_solution = Details::extractVector(solution, domain.cells);
auto res = solveDomain(domain, timer, logger, iteration, true);
local_report = res.first;
@ -881,7 +889,7 @@ private:
auto local_solution = Details::extractVector(solution, domain.cells);
Details::setGlobal(local_solution, domain.cells, locally_solved);
} else {
model_.wellModel().setPrimaryVarsDomain(domain.index, initial_local_well_primary_vars);
wellModel_.setPrimaryVarsDomain(domain.index, initial_local_well_primary_vars);
Details::setGlobal(initial_local_solution, domain.cells, solution);
model_.simulator().model().invalidateAndUpdateIntensiveQuantities(/*timeIdx=*/0, domain);
}
@ -996,6 +1004,7 @@ private:
}
BlackoilModel<TypeTag>& model_; //!< Reference to model
BlackoilWellModelNldd<TypeTag> wellModel_; //!< NLDD well model adapter
std::vector<Domain> domains_; //!< Vector of subdomains
std::vector<std::unique_ptr<Mat>> domain_matrices_; //!< Vector of matrix operator for each subdomain
std::vector<ISTLSolverType> domain_linsolvers_; //!< Vector of linear solvers for each domain

View File

@ -57,8 +57,7 @@ namespace Opm
/// Representing a part of a grid, in a way suitable for performing
/// local solves.
template <class Grid>
struct SubDomain
struct SubDomainIndices
{
// The index of a subdomain is arbitrary, but can be used by the
// solvers to keep track of well locations etc.
@ -72,10 +71,21 @@ namespace Opm
std::vector<bool> interior;
// Enables subdomain solves and linearization using the generic linearization
// approach (i.e. FvBaseLinearizer as opposed to TpfaLinearizer).
SubDomainIndices(const int i, std::vector<int>&& c, std::vector<bool>&& in)
: index(i), cells(std::move(c)), interior(std::move(in))
{}
};
/// Representing a part of a grid, in a way suitable for performing
/// local solves.
template <class Grid>
struct SubDomain : public SubDomainIndices
{
Dune::SubGridPart<Grid> view;
// Constructor that moves from its argument.
SubDomain(const int i, std::vector<int>&& c, std::vector<bool>&& in, Dune::SubGridPart<Grid>&& v)
: index(i), cells(std::move(c)), interior(std::move(in)), view(std::move(v))
: SubDomainIndices(i, std::move(c), std::move(in))
, view(std::move(v))
{}
};

View File

@ -188,7 +188,8 @@ public:
std::size_t well_index = 0;
for (const auto& well : this->wellMod_) {
if (this->wellMod_.well_domain().at(well->name()) == domainIndex_) {
this->applySingleWell(x, y, well, this->wellMod_.well_local_cells()[well_index]);
this->applySingleWell(x, y, well,
this->wellMod_.well_local_cells()[well_index]);
}
++well_index;
}
@ -199,7 +200,10 @@ public:
const bool use_well_weights) const override
{
OPM_TIMEBLOCK(addWellPressureEquations);
this->wellMod_.addWellPressureEquationsDomain(jacobian, weights, use_well_weights, domainIndex_);
this->wellMod_.addWellPressureEquationsDomain(jacobian,
weights,
use_well_weights,
domainIndex_);
}
private:

View File

@ -30,8 +30,6 @@
#include <opm/common/OpmLog/OpmLog.hpp>
#include <opm/grid/utility/SparseTable.hpp>
#include <opm/input/eclipse/Schedule/Group/Group.hpp>
#include <opm/input/eclipse/Schedule/Group/GuideRate.hpp>
#include <opm/input/eclipse/Schedule/Schedule.hpp>
@ -41,7 +39,6 @@
#include <opm/simulators/flow/countGlobalCells.hpp>
#include <opm/simulators/flow/FlowBaseVanguard.hpp>
#include <opm/simulators/flow/SubDomain.hpp>
#include <opm/simulators/linalg/matrixblock.hh>
@ -81,6 +78,9 @@
namespace Opm {
template<class Scalar> class BlackoilWellModelNldd;
template<class T> class SparseTable;
#if COMPILE_GPU_BRIDGE
template<class Scalar> class WellContributions;
#endif
@ -132,8 +132,6 @@ template<class Scalar> class WellContributions;
using AverageRegionalPressureType = RegionAverageCalculator::
AverageRegionalPressure<FluidSystem, std::vector<int> >;
using Domain = SubDomain<Grid>;
explicit BlackoilWellModel(Simulator& simulator);
void init();
@ -248,11 +246,6 @@ template<class Scalar> class WellContributions;
// Check if well equations is converged.
ConvergenceReport getWellConvergence(const std::vector<Scalar>& B_avg, const bool checkWellGroupControls = false) const;
// Check if well equations are converged locally.
ConvergenceReport getDomainWellConvergence(const Domain& domain,
const std::vector<Scalar>& B_avg,
DeferredLogger& local_deferredLogger) const;
const SimulatorReportSingle& lastReport() const;
void addWellContributions(SparseMatrixAdapter& jacobian) const;
@ -274,7 +267,6 @@ template<class Scalar> class WellContributions;
// at the beginning of each time step (Not report step)
void prepareTimeStep(DeferredLogger& deferred_logger);
void initPrimaryVariablesEvaluation() const;
void initPrimaryVariablesEvaluationDomain(const Domain& domain) const;
std::pair<bool, bool>
updateWellControls(const bool mandatory_network_balance, DeferredLogger& deferred_logger, const bool relax_network_tolerance = false);
@ -292,15 +284,23 @@ template<class Scalar> class WellContributions;
using PressureMatrix = Dune::BCRSMatrix<Opm::MatrixBlock<Scalar, 1, 1>>;
void addWellPressureEquations(PressureMatrix& jacobian, const BVector& weights,const bool use_well_weights) const;
void addWellPressureEquationsDomain([[maybe_unused]] PressureMatrix& jacobian,
[[maybe_unused]] const BVector& weights,
[[maybe_unused]] const bool use_well_weights,
[[maybe_unused]] const int domainIndex) const;
void addWellPressureEquations(PressureMatrix& jacobian,
const BVector& weights,
const bool use_well_weights) const;
void addWellPressureEquationsStruct(PressureMatrix& jacobian) const;
void addWellPressureEquationsDomain(PressureMatrix& jacobian,
const BVector& weights,
const bool use_well_weights,
const int domainIndex) const
{
if (!nldd_) {
OPM_THROW(std::logic_error, "Attempt to access NLDD data without a NLDD solver");
}
return nldd_->addWellPressureEquations(jacobian,
weights,
use_well_weights,
domainIndex);
}
/// \brief Get list of local nonshut wells
const std::vector<WellInterfacePtr>& localNonshutWells() const
@ -308,24 +308,32 @@ template<class Scalar> class WellContributions;
return well_container_;
}
// prototype for assemble function for ASPIN solveLocal()
// will try to merge back to assemble() when done prototyping
void assembleDomain(const int iterationIdx,
const double dt,
const Domain& domain);
void updateWellControlsDomain(DeferredLogger& deferred_logger, const Domain& domain);
void setupDomains(const std::vector<Domain>& domains);
const SparseTable<int>& well_local_cells() const
{
return well_local_cells_;
if (!nldd_) {
OPM_THROW(std::logic_error, "Attempt to access NLDD data without a NLDD solver");
}
return nldd_->well_local_cells();
}
const std::map<std::string, int>& well_domain() const
{
if (!nldd_) {
OPM_THROW(std::logic_error, "Attempt to access NLDD data without a NLDD solver");
}
return nldd_->well_domain();
}
auto begin() const { return well_container_.begin(); }
auto end() const { return well_container_.end(); }
bool empty() const { return well_container_.empty(); }
bool addMatrixContributions() const { return param_.matrix_add_well_contributions_; }
bool addMatrixContributions() const
{ return param_.matrix_add_well_contributions_; }
int numStrictIterations() const
{ return param_.strict_outer_iter_wells_; }
int compressedIndexForInterior(int cartesian_cell_idx) const override
{
@ -339,7 +347,16 @@ template<class Scalar> class WellContributions;
// using the solution x to recover the solution xw for wells and applying
// xw to update Well State
void recoverWellSolutionAndUpdateWellStateDomain(const BVector& x,
const Domain& domain);
const int domainIdx);
const Grid& grid() const
{ return simulator_.vanguard().grid(); }
const Simulator& simulator() const
{ return simulator_; }
void setNlddAdapter(BlackoilWellModelNldd<TypeTag>* mod)
{ nldd_ = mod; }
protected:
Simulator& simulator_;
@ -385,12 +402,6 @@ template<class Scalar> class WellContributions;
std::vector<Scalar> B_avg_{};
// Store the local index of the wells perforated cells in the domain, if using subdomains
SparseTable<int> well_local_cells_;
const Grid& grid() const
{ return simulator_.vanguard().grid(); }
const EquilGrid& equilGrid() const
{ return simulator_.vanguard().equilGrid(); }
@ -464,7 +475,6 @@ template<class Scalar> class WellContributions;
int reportStepIndex() const;
void assembleWellEq(const double dt, DeferredLogger& deferred_logger);
void assembleWellEqDomain(const double dt, const Domain& domain, DeferredLogger& deferred_logger);
void prepareWellsBeforeAssembling(const double dt, DeferredLogger& deferred_logger);
@ -495,6 +505,7 @@ template<class Scalar> class WellContributions;
BlackoilWellModel(Simulator& simulator, const PhaseUsage& pu);
BlackoilWellModelGasLift<TypeTag> gaslift_;
BlackoilWellModelNldd<TypeTag>* nldd_ = nullptr; //!< NLDD well model adapter (not owned)
// These members are used to avoid reallocation in specific functions
// instead of using local variables.

View File

@ -2017,35 +2017,6 @@ logPrimaryVars() const
OpmLog::debug(os.str());
}
template<class Scalar>
std::vector<Scalar>
BlackoilWellModelGeneric<Scalar>::
getPrimaryVarsDomain(const int domainIdx) const
{
std::vector<Scalar> ret;
for (const auto& well : this->well_container_generic_) {
if (this->well_domain_.at(well->name()) == domainIdx) {
const auto& pv = well->getPrimaryVars();
ret.insert(ret.end(), pv.begin(), pv.end());
}
}
return ret;
}
template<class Scalar>
void BlackoilWellModelGeneric<Scalar>::
setPrimaryVarsDomain(const int domainIdx, const std::vector<Scalar>& vars)
{
std::size_t offset = 0;
for (auto& well : this->well_container_generic_) {
if (this->well_domain_.at(well->name()) == domainIdx) {
int num_pri_vars = well->setPrimaryVars(vars.begin() + offset);
offset += num_pri_vars;
}
}
assert(offset == vars.size());
}
template<class Scalar>
void BlackoilWellModelGeneric<Scalar>::
reportGroupSwitching(DeferredLogger& local_deferredLogger) const

View File

@ -118,7 +118,12 @@ public:
// whether there exists any multisegment well open on this process
bool anyMSWellOpenLocal() const;
const std::vector<Well>& eclWells() const { return wells_ecl_; }
const std::vector<Well>& eclWells() const
{ return wells_ecl_; }
bool terminalOutput() const
{ return terminal_output_; }
const Well& getWellEcl(const std::string& well_name) const;
std::vector<Well> getLocalWells(const int timeStepIdx) const;
const Schedule& schedule() const { return schedule_; }
@ -127,6 +132,9 @@ public:
std::vector<const WellInterfaceGeneric<Scalar>*> genericWells() const
{ return {well_container_generic_.begin(), well_container_generic_.end()}; }
std::vector<WellInterfaceGeneric<Scalar>*> genericWells()
{ return well_container_generic_; }
/*
Immutable version of the currently active wellstate.
*/
@ -217,11 +225,6 @@ public:
const std::map<std::string, double>& wellOpenTimes() const { return well_open_times_; }
const std::map<std::string, double>& wellCloseTimes() const { return well_close_times_; }
const std::map<std::string, int>& well_domain() const
{
return well_domain_;
}
std::vector<int> getCellsForConnections(const Well& well) const;
bool reportStepStarts() const { return report_step_starts_; }
@ -236,8 +239,6 @@ public:
bool wasDynamicallyShutThisTimeStep(const std::string& well_name) const;
void logPrimaryVars() const;
std::vector<Scalar> getPrimaryVarsDomain(const int domainIdx) const;
void setPrimaryVarsDomain(const int domainIdx, const std::vector<Scalar>& vars);
template<class Serializer>
void serializeOp(Serializer& serializer)
@ -537,9 +538,6 @@ protected:
// Store map of group name and close offending well for output
std::map<std::string, std::pair<std::string, std::string>> closed_offending_wells_;
// Keep track of the domain of each well, if using subdomains.
std::map<std::string, int> well_domain_;
private:
WellInterfaceGeneric<Scalar>* getGenWell(const std::string& well_name);

View File

@ -0,0 +1,170 @@
/*
Copyright 2016 - 2019 SINTEF Digital, Mathematics & Cybernetics.
Copyright 2016 - 2018 Equinor ASA.
Copyright 2017 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2016 - 2018 Norce AS
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <opm/simulators/wells/BlackoilWellModelNldd.hpp>
namespace Opm {
template <typename Scalar>
std::vector<Scalar>
BlackoilWellModelNlddGeneric<Scalar>::
getPrimaryVarsDomain(const int domainIdx) const
{
std::vector<Scalar> ret;
for (const auto& well : genWellModel_.genericWells()) {
if (this->well_domain_.at(well->name()) == domainIdx) {
const auto& pv = well->getPrimaryVars();
ret.insert(ret.end(), pv.begin(), pv.end());
}
}
return ret;
}
template <typename Scalar>
void
BlackoilWellModelNlddGeneric<Scalar>::
setPrimaryVarsDomain(const int domainIdx, const std::vector<Scalar>& vars)
{
std::size_t offset = 0;
for (const auto& well : genWellModel_.genericWells()) {
if (this->well_domain_.at(well->name()) == domainIdx) {
int num_pri_vars = well->setPrimaryVars(vars.begin() + offset);
offset += num_pri_vars;
}
}
assert(offset == vars.size());
}
template <typename Scalar>
void
BlackoilWellModelNlddGeneric<Scalar>::
findWellDomains(const std::vector<const SubDomainIndices*>& domains)
{
// TODO: This loop nest may be slow for very large numbers of
// domains and wells, but that has not been observed on tests so
// far. Using the partition vector instead would be faster if we
// need to change.
for (const auto& wellPtr : genWellModel_.genericWells()) {
const int first_well_cell = wellPtr->cells().front();
for (const auto& domain : domains) {
auto cell_present = [domain](const auto cell)
{
return std::binary_search(domain->cells.begin(),
domain->cells.end(), cell);
};
if (cell_present(first_well_cell)) {
// Assuming that if the first well cell is found in a domain,
// then all of that well's cells are in that same domain.
this->well_domain_[wellPtr->name()] = domain->index;
// Verify that all of that well's cells are in that same domain.
for (int well_cell : wellPtr->cells()) {
if (! cell_present(well_cell)) {
OPM_THROW(std::runtime_error,
fmt::format("Well '{}' found on multiple domains.",
wellPtr->name()));
}
}
}
}
}
}
template <typename Scalar>
void
BlackoilWellModelNlddGeneric<Scalar>::
logDomains() const
{
// Write well/domain info to the DBG file.
const int rank = genWellModel_.comm().rank();
DeferredLogger local_log;
if (!this->well_domain_.empty()) {
std::ostringstream os;
os << "Well name Rank Domain\n";
for (const auto& [wname, domain] : this->well_domain_) {
os << wname << std::setw(19 - wname.size()) << rank << std::setw(12) << domain << '\n';
}
local_log.debug(os.str());
}
auto global_log = gatherDeferredLogger(local_log, genWellModel_.comm());
if (genWellModel_.terminalOutput()) {
global_log.logMessages();
}
}
template <typename Scalar>
void
BlackoilWellModelNlddGeneric<Scalar>::
calcLocalIndices(const std::vector<const SubDomainIndices*>& domains)
{
well_local_cells_.clear();
well_local_cells_.reserve(genWellModel_.genericWells().size(), 10);
std::vector<int> local_cells;
for (const auto& well : genWellModel_.genericWells()) {
const auto& global_cells = well->cells();
const int domain_index = this->well_domain_.at(well->name());
const auto& domain_cells = domains[domain_index]->cells;
local_cells.resize(global_cells.size());
// find the local cell index for each well cell in the domain
// assume domain_cells is sorted
for (size_t i = 0; i < global_cells.size(); ++i) {
auto it = std::lower_bound(domain_cells.begin(), domain_cells.end(), global_cells[i]);
if (it != domain_cells.end() && *it == global_cells[i]) {
local_cells[i] = std::distance(domain_cells.begin(), it);
} else {
OPM_THROW(std::runtime_error, fmt::format("Cell {} not found in domain {}",
global_cells[i], domain_index));
}
}
well_local_cells_.appendRow(local_cells.begin(), local_cells.end());
}
}
template <typename Scalar>
void
BlackoilWellModelNlddGeneric<Scalar>::
calcDomains(const std::vector<const SubDomainIndices*>& domains)
{
const Opm::Parallel::Communication& comm = genWellModel_.comm();
OPM_BEGIN_PARALLEL_TRY_CATCH();
this->findWellDomains(domains);
OPM_END_PARALLEL_TRY_CATCH("BlackoilWellModel::setupDomains(): "
"well found on multiple domains.", comm);
// Write well/domain info to the DBG file.
this->logDomains();
// Pre-calculate the local cell indices for each well
this->calcLocalIndices(domains);
}
template class BlackoilWellModelNlddGeneric<double>;
#if FLOW_INSTANTIATE_FLOAT
template class BlackoilWellModelNlddGeneric<float>;
#endif
} // namespace Opm

View File

@ -0,0 +1,141 @@
/*
Copyright 2016 SINTEF ICT, Applied Mathematics.
Copyright 2016 - 2017 Statoil ASA.
Copyright 2017 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2016 - 2018 IRIS AS
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_BLACKOILWELLMODEL_NLDD_HEADER_INCLUDED
#define OPM_BLACKOILWELLMODEL_NLDD_HEADER_INCLUDED
#include <opm/grid/utility/SparseTable.hpp>
#include <opm/simulators/timestepping/ConvergenceReport.hpp>
#include <opm/simulators/utils/DeferredLogger.hpp>
#include <opm/simulators/wells/BlackoilWellModel.hpp>
#include <map>
#include <string>
#include <vector>
namespace Opm {
template<class Scalar>
class BlackoilWellModelNlddGeneric
{
public:
std::vector<Scalar> getPrimaryVarsDomain(const int domainIdx) const;
void setPrimaryVarsDomain(const int domainIdx, const std::vector<Scalar>& vars);
const SparseTable<int>& well_local_cells() const
{ return well_local_cells_; }
const std::map<std::string, int>& well_domain() const
{ return well_domain_; }
protected:
BlackoilWellModelNlddGeneric(BlackoilWellModelGeneric<Scalar>& model)
: genWellModel_(model)
{}
void calcDomains(const std::vector<const SubDomainIndices*>& domains);
private:
void logDomains() const;
void findWellDomains(const std::vector<const SubDomainIndices*>& domains);
void calcLocalIndices(const std::vector<const SubDomainIndices*>& domains);
BlackoilWellModelGeneric<Scalar>& genWellModel_;
// Keep track of the domain of each well
std::map<std::string, int> well_domain_{};
// Store the local index of the wells perforated cells in the domain
SparseTable<int> well_local_cells_{};
};
/// Class for handling the blackoil well model in a NLDD solver.
template<typename TypeTag>
class BlackoilWellModelNldd :
public BlackoilWellModelNlddGeneric<GetPropType<TypeTag, Properties::Scalar>>
{
public:
// --------- Types ---------
using Grid = GetPropType<TypeTag, Properties::Grid>;
using Scalar = GetPropType<TypeTag, Properties::Scalar>;
using PressureMatrix = typename BlackoilWellModel<TypeTag>::PressureMatrix;
using BVector = typename BlackoilWellModel<TypeTag>::BVector;
using Domain = SubDomain<Grid>;
BlackoilWellModelNldd(BlackoilWellModel<TypeTag>& model)
: BlackoilWellModelNlddGeneric<Scalar>(model)
, wellModel_(model)
{}
void initPrimaryVariablesEvaluation(const Domain& domain) const;
void addWellPressureEquations(PressureMatrix& jacobian,
const BVector& weights,
const bool use_well_weights,
const int domainIndex) const;
// prototype for assemble function for ASPIN solveLocal()
// will try to merge back to assemble() when done prototyping
void assemble(const int iterationIdx,
const double dt,
const Domain& domain);
void updateWellControls(DeferredLogger& deferred_logger,
const Domain& domain);
void setupDomains(const std::vector<Domain>& domains);
// Check if well equations are converged locally.
ConvergenceReport getWellConvergence(const Domain& domain,
const std::vector<Scalar>& B_avg,
DeferredLogger& local_deferredLogger) const;
// using the solution x to recover the solution xw for wells and applying
// xw to update Well State
void recoverWellSolutionAndUpdateWellState(const BVector& x,
const int domainIdx);
private:
BlackoilWellModel<TypeTag>& wellModel_;
void assembleWellEq(const double dt,
const Domain& domain,
DeferredLogger& deferred_logger);
// These members are used to avoid reallocation in specific functions
// instead of using local variables.
// Their state is not relevant between function calls, so they can
// (and must) be mutable, as the functions using them are const.
mutable BVector x_local_;
};
} // namespace Opm
#include "BlackoilWellModelNldd_impl.hpp"
#endif

View File

@ -0,0 +1,227 @@
/*
Copyright 2016 - 2019 SINTEF Digital, Mathematics & Cybernetics.
Copyright 2016 - 2018 Equinor ASA.
Copyright 2017 Dr. Blatt - HPC-Simulation-Software & Services
Copyright 2016 - 2018 Norce AS
This file is part of the Open Porous Media project (OPM).
OPM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OPM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPM_BLACKOILWELLMODEL_NLDD_IMPL_HEADER_INCLUDED
#define OPM_BLACKOILWELLMODEL_NLDD_IMPL_HEADER_INCLUDED
// Improve IDE experience
#ifndef OPM_BLACKOILWELLMODEL_NLDD_HEADER_INCLUDED
#include <config.h>
#include <opm/simulators/wells/BlackoilWellModelNldd.hpp>
#endif
#include <algorithm>
namespace Opm {
template<typename TypeTag>
void
BlackoilWellModelNldd<TypeTag>::
assemble(const int /*iterationIdx*/,
const double dt,
const Domain& domain)
{
// We assume that calculateExplicitQuantities() and
// prepareTimeStep() have been called already for the entire
// well model, so we do not need to do it here (when
// iterationIdx is 0).
DeferredLogger local_deferredLogger;
this->updateWellControls(local_deferredLogger, domain);
this->initPrimaryVariablesEvaluation(domain);
this->assembleWellEq(dt, domain, local_deferredLogger);
}
template<typename TypeTag>
void
BlackoilWellModelNldd<TypeTag>::
assembleWellEq(const double dt,
const Domain& domain,
DeferredLogger& deferred_logger)
{
for (const auto& well : wellModel_.localNonshutWells()) {
if (this->well_domain().at(well->name()) == domain.index) {
well->assembleWellEq(wellModel_.simulator(),
dt,
wellModel_.wellState(),
wellModel_.groupState(),
deferred_logger);
}
}
}
template<typename TypeTag>
void
BlackoilWellModelNldd<TypeTag>::
addWellPressureEquations(PressureMatrix& /*jacobian*/,
const BVector& /*weights*/,
const bool /*use_well_weights*/,
const int /*domainIndex*/) const
{
throw std::logic_error("CPRW is not yet implemented for NLDD subdomains");
// To fix this function, rdofs should be the size of the domain, and the nw should be the number of wells in the domain
// int nw = this->numLocalWellsEnd(); // should number of wells in the domain
// int rdofs = local_num_cells_; // should be the size of the domain
// for ( int i = 0; i < nw; i++ ) {
// int wdof = rdofs + i;
// jacobian[wdof][wdof] = 1.0;// better scaling ?
// }
// for ( const auto& well : well_container_ ) {
// if (well_domain_.at(well->name()) == domainIndex) {
// weights should be the size of the domain
// well->addWellPressureEquations(jacobian, weights, pressureVarIndex, use_well_weights, this->wellState());
// }
// }
}
template<typename TypeTag>
void
BlackoilWellModelNldd<TypeTag>::
recoverWellSolutionAndUpdateWellState(const BVector& x,
const int domainIdx)
{
// Note: no point in trying to do a parallel gathering
// try/catch here, as this function is not called in
// parallel but for each individual domain of each rank.
DeferredLogger local_deferredLogger;
for (const auto& well : wellModel_.localNonshutWells()) {
if (this->well_domain().at(well->name()) == domainIdx) {
const auto& cells = well->cells();
x_local_.resize(cells.size());
for (size_t i = 0; i < cells.size(); ++i) {
x_local_[i] = x[cells[i]];
}
well->recoverWellSolutionAndUpdateWellState(wellModel_.simulator(),
x_local_,
wellModel_.wellState(),
local_deferredLogger);
}
}
// TODO: avoid losing the logging information that could
// be stored in the local_deferredlogger in a parallel case.
if (wellModel_.terminalOutput()) {
local_deferredLogger.logMessages();
}
}
template<typename TypeTag>
void
BlackoilWellModelNldd<TypeTag>::
initPrimaryVariablesEvaluation(const Domain& domain) const
{
for (auto& well : wellModel_.localNonshutWells()) {
if (this->well_domain().at(well->name()) == domain.index) {
well->initPrimaryVariablesEvaluation();
}
}
}
template<typename TypeTag>
ConvergenceReport
BlackoilWellModelNldd<TypeTag>::
getWellConvergence(const Domain& domain,
const std::vector<Scalar>& B_avg,
DeferredLogger& local_deferredLogger) const
{
const int iterationIdx = wellModel_.simulator().model().newtonMethod().numIterations();
const bool relax_tolerance = iterationIdx > wellModel_.numStrictIterations();
ConvergenceReport report;
for (const auto& well : wellModel_.localNonshutWells()) {
if ((this->well_domain().at(well->name()) == domain.index)) {
if (well->isOperableAndSolvable() || well->wellIsStopped()) {
report += well->getWellConvergence(wellModel_.simulator(),
wellModel_.wellState(),
B_avg,
local_deferredLogger,
relax_tolerance);
} else {
ConvergenceReport xreport;
using CR = ConvergenceReport;
xreport.setWellFailed({CR::WellFailure::Type::Unsolvable,
CR::Severity::Normal, -1, well->name()});
report += xreport;
}
}
}
// Log debug messages for NaN or too large residuals.
if (wellModel_.terminalOutput()) {
for (const auto& f : report.wellFailures()) {
if (f.severity() == ConvergenceReport::Severity::NotANumber) {
local_deferredLogger.debug("NaN residual found with phase " +
std::to_string(f.phase()) +
" for well " + f.wellName());
} else if (f.severity() == ConvergenceReport::Severity::TooLarge) {
local_deferredLogger.debug("Too large residual found with phase " +
std::to_string(f.phase()) +
" for well " + f.wellName());
}
}
}
return report;
}
template<typename TypeTag>
void
BlackoilWellModelNldd<TypeTag>::
updateWellControls(DeferredLogger& deferred_logger,
const Domain& domain)
{
if (!wellModel_.wellsActive()) {
return;
}
// TODO: decide on and implement an approach to handling of
// group controls, network and similar for domain solves.
// Check only individual well constraints and communicate.
for (const auto& well : wellModel_.localNonshutWells()) {
if (this->well_domain().at(well->name()) == domain.index) {
constexpr auto mode = WellInterface<TypeTag>::IndividualOrGroup::Individual;
well->updateWellControl(wellModel_.simulator(),
mode,
wellModel_.wellState(),
wellModel_.groupState(),
deferred_logger);
}
}
}
template <typename TypeTag>
void
BlackoilWellModelNldd<TypeTag>::
setupDomains(const std::vector<Domain>& domains)
{
std::vector<const SubDomainIndices*> genDomains;
std::transform(domains.begin(), domains.end(),
std::back_inserter(genDomains),
[](const auto& domain)
{ return static_cast<const SubDomainIndices*>(&domain); });
this->calcDomains(genDomains);
}
} // namespace Opm
#endif

View File

@ -41,6 +41,7 @@
#include <opm/input/eclipse/Units/UnitSystem.hpp>
#include <opm/simulators/wells/BlackoilWellModelConstraints.hpp>
#include <opm/simulators/wells/BlackoilWellModelNldd.hpp>
#include <opm/simulators/wells/ParallelPAvgDynamicSourceData.hpp>
#include <opm/simulators/wells/ParallelWBPCalculation.hpp>
#include <opm/simulators/wells/VFPProperties.hpp>
@ -1449,46 +1450,6 @@ namespace Opm {
return well_group_thp_updated;
}
template<typename TypeTag>
void
BlackoilWellModel<TypeTag>::
assembleDomain([[maybe_unused]] const int iterationIdx,
const double dt,
const Domain& domain)
{
last_report_ = SimulatorReportSingle();
Dune::Timer perfTimer;
perfTimer.start();
{
const int episodeIdx = simulator_.episodeIndex();
const auto& network = this->schedule()[episodeIdx].network();
if (!this->wellsActive() && !network.active()) {
return;
}
}
// We assume that calculateExplicitQuantities() and
// prepareTimeStep() have been called already for the entire
// well model, so we do not need to do it here (when
// iterationIdx is 0).
DeferredLogger local_deferredLogger;
updateWellControlsDomain(local_deferredLogger, domain);
initPrimaryVariablesEvaluationDomain(domain);
assembleWellEqDomain(dt, domain, local_deferredLogger);
// TODO: errors here must be caught higher up, as this method is not called in parallel.
// We will log errors on rank 0, but not other ranks for now.
if (this->terminal_output_) {
local_deferredLogger.logMessages();
}
last_report_.converged = true;
last_report_.assemble_time_well += perfTimer.stop();
}
template<typename TypeTag>
void
BlackoilWellModel<TypeTag>::
@ -1500,19 +1461,6 @@ namespace Opm {
}
template<typename TypeTag>
void
BlackoilWellModel<TypeTag>::
assembleWellEqDomain(const double dt, const Domain& domain, DeferredLogger& deferred_logger)
{
for (auto& well : well_container_) {
if (this->well_domain_.at(well->name()) == domain.index) {
well->assembleWellEq(simulator_, dt, this->wellState(), this->groupState(), deferred_logger);
}
}
}
template<typename TypeTag>
void
BlackoilWellModel<TypeTag>::
@ -1613,31 +1561,6 @@ namespace Opm {
}
}
template<typename TypeTag>
void
BlackoilWellModel<TypeTag>::
addWellPressureEquationsDomain([[maybe_unused]] PressureMatrix& jacobian,
[[maybe_unused]] const BVector& weights,
[[maybe_unused]] const bool use_well_weights,
[[maybe_unused]] const int domainIndex) const
{
throw std::logic_error("CPRW is not yet implemented for NLDD subdomains");
// To fix this function, rdofs should be the size of the domain, and the nw should be the number of wells in the domain
// int nw = this->numLocalWellsEnd(); // should number of wells in the domain
// int rdofs = local_num_cells_; // should be the size of the domain
// for ( int i = 0; i < nw; i++ ) {
// int wdof = rdofs + i;
// jacobian[wdof][wdof] = 1.0;// better scaling ?
// }
// for ( const auto& well : well_container_ ) {
// if (well_domain_.at(well->name()) == domainIndex) {
// weights should be the size of the domain
// well->addWellPressureEquations(jacobian, weights, pressureVarIndex, use_well_weights, this->wellState());
// }
// }
}
template <typename TypeTag>
void BlackoilWellModel<TypeTag>::
addReservoirSourceTerms(GlobalEqVector& residual,
@ -1718,30 +1641,13 @@ namespace Opm {
template<typename TypeTag>
void
BlackoilWellModel<TypeTag>::
recoverWellSolutionAndUpdateWellStateDomain(const BVector& x, const Domain& domain)
recoverWellSolutionAndUpdateWellStateDomain(const BVector& x, const int domainIdx)
{
// Note: no point in trying to do a parallel gathering
// try/catch here, as this function is not called in
// parallel but for each individual domain of each rank.
DeferredLogger local_deferredLogger;
for (auto& well : well_container_) {
if (this->well_domain_.at(well->name()) == domain.index) {
const auto& cells = well->cells();
x_local_.resize(cells.size());
if (!nldd_) {
OPM_THROW(std::logic_error, "Attempt to call NLDD method without a NLDD solver");
}
for (size_t i = 0; i < cells.size(); ++i) {
x_local_[i] = x[cells[i]];
}
well->recoverWellSolutionAndUpdateWellState(simulator_, x_local_,
this->wellState(),
local_deferredLogger);
}
}
// TODO: avoid losing the logging information that could
// be stored in the local_deferredlogger in a parallel case.
if (this->terminal_output_) {
local_deferredLogger.logMessages();
}
return nldd_->recoverWellSolutionAndUpdateWellState(x, domainIdx);
}
@ -1756,68 +1662,6 @@ namespace Opm {
}
template<typename TypeTag>
void
BlackoilWellModel<TypeTag>::
initPrimaryVariablesEvaluationDomain(const Domain& domain) const
{
for (auto& well : well_container_) {
if (this->well_domain_.at(well->name()) == domain.index) {
well->initPrimaryVariablesEvaluation();
}
}
}
template<typename TypeTag>
ConvergenceReport
BlackoilWellModel<TypeTag>::
getDomainWellConvergence(const Domain& domain,
const std::vector<Scalar>& B_avg,
DeferredLogger& local_deferredLogger) const
{
const int iterationIdx = simulator_.model().newtonMethod().numIterations();
const bool relax_tolerance = iterationIdx > param_.strict_outer_iter_wells_;
ConvergenceReport report;
for (const auto& well : well_container_) {
if ((this->well_domain_.at(well->name()) == domain.index)) {
if (well->isOperableAndSolvable() || well->wellIsStopped()) {
report += well->getWellConvergence(simulator_,
this->wellState(),
B_avg,
local_deferredLogger,
relax_tolerance);
} else {
ConvergenceReport xreport;
using CR = ConvergenceReport;
xreport.setWellFailed({CR::WellFailure::Type::Unsolvable, CR::Severity::Normal, -1, well->name()});
report += xreport;
}
}
}
// Log debug messages for NaN or too large residuals.
if (this->terminal_output_) {
for (const auto& f : report.wellFailures()) {
if (f.severity() == ConvergenceReport::Severity::NotANumber) {
local_deferredLogger.debug("NaN residual found with phase " + std::to_string(f.phase()) + " for well " + f.wellName());
} else if (f.severity() == ConvergenceReport::Severity::TooLarge) {
local_deferredLogger.debug("Too large residual found with phase " + std::to_string(f.phase()) + " for well " + f.wellName());
}
}
}
return report;
}
template<typename TypeTag>
ConvergenceReport
BlackoilWellModel<TypeTag>::
@ -1981,29 +1825,6 @@ namespace Opm {
return { changed_well_group, more_network_update };
}
template<typename TypeTag>
void
BlackoilWellModel<TypeTag>::
updateWellControlsDomain(DeferredLogger& deferred_logger, const Domain& domain)
{
if ( !this->wellsActive() ) return ;
// TODO: decide on and implement an approach to handling of
// group controls, network and similar for domain solves.
// Check only individual well constraints and communicate.
for (const auto& well : well_container_) {
if (this->well_domain_.at(well->name()) == domain.index) {
const auto mode = WellInterface<TypeTag>::IndividualOrGroup::Individual;
well->updateWellControl(simulator_, mode, this->wellState(), this->groupState(), deferred_logger);
}
}
}
template<typename TypeTag>
void
BlackoilWellModel<TypeTag>::
@ -2480,87 +2301,6 @@ namespace Opm {
}
}
}
template <typename TypeTag>
void
BlackoilWellModel<TypeTag>::
setupDomains(const std::vector<Domain>& domains)
{
OPM_BEGIN_PARALLEL_TRY_CATCH();
// TODO: This loop nest may be slow for very large numbers of
// domains and wells, but that has not been observed on tests so
// far. Using the partition vector instead would be faster if we
// need to change.
for (const auto& wellPtr : this->well_container_) {
const int first_well_cell = wellPtr->cells().front();
for (const auto& domain : domains) {
auto cell_present = [&domain](const auto cell)
{
return std::binary_search(domain.cells.begin(),
domain.cells.end(), cell);
};
if (cell_present(first_well_cell)) {
// Assuming that if the first well cell is found in a domain,
// then all of that well's cells are in that same domain.
this->well_domain_[wellPtr->name()] = domain.index;
// Verify that all of that well's cells are in that same domain.
for (int well_cell : wellPtr->cells()) {
if (! cell_present(well_cell)) {
OPM_THROW(std::runtime_error,
fmt::format("Well '{}' found on multiple domains.",
wellPtr->name()));
}
}
}
}
}
OPM_END_PARALLEL_TRY_CATCH("BlackoilWellModel::setupDomains(): well found on multiple domains.",
simulator_.gridView().comm());
// Write well/domain info to the DBG file.
const Opm::Parallel::Communication& comm = grid().comm();
const int rank = comm.rank();
DeferredLogger local_log;
if (!this->well_domain_.empty()) {
std::ostringstream os;
os << "Well name Rank Domain\n";
for (const auto& [wname, domain] : this->well_domain_) {
os << wname << std::setw(19 - wname.size()) << rank << std::setw(12) << domain << '\n';
}
local_log.debug(os.str());
}
auto global_log = gatherDeferredLogger(local_log, comm);
if (this->terminal_output_) {
global_log.logMessages();
}
// Pre-calculate the local cell indices for each well
well_local_cells_.clear();
well_local_cells_.reserve(well_container_.size(), 10);
std::vector<int> local_cells;
for (const auto& well : well_container_) {
const auto& global_cells = well->cells();
const int domain_index = this->well_domain_.at(well->name());
const auto& domain_cells = domains[domain_index].cells;
local_cells.resize(global_cells.size());
// find the local cell index for each well cell in the domain
// assume domain_cells is sorted
for (size_t i = 0; i < global_cells.size(); ++i) {
auto it = std::lower_bound(domain_cells.begin(), domain_cells.end(), global_cells[i]);
if (it != domain_cells.end() && *it == global_cells[i]) {
local_cells[i] = std::distance(domain_cells.begin(), it);
} else {
OPM_THROW(std::runtime_error, fmt::format("Cell {} not found in domain {}",
global_cells[i], domain_index));
}
}
well_local_cells_.appendRow(local_cells.begin(), local_cells.end());
}
}
} // namespace Opm
#endif

View File

@ -128,7 +128,7 @@ public:
void postSolveDomain(GlobalEqVector& deltaX, const Domain& domain)
{
model_.recoverWellSolutionAndUpdateWellStateDomain(deltaX, domain);
model_.recoverWellSolutionAndUpdateWellStateDomain(deltaX, domain.index);
}
template <class Restarter>

View File

@ -39,6 +39,10 @@ namespace Opm {
#include <opm/material/fluidstates/BlackOilFluidState.hpp>
#include <opm/models/blackoil/blackoilproperties.hh>
#include <opm/simulators/linalg/linalgproperties.hh>
#include <opm/simulators/wells/BlackoilWellModel.hpp>
#include <opm/simulators/wells/GasLiftGroupInfo.hpp>
#include <opm/simulators/wells/GasLiftSingleWell.hpp>