Skip to content

Commit d466ca6

Browse files
authored
Minres qlp npc (#1026)
* Add linesearch and nonpositive curvature detection to MINRES-QLP Introduces a 'linesearch' option to MINRES-QLP for inexact Newton methods, enabling detection and handling of nonpositive curvature directions. Updates workspace and statistics to support this feature, adds related documentation, and includes new tests for linesearch behavior and error handling.(not yet working ) * Improve nonpositive curvature detection in MINRES-QLP Refactored and enabled nonpositive curvature checks in MINRES-QLP with linesearch, correcting the logic for first iteration and updating residual computation. For Minres, Need to divide by β, since v has not been normalized yet.
1 parent 1069f79 commit d466ca6

File tree

4 files changed

+97
-5
lines changed

4 files changed

+97
-5
lines changed

src/krylov_workspaces.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,7 @@ mutable struct MinresQlpWorkspace{T,FC,S} <: KrylovWorkspace{T,FC,S}
624624
wₖ :: S
625625
M⁻¹vₖ₋₁ :: S
626626
M⁻¹vₖ :: S
627+
npc_dir :: S
627628
x :: S
628629
p :: S
629630
vₖ :: S
@@ -642,11 +643,12 @@ function MinresQlpWorkspace(kc::KrylovConstructor)
642643
wₖ = similar(kc.vn)
643644
M⁻¹vₖ₋₁ = similar(kc.vn)
644645
M⁻¹vₖ = similar(kc.vn)
646+
npc_dir = similar(kc.vn_empty)
645647
x = similar(kc.vn)
646648
p = similar(kc.vn)
647649
vₖ = similar(kc.vn_empty)
648650
stats = SimpleStats(0, false, false, false, 0, T[], T[], T[], 0.0, "unknown")
649-
workspace = MinresQlpWorkspace{T,FC,S}(m, n, Δx, wₖ₋₁, wₖ, M⁻¹vₖ₋₁, M⁻¹vₖ, x, p, vₖ, false, stats)
651+
workspace = MinresQlpWorkspace{T,FC,S}(m, n, Δx, wₖ₋₁, wₖ, M⁻¹vₖ₋₁, M⁻¹vₖ, npc_dir, x, p, vₖ, false, stats)
650652
return workspace
651653
end
652654

@@ -661,9 +663,10 @@ function MinresQlpWorkspace(m::Integer, n::Integer, S::Type)
661663
x = S(undef, n)
662664
p = S(undef, n)
663665
vₖ = S(undef, 0)
666+
npc_dir = S(undef, 0)
664667
S = isconcretetype(S) ? S : typeof(x)
665668
stats = SimpleStats(0, false, false, false, 0, T[], T[], T[], 0.0, "unknown")
666-
workspace = MinresQlpWorkspace{T,FC,S}(m, n, Δx, wₖ₋₁, wₖ, M⁻¹vₖ₋₁, M⁻¹vₖ, x, p, vₖ, false, stats)
669+
workspace = MinresQlpWorkspace{T,FC,S}(m, n, Δx, wₖ₋₁, wₖ, M⁻¹vₖ₋₁, M⁻¹vₖ, npc_dir, x, p, vₖ, false, stats)
667670
return workspace
668671
end
669672

src/minres.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,8 @@ kwargs_minres = (:M, :ldiv, :linesearch ,:λ, :atol, :rtol, :etol, :conlim, :itm
374374
if linesearch
375375
# compute the residual vector and store it in npc_dir
376376
kscal!(n, sn * sn, npc_dir) # npc_dir = sn * sn * npc_dir
377-
kaxpy!(n, -ϕbar * cs, v, npc_dir) # npc_dir = npc_dir - ϕbar * cs * v
377+
# Need to divide by β, since v has not been normalized yet.
378+
kaxpy!(n, -ϕbar * cs / β, v, npc_dir) # npc_dir = npc_dir - ϕbar * cs * v
378379
end
379380

380381
# Update x.

src/minres_qlp.jl

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
#
1111
# S.-C. T. Choi and M. A. Saunders, Algorithm 937: MINRES-QLP for symmetric and Hermitian linear equations and least-squares problems.
1212
# ACM Transactions on Mathematical Software, 40(2), pp. 1--12, 2014.
13+
#
14+
# Negative curvature detection follows
15+
# Liu, Yang, and Roosta, MINRES: from negative curvature detection to monotonicity properties,
16+
# SIAM Journal on Optimization, 32(4), pp. 2636--2661, 2022.
1317
#
1418
# Alexis Montoison, <[email protected]>
1519
# Montreal, September 2019.
@@ -19,7 +23,7 @@ export minres_qlp, minres_qlp!
1923
"""
2024
(x, stats) = minres_qlp(A, b::AbstractVector{FC};
2125
M=I, ldiv::Bool=false, Artol::T=√eps(T),
22-
λ::T=zero(T), atol::T=√eps(T),
26+
linesearch::Bool=false, λ::T=zero(T), atol::T=√eps(T),
2327
rtol::T=√eps(T), itmax::Int=0,
2428
timemax::Float64=Inf, verbose::Int=0, history::Bool=false,
2529
callback=workspace->false, iostream::IO=kstdout)
@@ -60,6 +64,9 @@ For an in-place variant that reuses memory across solves, see [`minres_qlp!`](@r
6064
* `atol`: absolute stopping tolerance based on the residual norm;
6165
* `rtol`: relative stopping tolerance based on the residual norm;
6266
* `Artol`: relative stopping tolerance based on the Aᴴ-residual norm;
67+
* `linesearch`: if `true`, indicate that the solution is to be used in an inexact Newton method with linesearch. If `true` and nonpositive curvature is detected, the behavior depends on the iteration:
68+
– at iteration k = 1, the solver takes the right-hand side (i.e., the preconditioned negative gradient) as the current solution. The same search direction is returned in `workspace.npc_dir`, and `stats.npcCount` is set to 1;
69+
– at iteration k > 1, the solver returns the solution from iteration k – 1, the residual from iteration k is a nonpositive curvature direction stored in `stats.npc_dir` and `stats.npcCount` is set to 1;
6370
* `itmax`: the maximum number of iterations. If `itmax=0`, the default number of iterations is set to `2n`;
6471
* `timemax`: the time limit in seconds;
6572
* `verbose`: additional details can be displayed if verbose mode is enabled (verbose > 0). Information will be displayed every `verbose` iterations;
@@ -77,6 +84,8 @@ For an in-place variant that reuses memory across solves, see [`minres_qlp!`](@r
7784
* S.-C. T. Choi, *Iterative methods for singular linear equations and least-squares problems*, Ph.D. thesis, ICME, Stanford University, 2006.
7885
* S.-C. T. Choi, C. C. Paige and M. A. Saunders, [*MINRES-QLP: A Krylov subspace method for indefinite or singular symmetric systems*](https://doi.org/10.1137/100787921), SIAM Journal on Scientific Computing, Vol. 33(4), pp. 1810--1836, 2011.
7986
* S.-C. T. Choi and M. A. Saunders, [*Algorithm 937: MINRES-QLP for symmetric and Hermitian linear equations and least-squares problems*](https://doi.org/10.1145/2527267), ACM Transactions on Mathematical Software, 40(2), pp. 1--12, 2014.
87+
* Y. Liu and F. Roosta, [*MINRES: From Negative Curvature Detection to Monotonicity Properties*](https://doi.org/10.1137/21M143666X), SIAM Journal on Optimization, 32(4), pp. 2636--2661, 2022.
88+
8089
"""
8190
function minres_qlp end
8291

@@ -100,6 +109,7 @@ def_optargs_minres_qlp = (:(x0::AbstractVector),)
100109

101110
def_kwargs_minres_qlp = (:(; M = I ),
102111
:(; ldiv::Bool = false ),
112+
:(; linesearch::Bool = false ),
103113
:(; λ::T = zero(T) ),
104114
:(; atol::T = eps(T) ),
105115
:(; rtol::T = eps(T) ),
@@ -115,7 +125,7 @@ def_kwargs_minres_qlp = extract_parameters.(def_kwargs_minres_qlp)
115125

116126
args_minres_qlp = (:A, :b)
117127
optargs_minres_qlp = (:x0,)
118-
kwargs_minres_qlp = (:M, :ldiv, , :atol, :rtol, :Artol, :itmax, :timemax, :verbose, :history, :callback, :iostream)
128+
kwargs_minres_qlp = (:M, :ldiv, :linesearch, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :verbose, :history, :callback, :iostream)
119129

120130
@eval begin
121131
function minres_qlp!(workspace :: MinresQlpWorkspace{T,FC,S}, $(def_args_minres_qlp...); $(def_kwargs_minres_qlp...)) where {T <: AbstractFloat, FC <: FloatOrComplex{T}, S <: AbstractVector{FC}}
@@ -129,6 +139,7 @@ kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :ve
129139
m == n || error("System must be square")
130140
length(b) == m || error("Inconsistent problem size")
131141
(verbose > 0) && @printf(iostream, "MINRES-QLP: system of size %d\n", n)
142+
(workspace.warm_start && linesearch) && error("warm_start and linesearch cannot be used together")
132143

133144
# Tests M = Iₙ
134145
MisI = (M === I)
@@ -139,6 +150,10 @@ kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :ve
139150

140151
# Set up workspace.
141152
allocate_if(!MisI, workspace, :vₖ, S, workspace.x) # The length of vₖ is n
153+
allocate_if(linesearch, workspace, :npc_dir , S, workspace.x) # The length of npc_dir is n
154+
if linesearch
155+
npc_dir = workspace.npc_dir
156+
end
142157
wₖ₋₁, wₖ, M⁻¹vₖ₋₁, M⁻¹vₖ = workspace.wₖ₋₁, workspace.wₖ, workspace.M⁻¹vₖ₋₁, workspace.M⁻¹vₖ
143158
Δx, x, p, stats = workspace.Δx, workspace.x, workspace.p, workspace.stats
144159
warm_start = workspace.warm_start
@@ -161,6 +176,7 @@ kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :ve
161176
# β₁v₁ = Mb
162177
MisI || mulorldiv!(vₖ, M, M⁻¹vₖ, ldiv)
163178
βₖ = knorm_elliptic(n, vₖ, M⁻¹vₖ)
179+
linesearch && kcopy!(n, npc_dir , vₖ) # npc_dir ← v; contain the preconditioned initial residual
164180
if βₖ 0
165181
kdiv!(n, M⁻¹vₖ, βₖ)
166182
MisI || kdiv!(n, vₖ, βₖ)
@@ -217,6 +233,7 @@ kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :ve
217233
status = "unknown"
218234
user_requested_exit = false
219235
overtimed = false
236+
stats.indefinite = false
220237

221238
while !(solved || tired || inconsistent || ill_cond_mach || breakdown || user_requested_exit || overtimed)
222239
# Update iteration index.
@@ -284,6 +301,32 @@ kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :ve
284301
end
285302
iter == 1 && (λbarₖ = αₖ)
286303

304+
# Check for nonpositive curvature
305+
if linesearch
306+
# if the first iteration, we need to check c₁ * λbar₁ = -α₁
307+
if iter == 1
308+
= -αₖ
309+
else
310+
= cₖ₋₁ * λbarₖ
311+
end
312+
if 0
313+
if iter == 1
314+
kcopy!(n, x, b)
315+
end
316+
# Nonpositive curvature detected.
317+
(verbose > 0) && @printf(iostream, "nonpositive curvature detected: cₖ * λbarₖ = %e\n", cγ)
318+
stats.solved = true
319+
stats.npcCount = 1
320+
stats.niter = iter
321+
stats.inconsistent = false
322+
stats.timer = start_time |> ktimer
323+
stats.status = "nonpositive curvature"
324+
workspace.warm_start = false
325+
stats.indefinite = true
326+
return workspace
327+
end
328+
end
329+
287330
# Compute and apply current Givens reflection Qₖ.ₖ₊₁
288331
# [cₖ sₖ] [λbarₖ] = [λₖ]
289332
# [sₖ -cₖ] [βₖ₊₁ ] [0 ]
@@ -297,6 +340,12 @@ kwargs_minres_qlp = (:M, :ldiv, :λ, :atol, :rtol, :Artol, :itmax, :timemax, :ve
297340
ζₖ = cₖ * ζbarₖ
298341
ζbarₖ₊₁ = sₖ * ζbarₖ
299342

343+
# check for nonpositive curvature
344+
if linesearch
345+
kscal!(n, sₖ * sₖ, npc_dir) # npc_dir = sₖ * sₖ * npc_dir
346+
kaxpy!(n, -ζbarₖ₊₁ * cₖ , vₖ₊₁, npc_dir) # npc_dir = npc_dir - ζbarₖ * cₖ * vₖ, this is rₖ
347+
end
348+
300349
# Update the LQ factorization of Rₖ = LₖPₖ.
301350
# [ λ₁ γ₁ ϵ₁ 0 • • 0 ] [ μ₁ 0 • • • • 0 ]
302351
# [ 0 λ₂ γ₂ • • • ] [ ψ₁ μ₂ • • ]

test/test_minres_qlp.jl

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,45 @@
8484
@test workspace.stats.status == "user-requested exit"
8585
@test cb_n2(workspace)
8686

87+
# Test linesearch
88+
A, b = symmetric_indefinite(FC=FC)
89+
workspace = MinresQlpWorkspace(A, b)
90+
minres_qlp!(workspace, A, b, linesearch=true)
91+
x, stats, npc_dir = workspace.x, workspace.stats, workspace.npc_dir
92+
@test stats.status == "nonpositive curvature"
93+
@test stats.indefinite == true
94+
# Verify that the returned direction indeed exhibits nonpositive curvature.
95+
# For both real and complex cases, ensure to take the real part.
96+
@test real(dot(npc_dir, A * npc_dir)) <= 0
97+
98+
# Test Linesearch which would stop on the first call since A is negative definite
99+
A, b = symmetric_indefinite(FC=FC; shift = 5)
100+
workspace = MinresQlpWorkspace(A, b)
101+
minres_qlp!(workspace, A, b, linesearch=true)
102+
x, stats, npc_dir = workspace.x, workspace.stats, workspace.npc_dir
103+
@test stats.status == "nonpositive curvature"
104+
@test stats.niter == 1
105+
@test all(x .== b)
106+
@test stats.solved == true
107+
@test stats.indefinite == true
108+
@test stats.npcCount == 1
109+
@test real(dot(npc_dir, A * npc_dir)) <= 0
110+
111+
# Test when b^TAb=0 and linesearch is true
112+
A, b = system_zero_quad(FC=FC)
113+
workspace = MinresQlpWorkspace(A, b)
114+
minres_qlp!(workspace, A, b, linesearch=true)
115+
x, stats, npc_dir = workspace.x, workspace.stats, workspace.npc_dir
116+
@test stats.status == "nonpositive curvature"
117+
@test all(x .== b)
118+
@test stats.solved == true
119+
@test stats.indefinite == true
120+
@test real(dot(npc_dir, A * npc_dir)) 0.0
121+
122+
# Test if warm_start and linesearch are both true, it should throw an error
123+
A, b = symmetric_indefinite(FC=FC)
124+
@test_throws MethodError minres_qlp(A, b, warm_start = true, linesearch = true)
125+
87126
@test_throws TypeError minres_qlp(A, b, callback = workspace -> "string", history = true)
88127
end
89128
end

0 commit comments

Comments
 (0)