1 /* 2 This file implements an AmgX preconditioner in PETSc as part of PC. 3 */ 4 5 /* 6 Include files needed for the AmgX preconditioner: 7 pcimpl.h - private include file intended for use by all preconditioners 8 */ 9 10 #include <petsc/private/pcimpl.h> /*I "petscpc.h" I*/ 11 #include <petscdevice_cuda.h> 12 #include <amgx_c.h> 13 #include <limits> 14 #include <vector> 15 #include <algorithm> 16 #include <map> 17 #include <numeric> 18 #include "cuda_runtime.h" 19 20 enum class AmgXSmoother { 21 PCG, 22 PCGF, 23 PBiCGStab, 24 GMRES, 25 FGMRES, 26 JacobiL1, 27 BlockJacobi, 28 GS, 29 MulticolorGS, 30 MulticolorILU, 31 MulticolorDILU, 32 ChebyshevPoly, 33 NoSolver 34 }; 35 enum class AmgXAMGMethod { 36 Classical, 37 Aggregation 38 }; 39 enum class AmgXSelector { 40 Size2, 41 Size4, 42 Size8, 43 MultiPairwise, 44 PMIS, 45 HMIS 46 }; 47 enum class AmgXCoarseSolver { 48 DenseLU, 49 NoSolver 50 }; 51 enum class AmgXAMGCycle { 52 V, 53 W, 54 F, 55 CG, 56 CGF 57 }; 58 59 struct AmgXControlMap { 60 static const std::map<std::string, AmgXAMGMethod> AMGMethods; 61 static const std::map<std::string, AmgXSmoother> Smoothers; 62 static const std::map<std::string, AmgXSelector> Selectors; 63 static const std::map<std::string, AmgXCoarseSolver> CoarseSolvers; 64 static const std::map<std::string, AmgXAMGCycle> AMGCycles; 65 }; 66 67 const std::map<std::string, AmgXAMGMethod> AmgXControlMap::AMGMethods = { 68 {"CLASSICAL", AmgXAMGMethod::Classical }, 69 {"AGGREGATION", AmgXAMGMethod::Aggregation} 70 }; 71 72 const std::map<std::string, AmgXSmoother> AmgXControlMap::Smoothers = { 73 {"PCG", AmgXSmoother::PCG }, 74 {"PCGF", AmgXSmoother::PCGF }, 75 {"PBICGSTAB", AmgXSmoother::PBiCGStab }, 76 {"GMRES", AmgXSmoother::GMRES }, 77 {"FGMRES", AmgXSmoother::FGMRES }, 78 {"JACOBI_L1", AmgXSmoother::JacobiL1 }, 79 {"BLOCK_JACOBI", AmgXSmoother::BlockJacobi }, 80 {"GS", AmgXSmoother::GS }, 81 {"MULTICOLOR_GS", AmgXSmoother::MulticolorGS }, 82 {"MULTICOLOR_ILU", AmgXSmoother::MulticolorILU }, 83 {"MULTICOLOR_DILU", AmgXSmoother::MulticolorDILU}, 84 {"CHEBYSHEV_POLY", AmgXSmoother::ChebyshevPoly }, 85 {"NOSOLVER", AmgXSmoother::NoSolver } 86 }; 87 88 const std::map<std::string, AmgXSelector> AmgXControlMap::Selectors = { 89 {"SIZE_2", AmgXSelector::Size2 }, 90 {"SIZE_4", AmgXSelector::Size4 }, 91 {"SIZE_8", AmgXSelector::Size8 }, 92 {"MULTI_PAIRWISE", AmgXSelector::MultiPairwise}, 93 {"PMIS", AmgXSelector::PMIS }, 94 {"HMIS", AmgXSelector::HMIS } 95 }; 96 97 const std::map<std::string, AmgXCoarseSolver> AmgXControlMap::CoarseSolvers = { 98 {"DENSE_LU_SOLVER", AmgXCoarseSolver::DenseLU }, 99 {"NOSOLVER", AmgXCoarseSolver::NoSolver} 100 }; 101 102 const std::map<std::string, AmgXAMGCycle> AmgXControlMap::AMGCycles = { 103 {"V", AmgXAMGCycle::V }, 104 {"W", AmgXAMGCycle::W }, 105 {"F", AmgXAMGCycle::F }, 106 {"CG", AmgXAMGCycle::CG }, 107 {"CGF", AmgXAMGCycle::CGF} 108 }; 109 110 /* 111 Private context (data structure) for the AMGX preconditioner. 112 */ 113 struct PC_AMGX { 114 AMGX_solver_handle solver; 115 AMGX_config_handle cfg; 116 AMGX_resources_handle rsrc; 117 bool solve_state_init; 118 bool rsrc_init; 119 PetscBool verbose; 120 121 AMGX_matrix_handle A; 122 AMGX_vector_handle sol; 123 AMGX_vector_handle rhs; 124 125 MPI_Comm comm; 126 PetscMPIInt rank = 0; 127 PetscMPIInt nranks = 0; 128 int devID = 0; 129 130 void *lib_handle = 0; 131 std::string cfg_contents; 132 133 // Cached state for re-setup 134 PetscInt nnz; 135 PetscInt nLocalRows; 136 PetscInt nGlobalRows; 137 PetscInt bSize; 138 Mat localA; 139 const PetscScalar *values; 140 141 // AMG Control parameters 142 AmgXSmoother smoother; 143 AmgXAMGMethod amg_method; 144 AmgXSelector selector; 145 AmgXCoarseSolver coarse_solver; 146 AmgXAMGCycle amg_cycle; 147 PetscInt presweeps; 148 PetscInt postsweeps; 149 PetscInt max_levels; 150 PetscInt aggressive_levels; 151 PetscInt dense_lu_num_rows; 152 PetscScalar strength_threshold; 153 PetscBool print_grid_stats; 154 PetscBool exact_coarse_solve; 155 156 // Smoother control parameters 157 PetscScalar jacobi_relaxation_factor; 158 PetscScalar gs_symmetric; 159 }; 160 161 static PetscInt s_count = 0; 162 163 // Buffer of messages from AmgX 164 // Currently necessary hack before we adapt AmgX to print from single rank only 165 static std::string amgx_output{}; 166 167 // A print callback that allows AmgX to return status messages 168 static void print_callback(const char *msg, int length) 169 { 170 amgx_output.append(msg); 171 } 172 173 // Outputs messages from the AmgX message buffer and clears it 174 static PetscErrorCode amgx_output_messages(PC_AMGX *amgx) 175 { 176 PetscFunctionBegin; 177 // If AmgX output is enabled and we have a message, output it 178 if (amgx->verbose && !amgx_output.empty()) { 179 // Only a single rank to output the AmgX messages 180 PetscCall(PetscPrintf(amgx->comm, "AMGX: %s", amgx_output.c_str())); 181 182 // Note that all ranks clear their received output 183 amgx_output.clear(); 184 } 185 PetscFunctionReturn(PETSC_SUCCESS); 186 } 187 188 // XXX Need to add call in AmgX API that gracefully destroys everything 189 // without abort etc. 190 #define PetscCallAmgX(rc) \ 191 do { \ 192 AMGX_RC err = (rc); \ 193 char msg[4096]; \ 194 switch (err) { \ 195 case AMGX_RC_OK: \ 196 break; \ 197 default: \ 198 AMGX_get_error_string(err, msg, 4096); \ 199 SETERRQ(amgx->comm, PETSC_ERR_LIB, "%s", msg); \ 200 } \ 201 } while (0) 202 203 /* 204 PCSetUp_AMGX - Prepares for the use of the AmgX preconditioner 205 by setting data structures and options. 206 207 Input Parameter: 208 . pc - the preconditioner context 209 210 Application Interface Routine: PCSetUp() 211 212 Note: 213 The interface routine PCSetUp() is not usually called directly by 214 the user, but instead is called by PCApply() if necessary. 215 */ 216 static PetscErrorCode PCSetUp_AMGX(PC pc) 217 { 218 PC_AMGX *amgx = (PC_AMGX *)pc->data; 219 Mat Pmat = pc->pmat; 220 PetscBool is_dev_ptrs; 221 222 PetscFunctionBegin; 223 PetscCall(PetscObjectTypeCompareAny((PetscObject)Pmat, &is_dev_ptrs, MATAIJCUSPARSE, MATSEQAIJCUSPARSE, MATMPIAIJCUSPARSE, "")); 224 225 // At the present time, an AmgX matrix is a sequential matrix 226 // Non-sequential/MPI matrices must be adapted to extract the local matrix 227 bool partial_setup_allowed = (pc->setupcalled && pc->flag != DIFFERENT_NONZERO_PATTERN); 228 if (amgx->nranks > 1) { 229 if (partial_setup_allowed) { 230 PetscCall(MatMPIAIJGetLocalMat(Pmat, MAT_REUSE_MATRIX, &amgx->localA)); 231 } else { 232 PetscCall(MatMPIAIJGetLocalMat(Pmat, MAT_INITIAL_MATRIX, &amgx->localA)); 233 } 234 235 if (is_dev_ptrs) PetscCall(MatConvert(amgx->localA, MATSEQAIJCUSPARSE, MAT_INPLACE_MATRIX, &amgx->localA)); 236 } else { 237 amgx->localA = Pmat; 238 } 239 240 if (is_dev_ptrs) { 241 PetscCall(MatSeqAIJCUSPARSEGetArrayRead(amgx->localA, &amgx->values)); 242 } else { 243 PetscCall(MatSeqAIJGetArrayRead(amgx->localA, &amgx->values)); 244 } 245 246 if (!partial_setup_allowed) { 247 // Initialise resources and matrices 248 if (!amgx->rsrc_init) { 249 // Read configuration file 250 PetscCallAmgX(AMGX_config_create(&amgx->cfg, amgx->cfg_contents.c_str())); 251 PetscCallAmgX(AMGX_resources_create(&amgx->rsrc, amgx->cfg, &amgx->comm, 1, &amgx->devID)); 252 amgx->rsrc_init = true; 253 } 254 255 PetscCheck(!amgx->solve_state_init, amgx->comm, PETSC_ERR_PLIB, "AmgX solve state initialisation already called."); 256 PetscCallAmgX(AMGX_matrix_create(&amgx->A, amgx->rsrc, AMGX_mode_dDDI)); 257 PetscCallAmgX(AMGX_vector_create(&amgx->sol, amgx->rsrc, AMGX_mode_dDDI)); 258 PetscCallAmgX(AMGX_vector_create(&amgx->rhs, amgx->rsrc, AMGX_mode_dDDI)); 259 PetscCallAmgX(AMGX_solver_create(&amgx->solver, amgx->rsrc, AMGX_mode_dDDI, amgx->cfg)); 260 amgx->solve_state_init = true; 261 262 // Extract the CSR data 263 PetscBool done; 264 const PetscInt *colIndices; 265 const PetscInt *rowOffsets; 266 PetscCall(MatGetRowIJ(amgx->localA, 0, PETSC_FALSE, PETSC_FALSE, &amgx->nLocalRows, &rowOffsets, &colIndices, &done)); 267 PetscCheck(done, amgx->comm, PETSC_ERR_PLIB, "MatGetRowIJ was not successful"); 268 PetscCheck(amgx->nLocalRows < std::numeric_limits<int>::max(), PETSC_COMM_SELF, PETSC_ERR_PLIB, "AmgX restricted to int local rows but nLocalRows = %" PetscInt_FMT " > max<int>", amgx->nLocalRows); 269 270 if (is_dev_ptrs) { 271 PetscCallCUDA(cudaMemcpy(&amgx->nnz, &rowOffsets[amgx->nLocalRows], sizeof(int), cudaMemcpyDefault)); 272 } else { 273 amgx->nnz = rowOffsets[amgx->nLocalRows]; 274 } 275 276 PetscCheck(amgx->nnz < std::numeric_limits<int>::max(), PETSC_COMM_SELF, PETSC_ERR_PLIB, "Support for 64-bit integer nnz not yet implemented, nnz = %" PetscInt_FMT ".", amgx->nnz); 277 278 // Allocate space for some partition offsets 279 std::vector<PetscInt> partitionOffsets(amgx->nranks + 1); 280 281 // Fetch the number of local rows per rank 282 partitionOffsets[0] = 0; /* could use PetscLayoutGetRanges */ 283 PetscCallMPI(MPI_Allgather(&amgx->nLocalRows, 1, MPIU_INT, partitionOffsets.data() + 1, 1, MPIU_INT, amgx->comm)); 284 std::partial_sum(partitionOffsets.begin(), partitionOffsets.end(), partitionOffsets.begin()); 285 286 // Fetch the number of global rows 287 amgx->nGlobalRows = partitionOffsets[amgx->nranks]; 288 289 PetscCall(MatGetBlockSize(Pmat, &amgx->bSize)); 290 291 // XXX Currently constrained to 32-bit indices, to be changed in the future 292 // Create the distribution and upload the matrix data 293 AMGX_distribution_handle dist; 294 PetscCallAmgX(AMGX_distribution_create(&dist, amgx->cfg)); 295 PetscCallAmgX(AMGX_distribution_set_32bit_colindices(dist, true)); 296 PetscCallAmgX(AMGX_distribution_set_partition_data(dist, AMGX_DIST_PARTITION_OFFSETS, partitionOffsets.data())); 297 PetscCallAmgX(AMGX_matrix_upload_distributed(amgx->A, amgx->nGlobalRows, (int)amgx->nLocalRows, (int)amgx->nnz, amgx->bSize, amgx->bSize, rowOffsets, colIndices, amgx->values, NULL, dist)); 298 PetscCallAmgX(AMGX_solver_setup(amgx->solver, amgx->A)); 299 PetscCallAmgX(AMGX_vector_bind(amgx->sol, amgx->A)); 300 PetscCallAmgX(AMGX_vector_bind(amgx->rhs, amgx->A)); 301 302 PetscInt nlr = 0; 303 PetscCall(MatRestoreRowIJ(amgx->localA, 0, PETSC_FALSE, PETSC_FALSE, &nlr, &rowOffsets, &colIndices, &done)); 304 } else { 305 // The fast path for if the sparsity pattern persists 306 PetscCallAmgX(AMGX_matrix_replace_coefficients(amgx->A, amgx->nLocalRows, amgx->nnz, amgx->values, NULL)); 307 PetscCallAmgX(AMGX_solver_resetup(amgx->solver, amgx->A)); 308 } 309 310 if (is_dev_ptrs) { 311 PetscCall(MatSeqAIJCUSPARSERestoreArrayRead(amgx->localA, &amgx->values)); 312 } else { 313 PetscCall(MatSeqAIJRestoreArrayRead(amgx->localA, &amgx->values)); 314 } 315 PetscCall(amgx_output_messages(amgx)); 316 PetscFunctionReturn(PETSC_SUCCESS); 317 } 318 319 /* 320 PCApply_AMGX - Applies the AmgX preconditioner to a vector. 321 322 Input Parameters: 323 . pc - the preconditioner context 324 . b - rhs vector 325 326 Output Parameter: 327 . x - solution vector 328 329 Application Interface Routine: PCApply() 330 */ 331 static PetscErrorCode PCApply_AMGX(PC pc, Vec b, Vec x) 332 { 333 PC_AMGX *amgx = (PC_AMGX *)pc->data; 334 PetscScalar *x_; 335 const PetscScalar *b_; 336 PetscBool is_dev_ptrs; 337 338 PetscFunctionBegin; 339 PetscCall(PetscObjectTypeCompareAny((PetscObject)x, &is_dev_ptrs, VECCUDA, VECMPICUDA, VECSEQCUDA, "")); 340 341 if (is_dev_ptrs) { 342 PetscCall(VecCUDAGetArrayWrite(x, &x_)); 343 PetscCall(VecCUDAGetArrayRead(b, &b_)); 344 } else { 345 PetscCall(VecGetArrayWrite(x, &x_)); 346 PetscCall(VecGetArrayRead(b, &b_)); 347 } 348 349 PetscCallAmgX(AMGX_vector_upload(amgx->sol, amgx->nLocalRows, 1, x_)); 350 PetscCallAmgX(AMGX_vector_upload(amgx->rhs, amgx->nLocalRows, 1, b_)); 351 PetscCallAmgX(AMGX_solver_solve_with_0_initial_guess(amgx->solver, amgx->rhs, amgx->sol)); 352 353 AMGX_SOLVE_STATUS status; 354 PetscCallAmgX(AMGX_solver_get_status(amgx->solver, &status)); 355 PetscCall(PCSetErrorIfFailure(pc, static_cast<PetscBool>(status == AMGX_SOLVE_FAILED))); 356 PetscCheck(status != AMGX_SOLVE_FAILED, amgx->comm, PETSC_ERR_CONV_FAILED, "AmgX solver failed to solve the system! The error code is %d.", status); 357 PetscCallAmgX(AMGX_vector_download(amgx->sol, x_)); 358 359 if (is_dev_ptrs) { 360 PetscCall(VecCUDARestoreArrayWrite(x, &x_)); 361 PetscCall(VecCUDARestoreArrayRead(b, &b_)); 362 } else { 363 PetscCall(VecRestoreArrayWrite(x, &x_)); 364 PetscCall(VecRestoreArrayRead(b, &b_)); 365 } 366 PetscCall(amgx_output_messages(amgx)); 367 PetscFunctionReturn(PETSC_SUCCESS); 368 } 369 370 static PetscErrorCode PCReset_AMGX(PC pc) 371 { 372 PC_AMGX *amgx = (PC_AMGX *)pc->data; 373 374 PetscFunctionBegin; 375 if (amgx->solve_state_init) { 376 PetscCallAmgX(AMGX_solver_destroy(amgx->solver)); 377 PetscCallAmgX(AMGX_matrix_destroy(amgx->A)); 378 PetscCallAmgX(AMGX_vector_destroy(amgx->sol)); 379 PetscCallAmgX(AMGX_vector_destroy(amgx->rhs)); 380 if (amgx->nranks > 1) PetscCall(MatDestroy(&amgx->localA)); 381 PetscCall(amgx_output_messages(amgx)); 382 amgx->solve_state_init = false; 383 } 384 PetscFunctionReturn(PETSC_SUCCESS); 385 } 386 387 /* 388 PCDestroy_AMGX - Destroys the private context for the AmgX preconditioner 389 that was created with PCCreate_AMGX(). 390 391 Input Parameter: 392 . pc - the preconditioner context 393 394 Application Interface Routine: PCDestroy() 395 */ 396 static PetscErrorCode PCDestroy_AMGX(PC pc) 397 { 398 PC_AMGX *amgx = (PC_AMGX *)pc->data; 399 400 PetscFunctionBegin; 401 /* decrease the number of instances, only the last instance need to destroy resource and finalizing AmgX */ 402 if (s_count == 1) { 403 /* can put this in a PCAMGXInitializePackage method */ 404 PetscCheck(amgx->rsrc != nullptr, PETSC_COMM_SELF, PETSC_ERR_PLIB, "s_rsrc == NULL"); 405 PetscCallAmgX(AMGX_resources_destroy(amgx->rsrc)); 406 /* destroy config (need to use AMGX_SAFE_CALL after this point) */ 407 PetscCallAmgX(AMGX_config_destroy(amgx->cfg)); 408 PetscCallAmgX(AMGX_finalize_plugins()); 409 PetscCallAmgX(AMGX_finalize()); 410 PetscCallMPI(MPI_Comm_free(&amgx->comm)); 411 } else { 412 PetscCallAmgX(AMGX_config_destroy(amgx->cfg)); 413 } 414 s_count -= 1; 415 PetscCall(PetscFree(amgx)); 416 PetscFunctionReturn(PETSC_SUCCESS); 417 } 418 419 template <class T> 420 std::string map_reverse_lookup(const std::map<std::string, T> &map, const T &key) 421 { 422 for (auto const &m : map) { 423 if (m.second == key) return m.first; 424 } 425 return ""; 426 } 427 428 static PetscErrorCode PCSetFromOptions_AMGX(PC pc, PetscOptionItems *PetscOptionsObject) 429 { 430 PC_AMGX *amgx = (PC_AMGX *)pc->data; 431 constexpr int MAX_PARAM_LEN = 128; 432 char option[MAX_PARAM_LEN]; 433 434 PetscFunctionBegin; 435 PetscOptionsHeadBegin(PetscOptionsObject, "AmgX options"); 436 amgx->cfg_contents = "config_version=2,"; 437 amgx->cfg_contents += "determinism_flag=1,"; 438 439 // Set exact coarse solve 440 PetscCall(PetscOptionsBool("-pc_amgx_exact_coarse_solve", "AmgX AMG Exact Coarse Solve", "", amgx->exact_coarse_solve, &amgx->exact_coarse_solve, NULL)); 441 if (amgx->exact_coarse_solve) amgx->cfg_contents += "exact_coarse_solve=1,"; 442 443 amgx->cfg_contents += "solver(amg)=AMG,"; 444 445 // Set method 446 std::string def_amg_method = map_reverse_lookup(AmgXControlMap::AMGMethods, amgx->amg_method); 447 PetscCall(PetscStrncpy(option, def_amg_method.c_str(), sizeof(option))); 448 PetscCall(PetscOptionsString("-pc_amgx_amg_method", "AmgX AMG Method", "", option, option, MAX_PARAM_LEN, NULL)); 449 PetscCheck(AmgXControlMap::AMGMethods.count(option) == 1, PETSC_COMM_SELF, PETSC_ERR_PLIB, "AMG Method %s not registered for AmgX.", option); 450 amgx->amg_method = AmgXControlMap::AMGMethods.at(option); 451 amgx->cfg_contents += "amg:algorithm=" + std::string(option) + ","; 452 453 // Set cycle 454 std::string def_amg_cycle = map_reverse_lookup(AmgXControlMap::AMGCycles, amgx->amg_cycle); 455 PetscCall(PetscStrncpy(option, def_amg_cycle.c_str(), sizeof(option))); 456 PetscCall(PetscOptionsString("-pc_amgx_amg_cycle", "AmgX AMG Cycle", "", option, option, MAX_PARAM_LEN, NULL)); 457 PetscCheck(AmgXControlMap::AMGCycles.count(option) == 1, PETSC_COMM_SELF, PETSC_ERR_PLIB, "AMG Cycle %s not registered for AmgX.", option); 458 amgx->amg_cycle = AmgXControlMap::AMGCycles.at(option); 459 amgx->cfg_contents += "amg:cycle=" + std::string(option) + ","; 460 461 // Set smoother 462 std::string def_smoother = map_reverse_lookup(AmgXControlMap::Smoothers, amgx->smoother); 463 PetscCall(PetscStrncpy(option, def_smoother.c_str(), sizeof(option))); 464 PetscCall(PetscOptionsString("-pc_amgx_smoother", "AmgX Smoother", "", option, option, MAX_PARAM_LEN, NULL)); 465 PetscCheck(AmgXControlMap::Smoothers.count(option) == 1, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Smoother %s not registered for AmgX.", option); 466 amgx->smoother = AmgXControlMap::Smoothers.at(option); 467 amgx->cfg_contents += "amg:smoother(smooth)=" + std::string(option) + ","; 468 469 if (amgx->smoother == AmgXSmoother::JacobiL1 || amgx->smoother == AmgXSmoother::BlockJacobi) { 470 PetscCall(PetscOptionsScalar("-pc_amgx_jacobi_relaxation_factor", "AmgX AMG Jacobi Relaxation Factor", "", amgx->jacobi_relaxation_factor, &amgx->jacobi_relaxation_factor, NULL)); 471 amgx->cfg_contents += "smooth:relaxation_factor=" + std::to_string(amgx->jacobi_relaxation_factor) + ","; 472 } else if (amgx->smoother == AmgXSmoother::GS || amgx->smoother == AmgXSmoother::MulticolorGS) { 473 PetscCall(PetscOptionsScalar("-pc_amgx_gs_symmetric", "AmgX AMG Gauss Seidel Symmetric", "", amgx->gs_symmetric, &amgx->gs_symmetric, NULL)); 474 amgx->cfg_contents += "smooth:symmetric_GS=" + std::to_string(amgx->gs_symmetric) + ","; 475 } 476 477 // Set selector 478 std::string def_selector = map_reverse_lookup(AmgXControlMap::Selectors, amgx->selector); 479 PetscCall(PetscStrncpy(option, def_selector.c_str(), sizeof(option))); 480 PetscCall(PetscOptionsString("-pc_amgx_selector", "AmgX Selector", "", option, option, MAX_PARAM_LEN, NULL)); 481 PetscCheck(AmgXControlMap::Selectors.count(option) == 1, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Selector %s not registered for AmgX.", option); 482 483 // Double check that the user has selected an appropriate selector for the AMG method 484 if (amgx->amg_method == AmgXAMGMethod::Classical) { 485 PetscCheck(amgx->selector == AmgXSelector::PMIS || amgx->selector == AmgXSelector::HMIS, amgx->comm, PETSC_ERR_PLIB, "Chosen selector is not used for AmgX Classical AMG: selector=%s", option); 486 amgx->cfg_contents += "amg:interpolator=D2,"; 487 } else if (amgx->amg_method == AmgXAMGMethod::Aggregation) { 488 PetscCheck(amgx->selector == AmgXSelector::Size2 || amgx->selector == AmgXSelector::Size4 || amgx->selector == AmgXSelector::Size8 || amgx->selector == AmgXSelector::MultiPairwise, amgx->comm, PETSC_ERR_PLIB, "Chosen selector is not used for AmgX Aggregation AMG"); 489 } 490 amgx->selector = AmgXControlMap::Selectors.at(option); 491 amgx->cfg_contents += "amg:selector=" + std::string(option) + ","; 492 493 // Set presweeps 494 PetscCall(PetscOptionsInt("-pc_amgx_presweeps", "AmgX AMG Presweep Count", "", amgx->presweeps, &amgx->presweeps, NULL)); 495 amgx->cfg_contents += "amg:presweeps=" + std::to_string(amgx->presweeps) + ","; 496 497 // Set postsweeps 498 PetscCall(PetscOptionsInt("-pc_amgx_postsweeps", "AmgX AMG Postsweep Count", "", amgx->postsweeps, &amgx->postsweeps, NULL)); 499 amgx->cfg_contents += "amg:postsweeps=" + std::to_string(amgx->postsweeps) + ","; 500 501 // Set max levels 502 PetscCall(PetscOptionsInt("-pc_amgx_max_levels", "AmgX AMG Max Level Count", "", amgx->max_levels, &amgx->max_levels, NULL)); 503 amgx->cfg_contents += "amg:max_levels=" + std::to_string(amgx->max_levels) + ","; 504 505 // Set dense LU num rows 506 PetscCall(PetscOptionsInt("-pc_amgx_dense_lu_num_rows", "AmgX Dense LU Number of Rows", "", amgx->dense_lu_num_rows, &amgx->dense_lu_num_rows, NULL)); 507 amgx->cfg_contents += "amg:dense_lu_num_rows=" + std::to_string(amgx->dense_lu_num_rows) + ","; 508 509 // Set strength threshold 510 PetscCall(PetscOptionsScalar("-pc_amgx_strength_threshold", "AmgX AMG Strength Threshold", "", amgx->strength_threshold, &amgx->strength_threshold, NULL)); 511 amgx->cfg_contents += "amg:strength_threshold=" + std::to_string(amgx->strength_threshold) + ","; 512 513 // Set aggressive_levels 514 PetscCall(PetscOptionsInt("-pc_amgx_aggressive_levels", "AmgX AMG Presweep Count", "", amgx->aggressive_levels, &amgx->aggressive_levels, NULL)); 515 if (amgx->aggressive_levels > 0) amgx->cfg_contents += "amg:aggressive_levels=" + std::to_string(amgx->aggressive_levels) + ","; 516 517 // Set coarse solver 518 std::string def_coarse_solver = map_reverse_lookup(AmgXControlMap::CoarseSolvers, amgx->coarse_solver); 519 PetscCall(PetscStrncpy(option, def_coarse_solver.c_str(), sizeof(option))); 520 PetscCall(PetscOptionsString("-pc_amgx_coarse_solver", "AmgX CoarseSolver", "", option, option, MAX_PARAM_LEN, NULL)); 521 PetscCheck(AmgXControlMap::CoarseSolvers.count(option) == 1, PETSC_COMM_SELF, PETSC_ERR_PLIB, "CoarseSolver %s not registered for AmgX.", option); 522 amgx->coarse_solver = AmgXControlMap::CoarseSolvers.at(option); 523 amgx->cfg_contents += "amg:coarse_solver=" + std::string(option) + ","; 524 525 // Set max iterations 526 amgx->cfg_contents += "amg:max_iters=1,"; 527 528 // Set output control parameters 529 PetscCall(PetscOptionsBool("-pc_amgx_print_grid_stats", "AmgX Print Grid Stats", "", amgx->print_grid_stats, &amgx->print_grid_stats, NULL)); 530 531 if (amgx->print_grid_stats) amgx->cfg_contents += "amg:print_grid_stats=1,"; 532 amgx->cfg_contents += "amg:monitor_residual=0"; 533 534 // Set whether AmgX output will be seen 535 PetscCall(PetscOptionsBool("-pc_amgx_verbose", "Enable output from AmgX", "", amgx->verbose, &amgx->verbose, NULL)); 536 PetscOptionsHeadEnd(); 537 PetscFunctionReturn(PETSC_SUCCESS); 538 } 539 540 static PetscErrorCode PCView_AMGX(PC pc, PetscViewer viewer) 541 { 542 PC_AMGX *amgx = (PC_AMGX *)pc->data; 543 PetscBool iascii; 544 545 PetscFunctionBegin; 546 PetscCall(PetscObjectTypeCompare((PetscObject)viewer, PETSCVIEWERASCII, &iascii)); 547 if (iascii) { 548 std::string output_cfg(amgx->cfg_contents); 549 std::replace(output_cfg.begin(), output_cfg.end(), ',', '\n'); 550 PetscCall(PetscViewerASCIIPrintf(viewer, "\n%s\n", output_cfg.c_str())); 551 } 552 PetscFunctionReturn(PETSC_SUCCESS); 553 } 554 555 /*MC 556 PCAMGX - Interface to NVIDIA's AmgX algebraic multigrid 557 558 Options Database Keys: 559 + -pc_amgx_amg_method <CLASSICAL,AGGREGATION> - set the AMG algorithm to use 560 . -pc_amgx_amg_cycle <V,W,F,CG> - set the AMG cycle type 561 . -pc_amgx_smoother <PCG,PCGF,PBICGSTAB,GMRES,FGMRES,JACOBI_L1,BLOCK_JACOBI,GS,MULTICOLOR_GS,MULTICOLOR_ILU,MULTICOLOR_DILU,CHEBYSHEV_POLY,NOSOLVER> - set the AMG pre/post smoother 562 . -pc_amgx_jacobi_relaxation_factor - set the relaxation factor for Jacobi smoothing 563 . -pc_amgx_gs_symmetric - enforce symmetric Gauss-Seidel smoothing (only applies if GS smoothing is selected) 564 . -pc_amgx_selector <SIZE_2,SIZE_4,SIZE_8,MULTI_PAIRWISE,PMIS,HMIS> - set the AMG coarse selector 565 . -pc_amgx_presweeps - set the number of AMG pre-sweeps 566 . -pc_amgx_postsweeps - set the number of AMG post-sweeps 567 . -pc_amgx_max_levels - set the maximum number of levels in the AMG level hierarchy 568 . -pc_amgx_strength_threshold - set the strength threshold for the AMG coarsening 569 . -pc_amgx_aggressive_levels - set the number of levels (from the finest) that should apply aggressive coarsening 570 . -pc_amgx_coarse_solver <DENSE_LU_SOLVER,NOSOLVER> - set the coarse solve 571 . -pc_amgx_print_grid_stats - output the AMG grid hierarchy to stdout 572 - -pc_amgx_verbose - enable AmgX output 573 574 Level: intermediate 575 576 Note: 577 Implementation will accept host or device pointers, but good performance will require that the `KSP` is also GPU accelerated so that data is not frequently transferred between host and device. 578 579 .seealso: [](ch_ksp), `PCGAMG`, `PCHYPRE`, `PCMG`, `PCAmgXGetResources()`, `PCCreate()`, `PCSetType()`, `PCType` (for list of available types), `PC` 580 M*/ 581 582 PETSC_EXTERN PetscErrorCode PCCreate_AMGX(PC pc) 583 { 584 PC_AMGX *amgx; 585 586 PetscFunctionBegin; 587 PetscCall(PetscNew(&amgx)); 588 pc->ops->apply = PCApply_AMGX; 589 pc->ops->setfromoptions = PCSetFromOptions_AMGX; 590 pc->ops->setup = PCSetUp_AMGX; 591 pc->ops->view = PCView_AMGX; 592 pc->ops->destroy = PCDestroy_AMGX; 593 pc->ops->reset = PCReset_AMGX; 594 pc->data = (void *)amgx; 595 596 // Set the defaults 597 amgx->selector = AmgXSelector::PMIS; 598 amgx->smoother = AmgXSmoother::BlockJacobi; 599 amgx->amg_method = AmgXAMGMethod::Classical; 600 amgx->coarse_solver = AmgXCoarseSolver::DenseLU; 601 amgx->amg_cycle = AmgXAMGCycle::V; 602 amgx->exact_coarse_solve = PETSC_TRUE; 603 amgx->presweeps = 1; 604 amgx->postsweeps = 1; 605 amgx->max_levels = 100; 606 amgx->strength_threshold = 0.5; 607 amgx->aggressive_levels = 0; 608 amgx->dense_lu_num_rows = 1; 609 amgx->jacobi_relaxation_factor = 0.9; 610 amgx->gs_symmetric = PETSC_FALSE; 611 amgx->print_grid_stats = PETSC_FALSE; 612 amgx->verbose = PETSC_FALSE; 613 amgx->rsrc_init = false; 614 amgx->solve_state_init = false; 615 616 s_count++; 617 618 PetscCallCUDA(cudaGetDevice(&amgx->devID)); 619 if (s_count == 1) { 620 PetscCallAmgX(AMGX_initialize()); 621 PetscCallAmgX(AMGX_initialize_plugins()); 622 PetscCallAmgX(AMGX_register_print_callback(&print_callback)); 623 PetscCallAmgX(AMGX_install_signal_handler()); 624 } 625 /* This communicator is not yet known to this system, so we duplicate it and make an internal communicator */ 626 PetscCallMPI(MPI_Comm_dup(PetscObjectComm((PetscObject)pc), &amgx->comm)); 627 PetscCallMPI(MPI_Comm_size(amgx->comm, &amgx->nranks)); 628 PetscCallMPI(MPI_Comm_rank(amgx->comm, &amgx->rank)); 629 630 PetscCall(amgx_output_messages(amgx)); 631 PetscFunctionReturn(PETSC_SUCCESS); 632 } 633 634 /*@C 635 PCAmgXGetResources - get AMGx's internal resource object 636 637 Not Collective 638 639 Input Parameter: 640 . pc - the PC 641 642 Output Parameter: 643 . rsrc_out - pointer to the AMGx resource object 644 645 Level: advanced 646 647 .seealso: [](ch_ksp), `PCAMGX`, `PC`, `PCGAMG` 648 @*/ 649 PETSC_EXTERN PetscErrorCode PCAmgXGetResources(PC pc, void *rsrc_out) 650 { 651 PC_AMGX *amgx = (PC_AMGX *)pc->data; 652 653 PetscFunctionBegin; 654 if (!amgx->rsrc_init) { 655 // Read configuration file 656 PetscCallAmgX(AMGX_config_create(&amgx->cfg, amgx->cfg_contents.c_str())); 657 PetscCallAmgX(AMGX_resources_create(&amgx->rsrc, amgx->cfg, &amgx->comm, 1, &amgx->devID)); 658 amgx->rsrc_init = true; 659 } 660 *static_cast<AMGX_resources_handle *>(rsrc_out) = amgx->rsrc; 661 PetscFunctionReturn(PETSC_SUCCESS); 662 } 663