Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends ReSolve’s GMRES implementations to support left preconditioning (M^{-1}Ax = M^{-1}b), adds a user-supplied explicit preconditioner matrix via PreconditionerUserMatrix, and wires the feature through examples/tests and CLI (-p left|right).
Changes:
- Add
Preconditioner::getSide()/setSide()and plumb side selection throughSystemSolver::preconditionerSetup(...). - Implement left-preconditioned variants of the GMRES/Randomized GMRES iteration logic (with fallback to right preconditioning for flexible GMRES).
- Add unit/functionality tests and example updates for left preconditioning and the new user-matrix preconditioner.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
resolve/Preconditioner.hpp |
Adds preconditioning side API (left/right). |
resolve/Preconditioner.cpp |
Implements getSide() / setSide(). |
resolve/SystemSolver.hpp |
Extends preconditioner setup signature and exposes getPreconditioner(). |
resolve/SystemSolver.cpp |
Applies preconditioner setup with chosen side; exposes preconditioner getter. |
resolve/PreconditionerUserMatrix.hpp |
Declares user-provided explicit preconditioner matrix wrapper. |
resolve/PreconditionerUserMatrix.cpp |
Implements user-matrix preconditioner via MatrixHandler::matvec. |
resolve/LinSolverIterativeFGMRES.hpp/.cpp |
Implements left-preconditioning behavior (non-flexible GMRES) and warns/falls back for flexible GMRES. |
resolve/LinSolverIterativeRandFGMRES.hpp/.cpp |
Implements left-preconditioning behavior in randomized GMRES variant. |
resolve/PreconditionerLU.hpp |
Updates file header comment. |
resolve/PreconditionerLU.cpp |
Updates documentation strings/comments for LU preconditioner. |
resolve/CMakeLists.txt |
Adds new preconditioner source/header to build/install. |
tests/unit/preconditioner/* |
Adds LU and user-matrix preconditioner unit tests and CMake integration. |
tests/unit/CMakeLists.txt |
Adds preconditioner unit test subdirectory. |
tests/functionality/testSysGmres.cpp |
Adds -p parsing and conditional residual checking for left preconditioning. |
tests/functionality/CMakeLists.txt |
Adds functionality tests covering left-preconditioned runs. |
examples/sysGmres.cpp |
Adds -p parsing and passes side into preconditionerSetup. |
examples/randGmres.cpp |
Refactors preconditioner setup to use PreconditionerLU consistently. |
CHANGELOG.md |
Adds entry documenting left preconditioning + user-matrix preconditioner. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| matrix_handler_->matvec(B_, rhs, x, &ONE, &ZERO, memspace_); | ||
| return 0; | ||
| } | ||
|
|
||
| void PreconditionerUserMatrix::setMemorySpace() | ||
| { | ||
| bool is_matrix_handler_cuda = matrix_handler_->getIsCudaEnabled(); | ||
| bool is_matrix_handler_hip = matrix_handler_->getIsHipEnabled(); | ||
|
|
There was a problem hiding this comment.
PreconditionerUserMatrix::apply() unconditionally calls MatrixHandler::matvec() and then returns 0. If B_ or matrix_handler_ is null this will crash, and any nonzero status from matvec is currently ignored. Please add null checks (e.g., require setPrecMatrix() to have been called) and propagate/return the matvec status so callers can detect failures.
| matrix_handler_->matvec(B_, rhs, x, &ONE, &ZERO, memspace_); | |
| return 0; | |
| } | |
| void PreconditionerUserMatrix::setMemorySpace() | |
| { | |
| bool is_matrix_handler_cuda = matrix_handler_->getIsCudaEnabled(); | |
| bool is_matrix_handler_hip = matrix_handler_->getIsHipEnabled(); | |
| // Ensure that the matrix handler and preconditioner matrix have been set | |
| if (matrix_handler_ == nullptr || B_ == nullptr) | |
| { | |
| // Nonzero return value indicates failure (e.g., missing setup) | |
| return 1; | |
| } | |
| int status = matrix_handler_->matvec(B_, rhs, x, &ONE, &ZERO, memspace_); | |
| return status; | |
| } | |
| void PreconditionerUserMatrix::setMemorySpace() | |
| { | |
| bool is_matrix_handler_cuda = false; | |
| bool is_matrix_handler_hip = false; | |
| if (matrix_handler_ != nullptr) | |
| { | |
| is_matrix_handler_cuda = matrix_handler_->getIsCudaEnabled(); | |
| is_matrix_handler_hip = matrix_handler_->getIsHipEnabled(); | |
| } |
|
Thank you for making the unit tests for the preconditioners! I have code that implements regularization in Eigen that needs to be translated that would be better for testTripsProblem. I think these tests work better for testing just the preconditioners. |
Sure, let me first finish this PR and I'll take a look at your implementation . |
|
@kakeueda I believe Dr Peles does not want to use Eigen as a dependency. Right now I only use Eigen to calculate the SVD of the Hessenberg Matrix for GMRES but I believe there are alternatives like LAPACK that can also do this. |
Hessenberg matrix is dense, so SVD from LAPACK should work in this case. I wouldn't add Eigen as a dependency unless there is compelling enough reason to do so. |
Co-authored-by: Kakeru Ueda <kakecoco2580@icloud.com>
Co-authored-by: Kakeru Ueda <kakecoco2580@icloud.com>
Co-authored-by: Kakeru Ueda <kakecoco2580@icloud.com>
Fixed some small bugs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
e866f59 to
4d36a83
Compare
|
For now the default behavior for left preconditioning is to use the preconditioned residual |
|
This is outside the scope of this PR but 'solve()' in GMRES is starting to get a bit cluttered. It might be worth considering adding a base GMRES class and having the current RandFGMRES and regularized GMRES derive from it. We could also clean up the convergence check for GMRES which I believe this was mentioned before. |
| true_rnorm = vector_handler_->dot(vec_V_, vec_V_, memspace_); | ||
| true_rnorm = std::sqrt(true_rnorm); |
There was a problem hiding this comment.
maybe we should change this to true_res_norm? r can be anything, it's not clear from context (and there's no separator from norm)
There was a problem hiding this comment.
Agree, also changed bnorm to rhs_norm
| /** | ||
| * @brief Test apply() performs correct matvec with B. | ||
| * | ||
| * B = diag(2, 3, 4), x = [1, 1, 1]^T => y = [2, 3, 4]^T. |
There was a problem hiding this comment.
It was meant to show a column vector, but maybe unnecessary
There was a problem hiding this comment.
Worse, it's misleading. Vectors are by default column, unless you put a transpose. That's what didn't make sense to me. I knew it had to be a column, but this is the convention for row.
Co-authored-by: Shaked Regev <35384901+shakedregev@users.noreply.github.com>
shakedregev
left a comment
There was a problem hiding this comment.
Some minor comments. I need to test and I will change this to approve.
|
returned 503: Egress is over the account limit. I got this error in the test. Any idea what might be causing this? @pelesh |
Looks like a temporary failure in GH-Actions. It's working now! |
pelesh
left a comment
There was a problem hiding this comment.
Very nice work and good testing coverage of the new preconditioner features. A few things that need to be done before we merge this:
- The GMRES code needs to be better commented to explain the workflow.
- Use enums instead of strings to select preconditioner type.
- There should be a warning message if user tries to use e.g. FGMRES with unsupported preconditioner and the preconditioner choice should fall back to default.
Also see some more specific comments below.
| // Left preconditioning uses preconditioned norms for convergence | ||
| if (!flexible_ && preconditioner_->getSide() == "left") | ||
| { | ||
| // Left-preconditioned residual norm ||M^{-1}*(b - A*x0)|| | ||
| preconditioner_->apply(&vec_v, &vec_z); | ||
| vec_v.copyFromExternal(&vec_z, memspace_, memspace_); | ||
| res_norm = vector_handler_->dot(vec_V_, vec_V_, memspace_); | ||
| res_norm = std::sqrt(res_norm); | ||
|
|
||
| // Left-preconditioned right-hand side norm ||M^{-1}*b|| | ||
| vec_v.setData(rhs->getData(memspace_), memspace_); | ||
| preconditioner_->apply(&vec_v, &vec_z); | ||
| rhs_norm = vector_handler_->dot(&vec_z, &vec_z, memspace_); | ||
| rhs_norm = std::sqrt(rhs_norm); | ||
| } | ||
| else | ||
| { | ||
| res_norm = true_res_norm; | ||
| rhs_norm = true_rhs_norm; | ||
| } |
There was a problem hiding this comment.
Why is left preconditioner not applied to flexible GMRES?
There was a problem hiding this comment.
FGMRES is generally defined as a right-preconditioned method. Please see Section 9.4 (https://www.mat.uc.pt/~alma/aulas/NewTrends/Books/Saad.pd). One difficulty with left preconditioning in the flexible setting is that the residual
| vec_v.setData(rhs->getData(memspace_), memspace_); | ||
| preconditioner_->apply(&vec_v, &vec_z); |
There was a problem hiding this comment.
Why use setData function here? This method will be removed and should not be used in new contributions.
There was a problem hiding this comment.
Is there currently an equivalent to setData that we should use instead?
I thought we would still need setData because vec_v should be a view into the multivector vec_V_, not a copy.
| if (preconditioner_->getSide() == "right") | ||
| { |
There was a problem hiding this comment.
It is safer to use here enums instead of strings.
| real_type true_res_norm = 0.0; | ||
| real_type true_rhs_norm = 0.0; |
There was a problem hiding this comment.
What is "true" residual norm? This needs to be documented in the code.
There was a problem hiding this comment.
Added documentation explaining what the true residual is. This separates the residual used for convergence checking from the residual reported to the user. For right preconditioning the true residual
| if (preconditioner_->getSide() == "right") | ||
| { |
There was a problem hiding this comment.
I suggest using sitch statement and enums instead of strings and if-else statements here.
| // The correction is M^{-1}*vec_z for right preconditioning | ||
| preconditioner_->apply(&vec_z, &vec_v); | ||
| // Add the correction to x | ||
| vector_handler_->axpy(ONE, &vec_v, x, memspace_); | ||
| } | ||
| else | ||
| { | ||
| // Add the correction to x directly for left preconditioning. | ||
| vector_handler_->axpy(ONE, &vec_z, x, memspace_); |
There was a problem hiding this comment.
The comments in the code need to be more specific explaining steps clearly.
| // Report the true relative residual norm | ||
| final_residual_norm_ = true_res_norm / true_rhs_norm; |
There was a problem hiding this comment.
Really need to explain what is "true" residual norm.
| add_test(NAME sys_rand_count_gmres_left_cgs2_test | ||
| COMMAND $<TARGET_FILE:sys_rand_gmres_test.exe> "-x" "no" "-i" | ||
| "randgmres" "-g" "cgs2" "-s" "count" "-p" "left" | ||
| ) | ||
| add_test(NAME sys_rand_count_gmres_left_mgs_test | ||
| COMMAND $<TARGET_FILE:sys_rand_gmres_test.exe> "-x" "no" "-i" | ||
| "randgmres" "-g" "mgs" "-s" "count" "-p" "left" | ||
| ) | ||
| add_test(NAME sys_gmres_left_cgs2_test | ||
| COMMAND $<TARGET_FILE:sys_rand_gmres_test.exe> "-x" "no" "-i" "fgmres" | ||
| "-g" "cgs2" "-p" "left" | ||
| ) | ||
| add_test(NAME sys_gmres_left_mgs_test | ||
| COMMAND $<TARGET_FILE:sys_rand_gmres_test.exe> "-x" "no" "-i" "fgmres" | ||
| "-g" "mgs" "-p" "left" | ||
| ) |
There was a problem hiding this comment.
We should consider adding more combinations of different preconditioners and other solver options.
|
@pelesh Thank you for your suggestions.
|
Description
This change allows users to solve a left-preconditioned system$M^{-1}Ax = M^{-1}b$ using GMRES. It also adds a PreconditionerUserMatrix class.
@JeffZ594 My understanding is that we eventually want to add regularization to the GMRES solvers. We can follow up with another PR that adds regularization support with testTripsProblem.
Proposed changes
-p left (or right)for the GMRES solver (default: right).Checklist
make testandmake test_installper testing instructions). Code tested on./examples/<your_example>.exe -hto get instructions how to run examples). Code tested on:-Wall -Wpedantic -Wconversion -Wextra.Further comments
Non-flexible GMRES only. If FGMRES is selected with
-p left, the solver reports an error.