Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6dd1454
Add a CPRW pressure stage to the system solver
hnil Aug 4, 2026
bf21d5d
Add cell-average well weights and regularise the coarse well diagonal
hnil Aug 4, 2026
dce7001
Default the CPRW pressure stage to the standard solver's weighting
hnil Aug 4, 2026
66d642a
Add composable system preconditioner parts and a system transfer policy
hnil Aug 4, 2026
496fb6d
Sum the coarse well column over all of a well's blocks
hnil Aug 4, 2026
cfde17b
Make cellavg the classic per-well weighting, add cellblockavg
hnil Aug 4, 2026
35dfe3b
Optionally give pressure-controlled wells a trivial coarse equation
hnil Aug 4, 2026
15a984a
Add the classic coarse diagonal convention for multisegment wells
hnil Aug 4, 2026
f51cab8
Dump the CPRW coarse matrix from both implementations
hnil Aug 4, 2026
e298613
Dump the CPR weights from both CPRW implementations
hnil Aug 4, 2026
397825c
Dump the fine system on the system-solver path too
hnil Aug 4, 2026
b831e1f
Dump the primary variables and their meanings at every linearization
hnil Aug 4, 2026
2e00bc3
Let the classic CPRW contract D for multisegment wells
hnil Aug 4, 2026
bd08be1
Default the CPRW pressure stage to the classic well transfer
hnil Aug 4, 2026
51d443f
Add composable sweep steps for the system preconditioner
hnil Aug 6, 2026
a432593
TwoLevelMethodCpr: only resize the work vectors when the type allows it
hnil Aug 6, 2026
4ca0973
Add the general system preconditioner, a two-level method on the system
hnil Aug 6, 2026
a5792b9
Add --linear-solver=general_system_cpr and general_system_cprw
hnil Aug 6, 2026
8e873e0
Assert the general preconditioner reproduces the fixed one
hnil Aug 6, 2026
e969719
Skip the residual updates a sweep never reads again
hnil Aug 6, 2026
ac602cc
Make the response to a changed well structure configurable
hnil Aug 6, 2026
87bcc1e
PropertyTree: read and write arrays of sub trees
hnil Aug 6, 2026
e79a9e9
Give general_system_cpr a JSON layout shaped like cpr's
hnil Aug 6, 2026
dde5768
Add well_thp_is_pressure_control to the system CPRW pressure stage
hnil Aug 12, 2026
47240d7
Add cellavg_vfp: control-row elimination for THP wells
hnil Aug 12, 2026
c2461d4
Fold the THP options into a single well_thp_treatment key
hnil Aug 12, 2026
dc5f14c
Scale the trivial well rows to the reservoir coarse diagonals
hnil Aug 12, 2026
b0758d8
Scale the trivial well rows in the classic CPRW too
hnil Aug 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CMakeLists_files.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,9 @@ list (APPEND TEST_SOURCE_FILES
tests/test_tpsa_localresidual.cpp
tests/test_tpsa_primaryvariables.cpp
tests/test_vfpproperties.cpp
tests/test_GeneralSystemPreconditioner.cpp
tests/test_MswCprWellDiagonal.cpp
tests/test_SystemCprwPressureStage.cpp
tests/test_WellMatrixMerger.cpp
tests/test_WaterSatfuncConsistencyChecks.cpp
tests/test_wellmodel.cpp
Expand Down Expand Up @@ -690,6 +693,10 @@ list (APPEND TEST_DATA_FILES
tests/options_system_cpr_missing_smoother.json
tests/options_system_cpr_missing_well.json
tests/options_system_cpr_res_precond_not_cpr.json
tests/options_system_cprw_approx_wells.json
tests/options_system_cprw_approx_wells_bad_outer.json
tests/options_system_cprw_complete.json
tests/options_system_cprw_missing_coarsesolver.json
tests/GCONSUMP.DATA
tests/GCONSUMP_COMPLEX.DATA
tests/GROUP_HIGHER_CONSTRAINTS.DATA
Expand Down Expand Up @@ -1122,6 +1129,10 @@ list (APPEND PUBLIC_HEADER_FILES
opm/simulators/linalg/ISTLSolverRuntimeOptionProxy.hpp
opm/simulators/linalg/Preconditioner2InverseOperator.hpp
opm/simulators/linalg/system/MultiComm.hpp
opm/simulators/linalg/system/SystemCprwPressureStage.hpp
opm/simulators/linalg/system/GeneralSystemPreconditioner.hpp
opm/simulators/linalg/system/SystemPreconditionerParts.hpp
opm/simulators/linalg/system/SystemPressureBhpTransferPolicy.hpp
opm/simulators/linalg/system/SystemPreconditioner.hpp
opm/simulators/linalg/system/SystemPreconditionerFactory.hpp
opm/simulators/linalg/system/SystemTypes.hpp
Expand Down
2 changes: 2 additions & 0 deletions opm/simulators/linalg/FlowLinearSolverParameters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ void FlowLinearSolverParameters::registerParameters()
("Scale linear system according to equation scale and primary variable types");
Parameters::Register<Parameters::LinearSolver>
("Configuration of solver. Valid options are: cprw (default), system_cpr (CPU-only), "
"system_cprw (CPU-only, system_cpr with the wells in the pressure stage), "
"general_system_cpr / general_system_cprw (the same composed from named parts), "
"ilu0, dilu, cpr (an alias for cprw), cpr_quasiimpes, "
"cpr_trueimpes, cpr_trueimpesanalytic, amg or hybrid (experimental). "
"Alternatively, you can request a configuration to be read from a "
Expand Down
57 changes: 51 additions & 6 deletions opm/simulators/linalg/ISTLSolver.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@

#include <any>
#include <cstddef>
#include <fstream>
#include <functional>
#include <iomanip>
#include <memory>
#include <set>
#include <sstream>
Expand Down Expand Up @@ -374,6 +376,37 @@ std::unique_ptr<Matrix> blockJacobiAdjacency(const Grid& grid,
if (isParallel() && type != "paroverilu0") {
detail::makeOverlapRowsInvalid(getMatrix(), overlapRows_);
}

dumpPrimaryVariables();
}

// Diagnostic (verbosity > 10): the primary variables and their meanings at
// every linearization, so two solver variants can be compared cell by cell.
void dumpPrimaryVariables() const
{
if (prm_[activeSolverNum_].template get<int>("verbosity", 0) <= 10) {
return;
}
static int counter = 0;
std::ofstream out("primvars_" + std::to_string(counter++) + ".txt");
if (!out) {
return;
}
const auto& sol = simulator_.model().solution(/*timeIdx=*/0);
out << std::setprecision(16);
for (std::size_t i = 0; i < sol.size(); ++i) {
const auto& pv = sol[i];
out << i;
for (unsigned j = 0; j < std::decay_t<decltype(pv)>::dimension; ++j) {
out << ' ' << pv[j];
}
if constexpr (requires { pv.primaryVarsMeaningGas(); }) {
out << " gas=" << static_cast<int>(pv.primaryVarsMeaningGas())
<< " wat=" << static_cast<int>(pv.primaryVarsMeaningWater())
<< " pre=" << static_cast<int>(pv.primaryVarsMeaningPressure());
}
out << '\n';
}
}

void prepare(const SparseMatrixAdapter& M, Vector& b) override
Expand Down Expand Up @@ -588,18 +621,30 @@ std::unique_ptr<Matrix> blockJacobiAdjacency(const Grid& grid,
const Matrix& matrix,
std::size_t pressIndex) const
{
std::function<Vector()> weightsCalculator;

using namespace std::string_literals;

auto preconditionerType = prm.get("preconditioner.type"s, "cpr"s);
// We use lower case as the internal canonical representation of solver names
std::ranges::transform(preconditionerType, preconditionerType.begin(), ::tolower);
if (preconditionerType == "cpr" || preconditionerType == "cprt"
|| preconditionerType == "cprw" || preconditionerType == "cprwt") {
const bool transpose = preconditionerType == "cprt" || preconditionerType == "cprwt";
if (preconditionerType != "cpr" && preconditionerType != "cprt"
&& preconditionerType != "cprw" && preconditionerType != "cprwt") {
return {};
}
const bool transpose = preconditionerType == "cprt" || preconditionerType == "cprwt";
return makeWeightsCalculator(prm.get("preconditioner.weight_type"s, "quasiimpes"s),
matrix, pressIndex, transpose);
}

// The same, for a caller that knows the weighting directly rather than
// through a CPR preconditioner sub-tree.
std::function<Vector()> makeWeightsCalculator(const std::string& weightsType,
const Matrix& matrix,
std::size_t pressIndex,
const bool transpose = false) const
{
std::function<Vector()> weightsCalculator;
{
const bool enableThreadParallel = this->parameters_[0].cpr_weights_thread_parallel_;
const auto weightsType = prm.get("preconditioner.weight_type"s, "quasiimpes"s);
if (weightsType == "quasiimpes") {
// weights will be created as default in the solver
// assignment p = pressureIndex prevent compiler warning about
Expand Down
6 changes: 4 additions & 2 deletions opm/simulators/linalg/ISTLSolverRuntimeOptionProxy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,15 @@ class ISTLSolverRuntimeOptionProxy : public AbstractISTLSolver<GetPropType<TypeT
void createSolver(const Simulator& simulator, Args&&... args)
{
auto linSolverConf = Parameters::Get<Parameters::LinearSolver>();
bool useSystemCpr = (linSolverConf == "system_cpr");
bool useSystemCpr = (linSolverConf == "system_cpr") || (linSolverConf == "system_cprw")
|| (linSolverConf == "general_system_cpr") || (linSolverConf == "general_system_cprw");
if (!useSystemCpr && linSolverConf.size() > 5
&& linSolverConf.ends_with(".json")
&& std::filesystem::exists(linSolverConf)) {
try {
PropertyTree prm(linSolverConf);
useSystemCpr = (prm.get<std::string>("preconditioner.type", "") == "system_cpr");
const auto type = prm.get<std::string>("preconditioner.type", "");
useSystemCpr = (type == "system_cpr") || (type == "general_system_cpr");
} catch (...) {}
}
if (useSystemCpr) {
Expand Down
43 changes: 41 additions & 2 deletions opm/simulators/linalg/PressureBhpTransferPolicy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,16 @@

#pragma once

#include <opm/common/ErrorMacros.hpp>
#include <opm/common/TimingMacros.hpp>

#include <opm/simulators/linalg/matrixblock.hh>
#include <opm/simulators/linalg/MatrixMarketSpecializations.hpp>

#include <dune/istl/matrixmarket.hh>

#include <fstream>
#include <string>
#include <opm/simulators/linalg/PropertyTree.hpp>
#include <opm/simulators/linalg/twolevelmethodcpr.hh>

Expand Down Expand Up @@ -199,14 +206,46 @@ namespace Opm
if (prm_.get<bool>("add_wells")) {
OPM_TIMEBLOCK(cprwAddWellEquation);
assert(transpose == false); // not implemented
bool use_well_weights = prm_.get<bool>("use_well_weights");
fineOperator.addWellPressureEquations(*coarseLevelMatrix_, weights_, use_well_weights);
const bool use_well_weights = prm_.get<bool>("use_well_weights");
// "contract_d" takes the coarse well diagonal from lambda' D(:,bhp)
// for every well; "auto" (the default) leaves the multisegment path
// on its historical row-sum diagonal. Standard wells contract D
// either way, so this only changes multisegment wells.
const auto diagonal = prm_.get<std::string>("well_coarse_diagonal", "auto");
if (diagonal != "auto" && diagonal != "contract_d") {
OPM_THROW(std::invalid_argument,
"Unknown well_coarse_diagonal '" + diagonal +
"'. Valid values are 'auto' and 'contract_d'.");
}
const bool contract_d_diagonal = (diagonal == "contract_d");
fineOperator.addWellPressureEquations(*coarseLevelMatrix_, weights_,
use_well_weights, contract_d_diagonal);
#ifndef NDEBUG
std::advance(rowCoarse, fineOperator.getNumberOfExtraEquations());
assert(rowCoarse == coarseLevelMatrix_->end());
#endif

}
dumpCoarseMatrix();
}

// verbosity above 10 writes the coarse system out, so that it can be
// compared entry by entry against another CPRW implementation.
void dumpCoarseMatrix() const
{
if (prm_.get<int>("verbosity", 0) <= 10) {
return;
}
static int counter = 0;
const int id = counter++;
std::ofstream out("cprw_coarse_" + std::to_string(id) + ".mm");
if (out) {
Dune::writeMatrixMarket(*coarseLevelMatrix_, out);
}
std::ofstream wout("cprw_weights_" + std::to_string(id) + ".mm");
if (wout) {
Dune::writeMatrixMarket(weights_, wout);
}
}

void moveToCoarseLevel(const typename ParentType::FineRangeType& fine) override
Expand Down
30 changes: 30 additions & 0 deletions opm/simulators/linalg/PropertyTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,36 @@ std::vector<std::string> PropertyTree::get_child_keys() const
return keys;
}

void PropertyTree::put_child_list(const std::string& key,
const std::vector<PropertyTree>& items)
{
// An array is a node whose children all have an empty key.
boost::property_tree::ptree array;
for (const auto& item : items) {
array.push_back(std::make_pair(std::string{}, *item.tree_));
}
tree_->put_child(key, array);
}

std::optional<std::vector<PropertyTree>>
PropertyTree::get_child_list(const std::string& child) const
{
auto subTree = this->tree_->get_child_optional(child);
if (! subTree) {
return std::nullopt;
}

// A JSON array parses to a node whose children all have an empty key, so
// they cannot be reached by name; walk them in order instead.
std::vector<PropertyTree> items;
items.reserve(subTree->size());
for (const auto& item : *subTree) {
items.emplace_back(PropertyTree(item.second));
}

return items;
}

template <typename T>
std::optional<std::vector<T>>
PropertyTree::get_child_items_as_vector(const std::string& child) const
Expand Down
25 changes: 25 additions & 0 deletions opm/simulators/linalg/PropertyTree.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,31 @@ class PropertyTree
/// \return Vector of strings containing the names of all immediate children
std::vector<std::string> get_child_keys() const;

/// Retrieve a node's children as sub trees, in order.
///
/// For a JSON array of objects. Such an array parses to a node whose
/// children all have an empty key, so they cannot be reached by name and
/// get_child_items_as_vector() cannot read them either -- that one reads
/// scalars.
///
/// \param[in] child Property key. Expected to be in hierarchical
/// notation for subtrees--i.e., using periods ('.') to separate
/// hierarchy levels.
///
/// \return The child sub trees in the order they appear. Nullopt if no
/// node named by \p child exists.
std::optional<std::vector<PropertyTree>>
get_child_list(const std::string& child) const;

/// Store a sequence of sub trees as a JSON array.
///
/// \param[in] key Property key. Expected to be in hierarchical
/// notation for subtrees--i.e., using periods ('.') to separate
/// hierarchy levels.
///
/// \param[in] items Sub trees, stored in the order given.
void put_child_list(const std::string& key, const std::vector<PropertyTree>& items);

/// Retrieve node items as linearised vector.
///
/// Assumes that the node's child is an array type of homongeneous
Expand Down
22 changes: 14 additions & 8 deletions opm/simulators/linalg/WellOperators.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ class LinearOperatorExtra : public Dune::LinearOperator<X, Y>
using PressureMatrix = Dune::BCRSMatrix<MatrixBlock<field_type, 1, 1>>;
virtual void addWellPressureEquations(PressureMatrix& jacobian,
const X& weights,
const bool use_well_weights) const = 0;
const bool use_well_weights,
const bool contract_d_diagonal) const = 0;
virtual void addWellPressureEquationsStruct(PressureMatrix& jacobian) const = 0;
virtual int getNumberOfExtraEquations() const = 0;
};
Expand Down Expand Up @@ -120,10 +121,11 @@ class WellModelAsLinearOperator : public LinearOperatorExtra<X, Y>

void addWellPressureEquations(PressureMatrix& jacobian,
const X& weights,
const bool use_well_weights) const override
const bool use_well_weights,
const bool contract_d_diagonal) const override
{
OPM_TIMEBLOCK(addWellPressureEquations);
wellMod_.addWellPressureEquations(jacobian, weights, use_well_weights);
wellMod_.addWellPressureEquations(jacobian, weights, use_well_weights, contract_d_diagonal);
}

void addWellPressureEquationsStruct(PressureMatrix& jacobian) const override
Expand Down Expand Up @@ -197,12 +199,14 @@ class DomainWellModelAsLinearOperator : public WellModelAsLinearOperator<WellMod

void addWellPressureEquations(PressureMatrix& jacobian,
const X& weights,
const bool use_well_weights) const override
const bool use_well_weights,
const bool contract_d_diagonal) const override
{
OPM_TIMEBLOCK(addWellPressureEquations);
this->wellMod_.addWellPressureEquationsDomain(jacobian,
weights,
use_well_weights,
contract_d_diagonal,
domainIndex_);
}

Expand Down Expand Up @@ -264,10 +268,11 @@ class WellModelMatrixAdapter : public Dune::AssembledLinearOperator<M,X,Y>

void addWellPressureEquations(PressureMatrix& jacobian,
const X& weights,
const bool use_well_weights) const
const bool use_well_weights,
const bool contract_d_diagonal) const
{
OPM_TIMEBLOCK(addWellPressureEquations);
wellOper_.addWellPressureEquations(jacobian, weights, use_well_weights);
wellOper_.addWellPressureEquations(jacobian, weights, use_well_weights, contract_d_diagonal);
}

void addWellPressureEquationsStruct(PressureMatrix& jacobian) const
Expand Down Expand Up @@ -359,10 +364,11 @@ class WellModelGhostLastMatrixAdapter : public Dune::AssembledLinearOperator<M,X

void addWellPressureEquations(PressureMatrix& jacobian,
const X& weights,
const bool use_well_weights) const
const bool use_well_weights,
const bool contract_d_diagonal) const
{
OPM_TIMEBLOCK(addWellPressureEquations);
wellOper_.addWellPressureEquations(jacobian, weights, use_well_weights);
wellOper_.addWellPressureEquations(jacobian, weights, use_well_weights, contract_d_diagonal);
}

void addWellPressureEquationsStruct(PressureMatrix& jacobian) const
Expand Down
Loading