Skip to content

Commit 3ffc73b

Browse files
JSO-compliant LM draft
1 parent 58f26a1 commit 3ffc73b

2 files changed

Lines changed: 211 additions & 12 deletions

File tree

src/LMModel.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ function NLPModels.grad!(nlp::LMModel, x::AbstractVector{T}, g::AbstractVector{T
4848
@lencheck nlp.meta.nvar g
4949
increment!(nlp, :neval_grad)
5050
nlp.v .= nlp.F
51-
@. nlp.g = nlp.σ .* x
51+
@. g = nlp.σ .* x
5252
mul!(nlp.v, nlp.J, x, one(T), one(T))
5353
mul!(g, nlp.J', nlp.v, one(T), one(T))
5454
return g

src/LM_alg.jl

Lines changed: 210 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,10 @@ function SolverCore.solve!(
9595
stats::GenericExecutionStats{T, V};
9696
callback = (args...) -> nothing,
9797
x::V = reg_nls.model.meta.x0,
98+
nonlinear::Bool = true,
9899
atol::T = eps(T),
99100
rtol::T = eps(T),
101+
neg_tol::T = zero(T),
100102
verbose::Int = 0,
101103
max_iter::Int = 10000,
102104
max_time::Float64 = 30.0,
@@ -124,8 +126,7 @@ function SolverCore.solve!(
124126
Fkn = solver.Fkn
125127
Jk = solver.Jk
126128
∇fk = solver.∇fk
127-
JdFk = solver.JdFk
128-
Jt_Fk = solver.Jt_Fk
129+
mν∇fk = solver.mν∇fk
129130
ψ = solver.ψ
130131
xkn = solver.xkn
131132
s = solver.s
@@ -162,8 +163,8 @@ function SolverCore.solve!(
162163
:xi => "√(ξ1/ν)",
163164
:normx => "‖x‖",
164165
:norms => "‖s‖",
165-
:normB => "‖J‖²",
166-
:arrow => "R2N",
166+
:normJ => "‖J‖²",
167+
:arrow => "LM",
167168
),
168169
colsep = 1,
169170
)
@@ -174,7 +175,7 @@ function SolverCore.solve!(
174175

175176
residual!(nls, xk, Fk)
176177
Jk = jac_op_residual(nls, xk)
177-
mul!(∇fk, Jk', Fk)
178+
jtprod_residual!(nls, xk, Fk, ∇fk)
178179
fk = dot(Fk, Fk) / 2
179180

180181
σmax, found_σ = opnorm(Jk)
@@ -184,11 +185,208 @@ function SolverCore.solve!(
184185

185186
@. mν∇fk = -ν * ∇fk
186187

187-
φ1(d) = let Fk = Fk, Jk = Jk,
188-
d -> dot(Fk, Fk) / 2
188+
set_iter!(stats, 0)
189+
start_time = time()
190+
set_time!(stats, 0.0)
191+
set_objective!(stats, fk + hk)
192+
set_solver_specific!(stats, :smooth_obj, fk)
193+
set_solver_specific!(stats, :nonsmooth_obj, hk)
194+
195+
φ1 = let Fk = Fk, ∇fk = ∇fk
196+
d -> dot(Fk, Fk) / 2 + dot(∇fk, d) # ∇fk = Jk^T Fk
197+
end
198+
199+
mk1 = let φ1 = φ1, ψ = ψ
200+
d -> φ1(d) + ψ(d)
201+
end
202+
203+
mk = let ψ = ψ, solver = solver
204+
d -> obj(solver.subpb.model, d) + ψ(d)
205+
end
206+
207+
prox!(s, ψ, mν∇fk, ν)
208+
ξ1 = fk + hk - mk1(s) + max(1, abs(fk + hk)) * 10 * eps()
209+
sqrt_ξ1_νInv = ξ1 0 ? sqrt(ξ1 / ν) : sqrt(-ξ1 / ν)
210+
solved = (ξ1 < 0 && sqrt_ξ1_νInv neg_tol) || (ξ1 0 && sqrt_ξ1_νInv atol)
211+
(ξ1 < 0 && sqrt_ξ1_νInv > neg_tol) &&
212+
error("LM: prox-gradient step should produce a decrease but ξ1 = $(ξ1)")
213+
atol += rtol * sqrt_ξ1_νInv # make stopping test absolute and relative
214+
215+
set_status!(
216+
stats,
217+
get_status(
218+
reg_nls,
219+
elapsed_time = stats.elapsed_time,
220+
iter = stats.iter,
221+
optimal = solved,
222+
improper = improper,
223+
max_eval = max_eval,
224+
max_time = max_time,
225+
max_iter = max_iter,
226+
),
227+
)
228+
229+
callback(nls, solver, stats)
230+
231+
done = stats.status != :unknown
232+
233+
while !done
234+
235+
sub_atol = stats.iter == 0 ? 1.0e-3 : min(sqrt_ξ1_νInv ^ (1.5), sqrt_ξ1_νInv * 1e-3)
236+
solver.subpb.model.σ = σk
237+
isa(solver.subsolver, R2DHSolver) && (solver.subsolver.D.d[1] = 1/ν)
238+
if isa(solver.subsolver, R2Solver) #FIXME
239+
solve!(
240+
solver.subsolver,
241+
solver.subpb,
242+
solver.substats,
243+
x = s,
244+
atol = sub_atol,
245+
ν = ν,
246+
)
247+
else
248+
solve!(
249+
solver.subsolver,
250+
solver.subpb,
251+
solver.substats,
252+
x = s,
253+
atol = sub_atol,
254+
σk = σk, #FIXME
255+
)
256+
end
257+
258+
s .= solver.substats.solution
259+
260+
xkn .= xk .+ s
261+
residual!(nls, xkn, Fkn)
262+
fkn = dot(Fkn, Fkn) / 2
263+
hkn = @views h(xkn[selected])
264+
mks = mk(s)
265+
ξ = fk + hk - mks + max(1, abs(hk)) * 10 * eps()
266+
267+
if 0 || isnan(ξ))
268+
error("LM: failed to compute a step: ξ = ")
269+
end
270+
271+
Δobj = fk + hk - (fkn + hkn) + max(1, abs(fk + hk)) * 10 * eps()
272+
ρk = Δobj / ξ
273+
274+
verbose > 0 &&
275+
stats.iter % verbose == 0 &&
276+
@info log_row(
277+
Any[
278+
stats.iter,
279+
solver.substats.iter,
280+
fk,
281+
hk,
282+
sqrt_ξ1_νInv,
283+
ρk,
284+
σk,
285+
norm(xk),
286+
norm(s),
287+
1 / ν,
288+
(η2 ρk < Inf) ? "" : (ρk < η1 ? "" : "="),
289+
],
290+
colsep = 1,
291+
)
292+
293+
if η1 ρk < Inf
294+
xk .= xkn
295+
296+
if has_bnds
297+
@. l_bound_m_x = l_bound - xk
298+
@. u_bound_m_x = u_bound - xk
299+
set_bounds!(ψ, l_bound_m_x, u_bound_m_x)
300+
end
301+
302+
#update functions
303+
Fk .= Fkn
304+
fk = fkn
305+
hk = hkn
306+
307+
# update gradient & Hessian
308+
shift!(ψ, xk)
309+
Jk = jac_op_residual(nls, xk)
310+
jtprod_residual!(nls, xk, Fk, ∇fk)
311+
312+
# update opnorm if not linear least squares
313+
if nonlinear == true
314+
σmax, found_σ = opnorm(Jk)
315+
found_σ || error("operator norm computation failed")
316+
end
317+
end
318+
319+
if η2 ρk < Inf
320+
σk = max(σk / γ, σmin)
321+
end
322+
323+
if ρk < η1 || ρk == Inf
324+
σk = σk * γ
325+
end
326+
327+
set_objective!(stats, fk + hk)
328+
set_solver_specific!(stats, :smooth_obj, fk)
329+
set_solver_specific!(stats, :nonsmooth_obj, hk)
330+
set_iter!(stats, stats.iter + 1)
331+
set_time!(stats, time() - start_time)
332+
333+
ν = θ / (σmax^2 + σk) # ‖J'J + σₖ I‖ = ‖J‖² + σₖ
334+
335+
@. mν∇fk = - ν * ∇fk
336+
prox!(s, ψ, mν∇fk, ν)
337+
mks = mk1(s)
338+
339+
ξ1 = fk + hk - mks + max(1, abs(hk)) * 10 * eps()
340+
341+
sqrt_ξ1_νInv = ξ1 0 ? sqrt(ξ1 / ν) : sqrt(-ξ1 / ν)
342+
solved = (ξ1 < 0 && sqrt_ξ1_νInv neg_tol) || (ξ1 0 && sqrt_ξ1_νInv atol)
343+
344+
(ξ1 < 0 && sqrt_ξ1_νInv > neg_tol) &&
345+
error("LM: prox-gradient step should produce a decrease but ξ1 = $(ξ1)")
346+
347+
set_status!(
348+
stats,
349+
get_status(
350+
reg_nls,
351+
elapsed_time = stats.elapsed_time,
352+
iter = stats.iter,
353+
optimal = solved,
354+
improper = improper,
355+
max_eval = max_eval,
356+
max_time = max_time,
357+
max_iter = max_iter,
358+
),
359+
)
360+
361+
callback(nls, solver, stats)
362+
363+
done = stats.status != :unknown
364+
365+
end
366+
367+
if verbose > 0 && stats.status == :first_order
368+
@info log_row(
369+
Any[
370+
stats.iter,
371+
0,
372+
fk,
373+
hk,
374+
sqrt_ξ1_νInv,
375+
ρk,
376+
σk,
377+
norm(xk),
378+
norm(s),
379+
1 / ν,
380+
"",
381+
],
382+
colsep = 1,
383+
)
384+
@info "LM: terminating with √(ξ1/ν) = $(sqrt_ξ1_νInv)"
189385
end
190386

191-
return
387+
set_solution!(stats, xk)
388+
set_residuals!(stats, zero(T), sqrt_ξ1_νInv)
389+
return stats
192390
end
193391

194392
"""
@@ -388,9 +586,10 @@ function LM(
388586
subsolver_options.ν = ν
389587
subsolver_args = subsolver == R2DH ? (SpectralGradient(1 / ν, nls.meta.nvar),) : ()
390588
@debug "setting inner stopping tolerance to" subsolver_options.optTol
391-
s, iter, _ = with_logger(subsolver_logger) do
392-
subsolver(φ, ∇φ!, ψ, subsolver_args..., subsolver_options, s)
393-
end
589+
#s, iter, _ = with_logger(subsolver_logger) do
590+
#subsolver_options.verbose = 1
591+
s, iter, _ = subsolver(φ, ∇φ!, ψ, subsolver_args..., subsolver_options, s)
592+
#end
394593
# restore initial subsolver_options here so that it is not modified if there is an error
395594
subsolver_options.ν = ν_subsolver
396595
subsolver_options.ϵa = ϵa_subsolver

0 commit comments

Comments
 (0)