opm-simulators
ISTLSolverSystem.hpp
1 /*
2  Copyright Equinor ASA 2026
3 
4  This file is part of the Open Porous Media project (OPM).
5 
6  OPM is free software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  OPM is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with OPM. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #ifndef OPM_ISTLSOLVERSYSTEM_HEADER_INCLUDED
20 #define OPM_ISTLSOLVERSYSTEM_HEADER_INCLUDED
21 
22 #include <opm/simulators/linalg/system/SystemTypes.hpp>
23 #include <opm/simulators/linalg/system/SystemPreconditionerFactory.hpp>
24 #include <opm/simulators/linalg/system/WellMatrixMerger.hpp>
25 
26 #include <opm/simulators/linalg/FlexibleSolver.hpp>
27 #include <opm/simulators/linalg/ISTLSolver.hpp>
28 
29 namespace Opm
30 {
31 
32 template <class TypeTag>
33 class ISTLSolverSystem : public ISTLSolver<TypeTag>
34 {
35 protected:
39  using Matrix = typename SparseMatrixAdapter::IstlMatrix;
42 
43  // Compile-time validation: SystemPreconditionerFactory and related types
44  // are hardcoded for standard 3-phase blackoil (3 reservoir equations, 4 well equations).
45  // See SystemTypes.hpp for details.
46  static_assert(Indices::numEq == 3,
47  "ISTLSolverSystem (with system_cpr preconditioner) only supports "
48  "3-equation blackoil models. This model has different equation count.");
49 
50  constexpr static std::size_t pressureIndex
51  = Indices::pressureSwitchIdx;
52 
53  enum { enablePolymerMolarWeight = getPropValue<TypeTag, Properties::EnablePolymerMW>() };
54  constexpr static bool isIncompatibleWithCprw = enablePolymerMolarWeight;
55 
56 #if HAVE_MPI
57  using CommunicationType = Dune::OwnerOverlapCopyCommunication<int, int>;
58 #else
59  using CommunicationType = Dune::Communication<int>;
60 #endif
62 
63  static constexpr auto _0 = Dune::Indices::_0;
64  static constexpr auto _1 = Dune::Indices::_1;
65 
66 public:
67  ISTLSolverSystem(const Simulator& simulator,
68  const FlowLinearSolverParameters& parameters,
69  bool forceSerial = false)
70  : Parent(simulator, parameters, forceSerial)
71  {
72  }
73 
74  explicit ISTLSolverSystem(const Simulator& simulator)
75  : Parent(simulator)
76  {
77  }
78 
79  void prepare(const SparseMatrixAdapter& M, Vector& b) override
80  {
81  OPM_TIMEBLOCK(istlSolverPrepare);
82  this->initPrepare(M.istlMatrix(), b);
83  prepareSystemSolver();
84  }
85 
86  void prepare(const Matrix& M, Vector& b) override
87  {
88  OPM_TIMEBLOCK(istlSolverPrepare);
89  this->initPrepare(M, b);
90  prepareSystemSolver();
91  }
92 
93  bool solve(Vector& x) override
94  {
95  OPM_TIMEBLOCK(istlSolverSolve);
96  ++this->solveCount_;
97 
98  const std::size_t numRes = Parent::matrix_->N();
99  const std::size_t numWell = cachedWellStructure_.totalWellBlocks;
100 
101  sysX_[_0].resize(numRes);
102  sysX_[_0] = 0.0;
103  sysX_[_1].resize(numWell);
104  sysX_[_1] = 0.0;
105 
106  sysRhs_[_0].resize(numRes);
107  sysRhs_[_0] = *Parent::rhs_;
108  sysRhs_[_1].resize(numWell);
109  sysRhs_[_1] = 0.0;
110 
111  Dune::InverseOperatorResult result;
112  sysSolver_->apply(sysX_, sysRhs_, result);
113  this->iterations_ = result.iterations;
114 
115  x = sysX_[_0];
116 
117  return this->checkConvergence(result);
118  }
119 
120 private:
121  bool sysInitialized_ = false;
122  WellMatrixStructure cachedWellStructure_;
123 
124  // Current per-well B/C/D blocks for the explicit 2x2 system matrix.
125  std::vector<WRMatrix<Scalar>> wellBMatrices_;
126  std::vector<RWMatrix<Scalar>> wellCMatrices_;
127  std::vector<WWMatrix<Scalar>> wellDMatrices_;
128  Opm::SparseTable<int> wellCells_;
129 
130  // Owned storage for merged well matrices; SystemMatrix points into these.
131  WRMatrix<Scalar> mergedB_;
132  RWMatrix<Scalar> mergedC_;
133  WWMatrix<Scalar> mergedD_;
134 
135  SystemMatrix<Scalar> sysMatrix_;
136  SystemVector<Scalar> sysX_;
137  SystemVector<Scalar> sysRhs_;
138 
139  // Serial solver components
140  std::unique_ptr<SystemSeqOp<Scalar>> sysOp_;
141  std::unique_ptr<Dune::FlexibleSolver<SystemSeqOp<Scalar>>> sysFlexSolverSeq_;
142 
143  // Parallel solver components
144 #if HAVE_MPI
145  using WellComm = Dune::JacComm;
146  std::unique_ptr<WellComm> wellComm_;
147  std::unique_ptr<SystemComm> systemComm_;
148  std::unique_ptr<SystemParOp<Scalar>> sysOpPar_;
149  std::unique_ptr<Dune::FlexibleSolver<SystemParOp<Scalar>>> sysFlexSolverPar_;
150 #endif
151 
152  using SysSolverType = Dune::InverseOperator<SystemVector<Scalar>, SystemVector<Scalar>>;
155 #if HAVE_MPI
156  using ParSysPrecondType = SystemPreconditioner<Scalar, ParResOperator<Scalar>, ParResComm>;
157 #endif
158  SysSolverType* sysSolver_ = nullptr;
159  SysPrecondType* sysPrecond_ = nullptr;
160 
161  void prepareSystemSolver()
162  {
163  OPM_TIMEBLOCK(flexibleSolverPrepare);
164 
165  wellBMatrices_.clear();
166  wellCMatrices_.clear();
167  wellDMatrices_.clear();
168  wellCells_.clear();
169 
170  this->simulator_.problem().wellModel().addBCDMatrix(
171  wellBMatrices_, wellCMatrices_, wellDMatrices_, wellCells_);
172 
173  const Opm::WellMatrixMerger<Scalar> merger(
174  Parent::matrix_->N(), wellBMatrices_, wellCMatrices_, wellDMatrices_, wellCells_);
175 
176  const bool localStructureChanged = !sysInitialized_
177  || !merger.hasSameStructure(cachedWellStructure_);
178 
179  // All ranks must agree on whether to take the structure-change path,
180  // because the distributed solver create and update paths use different
181  // MPI-collective sequences.
182 #if HAVE_MPI
183  const bool globalStructureChanged = this->comm_->communicator().max(
184  static_cast<int>(localStructureChanged)) > 0;
185 #else
186  const bool globalStructureChanged = localStructureChanged;
187 #endif
188  const bool needStructureRefresh = !sysInitialized_ || globalStructureChanged;
189 
190  const auto& prm = this->prm_[this->activeSolverNum_];
191 
192  if (needStructureRefresh) {
193  OPM_TIMEBLOCK(flexibleSolverCreate);
194  merger.buildMatrices(mergedB_, mergedC_, mergedD_);
195  sysMatrix_.A = Parent::matrix_;
196  sysMatrix_.B = &mergedB_;
197  sysMatrix_.C = &mergedC_;
198  sysMatrix_.D = &mergedD_;
199  cachedWellStructure_ = merger.buildStructure();
200 
201  refreshSystemSolverForChangedWellStructure(prm);
202  sysInitialized_ = true;
203  } else {
204  OPM_TIMEBLOCK(flexibleSolverUpdate);
205  // Pattern unchanged: write fresh values into the existing merged
206  // matrices without any (de)allocation.
207  merger.updateValues(mergedB_, mergedC_, mergedD_);
208 
209  // Refresh A pointer in case the reservoir matrix was reallocated.
210  sysMatrix_.A = Parent::matrix_;
211  sysMatrix_.B = &mergedB_;
212  sysMatrix_.C = &mergedC_;
213  sysMatrix_.D = &mergedD_;
214  sysPrecond_->update();
215  }
216  }
217 
218  void refreshSystemSolverForChangedWellStructure(const Opm::PropertyTree& prm)
219  {
220  if (!sysInitialized_ || !sysPrecond_) {
221  createSystemSolver(prm);
222  return;
223  }
224 
225 #if HAVE_MPI
226  if (this->comm_->communicator().size() > 1) {
227  if (auto* precond = dynamic_cast<ParSysPrecondType*>(sysPrecond_)) {
228  precond->updateForChangedWellStructure();
229  } else
230  { // Rebuild the parallel solver if the parallel preconditioner cannot be updated in-place.
231  createSystemSolver(prm);
232  }
233  return;
234  }
235 #endif
236 
237  if (auto* precond = dynamic_cast<SeqSysPrecondType*>(sysPrecond_)) {
238  precond->updateForChangedWellStructure();
239  } else
240  { // Rebuild the solver if the sequential preconditioner cannot be updated in-place
241  createSystemSolver(prm);
242  }
243  }
244 
245  void createSystemSolver(const Opm::PropertyTree& prm)
246  {
247  // Derive weights from the reservoir sub-block config (which uses CPR internally)
248  auto resSolverPrm = prm.get_child("preconditioner.reservoir_solver");
249  std::function<ResVector<Scalar>()> resWeightCalc
250  = this->getWeightsCalculator(resSolverPrm, this->getMatrix(), pressureIndex);
251 
252  std::function<SystemVector<Scalar>()> sysWeightCalc;
253  if (resWeightCalc) {
254  sysWeightCalc = [resWeightCalc]() {
255  SystemVector<Scalar> w;
256  w[_0] = resWeightCalc();
257  return w;
258  };
259  }
260 
261 #if HAVE_MPI
262  const bool is_parallel = this->comm_->communicator().size() > 1;
263  if (is_parallel) {
264  wellComm_ = std::make_unique<WellComm>();
265  systemComm_ = std::make_unique<SystemComm>(*(this->comm_), *wellComm_);
266 
267  sysOpPar_ = std::make_unique<SystemParOp<Scalar>>(sysMatrix_, *systemComm_);
268 
269  sysFlexSolverPar_ = std::make_unique<Dune::FlexibleSolver<SystemParOp<Scalar>>>(
270  *sysOpPar_, *systemComm_, prm, sysWeightCalc, pressureIndex);
271 
272  sysSolver_ = sysFlexSolverPar_.get();
273  sysPrecond_ = &sysFlexSolverPar_->preconditioner();
274  }
275  else
276 #endif
277  {
278  sysOp_ = std::make_unique<SystemSeqOp<Scalar>>(sysMatrix_);
279 
280  sysFlexSolverSeq_ = std::make_unique<Dune::FlexibleSolver<SystemSeqOp<Scalar>>>(
281  *sysOp_, prm, sysWeightCalc, pressureIndex);
282 
283  sysSolver_ = sysFlexSolverSeq_.get();
284  sysPrecond_ = &sysFlexSolverSeq_->preconditioner();
285  }
286  }
287 };
288 
289 } // namespace Opm
290 
291 #endif // OPM_ISTLSOLVERSYSTEM_HEADER_INCLUDED
PropertyTree get_child(const std::string &key) const
Retrieve copy of sub tree rooted at node.
Definition: PropertyTree.cpp:82
typename Properties::Detail::GetPropImpl< TypeTag, Property >::type::type GetPropType
get the type alias defined in the property (equivalent to old macro GET_PROP_TYPE(...))
Definition: propertysystem.hh:233
Definition: SystemTypes.hpp:76
This class solves the fully implicit black-oil system by solving the reduced system (after eliminatin...
Definition: FlowLinearSolverParameters.hpp:40
Definition: SystemPreconditioner.hpp:56
Structs needed for tpfalinearizer and its gpuparams struct extracted to be defined in one place that ...
Definition: blackoilbioeffectsmodules.hh:45
Definition: WellMatrixMerger.hpp:163
This class carries all parameters for the NewtonIterationBlackoilInterleaved class.
Definition: FlowLinearSolverParameters.hpp:97
Definition: ISTLSolverSystem.hpp:33
Hierarchical collection of key/value pairs.
Definition: PropertyTree.hpp:38
Definition: WellMatrixMerger.hpp:65