AmgxInterface.hpp
Go to the documentation of this file.
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>
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
37namespace Opm::gpuistl
38{
39
43class AmgxError : public std::runtime_error
44{
45public:
46 explicit AmgxError(const std::string& msg)
47 : std::runtime_error(msg)
48 {
49 }
50};
51
62inline 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
91inline void
92amgxSafeCall(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{
123public:
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>
417 static void updateAmgxMatrixFromGpuSparseMatrix(const GpuSparseMatrix<T>& gpuSparseMatrix,
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
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, GpuSparseMatrix<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
#define OPM_AMGX_SAFE_CALL(expr)
Macro to wrap AMGX function calls with error checking.
Definition: AmgxInterface.hpp:107
Exception class for AMGX errors.
Definition: AmgxInterface.hpp:44
AmgxError(const std::string &msg)
Definition: AmgxInterface.hpp:46
Unified interface for AMGX operations with both CPU and GPU data structures.
Definition: AmgxInterface.hpp:122
static void updateAmgxMatrixFromGpuSparseMatrix(const GpuSparseMatrix< T > &gpuSparseMatrix, AMGX_matrix_handle amgxMatrix)
Update an AMGX matrix from a GpuSparseMatrix (device-to-device transfer)
Definition: AmgxInterface.hpp:417
static AMGX_Mode determineAmgxMode()
Determine the appropriate AMGX mode based on matrix and vector field types.
Definition: AmgxInterface.hpp:613
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 destroyResources(AMGX_resources_handle resources)
Destroy an AMGX resources handle.
Definition: AmgxInterface.hpp:244
static AMGX_resources_handle createResources(AMGX_config_handle config)
Create AMGX resources from a config.
Definition: AmgxInterface.hpp:167
static void initialize()
Initialize the AMGX library.
Definition: AmgxInterface.hpp:130
static void updateGpuSparseMatrixFromAmgxMatrix(AMGX_matrix_handle amgxMatrix, GpuSparseMatrix< T > &gpuSparseMatrix)
Update a GpuSparseMatrix 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
static void updateAmgxMatrixCoefficientsFromGpuSparseMatrix(const GpuSparseMatrix< T > &gpuSparseMatrix, AMGX_matrix_handle amgxMatrix)
Update only the coefficient values of an AMGX matrix from a GpuSparseMatrix.
Definition: AmgxInterface.hpp:446
static void updateMatrixValues(const MatrixType &matrix, AMGX_matrix_handle amgx_matrix)
Update matrix values in AMGX.
Definition: AmgxInterface.hpp:590
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
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 destroyConfig(AMGX_config_handle config)
Destroy an AMGX config handle.
Definition: AmgxInterface.hpp:226
static AMGX_config_handle createConfig(const std::string &config_string)
Create an AMGX config handle from a configuration string.
Definition: AmgxInterface.hpp:153
static void destroySolver(AMGX_solver_handle solver)
Destroy an AMGX solver handle.
Definition: AmgxInterface.hpp:257
static AMGX_matrix_handle createMatrix(AMGX_resources_handle resources, AMGX_Mode mode)
Create an AMGX matrix.
Definition: AmgxInterface.hpp:198
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
static AMGX_vector_handle createVector(AMGX_resources_handle resources, AMGX_Mode mode)
Create an AMGX vector.
Definition: AmgxInterface.hpp:213
static void finalize()
Finalize the AMGX library.
Definition: AmgxInterface.hpp:141
static void destroyMatrix(AMGX_matrix_handle amgx_matrix, const MatrixType &matrix)
Destroy an AMGX matrix handle.
Definition: AmgxInterface.hpp:272
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
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
The GpuSparseMatrix class simple wrapper class for a CuSparse matrix.
Definition: GpuSparseMatrix.hpp:60
GpuVector< int > & getRowIndices()
getRowIndices returns the row indices used to represent the BSR structure.
Definition: GpuSparseMatrix.hpp:207
size_t nonzeroes() const
nonzeroes behaves as the Dune::BCRSMatrix::nonzeros() function and returns the number of non zero blo...
Definition: GpuSparseMatrix.hpp:166
size_t blockSize() const
blockSize size of the blocks
Definition: GpuSparseMatrix.hpp:273
size_t N() const
N returns the number of rows (which is equal to the number of columns)
Definition: GpuSparseMatrix.hpp:152
GpuVector< int > & getColumnIndices()
getColumnIndices returns the column indices used to represent the BSR structure.
Definition: GpuSparseMatrix.hpp:233
GpuVector< T > & getNonZeroValues()
getNonZeroValues returns the GPU vector containing the non-zero values (ordered by block)
Definition: GpuSparseMatrix.hpp:181
Definition: gpu_type_detection.hpp:30
#define OPM_GPU_SAFE_CALL(expression)
OPM_GPU_SAFE_CALL checks the return type of the GPU expression (function call) and throws an exceptio...
Definition: gpu_safe_call.hpp:150
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:52
Definition: AmgxInterface.hpp:38
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
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
Type trait to detect if a type is a GPU type.
Definition: gpu_type_detection.hpp:40