Skip to content

Conversation

@timholy
Copy link
Contributor

@timholy timholy commented Nov 22, 2025

This is a proof-of-concept that in a For problems Ax=b, this allows the vector space
of x (vn) to be different from the vector space of b (vm); also for some bipartite problems

[A B; C D] [x; y] = [b; c]

it allows x and y to be of different types.

This PR implements the actual change only for CGLS for CGLS, LSQR, TRICG, TRIMR, and GPMR.
Extending to other solvers is left as an exercise for the reader.

Fixes #1037

@amontoison
Copy link
Member

We need to update a few things in cgls.jl too such that that the type of the workspace, the consistency check and the calls to allocate_if.

@codecov
Copy link

codecov bot commented Nov 22, 2025

Codecov Report

❌ Patch coverage is 97.80220% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.65%. Comparing base (9536ef7) to head (d4d1780).
⚠️ Report is 103 commits behind head on main.

Files with missing lines Patch % Lines
src/interface.jl 94.73% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1038      +/-   ##
==========================================
+ Coverage   94.68%   97.65%   +2.97%     
==========================================
  Files          45       49       +4     
  Lines        8027     9512    +1485     
==========================================
+ Hits         7600     9289    +1689     
+ Misses        427      223     -204     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@amontoison
Copy link
Member

amontoison commented Nov 22, 2025

We need a few more modifications in src/interfaces.jl and it should be good I think.

I should have done that in the previous breaking-release 😢

@amontoison
Copy link
Member

@timholy
Screenshot_20251123_002932_Samsung Internet

@timholy
Copy link
Contributor Author

timholy commented Nov 22, 2025

We need a few more modifications in src/interfaces.jl and it should be good I think.

I am sure you're right, but I'll need some hints about where. Note that

@eval krylov_solve!(workspace :: $workspace{T,FC,S}, $(def_args...); $(def_kwargs...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}} = $(krylov!)(workspace, $(args...); $(kwargs...))

seems unlikely to break, although perhaps they could be updated. Is it the subtypes of BlockKrylovWorkspace that you're concerned about?

Breakages...

Some of those appear to be "chains": for example, AdaptiveRegularization and DCISolver depend on RipQP which does indeed break. But fix RipQP and those two (I didn't check any more) might work fine.

What do you think about #1037 (comment)? If that would be acceptable and it succeeds in making it non-breaking, then could this be merged?

@amontoison
Copy link
Member

If we find a way to make it non-breaking, I am ok to merge it!

@timholy
Copy link
Contributor Author

timholy commented Nov 22, 2025

Locally this seems non-breaking.

@timholy
Copy link
Contributor Author

timholy commented Nov 23, 2025

I'm unclear on whether the macos failures are "my fault" (I don't think so). And, can I see the updated breakage report (i.e., like #1038 (comment)) myself? I clicked on the Breakage / upload job but I don't see anything I know how to interpret.

@amontoison
Copy link
Member

I'm unclear on whether the macos failures are "my fault" (I don't think so).

I don't think it is your fault. I will restart them now.

And, can I see the updated breakage report (i.e., like #1038 (comment)) myself? I clicked on the Breakage / upload job but I don't see anything I know how to interpret.

You need to go to "Summary" of the workflow Breakage to see the table.
https://github.com/JuliaSmoothOptimizers/Krylov.jl/actions/runs/19601101499?pr=1038

@timholy timholy marked this pull request as draft November 23, 2025 12:04
@timholy timholy marked this pull request as ready for review November 23, 2025 13:23
@timholy
Copy link
Contributor Author

timholy commented Nov 23, 2025

Now this is a bit more serious: in particular I added some tests. I also now support 5 methods, CGLS, LSQR, TRICG, TRIMR, and GPMR.

I only supported the latter 3 in the generic interface, because there you can specify both b and c. Supporting CGLS and LSQR in the generic interface would likely best be done through the types of the RHS and a warm-start vector, but neither method supports warm-starting.

(If/when this merges, it should be squashed.)

@timholy timholy closed this Nov 24, 2025
@timholy timholy reopened this Nov 24, 2025
@timholy
Copy link
Contributor Author

timholy commented Nov 24, 2025

Now I've squashed this down to a single commit and all tests pass locally. JSOSolvers using this branch also passes tests without modification.

@amontoison
Copy link
Member

amontoison commented Nov 24, 2025

@timholy Should I do a new patch release with just these modifications:

# TODO: in the next breaking release, change to KrylovWorkspace{T,FC,Sm,Sn} and delete the alias below
abstract type _KrylovWorkspace{T,FC,Sm,Sn} end

"Abstract type for using Krylov solvers in-place."
const KrylovWorkspace{T,FC,S} = _KrylovWorkspace{T,FC,S,S}

Such that we can update RipQP.jl and ensure with the breakage workflow here that this PR is non-breaking?

@timholy
Copy link
Contributor Author

timholy commented Nov 24, 2025

If I understand what you're asking (though perhaps I don't), that's already part of this PR. Here's the squashed commit message:

For problems `Ax=b`, this allows the vector space of `x` (`vn`) to be
different from the vector space of `b` (`vm`); also for some bipartite
problems

    [A B; C D] [x; y] = [b; c]

it allows `x` and `y` to be of different types.

The key change is to introduce `KrylovWorkspaceNext{T,FC,Sm,Sn}`, for
which `Sm` and `Sn` are the types of the length-`m` and length-`n`
vectors, respectively. `KrylovWorkspace{T,FC,S}` is now just a type
alias for `KrylovWorkspaceNext{T,FC,S,S}`. This is done for reasons of
backwards compatibility; in the next breaking release, we should rename
`KrylovWorkspaceNext` to `KrylovWorkspace`.

This PR exploits the new flexibility for CGLS, LSQR, TRICG, TRIMR, and
GPMR. Extending to other solvers is left as an exercise for the reader.

Fixes https://github.com/JuliaSmoothOptimizers/Krylov.jl/issues/1037

The breakage test seems to report no new breakages.

@amontoison
Copy link
Member

Oh ok, you did something even better than what I expected.

I thought that we needed to add KrylovWorkspaceNext first in a patch release. Update RipQP.jl to use it and then do another release of Krylov.jl with this PR.

It is a very neat solution 🙂

@amontoison
Copy link
Member

amontoison commented Nov 24, 2025

@gdalle Does it also solve your problem?

@timholy
Copy link
Contributor Author

timholy commented Nov 24, 2025

For @gdalle's benefit: since I didn't convert gmres, here is the patch that would be needed:

diff --git a/src/gmres.jl b/src/gmres.jl
index 5841c90b5..0ecbf7541 100644
--- a/src/gmres.jl
+++ b/src/gmres.jl
@@ -114,7 +114,7 @@ optargs_gmres = (:x0,)
 kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :itmax, :timemax, :verbose, :history, :callback, :iostream)

 @eval begin
-  function gmres!(workspace :: GmresWorkspace{T,FC,S}, $(def_args_gmres...); $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}}
+  function gmres!(workspace :: GmresWorkspace{T,FC,Sm,Sn}, $(def_args_gmres...); $(def_kwargs_gmres...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, Sm <: AbstractVector{FC}, Sn <: AbstractVector{FC}}

     # Timer
     start_time = time_ns()
@@ -132,12 +132,12 @@ kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :it

     # Check type consistency
     eltype(A) == FC || @warn "eltype(A) ≠ $FC. This could lead to errors or additional allocations in operator-vector products."
-    ktypeof(b) == S || error("ktypeof(b) must be equal to $S")
+    # ktypeof(b) == Sm || error("ktypeof(b) must be equal to $Sm")

     # Set up workspace.
-    allocate_if(!MisI  , workspace, :q , S, workspace.x)  # The length of q is n
-    allocate_if(!NisI  , workspace, :p , S, workspace.x)  # The length of p is n
-    allocate_if(restart, workspace, :Δx, S, workspace.x)  # The length of Δx is n
+    allocate_if(!MisI  , workspace, :q , Sn, workspace.x)  # The length of q is n
+    allocate_if(!NisI  , workspace, :p , Sn, workspace.x)  # The length of p is n
+    allocate_if(restart, workspace, :Δx, Sn, workspace.x)  # The length of Δx is n
     Δx, x, w, V, z = workspace.Δx, workspace.x, workspace.w, workspace.V, workspace.z
     c, s, R, stats = workspace.c, workspace.s, workspace.R, workspace.stats
     warm_start = workspace.warm_start
@@ -297,7 +297,7 @@ kwargs_gmres = (:M, :N, :ldiv, :restart, :reorthogonalization, :atol, :rtol, :it
         # Stopping conditions that do not depend on user input.
         # This is to guard against tolerances that are unreasonably small.
         resid_decrease_mach = (rNorm + one(T) ≤ one(T))
-
+
         # Update stopping criterion.
         user_requested_exit = callback(workspace) :: Bool
         resid_decrease_lim = rNorm ≤ ε
diff --git a/src/krylov_workspaces.jl b/src/krylov_workspaces.jl
index 34b4be6c2..1b5f0df93 100644
--- a/src/krylov_workspaces.jl
+++ b/src/krylov_workspaces.jl
@@ -2391,15 +2391,15 @@ The following outer constructors can be used to initialize this workspace:

 `memory` is set to `n` if the value given is larger than `n`.
 """
-mutable struct GmresWorkspace{T,FC,S} <: KrylovWorkspace{T,FC,S}
+mutable struct GmresWorkspace{T,FC,Sm,Sn} <: KrylovWorkspaceNext{T,FC,Sm,Sn}
   m          :: Int
   n          :: Int
-  Δx         :: S
-  x          :: S
-  w          :: S
-  p          :: S
-  q          :: S
-  V          :: Vector{S}
+  Δx         :: Sn
+  x          :: Sn
+  w          :: Sn
+  p          :: Sn
+  q          :: Sn
+  V          :: Vector{Sn}
   c          :: Vector{T}
   s          :: Vector{FC}
   z          :: Vector{FC}
@@ -2409,9 +2409,8 @@ mutable struct GmresWorkspace{T,FC,S} <: KrylovWorkspace{T,FC,S}
   stats      :: SimpleStats{T}
 end

-function GmresWorkspace(kc::KrylovConstructor; memory::Integer = 20)
-  S  = typeof(kc.vm)
-  FC = eltype(S)
+function GmresWorkspace(kc::KrylovConstructor{Sm,Sn}; memory::Integer = 20) where {Sm,Sn}
+  FC = eltype(Sn)
   T  = real(FC)
   m  = length(kc.vm)
   n  = length(kc.vn)
@@ -2421,13 +2420,13 @@ function GmresWorkspace(kc::KrylovConstructor; memory::Integer = 20)
   w  = similar(kc.vn)
   p  = similar(kc.vn_empty)
   q  = similar(kc.vn_empty)
-  V  = S[similar(kc.vn) for i = 1 : memory]
+  V  = Sn[similar(kc.vn) for i = 1 : memory]
   c  = Vector{T}(undef, memory)
   s  = Vector{FC}(undef, memory)
   z  = Vector{FC}(undef, memory)
   R  = Vector{FC}(undef, div(memory * (memory+1), 2))
   stats = SimpleStats(0, false, false, false, 0, T[], T[], T[], 0.0, "unknown")
-  workspace = GmresWorkspace{T,FC,S}(m, n, Δx, x, w, p, q, V, c, s, z, R, false, 0, stats)
+  workspace = GmresWorkspace{T,FC,Sm,Sn}(m, n, Δx, x, w, p, q, V, c, s, z, R, false, 0, stats)
   return workspace
 end

With that:

julia> using Krylov, LinearAlgebra, StaticArrays
Precompiling Krylov finished.
  1 dependency successfully precompiled in 4 seconds. 2 already precompiled.

julia> A = rand(2, 2)
2×2 Matrix{Float64}:
 0.991096  0.709605
 0.894476  0.0801216

julia> b = SVector{2}(rand(2))
2-element SVector{2, Float64} with indices SOneTo(2):
 0.48823909171743063
 0.1536619453559741

julia> workspace = GmresWorkspace(KrylovConstructor(b, rand(2)));

julia> gmres!(workspace, A, b)
GmresWorkspace{Float64, Float64, SVector{2, Float64}, Vector{Float64}}(2, 2, [0.0, 0.0], [0.1259115797700803, 0.5121841870497231], [5.551115123125783e-17, 0.0], [0.0, 0.0], [0.0, 0.0], [[0.9538732844068873, 0.30020952232535425], [-0.30020952232535403, 0.9538732844068875]], [0.941666626182997, 1.0], [0.33654712171274304, 1.45256928199235e-16], [0.27386626227694344, 0.4507589575043843], [1.4531063190880973, 0.1864285293723651, 0.38215837219907683], false, 2, SimpleStats
 niter: 2
 solved: true
 inconsistent: false
 indefinite: false
 npcCount: 0
 residuals: []
 Aresiduals: []
 κ₂(A): []
 timer: 107.95μs
 status: solution good enough given atol and rtol
)

julia> Krylov.solution(workspace)
2-element Vector{Float64}:
 0.1259115797700803
 0.5121841870497231

julia> A \ b
2-element Vector{Float64}:
 0.12591157977008033
 0.512184187049723

julia> using ComponentArrays

julia> b = ComponentVector(b1=rand(), b2=rand())
ComponentVector{Float64}(b1 = 0.15879398420905388, b2 = 0.013338194763834355)

julia> workspace = GmresWorkspace(KrylovConstructor(b, rand(2)));

julia> gmres!(workspace, A, b);

julia> Krylov.solution(workspace)
2-element Vector{Float64}:
 -0.0058668876892307275
  0.231972118028271

julia> A \ b
2-element Vector{Float64}:
 -0.005866887689230701
  0.23197211802827095

@timholy
Copy link
Contributor Author

timholy commented Nov 24, 2025

@amontoison force-push was because I discovered that I needed to update show for the new parametrization:

function show(io :: IO, workspace :: Union{KrylovWorkspace{T,FC,S}, BlockKrylovWorkspace{T,FC,S}}; show_stats :: Bool=true) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}}

Otherwise the new workspaces were not being pretty-printed.

You may want to play with this a bit before making a new release, just to make sure everything is working as expected. Or, if there is a type that is often used in "general" tests, we could convert that one over in hopes of discovering any additional breakages.

@amontoison
Copy link
Member

I don't understand why you need Sm in GmresWorkspace. GMRES only solves square systems of size n.

If the issue is to provide a right-hand side that is a different type of the S in the workspace, it is another problem.
Am I wrong?

@timholy
Copy link
Contributor Author

timholy commented Nov 24, 2025

I was just using GMRES because @gdalle did in #766 and #769. Agreed that this is a different issue.

For problems `Ax=b`, this allows the vector space of `x` (`vn`) to be
different from the vector space of `b` (`vm`); also for some bipartite
problems

    [A B; C D] [x; y] = [b; c]

it allows `x` and `y` to be of different types.

The key change is to introduce `KrylovWorkspaceNext{T,FC,Sm,Sn}`, for
which `Sm` and `Sn` are the types of the length-`m` and length-`n`
vectors, respectively. `KrylovWorkspace{T,FC,S}` is now just a type
alias for `KrylovWorkspaceNext{T,FC,S,S}`. This is done for reasons of
backwards compatibility; in the next breaking release, we should rename
`KrylovWorkspaceNext` to `KrylovWorkspace`.

This PR exploits the new flexibility for CGLS, LSQR, TRICG, TRIMR, and
GPMR. Extending to other solvers is left as an exercise for the reader.

Fixes JuliaSmoothOptimizers#1037
@amontoison
Copy link
Member

@amontoison force-push was because I discovered that I needed to update show for the new parametrization:

function show(io :: IO, workspace :: Union{KrylovWorkspace{T,FC,S}, BlockKrylovWorkspace{T,FC,S}}; show_stats :: Bool=true) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}}

Otherwise the new workspaces were not being pretty-printed.

You may want to play with this a bit before making a new release, just to make sure everything is working as expected. Or, if there is a type that is often used in "general" tests, we could convert that one over in hopes of discovering any additional breakages.

Yes, I agree.

Do you need these features soon or can you wait the second half of December?
I would like to update all the other solvers for rectangular systems and not a subset of them.

I also want to test that I don't break LinearSolve.jl and other packages outside JSO.

@timholy
Copy link
Contributor Author

timholy commented Nov 24, 2025

Just knowing this is on the schedule for merging soon is sufficient; I can go ahead and use it locally. The package I'm developing this for still has a couple of months of work left before it goes public, and it sounds like this will be merged by then.

@amontoison
Copy link
Member

amontoison commented Nov 24, 2025

I was just using GMRES because @gdalle did in #766 and #769. Agreed that this is a different issue.

@timholy The feature requested by Guillaume was not related to #766 / #769. He asked me the support of two different types for vectors of size n and size m (like you) because, depending on the AD packages, forward and reverse can be different vector types.

I didn't find a related issue. Maybe we discussed about somewhere else.

@gdalle
Copy link

gdalle commented Nov 24, 2025

I don't have time to review in detail but on the whole it looks like a clever solution to our common problem. It might even be enough to convince me to revert to Krylov.jl inside ImplicitDifferentiation.jl, and to use it for upcoming GPU work!

As for GMRES and square systems, I think we had a discussion elsewhere where I said that just because input and output have the same size doesn't mean they need to have the same type. The real key aspect here is whether a given solver is designed assuming that $A$ is an endomorphism (stays within the same space), which apparently is the case for GMRES.

@amontoison
Copy link
Member

Just knowing this is on the schedule for merging soon is sufficient; I can go ahead and use it locally. The package I'm developing this for still has a couple of months of work left before it goes public, and it sounds like this will be merged by then.

I will do a new release before the end of the year.

@amontoison
Copy link
Member

I don't have time to review in detail but on the whole it looks like a clever solution to our common problem. It might even be enough to convince me to revert to Krylov.jl inside ImplicitDifferentiation.jl, and to use it for upcoming GPU work!

Great!
@rveltz tried Krylov.jl a few months ago and it was a big gap in terms of performance with the other (Krylov) packages.

As for GMRES and square systems, I think we had a discussion elsewhere where I said that just because input and output have the same size doesn't mean they need to have the same type. The real key aspect here is whether a given solver is designed assuming that A is an endomorphism (stays within the same space), which apparently is the case for GMRES.

My issue is that you will do mul!(v, A, b) and then mul!(v2, A, v) in some solvers. So if b doesn't have the same type of v or v2 (in the square case), the user may have an error a little bit criptic.

I documented that we only need mul!(y::S, A, x::S) and I return a nice error if we have a case where y and x have a different type.

Adding an option to bypass these checks could be a solution for you Guillaume.

@timholy
Copy link
Contributor Author

timholy commented Nov 24, 2025

You may not need user intervention: you could use an error hint if user code throws an error and perhaps check for Sm !== Sn in deciding what type of hint to show.

In my case, the only two efficient combinations defined are mul!(y::Sm, A, x::Sn) and mul!(x::Sn, A', y::Sm) for Sm !== Sn. So any code that returns an error just because they are different will be problematic.

@gdalle
Copy link

gdalle commented Nov 24, 2025

I kind of agree here: why go through the trouble of throwing an error if the code might work? If it's gonna error, it will error downstream anyway, but being overly careful prevents unexpected uses of your library

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.

KrylovConstructor: vm and vn should allow different types

3 participants