opm-simulators
HyprePreconditioner.hpp
1 /*
2  Copyright 2024 SINTEF AS
3  Copyright 2024-2025 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>
26 #include <opm/simulators/linalg/PreconditionerWithUpdate.hpp>
27 #include <opm/simulators/linalg/PropertyTree.hpp>
28 #include <opm/simulators/linalg/hypreinterface/HypreInterface.hpp>
29 
30 #if HYPRE_USING_CUDA
31 #include <opm/simulators/linalg/gpuistl/detail/gpu_type_detection.hpp>
32 #elif HYPRE_USING_HIP
33 #include <opm/simulators/linalg/gpuistl_hip/detail/gpu_type_detection.hpp>
34 #endif
35 
36 #include <dune/common/fmatrix.hh>
37 #include <dune/istl/bcrsmatrix.hh>
38 
39 #include <HYPRE.h>
40 #include <HYPRE_krylov.h>
41 #include <HYPRE_parcsr_ls.h>
42 #include <_hypre_utilities.h>
43 
44 #include <numeric>
45 #include <vector>
46 
47 namespace Opm::linalg
48 {
49 
68 template <class M, class X, class Y, class Comm>
70 {
71 public:
73  using matrix_type = M;
75  using matrix_field_type = typename M::field_type;
77  using domain_type = X;
79  using range_type = Y;
81  using vector_field_type = typename X::field_type;
82 
92  HyprePreconditioner(const M& A, const Opm::PropertyTree prm, const Comm& comm)
93  : A_(A)
94  , comm_(comm)
95  {
96  OPM_TIMEBLOCK(prec_construct);
97  int size;
98  int rank;
99  MPI_Comm mpi_comm;
100  if constexpr (std::is_same_v<Comm, Dune::Amg::SequentialInformation>) {
101  mpi_comm = MPI_COMM_SELF;
102  } else {
103  mpi_comm = comm.communicator();
104  }
105  MPI_Comm_size(mpi_comm, &size);
106  MPI_Comm_rank(mpi_comm, &rank);
107  if (size > 1) {
108  assert(size == comm.communicator().size());
109  assert(rank == comm.communicator().rank());
110  }
111  // Set use_gpu_backend_ to user value if specified, otherwise match input type
112 #if HYPRE_USING_CUDA || HYPRE_USING_HIP
113  use_gpu_backend_ = prm.get<bool>("use_gpu", gpuistl::is_gpu_type<M>::value);
114 #endif
115 
116  // Initialize Hypre library with backend configuration
117  HypreInterface::initialize(use_gpu_backend_);
118 
119  // Create solver
121  HypreInterface::setSolverParameters(solver_, prm, use_gpu_backend_);
122 
123  // Setup parallel info and mappings
124  par_info_ = HypreInterface::setupHypreParallelInfo(comm_, A_);
125 
126  // Setup sparsity pattern
127  sparsity_pattern_ = HypreInterface::setupSparsityPattern(A_, par_info_, par_info_.owner_first);
128 
129  // Setup host arrays
131  sparsity_pattern_.ncols,
132  par_info_.local_dune_to_local_hypre,
133  par_info_.owner_first);
134 
135  // Create indices for vector operations - simple sequential indices for owned DOFs
136  host_arrays_.indices.resize(par_info_.N_owned);
137  std::iota(host_arrays_.indices.begin(), host_arrays_.indices.end(), par_info_.dof_offset);
138 
139  // Setup continuous vector values buffer - only needed for non-owner-first
140  if (!par_info_.owner_first) {
141  host_arrays_.continuous_vector_values.resize(par_info_.N_owned);
142  }
143 
144  // Allocate device arrays if using GPU backend
145  if (use_gpu_backend_) {
146 #if HYPRE_USING_CUDA || HYPRE_USING_HIP
147  device_arrays_.ncols_device = hypre_CTAlloc(HYPRE_Int, par_info_.N_owned, HYPRE_MEMORY_DEVICE);
148  device_arrays_.rows_device = hypre_CTAlloc(HYPRE_BigInt, par_info_.N_owned, HYPRE_MEMORY_DEVICE);
149  device_arrays_.cols_device = hypre_CTAlloc(HYPRE_BigInt, sparsity_pattern_.nnz, HYPRE_MEMORY_DEVICE);
150  device_arrays_.row_indexes_device = hypre_CTAlloc(HYPRE_Int, par_info_.N_owned, HYPRE_MEMORY_DEVICE);
151  device_arrays_.indices_device = hypre_CTAlloc(HYPRE_BigInt, par_info_.N_owned, HYPRE_MEMORY_DEVICE);
152  device_arrays_.vector_buffer_device = hypre_CTAlloc(HYPRE_Real, par_info_.N_owned, HYPRE_MEMORY_DEVICE);
153  if constexpr (!Opm::gpuistl::is_gpu_type<M>::value) {
154  // For CPU input and GPU backend we need to allocate space for transfering the matrix values
155  // Note that the buffer must be allocated with the number of nonzeroes in the matrix, not the
156  // sparsity_pattern.nnz because we need to copy the entire matrix values from the host to the device.
157  device_arrays_.matrix_buffer_device = hypre_CTAlloc(HYPRE_Real, A_.nonzeroes(), HYPRE_MEMORY_DEVICE);
158  }
159  // Copy data to device
160  hypre_TMemcpy(device_arrays_.ncols_device, sparsity_pattern_.ncols.data(), HYPRE_Int, par_info_.N_owned, HYPRE_MEMORY_DEVICE, HYPRE_MEMORY_HOST);
161  hypre_TMemcpy(device_arrays_.rows_device, sparsity_pattern_.rows.data(), HYPRE_BigInt, par_info_.N_owned, HYPRE_MEMORY_DEVICE, HYPRE_MEMORY_HOST);
162  hypre_TMemcpy(device_arrays_.cols_device, sparsity_pattern_.cols.data(), HYPRE_BigInt, sparsity_pattern_.nnz, HYPRE_MEMORY_DEVICE, HYPRE_MEMORY_HOST);
163  hypre_TMemcpy(device_arrays_.row_indexes_device, host_arrays_.row_indexes.data(), HYPRE_Int, par_info_.N_owned, HYPRE_MEMORY_DEVICE, HYPRE_MEMORY_HOST);
164  hypre_TMemcpy(device_arrays_.indices_device, host_arrays_.indices.data(), HYPRE_BigInt, par_info_.N_owned, HYPRE_MEMORY_DEVICE, HYPRE_MEMORY_HOST);
165 #endif
166  }
167 
168  // Create Hypre matrix and vectors
169  A_hypre_ = HypreInterface::createMatrix(par_info_.N_owned, par_info_.dof_offset, comm_);
170  x_hypre_ = HypreInterface::createVector(par_info_.N_owned, par_info_.dof_offset, comm_);
171  b_hypre_ = HypreInterface::createVector(par_info_.N_owned, par_info_.dof_offset, comm_);
172 
173  // Perform initial update
174  update();
175  }
176 
183  {
184  // Clean up device arrays if allocated
185  if (use_gpu_backend_) {
186 #if HYPRE_USING_CUDA || HYPRE_USING_HIP
187  if (device_arrays_.ncols_device) {
188  hypre_TFree(device_arrays_.ncols_device, HYPRE_MEMORY_DEVICE);
189  }
190  if (device_arrays_.rows_device) {
191  hypre_TFree(device_arrays_.rows_device, HYPRE_MEMORY_DEVICE);
192  }
193  if (device_arrays_.cols_device) {
194  hypre_TFree(device_arrays_.cols_device, HYPRE_MEMORY_DEVICE);
195  }
196  if (device_arrays_.row_indexes_device) {
197  hypre_TFree(device_arrays_.row_indexes_device, HYPRE_MEMORY_DEVICE);
198  }
199  if (device_arrays_.indices_device) {
200  hypre_TFree(device_arrays_.indices_device, HYPRE_MEMORY_DEVICE);
201  }
202  if (device_arrays_.vector_buffer_device) {
203  hypre_TFree(device_arrays_.vector_buffer_device, HYPRE_MEMORY_DEVICE);
204  }
205  if (device_arrays_.matrix_buffer_device) {
206  hypre_TFree(device_arrays_.matrix_buffer_device, HYPRE_MEMORY_DEVICE);
207  }
208 #endif
209  }
210 
215  }
216 
222  void update() override
223  {
224  OPM_TIMEBLOCK(prec_update);
225 
226  // Update matrix values using pre-allocated helper arrays
227  HypreInterface::updateMatrixValues(A_, A_hypre_, sparsity_pattern_,
228  host_arrays_, device_arrays_, use_gpu_backend_);
229 
230  // Get the underlying ParCSR matrix for setup
231  HYPRE_ParCSRMatrix parcsr_A;
232  HYPRE_SAFE_CALL(HYPRE_IJMatrixGetObject(A_hypre_, reinterpret_cast<void**>(&parcsr_A)));
233 
234  // Get the underlying ParVector objects
235  HYPRE_ParVector par_x, par_b;
236  HYPRE_SAFE_CALL(HYPRE_IJVectorGetObject(x_hypre_, reinterpret_cast<void**>(&par_x)));
237  HYPRE_SAFE_CALL(HYPRE_IJVectorGetObject(b_hypre_, reinterpret_cast<void**>(&par_b)));
238 
239  // Setup the solver
240  HYPRE_SAFE_CALL(HYPRE_BoomerAMGSetup(solver_, parcsr_A, par_b, par_x));
241  }
242 
251  void pre(X& v, Y& /*d*/) override
252  {
253  comm_.copyOwnerToAll(v, v); // From dune: make dirichlet values consistent ??
254  }
255 
266  void apply(X& v, const Y& d) override
267  {
268  OPM_TIMEBLOCK(prec_apply);
269 
270  // Transfer vectors to Hypre
271  HypreInterface::transferVectorToHypre(v, x_hypre_, host_arrays_,
272  device_arrays_, par_info_, use_gpu_backend_);
273  HypreInterface::transferVectorToHypre(d, b_hypre_, host_arrays_,
274  device_arrays_, par_info_, use_gpu_backend_);
275 
276  // Get the underlying ParCSR matrix and ParVector objects
277  HYPRE_ParCSRMatrix parcsr_A;
278  HYPRE_ParVector par_x, par_b;
279  HYPRE_SAFE_CALL(HYPRE_IJMatrixGetObject(A_hypre_, reinterpret_cast<void**>(&parcsr_A)));
280  HYPRE_SAFE_CALL(HYPRE_IJVectorGetObject(x_hypre_, reinterpret_cast<void**>(&par_x)));
281  HYPRE_SAFE_CALL(HYPRE_IJVectorGetObject(b_hypre_, reinterpret_cast<void**>(&par_b)));
282 
283  // Apply the preconditioner (one AMG V-cycle)
284  HYPRE_SAFE_CALL(HYPRE_BoomerAMGSolve(solver_, parcsr_A, par_b, par_x));
285 
286  // Transfer result back
287  HypreInterface::transferVectorFromHypre(x_hypre_, v, host_arrays_,
288  device_arrays_, par_info_, use_gpu_backend_);
289  // NB do we need to sync values to get correct values since a preconditioner
290  // consistent result (a operator apply should give unique).
291  comm_.copyOwnerToAll(v, v);
292  }
293 
301  void post(X& /*v*/) override
302  {
303  }
304 
310  Dune::SolverCategory::Category category() const override
311  {
312  return std::is_same_v<Comm, Dune::Amg::SequentialInformation> ? Dune::SolverCategory::sequential
313  : Dune::SolverCategory::overlapping;
314  }
315 
321  bool hasPerfectUpdate() const override
322  {
323  // The Hypre preconditioner can depend on the values of the matrix so it does not have perfect update.
324  // However, copying the matrix to Hypre requires to setup the solver again, so this is handled internally.
325  // So for ISTLSolver, we can return true.
326  return true;
327  }
328 
329 private:
330  // Reference to the input matrix
331  const M& A_;
332  const Comm& comm_;
333 
334  // Parallel information and sparsity pattern from HypreInterface
336  HypreInterface::SparsityPattern sparsity_pattern_;
337  HypreInterface::HostDataArrays host_arrays_;
338  HypreInterface::DeviceDataArrays device_arrays_;
339 
340  // Hypre handles
341  HYPRE_Solver solver_ = nullptr;
342  HYPRE_IJMatrix A_hypre_ = nullptr;
343  HYPRE_IJVector x_hypre_ = nullptr;
344  HYPRE_IJVector b_hypre_ = nullptr;
345 
346  // Backend configuration
347  bool use_gpu_backend_ = false;
348 };
349 
350 } // namespace Hypre
351 
352 #endif
HYPRE_Real * vector_buffer_device
Device buffer for vector operations Used when input type and backend are different, for transferring values between host and device.
Definition: HypreDataStructures.hpp:149
HyprePreconditioner(const M &A, const Opm::PropertyTree prm, const Comm &comm)
Constructor for the HyprePreconditioner class.
Definition: HyprePreconditioner.hpp:92
std::vector< HYPRE_BigInt > rows
Global row indices for owned rows (size: N_owned)
Definition: HypreDataStructures.hpp:91
HYPRE_Int nnz
Number of non-zero entries in matrix.
Definition: HypreDataStructures.hpp:97
void transferVectorFromHypre(HYPRE_IJVector hypre_vec, VectorType &vec, HostDataArrays &host_arrays, const DeviceDataArrays &device_arrays, const ParallelInfo &par_info, bool use_gpu_backend)
Transfer vector from Hypre to any vector type (CPU or GPU)
Definition: HypreInterface.hpp:186
Type trait to detect if a type is a GPU type.
Definition: gpu_type_detection.hpp:40
Parallel domain decomposition information for HYPRE-Dune interface.
Definition: HypreDataStructures.hpp:37
Y range_type
The range type of the preconditioner.
Definition: HyprePreconditioner.hpp:79
HYPRE_Int * ncols_device
Mirrors host data arrays.
Definition: HypreDataStructures.hpp:139
std::vector< HYPRE_BigInt > cols
Global column indices in CSR format (size: nnz)
Definition: HypreDataStructures.hpp:94
T get(const std::string &key) const
Retrieve property value given hierarchical property key.
Definition: PropertyTree.cpp:59
std::vector< HYPRE_Int > computeRowIndexes(const MatrixType &matrix, const std::vector< HYPRE_Int > &ncols, const std::vector< int > &local_dune_to_local_hypre, bool owner_first)
Compute row indexes for HYPRE_IJMatrixSetValues2.
Definition: HypreSetup.hpp:557
void destroySolver(HYPRE_Solver solver)
Destroy Hypre solver.
Definition: HypreSetup.hpp:214
Compressed Sparse Row (CSR) sparsity pattern for HYPRE matrix assembly.
Definition: HypreDataStructures.hpp:86
SparsityPattern setupSparsityPattern(const MatrixType &matrix, const ParallelInfo &par_info, bool owner_first)
Setup sparsity pattern from matrix (automatically detects CPU/GPU type)
Definition: HypreSetup.hpp:462
void apply(X &v, const Y &d) override
Applies the preconditioner to a vector.
Definition: HyprePreconditioner.hpp:266
HYPRE_IJMatrix createMatrix(HYPRE_Int N, HYPRE_Int dof_offset, const CommType &comm)
Create Hypre matrix.
Definition: HypreSetup.hpp:165
void destroyMatrix(HYPRE_IJMatrix matrix)
Destroy Hypre matrix.
Definition: HypreSetup.hpp:228
std::vector< HYPRE_Int > ncols
Non-zero entries per owned row (size: N_owned)
Definition: HypreDataStructures.hpp:88
void transferVectorToHypre(const VectorType &vec, HYPRE_IJVector hypre_vec, HostDataArrays &host_arrays, const DeviceDataArrays &device_arrays, const ParallelInfo &par_info, bool use_gpu_backend)
Transfer vector to Hypre from any vector type (CPU or GPU)
Definition: HypreInterface.hpp:166
Host arrays for HYPRE matrix and vector data transfers.
Definition: HypreDataStructures.hpp:106
bool hasPerfectUpdate() const override
Checks if the preconditioner has a perfect update.
Definition: HyprePreconditioner.hpp:321
void initialize(bool use_gpu_backend)
Initialize the Hypre library and set memory/execution policy.
X domain_type
The domain type of the preconditioner.
Definition: HyprePreconditioner.hpp:77
Interface class adding the update() method to the preconditioner interface.
Definition: PreconditionerWithUpdate.hpp:33
std::vector< int > local_dune_to_local_hypre
Mapping from local Dune indices to local HYPRE indices.
Definition: HypreDataStructures.hpp:44
Wrapper for Hypre&#39;s BoomerAMG preconditioner.
Definition: HyprePreconditioner.hpp:69
void destroyVector(HYPRE_IJVector vector)
Destroy Hypre vector.
Definition: HypreSetup.hpp:242
HYPRE_IJVector createVector(HYPRE_Int N, HYPRE_Int dof_offset, const CommType &comm)
Create Hypre vector.
Definition: HypreSetup.hpp:192
HYPRE_Solver createAMGSolver()
Create Hypre solver (BoomerAMG)
Definition: HypreSetup.hpp:112
Dune::SolverCategory::Category category() const override
Returns the solver category.
Definition: HyprePreconditioner.hpp:310
M matrix_type
The matrix type the preconditioner is for.
Definition: HyprePreconditioner.hpp:73
ParallelInfo setupHypreParallelInfo(const CommType &comm, const MatrixType &matrix)
Setup parallel information for Hypre (automatically detects serial/parallel)
Definition: HypreSetup.hpp:258
void setSolverParameters(HYPRE_Solver solver, const PropertyTree &prm, bool use_gpu_backend)
Set solver parameters from property tree.
Definition: HypreSetup.hpp:128
void update() override
Updates the preconditioner with the current matrix values.
Definition: HyprePreconditioner.hpp:222
HYPRE_Real * matrix_buffer_device
Device buffer for matrix values, only needed for CPU input + GPU backend.
Definition: HypreDataStructures.hpp:155
typename M::field_type matrix_field_type
The field type of the matrix.
Definition: HyprePreconditioner.hpp:75
bool owner_first
Whether owned DOFs appear first in local Dune ordering.
Definition: HypreDataStructures.hpp:77
GPU device memory arrays for HYPRE operations with GPU backend.
Definition: HypreDataStructures.hpp:137
std::vector< HYPRE_Real > continuous_vector_values
Temporary buffer for vector values in non-owner-first ordering.
Definition: HypreDataStructures.hpp:128
HYPRE_Int N_owned
Number of DOFs owned by this MPI process.
Definition: HypreDataStructures.hpp:62
std::vector< HYPRE_Int > row_indexes
Pre-computed row start indexes for HYPRE_IJMatrixSetValues2.
Definition: HypreDataStructures.hpp:113
void updateMatrixValues(const MatrixType &matrix, HYPRE_IJMatrix hypre_matrix, const SparsityPattern &sparsity_pattern, const HostDataArrays &host_arrays, const DeviceDataArrays &device_arrays, bool use_gpu_backend)
Update matrix values in Hypre.
Definition: HypreInterface.hpp:206
void post(X &) override
Post-processing step after applying the preconditioner.
Definition: HyprePreconditioner.hpp:301
std::vector< HYPRE_BigInt > indices
Global DOF indices for owned degrees of freedom.
Definition: HypreDataStructures.hpp:120
~HyprePreconditioner()
Destructor for HyprePreconditioner.
Definition: HyprePreconditioner.hpp:182
HYPRE_Int dof_offset
Global index offset for this process&#39;s owned DOFs.
Definition: HypreDataStructures.hpp:69
Hierarchical collection of key/value pairs.
Definition: PropertyTree.hpp:38
typename X::field_type vector_field_type
The field type of the vectors.
Definition: HyprePreconditioner.hpp:81
void pre(X &v, Y &) override
Pre-processing step before applying the preconditioner.
Definition: HyprePreconditioner.hpp:251