From 11e9ea2acda062776a6a859f9b9be5a6efacdeca Mon Sep 17 00:00:00 2001 From: tmigot Date: Thu, 25 May 2023 17:13:27 +0000 Subject: [PATCH] :robot: Format .jl files --- docs/make.jl | 39 +++-- src/TunerTestSolver.jl | 38 +++-- src/lbfgs.jl | 362 +++++++++++++++++++++-------------------- src/parameters.jl | 2 +- tuning/benchmark.jl | 48 +++--- 5 files changed, 252 insertions(+), 237 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index 307d468..360af06 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,26 +1,31 @@ using TunerTestSolver using Documenter -DocMeta.setdocmeta!(TunerTestSolver, :DocTestSetup, :(using TunerTestSolver); recursive = true) +DocMeta.setdocmeta!( + TunerTestSolver, + :DocTestSetup, + :(using TunerTestSolver); + recursive = true, +) makedocs(; - modules = [TunerTestSolver], - doctest = true, - linkcheck = false, - strict = false, - authors = "Tangi Migot and Monssaf Toukal", - repo = "https://github.com/ProofOfConceptForJuliSmoothOptimizers/TunerTestSolver.jl/blob/{commit}{path}#{line}", - sitename = "TunerTestSolver.jl", - format = Documenter.HTML(; - prettyurls = get(ENV, "CI", "false") == "true", - canonical = "https://ProofOfConceptForJuliSmoothOptimizers.github.io/TunerTestSolver.jl", - assets = ["assets/style.css"], - ), - pages = ["Home" => "index.md", "Reference" => "reference.md"], + modules = [TunerTestSolver], + doctest = true, + linkcheck = false, + strict = false, + authors = "Tangi Migot and Monssaf Toukal", + repo = "https://github.com/ProofOfConceptForJuliSmoothOptimizers/TunerTestSolver.jl/blob/{commit}{path}#{line}", + sitename = "TunerTestSolver.jl", + format = Documenter.HTML(; + prettyurls = get(ENV, "CI", "false") == "true", + canonical = "https://ProofOfConceptForJuliSmoothOptimizers.github.io/TunerTestSolver.jl", + assets = ["assets/style.css"], + ), + pages = ["Home" => "index.md", "Reference" => "reference.md"], ) deploydocs(; - repo = "github.com/ProofOfConceptForJuliSmoothOptimizers/TunerTestSolver.jl", - push_preview = true, - devbranch = "main", + repo = "github.com/ProofOfConceptForJuliSmoothOptimizers/TunerTestSolver.jl", + push_preview = true, + devbranch = "main", ) diff --git a/src/TunerTestSolver.jl b/src/TunerTestSolver.jl index 3d4cfaf..adc078f 100644 --- a/src/TunerTestSolver.jl +++ b/src/TunerTestSolver.jl @@ -4,24 +4,28 @@ using SolverParameters include("parameters.jl") -struct LBFGSParameterSet{T <: Real} <: AbstractParameterSet - mem::Parameter{Int, IntegerRange{Int}} - τ₁::Parameter{T, RealInterval{T}} - bk_max::Parameter{Int, IntegerRange{Int}} - # add scaling - - function LBFGSParameterSet{T}(;mem::Int = mem, τ₁::T = T(τ₁), bk_max::Int = bk_max) where {T} - p_set = new( - Parameter(mem, IntegerRange(Int(1), Int(20)), "mem"), - Parameter(τ₁, RealInterval(T(0), T(1)), "τ₁"), - Parameter(bk_max, IntegerRange(Int(10), Int(50)), "bk_max"), - ) - return p_set - end +struct LBFGSParameterSet{T<:Real} <: AbstractParameterSet + mem::Parameter{Int,IntegerRange{Int}} + τ₁::Parameter{T,RealInterval{T}} + bk_max::Parameter{Int,IntegerRange{Int}} + # add scaling - function LBFGSParameterSet(;kwargs...) - return LBFGSParameterSet{Float64}(; kwargs...) - end + function LBFGSParameterSet{T}(; + mem::Int = mem, + τ₁::T = T(τ₁), + bk_max::Int = bk_max, + ) where {T} + p_set = new( + Parameter(mem, IntegerRange(Int(1), Int(20)), "mem"), + Parameter(τ₁, RealInterval(T(0), T(1)), "τ₁"), + Parameter(bk_max, IntegerRange(Int(10), Int(50)), "bk_max"), + ) + return p_set + end + + function LBFGSParameterSet(; kwargs...) + return LBFGSParameterSet{Float64}(; kwargs...) + end end include("lbfgs.jl") diff --git a/src/lbfgs.jl b/src/lbfgs.jl index 4548d3e..2f10641 100644 --- a/src/lbfgs.jl +++ b/src/lbfgs.jl @@ -8,222 +8,230 @@ import SolverCore.solve! import Krylov.solve! function get_status( - nlp; - elapsed_time = 0.0, - optimal = false, - unbounded = false, - max_eval = Inf, - max_time = Inf, + nlp; + elapsed_time = 0.0, + optimal = false, + unbounded = false, + max_eval = Inf, + max_time = Inf, ) - if optimal - :first_order - elseif unbounded - :unbounded - elseif neval_obj(nlp) > max_eval ≥ 0 - :max_eval - elseif elapsed_time > max_time - :max_time - else - :unknown - end + if optimal + :first_order + elseif unbounded + :unbounded + elseif neval_obj(nlp) > max_eval ≥ 0 + :max_eval + elseif elapsed_time > max_time + :max_time + else + :unknown + end end -mutable struct LBFGSSolver{T, V, Op <: AbstractLinearOperator{T}, M <: AbstractNLPModel{T, V}} <: +mutable struct LBFGSSolver{T,V,Op<:AbstractLinearOperator{T},M<:AbstractNLPModel{T,V}} <: AbstractOptimizationSolver - x::V - xt::V - gx::V - gt::V - d::V - H::Op - h::LineModel{T, V, M} + x::V + xt::V + gx::V + gt::V + d::V + H::Op + h::LineModel{T,V,M} end -function LBFGSSolver(nlp::M; mem::Int = 5) where {T, V, M <: AbstractNLPModel{T, V}} - nvar = nlp.meta.nvar - x = V(undef, nvar) - d = V(undef, nvar) - xt = V(undef, nvar) - gx = V(undef, nvar) - gt = V(undef, nvar) - H = InverseLBFGSOperator(T, nvar, mem = mem, scaling = true) - h = LineModel(nlp, x, d) - Op = typeof(H) - return LBFGSSolver{T, V, Op, M}(x, xt, gx, gt, d, H, h) +function LBFGSSolver(nlp::M; mem::Int = 5) where {T,V,M<:AbstractNLPModel{T,V}} + nvar = nlp.meta.nvar + x = V(undef, nvar) + d = V(undef, nvar) + xt = V(undef, nvar) + gx = V(undef, nvar) + gt = V(undef, nvar) + H = InverseLBFGSOperator(T, nvar, mem = mem, scaling = true) + h = LineModel(nlp, x, d) + Op = typeof(H) + return LBFGSSolver{T,V,Op,M}(x, xt, gx, gt, d, H, h) end function SolverCore.reset!(solver::LBFGSSolver) - reset!(solver.H) + reset!(solver.H) end function SolverCore.reset!(solver::LBFGSSolver, nlp::AbstractNLPModel) - reset!(solver.H) - solver.h = LineModel(nlp, solver.x, solver.d) - solver + reset!(solver.H) + solver.h = LineModel(nlp, solver.x, solver.d) + solver end -function SolverCore.solve!(solver::AbstractOptimizationSolver, param::AbstractParameterSet, model::AbstractNLPModel; kwargs...) - stats = GenericExecutionStats(model) - solve!(solver, param, model, stats; kwargs...) +function SolverCore.solve!( + solver::AbstractOptimizationSolver, + param::AbstractParameterSet, + model::AbstractNLPModel; + kwargs..., +) + stats = GenericExecutionStats(model) + solve!(solver, param, model, stats; kwargs...) end -function lbfgs( - nlp::AbstractNLPModel; - mem::Int = 5, - kwargs..., -) - param = LBFGSParameterSet(mem = mem) - return lbfgs(nlp, param; kwargs...) +function lbfgs(nlp::AbstractNLPModel; mem::Int = 5, kwargs...) + param = LBFGSParameterSet(mem = mem) + return lbfgs(nlp, param; kwargs...) end function lbfgs( - nlp::AbstractNLPModel, - param::LBFGSParameterSet; - x::V = nlp.meta.x0, - kwargs..., + nlp::AbstractNLPModel, + param::LBFGSParameterSet; + x::V = nlp.meta.x0, + kwargs..., ) where {V} - mem = value(param.mem) - solver = LBFGSSolver(nlp; mem = mem) - return solve!(solver, param, nlp; x = x, kwargs...) + mem = value(param.mem) + solver = LBFGSSolver(nlp; mem = mem) + return solve!(solver, param, nlp; x = x, kwargs...) end function SolverCore.solve!( - solver::LBFGSSolver{T, V}, - param::LBFGSParameterSet, - nlp::AbstractNLPModel{T, V}, - stats::GenericExecutionStats{T, V}; - callback = (args...) -> nothing, - x::V = nlp.meta.x0, - atol::T = √eps(T), - rtol::T = √eps(T), - max_eval::Int = -1, - max_time::Float64 = 30.0, - τ₁::T = T(0.9999), # parameter - bk_max::Int = 25, # parameter - verbose::Int = 0, - verbose_subsolver::Int = 0, -) where {T, V} - if !(nlp.meta.minimize) - error("lbfgs only works for minimization problem") - end - if !unconstrained(nlp) - error("lbfgs should only be called for unconstrained problems. Try tron instead") - end - - reset!(stats) - start_time = time() - set_time!(stats, 0.0) - - n = nlp.meta.nvar - - solver.x .= x - x = solver.x - xt = solver.xt - ∇f = solver.gx - ∇ft = solver.gt - d = solver.d - h = solver.h - H = solver.H - reset!(H) - - f, ∇f = objgrad!(nlp, x, ∇f) - - ∇fNorm = nrm2(n, ∇f) - ϵ = atol + rtol * ∇fNorm - - set_iter!(stats, 0) - set_objective!(stats, f) - set_dual_residual!(stats, ∇fNorm) - - verbose > 0 && @info log_header( - [:iter, :f, :dual, :slope, :bk], - [Int, T, T, T, Int], - hdr_override = Dict(:f => "f(x)", :dual => "‖∇f‖", :slope => "∇fᵀd"), - ) - - optimal = ∇fNorm ≤ ϵ - - set_status!( - stats, - get_status( - nlp, - elapsed_time = stats.elapsed_time, - optimal = optimal, - max_eval = max_eval, - max_time = max_time, - ), - ) - - callback(nlp, solver, stats) - - done = - (stats.status == :first_order) || - (stats.status == :max_eval) || - (stats.status == :max_time) || - (stats.status == :user) || - (stats.status == :not_desc) - - while !done - mul!(d, H, ∇f, -one(T), zero(T)) - slope = dot(n, d, ∇f) - if slope ≥ 0 - @error "not a descent direction" slope - set_status!(stats, :not_desc) - done = true - continue + solver::LBFGSSolver{T,V}, + param::LBFGSParameterSet, + nlp::AbstractNLPModel{T,V}, + stats::GenericExecutionStats{T,V}; + callback = (args...) -> nothing, + x::V = nlp.meta.x0, + atol::T = √eps(T), + rtol::T = √eps(T), + max_eval::Int = -1, + max_time::Float64 = 30.0, + τ₁::T = T(0.9999), # parameter + bk_max::Int = 25, # parameter + verbose::Int = 0, + verbose_subsolver::Int = 0, +) where {T,V} + if !(nlp.meta.minimize) + error("lbfgs only works for minimization problem") + end + if !unconstrained(nlp) + error("lbfgs should only be called for unconstrained problems. Try tron instead") end - # Perform improved Armijo linesearch. - t, good_grad, ft, nbk, nbW = - armijo_wolfe(h, f, slope, ∇ft, τ₁ = τ₁, bk_max = bk_max, verbose = Bool(verbose_subsolver)) - - verbose > 0 && - mod(stats.iter, verbose) == 0 && - @info log_row(Any[stats.iter, f, ∇fNorm, slope, nbk]) + reset!(stats) + start_time = time() + set_time!(stats, 0.0) - copyaxpy!(n, t, d, x, xt) - good_grad || grad!(nlp, xt, ∇ft) + n = nlp.meta.nvar - # Update L-BFGS approximation. - d .*= t - @. ∇f = ∇ft - ∇f - push!(H, d, ∇f) + solver.x .= x + x = solver.x + xt = solver.xt + ∇f = solver.gx + ∇ft = solver.gt + d = solver.d + h = solver.h + H = solver.H + reset!(H) - # Move on. - x .= xt - f = ft - ∇f .= ∇ft + f, ∇f = objgrad!(nlp, x, ∇f) ∇fNorm = nrm2(n, ∇f) + ϵ = atol + rtol * ∇fNorm + set_iter!(stats, 0) set_objective!(stats, f) - set_iter!(stats, stats.iter + 1) - set_time!(stats, time() - start_time) set_dual_residual!(stats, ∇fNorm) + + verbose > 0 && @info log_header( + [:iter, :f, :dual, :slope, :bk], + [Int, T, T, T, Int], + hdr_override = Dict(:f => "f(x)", :dual => "‖∇f‖", :slope => "∇fᵀd"), + ) + optimal = ∇fNorm ≤ ϵ set_status!( - stats, - get_status( - nlp, - elapsed_time = stats.elapsed_time, - optimal = optimal, - max_eval = max_eval, - max_time = max_time, - ), + stats, + get_status( + nlp, + elapsed_time = stats.elapsed_time, + optimal = optimal, + max_eval = max_eval, + max_time = max_time, + ), ) callback(nlp, solver, stats) done = - (stats.status == :first_order) || - (stats.status == :max_eval) || - (stats.status == :max_time) || - (stats.status == :user) || - (stats.status == :not_desc) - end - verbose > 0 && @info log_row(Any[stats.iter, f, ∇fNorm]) - - set_solution!(stats, x) - stats + (stats.status == :first_order) || + (stats.status == :max_eval) || + (stats.status == :max_time) || + (stats.status == :user) || + (stats.status == :not_desc) + + while !done + mul!(d, H, ∇f, -one(T), zero(T)) + slope = dot(n, d, ∇f) + if slope ≥ 0 + @error "not a descent direction" slope + set_status!(stats, :not_desc) + done = true + continue + end + + # Perform improved Armijo linesearch. + t, good_grad, ft, nbk, nbW = armijo_wolfe( + h, + f, + slope, + ∇ft, + τ₁ = τ₁, + bk_max = bk_max, + verbose = Bool(verbose_subsolver), + ) + + verbose > 0 && + mod(stats.iter, verbose) == 0 && + @info log_row(Any[stats.iter, f, ∇fNorm, slope, nbk]) + + copyaxpy!(n, t, d, x, xt) + good_grad || grad!(nlp, xt, ∇ft) + + # Update L-BFGS approximation. + d .*= t + @. ∇f = ∇ft - ∇f + push!(H, d, ∇f) + + # Move on. + x .= xt + f = ft + ∇f .= ∇ft + + ∇fNorm = nrm2(n, ∇f) + + set_objective!(stats, f) + set_iter!(stats, stats.iter + 1) + set_time!(stats, time() - start_time) + set_dual_residual!(stats, ∇fNorm) + optimal = ∇fNorm ≤ ϵ + + set_status!( + stats, + get_status( + nlp, + elapsed_time = stats.elapsed_time, + optimal = optimal, + max_eval = max_eval, + max_time = max_time, + ), + ) + + callback(nlp, solver, stats) + + done = + (stats.status == :first_order) || + (stats.status == :max_eval) || + (stats.status == :max_time) || + (stats.status == :user) || + (stats.status == :not_desc) + end + verbose > 0 && @info log_row(Any[stats.iter, f, ∇fNorm]) + + set_solution!(stats, x) + stats end diff --git a/src/parameters.jl b/src/parameters.jl index 4f84818..141df40 100644 --- a/src/parameters.jl +++ b/src/parameters.jl @@ -1,3 +1,3 @@ mem = 5 τ₁ = 0.9999 -bk_max = 25 \ No newline at end of file +bk_max = 25 diff --git a/tuning/benchmark.jl b/tuning/benchmark.jl index 46b2585..cc1b87f 100644 --- a/tuning/benchmark.jl +++ b/tuning/benchmark.jl @@ -7,45 +7,43 @@ using BenchmarkTools # 1st basic strategy: select 5 small unconstrained problems n = 5 meta = OptimizationProblems.meta -list = meta[ - meta.minimize .& (meta.ncon .== 0) .& .!meta.has_bounds .& (20 .≤ meta.nvar .≤ 100), - :name -] +list = + meta[meta.minimize.&(meta.ncon.==0).&.!meta.has_bounds.&(20 .≤ meta.nvar .≤ 100), :name] problems = [eval(p)() for p ∈ Symbol.(list[1:n])] param_set = TunerTestSolver.LBFGSParameterSet() function fun(vec_metrics::Vector{ProblemMetrics}) - penalty = 1e2 - global fx = 0 - for p in vec_metrics - failed = is_failure(BBModels.get_status(p)) - fx += failed * penalty - if !failed - id = get_pb_id(p) - nlp = problems[id] - nvar = nlp isa AbstractNLPModel ? nlp.meta.nvar : nlp().meta.nvar # better way to access number of variables? - nobj = get_counters(p).neval_obj - ngrad = get_counters(p).neval_grad * nvar - nhprod = get_counters(p).neval_hprod * nvar - nhess = get_counters(p).neval_hess * nvar^2 - fx += nobj + ngrad + nhprod + nhess + penalty = 1e2 + global fx = 0 + for p in vec_metrics + failed = is_failure(BBModels.get_status(p)) + fx += failed * penalty + if !failed + id = get_pb_id(p) + nlp = problems[id] + nvar = nlp isa AbstractNLPModel ? nlp.meta.nvar : nlp().meta.nvar # better way to access number of variables? + nobj = get_counters(p).neval_obj + ngrad = get_counters(p).neval_grad * nvar + nhprod = get_counters(p).neval_hprod * nvar + nhess = get_counters(p).neval_hess * nvar^2 + fx += nobj + ngrad + nhprod + nhess + end end - end - return fx + return fx end model = BBModel( - param_set, # AbstractParameterSet - problems, # vector of AbstractNLPModel - TunerTestSolver.lbfgs, # (::AbstractNLPModel, ::AbstractParameterSet) -> GenericExecutionStats - fun, # time_only, memory_only, sumfc OR a hand-made function + param_set, # AbstractParameterSet + problems, # vector of AbstractNLPModel + TunerTestSolver.lbfgs, # (::AbstractNLPModel, ::AbstractParameterSet) -> GenericExecutionStats + fun, # time_only, memory_only, sumfc OR a hand-made function ) vals = BBModels.random_search(model, verbose = 0) open("parameters.jl", "w") do io for (name, val) in zip(names(param_set), vals) - println(io, "$name = $val") + println(io, "$name = $val") end end