HyprePreconditioner.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_HYPRE_PRECONDITIONER_HEADER_INCLUDED
22#define OPM_HYPRE_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 <HYPRE.h>
33#include <HYPRE_parcsr_ls.h>
34#include <HYPRE_krylov.h>
35#include <_hypre_utilities.h>
36
37#include <vector>
38#include <numeric>
39
40namespace Hypre {
41
52template<class M, class X, class Y>
54public:
55
64 HyprePreconditioner (const M& A, const Opm::PropertyTree prm)
65 : A_(A)
66 {
67 OPM_TIMEBLOCK(prec_construct);
68
69 int size;
70 MPI_Comm_size(MPI_COMM_WORLD, &size);
71 if (size > 1) {
72 OPM_THROW(std::runtime_error, "HyprePreconditioner is currently only implemented for sequential runs");
73 }
74
75 use_gpu_ = prm.get<bool>("use_gpu", false);
76
77 // Set memory location and execution policy
78#if HYPRE_USING_CUDA || HYPRE_USING_HIP
79 if (use_gpu_) {
80 HYPRE_SetMemoryLocation(HYPRE_MEMORY_DEVICE);
81 HYPRE_SetExecutionPolicy(HYPRE_EXEC_DEVICE);
82 // use hypre's SpGEMM instead of vendor implementation
83 HYPRE_SetSpGemmUseVendor(false);
84 // use cuRand for PMIS
85 HYPRE_SetUseGpuRand(1);
86 HYPRE_DeviceInitialize();
87 HYPRE_PrintDeviceInfo();
88 }
89 else
90#endif
91 {
92 HYPRE_SetMemoryLocation(HYPRE_MEMORY_HOST);
93 HYPRE_SetExecutionPolicy(HYPRE_EXEC_HOST);
94 }
95
96 // Create the solver (BoomerAMG)
97 HYPRE_BoomerAMGCreate(&solver_);
98
99 // Set parameters from property tree with defaults
100 HYPRE_BoomerAMGSetPrintLevel(solver_, prm.get<int>("print_level", 0));
101 HYPRE_BoomerAMGSetMaxIter(solver_, prm.get<int>("max_iter", 1));
102 HYPRE_BoomerAMGSetStrongThreshold(solver_, prm.get<double>("strong_threshold", 0.5));
103 HYPRE_BoomerAMGSetAggTruncFactor(solver_, prm.get<double>("agg_trunc_factor", 0.3));
104 HYPRE_BoomerAMGSetInterpType(solver_, prm.get<int>("interp_type", 6));
105 HYPRE_BoomerAMGSetMaxLevels(solver_, prm.get<int>("max_levels", 15));
106 HYPRE_BoomerAMGSetTol(solver_, prm.get<double>("tolerance", 0.0));
107
108 if (use_gpu_) {
109 HYPRE_BoomerAMGSetRelaxType(solver_, 16);
110 HYPRE_BoomerAMGSetCoarsenType(solver_, 8);
111 HYPRE_BoomerAMGSetAggNumLevels(solver_, 0);
112 HYPRE_BoomerAMGSetAggInterpType(solver_, 6);
113 // Keep transpose to avoid SpMTV
114 HYPRE_BoomerAMGSetKeepTranspose(solver_, true);
115 }
116 else {
117 HYPRE_BoomerAMGSetRelaxType(solver_, prm.get<int>("relax_type", 13));
118 HYPRE_BoomerAMGSetCoarsenType(solver_, prm.get<int>("coarsen_type", 10));
119 HYPRE_BoomerAMGSetAggNumLevels(solver_, prm.get<int>("agg_num_levels", 1));
120 HYPRE_BoomerAMGSetAggInterpType(solver_, prm.get<int>("agg_interp_type", 4));
121 }
122
123 // Create Hypre vectors
124 N_ = A_.N();
125 nnz_ = A_.nonzeroes();
126 HYPRE_IJVectorCreate(MPI_COMM_SELF, 0, N_-1, &x_hypre_);
127 HYPRE_IJVectorCreate(MPI_COMM_SELF, 0, N_-1, &b_hypre_);
128 HYPRE_IJVectorSetObjectType(x_hypre_, HYPRE_PARCSR);
129 HYPRE_IJVectorSetObjectType(b_hypre_, HYPRE_PARCSR);
130 HYPRE_IJVectorInitialize(x_hypre_);
131 HYPRE_IJVectorInitialize(b_hypre_);
132 // Create indices vector
133 indices_.resize(N_);
134 std::iota(indices_.begin(), indices_.end(), 0);
135 if (use_gpu_) {
136 indices_device_ = hypre_CTAlloc(HYPRE_BigInt, N_, HYPRE_MEMORY_DEVICE);
137 hypre_TMemcpy(indices_device_, indices_.data(), HYPRE_BigInt, N_, HYPRE_MEMORY_DEVICE, HYPRE_MEMORY_HOST);
138 // Allocate device vectors
139 x_values_device_ = hypre_CTAlloc(HYPRE_Real, N_, HYPRE_MEMORY_DEVICE);
140 b_values_device_ = hypre_CTAlloc(HYPRE_Real, N_, HYPRE_MEMORY_DEVICE);
141 }
142
143 // Create Hypre matrix
144 HYPRE_IJMatrixCreate(MPI_COMM_SELF, 0, N_-1, 0, N_-1, &A_hypre_);
145 HYPRE_IJMatrixSetObjectType(A_hypre_, HYPRE_PARCSR);
146 HYPRE_IJMatrixInitialize(A_hypre_);
147
148 setupSparsityPattern();
149 update();
150 }
151
158 if (solver_) {
159 HYPRE_BoomerAMGDestroy(solver_);
160 }
161 if (A_hypre_) {
162 HYPRE_IJMatrixDestroy(A_hypre_);
163 }
164 if (x_hypre_) {
165 HYPRE_IJVectorDestroy(x_hypre_);
166 }
167 if (b_hypre_) {
168 HYPRE_IJVectorDestroy(b_hypre_);
169 }
170 if (values_device_) {
171 hypre_TFree(values_device_, HYPRE_MEMORY_DEVICE);
172 }
173 if (x_values_device_) {
174 hypre_TFree(x_values_device_, HYPRE_MEMORY_DEVICE);
175 }
176 if (b_values_device_) {
177 hypre_TFree(b_values_device_, HYPRE_MEMORY_DEVICE);
178 }
179 if (indices_device_) {
180 hypre_TFree(indices_device_, HYPRE_MEMORY_DEVICE);
181 }
182 }
183
189 void update() override {
190 OPM_TIMEBLOCK(prec_update);
191 copyMatrixToHypre();
192 HYPRE_BoomerAMGSetup(solver_, parcsr_A_, par_b_, par_x_);
193 }
194
203 void pre(X& /*v*/, Y& /*d*/) override {
204 }
205
214 void apply(X& v, const Y& d) override {
215 OPM_TIMEBLOCK(prec_apply);
216
217 // Copy vectors to Hypre format
218 copyVectorsToHypre(v, d);
219
220 // Apply the preconditioner (one AMG V-cycle)
221 HYPRE_BoomerAMGSolve(solver_, parcsr_A_, par_b_, par_x_);
222
223 // Copy result back
224 copyVectorFromHypre(v);
225 }
226
234 void post(X& /*v*/) override {
235 }
236
242 Dune::SolverCategory::Category category() const override {
243 return Dune::SolverCategory::sequential;
244 }
245
251 bool hasPerfectUpdate() const override
252 {
253 // The Hypre preconditioner can depend on the values of the matrix so it does not have perfect update.
254 // However, copying the matrix to Hypre requires to setup the solver again, so this is handled internally.
255 // So for ISTLSolver, we can return true.
256 return true;
257 }
258
259private:
265 void setupSparsityPattern() {
266 // Allocate arrays required by Hypre
267 ncols_.resize(N_);
268 rows_.resize(N_);
269 cols_.resize(nnz_);
270
271 // Setup arrays and fill column indices
272 int pos = 0;
273 for (auto row = A_.begin(); row != A_.end(); ++row) {
274 const int rowIdx = row.index();
275 rows_[rowIdx] = rowIdx;
276 ncols_[rowIdx] = row->size();
277
278 for (auto col = row->begin(); col != row->end(); ++col) {
279 cols_[pos++] = col.index();
280 }
281 }
282 if (use_gpu_) {
283 // Allocate device arrays
284 ncols_device_ = hypre_CTAlloc(HYPRE_Int, N_, HYPRE_MEMORY_DEVICE);
285 rows_device_ = hypre_CTAlloc(HYPRE_BigInt, N_, HYPRE_MEMORY_DEVICE);
286 cols_device_ = hypre_CTAlloc(HYPRE_BigInt, nnz_, HYPRE_MEMORY_DEVICE);
287 values_device_ = hypre_CTAlloc(HYPRE_Real, nnz_, HYPRE_MEMORY_DEVICE);
288
289 // Copy to device
290 hypre_TMemcpy(ncols_device_, ncols_.data(), HYPRE_Int, N_, HYPRE_MEMORY_DEVICE, HYPRE_MEMORY_HOST);
291 hypre_TMemcpy(rows_device_, rows_.data(), HYPRE_BigInt, N_, HYPRE_MEMORY_DEVICE, HYPRE_MEMORY_HOST);
292 hypre_TMemcpy(cols_device_, cols_.data(), HYPRE_BigInt, nnz_, HYPRE_MEMORY_DEVICE, HYPRE_MEMORY_HOST);
293 }
294 }
295
303 void copyMatrixToHypre() {
304 OPM_TIMEBLOCK(prec_copy_matrix);
305 // Get pointer to matrix values array
306 const HYPRE_Real* values = &(A_[0][0][0][0]);
307 // Indexing explanation:
308 // A_[0] - First row of the matrix
309 // [0] - First block in that row
310 // [0] - First row within the 1x1 block
311 // [0] - First column within the 1x1 block
312
313 if (use_gpu_) {
314 hypre_TMemcpy(values_device_, values, HYPRE_Real, nnz_, HYPRE_MEMORY_DEVICE, HYPRE_MEMORY_HOST);
315 HYPRE_IJMatrixSetValues(A_hypre_, N_, ncols_device_, rows_device_, cols_device_, values_device_);
316 }
317 else {
318 HYPRE_IJMatrixSetValues(A_hypre_, N_, ncols_.data(), rows_.data(), cols_.data(), values);
319 }
320
321 HYPRE_IJMatrixAssemble(A_hypre_);
322 HYPRE_IJMatrixGetObject(A_hypre_, reinterpret_cast<void**>(&parcsr_A_));
323 }
324
334 void copyVectorsToHypre(const X& v, const Y& d) {
335 OPM_TIMEBLOCK(prec_copy_vectors_to_hypre);
336 const HYPRE_Real* x_vals = &(v[0][0]);
337 const HYPRE_Real* b_vals = &(d[0][0]);
338
339 if (use_gpu_) {
340 hypre_TMemcpy(x_values_device_, x_vals, HYPRE_Real, N_, HYPRE_MEMORY_DEVICE, HYPRE_MEMORY_HOST);
341 hypre_TMemcpy(b_values_device_, b_vals, HYPRE_Real, N_, HYPRE_MEMORY_DEVICE, HYPRE_MEMORY_HOST);
342
343 HYPRE_IJVectorSetValues(x_hypre_, N_, indices_device_, x_values_device_);
344 HYPRE_IJVectorSetValues(b_hypre_, N_, indices_device_, b_values_device_);
345 }
346 else {
347 HYPRE_IJVectorSetValues(x_hypre_, N_, indices_.data(), x_vals);
348 HYPRE_IJVectorSetValues(b_hypre_, N_, indices_.data(), b_vals);
349 }
350
351 HYPRE_IJVectorAssemble(x_hypre_);
352 HYPRE_IJVectorAssemble(b_hypre_);
353 HYPRE_IJVectorGetObject(x_hypre_, reinterpret_cast<void**>(&par_x_));
354 HYPRE_IJVectorGetObject(b_hypre_, reinterpret_cast<void**>(&par_b_));
355 }
356
365 void copyVectorFromHypre(X& v) {
366 OPM_TIMEBLOCK(prec_copy_vector_from_hypre);
367 HYPRE_Real* values = &(v[0][0]);
368 if (use_gpu_) {
369 HYPRE_IJVectorGetValues(x_hypre_, N_, indices_device_, x_values_device_);
370 hypre_TMemcpy(values, x_values_device_, HYPRE_Real, N_, HYPRE_MEMORY_HOST, HYPRE_MEMORY_DEVICE);
371 }
372 else {
373 HYPRE_IJVectorGetValues(x_hypre_, N_, indices_.data(), values);
374 }
375 }
376
377 const M& A_;
378 bool use_gpu_ = false;
379
380 HYPRE_Solver solver_ = nullptr;
381 HYPRE_IJMatrix A_hypre_ = nullptr;
382 HYPRE_ParCSRMatrix parcsr_A_ = nullptr;
383 HYPRE_IJVector x_hypre_ = nullptr;
384 HYPRE_IJVector b_hypre_ = nullptr;
385 HYPRE_ParVector par_x_ = nullptr;
386 HYPRE_ParVector par_b_ = nullptr;
387
388 std::vector<HYPRE_Int> ncols_;
389 std::vector<HYPRE_BigInt> rows_;
390 std::vector<HYPRE_BigInt> cols_;
391 HYPRE_Int* ncols_device_ = nullptr;
392 HYPRE_BigInt* rows_device_ = nullptr;
393 HYPRE_BigInt* cols_device_ = nullptr;
394 HYPRE_Real* values_device_ = nullptr;
395
396 std::vector<HYPRE_BigInt> indices_;
397 HYPRE_BigInt* indices_device_ = nullptr;
398 HYPRE_Int N_ = -1;
399 HYPRE_Int nnz_ = -1;
400
401 HYPRE_Real* x_values_device_ = nullptr;
402 HYPRE_Real* b_values_device_ = nullptr;
403};
404
405} // namespace Hypre
406
407#endif
Interface class adding the update() method to the preconditioner interface.
Definition: PreconditionerWithUpdate.hpp:32
Wrapper for Hypre's BoomerAMG preconditioner.
Definition: HyprePreconditioner.hpp:53
HyprePreconditioner(const M &A, const Opm::PropertyTree prm)
Constructor for the HyprePreconditioner class.
Definition: HyprePreconditioner.hpp:64
void pre(X &, Y &) override
Pre-processing step before applying the preconditioner.
Definition: HyprePreconditioner.hpp:203
Dune::SolverCategory::Category category() const override
Returns the solver category.
Definition: HyprePreconditioner.hpp:242
void post(X &) override
Post-processing step after applying the preconditioner.
Definition: HyprePreconditioner.hpp:234
bool hasPerfectUpdate() const override
Checks if the preconditioner has a perfect update.
Definition: HyprePreconditioner.hpp:251
void update() override
Updates the preconditioner with the current matrix values.
Definition: HyprePreconditioner.hpp:189
void apply(X &v, const Y &d) override
Applies the preconditioner to a vector.
Definition: HyprePreconditioner.hpp:214
~HyprePreconditioner()
Destructor for the HyprePreconditioner class.
Definition: HyprePreconditioner.hpp:157
Hierarchical collection of key/value pairs.
Definition: PropertyTree.hpp:39
T get(const std::string &key) const
Definition: HyprePreconditioner.hpp:40