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 validateInputs();
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 void validateInputs() const
254 {
255 const auto numWells = bMatrices_.size();
256 assert(cMatrices_.size() == numWells);
257 assert(dMatrices_.size() == numWells);
258 assert(static_cast<std::size_t>(wellCells_.size()) == numWells);
259
260 for (std::size_t well = 0; well < numWells; ++well) {
261 const auto& B = bMatrices_[well];
262 const auto& C = cMatrices_[well];
263 const auto& D = dMatrices_[well];
264 const auto& cells = wellCells_[well];
265
266 assert(cells.size() == B.M());
267 assert(cells.size() == C.N());
268 assert(B.N() == C.M());
269 assert(B.N() == D.N());
270 assert(C.M() == D.M());
271 assert(D.N() == D.M());
272 }
273 }
274
275 template<class Matrix>
276 static void initializeEmptyMatrix(Matrix& matrix, std::size_t rows, std::size_t cols)
277 {
278 matrix.setSize(rows, cols);
279 matrix.setBuildMode(Matrix::random);
280 for (std::size_t row = 0; row < rows; ++row) {
281 matrix.setrowsize(row, 0);
282 }
283 matrix.endrowsizes();
284 matrix.endindices();
285 }
286
287 template<class MatrixVectorT, class DimensionFn>
288 static std::size_t sumMatrixDimension(const MatrixVectorT& matrices, DimensionFn&& dimension)
289 {
290 std::size_t total = 0;
291 for (const auto& matrix : matrices) {
292 total += dimension(matrix);
293 }
294 return total;
295 }
296
297 template<class Row>
298 static std::size_t countRowEntries(const Row& row)
299 {
300 return static_cast<std::size_t>(std::distance(row.begin(), row.end()));
301 }
302
303 template<class Row, class Matrix, class ColumnMapper>
304 static void assignRowValues(const Row& row,
305 Matrix& mergedMatrix,
306 std::size_t mergedRow,
307 ColumnMapper&& mapColumn)
308 {
309 for (auto colIt = row.begin(); colIt != row.end(); ++colIt) {
310 mergedMatrix[mergedRow][mapColumn(colIt.index())] = *colIt;
311 }
312 }
313
314 void fillBValues(BMatrix& mergedMatrix) const
315 {
316 std::size_t rowOffset = 0;
317 for (std::size_t well = 0; well < bMatrices_.size(); ++well) {
318 const auto& matrix = bMatrices_[well];
319 const auto& cells = wellCells_[well];
320 for (std::size_t row = 0; row < matrix.N(); ++row) {
321 assignRowValues(matrix[row], mergedMatrix, rowOffset + row,
322 [&cells](auto localColumn) {
323 assert(cells[localColumn] >= 0);
324 return static_cast<std::size_t>(cells[localColumn]);
325 });
326 }
327 rowOffset += matrix.N();
328 }
329 }
330
331 void fillCValues(CMatrix& mergedMatrix) const
332 {
333 std::size_t colOffset = 0;
334 for (std::size_t well = 0; well < cMatrices_.size(); ++well) {
335 const auto& matrix = cMatrices_[well];
336 const auto& cells = wellCells_[well];
337 for (std::size_t row = 0; row < matrix.N(); ++row) {
338 assert(cells[row] >= 0);
339 const auto cell = static_cast<std::size_t>(cells[row]);
340 assignRowValues(matrix[row], mergedMatrix, cell,
341 [colOffset](auto localColumn) { return colOffset + localColumn; });
342 }
343 colOffset += matrix.M();
344 }
345 }
346
347 void fillDValues(DMatrix& mergedMatrix) const
348 {
349 std::size_t rowOffset = 0;
350 for (const auto& matrix : dMatrices_) {
351 for (std::size_t row = 0; row < matrix.N(); ++row) {
352 assignRowValues(matrix[row], mergedMatrix, rowOffset + row,
353 [rowOffset](auto localColumn) { return rowOffset + localColumn; });
354 }
355 rowOffset += matrix.N();
356 }
357 }
358
359 void mergeBMatrix(BMatrix& mergedMatrix) const
360 {
361 if (bMatrices_.empty()) {
362 initializeEmptyMatrix(mergedMatrix, 0, numResDofs_);
363 return;
364 }
365
366 const auto totalRows = sumMatrixDimension(bMatrices_, [](const auto& matrix) { return matrix.N(); });
367
368 mergedMatrix.setSize(totalRows, numResDofs_);
369 mergedMatrix.setBuildMode(BMatrix::random);
370
371 std::size_t rowOffset = 0;
372 for (const auto& matrix : bMatrices_) {
373 for (std::size_t row = 0; row < matrix.N(); ++row) {
374 mergedMatrix.setrowsize(rowOffset + row, countRowEntries(matrix[row]));
375 }
376 rowOffset += matrix.N();
377 }
378 mergedMatrix.endrowsizes();
379
380 rowOffset = 0;
381 for (std::size_t well = 0; well < bMatrices_.size(); ++well) {
382 const auto& matrix = bMatrices_[well];
383 const auto& cells = wellCells_[well];
384 for (std::size_t row = 0; row < matrix.N(); ++row) {
385 for (auto colIt = matrix[row].begin(); colIt != matrix[row].end(); ++colIt) {
386 assert(cells[colIt.index()] >= 0);
387 mergedMatrix.addindex(rowOffset + row,
388 static_cast<std::size_t>(cells[colIt.index()]));
389 }
390 }
391 rowOffset += matrix.N();
392 }
393 mergedMatrix.endindices();
394
395 fillBValues(mergedMatrix);
396 }
397
398 void mergeCMatrix(CMatrix& mergedMatrix) const
399 {
400 if (cMatrices_.empty()) {
401 initializeEmptyMatrix(mergedMatrix, numResDofs_, 0);
402 return;
403 }
404
405 const auto totalCols = sumMatrixDimension(cMatrices_, [](const auto& matrix) { return matrix.M(); });
406
407 mergedMatrix.setSize(numResDofs_, totalCols);
408 mergedMatrix.setBuildMode(CMatrix::random);
409
410 std::vector<std::size_t> rowSizes(numResDofs_, 0);
411 for (std::size_t well = 0; well < cMatrices_.size(); ++well) {
412 const auto& matrix = cMatrices_[well];
413 const auto& cells = wellCells_[well];
414 for (std::size_t rowIdx = 0; rowIdx < matrix.N(); ++rowIdx) {
415 assert(cells[rowIdx] >= 0);
416 rowSizes[static_cast<std::size_t>(cells[rowIdx])] += countRowEntries(matrix[rowIdx]);
417 }
418 }
419 for (std::size_t row = 0; row < numResDofs_; ++row) {
420 mergedMatrix.setrowsize(row, rowSizes[row]);
421 }
422 mergedMatrix.endrowsizes();
423
424 std::size_t colOffset = 0;
425 for (std::size_t well = 0; well < cMatrices_.size(); ++well) {
426 const auto& matrix = cMatrices_[well];
427 const auto& cells = wellCells_[well];
428 for (std::size_t rowIdx = 0; rowIdx < matrix.N(); ++rowIdx) {
429 assert(cells[rowIdx] >= 0);
430 const auto cell = static_cast<std::size_t>(cells[rowIdx]);
431 for (auto colIt = matrix[rowIdx].begin(); colIt != matrix[rowIdx].end(); ++colIt) {
432 mergedMatrix.addindex(cell, colOffset + colIt.index());
433 }
434 }
435 colOffset += matrix.M();
436 }
437 mergedMatrix.endindices();
438
439 fillCValues(mergedMatrix);
440 }
441
442 void mergeDMatrix(DMatrix& mergedMatrix) const
443 {
444 if (dMatrices_.empty()) {
445 initializeEmptyMatrix(mergedMatrix, 0, 0);
446 return;
447 }
448
449 const auto totalSize = sumMatrixDimension(dMatrices_, [](const auto& matrix) { return matrix.N(); });
450
451 mergedMatrix.setSize(totalSize, totalSize);
452 mergedMatrix.setBuildMode(DMatrix::random);
453
454 std::size_t rowOffset = 0;
455 for (const auto& matrix : dMatrices_) {
456 for (std::size_t rowIdx = 0; rowIdx < matrix.N(); ++rowIdx) {
457 mergedMatrix.setrowsize(rowOffset + rowIdx, countRowEntries(matrix[rowIdx]));
458 }
459 rowOffset += matrix.N();
460 }
461 mergedMatrix.endrowsizes();
462
463 rowOffset = 0;
464 for (const auto& matrix : dMatrices_) {
465 for (std::size_t rowIdx = 0; rowIdx < matrix.N(); ++rowIdx) {
466 for (auto colIt = matrix[rowIdx].begin(); colIt != matrix[rowIdx].end(); ++colIt) {
467 mergedMatrix.addindex(rowOffset + rowIdx, rowOffset + colIt.index());
468 }
469 }
470 rowOffset += matrix.N();
471 }
472 mergedMatrix.endindices();
473
474 fillDValues(mergedMatrix);
475 }
476
477 std::size_t numResDofs_;
478 const std::vector<BMatrix>& bMatrices_;
479 const std::vector<CMatrix>& cMatrices_;
480 const std::vector<DMatrix>& dMatrices_;
481 const Opm::SparseTable<int>& wellCells_;
482};
483
484} // namespace Opm
485
486#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