opm-simulators
AmgxInterface.hpp
1 /*
2  Copyright 2025 Equinor ASA
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_AMGX_INTERFACE_HPP
21 #define OPM_AMGX_INTERFACE_HPP
22 
23 #include <opm/common/ErrorMacros.hpp>
24 #include <opm/simulators/linalg/gpuistl/GpuSparseMatrixWrapper.hpp>
25 #include <opm/simulators/linalg/gpuistl/GpuVector.hpp>
26 #include <opm/simulators/linalg/gpuistl/detail/gpu_safe_call.hpp>
27 #include <opm/simulators/linalg/gpuistl/detail/gpu_type_detection.hpp>
28 
29 #include <amgx_c.h>
30 #include <cuda_runtime.h>
31 
32 #include <fmt/core.h>
33 
34 #include <string>
35 #include <vector>
36 
37 namespace Opm::gpuistl
38 {
39 
43 class AmgxError : public std::runtime_error
44 {
45 public:
46  explicit AmgxError(const std::string& msg)
47  : std::runtime_error(msg)
48  {
49  }
50 };
51 
62 inline std::string
64  AMGX_RC err, const std::string& expression, const std::string& file, const std::string& function, int line)
65 {
66  char amgx_err_msg[4096];
67  AMGX_get_error_string(err, amgx_err_msg, sizeof(amgx_err_msg));
68 
69  return fmt::format(
70  "AMGX error in expression: {}\nError code: {}\nError message: {}\nLocation: {}:{} in function {}",
71  expression,
72  static_cast<int>(err),
73  amgx_err_msg,
74  file,
75  line,
76  function);
77 }
78 
91 inline void
92 amgxSafeCall(AMGX_RC rc, const std::string& expression, const std::string& file, const std::string& function, int line)
93 {
94  if (rc != AMGX_RC_OK) {
95  throw AmgxError(getAmgxErrorMessage(rc, expression, file, function, line));
96  }
97 }
98 
107 #define OPM_AMGX_SAFE_CALL(expr) ::Opm::gpuistl::amgxSafeCall((expr), #expr, __FILE__, __func__, __LINE__)
108 
109 
122 {
123 public:
130  static void initialize()
131  {
132  OPM_AMGX_SAFE_CALL(AMGX_initialize());
133  }
134 
141  static void finalize()
142  {
143  OPM_AMGX_SAFE_CALL(AMGX_finalize());
144  }
145 
153  static AMGX_config_handle createConfig(const std::string& config_string)
154  {
155  AMGX_config_handle config;
156  OPM_AMGX_SAFE_CALL(AMGX_config_create(&config, config_string.c_str()));
157  return config;
158  }
159 
167  static AMGX_resources_handle createResources(AMGX_config_handle config)
168  {
169  AMGX_resources_handle resources;
170  OPM_AMGX_SAFE_CALL(AMGX_resources_create_simple(&resources, config));
171  return resources;
172  }
173 
183  static AMGX_solver_handle createSolver(AMGX_resources_handle resources, AMGX_Mode mode, AMGX_config_handle config)
184  {
185  AMGX_solver_handle solver;
186  OPM_AMGX_SAFE_CALL(AMGX_solver_create(&solver, resources, mode, config));
187  return solver;
188  }
189 
198  static AMGX_matrix_handle createMatrix(AMGX_resources_handle resources, AMGX_Mode mode)
199  {
200  AMGX_matrix_handle matrix;
201  OPM_AMGX_SAFE_CALL(AMGX_matrix_create(&matrix, resources, mode));
202  return matrix;
203  }
204 
213  static AMGX_vector_handle createVector(AMGX_resources_handle resources, AMGX_Mode mode)
214  {
215  AMGX_vector_handle vector;
216  OPM_AMGX_SAFE_CALL(AMGX_vector_create(&vector, resources, mode));
217  return vector;
218  }
219 
226  static void destroyConfig(AMGX_config_handle config)
227  {
228  if (config) {
229  OPM_AMGX_SAFE_CALL(AMGX_config_destroy(config));
230  }
231  }
232 
244  static void destroyResources(AMGX_resources_handle resources)
245  {
246  if (resources) {
247  OPM_AMGX_SAFE_CALL(AMGX_resources_destroy(resources));
248  }
249  }
250 
257  static void destroySolver(AMGX_solver_handle solver)
258  {
259  if (solver) {
260  OPM_AMGX_SAFE_CALL(AMGX_solver_destroy(solver));
261  }
262  }
263 
271  template <typename MatrixType>
272  static void destroyMatrix(AMGX_matrix_handle amgx_matrix, const MatrixType& matrix)
273  {
274  if constexpr (!is_gpu_type<MatrixType>::value) {
275  // Unpin memory for CPU matrices
276  using T = typename MatrixType::field_type;
277  const T* values = &(matrix[0][0][0][0]);
278  OPM_AMGX_SAFE_CALL(AMGX_unpin_memory(const_cast<T*>(values)));
279  }
280 
281  if (amgx_matrix) {
282  OPM_AMGX_SAFE_CALL(AMGX_matrix_destroy(amgx_matrix));
283  }
284  }
285 
292  static void destroyVector(AMGX_vector_handle vector)
293  {
294  if (vector) {
295  OPM_AMGX_SAFE_CALL(AMGX_vector_destroy(vector));
296  }
297  }
298 
309  template <typename T>
310  static void updateAmgxFromGpuVector(const GpuVector<T>& gpu_vec, AMGX_vector_handle amgx_vec)
311  {
312  // Get vector size from AMGX to verify compatibility
313  int n, block_dim;
314  OPM_AMGX_SAFE_CALL(AMGX_vector_get_size(amgx_vec, &n, &block_dim));
315 
316  if (n > 0 && static_cast<size_t>(n * block_dim) != gpu_vec.dim()) {
317  throw AmgxError(fmt::format("Vector size mismatch in updateAmgxFromGpuVector: "
318  "AMGX vector size {} vs. GpuVector size {}",
319  n * block_dim,
320  gpu_vec.dim()));
321  }
322 
323  // Get raw device pointer directly from GpuVector
324  const T* device_ptr = gpu_vec.data();
325 
326  // Update AMGX vector directly with the device pointer
327  OPM_AMGX_SAFE_CALL(AMGX_vector_upload(amgx_vec, n, block_dim, const_cast<T*>(device_ptr)));
328  }
329 
339  template <typename T>
340  static void updateGpuVectorFromAmgx(AMGX_vector_handle amgx_vec, GpuVector<T>& gpu_vec)
341  {
342  // Get vector size from AMGX to verify compatibility
343  int n, block_dim;
344  OPM_AMGX_SAFE_CALL(AMGX_vector_get_size(amgx_vec, &n, &block_dim));
345 
346  if (static_cast<size_t>(n * block_dim) != gpu_vec.dim()) {
347  throw AmgxError(fmt::format("Vector size mismatch in updateGpuVectorFromAmgx: "
348  "AMGX vector size {} vs. GpuVector size {}",
349  n * block_dim,
350  gpu_vec.dim()));
351  }
352 
353  // Get destination device pointer from GpuVector
354  T* dst_device_ptr = gpu_vec.data();
355 
356  // Download data directly from AMGX vector to GpuVector's device memory
357  OPM_AMGX_SAFE_CALL(AMGX_vector_download(amgx_vec, dst_device_ptr));
358  }
359 
369  template <typename VectorType>
370  static void transferVectorToAmgx(const VectorType& vec, AMGX_vector_handle amgx_vec)
371  {
372  if constexpr (is_gpu_type<VectorType>::value) {
373  // GPU implementation - device-to-device transfer
374  updateAmgxFromGpuVector(vec, amgx_vec);
375  } else {
376  // CPU implementation - host-to-device transfer
377  // For BlockVector, get dimensions
378  const int N = vec.size();
379  const int block_size = 1; // Assumes scalar vectors for CPU case
380 
381  // Upload vector to AMGX
382  OPM_AMGX_SAFE_CALL(AMGX_vector_upload(amgx_vec, N, block_size, &vec[0][0]));
383  }
384  }
385 
395  template <typename VectorType>
396  static void transferVectorFromAmgx(AMGX_vector_handle amgx_vec, VectorType& vec)
397  {
398  if constexpr (is_gpu_type<VectorType>::value) {
399  // GPU implementation - device-to-device transfer
400  updateGpuVectorFromAmgx(amgx_vec, vec);
401  } else {
402  // CPU implementation - device-to-host transfer
403  OPM_AMGX_SAFE_CALL(AMGX_vector_download(amgx_vec, &vec[0][0]));
404  }
405  }
406 
416  template <typename T>
418  AMGX_matrix_handle amgxMatrix)
419  {
420  // Get matrix dimensions and sparsity information
421  auto n = detail::to_int(gpuSparseMatrix.N());
422  auto nnz = detail::to_int(gpuSparseMatrix.nonzeroes());
423  auto block_size = detail::to_int(gpuSparseMatrix.blockSize());
424 
425  // Get device pointers directly from GpuSparseMatrix
426  const T* values = gpuSparseMatrix.getNonZeroValues().data();
427  const int* row_ptrs = gpuSparseMatrix.getRowIndices().data();
428  const int* col_indices = gpuSparseMatrix.getColumnIndices().data();
429 
430  // Update AMGX matrix with the device pointers
431  OPM_AMGX_SAFE_CALL(
432  AMGX_matrix_upload_all(amgxMatrix, n, nnz, block_size, block_size, row_ptrs, col_indices, values, nullptr));
433  }
434 
445  template <typename T>
447  AMGX_matrix_handle amgxMatrix)
448  {
449  // Get matrix dimensions and sparsity information
450  auto n = detail::to_int(gpuSparseMatrix.N());
451  auto nnz = detail::to_int(gpuSparseMatrix.nonzeroes());
452 
453  // Get device pointer to values directly from GpuSparseMatrix
454  const T* values = gpuSparseMatrix.getNonZeroValues().data();
455 
456  // Update coefficients in AMGX matrix with the device pointer
457  OPM_AMGX_SAFE_CALL(AMGX_matrix_replace_coefficients(amgxMatrix, n, nnz, values, nullptr));
458  }
459 
473  template <typename T>
474  static void updateGpuSparseMatrixFromAmgxMatrix(AMGX_matrix_handle amgxMatrix, GpuSparseMatrixWrapper<T>& gpuSparseMatrix)
475  {
476  // Get matrix dimensions from AMGX
477  int n, nnz, block_sizex, block_sizey;
478  OPM_AMGX_SAFE_CALL(AMGX_matrix_get_size(amgxMatrix, &n, &block_sizex, &block_sizey));
479  OPM_AMGX_SAFE_CALL(AMGX_matrix_get_nnz(amgxMatrix, &nnz));
480 
481  // Allocate temporary device memory for row pointers and column indices (required by AMGX API)
482  int* temp_row_ptrs;
483  int* temp_col_indices;
484  OPM_GPU_SAFE_CALL(cudaMalloc(&temp_row_ptrs, (n + 1) * sizeof(int)));
485  OPM_GPU_SAFE_CALL(cudaMalloc(&temp_col_indices, nnz * sizeof(int)));
486 
487  // Get device pointer to values in the GpuSparseMatrix
488  T* gpu_values = gpuSparseMatrix.getNonZeroValues().data();
489 
490  // AMGX requires a valid pointer for diagonal data (even if unused)
491  void* diag_data_ptr = nullptr;
492 
493  // Download matrix values directly from AMGX to GpuSparseMatrix
494  OPM_AMGX_SAFE_CALL(AMGX_matrix_download_all(amgxMatrix,
495  temp_row_ptrs,
496  temp_col_indices,
497  gpu_values,
498  &diag_data_ptr));
499 
500  // Clean up temporary device memory
501  OPM_GPU_SAFE_CALL(cudaFree(temp_row_ptrs));
502  OPM_GPU_SAFE_CALL(cudaFree(temp_col_indices));
503  }
504 
514  template <typename MatrixType>
515  static void initializeMatrix(const MatrixType& matrix, AMGX_matrix_handle amgx_matrix)
516  {
517  if constexpr (is_gpu_type<MatrixType>::value) {
518  // GPU implementation - device-to-device transfer
519  updateAmgxMatrixFromGpuSparseMatrix(matrix, amgx_matrix);
520  } else {
521  // CPU implementation - host-to-device transfer
522  auto N = detail::to_int(matrix.N());
523  auto nnz = detail::to_int(matrix.nonzeroes());
524  const int block_size = 1; // Assumes scalar matrices for CPU case
525 
526  // Setup sparsity pattern
527  std::vector<int> row_ptrs(N + 1);
528  std::vector<int> col_indices(nnz);
529 
530  int pos = 0;
531  row_ptrs[0] = 0;
532  for (auto row = matrix.begin(); row != matrix.end(); ++row) {
533  for (auto col = row->begin(); col != row->end(); ++col) {
534  col_indices[pos++] = col.index();
535  }
536  row_ptrs[row.index() + 1] = pos;
537  }
538 
539  // Get values pointer
540  using T = typename MatrixType::field_type;
541  const T* values = &(matrix[0][0][0][0]);
542  // Indexing explanation:
543  // matrix[0] - First row of the matrix
544  // [0] - First block in that row
545  // [0] - First row within the 1x1 block
546  // [0] - First column within the 1x1 block
547 
548  // Pin memory for CPU-to-GPU transfers
549  OPM_AMGX_SAFE_CALL(AMGX_pin_memory(const_cast<T*>(values), sizeof(T) * nnz * block_size * block_size));
550 
551  // Upload to AMGX
552  OPM_AMGX_SAFE_CALL(AMGX_matrix_upload_all(amgx_matrix,
553  N,
554  nnz,
555  block_size,
556  block_size,
557  row_ptrs.data(),
558  col_indices.data(),
559  const_cast<T*>(values),
560  nullptr));
561  }
562  }
563 
574  static void initializeVector(int N, int block_size, AMGX_vector_handle amgx_vector)
575  {
576  OPM_AMGX_SAFE_CALL(AMGX_vector_set_zero(amgx_vector, N, block_size));
577  }
578 
589  template <typename MatrixType>
590  static void updateMatrixValues(const MatrixType& matrix, AMGX_matrix_handle amgx_matrix)
591  {
592  if constexpr (is_gpu_type<MatrixType>::value) {
593  // GPU implementation - device-to-device transfer
595  } else {
596  // CPU implementation - host-to-device transfer
597  using T = typename MatrixType::field_type;
598  const T* values = &(matrix[0][0][0][0]);
599  OPM_AMGX_SAFE_CALL(AMGX_matrix_replace_coefficients(
600  amgx_matrix, matrix.N(), matrix.nonzeroes(), const_cast<T*>(values), nullptr));
601  }
602  }
603 
612  template <typename MatrixFieldType, typename VectorFieldType>
613  static AMGX_Mode determineAmgxMode()
614  {
615  if constexpr (std::is_same_v<MatrixFieldType, double> && std::is_same_v<VectorFieldType, double>) {
616  return AMGX_mode_dDDI;
617  } else if constexpr (std::is_same_v<MatrixFieldType, float> && std::is_same_v<VectorFieldType, double>) {
618  return AMGX_mode_dDFI;
619  } else if constexpr (std::is_same_v<MatrixFieldType, float> && std::is_same_v<VectorFieldType, float>) {
620  return AMGX_mode_dFFI;
621  } else {
622  OPM_THROW(std::runtime_error,
623  "Unsupported combination of matrix and vector types for AMGX: "
624  + std::string(typeid(MatrixFieldType).name()) + " and "
625  + std::string(typeid(VectorFieldType).name()));
626  }
627  }
628 };
629 
630 } // namespace Opm::gpuistl
631 
632 #endif // OPM_AMGX_INTERFACE_HPP
static void updateMatrixValues(const MatrixType &matrix, AMGX_matrix_handle amgx_matrix)
Update matrix values in AMGX.
Definition: AmgxInterface.hpp:590
static AMGX_config_handle createConfig(const std::string &config_string)
Create an AMGX config handle from a configuration string.
Definition: AmgxInterface.hpp:153
Type trait to detect if a type is a GPU type.
Definition: gpu_type_detection.hpp:40
The GpuSparseMatrixWrapper Checks CUDA/HIP version and dispatches a version either using the old or t...
Definition: gpu_type_detection.hpp:32
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
static void updateAmgxMatrixCoefficientsFromGpuSparseMatrix(const GpuSparseMatrixWrapper< T > &gpuSparseMatrix, AMGX_matrix_handle amgxMatrix)
Update only the coefficient values of an AMGX matrix from a GpuSparseMatrix.
Definition: AmgxInterface.hpp:446
std::size_t N() const
N returns the number of rows (which is equal to the number of columns)
Definition: GpuSparseMatrixWrapper.hpp:232
static AMGX_Mode determineAmgxMode()
Determine the appropriate AMGX mode based on matrix and vector field types.
Definition: AmgxInterface.hpp:613
GpuVector< int > & getRowIndices()
getRowIndices returns the row indices used to represent the BSR structure.
Definition: GpuSparseMatrixWrapper.hpp:271
Definition: gpu_type_detection.hpp:30
std::size_t nonzeroes() const
nonzeroes behaves as the Dune::BCRSMatrix::nonzeros() function and returns the number of non zero blo...
Definition: GpuSparseMatrixWrapper.hpp:241
static void initialize()
Initialize the AMGX library.
Definition: AmgxInterface.hpp:130
static AMGX_matrix_handle createMatrix(AMGX_resources_handle resources, AMGX_Mode mode)
Create an AMGX matrix.
Definition: AmgxInterface.hpp:198
Unified interface for AMGX operations with both CPU and GPU data structures.
Definition: AmgxInterface.hpp:121
static void destroyResources(AMGX_resources_handle resources)
Destroy an AMGX resources handle.
Definition: AmgxInterface.hpp:244
static void transferVectorToAmgx(const VectorType &vec, AMGX_vector_handle amgx_vec)
Transfer vector to AMGX from any vector type (CPU or GPU)
Definition: AmgxInterface.hpp:370
std::string getAmgxErrorMessage(AMGX_RC err, const std::string &expression, const std::string &file, const std::string &function, int line)
Get a descriptive error message for an AMGX error code.
Definition: AmgxInterface.hpp:63
static void initializeVector(int N, int block_size, AMGX_vector_handle amgx_vector)
Initialize an AMGX vector with zeros.
Definition: AmgxInterface.hpp:574
static void updateAmgxMatrixFromGpuSparseMatrix(const GpuSparseMatrixWrapper< T > &gpuSparseMatrix, AMGX_matrix_handle amgxMatrix)
Update an AMGX matrix from a GpuSparseMatrixWrapper (device-to-device transfer)
Definition: AmgxInterface.hpp:417
GpuVector< T > & getNonZeroValues()
getNonZeroValues returns the GPU vector containing the non-zero values (ordered by block) ...
Definition: GpuSparseMatrixWrapper.hpp:251
static AMGX_vector_handle createVector(AMGX_resources_handle resources, AMGX_Mode mode)
Create an AMGX vector.
Definition: AmgxInterface.hpp:213
static AMGX_resources_handle createResources(AMGX_config_handle config)
Create AMGX resources from a config.
Definition: AmgxInterface.hpp:167
static void destroyConfig(AMGX_config_handle config)
Destroy an AMGX config handle.
Definition: AmgxInterface.hpp:226
std::size_t blockSize() const
blockSize size of the blocks
Definition: GpuSparseMatrixWrapper.hpp:320
static void updateGpuVectorFromAmgx(AMGX_vector_handle amgx_vec, GpuVector< T > &gpu_vec)
Update a GpuVector from an AMGX vector (device-to-device transfer)
Definition: AmgxInterface.hpp:340
static void destroyMatrix(AMGX_matrix_handle amgx_matrix, const MatrixType &matrix)
Destroy an AMGX matrix handle.
Definition: AmgxInterface.hpp:272
static void updateAmgxFromGpuVector(const GpuVector< T > &gpu_vec, AMGX_vector_handle amgx_vec)
Update an AMGX vector from a GpuVector (device-to-device transfer)
Definition: AmgxInterface.hpp:310
A small, fixed‑dimension MiniVector class backed by std::array that can be used in both host and CUD...
Definition: AmgxInterface.hpp:37
static void finalize()
Finalize the AMGX library.
Definition: AmgxInterface.hpp:141
GpuVector< int > & getColumnIndices()
getColumnIndices returns the column indices used to represent the BSR structure.
Definition: GpuSparseMatrixWrapper.hpp:291
static void updateGpuSparseMatrixFromAmgxMatrix(AMGX_matrix_handle amgxMatrix, GpuSparseMatrixWrapper< T > &gpuSparseMatrix)
Update a GpuSparseMatrixWrapper from an AMGX matrix (device-to-device transfer)
Definition: AmgxInterface.hpp:474
static void destroyVector(AMGX_vector_handle vector)
Destroy an AMGX vector handle.
Definition: AmgxInterface.hpp:292
static AMGX_solver_handle createSolver(AMGX_resources_handle resources, AMGX_Mode mode, AMGX_config_handle config)
Create an AMGX solver.
Definition: AmgxInterface.hpp:183
void amgxSafeCall(AMGX_RC rc, const std::string &expression, const std::string &file, const std::string &function, int line)
Safe call wrapper for AMGX functions.
Definition: AmgxInterface.hpp:92
static void initializeMatrix(const MatrixType &matrix, AMGX_matrix_handle amgx_matrix)
Initialize an AMGX matrix from any matrix type (CPU or GPU)
Definition: AmgxInterface.hpp:515
Exception class for AMGX errors.
Definition: AmgxInterface.hpp:43
static void destroySolver(AMGX_solver_handle solver)
Destroy an AMGX solver handle.
Definition: AmgxInterface.hpp:257
static void transferVectorFromAmgx(AMGX_vector_handle amgx_vec, VectorType &vec)
Transfer vector from AMGX to any vector type (CPU or GPU)
Definition: AmgxInterface.hpp:396