Skip to content

Commit 2ff471b

Browse files
Add monotonicity parameter to LMSolver and TRSolver with history tracking
1 parent 4a755c5 commit 2ff471b

File tree

2 files changed

+35
-7
lines changed

2 files changed

+35
-7
lines changed

src/LM_alg.jl

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@ mutable struct LMSolver{
2222
has_bnds::Bool
2323
l_bound::V
2424
u_bound::V
25+
m_fh_hist::V
2526
l_bound_m_x::V
2627
u_bound_m_x::V
2728
subsolver::ST
2829
subpb::PB
2930
substats::GenericExecutionStats{T, V, V, T}
3031
end
3132

32-
function LMSolver(reg_nls::AbstractRegularizedNLPModel{T, V}; subsolver = R2Solver) where {T, V}
33+
function LMSolver(reg_nls::AbstractRegularizedNLPModel{T, V}; subsolver = R2Solver, m_monotone::Int = 1) where {T, V}
3334
x0 = reg_nls.model.meta.x0
3435
l_bound = reg_nls.model.meta.lvar
3536
u_bound = reg_nls.model.meta.uvar
@@ -54,6 +55,8 @@ function LMSolver(reg_nls::AbstractRegularizedNLPModel{T, V}; subsolver = R2Solv
5455
u_bound_m_x = similar(xk, 0)
5556
end
5657

58+
m_fh_hist = fill(T(-Inf), m_monotone - 1)
59+
5760
ψ =
5861
has_bnds ? shifted(reg_nls.h, xk, l_bound_m_x, u_bound_m_x, reg_nls.selected) :
5962
shifted(reg_nls.h, xk)
@@ -80,6 +83,7 @@ function LMSolver(reg_nls::AbstractRegularizedNLPModel{T, V}; subsolver = R2Solv
8083
u_bound,
8184
l_bound_m_x,
8285
u_bound_m_x,
86+
m_fh_hist,
8387
subsolver,
8488
subpb,
8589
substats,
@@ -105,7 +109,7 @@ where F(x) and J(x) are the residual and its Jacobian at x, respectively, ψ(s;
105109
106110
For advanced usage, first define a solver "LMSolver" to preallocate the memory used in the algorithm, and then call `solve!`:
107111
108-
solver = LMSolver(reg_nls; subsolver = R2Solver)
112+
solver = LMSolver(reg_nls; subsolver = R2Solver, m_monotone = 1)
109113
solve!(solver, reg_nls)
110114
111115
stats = RegularizedExecutionStats(reg_nls)
@@ -130,6 +134,7 @@ For advanced usage, first define a solver "LMSolver" to preallocate the memory u
130134
- `η2::T = T(0.9)`: very successful iteration threshold;
131135
- `γ::T = T(3)`: regularization parameter multiplier, σ := σ/γ when the iteration is very successful and σ := σγ when the iteration is unsuccessful;
132136
- `θ::T = 1/(1 + eps(T)^(1 / 5))`: is the model decrease fraction with respect to the decrease of the Cauchy model;
137+
- `m_monotone::Int = 1`: monotonicity parameter. By default, LM is monotone but the non-monotone variant will be used if `m_monotone > 1`;
133138
- `subsolver = R2Solver`: the solver used to solve the subproblems.
134139
135140
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.
@@ -166,7 +171,8 @@ end
166171
function LM(reg_nls::AbstractRegularizedNLPModel; kwargs...)
167172
kwargs_dict = Dict(kwargs...)
168173
subsolver = pop!(kwargs_dict, :subsolver, R2Solver)
169-
solver = LMSolver(reg_nls, subsolver = subsolver)
174+
m_monotone = pop!(kwargs_dict, :m_monotone, 1)
175+
solver = LMSolver(reg_nlp, subsolver = subsolver, m_monotone = m_monotone)
170176
stats = RegularizedExecutionStats(reg_nls)
171177
solve!(solver, reg_nls, stats; kwargs_dict...)
172178
return stats
@@ -214,7 +220,11 @@ function SolverCore.solve!(
214220
ψ = solver.ψ
215221
xkn = solver.xkn
216222
s = solver.s
223+
m_fh_hist = solver.m_fh_hist .= T(-Inf)
217224
has_bnds = solver.has_bnds
225+
226+
m_monotone = length(m_fh_hist) + 1
227+
218228
if has_bnds
219229
l_bound = solver.l_bound
220230
u_bound = solver.u_bound
@@ -275,6 +285,7 @@ function SolverCore.solve!(
275285
set_solver_specific!(stats, :smooth_obj, fk)
276286
set_solver_specific!(stats, :nonsmooth_obj, hk)
277287
set_solver_specific!(stats, :prox_evals, prox_evals + 1)
288+
m_monotone > 1 && (m_fh_hist[stats.iter % (m_monotone - 1) + 1] = fk + hk)
278289

279290
φ1 = let Fk = Fk, ∇fk = ∇fk
280291
d -> dot(Fk, Fk) / 2 + dot(∇fk, d) # ∇fk = Jk^T Fk
@@ -345,7 +356,8 @@ function SolverCore.solve!(
345356
error("LM: failed to compute a step: ξ = ")
346357
end
347358

348-
Δobj = fk + hk - (fkn + hkn) + max(1, abs(fk + hk)) * 10 * eps()
359+
fhmax = m_monotone > 1 ? maximum(m_fh_hist) : fk + hk
360+
Δobj = fhmax - (fkn + hkn) + max(1, abs(fk + hk)) * 10 * eps()
349361
ρk = Δobj / ξ
350362

351363
verbose > 0 &&
@@ -400,6 +412,8 @@ function SolverCore.solve!(
400412
σk = σk * γ
401413
end
402414

415+
m_monotone > 1 && (m_fh_hist[stats.iter % (m_monotone - 1) + 1] = fk + hk)
416+
403417
set_objective!(stats, fk + hk)
404418
set_solver_specific!(stats, :smooth_obj, fk)
405419
set_solver_specific!(stats, :nonsmooth_obj, hk)

src/TR_alg.jl

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ mutable struct TRSolver{
2323
u_bound::V
2424
l_bound_m_x::V
2525
u_bound_m_x::V
26+
m_fh_hist::V
2627
subsolver::ST
2728
subpb::PB
2829
substats::GenericExecutionStats{T, V, V, T}
@@ -32,6 +33,7 @@ function TRSolver(
3233
reg_nlp::AbstractRegularizedNLPModel{T, V};
3334
χ::X = NormLinf(one(T)),
3435
subsolver = R2Solver,
36+
m_monotone::Int = 1,
3537
) where {T, V, X}
3638
x0 = reg_nlp.model.meta.x0
3739
l_bound = reg_nlp.model.meta.lvar
@@ -54,6 +56,8 @@ function TRSolver(
5456
u_bound_m_x = similar(xk, 0)
5557
end
5658

59+
m_fh_hist = fill(T(-Inf), m_monotone - 1)
60+
5761
ψ =
5862
has_bnds || subsolver == TRDHSolver ?
5963
shifted(reg_nlp.h, xk, l_bound_m_x, u_bound_m_x, reg_nlp.selected) :
@@ -81,6 +85,7 @@ function TRSolver(
8185
u_bound,
8286
l_bound_m_x,
8387
u_bound_m_x,
88+
m_fh_hist,
8489
subsolver,
8590
subpb,
8691
substats,
@@ -107,7 +112,7 @@ where φ(s ; xₖ) = f(xₖ) + ∇f(xₖ)ᵀs + ½ sᵀ Bₖ s is a quadratic a
107112
108113
For advanced usage, first define a solver "TRSolver" to preallocate the memory used in the algorithm, and then call `solve!`:
109114
110-
solver = TR(reg_nlp; χ = NormLinf(1), subsolver = R2Solver)
115+
solver = TRSolver(reg_nlp; χ = NormLinf(1), subsolver = R2Solver, m_monotone = 1)
111116
solve!(solver, reg_nlp)
112117
113118
stats = RegularizedExecutionStats(reg_nlp)
@@ -129,6 +134,7 @@ For advanced usage, first define a solver "TRSolver" to preallocate the memory u
129134
- `η1::T = √√eps(T)`: successful iteration threshold;
130135
- `η2::T = T(0.9)`: very successful iteration threshold;
131136
- `γ::T = T(3)`: trust-region radius parameter multiplier. Must satisfy `γ > 1`. The trust-region radius is updated as Δ := Δ*γ when the iteration is very successful and Δ := Δ/γ when the iteration is unsuccessful;
137+
- `m_monotone::Int = 1`: monotonicity parameter. By default, TR is monotone but the non-monotone variant will be used if `m_monotone > 1`;
132138
- `χ::F = NormLinf(1)`: norm used to define the trust-region;`
133139
- `subsolver::S = R2Solver`: subsolver used to solve the subproblem that appears at each iteration.
134140
- `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,)`.
@@ -175,7 +181,8 @@ function TR(reg_nlp::AbstractRegularizedNLPModel{T, V}; kwargs...) where {T, V}
175181
kwargs_dict = Dict(kwargs...)
176182
subsolver = pop!(kwargs_dict, :subsolver, R2Solver)
177183
χ = pop!(kwargs_dict, , NormLinf(one(T)))
178-
solver = TRSolver(reg_nlp, subsolver = subsolver, χ = χ)
184+
m_monotone = pop!(kwargs_dict, :m_monotone, 1)
185+
solver = TRSolver(reg_nlp, subsolver = subsolver, χ = χ, m_monotone = m_monotone)
179186
stats = RegularizedExecutionStats(reg_nlp)
180187
solve!(solver, reg_nlp, stats; kwargs_dict...)
181188
return stats
@@ -221,8 +228,11 @@ function SolverCore.solve!(
221228
xkn = solver.xkn
222229
s = solver.s
223230
χ = solver.χ
231+
m_fh_hist = solver.m_fh_hist .= T(-Inf)
224232
has_bnds = solver.has_bnds
225233

234+
m_monotone = length(m_fh_hist) + 1
235+
226236
if has_bnds || isa(solver.subsolver, TRDHSolver) #TODO elsewhere ?
227237
l_bound_m_x, u_bound_m_x = solver.l_bound_m_x, solver.u_bound_m_x
228238
l_bound, u_bound = solver.l_bound, solver.u_bound
@@ -293,6 +303,7 @@ function SolverCore.solve!(
293303
set_solver_specific!(stats, :smooth_obj, fk)
294304
set_solver_specific!(stats, :nonsmooth_obj, hk)
295305
set_solver_specific!(stats, :prox_evals, prox_evals + 1)
306+
m_monotone > 1 && (m_fh_hist[stats.iter % (m_monotone - 1) + 1] = fk + hk)
296307

297308
# models
298309
φ1 = let ∇fk = ∇fk
@@ -380,7 +391,8 @@ function SolverCore.solve!(
380391
hkn = @views h(xkn[selected])
381392
sNorm = χ(s)
382393

383-
Δobj = fk + hk - (fkn + hkn) + max(1, abs(fk + hk)) * 10 * eps()
394+
fhmax = m_monotone > 1 ? maximum(m_fh_hist) : fk + hk
395+
Δobj = fhmax - (fkn + hkn) + max(1, abs(fk + hk)) * 10 * eps()
384396
ξ = hk - mk(s) + max(1, abs(hk)) * 10 * eps()
385397

386398
if 0 || isnan(ξ))
@@ -452,6 +464,8 @@ function SolverCore.solve!(
452464
end
453465
end
454466

467+
m_monotone > 1 && (m_fh_hist[stats.iter % (m_monotone - 1) + 1] = fk + hk)
468+
455469
set_objective!(stats, fk + hk)
456470
set_solver_specific!(stats, :smooth_obj, fk)
457471
set_solver_specific!(stats, :nonsmooth_obj, hk)

0 commit comments

Comments
 (0)