Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions src/R2N.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mutable struct R2NSolver{
xkn::V
s::V
s1::V
v0::V
has_bnds::Bool
l_bound::V
u_bound::V
Expand Down Expand Up @@ -46,6 +47,10 @@ function R2NSolver(
xkn = similar(x0)
s = similar(x0)
s1 = similar(x0)

v0 = [(-1.0)^i for i in 0:(reg_nlp.model.meta.nvar-1)]
v0 ./= sqrt(reg_nlp.model.meta.nvar)

has_bnds = any(l_bound .!= T(-Inf)) || any(u_bound .!= T(Inf))
if has_bnds
l_bound_m_x = similar(xk)
Expand Down Expand Up @@ -78,6 +83,7 @@ function R2NSolver(
xkn,
s,
s1,
v0,
has_bnds,
l_bound,
u_bound,
Expand Down Expand Up @@ -133,6 +139,7 @@ For advanced usage, first define a solver "R2NSolver" to preallocate the memory
- `η2::T = T(0.9)`: very successful iteration threshold;
- `γ::T = T(3)`: regularization parameter multiplier, σ := σ/γ when the iteration is very successful and σ := σγ when the iteration is unsuccessful;
- `θ::T = 1/(1 + eps(T)^(1 / 5))`: is the model decrease fraction with respect to the decrease of the Cauchy model;
- `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;
- `m_monotone::Int = 1`: monotonicity parameter. By default, R2N is monotone but the non-monotone variant will be used if `m_monotone > 1`;
- `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,)`.

Expand Down Expand Up @@ -160,7 +167,6 @@ function R2N(
selected = pop!(kwargs_dict, :selected, 1:(nlp.meta.nvar))
x0 = pop!(kwargs_dict, :x0, nlp.meta.x0)
reg_nlp = RegularizedNLPModel(nlp, h, selected)
sub_kwargs = pop!(kwargs_dict, :sub_kwargs, NamedTuple())
return R2N(
reg_nlp,
x = x0,
Expand All @@ -174,8 +180,7 @@ function R2N(
σk = options.σk,
η1 = options.η1,
η2 = options.η2,
γ = options.γ,
sub_kwargs = sub_kwargs;
γ = options.γ;
kwargs_dict...,
)
end
Expand Down Expand Up @@ -212,6 +217,7 @@ function SolverCore.solve!(
γ::T = T(3),
β::T = 1 / eps(T),
θ::T = 1/(1 + eps(T)^(1 / 5)),
opnorm_maxiter::Int = 5,
sub_kwargs::NamedTuple = NamedTuple(),
) where {T, V, G}
reset!(stats)
Expand Down Expand Up @@ -285,9 +291,14 @@ function SolverCore.solve!(

quasiNewtTest = isa(nlp, QuasiNewtonModel)
λmax::T = T(1)
found_λ = true
solver.subpb.model.B = hess_op(nlp, xk)

λmax, found_λ = opnorm(solver.subpb.model.B)
if opnorm_maxiter ≤ 0
λmax, found_λ = opnorm(solver.subpb.model.B)
else
λmax = power_method!(solver.subpb.model.B, solver.v0, solver.subpb.model.v, opnorm_maxiter)
end
found_λ || error("operator norm computation failed")

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

λmax, found_λ = opnorm(solver.subpb.model.B)
if opnorm_maxiter ≤ 0
λmax, found_λ = opnorm(solver.subpb.model.B)
else
λmax = power_method!(solver.subpb.model.B, solver.v0, solver.subpb.model.v, opnorm_maxiter)
end
found_λ || error("operator norm computation failed")
end

Expand Down
23 changes: 19 additions & 4 deletions src/TR_alg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mutable struct TRSolver{
χ::N
xkn::V
s::V
v0::V
has_bnds::Bool
l_bound::V
u_bound::V
Expand Down Expand Up @@ -58,6 +59,9 @@ function TRSolver(

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

v0 = [(-1.0)^i for i in 0:(reg_nlp.model.meta.nvar-1)]
v0 ./= sqrt(reg_nlp.model.meta.nvar)

ψ =
has_bnds || subsolver == TRDHSolver ?
shifted(reg_nlp.h, xk, l_bound_m_x, u_bound_m_x, reg_nlp.selected) :
Expand All @@ -80,6 +84,7 @@ function TRSolver(
χ,
xkn,
s,
v0,
has_bnds,
l_bound,
u_bound,
Expand Down Expand Up @@ -135,6 +140,7 @@ For advanced usage, first define a solver "TRSolver" to preallocate the memory u
- `η2::T = T(0.9)`: very successful iteration threshold;
- `γ::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;
- `m_monotone::Int = 1`: monotonicity parameter. By default, TR is monotone but the non-monotone variant will be used if `m_monotone > 1`;
- `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;
- `χ::F = NormLinf(1)`: norm used to define the trust-region;`
- `subsolver::S = R2Solver`: subsolver used to solve the subproblem that appears at each iteration.
- `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,)`.
Expand Down Expand Up @@ -208,6 +214,7 @@ function SolverCore.solve!(
η2::T = T(0.9),
γ::T = T(3),
sub_kwargs::NamedTuple = NamedTuple(),
opnorm_maxiter::Int = 5,
) where {T, G, V}
reset!(stats)

Expand Down Expand Up @@ -286,9 +293,14 @@ function SolverCore.solve!(
∇fk⁻ .= ∇fk

quasiNewtTest = isa(nlp, QuasiNewtonModel)
λmax = T(1)
λmax::T = T(1)
found_λ = true

λmax, found_λ = opnorm(solver.subpb.model.B)
if opnorm_maxiter ≤ 0
λmax, found_λ = opnorm(solver.subpb.model.B)
else
λmax = power_method!(solver.subpb.model.B, solver.v0, solver.subpb.model.v, opnorm_maxiter)
end
found_λ || error("operator norm computation failed")

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

done = stats.status != :unknown

while !done
sub_atol = stats.iter == 0 ? 1e-5 : max(sub_atol, min(1e-2, sqrt_ξ1_νInv))
∆_effective = min(β * χ(s), Δk)
Expand Down Expand Up @@ -446,7 +457,11 @@ function SolverCore.solve!(
push!(nlp, s, ∇fk⁻) # update QN operator
end

λmax, found_λ = opnorm(solver.subpb.model.B)
if opnorm_maxiter ≤ 0
λmax, found_λ = opnorm(solver.subpb.model.B)
else
λmax = power_method!(solver.subpb.model.B, solver.v0, solver.subpb.model.v, opnorm_maxiter)
end
found_λ || error("operator norm computation failed")

∇fk⁻ .= ∇fk
Expand Down
13 changes: 13 additions & 0 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@ export RegularizedExecutionStats

import SolverCore.GenericExecutionStats

function power_method!(B::M, v₀::S, v₁::S, max_iter::Int = 1) where{M, S}
@assert max_iter >= 1 "max_iter must be at least 1."
mul!(v₁, B, v₀)
normalize!(v₁) # v1 = B*v0 / ‖B*v0‖
for i = 2:max_iter
v₀ .= v₁ # v0 = v1
mul!(v₁, B, v₀)
normalize!(v₁)
end
mul!(v₁, B, v₀)
return abs(dot(v₀, v₁))
end

# use Arpack to obtain largest eigenvalue in magnitude with a minimum of robustness
function LinearAlgebra.opnorm(B; kwargs...)
m, n = size(B)
Expand Down
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ using ADNLPModels,
RegularizedOptimization,
SolverCore

Random.seed!(0)
const global compound = 1
const global nz = 10 * compound
const global options = ROSolverOptions(ν = 1.0, β = 1e16, ϵa = 1e-6, ϵr = 1e-6, verbose = 10)
Expand Down
6 changes: 2 additions & 4 deletions test/test_allocs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,18 @@ end
(:R2DHSolver, "R2DH"),
(:R2NSolver, "R2N"),
(:TRDHSolver, "TRDH"),
(:TRSolver, "TR"),
)
@testset "$(solver_name)" begin
(solver_name == "R2N" || solver_name == "TR") && continue #FIXME
reg_nlp = RegularizedNLPModel(LBFGSModel(bpdn), h)
solver = eval(solver)(reg_nlp)
stats = RegularizedExecutionStats(reg_nlp)
solver_name == "R2" &&
@test @wrappedallocs(solve!(solver, reg_nlp, stats, ν = 1.0, atol = 1e-6, rtol = 1e-6)) ==
0
solver_name == "R2DH" && @test @wrappedallocs(
(solver_name == "R2DH" || solver_name == "R2N") && @test @wrappedallocs(
solve!(solver, reg_nlp, stats, σk = 1.0, atol = 1e-6, rtol = 1e-6)
) == 0
solver_name == "TRDH" &&
(solver_name == "TRDH") &&
@test @wrappedallocs(solve!(solver, reg_nlp, stats, atol = 1e-6, rtol = 1e-6)) == 0
@test stats.status == :first_order
end
Expand Down
Loading