AmgxPreconditioner.hpp
Go to the documentation of this file.
1/*
2 Copyright 2024 SINTEF AS
3 Copyright 2024 Equinor ASA
4
5 This file is part of the Open Porous Media project (OPM).
6
7 OPM is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 OPM is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with OPM. If not, see <http://www.gnu.org/licenses/>.
19*/
20
21#ifndef OPM_AMGX_PRECONDITIONER_HEADER_INCLUDED
22#define OPM_AMGX_PRECONDITIONER_HEADER_INCLUDED
23
24#include <opm/common/ErrorMacros.hpp>
25#include <opm/common/TimingMacros.hpp>
28
29#include <dune/common/fmatrix.hh>
30#include <dune/istl/bcrsmatrix.hh>
31
32#include <amgx_c.h>
33
34#include <vector>
35
36namespace Amgx {
37
43struct AmgxConfig {
47 std::string solver = "AMG";
48 std::string algorithm = "CLASSICAL";
49 std::string interpolator = "D2";
50 std::string selector = "PMIS";
51 std::string smoother = "BLOCK_JACOBI";
52 int presweeps = 3;
53 int postsweeps = 3;
54 double strength_threshold = 0.5;
55 int max_iters = 1;
56
57 explicit AmgxConfig(const Opm::PropertyTree& prm) {
58 determinism_flag = prm.get<int>("determinism_flag", determinism_flag);
59 print_grid_stats = prm.get<int>("print_grid_stats", print_grid_stats);
60 print_solve_stats = prm.get<int>("print_solve_stats", print_solve_stats);
61 solver = prm.get<std::string>("solver", solver);
62 algorithm = prm.get<std::string>("algorithm", algorithm);
63 interpolator = prm.get<std::string>("interpolator", interpolator);
64 selector = prm.get<std::string>("selector", selector);
65 smoother = prm.get<std::string>("smoother", smoother);
66 presweeps = prm.get<int>("presweeps", presweeps);
67 postsweeps = prm.get<int>("postsweeps", postsweeps);
68 strength_threshold = prm.get<double>("strength_threshold", strength_threshold);
69 max_iters = prm.get<int>("max_iters", max_iters);
70 }
71
72 std::string toString() const {
73 return "config_version=2, "
74 "determinism_flag=" + std::to_string(determinism_flag) + ", "
75 "print_grid_stats=" + std::to_string(print_grid_stats) + ", "
76 "print_solve_stats=" + std::to_string(print_solve_stats) + ", "
77 "solver=" + solver + ", "
78 "algorithm=" + algorithm + ", "
79 "interpolator=" + interpolator + ", "
80 "selector=" + selector + ", "
81 "smoother=" + smoother + ", "
82 "presweeps=" + std::to_string(presweeps) + ", "
83 "postsweeps=" + std::to_string(postsweeps) + ", "
84 "strength_threshold=" + std::to_string(strength_threshold) + ", "
85 "max_iters=" + std::to_string(max_iters);
86 }
87};
88
100template<class M, class X, class Y>
102{
103public:
105 using matrix_type = M;
107 using matrix_field_type = typename M::field_type;
109 using domain_type = X;
111 using range_type = Y;
113 using vector_field_type = typename X::field_type;
114
115 static constexpr int block_size = 1;
116
126 : A_(A)
127 , N_(A.N())
128 , nnz_(A.nonzeroes())
129 {
130 OPM_TIMEBLOCK(prec_construct);
131
132 // Create configuration
133 AmgxConfig config(prm);
134 AMGX_SAFE_CALL(AMGX_config_create(&cfg_, config.toString().c_str()));
135 AMGX_SAFE_CALL(AMGX_resources_create_simple(&rsrc_, cfg_));
136
137 // Setup frequency is set in the property tree
138 setup_frequency_ = prm.get<int>("setup_frequency", 30);
139
140 // Select appropriate AMGX mode based on matrix and vector scalar types
141 AMGX_Mode amgx_mode;
142 if constexpr (std::is_same_v<matrix_field_type, double> && std::is_same_v<vector_field_type, double>) {
143 amgx_mode = AMGX_mode_dDDI;
144 } else if constexpr (std::is_same_v<matrix_field_type, float> && std::is_same_v<vector_field_type, double>) {
145 amgx_mode = AMGX_mode_dDFI;
146 } else if constexpr (std::is_same_v<matrix_field_type, float> && std::is_same_v<vector_field_type, float>) {
147 amgx_mode = AMGX_mode_dFFI;
148 } else {
149 OPM_THROW(std::runtime_error, "Unsupported combination of matrix and vector types in AmgxPreconditioner");
150 }
151
152 // Create solver and matrix/vector handles with selected mode
153 AMGX_SAFE_CALL(AMGX_solver_create(&solver_, rsrc_, amgx_mode, cfg_));
154 AMGX_SAFE_CALL(AMGX_matrix_create(&A_amgx_, rsrc_, amgx_mode));
155 AMGX_SAFE_CALL(AMGX_vector_create(&x_amgx_, rsrc_, amgx_mode));
156 AMGX_SAFE_CALL(AMGX_vector_create(&b_amgx_, rsrc_, amgx_mode));
157
158 // Setup matrix structure
159 std::vector<int> row_ptrs(N_ + 1);
160 std::vector<int> col_indices(nnz_);
161 setupSparsityPattern(row_ptrs, col_indices);
162
163 // initialize matrix with values
164 const matrix_field_type* values = &(A_[0][0][0][0]);
165 AMGX_SAFE_CALL(AMGX_pin_memory(const_cast<matrix_field_type*>(values), sizeof(matrix_field_type) * nnz_ * block_size * block_size));
166 AMGX_SAFE_CALL(AMGX_matrix_upload_all(A_amgx_, N_, nnz_, block_size, block_size,
167 row_ptrs.data(), col_indices.data(),
168 values, nullptr));
169 update();
170 }
171
178 {
179 const matrix_field_type* values = &(A_[0][0][0][0]);
180 AMGX_SAFE_CALL(AMGX_unpin_memory(const_cast<matrix_field_type*>(values)));
181 if (solver_) {
182 AMGX_SAFE_CALL(AMGX_solver_destroy(solver_));
183 }
184 if (x_amgx_) {
185 AMGX_SAFE_CALL(AMGX_vector_destroy(x_amgx_));
186 }
187 if (b_amgx_) {
188 AMGX_SAFE_CALL(AMGX_vector_destroy(b_amgx_));
189 }
190 if (A_amgx_) {
191 AMGX_SAFE_CALL(AMGX_matrix_destroy(A_amgx_));
192 }
193 // Destroying resources and config crashes when reinitializing
194 //if (rsrc_) {
195 // AMGX_SAFE_CALL(AMGX_resources_destroy(rsrc_));
196 //}
197 //if (cfg_) {
198 // AMGX_SAFE_CALL(AMGX_config_destroy(cfg_));
199 //}
200 }
201
210 void pre(X& /*v*/, Y& /*d*/) override {
211 }
212
222 void apply(X& v, const Y& d) override
223 {
224 OPM_TIMEBLOCK(prec_apply);
225
226 // Upload vectors to AMGX
227 AMGX_SAFE_CALL(AMGX_vector_upload(x_amgx_, N_, block_size, &v[0][0]));
228 AMGX_SAFE_CALL(AMGX_vector_upload(b_amgx_, N_, block_size, &d[0][0]));
229
230 // Apply preconditioner
231 AMGX_SAFE_CALL(AMGX_solver_solve(solver_, b_amgx_, x_amgx_));
232
233 // Download result
234 AMGX_SAFE_CALL(AMGX_vector_download(x_amgx_, &v[0][0]));
235 }
236
244 void post(X& /*v*/) override {
245 }
246
252 void update() override
253 {
254 OPM_TIMEBLOCK(prec_update);
255 copyMatrixToAmgx();
256 if (update_counter_ == 0) {
257 AMGX_SAFE_CALL(AMGX_solver_setup(solver_, A_amgx_));
258 } else {
259 AMGX_SAFE_CALL(AMGX_solver_resetup(solver_, A_amgx_));
260 }
261
262 ++update_counter_;
263 if (update_counter_ >= setup_frequency_) {
264 update_counter_ = 0;
265 }
266 }
267
273 Dune::SolverCategory::Category category() const override
274 {
275 return Dune::SolverCategory::sequential;
276 }
277
283 bool hasPerfectUpdate() const override
284 {
285 // The AMG hierarchy of the Amgx preconditioner can depend on the values of the matrix, so it must be recreated
286 // when the matrix values change, at given frequency. Since this is handled internally, we return true.
287 return true;
288 }
289
290private:
299 void setupSparsityPattern(std::vector<int>& row_ptrs, std::vector<int>& col_indices)
300 {
301 int pos = 0;
302 row_ptrs[0] = 0;
303 for (auto row = A_.begin(); row != A_.end(); ++row) {
304 for (auto col = row->begin(); col != row->end(); ++col) {
305 col_indices[pos++] = col.index();
306 }
307 row_ptrs[row.index() + 1] = pos;
308 }
309 }
310
318 void copyMatrixToAmgx()
319 {
320 // Get direct pointer to matrix values
321 const matrix_field_type* values = &(A_[0][0][0][0]);
322 // Indexing explanation:
323 // A_[0] - First row of the matrix
324 // [0] - First block in that row
325 // [0] - First row within the 1x1 block
326 // [0] - First column within the 1x1 block
327 // update matrix with new values, assuming the sparsity structure is the same
328 AMGX_SAFE_CALL(AMGX_matrix_replace_coefficients(A_amgx_, N_, nnz_, values, nullptr));
329 }
330
331 const M& A_;
332 const int N_;
333 const int nnz_;
334 // Internal variables to control AMGX setup and reuse frequency
335 int setup_frequency_ = -1;
336 int update_counter_ = 0;
337
338 AMGX_config_handle cfg_ = nullptr;
339 AMGX_resources_handle rsrc_ = nullptr;
340 AMGX_solver_handle solver_ = nullptr;
341 AMGX_matrix_handle A_amgx_ = nullptr;
342 AMGX_vector_handle x_amgx_ = nullptr;
343 AMGX_vector_handle b_amgx_ = nullptr;
344};
345
346} // namespace Amgx
347
348#endif // OPM_AMGX_PRECONDITIONER_HEADER_INCLUDED
Wrapper for AMGX's AMG preconditioner.
Definition: AmgxPreconditioner.hpp:102
bool hasPerfectUpdate() const override
Checks if the preconditioner has a perfect update.
Definition: AmgxPreconditioner.hpp:283
Y range_type
The range type of the preconditioner.
Definition: AmgxPreconditioner.hpp:111
void update() override
Updates the preconditioner with the current matrix values.
Definition: AmgxPreconditioner.hpp:252
void apply(X &v, const Y &d) override
Applies the preconditioner to a vector.
Definition: AmgxPreconditioner.hpp:222
typename M::field_type matrix_field_type
The field type of the matrix.
Definition: AmgxPreconditioner.hpp:107
static constexpr int block_size
Definition: AmgxPreconditioner.hpp:115
AmgxPreconditioner(const M &A, const Opm::PropertyTree prm)
Constructor for the AmgxPreconditioner class.
Definition: AmgxPreconditioner.hpp:125
Dune::SolverCategory::Category category() const override
Returns the solver category.
Definition: AmgxPreconditioner.hpp:273
void pre(X &, Y &) override
Pre-processing step before applying the preconditioner.
Definition: AmgxPreconditioner.hpp:210
X domain_type
The domain type of the preconditioner.
Definition: AmgxPreconditioner.hpp:109
M matrix_type
The matrix type the preconditioner is for.
Definition: AmgxPreconditioner.hpp:105
typename X::field_type vector_field_type
The field type of the vectors.
Definition: AmgxPreconditioner.hpp:113
~AmgxPreconditioner()
Destructor for the AmgxPreconditioner class.
Definition: AmgxPreconditioner.hpp:177
void post(X &) override
Post-processing step after applying the preconditioner.
Definition: AmgxPreconditioner.hpp:244
Interface class adding the update() method to the preconditioner interface.
Definition: PreconditionerWithUpdate.hpp:32
Hierarchical collection of key/value pairs.
Definition: PropertyTree.hpp:39
T get(const std::string &key) const
Definition: AmgxPreconditioner.hpp:36
std::string to_string(const ConvergenceReport::ReservoirFailure::Type t)
Configuration structure for AMGX parameters.
Definition: AmgxPreconditioner.hpp:43
std::string algorithm
Definition: AmgxPreconditioner.hpp:48
std::string smoother
Definition: AmgxPreconditioner.hpp:51
std::string interpolator
Definition: AmgxPreconditioner.hpp:49
int postsweeps
Definition: AmgxPreconditioner.hpp:53
std::string solver
Definition: AmgxPreconditioner.hpp:47
int max_iters
Definition: AmgxPreconditioner.hpp:55
int presweeps
Definition: AmgxPreconditioner.hpp:52
double strength_threshold
Definition: AmgxPreconditioner.hpp:54
int determinism_flag
Definition: AmgxPreconditioner.hpp:44
int print_grid_stats
Definition: AmgxPreconditioner.hpp:45
int print_solve_stats
Definition: AmgxPreconditioner.hpp:46
std::string selector
Definition: AmgxPreconditioner.hpp:50
std::string toString() const
Definition: AmgxPreconditioner.hpp:72
AmgxConfig(const Opm::PropertyTree &prm)
Definition: AmgxPreconditioner.hpp:57