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