@@ -147,30 +147,51 @@ mutable struct QRMumpsSolver{T} <: AbstractQRMumpsSolver
147147 b_aug = Vector {T} (undef, m+ n)
148148
149149 # 7. Create the solver object and set a finalizer for safe cleanup.
150- solver = new {T} (spmat, spfct, irn, jcn, val, b_aug, m, n, nnzj)
150+ # Initialize 'closed' to false
151+ solver = new {T} (spmat, spfct, irn, jcn, val, b_aug, m, n, nnzj, false )
152+
153+ function free_qrm (s:: QRMumpsSolver )
154+ if ! s. closed
155+ qrm_spfct_destroy! (s. spfct)
156+ qrm_spmat_destroy! (s. spmat)
157+ s. closed = true
158+ end
159+ end
160+
161+ finalizer (free_qrm, solver) # TODO need more tests
151162 return solver
152163 end
153164end
154165
166+
155167const R2NLS_allowed_subsolvers = (:cgls , :crls , :lsqr , :lsmr , :qrmumps )
156168
157169"""
170+
158171 R2NLS(nlp; kwargs...)
159172
160173An implementation of the Levenberg-Marquardt method with regularization for nonlinear least-squares problems:
161174
162175 min ½‖F(x)‖²
163176
164177where `F: ℝⁿ → ℝᵐ` is a vector-valued function defining the least-squares residuals.
178+
165179For advanced usage, first create a `R2NLSSolver` to preallocate the necessary memory for the algorithm, and then call `solve!`:
180+
166181 solver = R2NLSSolver(nlp)
167182 solve!(solver, nlp; kwargs...)
183+
168184# Arguments
185+
169186- `nls::AbstractNLSModel{T, V}` is the nonlinear least-squares model to solve. See `NLPModels.jl` for additional details.
187+
170188# Keyword Arguments
189+
171190- `x::V = nlp.meta.x0`: the initial guess.
172- - `atol::T = √eps(T)`: absolute tolerance.
173- - `rtol::T = √eps(T)`: relative tolerance; the algorithm stops when ‖J(x)ᵀF(x)‖ ≤ atol + rtol * ‖J(x₀)ᵀF(x₀)‖.
191+ - `atol::T = √eps(T)`: is the absolute stopping tolerance.
192+ - `rtol::T = √eps(T)`: is the relative stopping tolerance; the algorithm stops when ‖J(x)ᵀF(x)‖ ≤ atol + rtol * ‖J(x₀)ᵀF(x₀)‖.
193+ - `Fatol::T = zero(T)`: absolute tolerance for the residual.
194+ - `Frtol::T = zero(T)`: relative tolerance for the residual; the algorithm stops when ‖F(x)‖ ≤ Fatol + Frtol * ‖F(x₀)‖.
174195- `params::R2NLSParameterSet = R2NLSParameterSet()`: algorithm parameters, see [`R2NLSParameterSet`](@ref).
175196- `η1::T = $(R2NLS_η1) `: step acceptance parameter, see [`R2NLSParameterSet`](@ref).
176197- `η2::T = $(R2NLS_η2) `: step acceptance parameter, see [`R2NLSParameterSet`](@ref).
@@ -189,19 +210,15 @@ For advanced usage, first create a `R2NLSSolver` to preallocate the necessary me
189210- `max_time::Float64 = 30.0`: maximum allowed time in seconds.
190211- `max_iter::Int = typemax(Int)`: maximum number of iterations.
191212- `verbose::Int = 0`: if > 0, displays iteration details every `verbose` iterations.
213+
192214# Output
215+
193216Returns a `GenericExecutionStats` object containing statistics and information about the optimization process (see `SolverCore.jl`).
217+
194218- `callback`: function called at each iteration, see [`Callback`](https://jso.dev/JSOSolvers.jl/stable/#Callback) section.
195219
196220# Examples
197- ```jldoctest
198- using JSOSolvers, ADNLPModels
199- F(x) = [x[1] - 1; 2 * (x[2] - x[1]^2)]
200- model = ADNLSModel(F, [-1.2; 1.0], 2)
201- stats = R2NLS(model)
202- # output
203- "Execution stats: first-order stationary"
204- ```
221+
205222```jldoctest
206223using JSOSolvers, ADNLPModels
207224F(x) = [x[1] - 1; 2 * (x[2] - x[1]^2)]
@@ -211,29 +228,30 @@ stats = solve!(solver, model)
211228# output
212229"Execution stats: first-order stationary"
213230```
231+
214232"""
215233mutable struct R2NLSSolver{
216234 T,
217235 V,
218236 Op <: Union{AbstractLinearOperator{T}, SparseMatrixCOO{T, Int}} ,
219237 Sub <: Union{KrylovWorkspace{T, T, V}, QRMumpsSolver{T}} ,
220238} <: AbstractOptimizationSolver
221- x:: V
222- xt:: V
223- temp:: V
224- gx:: V
225- Fx:: V
226- rt:: V
227- Jv:: V
228- Jtv:: V
229- Jx:: Op
230- ls_subsolver:: Sub
231- obj_vec:: V # used for non-monotone behaviour
232- subtol:: T
233- s:: V
234- scp:: V
235- σ:: T
236- params:: R2NLSParameterSet{T}
239+ x:: V # Current iterate x_k
240+ xt:: V # Trial iterate x_{k+1}
241+ temp:: V # Temporary vector for intermediate calculations (e.g. J*v)
242+ gx:: V # Gradient of the objective function: J' * F(x)
243+ Fx:: V # Residual vector F(x)
244+ rt:: V # Residual vector at trial point F(xt)
245+ Jv:: V # Storage for Jacobian-vector products (J * v)
246+ Jtv:: V # Storage for Jacobian-transpose-vector products (J' * v)
247+ Jx:: Op # The Jacobian operator J(x)
248+ ls_subsolver:: Sub # The solver for the linear least-squares subproblem
249+ obj_vec:: V # History of objective values for non-monotone strategy
250+ subtol:: T # Current tolerance for the subproblem solver
251+ s:: V # The calculated step direction
252+ scp:: V # The Cauchy point step
253+ σ:: T # Regularization parameter (Levenberg-Marquardt parameter)
254+ params:: R2NLSParameterSet{T} # Algorithmic parameters
237255end
238256
239257function R2NLSSolver (
@@ -321,10 +339,15 @@ end
321339
322340function SolverCore. reset! (solver:: R2NLSSolver{T} ) where {T}
323341 fill! (solver. obj_vec, typemin (T))
342+ solver. σ = eps (T)^ (1 / 5 )
343+ solver. subtol = one (T)
324344 solver
325345end
346+
326347function SolverCore. reset! (solver:: R2NLSSolver{T} , nlp:: AbstractNLSModel ) where {T}
327348 fill! (solver. obj_vec, typemin (T))
349+ solver. σ = eps (T)^ (1 / 5 )
350+ solver. subtol = one (T)
328351 solver
329352end
330353
@@ -410,57 +433,55 @@ function SolverCore.solve!(
410433 subtol = solver. subtol
411434
412435 σk = solver. σ
413- # preallocate storage for products with Jx and Jx'
414436 Jx = solver. Jx
437+
415438 if Jx isa SparseMatrixCOO
416439 jac_coord_residual! (nlp, x, view (ls_subsolver. val, 1 : ls_subsolver. nnzj))
417440 Jx. vals .= view (ls_subsolver. val, 1 : ls_subsolver. nnzj)
418441 end
419442
420443 residual! (nlp, x, r)
421- f = obj (nlp, x, r, recompute = false )
422- f0 = f
444+ resid_norm = norm (r )
445+ f = resid_norm ^ 2 / 2
423446
424447 mul! (∇f, Jx' , r)
425448
426449 norm_∇fk = norm (∇f)
427450 ρk = zero (T)
428451
429452 # Stopping criterion:
430- fmin = min (- one (T), f0) / eps (T)
431- unbounded = f < fmin
453+ unbounded = false
432454
433455 σk = 2 ^ round (log2 (norm_∇fk + 1 )) / norm_∇fk
434456 ϵ = atol + rtol * norm_∇fk
435457 ϵF = Fatol + Frtol * resid_norm
436458
437- # Preallocate xt.
438459 xt = solver. xt
439460 temp = solver. temp
440461
441- optimal = norm_∇fk ≤ ϵ
462+ stationary = norm_∇fk ≤ ϵ
442463 small_residual = 2 * √ f ≤ ϵF
443464
444465 set_iter! (stats, 0 )
445466 set_objective! (stats, f)
446467 set_dual_residual! (stats, norm_∇fk)
447468
448- if optimal
469+ if stationary
449470 @info " Optimal point found at initial point"
450471 @info log_header (
451- [:iter , :f , :dual , :σ , :ρ ],
472+ [:iter , :resid_norm , :dual , :σ , :ρ ],
452473 [Int, Float64, Float64, Float64, Float64],
453- hdr_override = Dict (:f => " f (x)" , :dual => " ‖∇f‖" ),
474+ hdr_override = Dict (:resid_norm => " ‖F (x)‖ " , :dual => " ‖∇f‖" ),
454475 )
455- @info log_row ([stats. iter, stats . objective , norm_∇fk, σk, ρk])
476+ @info log_row ([stats. iter, resid_norm , norm_∇fk, σk, ρk])
456477 end
457478 cp_step_log = " "
458479 if verbose > 0 && mod (stats. iter, verbose) == 0
459480 @info log_header (
460- [:iter , :f , :dual , :σ , :ρ , :sub_iter , :dir , :cp_step_log , :sub_status ],
481+ [:iter , :resid_norm , :dual , :σ , :ρ , :sub_iter , :dir , :cp_step_log , :sub_status ],
461482 [Int, Float64, Float64, Float64, Float64, Int, String, String, String],
462483 hdr_override = Dict (
463- :f => " f (x)" ,
484+ :resid_norm => " ‖F (x)‖ " ,
464485 :dual => " ‖∇f‖" ,
465486 :sub_iter => " subiter" ,
466487 :dir => " dir" ,
@@ -476,7 +497,7 @@ function SolverCore.solve!(
476497 get_status (
477498 nlp,
478499 elapsed_time = stats. elapsed_time,
479- optimal = optimal ,
500+ optimal = stationary ,
480501 unbounded = unbounded,
481502 max_eval = max_eval,
482503 iter = stats. iter,
@@ -506,7 +527,7 @@ function SolverCore.solve!(
506527 curv = dot (temp, temp) # curv = ∇f' Jx' Jx ∇f
507528 slope = σk * norm_∇fk^ 2 # slope= σ * ||∇f||^2
508529 γ_k = (curv + slope) / norm_∇fk^ 2
509- temp . = .- r
530+ @. temp = - r
510531 solver. σ = σk
511532
512533 if γ_k > 0
@@ -539,7 +560,8 @@ function SolverCore.solve!(
539560 curv = dot (temp, temp)
540561
541562 residual! (nlp, xt, rt)
542- fck = obj (nlp, x, rt, recompute = false )
563+ resid_norm_t = norm (rt)
564+ fck = resid_norm_t^ 2 / 2
543565
544566 ΔTk = - slope - curv / 2
545567 if non_mono_size > 1 # non-monotone behaviour
@@ -558,14 +580,16 @@ function SolverCore.solve!(
558580 jac_coord_residual! (nlp, x, view (ls_subsolver. val, 1 : ls_subsolver. nnzj))
559581 Jx. vals .= view (ls_subsolver. val, 1 : ls_subsolver. nnzj)
560582 end
583+
561584 # update Jx implicitly for other solvers
562585 x .= xt
563586 r .= rt
564587 f = fck
588+ resid_norm = resid_norm_t
565589 mul! (∇f, Jx' , r) # ∇f = Jx' * r
566590 set_objective! (stats, fck)
567- unbounded = fck < fmin
568591 norm_∇fk = norm (∇f)
592+
569593 if ρk >= η2
570594 σk = max (σmin, γ3 * σk)
571595 else # η1 ≤ ρk < η2
@@ -590,14 +614,14 @@ function SolverCore.solve!(
590614 subtol = solver. subtol
591615 norm_∇fk = stats. dual_feas
592616
593- optimal = norm_∇fk ≤ ϵ
617+ stationary = norm_∇fk ≤ ϵ
594618 small_residual = 2 * √ f ≤ ϵF
595619
596620 if verbose > 0 && mod (stats. iter, verbose) == 0
597621 dir_stat = step_accepted ? " ↘" : " ↗"
598622 @info log_row ([
599623 stats. iter,
600- stats . objective ,
624+ resid_norm ,
601625 norm_∇fk,
602626 σk,
603627 ρk,
@@ -616,7 +640,7 @@ function SolverCore.solve!(
616640 get_status (
617641 nlp,
618642 elapsed_time = stats. elapsed_time,
619- optimal = optimal ,
643+ optimal = stationary ,
620644 unbounded = unbounded,
621645 small_residual = small_residual,
622646 max_eval = max_eval,
0 commit comments