Skip to content

Commit 9a23dbc

Browse files
committed
Update R2NLS.jl
1 parent 8e2a5f4 commit 9a23dbc

File tree

1 file changed

+71
-47
lines changed

1 file changed

+71
-47
lines changed

src/R2NLS.jl

Lines changed: 71 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -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
153164
end
154165

166+
155167
const R2NLS_allowed_subsolvers = (:cgls, :crls, :lsqr, :lsmr, :qrmumps)
156168

157169
"""
170+
158171
R2NLS(nlp; kwargs...)
159172
160173
An implementation of the Levenberg-Marquardt method with regularization for nonlinear least-squares problems:
161174
162175
min ½‖F(x)‖²
163176
164177
where `F: ℝⁿ → ℝᵐ` is a vector-valued function defining the least-squares residuals.
178+
165179
For 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+
193216
Returns 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
206223
using JSOSolvers, ADNLPModels
207224
F(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
"""
215233
mutable 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
237255
end
238256

239257
function R2NLSSolver(
@@ -321,10 +339,15 @@ end
321339

322340
function 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
325345
end
346+
326347
function 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
329352
end
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

Comments
 (0)