Skip to content

Commit 94c5ec6

Browse files
R2: add cauchy step stopping criterion
1 parent 71d204a commit 94c5ec6

2 files changed

Lines changed: 42 additions & 11 deletions

File tree

src/R2_alg.jl

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ For advanced usage, first define a solver "R2Solver" to preallocate the memory u
143143
- `x::V = nlp.meta.x0`: the initial guess;
144144
- `atol::T = √eps(T)`: absolute tolerance;
145145
- `rtol::T = √eps(T)`: relative tolerance;
146+
- `atol_decr::T = atol`: (advanced) absolute tolerance for the optimality measure `√(ξₖ/νₖ)` (see below);
147+
- `rtol_decr::T = rtol`: (advanced) relative tolerance for the optimality measure `√(ξₖ/νₖ)` (see below);
148+
- `atol_step::T = atol`: (advanced) absolute tolerance for the optimality measure `‖sₖ‖/ν₁` (see below);
149+
- `rtol_step::T = rtol`: (advanced) relative tolerance for the optimality measure `‖sₖ‖/ν₁` (see below);
146150
- `neg_tol::T = eps(T)^(1 / 4)`: negative tolerance
147151
- `max_eval::Int = -1`: maximum number of evaluation of the objective function (negative number means unlimited);
148152
- `max_time::Float64 = 30.0`: maximum time limit in seconds;
@@ -156,7 +160,7 @@ For advanced usage, first define a solver "R2Solver" to preallocate the memory u
156160
- `compute_obj::Bool = true`: (advanced) whether `f(x₀)` should be computed or not. If set to false, then the value is retrieved from `stats.solver_specific[:smooth_obj]`;
157161
- `compute_grad::Bool = true`: (advanced) whether `∇f(x₀)` should be computed or not. If set to false, then the value is retrieved from `solver.∇fk`;
158162
159-
The algorithm stops either when `√(ξₖ/νₖ) < atol + rtol*√(ξ₀/ν₀) ` or `ξₖ < 0` and `√(-ξₖ/νₖ) < neg_tol` where ξₖ := f(xₖ) + h(xₖ) - φ(sₖ; xₖ) - ψ(sₖ; xₖ), and √(ξₖ/νₖ) is a stationarity measure.
163+
The algorithm stops either when `√(ξₖ/νₖ) < atol + rtol*√(ξ₀/ν₀) ` or `ξₖ < 0` and `√(-ξₖ/νₖ) < neg_tol` where ξₖ := f(xₖ) + h(xₖ) - φ(sₖ; xₖ) - ψ(sₖ; xₖ), and √(ξₖ/νₖ) is a stationarity measure or when `‖sₖ‖/νₖ < atol_step + rtol_step*‖s₀‖/ν₀` where `sₖ` is the current step.
160164
161165
# Output
162166
The value returned is a `GenericExecutionStats`, see `SolverCore.jl`.
@@ -315,6 +319,10 @@ function SolverCore.solve!(
315319
x::V = reg_nlp.model.meta.x0,
316320
atol::T = eps(T),
317321
rtol::T = eps(T),
322+
atol_decr::T = atol,
323+
rtol_decr::T = rtol,
324+
atol_step::T = atol,
325+
rtol_step::T = rtol,
318326
neg_tol::T = eps(T)^(1 / 4),
319327
verbose::Int = 0,
320328
max_iter::Int = 10000,
@@ -367,13 +375,14 @@ function SolverCore.solve!(
367375

368376
if verbose > 0
369377
@info log_header(
370-
[:iter, :fx, :hx, :xi, , , :normx, :norms, :arrow],
371-
[Int, Float64, Float64, Float64, Float64, Float64, Float64, Float64, Char],
378+
[:iter, :fx, :hx, :xi, :normsdnu, :ρ, , :normx, :norms, :arrow],
379+
[Int, T, T, T, T, T, T, T, T, Char],
372380
hdr_override = Dict{Symbol, String}( # TODO: Add this as constant dict elsewhere
373381
:iter => "iter",
374382
:fx => "f(x)",
375383
:hx => "h(x)",
376384
:xi => "√(ξ/ν)",
385+
:normsdnu => "‖s‖/ν",
377386
=> "ρ",
378387
=> "σ",
379388
:normx => "‖x‖",
@@ -408,15 +417,19 @@ function SolverCore.solve!(
408417
prox!(s, ψ, mν∇fk, ν)
409418
mks = mk(s)
410419

420+
# Estimate optimality and check stopping criteria
411421
ξ = hk - mks + max(1, abs(hk)) * 10 * eps()
412-
413422
sqrt_ξ_νInv = ξ 0 ? sqrt/ ν) : sqrt(-ξ / ν)
414-
atol += rtol * sqrt_ξ_νInv # make stopping test absolute and relative
415-
416-
solved =< 0 && sqrt_ξ_νInv neg_tol) || 0 && sqrt_ξ_νInv atol)
417423
< 0 && sqrt_ξ_νInv > neg_tol) &&
418424
error("R2: prox-gradient step should produce a decrease but ξ = $(ξ)")
425+
atol_decr += rtol_decr * sqrt_ξ_νInv # make stopping test absolute and relative
419426

427+
norm_s = norm(s)
428+
norm_sdν = norm_s / ν
429+
atol_step += rtol_step * norm_sdν # make stopping test absolute and relative
430+
431+
solved =< 0 && sqrt_ξ_νInv neg_tol) || 0 && sqrt_ξ_νInv atol_decr) || (norm_sdν atol_step)
432+
420433
set_status!(
421434
stats,
422435
get_status(
@@ -454,10 +467,11 @@ function SolverCore.solve!(
454467
fk,
455468
hk,
456469
sqrt_ξ_νInv,
470+
norm_sdν,
457471
ρk,
458472
σk,
459473
norm(xk),
460-
norm(s),
474+
norm_s,
461475
(η2 ρk < Inf) ? '' : (ρk < η1 ? '' : '='),
462476
],
463477
colsep = 1,
@@ -497,10 +511,14 @@ function SolverCore.solve!(
497511

498512
ξ = hk - mks + max(1, abs(hk)) * 10 * eps()
499513
sqrt_ξ_νInv = ξ 0 ? sqrt/ ν) : sqrt(-ξ / ν)
500-
solved =< 0 && sqrt_ξ_νInv neg_tol) || 0 && sqrt_ξ_νInv atol)
501514
< 0 && sqrt_ξ_νInv > neg_tol) &&
502515
error("R2: prox-gradient step should produce a decrease but ξ = $(ξ)")
503516

517+
norm_s = norm(s)
518+
norm_sdν = norm_s / ν
519+
520+
solved =< 0 && sqrt_ξ_νInv neg_tol) || 0 && sqrt_ξ_νInv atol_decr) || (norm_sdν atol_step)
521+
504522
set_status!(
505523
stats,
506524
get_status(
@@ -521,8 +539,8 @@ function SolverCore.solve!(
521539
end
522540

523541
if verbose > 0 && stats.status == :first_order
524-
@info log_row(Any[stats.iter, fk, hk, sqrt_ξ_νInv, ρk, σk, norm(xk), norm(s), ""], colsep = 1)
525-
@info "R2: terminating with √(ξ/ν) = $(sqrt_ξ_νInv)"
542+
@info log_row(Any[stats.iter, fk, hk, sqrt_ξ_νInv, norm_sdν, ρk, σk, norm(xk), norm_s, ""], colsep = 1)
543+
@info "R2: terminating with √(ξ/ν) = $(sqrt_ξ_νInv) and ‖s‖/ν = $(norm_sdν)"
526544
end
527545

528546
set_solution!(stats, xk)

test/runtests.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ for (mod, mod_name) ∈ ((x -> x, "exact"), (LSR1Model, "lsr1"), (LBFGSModel, "l
3737
@test length(out.solution) == bpdn.meta.nvar
3838
@test typeof(out.dual_feas) == eltype(out.solution)
3939
@test out.status == :first_order
40+
41+
# Test with the different stopping criteria
42+
out = solver(mod(bpdn), h, args..., options, x0 = x0, atol_decr = 1e-6, rtol_decr = 1e-6, atol_step = 0.0, rtol_step = 0.0)
43+
@test typeof(out.solution) == typeof(bpdn.meta.x0)
44+
@test length(out.solution) == bpdn.meta.nvar
45+
@test typeof(out.dual_feas) == eltype(out.solution)
46+
@test out.status == :first_order
47+
48+
out = solver(mod(bpdn), h, args..., options, x0 = x0, atol_decr = 0.0, rtol_decr = 0.0, atol_step = 1e-6, rtol_step = 1e-6)
49+
@test typeof(out.solution) == typeof(bpdn.meta.x0)
50+
@test length(out.solution) == bpdn.meta.nvar
51+
@test typeof(out.dual_feas) == eltype(out.solution)
52+
@test out.status == :first_order
4053
end
4154
end
4255
end

0 commit comments

Comments
 (0)