WellMatrixMerger.hpp
Go to the documentation of this file.
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
23#include <opm/grid/utility/SparseTable.hpp>
24
25#include <algorithm>
26#include <cassert>
27#include <cstddef>
28#include <iterator>
29#include <vector>
30
31namespace 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())
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
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
90template<class Matrix>
91MatrixSparsityPattern
92captureMatrixSparsity(const Matrix& matrix)
93{
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
113template<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.
162template<typename Scalar>
164{
165public:
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
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
252private:
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
Definition: WellMatrixMerger.hpp:164
WellMatrixStructure buildStructure() const
Definition: WellMatrixMerger.hpp:214
bool hasSameStructure(const WellMatrixStructure &cachedStructure) const
Definition: WellMatrixMerger.hpp:184
WRMatrix< Scalar > BMatrix
Definition: WellMatrixMerger.hpp:166
void updateValues(BMatrix &mergedB, CMatrix &mergedC, DMatrix &mergedD) const
Definition: WellMatrixMerger.hpp:243
RWMatrix< Scalar > CMatrix
Definition: WellMatrixMerger.hpp:167
WellMatrixMerger(const std::size_t nResDofs, const std::vector< BMatrix > &bMatrices, const std::vector< CMatrix > &cMatrices, const std::vector< DMatrix > &dMatrices, const Opm::SparseTable< int > &wellCells)
Definition: WellMatrixMerger.hpp:170
WWMatrix< Scalar > DMatrix
Definition: WellMatrixMerger.hpp:168
void buildMatrices(BMatrix &mergedB, CMatrix &mergedC, DMatrix &mergedD) const
Definition: WellMatrixMerger.hpp:234
Definition: blackoilbioeffectsmodules.hh:45
Dune::BCRSMatrix< Dune::FieldMatrix< Scalar, numWellDofs, numWellDofs > > WWMatrix
Definition: SystemTypes.hpp:52
Dune::BCRSMatrix< Dune::FieldMatrix< Scalar, numResDofs, numWellDofs > > RWMatrix
Definition: SystemTypes.hpp:48
bool hasSameMatrixSparsity(const Matrix &matrix, const MatrixSparsityPattern &pattern)
Definition: WellMatrixMerger.hpp:113
MatrixSparsityPattern captureMatrixSparsity(const Matrix &matrix)
Definition: WellMatrixMerger.hpp:92
Dune::BCRSMatrix< Dune::FieldMatrix< Scalar, numWellDofs, numResDofs > > WRMatrix
Definition: SystemTypes.hpp:50
Definition: WellMatrixMerger.hpp:40
std::vector< int > columnIndices
Definition: WellMatrixMerger.hpp:44
bool operator!=(const MatrixSparsityPattern &other) const
Definition: WellMatrixMerger.hpp:54
std::size_t rows
Definition: WellMatrixMerger.hpp:41
bool operator==(const MatrixSparsityPattern &other) const
Definition: WellMatrixMerger.hpp:46
std::vector< std::size_t > rowOffsets
Definition: WellMatrixMerger.hpp:43
std::size_t cols
Definition: WellMatrixMerger.hpp:42
Definition: WellMatrixMerger.hpp:66
bool operator==(const WellMatrixStructure &other) const
Definition: WellMatrixMerger.hpp:74
bool operator!=(const WellMatrixStructure &other) const
Definition: WellMatrixMerger.hpp:84
std::vector< MatrixSparsityPattern > cPatterns
Definition: WellMatrixMerger.hpp:71
std::size_t numResDofs
Definition: WellMatrixMerger.hpp:67
Opm::SparseTable< int > wellCells
Definition: WellMatrixMerger.hpp:69
std::vector< MatrixSparsityPattern > dPatterns
Definition: WellMatrixMerger.hpp:72
std::vector< MatrixSparsityPattern > bPatterns
Definition: WellMatrixMerger.hpp:70
std::size_t totalWellBlocks
Definition: WellMatrixMerger.hpp:68