Skip to content

Commit 9bc4177

Browse files
LM: only recompute Jacobian norm if problem is nonlinear (#156)
Co-authored-by: Dominique <[email protected]>
1 parent d0a6107 commit 9bc4177

File tree

2 files changed

+82
-37
lines changed

2 files changed

+82
-37
lines changed

src/LM_alg.jl

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ function LM(
4747
subsolver = R2,
4848
subsolver_options = ROSolverOptions(ϵa = options.ϵa),
4949
selected::AbstractVector{<:Integer} = 1:(nls.meta.nvar),
50+
nonlinear::Bool = true,
5051
) where {H}
5152
start_time = time()
5253
elapsed_time = 0.0
@@ -62,6 +63,7 @@ function LM(
6263
γ = options.γ
6364
θ = options.θ
6465
σmin = options.σmin
66+
σk = options.σk
6567

6668
# store initial values of the subsolver_options fields that will be modified
6769
ν_subsolver = subsolver_options.ν
@@ -85,7 +87,6 @@ function LM(
8587
end
8688

8789
# initialize parameters
88-
σk = max(1 / options.ν, σmin)
8990
xk = copy(x0)
9091
hk = h(xk[selected])
9192
if hk == Inf
@@ -101,6 +102,7 @@ function LM(
101102
xkn = similar(xk)
102103

103104
local ξ1
105+
local sqrt_ξ1_νInv
104106
k = 0
105107
Fobj_hist = zeros(maxIter)
106108
Hobj_hist = zeros(maxIter)
@@ -110,7 +112,7 @@ function LM(
110112

111113
if verbose > 0
112114
#! format: off
113-
@info @sprintf "%6s %8s %8s %8s %7s %7s %8s %7s %7s %7s %7s %1s" "outer" "inner" "f(x)" "h(x)" "ξ1" "√ξ" "ρ" "σ" "‖x‖" "‖s‖" "‖Jₖ‖²" "reg"
115+
@info @sprintf "%6s %8s %8s %8s %7s %7s %8s %7s %7s %7s %7s %1s" "outer" "inner" "f(x)" "h(x)" "(ξ1/ν)" "√ξ" "ρ" "σ" "‖x‖" "‖s‖" "‖Jₖ‖²" "reg"
114116
#! format: on
115117
end
116118

@@ -177,22 +179,24 @@ function LM(
177179
prox!(s, ψ, ∇fk, ν)
178180
ξ1 = fk + hk - mk1(s) + max(1, abs(fk + hk)) * 10 * eps() # TODO: isn't mk(s) returned by subsolver?
179181
ξ1 > 0 || error("LM: first prox-gradient step should produce a decrease but ξ1 = $(ξ1)")
182+
sqrt_ξ1_νInv = sqrt(ξ1 * νInv)
180183

181184
if ξ1 0 && k == 1
182-
ϵ_increment = ϵr * sqrt(ξ1)
185+
ϵ_increment = ϵr * sqrt_ξ1_νInv
183186
ϵ += ϵ_increment # make stopping test absolute and relative
184187
ϵ_subsolver += ϵ_increment
185188
end
186189

187-
if sqrt(ξ1) < ϵ
190+
if sqrt_ξ1_νInv < ϵ
188191
# the current xk is approximately first-order stationary
189192
optimal = true
190193
continue
191194
end
192195

193-
subsolver_options.ϵa = k == 1 ? 1.0e-1 : max(ϵ_subsolver, min(1.0e-2, ξ1 / 10))
196+
# subsolver_options.ϵa = k == 1 ? 1.0e-1 : max(ϵ_subsolver, min(1.0e-2, ξ1 / 10))
197+
subsolver_options.ϵa = k == 1 ? 1.0e-3 : min(sqrt_ξ1_νInv ^ (1.5) , sqrt_ξ1_νInv * 1e-3) # 1.0e-5 default
194198
subsolver_options.ν = ν
195-
subsolver_args = subsolver == TRDH ? (SpectralGradient(1 / ν, nls.meta.nvar),) : ()
199+
subsolver_args = subsolver == R2DH ? (SpectralGradient(νInv, nls.meta.nvar),) : ()
196200
@debug "setting inner stopping tolerance to" subsolver_options.optTol
197201
s, iter, _ = with_logger(subsolver_logger) do
198202
subsolver(φ, ∇φ!, ψ, subsolver_args..., subsolver_options, s)
@@ -222,7 +226,7 @@ function LM(
222226

223227
if (verbose > 0) && (k % ptf == 0)
224228
#! format: off
225-
@info @sprintf "%6d %8d %8.1e %8.1e %7.1e %7.1e %8.1e %7.1e %7.1e %7.1e %7.1e %1s" k iter fk hk sqrt(ξ1) sqrt(ξ) ρk σk norm(xk) norm(s) νInv σ_stat
229+
@info @sprintf "%6d %8d %8.1e %8.1e %7.1e %7.1e %8.1e %7.1e %7.1e %7.1e %7.1e %1s" k iter fk hk sqrt_ξ1_νInv sqrt(ξ) ρk σk norm(xk) norm(s) νInv σ_stat
226230
#! format: off
227231
end
228232

@@ -244,8 +248,11 @@ function LM(
244248
Jk = jac_op_residual(nls, xk)
245249
jtprod_residual!(nls, xk, Fk, ∇fk)
246250

247-
σmax, found_σ = opnorm(Jk)
248-
found_σ || error("operator norm computation failed")
251+
# update opnorm if not linear least squares
252+
if nonlinear == true
253+
σmax, found_σ = opnorm(Jk)
254+
found_σ || error("operator norm computation failed")
255+
end
249256

250257
Complex_hist[k] += 1
251258
end
@@ -262,9 +269,9 @@ function LM(
262269
@info @sprintf "%6d %8s %8.1e %8.1e" k "" fk hk
263270
elseif optimal
264271
#! format: off
265-
@info @sprintf "%6d %8d %8.1e %8.1e %7.1e %7.1e %8s %7.1e %7.1e %7.1e %7.1e" k 1 fk hk sqrt(ξ1) sqrt(ξ1) "" σk norm(xk) norm(s) νInv
272+
@info @sprintf "%6d %8d %8.1e %8.1e %7.1e %7.1e %8s %7.1e %7.1e %7.1e %7.1e" k 1 fk hk sqrt_ξ1_νInv sqrt(ξ1) "" σk norm(xk) norm(s) νInv
266273
#! format: on
267-
@info "LM: terminating with √ξ1 = $(sqrt(ξ1))"
274+
@info "LM: terminating with √(ξ1/ν) = $(sqrt_ξ1_νInv)"
268275
end
269276
end
270277
status = if optimal
@@ -281,7 +288,7 @@ function LM(
281288
set_status!(stats, status)
282289
set_solution!(stats, xk)
283290
set_objective!(stats, fk + hk)
284-
set_residuals!(stats, zero(eltype(xk)), ξ1 0 ? sqrt(ξ1) : ξ1)
291+
set_residuals!(stats, zero(eltype(xk)), ξ1 0 ? sqrt_ξ1_νInv : ξ1)
285292
set_iter!(stats, k)
286293
set_time!(stats, elapsed_time)
287294
set_solver_specific!(stats, :Fhist, Fobj_hist[1:k])

test/test_bounds.jl

Lines changed: 63 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -66,30 +66,68 @@ for (h, h_name) ∈ ((NormL0(λ), "l0"), (NormL1(λ), "l1"))
6666
@test h(out.solution) == out.solver_specific[:Hhist][end]
6767
@test out.status == :first_order
6868
end
69-
@testset "bpdn-with-bounds-ls-$(solver_name)-$(h_name)-TRDH" begin
70-
x0 = zeros(bpdn_nls2.meta.nvar)
71-
args = solver_sym == :LM ? () : (NormLinf(1.0),)
72-
@test has_bounds(bpdn_nls2)
73-
out = solver(
74-
bpdn_nls2,
75-
h,
76-
args...,
77-
options,
78-
x0 = x0,
79-
subsolver = TRDH,
80-
subsolver_options = subsolver_options,
81-
)
82-
@test typeof(out.solution) == typeof(bpdn_nls2.meta.x0)
83-
@test length(out.solution) == bpdn_nls2.meta.nvar
84-
@test typeof(out.solver_specific[:Fhist]) == typeof(out.solution)
85-
@test typeof(out.solver_specific[:Hhist]) == typeof(out.solution)
86-
@test typeof(out.solver_specific[:SubsolverCounter]) == Array{Int, 1}
87-
@test typeof(out.dual_feas) == eltype(out.solution)
88-
@test length(out.solver_specific[:Fhist]) == length(out.solver_specific[:Hhist])
89-
@test length(out.solver_specific[:Fhist]) == length(out.solver_specific[:SubsolverCounter])
90-
@test obj(bpdn_nls2, out.solution) == out.solver_specific[:Fhist][end]
91-
@test h(out.solution) == out.solver_specific[:Hhist][end]
92-
@test out.status == :first_order
93-
end
69+
end
70+
end
71+
72+
# LMTR with TRDH as subsolver
73+
for (h, h_name) ((NormL0(λ), "l0"), (NormL1(λ), "l1"))
74+
@testset "bpdn-with-bounds-ls-LMTR-$(h_name)-TRDH" begin
75+
x0 = zeros(bpdn_nls2.meta.nvar)
76+
@test has_bounds(bpdn_nls2)
77+
LMTR_out = LMTR(
78+
bpdn_nls2,
79+
h,
80+
NormLinf(1.0),
81+
options,
82+
x0 = x0,
83+
subsolver = TRDH,
84+
subsolver_options = subsolver_options,
85+
)
86+
@test typeof(LMTR_out.solution) == typeof(bpdn_nls2.meta.x0)
87+
@test length(LMTR_out.solution) == bpdn_nls2.meta.nvar
88+
@test typeof(LMTR_out.solver_specific[:Fhist]) == typeof(LMTR_out.solution)
89+
@test typeof(LMTR_out.solver_specific[:Hhist]) == typeof(LMTR_out.solution)
90+
@test typeof(LMTR_out.solver_specific[:SubsolverCounter]) == Array{Int, 1}
91+
@test typeof(LMTR_out.dual_feas) == eltype(LMTR_out.solution)
92+
@test length(LMTR_out.solver_specific[:Fhist]) == length(LMTR_out.solver_specific[:Hhist])
93+
@test length(LMTR_out.solver_specific[:Fhist]) ==
94+
length(LMTR_out.solver_specific[:SubsolverCounter])
95+
@test length(LMTR_out.solver_specific[:Fhist]) == length(LMTR_out.solver_specific[:NLSGradHist])
96+
@test LMTR_out.solver_specific[:NLSGradHist][end] ==
97+
bpdn_nls2.counters.neval_jprod_residual + bpdn_nls2.counters.neval_jtprod_residual - 1
98+
@test obj(bpdn_nls2, LMTR_out.solution) == LMTR_out.solver_specific[:Fhist][end]
99+
@test h(LMTR_out.solution) == LMTR_out.solver_specific[:Hhist][end]
100+
@test LMTR_out.status == :first_order
101+
end
102+
end
103+
104+
# LM with R2DH as subsolver
105+
for (h, h_name) ((NormL0(λ), "l0"), )
106+
@testset "bpdn-with-bounds-ls-LM-$(h_name)-R2DH" begin
107+
x0 = zeros(bpdn_nls2.meta.nvar)
108+
@test has_bounds(bpdn_nls2)
109+
LM_out = LM(
110+
bpdn_nls2,
111+
h,
112+
options,
113+
x0 = x0,
114+
subsolver = R2DH,
115+
subsolver_options = subsolver_options,
116+
)
117+
@test typeof(LM_out.solution) == typeof(bpdn_nls2.meta.x0)
118+
@test length(LM_out.solution) == bpdn_nls2.meta.nvar
119+
@test typeof(LM_out.solver_specific[:Fhist]) == typeof(LM_out.solution)
120+
@test typeof(LM_out.solver_specific[:Hhist]) == typeof(LM_out.solution)
121+
@test typeof(LM_out.solver_specific[:SubsolverCounter]) == Array{Int, 1}
122+
@test typeof(LM_out.dual_feas) == eltype(LM_out.solution)
123+
@test length(LM_out.solver_specific[:Fhist]) == length(LM_out.solver_specific[:Hhist])
124+
@test length(LM_out.solver_specific[:Fhist]) ==
125+
length(LM_out.solver_specific[:SubsolverCounter])
126+
@test length(LM_out.solver_specific[:Fhist]) == length(LM_out.solver_specific[:NLSGradHist])
127+
@test LM_out.solver_specific[:NLSGradHist][end] ==
128+
bpdn_nls2.counters.neval_jprod_residual + bpdn_nls2.counters.neval_jtprod_residual - 1
129+
@test obj(bpdn_nls2, LM_out.solution) == LM_out.solver_specific[:Fhist][end]
130+
@test h(LM_out.solution) == LM_out.solver_specific[:Hhist][end]
131+
@test LM_out.status == :first_order
94132
end
95133
end

0 commit comments

Comments
 (0)