20#ifndef OPM_UPWIND_TRANSPORT_SOLVER_HEADER_INCLUDED
21#define OPM_UPWIND_TRANSPORT_SOLVER_HEADER_INCLUDED
23#include <opm/common/utility/SparseTable.hpp>
105 template <
class Scalar>
107 std::vector<Scalar>& b,
111 for (std::size_t col = 0; col < m; ++col) {
113 std::size_t pivot = col;
114 for (std::size_t row = col + 1; row < m; ++row) {
115 if (std::abs(a[row * m + col]) > std::abs(a[pivot * m + col])) {
119 if (a[pivot * m + col] == Scalar {0}) {
120 throw std::runtime_error(
"Opm::solveReordered: singular matrix");
123 for (std::size_t k = 0; k < m; ++k) {
124 std::swap(a[pivot * m + k], a[col * m + k]);
126 for (std::size_t k = 0; k < numRhs; ++k) {
127 std::swap(b[pivot * numRhs + k], b[col * numRhs + k]);
130 for (std::size_t row = col + 1; row < m; ++row) {
131 const Scalar factor = a[row * m + col] / a[col * m + col];
132 if (factor == Scalar {0}) {
135 for (std::size_t k = col; k < m; ++k) {
136 a[row * m + k] -= factor * a[col * m + k];
138 for (std::size_t k = 0; k < numRhs; ++k) {
139 b[row * numRhs + k] -= factor * b[col * numRhs + k];
143 for (std::size_t col = m; col-- > 0;) {
144 for (std::size_t k = 0; k < numRhs; ++k) {
145 Scalar sum = b[col * numRhs + k];
146 for (std::size_t j = col + 1; j < m; ++j) {
147 sum -= a[col * m + j] * b[j * numRhs + k];
149 b[col * numRhs + k] = sum / a[col * m + col];
160 template <
class Block>
163 for (std::size_t a = 0; a < Block::rows; ++a) {
164 for (std::size_t b = 0; b < Block::cols; ++b) {
165 if (block[a][b] != 0) {
178 template <
class Block>
181 for (std::size_t a = 0; a < Block::rows; ++a) {
182 for (std::size_t b = 0; b < Block::cols; ++b) {
183 if (!std::isfinite(block[a][b])) {
197 template <
class Matrix,
class Function>
200 for (
auto row = matrix.begin(); row != matrix.end(); ++row) {
201 for (
auto entry = row->begin(); entry != row->end(); ++entry) {
203 f(row.index(), entry.index(), *entry);
221 template <
class Matrix>
224 using Block =
typename Matrix::block_type;
225 static_assert(Block::rows == Block::cols,
"The matrix blocks must be square");
228 using Scalar =
typename Block::field_type;
240 if (matrix.N() != matrix.M()) {
241 throw std::invalid_argument(
"Opm::solveReordered: the matrix must be square");
243 diagonal_.reserve(matrix.N());
244 for (std::size_t r = 0; r < matrix.N(); ++r) {
245 const auto entry = matrix[r].find(r);
246 if (entry == matrix[r].end()) {
247 throw std::invalid_argument(
"Opm::solveReordered: row " +
std::to_string(r)
248 +
" has no diagonal block");
253 throw std::runtime_error(
"Opm::solveReordered: NaN or infinite entry in the "
256 diagonal_.push_back(&*entry);
259 std::vector<int> rowStart(matrix.N() + 1, 0);
260 std::vector<int> columns;
261 std::vector<const Block*> blocks;
263 matrix, [&](std::size_t r, std::size_t c,
const Block& block) {
265 throw std::runtime_error(
"Opm::solveReordered: NaN or infinite entry in "
270 columns.push_back(
static_cast<int>(c));
271 blocks.push_back(&block);
273 for (std::size_t r = 0; r < matrix.N(); ++r) {
274 rowStart[r + 1] += rowStart[r];
276 graph_ = SparseTable<int>(std::move(columns), std::vector<int>(rowStart));
277 couplings_ = SparseTable<const Block*>(std::move(blocks), std::move(rowStart));
285 return diagonal_.size();
293 const SparseTable<int>&
graph()
const
304 return *diagonal_[c];
312 template <
class Function>
315 auto block = couplings_[
static_cast<int>(c)].begin();
316 for (
const int u : graph_[
static_cast<int>(c)]) {
317 f(
static_cast<std::size_t
>(u), **block++);
322 std::vector<const Block*> diagonal_;
323 SparseTable<int> graph_;
324 SparseTable<const Block*> couplings_;
333 template <
class Coefficients,
class Vectors>
336 using Scalar =
typename Coefficients::Scalar;
337 static constexpr std::size_t N = Coefficients::blockSize;
353 const std::string& name)
354 : coefficients_(coefficients)
357 , settings_(settings)
359 , numCells_(coefficients.numCells())
360 , numVectors_(rhs.size())
361 , localIndex_(numCells_, -1)
362 , cellSolution_(N * numVectors_)
363 , change_(N * numVectors_)
364 , scale_(N * numVectors_)
379 const auto components =
tarjan(coefficients_.graph());
382 for (
const auto cells : components.components()) {
383 const int size =
static_cast<int>(cells.size());
390 solveDirectly_(cells);
392 const int sweeps = solveByGaussSeidel_(cells);
404 void validate_()
const
406 if (x_.size() != numVectors_) {
407 throw std::invalid_argument(name_ +
": sizes do not match");
409 for (std::size_t q = 0; q < numVectors_; ++q) {
410 if (
static_cast<std::size_t
>(rhs_[q].size()) != numCells_
411 ||
static_cast<std::size_t
>(x_[q].size()) != numCells_) {
412 throw std::invalid_argument(name_ +
": wrong vector length");
426 void checkFinite_(Scalar value,
428 std::span<const int> cells,
429 bool useInLoop)
const
431 if (!std::isfinite(value)) [[unlikely]] {
432 throw std::runtime_error(nonFiniteMessage_(q, cells, useInLoop));
445 std::string nonFiniteMessage_(std::size_t q,
446 std::span<const int> cells,
447 bool useInLoop)
const
450 std::vector<int> sorted(cells.begin(), cells.end());
451 std::ranges::sort(sorted);
452 const auto inCells = [&sorted](std::size_t u) {
453 return std::ranges::binary_search(sorted,
static_cast<int>(u));
456 for (
const int cell : cells) {
457 const auto c =
static_cast<std::size_t
>(cell);
458 for (std::size_t a = 0; a < N && cause.empty(); ++a) {
459 if (!std::isfinite(rhs_[q][c][a])) {
463 coefficients_.forEachCoupling(c, [&](std::size_t u,
const auto&) {
464 if (!cause.empty()) {
467 if (useInLoop || !inCells(u)) {
468 for (std::size_t b = 0; b < N; ++b) {
469 if (!std::isfinite(x_[q][u][b])) {
471 + (inCells(u) ?
", an initial value of Gauss-Seidel" :
"");
477 if (!cause.empty()) {
482 cause =
"an overflow in the solve";
484 return name_ +
": NaN or infinite solution for right-hand side " +
std::to_string(q)
485 +
" in a component with row " +
std::to_string(cells.front()) +
", caused by "
497 std::array<Scalar, N> rightHandSide_(std::size_t q, std::size_t c)
const
499 std::array<Scalar, N> sum;
500 for (std::size_t a = 0; a < N; ++a) {
501 sum[a] = rhs_[q][c][a];
503 coefficients_.forEachCoupling(c, [&](std::size_t u,
const auto& block) {
504 if (localIndex_[u] < 0) {
505 for (std::size_t a = 0; a < N; ++a) {
506 for (std::size_t b = 0; b < N; ++b) {
507 sum[a] -= block[a][b] * x_[q][u][b];
521 void solveDiagonal_(std::size_t c)
523 if constexpr (N == 1) {
525 const Scalar diagonal = coefficients_.diagonal(c)[0][0];
526 if (diagonal == Scalar {0}) {
527 throw std::runtime_error(name_ +
": singular diagonal in row "
530 for (std::size_t q = 0; q < numVectors_; ++q) {
531 cellSolution_[q] = rightHandSide_(q, c)[0] / diagonal;
534 matrix_.resize(N * N);
535 const auto& diagonal = coefficients_.diagonal(c);
536 for (std::size_t a = 0; a < N; ++a) {
537 for (std::size_t b = 0; b < N; ++b) {
538 matrix_[a * N + b] = diagonal[a][b];
541 for (std::size_t q = 0; q < numVectors_; ++q) {
542 const auto sum = rightHandSide_(q, c);
543 for (std::size_t a = 0; a < N; ++a) {
544 cellSolution_[a * numVectors_ + q] = sum[a];
549 }
catch (
const std::runtime_error&) {
550 throw std::runtime_error(name_ +
": singular diagonal block in row "
560 void solveCell_(std::span<const int> cells)
562 const auto c =
static_cast<std::size_t
>(cells[0]);
564 for (std::size_t q = 0; q < numVectors_; ++q) {
565 for (std::size_t a = 0; a < N; ++a) {
566 const Scalar value = cellSolution_[a * numVectors_ + q];
567 checkFinite_(value, q, cells,
false);
577 void solveDirectly_(std::span<const int> cells)
579 const std::size_t m = cells.size();
580 const std::size_t size = m * N;
581 matrix_.assign(size * size, Scalar {0});
582 solution_.assign(size * numVectors_, Scalar {0});
583 for (std::size_t i = 0; i < m; ++i) {
584 localIndex_[cells[i]] =
static_cast<int>(i);
586 for (std::size_t i = 0; i < m; ++i) {
587 const auto c =
static_cast<std::size_t
>(cells[i]);
588 const auto& diagonal = coefficients_.diagonal(c);
589 for (std::size_t a = 0; a < N; ++a) {
590 for (std::size_t b = 0; b < N; ++b) {
591 matrix_[(i * N + a) * size + i * N + b] = diagonal[a][b];
594 coefficients_.forEachCoupling(c, [&](std::size_t u,
const auto& block) {
595 if (localIndex_[u] >= 0) {
596 const auto j =
static_cast<std::size_t
>(localIndex_[u]);
597 for (std::size_t a = 0; a < N; ++a) {
598 for (std::size_t b = 0; b < N; ++b) {
599 matrix_[(i * N + a) * size + j * N + b] += block[a][b];
604 for (std::size_t q = 0; q < numVectors_; ++q) {
606 const auto sum = rightHandSide_(q, c);
607 for (std::size_t a = 0; a < N; ++a) {
608 solution_[(i * N + a) * numVectors_ + q] = sum[a];
612 for (std::size_t i = 0; i < m; ++i) {
613 localIndex_[cells[i]] = -1;
617 }
catch (
const std::runtime_error&) {
618 throw std::runtime_error(name_ +
": singular matrix for a flow loop of "
621 for (std::size_t i = 0; i < m; ++i) {
622 for (std::size_t q = 0; q < numVectors_; ++q) {
623 for (std::size_t a = 0; a < N; ++a) {
624 const Scalar value = solution_[(i * N + a) * numVectors_ + q];
625 checkFinite_(value, q, cells,
false);
626 x_[q][
static_cast<std::size_t
>(cells[i])][a] = value;
638 int solveByGaussSeidel_(std::span<const int> cells)
640 for (
int sweep = 1; sweep <= settings_.
maxIterations; ++sweep) {
641 std::fill(change_.begin(), change_.end(), Scalar {0});
642 std::fill(scale_.begin(), scale_.end(), Scalar {0});
643 for (
const int cell : cells) {
644 const auto c =
static_cast<std::size_t
>(cell);
646 for (std::size_t q = 0; q < numVectors_; ++q) {
647 for (std::size_t a = 0; a < N; ++a) {
648 auto& value = x_[q][c][a];
649 const Scalar updated = cellSolution_[a * numVectors_ + q];
650 checkFinite_(updated, q, cells,
true);
651 const std::size_t k = q * N + a;
652 change_[k] = std::max(change_[k], std::abs(updated - value));
653 scale_[k] = std::max(scale_[k], std::abs(updated));
660 bool converged =
true;
661 for (std::size_t k = 0; k < change_.size(); ++k) {
662 converged = converged
670 throw std::runtime_error(name_ +
": no convergence after "
676 const Coefficients& coefficients_;
679 const UpwindTransportSettings& settings_;
681 std::size_t numCells_;
682 std::size_t numVectors_;
685 std::vector<int> localIndex_;
686 std::vector<Scalar> matrix_;
687 std::vector<Scalar> solution_;
688 std::vector<Scalar> cellSolution_;
689 std::vector<Scalar> change_;
690 std::vector<Scalar> scale_;
714template <
class Matrix,
class Vectors>
715UpwindTransportStatistics
721 const detail::MatrixCoefficients<Matrix> coefficients(matrix);
722 return detail::ReorderedSystem<detail::MatrixCoefficients<Matrix>, Vectors>(
723 coefficients, rhs, x, settings,
"Opm::solveReordered")
The coefficients of solveReordered(), from a sparse block matrix.
Definition: UpwindTransportSolver.hpp:223
typename Block::field_type Scalar
Definition: UpwindTransportSolver.hpp:228
MatrixCoefficients(const Matrix &matrix)
Collect the diagonal blocks and the nonzero couplings of a matrix.
Definition: UpwindTransportSolver.hpp:238
void forEachCoupling(std::size_t c, Function &&f) const
Call a function for each cell that a cell depends on.
Definition: UpwindTransportSolver.hpp:313
static constexpr std::size_t blockSize
Definition: UpwindTransportSolver.hpp:229
const Block & diagonal(std::size_t c) const
The diagonal block of a cell.
Definition: UpwindTransportSolver.hpp:302
std::size_t numCells() const
The number of cells, i.e. block rows of the matrix.
Definition: UpwindTransportSolver.hpp:283
const SparseTable< int > & graph() const
The dependency graph, as input to Opm::tarjan().
Definition: UpwindTransportSolver.hpp:293
The system of solveReordered() and the work arrays for solving it.
Definition: UpwindTransportSolver.hpp:335
ReorderedSystem(const Coefficients &coefficients, const Vectors &rhs, Vectors &x, const UpwindTransportSettings &settings, const std::string &name)
Set up the system.
Definition: UpwindTransportSolver.hpp:349
UpwindTransportStatistics solve()
Solve the components in the order of Opm::tarjan().
Definition: UpwindTransportSolver.hpp:377
if(!linsolver_)
Definition: FlexibleSolver_impl.hpp:336
bool isFiniteBlock(const Block &block)
Whether all entries of a block are finite.
Definition: UpwindTransportSolver.hpp:179
void solveDenseSystem(std::vector< Scalar > &a, std::vector< Scalar > &b, std::size_t m, std::size_t numRhs)
Solve a dense system a x = b by Gaussian elimination with partial pivoting.
Definition: UpwindTransportSolver.hpp:106
void forEachNonzeroCoupling(const Matrix &matrix, Function &&f)
Call a function for every nonzero off-diagonal block of a sparse block matrix.
Definition: UpwindTransportSolver.hpp:198
bool isNonzeroBlock(const Block &block)
Whether any entry of a block is nonzero. NaN counts as nonzero, so that it is rejected by MatrixCoeff...
Definition: UpwindTransportSolver.hpp:161
Definition: blackoilbioeffectsmodules.hh:45
StronglyConnectedComponents tarjan(const Graph &graph)
Compute the strongly connected components of a directed graph with Tarjan's algorithm.
Definition: Tarjan.hpp:116
UpwindTransportStatistics solveReordered(const Matrix &matrix, const Vectors &rhs, Vectors &x, const UpwindTransportSettings &settings={})
Solve the block system A x_q = b_q described in the file comment.
Definition: UpwindTransportSolver.hpp:716
std::string to_string(const ConvergenceReport::ReservoirFailure::Type t)
Settings of solveReordered().
Definition: UpwindTransportSolver.hpp:61
double relTolerance
Definition: UpwindTransportSolver.hpp:68
int maxDirectSize
Definition: UpwindTransportSolver.hpp:64
int maxIterations
Most Gauss-Seidel sweeps over a flow loop before giving up.
Definition: UpwindTransportSolver.hpp:75
double absTolerance
Definition: UpwindTransportSolver.hpp:72
Statistics of a solveReordered() call.
Definition: UpwindTransportSolver.hpp:80
int largestComponent
Number of cells in the largest component.
Definition: UpwindTransportSolver.hpp:88
int numLoops
Number of components with more than one cell, i.e. flow loops.
Definition: UpwindTransportSolver.hpp:85
int maxIterations
Most sweeps used for any flow loop that was solved by iteration.
Definition: UpwindTransportSolver.hpp:91
int numComponents
Number of components, i.e. cells or flow loops.
Definition: UpwindTransportSolver.hpp:82