Skip to content
40 changes: 31 additions & 9 deletions src/LMTR_alg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ For advanced usage, first define a solver "LMSolver" to preallocate the memory u
- `atol::T = √eps(T)`: absolute tolerance;
- `sub_atol::T = atol`: subsolver absolute tolerance;
- `rtol::T = √eps(T)`: relative tolerance;
- `atol_decr::T = atol`: (advanced) absolute tolerance for the optimality measure `√(ξₖ/νₖ)` (see below);
- `rtol_decr::T = rtol`: (advanced) relative tolerance for the optimality measure `√(ξₖ/νₖ)` (see below);
- `atol_step::T = atol`: (advanced) absolute tolerance for the optimality measure `‖sₖ₁‖/ν₁` (see below);
- `rtol_step::T = rtol`: (advanced) relative tolerance for the optimality measure `‖sₖ₁‖/ν₁` (see below);
- `neg_tol::T = zero(T): negative tolerance;
- `max_eval::Int = -1`: maximum number of evaluation of the objective function (negative number means unlimited);
- `max_time::Float64 = 30.0`: maximum time limit in seconds;
Expand All @@ -142,7 +146,7 @@ For advanced usage, first define a solver "LMSolver" to preallocate the memory u
- `subsolver::S = R2Solver`: subsolver used to solve the subproblem that appears at each iteration.
- `sub_kwargs::NamedTuple = NamedTuple()`: a named tuple containing the keyword arguments to be sent to the subsolver. The solver will fail if invalid keyword arguments are provided to the subsolver. For example, if the subsolver is `R2Solver`, you can pass `sub_kwargs = (max_iter = 100, σmin = 1e-6,)`.

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.
The algorithm stops either when `√(ξₖ/νₖ) < atol_decr + rtol_decr*√(ξ₀/ν₀) ` 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 Cauchy step.

# Output
The value returned is a `GenericExecutionStats`, see `SolverCore.jl`.
Expand Down Expand Up @@ -193,6 +197,10 @@ function SolverCore.solve!(
atol::T = √eps(T),
sub_atol::T = atol,
rtol::T = √eps(T),
atol_decr::T = atol,
rtol_decr::T = rtol,
atol_step::T = atol,
rtol_step::T = rtol,
neg_tol::T = zero(T),
verbose::Int = 0,
max_iter::Int = 10000,
Expand Down Expand Up @@ -251,12 +259,13 @@ function SolverCore.solve!(

if verbose > 0
@info log_header(
[:outer, :inner, :fx, :hx, :xi, :ρ, :Δ, :normx, :norms, :ν, :arrow],
[Int, Int, T, T, T, T, T, T, T, T, Char],
[:outer, :inner, :fx, :hx, :xi, :norms1dnu, :ρ, :Δ, :normx, :norms, :ν, :arrow],
[Int, Int, T, T, T, T, T, T, T, T, T, Char],
hdr_override = Dict{Symbol, String}(
:fx => "f(x)",
:hx => "h(x)",
:xi => "√(ξ1/ν)",
:norms1dnu => "‖s₁‖/ν",
:normx => "‖x‖",
:norms => "‖s‖",
:arrow => "LMTR",
Expand Down Expand Up @@ -302,14 +311,21 @@ function SolverCore.solve!(
# Take first proximal gradient step s1 and see if current xk is nearly stationary.
# s1 minimizes φ1(d) + ‖d‖² / 2 / ν + ψ(d) ⟺ s1 ∈ prox{νψ}(-ν∇φ1(0))
prox!(s, ψ, mν∇fk, ν)

# Estimate optimality and check stopping criteria
ξ1 = fk + hk - mk1(s) + max(1, abs(fk + hk)) * 10 * eps()
sqrt_ξ1_νInv = ξ1 ≥ 0 ? sqrt(ξ1 / ν) : sqrt(-ξ1 / ν)
solved = (ξ1 < 0 && sqrt_ξ1_νInv ≤ neg_tol) || (ξ1 ≥ 0 && sqrt_ξ1_νInv ≤ atol)
(ξ1 < 0 && sqrt_ξ1_νInv > neg_tol) &&
error("LMTR: prox-gradient step should produce a decrease but ξ1 = $(ξ1)")
atol += rtol * sqrt_ξ1_νInv # make stopping test absolute and relative
atol_decr += rtol_decr * sqrt_ξ1_νInv # make stopping test absolute and relative
sub_atol += rtol * sqrt_ξ1_νInv

norm_s_cauchy = norm(s)
norm_s_cauchydν = norm_s_cauchy / ν
atol_step += rtol_step * norm_s_cauchydν # make stopping test absolute and relative

solved = (ξ1 < 0 && sqrt_ξ1_νInv ≤ neg_tol) || (ξ1 ≥ 0 && sqrt_ξ1_νInv ≤ atol_decr) || (norm_s_cauchydν ≤ atol_step)

set_status!(
stats,
get_status(
Expand Down Expand Up @@ -390,6 +406,7 @@ function SolverCore.solve!(
fk,
hk,
sqrt_ξ1_νInv,
norm_s_cauchydν,
ρk,
∆_effective,
χ(xk),
Expand Down Expand Up @@ -455,12 +472,17 @@ function SolverCore.solve!(
prox!(s, ψ, mν∇fk, ν)
mks = mk1(s)

# Estimate optimality and check stopping criteria
ξ1 = fk + hk - mks + max(1, abs(hk)) * 10 * eps()
sqrt_ξ1_νInv = ξ1 ≥ 0 ? sqrt(ξ1 / ν) : sqrt(-ξ1 / ν)
solved = (ξ1 < 0 && sqrt_ξ1_νInv ≤ neg_tol) || (ξ1 ≥ 0 && sqrt_ξ1_νInv ≤ atol)
(ξ1 < 0 && sqrt_ξ1_νInv > neg_tol) &&
error("LM: prox-gradient step should produce a decrease but ξ1 = $(ξ1)")

norm_s_cauchy = norm(s)
norm_s_cauchydν = norm_s_cauchy / ν

solved = (ξ1 < 0 && sqrt_ξ1_νInv ≤ neg_tol) || (ξ1 ≥ 0 && sqrt_ξ1_νInv ≤ atol_decr) || (norm_s_cauchydν ≤ atol_step)

set_status!(
stats,
get_status(
Expand All @@ -481,11 +503,11 @@ function SolverCore.solve!(
end

if verbose > 0 && stats.status == :first_order
@info log_row(Any[stats.iter, 0, fk, hk, sqrt_ξ1_νInv, ρk, Δk, χ(xk), χ(s), ν, ""], colsep = 1)
@info "LMTR: terminating with √(ξ1/ν) = $(sqrt_ξ1_νInv)"
@info log_row(Any[stats.iter, 0, fk, hk, sqrt_ξ1_νInv, norm_s_cauchydν, ρk, Δk, χ(xk), χ(s), ν, ""], colsep = 1)
@info "LMTR: terminating with √(ξ1/ν) = $(sqrt_ξ1_νInv) and ‖s₁‖/ν = $(norm_s_cauchydν)"
end

set_solution!(stats, xk)
set_residuals!(stats, zero(T), sqrt_ξ1_νInv)
set_residuals!(stats, zero(T), min(sqrt_ξ1_νInv, norm_s_cauchydν))
return stats
end
42 changes: 31 additions & 11 deletions src/LM_alg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ For advanced usage, first define a solver "LMSolver" to preallocate the memory u
- `nonlinear::Bool = true`: whether the function `F` is nonlinear or not.
- `atol::T = √eps(T)`: absolute tolerance;
- `rtol::T = √eps(T)`: relative tolerance;
- `atol_decr::T = atol`: (advanced) absolute tolerance for the optimality measure `√(ξₖ/νₖ)` (see below);
- `rtol_decr::T = rtol`: (advanced) relative tolerance for the optimality measure `√(ξₖ/νₖ)` (see below);
- `atol_step::T = atol`: (advanced) absolute tolerance for the optimality measure `‖sₖ₁‖/ν₁` (see below);
- `rtol_step::T = rtol`: (advanced) relative tolerance for the optimality measure `‖sₖ₁‖/ν₁` (see below);
- `neg_tol::T = zero(T): negative tolerance;
- `max_eval::Int = -1`: maximum number of evaluation of the objective function (negative number means unlimited);
- `max_time::Float64 = 30.0`: maximum time limit in seconds;
Expand All @@ -142,7 +146,7 @@ For advanced usage, first define a solver "LMSolver" to preallocate the memory u
- `subsolver = R2Solver`: the solver used to solve the subproblems.
- `sub_kwargs::NamedTuple = NamedTuple()`: a named tuple containing the keyword arguments to be sent to the subsolver. The solver will fail if invalid keyword arguments are provided to the subsolver. For example, if the subsolver is `R2Solver`, you can pass `sub_kwargs = (max_iter = 100, σmin = 1e-6,)`.

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.
The algorithm stops either when `√(ξₖ/νₖ) < atol_decr + rtol_decr*√(ξ₀/ν₀) ` 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 Cauchy step.

# Output
The value returned is a `GenericExecutionStats`, see `SolverCore.jl`.
Expand Down Expand Up @@ -192,6 +196,10 @@ function SolverCore.solve!(
nonlinear::Bool = true,
atol::T = √eps(T),
rtol::T = √eps(T),
atol_decr::T = atol,
rtol_decr::T = rtol,
atol_step::T = atol,
rtol_step::T = rtol,
neg_tol::T = zero(T),
verbose::Int = 0,
max_iter::Int = 10000,
Expand Down Expand Up @@ -254,12 +262,13 @@ function SolverCore.solve!(

if verbose > 0
@info log_header(
[:outer, :inner, :fx, :hx, :xi, :ρ, :σ, :normx, :norms, :normJ, :arrow],
[Int, Int, T, T, T, T, T, T, T, T, Char],
[:outer, :inner, :fx, :hx, :xi, :normsdnu, :ρ, :σ, :normx, :norms, :normJ, :arrow],
[Int, Int, T, T, T, T, T, T, T, T, T, Char],
hdr_override = Dict{Symbol, String}(
:fx => "f(x)",
:hx => "h(x)",
:xi => "√(ξ1/ν)",
:normsdnu => "‖s₁‖/ν",
:normx => "‖x‖",
:norms => "‖s‖",
:normJ => "‖J‖²",
Expand Down Expand Up @@ -306,12 +315,19 @@ function SolverCore.solve!(
end

prox!(s, ψ, mν∇fk, ν)

# Estimate optimality and check stopping criteria
ξ1 = fk + hk - mk1(s) + max(1, abs(fk + hk)) * 10 * eps()
sqrt_ξ1_νInv = ξ1 ≥ 0 ? sqrt(ξ1 / ν) : sqrt(-ξ1 / ν)
solved = (ξ1 < 0 && sqrt_ξ1_νInv ≤ neg_tol) || (ξ1 ≥ 0 && sqrt_ξ1_νInv ≤ atol)
(ξ1 < 0 && sqrt_ξ1_νInv > neg_tol) &&
error("LM: prox-gradient step should produce a decrease but ξ1 = $(ξ1)")
atol += rtol * sqrt_ξ1_νInv # make stopping test absolute and relative
atol_decr += rtol_decr * sqrt_ξ1_νInv # make stopping test absolute and relative

norm_s_cauchy = norm(s)
norm_s_cauchydν = norm_s_cauchy / ν
atol_step += rtol_step * norm_s_cauchydν # make stopping test absolute and relative

solved = (ξ1 < 0 && sqrt_ξ1_νInv ≤ neg_tol) || (ξ1 ≥ 0 && sqrt_ξ1_νInv ≤ atol_decr) || (norm_s_cauchydν ≤ atol_step)

set_status!(
stats,
Expand Down Expand Up @@ -384,6 +400,7 @@ function SolverCore.solve!(
fk,
hk,
sqrt_ξ1_νInv,
norm_s_cauchydν,
ρk,
σk,
norm(xk),
Expand Down Expand Up @@ -443,14 +460,17 @@ function SolverCore.solve!(
prox!(s, ψ, mν∇fk, ν)
mks = mk1(s)

# Estimate optimality and check stopping criteria
ξ1 = fk + hk - mks + max(1, abs(hk)) * 10 * eps()

sqrt_ξ1_νInv = ξ1 ≥ 0 ? sqrt(ξ1 / ν) : sqrt(-ξ1 / ν)
solved = (ξ1 < 0 && sqrt_ξ1_νInv ≤ neg_tol) || (ξ1 ≥ 0 && sqrt_ξ1_νInv ≤ atol)

(ξ1 < 0 && sqrt_ξ1_νInv > neg_tol) &&
error("LM: prox-gradient step should produce a decrease but ξ1 = $(ξ1)")

norm_s_cauchy = norm(s)
norm_s_cauchydν = norm_s_cauchy / ν

solved = (ξ1 < 0 && sqrt_ξ1_νInv ≤ neg_tol) || (ξ1 ≥ 0 && sqrt_ξ1_νInv ≤ atol_decr) || (norm_s_cauchydν ≤ atol_step)

set_status!(
stats,
get_status(
Expand All @@ -472,13 +492,13 @@ function SolverCore.solve!(

if verbose > 0 && stats.status == :first_order
@info log_row(
Any[stats.iter, 0, fk, hk, sqrt_ξ1_νInv, ρk, σk, norm(xk), norm(s), 1 / ν, ""],
Any[stats.iter, 0, fk, hk, sqrt_ξ1_νInv, norm_s_cauchydν, ρk, σk, norm(xk), norm(s), 1 / ν, ""],
colsep = 1,
)
@info "LM: terminating with √(ξ1/ν) = $(sqrt_ξ1_νInv)"
@info "LM: terminating with √(ξ1/ν) = $(sqrt_ξ1_νInv) and ‖s‖/ν = $(norm_s_cauchydν)"
end

set_solution!(stats, xk)
set_residuals!(stats, zero(T), sqrt_ξ1_νInv)
set_residuals!(stats, zero(T), min(sqrt_ξ1_νInv, norm_s_cauchydν))
return stats
end
43 changes: 32 additions & 11 deletions src/R2DH.jl
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ or
- `x::V = nlp.meta.x0`: the initial guess;
- `atol::T = √eps(T)`: absolute tolerance;
- `rtol::T = √eps(T)`: relative tolerance;
- `atol_decr::T = atol`: (advanced) absolute tolerance for the optimality measure `√(ξₖ/νₖ)` (see below);
- `rtol_decr::T = rtol`: (advanced) relative tolerance for the optimality measure `√(ξₖ/νₖ)` (see below);
- `atol_step::T = atol`: (advanced) absolute tolerance for the optimality measure `‖sₖ‖/ν₁` (see below);
- `rtol_step::T = rtol`: (advanced) relative tolerance for the optimality measure `‖sₖ‖/ν₁` (see below);
- `neg_tol::T = eps(T)^(1 / 4)`: negative tolerance
- `max_eval::Int = -1`: maximum number of evaluation of the objective function (negative number means unlimited);
- `max_time::Float64 = 30.0`: maximum time limit in seconds;
Expand All @@ -136,7 +140,7 @@ or
- `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]`;
- `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`;

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.
The algorithm stops either when `√(ξₖ/νₖ) < atol_decr + rtol_decr*√(ξ₀/ν₀) ` 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.

# Output
The value returned is a `GenericExecutionStats`, see `SolverCore.jl`.
Expand Down Expand Up @@ -168,7 +172,7 @@ function R2DH(
η1 = options.η1,
η2 = options.η2,
γ = options.γ,
θ = options.θ,
θ = options.θ;
kwargs_dict...,
)
end
Expand Down Expand Up @@ -225,6 +229,10 @@ function SolverCore.solve!(
x::V = reg_nlp.model.meta.x0,
atol::T = √eps(T),
rtol::T = √eps(T),
atol_decr::T = atol,
rtol_decr::T = rtol,
atol_step::T = atol,
rtol_step::T = rtol,
neg_tol::T = eps(T)^(1 / 4),
verbose::Int = 0,
max_iter::Int = 10000,
Expand Down Expand Up @@ -286,12 +294,13 @@ function SolverCore.solve!(

if verbose > 0
@info log_header(
[:iter, :fx, :hx, :xi, :ρ, :σ, :normx, :norms, :arrow],
[Int, T, T, T, T, T, T, T, Char],
[:iter, :fx, :hx, :xi, :normsdnu, :ρ, :σ, :normx, :norms, :arrow],
[Int, T, T, T, T, T, T, T, T, Char],
hdr_override = Dict{Symbol, String}( # TODO: Add this as constant dict elsewhere
:fx => "f(x)",
:hx => "h(x)",
:xi => "√(ξ/ν)",
:normsdnu => "‖s‖/ν",
:normx => "‖x‖",
:norms => "‖s‖",
:arrow => "R2DH",
Expand Down Expand Up @@ -341,12 +350,18 @@ function SolverCore.solve!(

mks = mk(s)

# Estimate optimality and check stopping criteria
ξ = hk - mks + max(1, abs(hk)) * 10 * eps()
sqrt_ξ_νInv = ξ ≥ 0 ? sqrt(ξ / ν₁) : sqrt(-ξ / ν₁)
solved = (ξ < 0 && sqrt_ξ_νInv ≤ neg_tol) || (ξ ≥ 0 && sqrt_ξ_νInv ≤ atol)
(ξ < 0 && sqrt_ξ_νInv > neg_tol) &&
error("R2DH: prox-gradient step should produce a decrease but ξ = $(ξ)")
atol += rtol * sqrt_ξ_νInv # make stopping test absolute and relative
atol_decr += rtol_decr * sqrt_ξ_νInv # make stopping test absolute and relative

norm_s = norm(s)
norm_sdν = norm_s / ν₁
atol_step += rtol_step * norm_sdν # make stopping test absolute and relative

solved = (ξ < 0 && sqrt_ξ_νInv ≤ neg_tol) || (ξ ≥ 0 && sqrt_ξ_νInv ≤ atol_decr) || (norm_sdν ≤ atol_step)

set_status!(
stats,
Expand Down Expand Up @@ -386,10 +401,11 @@ function SolverCore.solve!(
fk,
hk,
sqrt_ξ_νInv,
norm_sdν,
ρk,
σk,
norm(xk),
norm(s),
norm_s,
(η2 ≤ ρk < Inf) ? '↘' : (ρk < η1 ? '↗' : '='),
],
colsep = 1,
Expand Down Expand Up @@ -439,12 +455,17 @@ function SolverCore.solve!(
spectral_test ? prox!(s, ψ, mν∇fk, ν₁) : iprox!(s, ψ, ∇fk, dkσk)
mks = mk(s)

# Estimate optimality and check stopping criteria
ξ = hk - mks + max(1, abs(hk)) * 10 * eps()
sqrt_ξ_νInv = ξ ≥ 0 ? sqrt(ξ / ν₁) : sqrt(-ξ / ν₁)
solved = (ξ < 0 && sqrt_ξ_νInv ≤ neg_tol) || (ξ ≥ 0 && sqrt_ξ_νInv ≤ atol)
(ξ < 0 && sqrt_ξ_νInv > neg_tol) &&
error("R2DH: prox-gradient step should produce a decrease but ξ = $(ξ)")

norm_s = norm(s)
norm_sdν = norm_s / ν₁

solved = (ξ < 0 && sqrt_ξ_νInv ≤ neg_tol) || (ξ ≥ 0 && sqrt_ξ_νInv ≤ atol_decr) || (norm_sdν ≤ atol_step)

set_status!(
stats,
get_status(
Expand All @@ -465,11 +486,11 @@ function SolverCore.solve!(
end

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

set_solution!(stats, xk)
set_residuals!(stats, zero(eltype(xk)), sqrt_ξ_νInv)
set_residuals!(stats, zero(eltype(xk)), min(sqrt_ξ_νInv, norm_sdν))
return stats
end
Loading
Loading