Skip to content

Use weak dependencies for NLopt, remove Parameters. #42

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

Merged
merged 2 commits into from
Mar 21, 2025
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
15 changes: 8 additions & 7 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
name = "MultistartOptimization"
uuid = "3933049c-43be-478e-a8bb-6e0f7fd53575"
authors = ["Tamas K. Papp <[email protected]>"]
version = "0.2.3"
version = "0.3.0"

[deps]
ArgCheck = "dce04be8-c92d-5529-be00-80e4d2c0e197"
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
NLopt = "76087f3c-5699-56af-9a33-bf431cd00edd"
Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a"
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
Sobol = "ed01d8cd-4d21-5b2a-85b4-cc3bdc58bad4"

[weakdeps]
NLopt = "76087f3c-5699-56af-9a33-bf431cd00edd"

[extensions]
MultistartOptimizationNLoptExt = "NLopt"

[compat]
ArgCheck = "1, 2"
DocStringExtensions = "0.8, 0.9"
NLopt = "0.5, 0.6, 1"
Parameters = "0.12"
Requires = "1.1"
Sobol = "1.3"
julia = "1.6"
julia = "1.10"
29 changes: 18 additions & 11 deletions src/nlopt.jl → ext/MultistartOptimizationNLoptExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,27 @@
#### Local minimization with NLopt. Loaded on demand when `NLopt` is.
####

module MultistartOptimizationNLoptExt

import NLopt

import MultistartOptimization: local_minimization
import MultistartOptimization: local_minimization, MinimizationProblem,
MultistartOptimization, NLopt_local_method

export NLoptLocalMethod
using ArgCheck: @argcheck
using DocStringExtensions: SIGNATURES

const NLopt_ret_success = Set([:SUCCESS, :STOPVAL_REACHED, :FTOL_REACHED, :XTOL_REACHED,
:MAXEVAL_REACHED, :MAXTIME_REACHED])

Base.@kwdef struct NLoptLocalMethod{S}
algorithm::NLopt.Algorithm
xtol_abs::Float64 = 1e-8
xtol_rel::Float64 = 1e-8
maxeval::Int = 0
maxtime::Float64 = 0.0
xtol_abs::Float64
xtol_rel::Float64
maxeval::Int
maxtime::Float64
"Return values which are considered as “success”."
ret_success::S = NLopt_ret_success
ret_success::S
end

"""
Expand All @@ -30,8 +34,9 @@ kept in the result), all negative return values are considered invalid.

See the NLopt documentation for the options. Defaults are changed slightly.
"""
function NLoptLocalMethod(algorithm::NLopt.Algorithm; options...)
NLoptLocalMethod(; algorithm = algorithm, options...)
function NLopt_local_method(algorithm::NLopt.Algorithm; xtol_abs = 1e-8, xtol_rel = 1e-8,
maxeval = 0, maxtime = 0.0, ret_success = NLopt_ret_success)
NLoptLocalMethod(; algorithm, xtol_abs, xtol_rel, maxeval, maxtime, ret_success)
end

"""
Expand All @@ -42,8 +47,8 @@ Solve `minimization_problem` using `local_method`, starting from `x`. Return a
"""
function local_minimization(local_method::NLoptLocalMethod,
minimization_problem::MinimizationProblem, x)
@unpack algorithm, xtol_abs, xtol_rel, maxeval, maxtime, ret_success = local_method
@unpack objective, lower_bounds, upper_bounds = minimization_problem
(; algorithm, xtol_abs, xtol_rel, maxeval, maxtime, ret_success) = local_method
(; objective, lower_bounds, upper_bounds) = minimization_problem
opt = NLopt.Opt(algorithm, length(x))
opt.lower_bounds = lower_bounds
opt.upper_bounds = upper_bounds
Expand All @@ -66,3 +71,5 @@ function nlopt_nondifferentiable_wrapper(fn)
end
return f̃
end

end
6 changes: 0 additions & 6 deletions src/MultistartOptimization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,9 @@ module MultistartOptimization
using ArgCheck: @argcheck
using Base.Threads: @spawn, fetch
using DocStringExtensions: FIELDS, FUNCTIONNAME, SIGNATURES, TYPEDEF
using Parameters: @unpack
using Requires: @require
using Sobol: SobolSeq, Sobol

include("generic_api.jl")
include("tiktak.jl")

function __init__()
@require NLopt="76087f3c-5699-56af-9a33-bf431cd00edd" include("nlopt.jl")
end

end # module
9 changes: 8 additions & 1 deletion src/generic_api.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#### generic API
####

export MinimizationProblem, local_minimization, multistart_minimization
export MinimizationProblem, local_minimization, multistart_minimization, NLopt_local_method

struct MinimizationProblem{F,T<:AbstractVector}
"The function to be minimized."
Expand Down Expand Up @@ -63,3 +63,10 @@ end
Multistart minimization using the given multistart and local methods.
"""
function multistart_minimization end

"""
A placeholder for integrating into the `NLopt` interface.

Methods are added when `NLopt` is loaded (via weak dependencies).
"""
function NLopt_local_method end
8 changes: 4 additions & 4 deletions src/tiktak.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function TikTak(quasirandom_N; keep_ratio = 0.1, θ_min = 0.1, θ_max = 0.995,
end

function _weight_parameter(t::TikTak, i)
@unpack initial_N, θ_min, θ_max, θ_pow = t
(; initial_N, θ_min, θ_max, θ_pow) = t
clamp((i / initial_N)^θ_pow, θ_min, θ_max)
end

Expand Down Expand Up @@ -76,7 +76,7 @@ When `use_threads`, execution is parallelized using `Threads.@spawn`.
"""
function sobol_starting_points(minimization_problem::MinimizationProblem, N::Integer,
use_threads::Bool)
@unpack objective, lower_bounds, upper_bounds = minimization_problem
(; objective, lower_bounds, upper_bounds) = minimization_problem
s = SobolSeq(lower_bounds, upper_bounds)
skip(s, N) # better uniformity
points = Iterators.take(s, N)
Expand Down Expand Up @@ -113,8 +113,8 @@ function multistart_minimization(multistart_method::TikTak, local_method,
minimization_problem;
use_threads = true,
prepend_points = Vector{Vector{Float64}}())
@unpack quasirandom_N, initial_N, θ_min, θ_max, θ_pow = multistart_method
@unpack objective, lower_bounds, upper_bounds = minimization_problem
(; quasirandom_N, initial_N, θ_min, θ_max, θ_pow) = multistart_method
(; objective, lower_bounds, upper_bounds) = minimization_problem
@argcheck(all(x -> all(lower_bounds .≤ x .≤ upper_bounds), prepend_points),
"prepend_points outside problem bounds")
quasirandom_points = sobol_starting_points(minimization_problem, quasirandom_N,
Expand Down
6 changes: 3 additions & 3 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ end
for F in setdiff(TEST_FUNCTIONS, (RASTRIGIN, )) # Rastrigin disabled for now
n = 10
P = MinimizationProblem(F, lower_bounds(F, n), upper_bounds(F, n))
local_method = NLoptLocalMethod(NLopt.LN_BOBYQA)
local_method = NLopt_local_method(NLopt.LN_BOBYQA)
multistart_method = TikTak(100)
p = multistart_minimization(multistart_method, local_method, P)
x₀ = minimum_location(F, n)
Expand Down Expand Up @@ -61,7 +61,7 @@ end
for F in setdiff(TEST_FUNCTIONS, (RASTRIGIN, )) # Rastrigin disabled for now
n = 10
P = MinimizationProblem(autodiff(F), lower_bounds(F, n), upper_bounds(F, n))
local_method = NLoptLocalMethod(NLopt.LD_LBFGS)
local_method = NLopt_local_method(NLopt.LD_LBFGS)
multistart_method = TikTak(100)
p = multistart_minimization(multistart_method, local_method, P)
x₀ = minimum_location(F, n)
Expand All @@ -83,7 +83,7 @@ end
α * sum(abs2, x) + (1 - α) * vz1
end
P = MinimizationProblem(g, lb, ub)
MM, LM = TikTak(100), NLoptLocalMethod(NLopt.LN_BOBYQA)
MM, LM = TikTak(100), NLopt_local_method(NLopt.LN_BOBYQA)
r0 = multistart_minimization(MM, LM, P; use_threads = false)
@test r0.value ≈ 0 atol = 1e-9 # sanity check
r1 = multistart_minimization(MM, LM, P; use_threads = false, prepend_points = [z1])
Expand Down
Loading