Refactor preconditioner implementation#167
Conversation
There was a problem hiding this comment.
Pull Request Overview
This PR refactors the preconditioner implementation by extracting the generation logic from a monolithic class into separate, focused header files. The main changes involve moving preconditioner-specific code into dedicated classes while introducing a centralized coarse solver generation routine for better code organization and maintainability.
- Extracted preconditioner implementations into dedicated header files (Jacobi, LU, Cholesky, ISAI, Multigrid)
- Added a centralized coarse solver generation function in CoarseSolver.cpp
- Introduced utility functions for Schwarz wrapping operations
Reviewed Changes
Copilot reviewed 12 out of 14 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Preconditioner/CoarseSolver.cpp | New implementation for centralized coarse solver generation |
| include/OGL/StoppingCriterion.hpp | Minor formatting improvement to debug message |
| include/OGL/Preconditioner/Schwarz.hpp | New utility functions for wrapping preconditioners with Schwarz methods |
| include/OGL/Preconditioner/PreconditionerWrapper.hpp | New base wrapper class definition |
| include/OGL/Preconditioner/Multigrid.hpp | New dedicated Multigrid preconditioner class |
| include/OGL/Preconditioner/LU.hpp | New LU factorization preconditioner class |
| include/OGL/Preconditioner/Jacobi.hpp | New Block Jacobi preconditioner class |
| include/OGL/Preconditioner/ISAI.hpp | New ISAI preconditioner class |
| include/OGL/Preconditioner/CoarseSolver.hpp | Header declaration for coarse solver generation function |
| include/OGL/Preconditioner/Cholesky.hpp | New Cholesky factorization preconditioner class |
| include/OGL/Preconditioner.hpp | Refactored main class to use new dedicated preconditioner classes |
| CMakeLists.txt | Added new source file to build configuration |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| if (solver == "CG") { | ||
| coarsest_solver = gko::share(cg::build() | ||
| .with_preconditioner(bjfac) | ||
| .with_criteria(solve_it, solve_red) | ||
| .on(exec)); | ||
| } | ||
| if (solver == "BiCGStab") { | ||
| coarsest_solver = gko::share(bicgstab::build() | ||
| .with_preconditioner(bjfac) | ||
| .with_criteria(solve_it, solve_red) | ||
| .on(exec)); | ||
| } | ||
| if (solver == "GMRES") { | ||
| coarsest_solver = gko::share(gmres::build() | ||
| .with_preconditioner(bjfac) | ||
| .with_criteria(solve_it, solve_red) | ||
| .on(exec)); | ||
| } | ||
| if (solver == "Jacobi") { | ||
| scalar relaxFac{ | ||
| solverDict.lookupOrDefault("relaxationFactor", scalar(0.9))}; | ||
| coarsest_solver = gko::share(ir::build() | ||
| .with_solver(bjfac) | ||
| .with_relaxation_factor(relaxFac) | ||
| .with_criteria(solve_it, solve_red) | ||
| .on(exec)); | ||
| } |
There was a problem hiding this comment.
This chain of if statements should use else-if to avoid unnecessary condition checks once a match is found. The current implementation continues checking all conditions even after finding a match.
| auto coarsening = d.lookupOrDefault<word>("coarsening", word("PGM")); | ||
| auto coarseWeight = d.lookupOrDefault("coarseWeight", scalar(0.1)); | ||
|
|
||
| if (coarsening == "fixed") { |
There was a problem hiding this comment.
This function has multiple return paths and incomplete logic. The function can return from either the 'fixed' or 'PGM' coarsening branches, but there's no return statement at the end for unhandled cases, which could lead to undefined behavior.
| std::shared_ptr<gko::LinOp> generate_factorization() const | ||
| { | ||
| if (factorization_ == "ILU") { |
There was a problem hiding this comment.
The function generate_factorization() has multiple return paths through if statements but no return statement at the end, which could lead to undefined behavior if none of the conditions are met.
| virtual std::shared_ptr<gko::LinOp> create() | ||
| { | ||
| if (type_ == "SPD") { | ||
| return dispatch_schwarz(mtx_, exec_, generate_precond_factory_spd(), | ||
| d_, verbose_); | ||
| } | ||
| if (type_ == "General") { | ||
| return dispatch_schwarz( | ||
| mtx_, exec_, generate_precond_factory_general(), d_, verbose_); | ||
| } | ||
| } |
There was a problem hiding this comment.
The function has multiple return paths but no return statement at the end, which could lead to undefined behavior if none of the type conditions are met.
| std::shared_ptr<gko::LinOp> generate_factorization() const | ||
| { | ||
| if (factorization_ == "IC") { |
There was a problem hiding this comment.
The function generate_factorization() has multiple return paths through if statements but no return statement at the end, which could lead to undefined behavior if none of the conditions are met.
9416a48 to
f29c729
Compare
82efe8e to
28ee971
Compare
78fea9e to
dc72d76
Compare
+ use coarse solver in schwarz, clean-up
+ format
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
| word frequencyMode = "optimizer"; | ||
| label minIter = minIter_; | ||
| label frequency = frequency_; | ||
| // in case of export_res all residuals need to be computed | ||
| if (!export_res) { | ||
| if (prev_solve_iters > 0 && adapt_minIter_ && prev_rel_cost > 0) { | ||
| minIter = prev_solve_iters * relaxationFactor_; | ||
| auto alpha = | ||
| sqrt(1.0 / (prev_solve_iters * (1.0 - relaxationFactor_)) * | ||
| prev_rel_cost); | ||
| frequency = min(norm_eval_limit_, max(1, label(1 / alpha))); | ||
| if (frequencyMode == "optimizer") { | ||
| auto alpha = sqrt( | ||
| 1.0 / (prev_solve_iters * (1.0 - relaxationFactor_)) * | ||
| prev_rel_cost); | ||
| frequency = min(norm_eval_limit_, max(1, label(1 / alpha))); | ||
| } | ||
| if (frequencyMode == "relative") { | ||
| frequency = label(prev_solve_iters * 0.075) + 1; |
| std::to_string(frequency); | ||
| std::to_string(frequency) + " prev_solve_iters " + | ||
| std::to_string(prev_solve_iters) + " adapt_minIter_ " + | ||
| std::to_string(adapt_minIter_) + " prev_rel_cost "; |
| auto start_axref = std::chrono::steady_clock::now(); | ||
| compute_Axref_dist(global_size[0], local_size[0], device_exec, gkomatrix, x, | ||
| Axref); | ||
|
|
||
| auto end_axref = std::chrono::steady_clock::now(); | ||
| auto delta_t_axref = std::chrono::duration_cast<std::chrono::microseconds>( | ||
| end_axref - start_axref) | ||
| .count() / | ||
| 1.0; |
| const AllToAllPattern allToAllIn, | ||
| label start_rank) | ||
| { | ||
| auto host_comm = exec_handler.get_host_comm(); |
| send_offsets.back() = scalar(allToAllIn.send_offsets.back()); | ||
| recv_offsets.back() = scalar(allToAllIn.recv_offsets.back()); |
| if (factorization_ == "ParILU") { | ||
| label iterations = d_.lookupOrDefault("iterations", label(5)); | ||
| auto factorization_factory = | ||
| gko::factorization::ParIlut<scalar, label>::build() | ||
| .with_skip_sorting(skip_sorting_) | ||
| .with_iterations(iterations) | ||
| .on(exec_); | ||
|
|
| type_(d.lookupOrDefault("type", word("general"))), | ||
| sparsityPower_(d.lookupOrDefault("sparsityPower", label(1))) | ||
| { | ||
| word msg = "Generate " + type_ + "ISAI" + | ||
| " preconditioner:\n\tsparsityPower: " + | ||
| std::to_string(sparsityPower_); | ||
| MLOG_0(verbose_, msg) | ||
| } | ||
|
|
||
| auto generate_precond_factory_spd() | ||
| { | ||
| return gko::preconditioner::Isai<gko::preconditioner::isai_type::spd, | ||
| scalar, label>::build() | ||
| .with_skip_sorting(skip_sorting_) | ||
| .with_sparsity_power(sparsityPower_) | ||
| .on(exec_); | ||
| } | ||
|
|
||
| auto generate_precond_factory_general() | ||
| { | ||
| return gko::preconditioner::Isai< | ||
| gko::preconditioner::isai_type::general, scalar, | ||
| label>::build() | ||
| .with_skip_sorting(skip_sorting_) | ||
| .with_sparsity_power(sparsityPower_) | ||
| .on(exec_); | ||
| } | ||
|
|
||
| virtual std::shared_ptr<gko::LinOp> create() | ||
| { | ||
| if (type_ == "SPD") { | ||
| auto b = generate_precond_factory_spd(); | ||
| return dispatch_schwarz( | ||
| mtx_, exec_, | ||
| gko::share( | ||
| b->generate(gko::as<RepartDistMatrix>(mtx_)->get_local())), | ||
| d_, verbose_); | ||
| } | ||
| if (type_ == "General") { | ||
| auto b = generate_precond_factory_general(); | ||
| return dispatch_schwarz( |
| /* @brief computes AllToAllPattern for repartioned communincator from global | ||
| * allToAll pattern by discarding all zero communication before and after the | ||
| * repartioner scope. |
| // NOTE in case of symmetric matrix 0 (upper) is same as 1 (lower) | ||
| // thus we can start at 1 |
| auto solveNormC = | ||
| d.lookupOrDefault("reductionCoarseSolver", label(1e-6)); | ||
| auto maxIterCoarse = d.lookupOrDefault("maxIterCoarse", label(50)); |
This PR refactors the preconditioner implementation by placing the generation into separate files. Additionally, a central coarse solver generation routine is added.