Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add R2N, R2DH #153

Merged
merged 38 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
15d353b
Add R2N, R2DH and update LM
MohamedLaghdafHABIBOULLAH Sep 12, 2024
5a06b65
add theta to nu and remove summation
MohamedLaghdafHABIBOULLAH Sep 16, 2024
15fbe1e
Change R2_alg so that it returns list of times
MohamedLaghdafHABIBOULLAH Sep 17, 2024
63e682a
add updated parameter of subsolvers and add sigam-k to input.jl struc…
MohamedLaghdafHABIBOULLAH Sep 25, 2024
17f24ea
change subsolver parameters of LM
MohamedLaghdafHABIBOULLAH Sep 27, 2024
187aabd
reset LM alg and change it in another PR
MohamedLaghdafHABIBOULLAH Sep 27, 2024
cdc9637
Remove the changes in R2_alg.jl
MohamedLaghdafHABIBOULLAH Oct 21, 2024
c548676
correct documentation
MohamedLaghdafHABIBOULLAH Oct 31, 2024
e86af66
Update src/R2N.jl
MohamedLaghdafHABIBOULLAH Oct 31, 2024
a5cb28d
Update src/R2N.jl
MohamedLaghdafHABIBOULLAH Oct 31, 2024
7d8a796
Update src/R2N.jl
MohamedLaghdafHABIBOULLAH Oct 31, 2024
f4b650f
update documentation of R2N
MohamedLaghdafHABIBOULLAH Oct 31, 2024
79c1bb7
Update documentation of src/R2DH.jl
MohamedLaghdafHABIBOULLAH Oct 31, 2024
10cb328
Update src/R2N.jl
MohamedLaghdafHABIBOULLAH Oct 31, 2024
6380b81
Update src/R2N.jl
MohamedLaghdafHABIBOULLAH Oct 31, 2024
30e7532
Update src/R2N.jl
MohamedLaghdafHABIBOULLAH Oct 31, 2024
b58744c
Update src/R2N.jl
MohamedLaghdafHABIBOULLAH Oct 31, 2024
0f643d0
Update src/R2N.jl
MohamedLaghdafHABIBOULLAH Oct 31, 2024
8496c6d
Update documentation and remove dead code
MohamedLaghdafHABIBOULLAH Oct 31, 2024
6c73721
Update runtests.jl
MohamedLaghdafHABIBOULLAH Oct 31, 2024
bed48f9
Update src/R2DH.jl
dpo Nov 1, 2024
464698a
Update src/R2N.jl
MohamedLaghdafHABIBOULLAH Nov 1, 2024
d38532e
update Mmonotone as in the paper
MohamedLaghdafHABIBOULLAH Nov 4, 2024
6e6e027
Update src/R2DH.jl
MohamedLaghdafHABIBOULLAH Nov 14, 2024
0201b86
Update src/R2DH.jl
MohamedLaghdafHABIBOULLAH Nov 14, 2024
7c3e27b
Update doc
MohamedLaghdafHABIBOULLAH Nov 14, 2024
5ba37f8
Update R2DH.jl
MohamedLaghdafHABIBOULLAH Nov 14, 2024
bebb8ce
Update src/R2N.jl
MohamedLaghdafHABIBOULLAH Jan 10, 2025
ff3d172
Update src/R2N.jl
MohamedLaghdafHABIBOULLAH Jan 10, 2025
cebac6c
Update src/R2N.jl
MohamedLaghdafHABIBOULLAH Jan 10, 2025
33ad276
Update src/R2N.jl
MohamedLaghdafHABIBOULLAH Jan 10, 2025
5fb02df
Update src/R2DH.jl
MohamedLaghdafHABIBOULLAH Jan 10, 2025
88409d3
Update src/R2DH.jl clean documentation
MohamedLaghdafHABIBOULLAH Jan 10, 2025
d9ca5e7
Update src/R2DH.jl remove redundant DNorm
MohamedLaghdafHABIBOULLAH Jan 10, 2025
2126667
Update test/runtests.jl
MohamedLaghdafHABIBOULLAH Jan 10, 2025
14710f1
Update doc R2DH.jl
MohamedLaghdafHABIBOULLAH Jan 10, 2025
7366a48
Remove the condition mk = -Inf
MohamedLaghdafHABIBOULLAH Jan 14, 2025
b8e36ea
update sigma of the subsolver
MohamedLaghdafHABIBOULLAH Jan 14, 2025
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
319 changes: 319 additions & 0 deletions src/R2DH.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
export R2DH

"""
R2DH(nlp, h, options)

A second-order quadratic regularization method for the problem

min f(x) + h(x)

where f: ℝⁿ → ℝ is C¹ and h: ℝⁿ → ℝ is lower semi-continuous, proper, and prox-bounded.

About each iterate xₖ, a step sₖ is computed as a solution of

min φ(s; xₖ) + ψ(s; xₖ)

where φ(s ; xₖ) = f(xₖ) + ∇f(xₖ)ᵀs + ½ sᵀ(Dₖ + σₖI)s is a quadratic approximation of f about xₖ,
ψ(s; xₖ) = h(xₖ + s), Dₖ is a diagonal Hessian approximation and σₖ > 0 is the regularization parameter.

### Arguments

* `nlp::AbstractDiagonalQNModel`: a smooth optimization problem
* `h`: a regularizer such as those defined in ProximalOperators
* `options::ROSolverOptions`: a structure containing algorithmic parameters

### Keyword Arguments

* `x0::AbstractVector`: an initial guess (in the first calling form: default = `nlp.meta.x0`)
* `selected::AbstractVector{<:Integer}`: subset of variables to which `h` is applied (default `1:length(x0)`).
* `D`: Diagonal quasi-Newton operator.
* `Mmonotone::Int`: number of previous values of the objective to consider for the non-monotone variant (default: 6).

The objective and gradient of `nlp` will be accessed.

### Return values
The value returned is a `GenericExecutionStats`, see `SolverCore.jl`.
"""
function R2DH(
nlp::AbstractDiagonalQNModel{R, S},
h,
options::ROSolverOptions{R};
kwargs...,
) where {R <: Real, S}
kwargs_dict = Dict(kwargs...)
x0 = pop!(kwargs_dict, :x0, nlp.meta.x0)
xk, k, outdict = R2DH(
x -> obj(nlp, x),
(g, x) -> grad!(nlp, x, g),
h,
hess_op(nlp, x0),
options,
x0;
l_bound = nlp.meta.lvar,
u_bound = nlp.meta.uvar,
kwargs...,
)
sqrt_ξ_νInv = outdict[:sqrt_ξ_νInv]
stats = GenericExecutionStats(nlp)
set_status!(stats, outdict[:status])
set_solution!(stats, xk)
set_objective!(stats, outdict[:fk] + outdict[:hk])
set_residuals!(stats, zero(eltype(xk)), sqrt_ξ_νInv)
set_iter!(stats, k)
set_time!(stats, outdict[:elapsed_time])
set_solver_specific!(stats, :Fhist, outdict[:Fhist])
set_solver_specific!(stats, :Hhist, outdict[:Hhist])
set_solver_specific!(stats, :Time_hist, outdict[:Time_hist])
set_solver_specific!(stats, :NonSmooth, outdict[:NonSmooth])
set_solver_specific!(stats, :SubsolverCounter, outdict[:Chist])
return stats
end

"""
R2DH(f, ∇f!, h, options, x0)

A second calling form for `R2DH` where the objective and gradient are passed as arguments.

### Arguments

* `f::Function`: the objective function
* `∇f!::Function`: the gradient function
* `h`: a regularizer such as those defined in ProximalOperators
* `D`: Diagonal quasi-Newton operator.
* `options::ROSolverOptions`: a structure containing algorithmic parameters
* `x0::AbstractVector`: an initial guess

### Keyword Arguments

* `Mmonotone::Int`: number of previous values of the objective to consider for the non-monotone variant (default: 6).
* `selected::AbstractVector{<:Integer}`: subset of variables to which `h` is applied (default `1:length(x0)`).

### Return values

* `xk`: the final iterate
* `k`: the number of iterations
* `outdict`: a dictionary containing the following fields:
* `Fhist`: an array with the history of values of the smooth objective
* `Hhist`: an array with the history of values of the nonsmooth objective
* `Time_hist`: an array with the history of elapsed times
* `Chist`: an array with the history of number of inner iterations
* `NonSmooth`: the nonsmooth term
* `status`: the status of the solver either `:first_order`, `:max_iter`, `:max_time` or `:exception`
* `fk`: the value of the smooth objective at the final iterate
* `hk`: the value of the nonsmooth objective at the final iterate
* `sqrt_ξ_νInv`: the square root of the ratio of the nonsmooth term to the regularization parameter
* `elapsed_time`: the elapsed time
"""
function R2DH(
f::F,
∇f!::G,
h::H,
D::DQN,
options::ROSolverOptions{R},
x0::AbstractVector{R};
Mmonotone::Int = 6,
selected::AbstractVector{<:Integer} = 1:length(x0),
kwargs...,
) where {F <: Function, G <: Function, H, R <: Real, DQN <: AbstractDiagonalQuasiNewtonOperator}
start_time = time()
elapsed_time = 0.0
ϵ = options.ϵa
ϵr = options.ϵr
neg_tol = options.neg_tol
verbose = options.verbose
maxIter = options.maxIter
maxTime = options.maxTime
σmin = options.σmin
σk = options.σk
η1 = options.η1
η2 = options.η2
ν = options.ν
γ = options.γ
θ = options.θ

local l_bound, u_bound
has_bnds = false
for (key, val) in kwargs
if key == :l_bound
l_bound = val
has_bnds = has_bnds || any(l_bound .!= R(-Inf))
elseif key == :u_bound
u_bound = val
has_bnds = has_bnds || any(u_bound .!= R(Inf))
end
end

if verbose == 0
ptf = Inf
elseif verbose == 1
ptf = round(maxIter / 10)
elseif verbose == 2
ptf = round(maxIter / 100)
else
ptf = 1
end

# initialize parameters
xk = copy(x0)
hk = h(xk[selected])
if hk == Inf
verbose > 0 && @info "R2DH: finding initial guess where nonsmooth term is finite"
prox!(xk, h, x0, one(eltype(x0)))
hk = h(xk[selected])
hk < Inf || error("prox computation must be erroneous")
verbose > 0 && @debug "R2DH: found point where h has value" hk
end
hk == -Inf && error("nonsmooth term is not proper")

xkn = similar(xk)
s = zero(xk)
ψ = has_bnds ? shifted(h, xk, l_bound - xk, u_bound - xk, selected) : shifted(h, xk)

Fobj_hist = zeros(maxIter+1)
Hobj_hist = zeros(maxIter+1)
time_hist = zeros(maxIter+1)
FHobj_hist = fill!(Vector{R}(undef, Mmonotone - 1), R(-Inf))
Complex_hist = zeros(Int, maxIter+1)
if verbose > 0
#! format: off
@info @sprintf "%6s %8s %8s %7s %8s %7s %7s %7s %1s" "iter" "f(x)" "h(x)" "√(ξ/ν)" "ρ" "σ" "‖x‖" "‖s‖" ""
#! format: off
end

local ξ
k = 0

fk = f(xk)
∇fk = similar(xk)
∇f!(∇fk, xk)
∇fk⁻ = copy(∇fk)
spectral_test = isa(D, SpectralGradient)
Dkσk = D.d .+ σk
DNorm = norm(D.d, Inf)

ν = 1 / ((DNorm + σk) * (1 + θ))
mν∇fk = -ν * ∇fk
sqrt_ξ_νInv = one(R)

optimal = false
tired = maxIter > 0 && k ≥ maxIter || elapsed_time > maxTime

while !(optimal || tired)
# model with diagonal hessian
φ(d) = ∇fk' * d + (d' * (Dkσk .* d)) / 2
mk(d) = φ(d) + ψ(d)

if spectral_test
prox!(s, ψ, mν∇fk, ν)
dpo marked this conversation as resolved.
Show resolved Hide resolved
else
iprox!(s, ψ, ∇fk, Dkσk)
end
mks = mk(s)

k = k + 1
elapsed_time = time() - start_time
Fobj_hist[k] = fk
Hobj_hist[k] = hk
time_hist[k] = elapsed_time
Mmonotone > 1 && (FHobj_hist[mod(k-1, Mmonotone - 1) + 1] = fk + hk)

Complex_hist[k] += 1
xkn .= xk .+ s
fkn = f(xkn)
hkn = h(xkn[selected])
hkn == -Inf && error("nonsmooth term is not proper")

fhmax = Mmonotone > 1 ? maximum(FHobj_hist) : fk + hk
Δobj = fhmax - (fkn + hkn) + max(1, abs(fhmax)) * 10 * eps()
Δmod = fhmax - (fk + mks) + max(1, abs(hk)) * 10 * eps()
ξ = hk - mks + max(1, abs(hk)) * 10 * eps()
sqrt_ξ_νInv = ξ ≥ 0 ? sqrt(ξ / ν) : sqrt(-ξ / ν)

if ξ ≥ 0 && k == 1
ϵ += ϵr * sqrt_ξ_νInv # make stopping test absolute and relative
end

if (ξ < 0 && sqrt_ξ_νInv ≤ neg_tol) || (ξ ≥ 0 && sqrt_ξ_νInv < ϵ)
# the current xk is approximately first-order stationary
optimal = true
continue
end

if (ξ ≤ 0 || isnan(ξ))
error("R2DH: failed to compute a step: ξ = $ξ")
end

ρk = Δobj / Δmod

σ_stat = (η2 ≤ ρk < Inf) ? "↘" : (ρk < η1 ? "↗" : "=")

if (verbose > 0) && ((k % ptf == 0) || (k == 1))
#! format: off
@info @sprintf "%6d %8.1e %8.1e %7.1e %8.1e %7.1e %7.1e %7.1e %1s" k fk hk sqrt_ξ_νInv ρk σk norm(xk) norm(s) σ_stat
#! format: on
end

if η2 ≤ ρk < Inf
σk = max(σk / γ, σmin)
end

if η1 ≤ ρk < Inf
xk .= xkn
has_bnds && set_bounds!(ψ, l_bound - xk, u_bound - xk)
fk = fkn
hk = hkn
shift!(ψ, xk)
∇f!(∇fk, xk)
push!(D, s, ∇fk - ∇fk⁻) # update QN operator
DNorm = norm(D.d, Inf)
∇fk⁻ .= ∇fk
end

if ρk < η1 || ρk == Inf
σk = σk * γ
end

Dkσk .= D.d .+ σk
ν = 1 / ((DNorm + σk) * (1 + θ))

tired = maxIter > 0 && k ≥ maxIter
if !tired
@. mν∇fk = -ν * ∇fk
end
end

if verbose > 0
if k == 1
@info @sprintf "%6d %8.1e %8.1e" k fk hk
elseif optimal
#! format: off
@info @sprintf "%6d %8.1e %8.1e %7.1e %8s %7.1e %7.1e %7.1e" k fk hk sqrt_ξ_νInv "" σk norm(xk) norm(s)
#! format: on
@info "R2DH: terminating with √(ξ/ν) = $(sqrt_ξ_νInv))"
end
end

status = if optimal
:first_order
elseif elapsed_time > maxTime
:max_time
elseif tired
:max_iter
else
:exception
end
outdict = Dict(
:Fhist => Fobj_hist[1:k],
:Hhist => Hobj_hist[1:k],
:Time_hist => time_hist[1:k],
:Chist => Complex_hist[1:k],
:NonSmooth => h,
:status => status,
:fk => fk,
:hk => hk,
:sqrt_ξ_νInv => sqrt_ξ_νInv,
:elapsed_time => elapsed_time,
)

return xk, k, outdict
end
Loading
Loading