opm-simulators
SystemPreconditioner.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_SYSTEMPRECONDITIONER_HEADER_INCLUDED
20 #define OPM_SYSTEMPRECONDITIONER_HEADER_INCLUDED
21 
22 #include <opm/simulators/linalg/system/MultiComm.hpp>
23 #include <opm/simulators/linalg/system/SystemTypes.hpp>
24 #include <opm/simulators/linalg/FlexibleSolver.hpp>
25 #include <opm/simulators/linalg/PreconditionerWithUpdate.hpp>
26 #include <opm/simulators/linalg/PropertyTree.hpp>
27 
28 #include <dune/istl/operators.hh>
29 #include <dune/istl/paamg/pinfo.hh>
30 
31 
32 namespace Opm
33 {
34 
35 // Reservoir operator/comm types used as template arguments.
36 template<typename Scalar>
37 using SeqResOperator = Dune::MatrixAdapter<RRMatrix<Scalar>, ResVector<Scalar>, ResVector<Scalar>>;
38 
39 #if HAVE_MPI
40 using ParResComm = Dune::OwnerOverlapCopyCommunication<int, int>;
41 template<typename Scalar>
42 using ParResOperator = Dune::OverlappingSchwarzOperator<RRMatrix<Scalar>, ResVector<Scalar>, ResVector<Scalar>, ParResComm>;
43 #endif
44 
45 // Preconditioner for the coupled reservoir-well system.
46 //
47 // Templated on scalar type, reservoir operator and communication types to
48 // unify sequential and parallel implementations. The 3-stage algorithm:
49 // 1. Reservoir CPR solve
50 // 2. Well solve + reservoir smoothing
51 // 3. Final well solve
52 //
53 // For parallel runs, copyOwnerToAll synchronises overlap DOFs before
54 // each reservoir sub-solve.
55 template <class Scalar, class ResOp, class ResComm = Dune::Amg::SequentialInformation>
56 class SystemPreconditioner : public Dune::PreconditionerWithUpdate<SystemVector<Scalar>, SystemVector<Scalar>>
57 {
58 public:
59  static constexpr bool isParallel = !std::is_same_v<ResComm, Dune::Amg::SequentialInformation>;
60 
62  using WellOperator = Dune::MatrixAdapter<WWMatrix<Scalar>, WellVector<Scalar>, WellVector<Scalar>>;
64 
65  static constexpr auto _0 = Dune::Indices::_0;
66  static constexpr auto _1 = Dune::Indices::_1;
67 
68  // Sequential constructor (enabled only for non-parallel specializations).
70  const std::function<ResVector<Scalar>()>& weightsCalculator,
71  int pressureIndex,
72  const Opm::PropertyTree& prm)
73  requires (!isParallel)
74  : S_(S)
75  , pressureIndex_(pressureIndex)
76  {
77  initSubSolvers(prm, weightsCalculator);
78  initWorkVectors();
79  }
80 
81  // Parallel constructor (enabled only for parallel specializations).
83  const std::function<ResVector<Scalar>()>& weightsCalculator,
84  int pressureIndex,
85  const Opm::PropertyTree& prm,
86  const ResComm& resComm)
87  requires (isParallel)
88  : S_(S)
89  , resComm_(&resComm)
90  , pressureIndex_(pressureIndex)
91  {
92  initSubSolvers(prm, weightsCalculator);
93  initWorkVectors();
94  }
95 
96  void pre(SystemVector<Scalar>&, SystemVector<Scalar>&) override
97  {
98  }
99 
100  void post(SystemVector<Scalar>&) override
101  {
102  }
103 
104  Dune::SolverCategory::Category category() const override
105  {
106  if constexpr (isParallel)
107  return Dune::SolverCategory::overlapping;
108  else
109  return Dune::SolverCategory::sequential;
110  }
111 
112  void update() override
113  {
114  resSolver_->preconditioner().update();
115  resSmoother_->preconditioner().update();
116  wellSolver_->preconditioner().update();
117  }
118 
119  void updateForChangedWellStructure()
120  {
121  resSolver_->preconditioner().update();
122  resSmoother_->preconditioner().update();
123  initWellSolver();
124  resizeWellWorkVectors();
125  }
126 
127  bool hasPerfectUpdate() const override
128  {
129  return true;
130  }
131 
132 // System matrix block structure:
133 //
134 // [ A C ] [ x_res ] [ resRes ]
135 // S = [ B D ] [ x_well ] = [ wRes ]
136 //
137 // A = reservoir-reservoir (top-left)
138 // C = reservoir-well coupling (top-right)
139 // B = well-reservoir coupling (bottom-left)
140 // D = well-well (bottom-right)
141  void apply(SystemVector<Scalar>& v, const SystemVector<Scalar>& d) override
142  {
143  // Extract blocks using the agreed convention
144  const auto& A = S_[_0][_0];
145  const auto& C = S_[_0][_1];
146  const auto& B = S_[_1][_0];
147  const auto& D = S_[_1][_1];
148 
149  resRes_ = d[_0];
150  wRes_ = d[_1];
151  resSol_ = 0.0;
152  wSol_ = 0.0;
153 
154  // Stage 1: Reservoir CPR solve
155  {
156  Dune::InverseOperatorResult res_result;
157  dresSol_ = 0.0;
158  tmp_resRes_ = resRes_;
159  syncResVector(tmp_resRes_);
160  resSolver_->apply(dresSol_, tmp_resRes_, res_result);
161  resSol_ += dresSol_;
162  // resRes_ -= A * dresSol_
163  A.mmv(dresSol_, resRes_);
164  // wRes_ -= B * dresSol_
165  B.mmv(dresSol_, wRes_);
166  }
167 
168  // Stage 2: Well solve + reservoir system smoothing
169  {
170  Dune::InverseOperatorResult well_result;
171  dwSol_ = 0.0;
172  tmp_wRes_ = wRes_;
173  wellSolver_->apply(dwSol_, tmp_wRes_, well_result);
174  wSol_ += dwSol_;
175  // resRes_ -= C * dwSol_
176  C.mmv(dwSol_, resRes_);
177  // resRes_ -= D * dwSol_
178  D.mmv(dwSol_, wRes_);
179 
180  Dune::InverseOperatorResult res_result;
181  dresSol_ = 0.0;
182  tmp_resRes_ = resRes_;
183  syncResVector(tmp_resRes_);
184  resSmoother_->apply(dresSol_, tmp_resRes_, res_result);
185  resSol_ += dresSol_;
186  // wRes_ -= B * dresSol_
187  B.mmv(dresSol_, wRes_);
188  }
189 
190  // Stage 3: Final well solve
191  {
192  Dune::InverseOperatorResult well_result;
193  dwSol_ = 0.0;
194  tmp_wRes_ = wRes_;
195  wellSolver_->apply(dwSol_, tmp_wRes_, well_result);
196  wSol_ += dwSol_;
197  }
198 
199  syncResVector(resSol_);
200  v[_0] = resSol_;
201  v[_1] = wSol_;
202  }
203 
204 private:
205  const SystemMatrix<Scalar>& S_;
206  const ResComm* resComm_ = nullptr;
207  int pressureIndex_ = 0;
208  static constexpr int dummyWellPressureIndex = std::numeric_limits<int>::min();
209  Opm::PropertyTree wellprm_;
210 
211  std::unique_ptr<ResOp> rop_;
212  std::unique_ptr<WellOperator> wop_;
213  std::unique_ptr<ResFlexibleSolverType> resSolver_;
214  std::unique_ptr<ResFlexibleSolverType> resSmoother_;
215  std::unique_ptr<WellFlexibleSolverType> wellSolver_;
216 
217  WellVector<Scalar> wSol_;
218  ResVector<Scalar> resSol_;
219  ResVector<Scalar> dresSol_;
220  WellVector<Scalar> dwSol_;
221  ResVector<Scalar> tmp_resRes_;
222  WellVector<Scalar> tmp_wRes_;
223  ResVector<Scalar> resRes_;
224  WellVector<Scalar> wRes_;
225 
226  void syncResVector(ResVector<Scalar>& v)
227  {
228  if constexpr (isParallel) {
229  resComm_->copyOwnerToAll(v, v);
230  }
231  }
232 
233  void initWellSolver()
234  {
235  wop_ = std::make_unique<WellOperator>(S_[_1][_1]);
236  std::function<WellVector<Scalar>()> weightsCalculatorWell;
237  wellSolver_ = std::make_unique<WellFlexibleSolverType>(
238  *wop_, wellprm_, weightsCalculatorWell, dummyWellPressureIndex);
239  }
240 
241  void initSubSolvers(const Opm::PropertyTree& prm,
242  const std::function<ResVector<Scalar>()>& weightsCalculator)
243  {
244  auto resprm = prm.get_child("reservoir_solver");
245  auto resprmsmoother = prm.get_child("reservoir_smoother");
246  wellprm_ = prm.get_child("well_solver");
247 
248  if constexpr (isParallel) {
249  rop_ = std::make_unique<ResOp>(S_[_0][_0], *resComm_);
250  resSolver_ = std::make_unique<ResFlexibleSolverType>(
251  *rop_, *resComm_, resprm, weightsCalculator, pressureIndex_);
252  resSmoother_ = std::make_unique<ResFlexibleSolverType>(
253  *rop_, *resComm_, resprmsmoother, weightsCalculator, pressureIndex_);
254  } else {
255  rop_ = std::make_unique<ResOp>(S_[_0][_0]);
256  resSolver_ = std::make_unique<ResFlexibleSolverType>(
257  *rop_, resprm, weightsCalculator, pressureIndex_);
258  resSmoother_ = std::make_unique<ResFlexibleSolverType>(
259  *rop_, resprmsmoother, weightsCalculator, pressureIndex_);
260  }
261 
262  initWellSolver();
263  }
264 
265  void initWorkVectors()
266  {
267  resizeReservoirWorkVectors();
268  resizeWellWorkVectors();
269  }
270 
271  void resizeReservoirWorkVectors()
272  {
273  const auto numRes = S_[_0][_0].N();
274  resSol_.resize(numRes);
275  dresSol_.resize(numRes);
276  tmp_resRes_.resize(numRes);
277  resRes_.resize(numRes);
278  }
279 
280  void resizeWellWorkVectors()
281  {
282  const auto numWell = S_[_1][_1].N();
283  wSol_.resize(numWell);
284  dwSol_.resize(numWell);
285  tmp_wRes_.resize(numWell);
286  wRes_.resize(numWell);
287  }
288 };
289 
290 } // namespace Opm
291 
292 #endif // OPM_SYSTEMPRECONDITIONER_HEADER_INCLUDED
PropertyTree get_child(const std::string &key) const
Retrieve copy of sub tree rooted at node.
Definition: PropertyTree.cpp:82
Definition: SystemTypes.hpp:76
A solver class that encapsulates all needed objects for a linear solver (operator, scalar product, iterative solver and preconditioner) and sets them up based on runtime parameters, using the PreconditionerFactory for setting up preconditioners.
Definition: FlexibleSolver.hpp:43
Definition: SystemPreconditioner.hpp:56
Structs needed for tpfalinearizer and its gpuparams struct extracted to be defined in one place that ...
Definition: blackoilbioeffectsmodules.hh:45
Interface class adding the update() method to the preconditioner interface.
Definition: PreconditionerWithUpdate.hpp:33
Hierarchical collection of key/value pairs.
Definition: PropertyTree.hpp:38