Skip to content

Commit 5e00a62

Browse files
Merge branch 'JuliaSmoothOptimizers:master' into documentation
2 parents 8f5c249 + a4f927b commit 5e00a62

File tree

6 files changed

+80
-38
lines changed

6 files changed

+80
-38
lines changed

src/R2N.jl

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ mutable struct R2NSolver{
1818
xkn::V
1919
s::V
2020
s1::V
21+
v0::V
2122
has_bnds::Bool
2223
l_bound::V
2324
u_bound::V
@@ -46,6 +47,10 @@ function R2NSolver(
4647
xkn = similar(x0)
4748
s = similar(x0)
4849
s1 = similar(x0)
50+
51+
v0 = [(-1.0)^i for i in 0:(reg_nlp.model.meta.nvar-1)]
52+
v0 ./= sqrt(reg_nlp.model.meta.nvar)
53+
4954
has_bnds = any(l_bound .!= T(-Inf)) || any(u_bound .!= T(Inf))
5055
if has_bnds
5156
l_bound_m_x = similar(xk)
@@ -78,6 +83,7 @@ function R2NSolver(
7883
xkn,
7984
s,
8085
s1,
86+
v0,
8187
has_bnds,
8288
l_bound,
8389
u_bound,
@@ -133,6 +139,7 @@ For advanced usage, first define a solver "R2NSolver" to preallocate the memory
133139
- `η2::T = T(0.9)`: very successful iteration threshold;
134140
- `γ::T = T(3)`: regularization parameter multiplier, σ := σ/γ when the iteration is very successful and σ := σγ when the iteration is unsuccessful;
135141
- `θ::T = 1/(1 + eps(T)^(1 / 5))`: is the model decrease fraction with respect to the decrease of the Cauchy model;
142+
- `opnorm_maxiter::Int = 5`: how many iterations of the power method to use to compute the operator norm of Bₖ. If a negative number is provided, then Arpack is used instead;
136143
- `m_monotone::Int = 1`: monotonicity parameter. By default, R2N is monotone but the non-monotone variant will be used if `m_monotone > 1`;
137144
- `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,)`.
138145
@@ -160,7 +167,6 @@ function R2N(
160167
selected = pop!(kwargs_dict, :selected, 1:(nlp.meta.nvar))
161168
x0 = pop!(kwargs_dict, :x0, nlp.meta.x0)
162169
reg_nlp = RegularizedNLPModel(nlp, h, selected)
163-
sub_kwargs = pop!(kwargs_dict, :sub_kwargs, NamedTuple())
164170
return R2N(
165171
reg_nlp,
166172
x = x0,
@@ -174,8 +180,7 @@ function R2N(
174180
σk = options.σk,
175181
η1 = options.η1,
176182
η2 = options.η2,
177-
γ = options.γ,
178-
sub_kwargs = sub_kwargs;
183+
γ = options.γ;
179184
kwargs_dict...,
180185
)
181186
end
@@ -212,6 +217,7 @@ function SolverCore.solve!(
212217
γ::T = T(3),
213218
β::T = 1 / eps(T),
214219
θ::T = 1/(1 + eps(T)^(1 / 5)),
220+
opnorm_maxiter::Int = 5,
215221
sub_kwargs::NamedTuple = NamedTuple(),
216222
) where {T, V, G}
217223
reset!(stats)
@@ -285,9 +291,14 @@ function SolverCore.solve!(
285291

286292
quasiNewtTest = isa(nlp, QuasiNewtonModel)
287293
λmax::T = T(1)
294+
found_λ = true
288295
solver.subpb.model.B = hess_op(nlp, xk)
289296

290-
λmax, found_λ = opnorm(solver.subpb.model.B)
297+
if opnorm_maxiter 0
298+
λmax, found_λ = opnorm(solver.subpb.model.B)
299+
else
300+
λmax = power_method!(solver.subpb.model.B, solver.v0, solver.subpb.model.v, opnorm_maxiter)
301+
end
291302
found_λ || error("operator norm computation failed")
292303

293304
ν₁ = θ / (λmax + σk)
@@ -437,7 +448,11 @@ function SolverCore.solve!(
437448
end
438449
solver.subpb.model.B = hess_op(nlp, xk)
439450

440-
λmax, found_λ = opnorm(solver.subpb.model.B)
451+
if opnorm_maxiter 0
452+
λmax, found_λ = opnorm(solver.subpb.model.B)
453+
else
454+
λmax = power_method!(solver.subpb.model.B, solver.v0, solver.subpb.model.v, opnorm_maxiter)
455+
end
441456
found_λ || error("operator norm computation failed")
442457
end
443458

src/R2_alg.jl

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -526,28 +526,3 @@ function SolverCore.solve!(
526526
set_residuals!(stats, zero(eltype(xk)), sqrt_ξ_νInv)
527527
return stats
528528
end
529-
530-
function get_status(
531-
reg_nlp::M;
532-
elapsed_time = 0.0,
533-
iter = 0,
534-
optimal = false,
535-
improper = false,
536-
max_eval = Inf,
537-
max_time = Inf,
538-
max_iter = Inf,
539-
) where {M <: AbstractRegularizedNLPModel}
540-
if optimal
541-
:first_order
542-
elseif improper
543-
:improper
544-
elseif iter > max_iter
545-
:max_iter
546-
elseif elapsed_time > max_time
547-
:max_time
548-
elseif neval_obj(reg_nlp.model) > max_eval && max_eval > -1
549-
:max_eval
550-
else
551-
:unknown
552-
end
553-
end

src/TR_alg.jl

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ mutable struct TRSolver{
1818
χ::N
1919
xkn::V
2020
s::V
21+
v0::V
2122
has_bnds::Bool
2223
l_bound::V
2324
u_bound::V
@@ -58,6 +59,9 @@ function TRSolver(
5859

5960
m_fh_hist = fill(T(-Inf), m_monotone - 1)
6061

62+
v0 = [(-1.0)^i for i in 0:(reg_nlp.model.meta.nvar-1)]
63+
v0 ./= sqrt(reg_nlp.model.meta.nvar)
64+
6165
ψ =
6266
has_bnds || subsolver == TRDHSolver ?
6367
shifted(reg_nlp.h, xk, l_bound_m_x, u_bound_m_x, reg_nlp.selected) :
@@ -80,6 +84,7 @@ function TRSolver(
8084
χ,
8185
xkn,
8286
s,
87+
v0,
8388
has_bnds,
8489
l_bound,
8590
u_bound,
@@ -135,6 +140,7 @@ For advanced usage, first define a solver "TRSolver" to preallocate the memory u
135140
- `η2::T = T(0.9)`: very successful iteration threshold;
136141
- `γ::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;
137142
- `m_monotone::Int = 1`: monotonicity parameter. By default, TR is monotone but the non-monotone variant will be used if `m_monotone > 1`;
143+
- `opnorm_maxiter::Int = 5`: how many iterations of the power method to use to compute the operator norm of Bₖ. If a negative number is provided, then Arpack is used instead;
138144
- `χ::F = NormLinf(1)`: norm used to define the trust-region;`
139145
- `subsolver::S = R2Solver`: subsolver used to solve the subproblem that appears at each iteration.
140146
- `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,)`.
@@ -208,6 +214,7 @@ function SolverCore.solve!(
208214
η2::T = T(0.9),
209215
γ::T = T(3),
210216
sub_kwargs::NamedTuple = NamedTuple(),
217+
opnorm_maxiter::Int = 5,
211218
) where {T, G, V}
212219
reset!(stats)
213220

@@ -286,9 +293,14 @@ function SolverCore.solve!(
286293
∇fk⁻ .= ∇fk
287294

288295
quasiNewtTest = isa(nlp, QuasiNewtonModel)
289-
λmax = T(1)
296+
λmax::T = T(1)
297+
found_λ = true
290298

291-
λmax, found_λ = opnorm(solver.subpb.model.B)
299+
if opnorm_maxiter 0
300+
λmax, found_λ = opnorm(solver.subpb.model.B)
301+
else
302+
λmax = power_method!(solver.subpb.model.B, solver.v0, solver.subpb.model.v, opnorm_maxiter)
303+
end
292304
found_λ || error("operator norm computation failed")
293305

294306
ν₁ = α * Δk / (1 + λmax ** Δk + 1))
@@ -345,7 +357,6 @@ function SolverCore.solve!(
345357
callback(nlp, solver, stats)
346358

347359
done = stats.status != :unknown
348-
349360
while !done
350361
sub_atol = stats.iter == 0 ? 1e-5 : max(sub_atol, min(1e-2, sqrt_ξ1_νInv))
351362
∆_effective = min* χ(s), Δk)
@@ -446,7 +457,11 @@ function SolverCore.solve!(
446457
push!(nlp, s, ∇fk⁻) # update QN operator
447458
end
448459

449-
λmax, found_λ = opnorm(solver.subpb.model.B)
460+
if opnorm_maxiter 0
461+
λmax, found_λ = opnorm(solver.subpb.model.B)
462+
else
463+
λmax = power_method!(solver.subpb.model.B, solver.v0, solver.subpb.model.v, opnorm_maxiter)
464+
end
450465
found_λ || error("operator norm computation failed")
451466

452467
∇fk⁻ .= ∇fk

src/utils.jl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@ export RegularizedExecutionStats
22

33
import SolverCore.GenericExecutionStats
44

5+
function power_method!(B::M, v₀::S, v₁::S, max_iter::Int = 1) where{M, S}
6+
@assert max_iter >= 1 "max_iter must be at least 1."
7+
mul!(v₁, B, v₀)
8+
normalize!(v₁) # v1 = B*v0 / ‖B*v0‖
9+
for i = 2:max_iter
10+
v₀ .= v₁ # v0 = v1
11+
mul!(v₁, B, v₀)
12+
normalize!(v₁)
13+
end
14+
mul!(v₁, B, v₀)
15+
return abs(dot(v₀, v₁))
16+
end
17+
518
# use Arpack to obtain largest eigenvalue in magnitude with a minimum of robustness
619
function LinearAlgebra.opnorm(B; kwargs...)
720
m, n = size(B)
@@ -117,3 +130,28 @@ function RegularizedExecutionStats(reg_nlp::AbstractRegularizedNLPModel{T, V}) w
117130
set_solver_specific!(stats, :prox_evals, T(Inf))
118131
return stats
119132
end
133+
134+
function get_status(
135+
reg_nlp::M;
136+
elapsed_time = 0.0,
137+
iter = 0,
138+
optimal = false,
139+
improper = false,
140+
max_eval = Inf,
141+
max_time = Inf,
142+
max_iter = Inf,
143+
) where {M <: AbstractRegularizedNLPModel}
144+
if optimal
145+
:first_order
146+
elseif improper
147+
:improper
148+
elseif iter >= max_iter
149+
:max_iter
150+
elseif elapsed_time >= max_time
151+
:max_time
152+
elseif neval_obj(reg_nlp.model) >= max_eval && max_eval >= 0
153+
:max_eval
154+
else
155+
:unknown
156+
end
157+
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ using ADNLPModels,
1010
RegularizedOptimization,
1111
SolverCore
1212

13+
Random.seed!(0)
1314
const global compound = 1
1415
const global nz = 10 * compound
1516
const global options = ROSolverOptions= 1.0, β = 1e16, ϵa = 1e-6, ϵr = 1e-6, verbose = 10)

test/test_allocs.jl

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,18 @@ end
4747
(:R2DHSolver, "R2DH"),
4848
(:R2NSolver, "R2N"),
4949
(:TRDHSolver, "TRDH"),
50-
(:TRSolver, "TR"),
5150
)
5251
@testset "$(solver_name)" begin
53-
(solver_name == "R2N" || solver_name == "TR") && continue #FIXME
5452
reg_nlp = RegularizedNLPModel(LBFGSModel(bpdn), h)
5553
solver = eval(solver)(reg_nlp)
5654
stats = RegularizedExecutionStats(reg_nlp)
5755
solver_name == "R2" &&
5856
@test @wrappedallocs(solve!(solver, reg_nlp, stats, ν = 1.0, atol = 1e-6, rtol = 1e-6)) ==
5957
0
60-
solver_name == "R2DH" && @test @wrappedallocs(
58+
(solver_name == "R2DH" || solver_name == "R2N") && @test @wrappedallocs(
6159
solve!(solver, reg_nlp, stats, σk = 1.0, atol = 1e-6, rtol = 1e-6)
6260
) == 0
63-
solver_name == "TRDH" &&
61+
(solver_name == "TRDH") &&
6462
@test @wrappedallocs(solve!(solver, reg_nlp, stats, atol = 1e-6, rtol = 1e-6)) == 0
6563
@test stats.status == :first_order
6664
end

0 commit comments

Comments
 (0)