getQuasiImpesWeights.hpp
Go to the documentation of this file.
1/*
2 Copyright 2019 SINTEF Digital, Mathematics and Cybernetics.
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
20#ifndef OPM_GET_QUASI_IMPES_WEIGHTS_HEADER_INCLUDED
21#define OPM_GET_QUASI_IMPES_WEIGHTS_HEADER_INCLUDED
22
23#include <dune/common/exceptions.hh>
24#include <dune/common/fvector.hh>
25
26#include <fmt/format.h>
27
28#include <opm/grid/utility/ElementChunks.hpp>
30#include <opm/material/common/MathToolbox.hpp>
32#include <algorithm>
33#include <cmath>
34#include <stdexcept>
35
36#if HAVE_CUDA
37#if USE_HIP
38#include <opm/simulators/linalg/gpuistl_hip/detail/cpr_amg_operations.hpp>
39#else
41#endif
42#endif
43
44
45namespace Opm
46{
47
48namespace Details
49{
50 template <class DenseMatrix>
51 DenseMatrix transposeDenseMatrix(const DenseMatrix& M)
52 {
53 DenseMatrix tmp;
54 for (int i = 0; i < M.rows; ++i)
55 for (int j = 0; j < M.cols; ++j)
56 tmp[j][i] = M[i][j];
57
58 return tmp;
59 }
60} // namespace Details
61
62namespace Amg
63{
64 template <class Matrix, class Vector>
65 void getQuasiImpesWeights(const Matrix& matrix,
66 const int pressureVarIndex,
67 const bool transpose,
68 Vector& weights,
69 [[maybe_unused]] bool enable_thread_parallel)
70 {
71 using VectorBlockType = typename Vector::block_type;
72 using MatrixBlockType = typename Matrix::block_type;
73 const Matrix& A = matrix;
74
75 VectorBlockType rhs(0.0);
76 rhs[pressureVarIndex] = 1.0;
77
78 // Declare variables outside the loop to avoid repetitive allocation
79 MatrixBlockType diag_block;
80 VectorBlockType bweights;
81 MatrixBlockType diag_block_transpose;
82
83 // Use OpenMP to parallelize over matrix rows (runtime controlled via if clause)
84#ifdef _OPENMP
85#pragma omp parallel for private(diag_block, bweights, diag_block_transpose) if(enable_thread_parallel)
86#endif
87 for (int row_idx = 0; row_idx < static_cast<int>(A.N()); ++row_idx) {
88 diag_block = MatrixBlockType(0.0);
89 // Find diagonal block for this row
90 const auto row_it = A.begin() + row_idx;
91 const auto endj = (*row_it).end();
92 for (auto j = (*row_it).begin(); j != endj; ++j) {
93 if (row_it.index() == j.index()) {
94 diag_block = (*j);
95 break;
96 }
97 }
98 if (transpose) {
99 diag_block.solve(bweights, rhs);
100 } else {
101 diag_block_transpose = Details::transposeDenseMatrix(diag_block);
102 diag_block_transpose.solve(bweights, rhs);
103 }
104
105 const double abs_max =
106 *std::ranges::max_element(bweights,
107 [](double a, double b)
108 { return std::fabs(a) < std::fabs(b); });
109 bweights /= std::fabs(abs_max);
110 weights[row_idx] = bweights;
111 }
112 }
113
114 template <class Matrix, class Vector>
115 Vector getQuasiImpesWeights(const Matrix& matrix,
116 const int pressureVarIndex,
117 const bool transpose,
118 bool enable_thread_parallel)
119 {
120 Vector weights(matrix.N());
121 getQuasiImpesWeights(matrix, pressureVarIndex, transpose, weights, enable_thread_parallel);
122 return weights;
123 }
124
125#if HAVE_CUDA
126 template <typename T>
127 std::vector<int> precomputeDiagonalIndices(const gpuistl::GpuSparseMatrixWrapper<T>& matrix) {
128 std::vector<int> diagonalIndices(matrix.N(), -1);
129 const auto rowIndices = matrix.getRowIndices().asStdVector();
130 const auto colIndices = matrix.getColumnIndices().asStdVector();
131
132 for (auto row = 0; row < Opm::gpuistl::detail::to_int(matrix.N()); ++row) {
133 for (auto i = rowIndices[row]; i < rowIndices[row+1]; ++i) {
134 if (colIndices[i] == row) {
135 diagonalIndices[row] = i;
136 break;
137 }
138 }
139 }
140 return diagonalIndices;
141 }
142
143 // GPU version that delegates to the GPU implementation
144 template <typename T, bool transpose>
145 void getQuasiImpesWeights(const gpuistl::GpuSparseMatrixWrapper<T>& matrix,
146 const int pressureVarIndex,
147 gpuistl::GpuVector<T>& weights,
148 const gpuistl::GpuVector<int>& diagonalIndices)
149 {
150 gpuistl::detail::getQuasiImpesWeights<T, transpose>(matrix, pressureVarIndex, weights, diagonalIndices);
151 }
152
153 template <typename T, bool transpose>
154 gpuistl::GpuVector<T> getQuasiImpesWeights(const gpuistl::GpuSparseMatrixWrapper<T>& matrix,
155 const int pressureVarIndex,
156 const gpuistl::GpuVector<int>& diagonalIndices)
157 {
158 gpuistl::GpuVector<T> weights(matrix.N() * matrix.blockSize());
159 getQuasiImpesWeights<T, transpose>(matrix, pressureVarIndex, weights, diagonalIndices);
160 return weights;
161 }
162#endif
163
164 template<class Vector, class ElementContext, class Model, class ElementChunksType>
165 void getTrueImpesWeights(int pressureVarIndex, Vector& weights,
166 const ElementContext& elemCtx,
167 const Model& model,
168 const ElementChunksType& element_chunks,
169 [[maybe_unused]] bool enable_thread_parallel)
170 {
171 using VectorBlockType = typename Vector::block_type;
172 using Matrix = typename std::decay_t<decltype(model.linearizer().jacobian())>;
173 using MatrixBlockType = typename Matrix::MatrixBlock;
174 constexpr int numEq = VectorBlockType::size();
175 using Evaluation = typename std::decay_t<decltype(model.localLinearizer(ThreadManager::threadId()).localResidual().residual(0))>
176 ::block_type;
177
178 VectorBlockType rhs(0.0);
179 rhs[pressureVarIndex] = 1.0;
180
181 // Declare variables outside the loop to avoid repetitive allocation
182 MatrixBlockType block;
183 VectorBlockType bweights;
184 MatrixBlockType block_transpose;
185 Dune::FieldVector<Evaluation, numEq> storage;
186
188#ifdef _OPENMP
189#pragma omp parallel for private(block, bweights, block_transpose, storage) if(enable_thread_parallel)
190#endif
191 for (const auto& chunk : element_chunks) {
192 const std::size_t thread_id = ThreadManager::threadId();
193 ElementContext localElemCtx(elemCtx.simulator());
194
195 for (const auto& elem : chunk) {
196 localElemCtx.updatePrimaryStencil(elem);
197 localElemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0);
198
199 model.localLinearizer(thread_id).localResidual().computeStorage(storage, localElemCtx, /*spaceIdx=*/0, /*timeIdx=*/0);
200
201 auto extrusionFactor = localElemCtx.intensiveQuantities(0, /*timeIdx=*/0).extrusionFactor();
202 auto scvVolume = localElemCtx.stencil(/*timeIdx=*/0).subControlVolume(0).volume() * extrusionFactor;
203 auto storage_scale = scvVolume / localElemCtx.simulator().timeStepSize();
204 const double pressure_scale = 50e5;
205
206 // Build the transposed matrix directly to avoid separate transpose step
207 for (int ii = 0; ii < numEq; ++ii) {
208 for (int jj = 0; jj < numEq; ++jj) {
209 block_transpose[jj][ii] = storage[ii].derivative(jj)/storage_scale;
210 if (jj == pressureVarIndex) {
211 block_transpose[jj][ii] *= pressure_scale;
212 }
213 }
214 }
215 try {
216 block_transpose.solve(bweights, rhs);
217 }
218 catch (const Dune::FMatrixError&) {
219 // Rank-deficient storage derivatives: no combination of the
220 // mass balances has a storage term that depends on pressure
221 // alone, so there is no weight vector to compute here.
222 //
223 // Deliberately no fallback value. bweights is indexed by
224 // equation while rhs is indexed by primary variable, so
225 // substituting rhs would put unit weight on whichever
226 // equation happens to share the pressure variable's index -
227 // an arbitrary choice that goes on to make the CPR pressure
228 // system itself singular. Name the cell instead, so the
229 // cause can be found.
230 const auto globalDofIdx =
231 localElemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0);
232
233 throw std::runtime_error {
234 fmt::format("Singular storage matrix when forming the CPR "
235 "pressure weights for cell {} (Cartesian index "
236 "{}). The storage derivatives of that cell are "
237 "rank deficient, so the pressure equation cannot "
238 "be formed there.",
239 globalDofIdx,
240 localElemCtx.simulator().vanguard()
241 .cartesianIndex(globalDofIdx))
242 };
243 }
244
245 const double abs_max =
246 *std::ranges::max_element(bweights,
247 [](double a, double b)
248 { return std::fabs(a) < std::fabs(b); });
249 // probably a scaling which could give approximately total compressibility would be better
250 bweights /= std::fabs(abs_max); // given normal densities this scales weights to about 1.
251
252 const auto index = localElemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0);
253 weights[index] = bweights;
254 }
255 }
256 OPM_END_PARALLEL_TRY_CATCH("getTrueImpesWeights() failed: ", elemCtx.simulator().vanguard().grid().comm());
257 }
258
259 template <class Vector, class ElementContext, class Model, class ElementChunksType>
260 void getTrueImpesWeightsAnalytic(int /*pressureVarIndex*/,
261 Vector& weights,
262 const ElementContext& elemCtx,
263 const Model& model,
264 const ElementChunksType& element_chunks,
265 [[maybe_unused]] bool enable_thread_parallel)
266 {
267 // The sequential residual is a linear combination of the
268 // mass balance residuals, with coefficients equal to (for
269 // water, oil, gas):
270 // 1/bw,
271 // (1/bo - rs/bg)/(1-rs*rv)
272 // (1/bg - rv/bo)/(1-rs*rv)
273 // These coefficients must be applied for both the residual and
274 // Jacobian.
275 using FluidSystem = typename Model::FluidSystem;
276 using LhsEval = double;
277
278 using PrimaryVariables = typename Model::PrimaryVariables;
279 using VectorBlockType = typename Vector::block_type;
280 using Evaluation =
281 typename std::decay_t<decltype(model.localLinearizer(ThreadManager::threadId()).localResidual().residual(0))>::block_type;
282 using Toolbox = MathToolbox<Evaluation>;
283
284 const auto& solution = model.solution(/*timeIdx*/ 0);
285 VectorBlockType bweights;
286
287 // Use OpenMP to parallelize over element chunks (runtime controlled via if clause)
289#ifdef _OPENMP
290#pragma omp parallel for private(bweights) if(enable_thread_parallel)
291#endif
292 for (const auto& chunk : element_chunks) {
293
294 // Each thread gets a unique copy of elemCtx
295 ElementContext localElemCtx(elemCtx.simulator());
296
297 for (const auto& elem : chunk) {
298 localElemCtx.updatePrimaryStencil(elem);
299 localElemCtx.updatePrimaryIntensiveQuantities(/*timeIdx=*/0);
300
301 const auto index = localElemCtx.globalSpaceIndex(/*spaceIdx=*/0, /*timeIdx=*/0);
302 const auto& intQuants = localElemCtx.intensiveQuantities(/*spaceIdx=*/0, /*timeIdx=*/0);
303 const auto& fs = intQuants.fluidState();
304
305 if (FluidSystem::phaseIsActive(FluidSystem::waterPhaseIdx)) {
306 const unsigned activeCompIdx = FluidSystem::canonicalToActiveCompIdx(
307 FluidSystem::solventComponentIndex(FluidSystem::waterPhaseIdx));
308 bweights[activeCompIdx]
309 = Toolbox::template decay<LhsEval>(1 / fs.invB(FluidSystem::waterPhaseIdx));
310 }
311
312 double denominator = 1.0;
313 double rs = Toolbox::template decay<double>(fs.Rs());
314 double rv = Toolbox::template decay<double>(fs.Rv());
315 const auto& priVars = solution[index];
316 if (priVars.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Rv) {
317 rs = 0.0;
318 }
319 if (priVars.primaryVarsMeaningGas() == PrimaryVariables::GasMeaning::Rs) {
320 rv = 0.0;
321 }
322 if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)
323 && FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) {
324 denominator = Toolbox::template decay<LhsEval>(1 - rs * rv);
325 }
326
327 if (FluidSystem::phaseIsActive(FluidSystem::oilPhaseIdx)) {
328 const unsigned activeCompIdx = FluidSystem::canonicalToActiveCompIdx(
329 FluidSystem::solventComponentIndex(FluidSystem::oilPhaseIdx));
330 bweights[activeCompIdx] = Toolbox::template decay<LhsEval>(
331 (1 / fs.invB(FluidSystem::oilPhaseIdx) - rs / fs.invB(FluidSystem::gasPhaseIdx))
332 / denominator);
333 }
334 if (FluidSystem::phaseIsActive(FluidSystem::gasPhaseIdx)) {
335 const unsigned activeCompIdx = FluidSystem::canonicalToActiveCompIdx(
336 FluidSystem::solventComponentIndex(FluidSystem::gasPhaseIdx));
337 bweights[activeCompIdx] = Toolbox::template decay<LhsEval>(
338 (1 / fs.invB(FluidSystem::gasPhaseIdx) - rv / fs.invB(FluidSystem::oilPhaseIdx))
339 / denominator);
340 }
341
342 weights[index] = bweights;
343 }
344 }
345 OPM_END_PARALLEL_TRY_CATCH("getTrueImpesAnalyticWeights() failed: ", elemCtx.simulator().vanguard().grid().comm());
346 }
347} // namespace Amg
348
349} // namespace Opm
350
351#endif // OPM_GET_QUASI_IMPES_WEIGHTS_HEADER_INCLUDED
#define OPM_END_PARALLEL_TRY_CATCH(prefix, comm)
Catch exception and throw in a parallel try-catch clause.
Definition: DeferredLoggingErrorHelpers.hpp:197
#define OPM_BEGIN_PARALLEL_TRY_CATCH()
Macro to setup the try of a parallel try-catch.
Definition: DeferredLoggingErrorHelpers.hpp:160
static unsigned threadId()
Return the index of the current OpenMP thread.
The GpuSparseMatrixWrapper Checks CUDA/HIP version and dispatches a version either using the old or t...
Definition: GpuSparseMatrixWrapper.hpp:62
GpuVector< int > & getRowIndices()
getRowIndices returns the row indices used to represent the BSR structure.
Definition: GpuSparseMatrixWrapper.hpp:271
std::size_t N() const
N returns the number of rows (which is equal to the number of columns)
Definition: GpuSparseMatrixWrapper.hpp:232
GpuVector< int > & getColumnIndices()
getColumnIndices returns the column indices used to represent the BSR structure.
Definition: GpuSparseMatrixWrapper.hpp:291
void getTrueImpesWeights(int pressureVarIndex, Vector &weights, const ElementContext &elemCtx, const Model &model, const ElementChunksType &element_chunks, bool enable_thread_parallel)
Definition: getQuasiImpesWeights.hpp:165
void getQuasiImpesWeights(const Matrix &matrix, const int pressureVarIndex, const bool transpose, Vector &weights, bool enable_thread_parallel)
Definition: getQuasiImpesWeights.hpp:65
void getTrueImpesWeightsAnalytic(int, Vector &weights, const ElementContext &elemCtx, const Model &model, const ElementChunksType &element_chunks, bool enable_thread_parallel)
Definition: getQuasiImpesWeights.hpp:260
DenseMatrix transposeDenseMatrix(const DenseMatrix &M)
Definition: getQuasiImpesWeights.hpp:51
int to_int(std::size_t s)
to_int converts a (on most relevant platforms) 64 bits unsigned size_t to a signed 32 bits signed int
Definition: safe_conversion.hpp:56
Definition: blackoilbioeffectsmodules.hh:45