opm-simulators
WellMatrixMerger.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_WELLMATRIXMERGER_HEADER_INCLUDED
20 #define OPM_WELLMATRIXMERGER_HEADER_INCLUDED
21 
22 #include <opm/simulators/linalg/system/SystemTypes.hpp>
23 #include <opm/grid/utility/SparseTable.hpp>
24 
25 #include <algorithm>
26 #include <cassert>
27 #include <cstddef>
28 #include <iterator>
29 #include <vector>
30 
31 namespace Opm
32 {
33 
34 // Exact sparsity signature for one assembled well matrix.
35 //
36 // Matrix dimensions alone are not sufficient for MSW wells: the D block
37 // connectivity depends on the current segment topology, so we keep the full
38 // per-row column pattern and compare that directly.
40 {
41  std::size_t rows = 0;
42  std::size_t cols = 0;
43  std::vector<std::size_t> rowOffsets;
44  std::vector<int> columnIndices;
45 
46  bool operator==(const MatrixSparsityPattern& other) const
47  {
48  return rows == other.rows
49  && cols == other.cols
50  && rowOffsets == other.rowOffsets
51  && columnIndices == other.columnIndices;
52  }
53 
54  bool operator!=(const MatrixSparsityPattern& other) const
55  {
56  return !(*this == other);
57  }
58 };
59 
60 // Structural cache key for the merged well part of the system matrix.
61 // totalWellBlocks is the sum of all individual well D-matrix dimensions,
62 // i.e., the total number of well degrees of freedom. It is stored here
63 // so that the well-vector size and the structure-rebuild decision stay
64 // in the same place.
66 {
67  std::size_t numResDofs = 0;
68  std::size_t totalWellBlocks = 0; // Aggregated well DOFs (sum of D_i.N())
69  Opm::SparseTable<int> wellCells;
70  std::vector<MatrixSparsityPattern> bPatterns;
71  std::vector<MatrixSparsityPattern> cPatterns;
72  std::vector<MatrixSparsityPattern> dPatterns;
73 
74  bool operator==(const WellMatrixStructure& other) const
75  {
76  return numResDofs == other.numResDofs
77  && totalWellBlocks == other.totalWellBlocks
78  && wellCells == other.wellCells
79  && bPatterns == other.bPatterns
80  && cPatterns == other.cPatterns
81  && dPatterns == other.dPatterns;
82  }
83 
84  bool operator!=(const WellMatrixStructure& other) const
85  {
86  return !(*this == other);
87  }
88 };
89 
90 template<class Matrix>
92 captureMatrixSparsity(const Matrix& matrix)
93 {
94  MatrixSparsityPattern pattern;
95  pattern.rows = matrix.N();
96  pattern.cols = matrix.M();
97  pattern.rowOffsets.reserve(matrix.N() + 1);
98  if constexpr (requires { matrix.nonzeroes(); }) {
99  pattern.columnIndices.reserve(matrix.nonzeroes());
100  }
101  pattern.rowOffsets.push_back(0);
102 
103  for (std::size_t rowIdx = 0; rowIdx < matrix.N(); ++rowIdx) {
104  for (auto colIt = matrix[rowIdx].begin(); colIt != matrix[rowIdx].end(); ++colIt) {
105  pattern.columnIndices.push_back(colIt.index());
106  }
107  pattern.rowOffsets.push_back(pattern.columnIndices.size());
108  }
109 
110  return pattern;
111 }
112 
113 template<class Matrix> bool hasSameMatrixSparsity(const Matrix& matrix,
114  const MatrixSparsityPattern& pattern)
115 {
116  if (matrix.N() != pattern.rows || matrix.M() != pattern.cols) {
117  return false;
118  }
119 
120  if (pattern.rowOffsets.size() != pattern.rows + 1
121  || pattern.rowOffsets.empty()
122  || pattern.rowOffsets.front() != 0)
123  {
124  return false;
125  }
126 
127  std::size_t entryOffset = 0;
128  for (std::size_t rowIdx = 0; rowIdx < matrix.N(); ++rowIdx) {
129  if (pattern.rowOffsets[rowIdx] != entryOffset) {
130  return false;
131  }
132 
133  for (auto colIt = matrix[rowIdx].begin(); colIt != matrix[rowIdx].end(); ++colIt) {
134  if (entryOffset >= pattern.columnIndices.size()
135  || static_cast<std::size_t>(pattern.columnIndices[entryOffset]) != colIt.index())
136  {
137  return false;
138  }
139  ++entryOffset;
140  }
141 
142  if (pattern.rowOffsets[rowIdx + 1] != entryOffset) {
143  return false;
144  }
145  }
146 
147  return entryOffset == pattern.columnIndices.size();
148 }
149 
150 // WellMatrixMerger assembles the global coupled well part of
151 //
152 // [ A C ]
153 // [ B D ]
154 //
155 // from the per-well blocks B_j, C_j and D_j. It preserves each well's local
156 // sparsity pattern and only does two structural operations: concatenate the
157 // well blocks and remap perforation-related rows/columns through the list of
158 // perforated reservoir cells for each well.
159 
160 // Give each block a distinctive value pattern so it is easy to see where it
161 // ended up after merging.
162 template<typename Scalar>
164 {
165 public:
166  using BMatrix = WRMatrix<Scalar>;
167  using CMatrix = RWMatrix<Scalar>;
168  using DMatrix = WWMatrix<Scalar>;
169 
170  WellMatrixMerger(const std::size_t nResDofs,
171  const std::vector<BMatrix>& bMatrices,
172  const std::vector<CMatrix>& cMatrices,
173  const std::vector<DMatrix>& dMatrices,
174  const Opm::SparseTable<int>& wellCells)
175  : numResDofs_(nResDofs)
176  , bMatrices_(bMatrices)
177  , cMatrices_(cMatrices)
178  , dMatrices_(dMatrices)
179  , wellCells_(wellCells)
180  {
181  assert(inputsAreValid());
182  }
183 
184  bool hasSameStructure(const WellMatrixStructure& cachedStructure) const
185  {
186  const auto numWells = bMatrices_.size();
187  if (cachedStructure.numResDofs != numResDofs_
188  || static_cast<std::size_t>(cachedStructure.wellCells.size()) != numWells
189  || cachedStructure.bPatterns.size() != numWells
190  || cachedStructure.cPatterns.size() != numWells
191  || cachedStructure.dPatterns.size() != numWells)
192  {
193  return false;
194  }
195 
196  std::size_t totalWellDofs = 0;
197  for (std::size_t well = 0; well < numWells; ++well) {
198  totalWellDofs += dMatrices_[well].N();
199 
200  const auto& cachedRow = cachedStructure.wellCells[well];
201  const auto& currentRow = wellCells_[well];
202  if (!std::ranges::equal(cachedRow.begin(), cachedRow.end(), currentRow.begin(), currentRow.end())
203  || !hasSameMatrixSparsity(bMatrices_[well], cachedStructure.bPatterns[well])
204  || !hasSameMatrixSparsity(cMatrices_[well], cachedStructure.cPatterns[well])
205  || !hasSameMatrixSparsity(dMatrices_[well], cachedStructure.dPatterns[well]))
206  {
207  return false;
208  }
209  }
210 
211  return cachedStructure.totalWellBlocks == totalWellDofs;
212  }
213 
214  WellMatrixStructure buildStructure() const
215  {
216  WellMatrixStructure structure;
217  structure.numResDofs = numResDofs_;
218  structure.totalWellBlocks = 0;
219  structure.wellCells = wellCells_;
220  structure.bPatterns.reserve(bMatrices_.size());
221  structure.cPatterns.reserve(cMatrices_.size());
222  structure.dPatterns.reserve(dMatrices_.size());
223 
224  for (std::size_t well = 0; well < bMatrices_.size(); ++well) {
225  structure.bPatterns.push_back(captureMatrixSparsity(bMatrices_[well]));
226  structure.cPatterns.push_back(captureMatrixSparsity(cMatrices_[well]));
227  structure.dPatterns.push_back(captureMatrixSparsity(dMatrices_[well]));
228  structure.totalWellBlocks += dMatrices_[well].N();
229  }
230 
231  return structure;
232  }
233 
234  void buildMatrices(BMatrix& mergedB,
235  CMatrix& mergedC,
236  DMatrix& mergedD) const
237  {
238  mergeBMatrix(mergedB);
239  mergeCMatrix(mergedC);
240  mergeDMatrix(mergedD);
241  }
242 
243  void updateValues(BMatrix& mergedB,
244  CMatrix& mergedC,
245  DMatrix& mergedD) const
246  {
247  fillBValues(mergedB);
248  fillCValues(mergedC);
249  fillDValues(mergedD);
250  }
251 
252 private:
253  bool inputsAreValid() const
254  {
255  const auto numWells = bMatrices_.size();
256 
257  if (cMatrices_.size() != numWells) {
258  return false;
259  }
260 
261  if (dMatrices_.size() != numWells) {
262  return false;
263  }
264 
265  if (static_cast<std::size_t>(wellCells_.size()) != numWells) {
266  return false;
267  }
268 
269  for (std::size_t well = 0; well < numWells; ++well) {
270  const auto& B = bMatrices_[well];
271  const auto& C = cMatrices_[well];
272  const auto& D = dMatrices_[well];
273  const auto& cells = wellCells_[well];
274 
275  if (cells.size() != B.M()) {
276  return false;
277  }
278 
279  if (cells.size() != C.N()) {
280  return false;
281  }
282 
283  if (B.N() != C.M()) {
284  return false;
285  }
286 
287  if (B.N() != D.N()) {
288  return false;
289  }
290 
291  if (C.M() != D.M()) {
292  return false;
293  }
294 
295  if (D.N() != D.M()) {
296  return false;
297  }
298  }
299 
300  return true;
301  }
302 
303  template<class Matrix>
304  static void initializeEmptyMatrix(Matrix& matrix, std::size_t rows, std::size_t cols)
305  {
306  matrix.setSize(rows, cols);
307  matrix.setBuildMode(Matrix::random);
308  for (std::size_t row = 0; row < rows; ++row) {
309  matrix.setrowsize(row, 0);
310  }
311  matrix.endrowsizes();
312  matrix.endindices();
313  }
314 
315  template<class MatrixVectorT, class DimensionFn>
316  static std::size_t sumMatrixDimension(const MatrixVectorT& matrices, DimensionFn&& dimension)
317  {
318  std::size_t total = 0;
319  for (const auto& matrix : matrices) {
320  total += dimension(matrix);
321  }
322  return total;
323  }
324 
325  template<class Row>
326  static std::size_t countRowEntries(const Row& row)
327  {
328  return static_cast<std::size_t>(std::distance(row.begin(), row.end()));
329  }
330 
331  template<class Row, class Matrix, class ColumnMapper>
332  static void assignRowValues(const Row& row,
333  Matrix& mergedMatrix,
334  std::size_t mergedRow,
335  ColumnMapper&& mapColumn)
336  {
337  for (auto colIt = row.begin(); colIt != row.end(); ++colIt) {
338  mergedMatrix[mergedRow][mapColumn(colIt.index())] = *colIt;
339  }
340  }
341 
342  void fillBValues(BMatrix& mergedMatrix) const
343  {
344  std::size_t rowOffset = 0;
345  for (std::size_t well = 0; well < bMatrices_.size(); ++well) {
346  const auto& matrix = bMatrices_[well];
347  const auto& cells = wellCells_[well];
348  for (std::size_t row = 0; row < matrix.N(); ++row) {
349  assignRowValues(matrix[row], mergedMatrix, rowOffset + row,
350  [&cells](auto localColumn) {
351  assert(cells[localColumn] >= 0);
352  return static_cast<std::size_t>(cells[localColumn]);
353  });
354  }
355  rowOffset += matrix.N();
356  }
357  }
358 
359  void fillCValues(CMatrix& mergedMatrix) const
360  {
361  std::size_t colOffset = 0;
362  for (std::size_t well = 0; well < cMatrices_.size(); ++well) {
363  const auto& matrix = cMatrices_[well];
364  const auto& cells = wellCells_[well];
365  for (std::size_t row = 0; row < matrix.N(); ++row) {
366  assert(cells[row] >= 0);
367  const auto cell = static_cast<std::size_t>(cells[row]);
368  assignRowValues(matrix[row], mergedMatrix, cell,
369  [colOffset](auto localColumn) { return colOffset + localColumn; });
370  }
371  colOffset += matrix.M();
372  }
373  }
374 
375  void fillDValues(DMatrix& mergedMatrix) const
376  {
377  std::size_t rowOffset = 0;
378  for (const auto& matrix : dMatrices_) {
379  for (std::size_t row = 0; row < matrix.N(); ++row) {
380  assignRowValues(matrix[row], mergedMatrix, rowOffset + row,
381  [rowOffset](auto localColumn) { return rowOffset + localColumn; });
382  }
383  rowOffset += matrix.N();
384  }
385  }
386 
387  void mergeBMatrix(BMatrix& mergedMatrix) const
388  {
389  if (bMatrices_.empty()) {
390  initializeEmptyMatrix(mergedMatrix, 0, numResDofs_);
391  return;
392  }
393 
394  const auto totalRows = sumMatrixDimension(bMatrices_, [](const auto& matrix) { return matrix.N(); });
395 
396  mergedMatrix.setSize(totalRows, numResDofs_);
397  mergedMatrix.setBuildMode(BMatrix::random);
398 
399  std::size_t rowOffset = 0;
400  for (const auto& matrix : bMatrices_) {
401  for (std::size_t row = 0; row < matrix.N(); ++row) {
402  mergedMatrix.setrowsize(rowOffset + row, countRowEntries(matrix[row]));
403  }
404  rowOffset += matrix.N();
405  }
406  mergedMatrix.endrowsizes();
407 
408  rowOffset = 0;
409  for (std::size_t well = 0; well < bMatrices_.size(); ++well) {
410  const auto& matrix = bMatrices_[well];
411  const auto& cells = wellCells_[well];
412  for (std::size_t row = 0; row < matrix.N(); ++row) {
413  for (auto colIt = matrix[row].begin(); colIt != matrix[row].end(); ++colIt) {
414  assert(cells[colIt.index()] >= 0);
415  mergedMatrix.addindex(rowOffset + row,
416  static_cast<std::size_t>(cells[colIt.index()]));
417  }
418  }
419  rowOffset += matrix.N();
420  }
421  mergedMatrix.endindices();
422 
423  fillBValues(mergedMatrix);
424  }
425 
426  void mergeCMatrix(CMatrix& mergedMatrix) const
427  {
428  if (cMatrices_.empty()) {
429  initializeEmptyMatrix(mergedMatrix, numResDofs_, 0);
430  return;
431  }
432 
433  const auto totalCols = sumMatrixDimension(cMatrices_, [](const auto& matrix) { return matrix.M(); });
434 
435  mergedMatrix.setSize(numResDofs_, totalCols);
436  mergedMatrix.setBuildMode(CMatrix::random);
437 
438  std::vector<std::size_t> rowSizes(numResDofs_, 0);
439  for (std::size_t well = 0; well < cMatrices_.size(); ++well) {
440  const auto& matrix = cMatrices_[well];
441  const auto& cells = wellCells_[well];
442  for (std::size_t rowIdx = 0; rowIdx < matrix.N(); ++rowIdx) {
443  assert(cells[rowIdx] >= 0);
444  rowSizes[static_cast<std::size_t>(cells[rowIdx])] += countRowEntries(matrix[rowIdx]);
445  }
446  }
447  for (std::size_t row = 0; row < numResDofs_; ++row) {
448  mergedMatrix.setrowsize(row, rowSizes[row]);
449  }
450  mergedMatrix.endrowsizes();
451 
452  std::size_t colOffset = 0;
453  for (std::size_t well = 0; well < cMatrices_.size(); ++well) {
454  const auto& matrix = cMatrices_[well];
455  const auto& cells = wellCells_[well];
456  for (std::size_t rowIdx = 0; rowIdx < matrix.N(); ++rowIdx) {
457  assert(cells[rowIdx] >= 0);
458  const auto cell = static_cast<std::size_t>(cells[rowIdx]);
459  for (auto colIt = matrix[rowIdx].begin(); colIt != matrix[rowIdx].end(); ++colIt) {
460  mergedMatrix.addindex(cell, colOffset + colIt.index());
461  }
462  }
463  colOffset += matrix.M();
464  }
465  mergedMatrix.endindices();
466 
467  fillCValues(mergedMatrix);
468  }
469 
470  void mergeDMatrix(DMatrix& mergedMatrix) const
471  {
472  if (dMatrices_.empty()) {
473  initializeEmptyMatrix(mergedMatrix, 0, 0);
474  return;
475  }
476 
477  const auto totalSize = sumMatrixDimension(dMatrices_, [](const auto& matrix) { return matrix.N(); });
478 
479  mergedMatrix.setSize(totalSize, totalSize);
480  mergedMatrix.setBuildMode(DMatrix::random);
481 
482  std::size_t rowOffset = 0;
483  for (const auto& matrix : dMatrices_) {
484  for (std::size_t rowIdx = 0; rowIdx < matrix.N(); ++rowIdx) {
485  mergedMatrix.setrowsize(rowOffset + rowIdx, countRowEntries(matrix[rowIdx]));
486  }
487  rowOffset += matrix.N();
488  }
489  mergedMatrix.endrowsizes();
490 
491  rowOffset = 0;
492  for (const auto& matrix : dMatrices_) {
493  for (std::size_t rowIdx = 0; rowIdx < matrix.N(); ++rowIdx) {
494  for (auto colIt = matrix[rowIdx].begin(); colIt != matrix[rowIdx].end(); ++colIt) {
495  mergedMatrix.addindex(rowOffset + rowIdx, rowOffset + colIt.index());
496  }
497  }
498  rowOffset += matrix.N();
499  }
500  mergedMatrix.endindices();
501 
502  fillDValues(mergedMatrix);
503  }
504 
505  std::size_t numResDofs_;
506  const std::vector<BMatrix>& bMatrices_;
507  const std::vector<CMatrix>& cMatrices_;
508  const std::vector<DMatrix>& dMatrices_;
509  const Opm::SparseTable<int>& wellCells_;
510 };
511 
512 } // namespace Opm
513 
514 #endif // OPM_WELLMATRIXMERGER_HEADER_INCLUDED
Structs needed for tpfalinearizer and its gpuparams struct extracted to be defined in one place that ...
Definition: blackoilbioeffectsmodules.hh:45
Definition: WellMatrixMerger.hpp:39
Definition: WellMatrixMerger.hpp:163
Definition: WellMatrixMerger.hpp:65