Skip to content

Support left preconditioning in GMRES #417

Open
kakeueda wants to merge 26 commits intodevelopfrom
kakeru/preconditioner-ABBA
Open

Support left preconditioning in GMRES #417
kakeueda wants to merge 26 commits intodevelopfrom
kakeru/preconditioner-ABBA

Conversation

@kakeueda
Copy link
Collaborator

@kakeueda kakeueda commented Mar 4, 2026

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

  • Updated GMRES to support left preconditioning.
  • Added a PreconditionerUserMatrix class so users can define $M^{-1}$ explicitly.
  • Added unit tests for lu and usermatrix preconditioners.
  • Added a new command line option -p left (or right) for the GMRES solver (default: right).

Checklist

  • All tests pass (make test and make test_install per testing instructions). Code tested on
    • CPU backend
    • CUDA backend
    • HIP backend
  • I have manually run the non-experimental examples and verified that residuals are close to machine precision. (In your build directory run: ./examples/<your_example>.exe -h to get instructions how to run examples). Code tested on:
    • CPU backend
    • CUDA backend
    • HIP backend
  • Code compiles cleanly with flags -Wall -Wpedantic -Wconversion -Wextra.
  • The new code follows Re::Solve style guidelines.
  • There are unit tests for the new code.
  • The new code is documented.
  • The feature branch is rebased with respect to the target branch.
  • I have updated CHANGELOG.md to reflect the changes in this PR. If this is a minor PR that is part of a larger fix already included in the file, state so.

Further comments

Left preconditioning for FGMRES is more involved, so for now non-flexible GMRES only. If FGMRES is selected with -p left, the solver reports an error.

Copilot AI review requested due to automatic review settings March 4, 2026 21:32
@kakeueda kakeueda self-assigned this Mar 4, 2026
@kakeueda kakeueda requested review from Copilot and removed request for Copilot March 4, 2026 21:35
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 through SystemSolver::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.

Comment on lines +73 to +81
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();

Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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();
}

Copilot uses AI. Check for mistakes.
@kakeueda kakeueda marked this pull request as draft March 4, 2026 23:52
@JeffZ594
Copy link
Collaborator

JeffZ594 commented Mar 9, 2026

@kakeueda

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.

@kakeueda
Copy link
Collaborator Author

@kakeueda

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 .
@pelesh Are you okay with adding Eigen as a new dependency?

@JeffZ594
Copy link
Collaborator

@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.

@pelesh
Copy link
Collaborator

pelesh commented Mar 18, 2026

@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.

kakeueda and others added 3 commits March 19, 2026 15:06
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@kakeueda kakeueda force-pushed the kakeru/preconditioner-ABBA branch from e866f59 to 4d36a83 Compare March 19, 2026 06:09
@ORNL ORNL deleted a comment from Copilot AI Mar 19, 2026
@kakeueda
Copy link
Collaborator Author

kakeueda commented Mar 19, 2026

For now the default behavior for left preconditioning is to use the preconditioned residual $||M^{-1}(b - Ax)||$ for convergence check and the true residual $||(b - Ax)||$ for monitoring (final_residual_norm_ stores this).

@kakeueda
Copy link
Collaborator Author

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.

@kakeueda kakeueda marked this pull request as ready for review March 19, 2026 13:30
@kakeueda kakeueda requested review from pelesh and shakedregev March 19, 2026 13:31
Comment on lines +470 to +471
true_rnorm = vector_handler_->dot(vec_V_, vec_V_, memspace_);
true_rnorm = std::sqrt(true_rnorm);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why tranpose?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was meant to show a column vector, but maybe unnecessary

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Copy link
Collaborator

@shakedregev shakedregev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments. I need to test and I will change this to approve.

Copy link
Collaborator

@shakedregev shakedregev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work.

@kakeueda
Copy link
Collaborator Author

kakeueda commented Mar 19, 2026

returned 503: Egress is over the account limit.

I got this error in the test. Any idea what might be causing this? @pelesh

@nkoukpaizan
Copy link
Collaborator

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants