diff --git a/src/NonconvexNOMAD.jl b/src/NonconvexNOMAD.jl index 03100c3..37fe129 100644 --- a/src/NonconvexNOMAD.jl +++ b/src/NonconvexNOMAD.jl @@ -25,9 +25,30 @@ speculative_search::Bool=true, speculative_search_max::Int=1, nm_search::Bool=tr nm_search_stop_on_success::Bool=false, max_time::Union{Nothing,Int}=nothing, linear_converter::String=SVD =# -function NOMADOptions(; linear_equality_constraints = false, min_mesh_size = 0.0, initial_mesh_size = Float64[], granularity = 0.0, display_stats = ["OBJ", "CONS_H", "BBE", "TIME"], extra_display_stats = String[], linear_constraints_atol=1e-6, kwargs...) +function NOMADOptions(; + linear_equality_constraints = false, + min_mesh_size = 0.0, + initial_mesh_size = Float64[], + granularity = 0.0, + display_stats = ["OBJ", "CONS_H", "BBE", "TIME"], + extra_display_stats = String[], + linear_constraints_atol = 1e-6, + kwargs..., +) display_stats = unique(vcat(display_stats, extra_display_stats)) - return NOMADOptions(merge((; linear_equality_constraints, min_mesh_size, initial_mesh_size, granularity, display_stats, linear_constraints_atol), NamedTuple(kwargs))) + return NOMADOptions( + merge( + (; + linear_equality_constraints, + min_mesh_size, + initial_mesh_size, + granularity, + display_stats, + linear_constraints_atol, + ), + NamedTuple(kwargs), + ), + ) end @params mutable struct NOMADWorkspace <: Workspace @@ -37,27 +58,44 @@ end alg::NOMADAlg end function NOMADWorkspace( - model::VecModel, optimizer::NOMADAlg, + model::VecModel, + optimizer::NOMADAlg, x0::AbstractVector = getinit(model); - options = NOMADOptions(), kwargs..., + options = NOMADOptions(), + kwargs..., ) return NOMADWorkspace(model, copy(x0), options, optimizer) end @params struct NOMADResult <: AbstractResult - minimizer - minimum - result - alg - options + minimizer::Any + minimum::Any + result::Any + alg::Any + options::Any end -function NonconvexCore._optimize_precheck(model::NonconvexCore.AbstractModel, ::NOMADAlg, x0; options) - length(model.eq_constraints.fs) == 0 || options.nt.linear_equality_constraints || throw(ArgumentError("NOMAD does not support nonlinear equality constraints, only bound constraints, inequality constraints and linear equality constraints. You can set the `linear_equality_constraints` option to `true` if the equality constraint functions are indeed linear/affine.")) - length(model.sd_constraints.fs) == 0 || throw(ArgumentError("NOMAD does not support semidefinite constraints, only bound constraints, inequality constraints and linear equality constraints.")) +function NonconvexCore._optimize_precheck( + model::NonconvexCore.AbstractModel, + ::NOMADAlg, + x0; + options, +) + length(model.eq_constraints.fs) == 0 || + options.nt.linear_equality_constraints || + throw( + ArgumentError( + "NOMAD does not support nonlinear equality constraints, only bound constraints, inequality constraints and linear equality constraints. You can set the `linear_equality_constraints` option to `true` if the equality constraint functions are indeed linear/affine.", + ), + ) + length(model.sd_constraints.fs) == 0 || throw( + ArgumentError( + "NOMAD does not support semidefinite constraints, only bound constraints, inequality constraints and linear equality constraints.", + ), + ) return end -@generated function drop_ks(nt::NamedTuple{names}, ::Val{ks}) where {names, ks} +@generated function drop_ks(nt::NamedTuple{names}, ::Val{ks}) where {names,ks} ns = Tuple(setdiff(names, ks)) return :(NamedTuple{$ns}(nt)) end @@ -76,10 +114,18 @@ function optimize!(workspace::NOMADWorkspace) end if A !== nothing && norm(A * x0 - b) > options.nt.linear_constraints_atol - throw(ArgumentError("The initial solution doesn't satisfy the linear equality constraints.")) + throw( + ArgumentError( + "The initial solution doesn't satisfy the linear equality constraints.", + ), + ) end if length(model.ineq_constraints.fs) > 0 && any(>(0), model.ineq_constraints(x0)) - throw(ArgumentError("The initial solution doesn't satisfy the inequality constraints.")) + throw( + ArgumentError( + "The initial solution doesn't satisfy the inequality constraints.", + ), + ) end nb_outputs = 1 @@ -103,7 +149,11 @@ function optimize!(workspace::NOMADWorkspace) nb_outputs += N fill("PB", N) else - throw(ArgumentError("""Unsupported flag `"type"` value, please choose from `:explicit` and `:progressive`.""")) + throw( + ArgumentError( + """Unsupported flag `"type"` value, please choose from `:explicit` and `:progressive`.""", + ), + ) end end else @@ -118,19 +168,20 @@ function optimize!(workspace::NOMADWorkspace) obj(x0) model.ineq_constraints(x0) - eval_bb = x -> begin - try - if length(model.ineq_constraints.fs) > 0 - out = [finite_or_inf(obj(x)); finite_or_inf.(model.ineq_constraints(x))] - else - out = [finite_or_inf(obj(x))] + eval_bb = + x -> begin + try + if length(model.ineq_constraints.fs) > 0 + out = [finite_or_inf(obj(x)) finite_or_inf.(model.ineq_constraints(x))] + else + out = [finite_or_inf(obj(x))] + end + return (true, true, out) + catch + out = fill(Inf, nb_outputs) + return (false, true, out) end - return (true, true, out) - catch - out = fill(Inf, nb_outputs) - return (false, true, out) end - end nb_inputs = length(x0) input_types = map(enumerate(model.integer)) do (i, int) @@ -165,12 +216,29 @@ function optimize!(workspace::NOMADWorkspace) upper_bound = getmax(model) nomad_problem = NOMAD.NomadProblem( - nb_inputs, nb_outputs, output_types, eval_bb; - input_types, lower_bound, upper_bound, A, b, - min_mesh_size, initial_mesh_size, granularity, + nb_inputs, + nb_outputs, + output_types, + eval_bb; + input_types, + lower_bound, + upper_bound, + A, + b, + min_mesh_size, + initial_mesh_size, + granularity, ) - nomad_options = drop_ks(options.nt, Val((:linear_equality_constraints, :min_mesh_size, :initial_mesh_size, :granularity))) + nomad_options = drop_ks( + options.nt, + Val(( + :linear_equality_constraints, + :min_mesh_size, + :initial_mesh_size, + :granularity, + )), + ) for k in keys(nomad_options) setproperty!(nomad_problem.options, k, nomad_options[k]) @@ -185,7 +253,7 @@ function optimize!(workspace::NOMADWorkspace) end end -function Workspace(model::VecModel, optimizer::NOMADAlg, args...; kwargs...,) +function Workspace(model::VecModel, optimizer::NOMADAlg, args...; kwargs...) return NOMADWorkspace(model, optimizer, args...; kwargs...) end diff --git a/test/runtests.jl b/test/runtests.jl index 5c45a46..d020cb1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,7 +1,7 @@ using NonconvexNOMAD, LinearAlgebra, Test f(x::AbstractVector) = sqrt(x[2]) -g(x::AbstractVector, a, b) = (a*x[1] + b)^3 - x[2] +g(x::AbstractVector, a, b) = (a * x[1] + b)^3 - x[2] x0 = [0.5, 2.3] @testset "Alg type - $alg_type" for alg_type in [:explicit, :progressive, :custom] @@ -14,8 +14,8 @@ x0 = [0.5, 2.3] alg = NOMADAlg(alg_type) r1 = NonconvexNOMAD.optimize(m, alg, x0, options = options) - @test abs(r1.minimum - sqrt(8/27)) < 1e-4 - @test norm(r1.minimizer - [1/3, 8/27]) < 1e-4 + @test abs(r1.minimum - sqrt(8 / 27)) < 1e-4 + @test norm(r1.minimizer - [1 / 3, 8 / 27]) < 1e-4 setinteger!(m, 1, true) r2 = NonconvexNOMAD.optimize(m, alg, [0.0, x0[2]], options = options) @@ -38,8 +38,8 @@ x0 = [0.5, 2.3] alg = NOMADAlg(alg_type) r1 = NonconvexNOMAD.optimize(m, alg, x0, options = options) - @test abs(r1.minimum - sqrt(8/27)) < 1e-4 - @test norm(r1.minimizer - [1/3, 8/27]) < 1e-4 + @test abs(r1.minimum - sqrt(8 / 27)) < 1e-4 + @test norm(r1.minimizer - [1 / 3, 8 / 27]) < 1e-4 setinteger!(m, 1, true) r2 = NonconvexNOMAD.optimize(m, alg, [0.0, x0[2]], options = options) @@ -58,13 +58,13 @@ x0 = [0.5, 2.3] m = Model(f) addvar!(m, [0.0, 0.0], [10.0, 10.0]) add_ineq_constraint!(m, x -> g(x, 2, 0), flags = [:explicit]) - add_eq_constraint!(m, x -> sum(x) - 1/3 - 8/27) + add_eq_constraint!(m, x -> sum(x) - 1 / 3 - 8 / 27) alg = NOMADAlg(alg_type) _x0 = x0 / sum(x0) * (1 / 3 + 8 / 27) r = NonconvexNOMAD.optimize(m, alg, _x0, options = options) - @test abs(r.minimum - sqrt(8/27)) < 1e-6 - @test norm(r.minimizer - [1/3, 8/27]) < 1e-6 + @test abs(r.minimum - sqrt(8 / 27)) < 1e-6 + @test norm(r.minimizer - [1 / 3, 8 / 27]) < 1e-6 end @testset "Equality constraints 2" begin @@ -72,36 +72,44 @@ x0 = [0.5, 2.3] m = Model(f) addvar!(m, [1e-4, 1e-4], [10.0, 10.0]) add_ineq_constraint!(m, x -> g(x, 2, 0), flags = [:progressive]) - add_eq_constraint!(m, x -> sum(x) - 1/3 - 8/27) + add_eq_constraint!(m, x -> sum(x) - 1 / 3 - 8 / 27) alg = NOMADAlg(alg_type) _x0 = x0 / sum(x0) * (1 / 3 + 8 / 27) r = NonconvexNOMAD.optimize(m, alg, _x0, options = options) - @test abs(r.minimum - sqrt(8/27)) < 1e-6 - @test norm(r.minimizer - [1/3, 8/27]) < 1e-6 + @test abs(r.minimum - sqrt(8 / 27)) < 1e-6 + @test norm(r.minimizer - [1 / 3, 8 / 27]) < 1e-6 end @testset "Block constraints 1" begin options = NOMADOptions() m = Model(f) addvar!(m, [1e-4, 1e-4], [10.0, 10.0]) - add_ineq_constraint!(m, FunctionWrapper(x -> [g(x, 2, 0), g(x, -1, 1)], 2), flags = [:explicit]) + add_ineq_constraint!( + m, + FunctionWrapper(x -> [g(x, 2, 0), g(x, -1, 1)], 2), + flags = [:explicit], + ) alg = NOMADAlg(alg_type) r = NonconvexNOMAD.optimize(m, alg, x0, options = options) - @test abs(r.minimum - sqrt(8/27)) < 1e-6 - @test norm(r.minimizer - [1/3, 8/27]) < 1e-6 + @test abs(r.minimum - sqrt(8 / 27)) < 1e-6 + @test norm(r.minimizer - [1 / 3, 8 / 27]) < 1e-6 end @testset "Block constraints 2" begin options = NOMADOptions() m = Model(f) addvar!(m, [1e-4, 1e-4], [10.0, 10.0]) - add_ineq_constraint!(m, FunctionWrapper(x -> [g(x, 2, 0), g(x, -1, 1)], 2), flags = [:progressive]) + add_ineq_constraint!( + m, + FunctionWrapper(x -> [g(x, 2, 0), g(x, -1, 1)], 2), + flags = [:progressive], + ) alg = NOMADAlg(alg_type) r = NonconvexNOMAD.optimize(m, alg, x0, options = options) - @test abs(r.minimum - sqrt(8/27)) < 1e-6 - @test norm(r.minimizer - [1/3, 8/27]) < 1e-6 + @test abs(r.minimum - sqrt(8 / 27)) < 1e-6 + @test norm(r.minimizer - [1 / 3, 8 / 27]) < 1e-6 end end