From 1705815490aae4998090987d7c446e58edff80c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Thu, 24 Apr 2025 00:38:03 +0300 Subject: [PATCH 01/12] refactor(OptimizationBBO): use the BBO API for setting the objective in the callback --- lib/OptimizationBBO/src/OptimizationBBO.jl | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/OptimizationBBO/src/OptimizationBBO.jl b/lib/OptimizationBBO/src/OptimizationBBO.jl index f0d3d6f33..bdc3d6f4c 100644 --- a/lib/OptimizationBBO/src/OptimizationBBO.jl +++ b/lib/OptimizationBBO/src/OptimizationBBO.jl @@ -83,6 +83,13 @@ function __map_optimizer_args(prob::Optimization.OptimizationCache, opt::BBO; return mapped_args end +# single objective +map_objective(obj) = obj +# multiobjective +function map_objective(obj::BlackBoxOptim.IndexedTupleFitness) + obj.orig +end + function SciMLBase.__solve(cache::Optimization.OptimizationCache{ F, RC, @@ -117,12 +124,13 @@ function SciMLBase.__solve(cache::Optimization.OptimizationCache{ else n_steps = BlackBoxOptim.num_steps(trace) curr_u = decompose_trace(trace, cache.progress) + objective = map_objective(BlackBoxOptim.best_fitness(trace)) opt_state = Optimization.OptimizationState(; iter = n_steps, u = curr_u, - objective = x[1], + objective, original = trace) - cb_call = cache.callback(opt_state, x...) + cb_call = cache.callback(opt_state, objective) end if !(cb_call isa Bool) From 420e5e6409ffbe4dcaf6ee4ec50f3a3b953a10b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Thu, 24 Apr 2025 00:43:42 +0300 Subject: [PATCH 02/12] refactor(OptimizationBBO): simplify loss function the loss function returns a vector or tuple in the multi-objective case and a scalar in the single objective case. no need for global variables --- lib/OptimizationBBO/src/OptimizationBBO.jl | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/OptimizationBBO/src/OptimizationBBO.jl b/lib/OptimizationBBO/src/OptimizationBBO.jl index bdc3d6f4c..6fadd0dfa 100644 --- a/lib/OptimizationBBO/src/OptimizationBBO.jl +++ b/lib/OptimizationBBO/src/OptimizationBBO.jl @@ -116,7 +116,6 @@ function SciMLBase.__solve(cache::Optimization.OptimizationCache{ P, C } - local x, cur, state function _cb(trace) if cache.callback === Optimization.DEFAULT_CALLBACK @@ -147,13 +146,7 @@ function SciMLBase.__solve(cache::Optimization.OptimizationCache{ maxtime = Optimization._check_and_convert_maxtime(cache.solver_args.maxtime) _loss = function (θ) - if isa(cache.f, MultiObjectiveOptimizationFunction) - x = (cache.f(θ, cache.p),) - return x[1] - else - x = cache.f(θ, cache.p) - return first(x) - end + cache.f(θ, cache.p) end opt_args = __map_optimizer_args(cache, cache.opt; From 934dc1396c95caeae252c97c8e1c43220ede361e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Thu, 24 Apr 2025 00:44:11 +0300 Subject: [PATCH 03/12] refactor(OptimizationBBO): use the BBO API for the elapsed time --- lib/OptimizationBBO/src/OptimizationBBO.jl | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/OptimizationBBO/src/OptimizationBBO.jl b/lib/OptimizationBBO/src/OptimizationBBO.jl index 6fadd0dfa..db4ef4d0d 100644 --- a/lib/OptimizationBBO/src/OptimizationBBO.jl +++ b/lib/OptimizationBBO/src/OptimizationBBO.jl @@ -158,8 +158,6 @@ function SciMLBase.__solve(cache::Optimization.OptimizationCache{ opt_setup = BlackBoxOptim.bbsetup(_loss; opt_args...) - t0 = time() - if isnothing(cache.u0) opt_res = BlackBoxOptim.bboptimize(opt_setup) else @@ -171,13 +169,11 @@ function SciMLBase.__solve(cache::Optimization.OptimizationCache{ Base.@logmsg(Base.LogLevel(-1), "", progress=1, _id=:OptimizationBBO) end - t1 = time() - # Use the improved convert function opt_ret = Optimization.deduce_retcode(opt_res.stop_reason) stats = Optimization.OptimizationStats(; iterations = opt_res.iterations, - time = t1 - t0, + time = opt_res.elapsed_time, fevals = opt_res.f_calls) SciMLBase.build_solution(cache, cache.opt, BlackBoxOptim.best_candidate(opt_res), From 5d2ab19ff373e2e0880a66fec40c1902a6e8e6e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Thu, 24 Apr 2025 00:44:47 +0300 Subject: [PATCH 04/12] test(OptimizationBBO): test that the callback reports the correct objective --- lib/OptimizationBBO/test/runtests.jl | 32 +++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/OptimizationBBO/test/runtests.jl b/lib/OptimizationBBO/test/runtests.jl index e5215fee8..5391e2d05 100644 --- a/lib/OptimizationBBO/test/runtests.jl +++ b/lib/OptimizationBBO/test/runtests.jl @@ -27,12 +27,14 @@ using Test fitness_progress_history = [] function cb(state, fitness) - push!(fitness_progress_history, [state.u, fitness]) + push!(fitness_progress_history, [deepcopy(state), fitness]) return false end sol = solve(prob, BBO_adaptive_de_rand_1_bin_radiuslimited(), callback = cb) # println(fitness_progress_history) @test !isempty(fitness_progress_history) + fp1 = fitness_progress_history[1] + @test BlackBoxOptim.best_fitness(fp1[1].original) == fp1[1].objective == fp1[2] @test_logs begin (Base.LogLevel(-1), "loss: 0.0") @@ -77,6 +79,34 @@ using Test @test sol_1.objective[2]≈1.7763568e-15 atol=1e-3 end + @testset "Sphere and Rastrigin Functions with callback" begin + function multi_obj_func_1(x, p) + f1 = sum(x .^ 2) # Sphere function + f2 = sum(x .^ 2 .- 10 .* cos.(2π .* x) .+ 10) # Rastrigin function + return (f1, f2) + end + + fitness_progress_history = [] + function cb(state, fitness) + push!(fitness_progress_history, deepcopy(state)) + return false + end + + mof_1 = MultiObjectiveOptimizationFunction(multi_obj_func_1) + prob_1 = Optimization.OptimizationProblem(mof_1, u0; lb = lb, ub = ub) + sol_1 = solve(prob_1, opt, NumDimensions = 2, + FitnessScheme = ParetoFitnessScheme{2}(is_minimizing = true), callback=cb) + + fp1 = fitness_progress_history[1] + @test BlackBoxOptim.best_fitness(fp1.original).orig == fp1.objective + @test length(fp1.objective) == 2 + + @test sol_1 ≠ nothing + println("Solution for Sphere and Rastrigin: ", sol_1) + @test sol_1.objective[1]≈6.9905986e-18 atol=1e-3 + @test sol_1.objective[2]≈1.7763568e-15 atol=1e-3 + end + # Test 2: Rosenbrock and Ackley Functions @testset "Rosenbrock and Ackley Functions" begin function multi_obj_func_2(x, p) From 59fe9095cab868aa6178194f956fafc928c50c63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Thu, 24 Apr 2025 00:45:25 +0300 Subject: [PATCH 05/12] refactor(OptimizationMOI): add the time and the iterations to the stats --- lib/OptimizationMOI/src/nlp.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/OptimizationMOI/src/nlp.jl b/lib/OptimizationMOI/src/nlp.jl index 24301e29a..3d5fa1ff1 100644 --- a/lib/OptimizationMOI/src/nlp.jl +++ b/lib/OptimizationMOI/src/nlp.jl @@ -559,7 +559,8 @@ function SciMLBase.__solve(cache::MOIOptimizationNLPCache) minimum = NaN opt_ret = SciMLBase.ReturnCode.Default end - stats = Optimization.OptimizationStats() + stats = Optimization.OptimizationStats(time = MOI.get(opt_setup, MOI.SolveTimeSec()), + iterations = MOI.get(opt_setup, MOI.BarrierIterations())) return SciMLBase.build_solution(cache, cache.opt, minimizer, From 54d2ae8ac9851f09a9dacdd0dda16f22c0241a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Thu, 24 Apr 2025 00:45:54 +0300 Subject: [PATCH 06/12] test(OptimizationMOI): test that the stats are reported --- lib/OptimizationMOI/test/runtests.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/OptimizationMOI/test/runtests.jl b/lib/OptimizationMOI/test/runtests.jl index f1002b3f0..8a3dea595 100644 --- a/lib/OptimizationMOI/test/runtests.jl +++ b/lib/OptimizationMOI/test/runtests.jl @@ -88,6 +88,10 @@ end sol = solve(prob, opt) #test reuse of optimizer @test 10 * sol.objective < l1 + # test stats + @test sol.stats.time > 0 + @test sol.stats.iterations > 0 + sol = solve(prob, OptimizationMOI.MOI.OptimizerWithAttributes(Ipopt.Optimizer, "max_cpu_time" => 60.0)) From 6f5f8791e0851b6893912935e3b0978669187247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Fri, 25 Apr 2025 02:35:53 +0300 Subject: [PATCH 07/12] refactor(OptimizationMOI): use BarrierIterations only when available unfortunatelly there's no API for determining this --- lib/OptimizationMOI/src/nlp.jl | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/OptimizationMOI/src/nlp.jl b/lib/OptimizationMOI/src/nlp.jl index 3d5fa1ff1..099a8c70a 100644 --- a/lib/OptimizationMOI/src/nlp.jl +++ b/lib/OptimizationMOI/src/nlp.jl @@ -559,8 +559,19 @@ function SciMLBase.__solve(cache::MOIOptimizationNLPCache) minimum = NaN opt_ret = SciMLBase.ReturnCode.Default end - stats = Optimization.OptimizationStats(time = MOI.get(opt_setup, MOI.SolveTimeSec()), - iterations = MOI.get(opt_setup, MOI.BarrierIterations())) + + # check if the solver supports BarrierIterations + iterations = try + MOI.get(opt_setup, MOI.BarrierIterations()) + catch e + if !(e isa MOI.GetAttributeNotAllowed) + rethrow(e) + end + 0 + end + + stats = Optimization.OptimizationStats(; time = MOI.get(opt_setup, MOI.SolveTimeSec()), + iterations) return SciMLBase.build_solution(cache, cache.opt, minimizer, From f691a6a27119d9b0d7d0994b399d3a5ac87d7f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Fri, 25 Apr 2025 02:36:17 +0300 Subject: [PATCH 08/12] test(OptimizationMOI): add more tests for stats --- lib/OptimizationMOI/test/runtests.jl | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/OptimizationMOI/test/runtests.jl b/lib/OptimizationMOI/test/runtests.jl index 8a3dea595..649facb01 100644 --- a/lib/OptimizationMOI/test/runtests.jl +++ b/lib/OptimizationMOI/test/runtests.jl @@ -77,7 +77,7 @@ end # cache interface cache = init(prob, Ipopt.Optimizer()) sol = solve!(cache) - @test 10 * sol.minimum < l1 + @test 10 * sol.objective < l1 optprob = OptimizationFunction(rosenbrock, Optimization.AutoZygote()) prob = OptimizationProblem(optprob, x0, _p; sense = Optimization.MinSense) @@ -97,11 +97,21 @@ end "max_cpu_time" => 60.0)) @test 10 * sol.objective < l1 + # test stats with AbstractBridgeOptimizer + sol = solve(prob, + OptimizationMOI.MOI.OptimizerWithAttributes(Ipopt.Optimizer, + "max_cpu_time" => 60.0, "max_iter" => 5)) + + @test 60 > sol.stats.time > 0 + @test sol.stats.iterations == 5 + sol = solve(prob, OptimizationMOI.MOI.OptimizerWithAttributes(NLopt.Optimizer, "algorithm" => :LN_BOBYQA)) @test 10 * sol.objective < l1 + @test sol.stats.time > 0 + sol = solve(prob, OptimizationMOI.MOI.OptimizerWithAttributes(NLopt.Optimizer, "algorithm" => :LD_LBFGS)) @@ -165,6 +175,7 @@ end res = solve(optprob, minlp_solver) @test res.u == [0.0, 0.0, 1.0, 0.0] @test res.objective == -4.0 + @test res.stats.time > 0 end @testset "Integer Domain" begin From e46be535cf2dddfb7dc0668369123a16d5e900d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Sat, 26 Apr 2025 05:44:37 +0300 Subject: [PATCH 09/12] test(OptimizationBBO): avoid deepcopy in callback https://github.com/SciML/Optimization.jl/pull/899#issuecomment-2831719558 --- lib/OptimizationBBO/test/runtests.jl | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/OptimizationBBO/test/runtests.jl b/lib/OptimizationBBO/test/runtests.jl index 5391e2d05..61dd47528 100644 --- a/lib/OptimizationBBO/test/runtests.jl +++ b/lib/OptimizationBBO/test/runtests.jl @@ -26,15 +26,20 @@ using Test @test 10 * sol.objective < l1 fitness_progress_history = [] + fitness_progress_history_orig = [] + loss_history = [] function cb(state, fitness) - push!(fitness_progress_history, [deepcopy(state), fitness]) + push!(fitness_progress_history, state.objective) + push!(fitness_progress_history_orig, BlackBoxOptim.best_fitness(state.original)) + push!(loss_history, fitness) return false end sol = solve(prob, BBO_adaptive_de_rand_1_bin_radiuslimited(), callback = cb) # println(fitness_progress_history) @test !isempty(fitness_progress_history) fp1 = fitness_progress_history[1] - @test BlackBoxOptim.best_fitness(fp1[1].original) == fp1[1].objective == fp1[2] + fp2 = fitness_progress_history_orig[1] + @test fp2 == fp1 == loss_history[1] @test_logs begin (Base.LogLevel(-1), "loss: 0.0") @@ -87,19 +92,23 @@ using Test end fitness_progress_history = [] + fitness_progress_history_orig = [] function cb(state, fitness) - push!(fitness_progress_history, deepcopy(state)) + push!(fitness_progress_history, state.objective) + push!(fitness_progress_history_orig, BlackBoxOptim.best_fitness(state.original)) return false end mof_1 = MultiObjectiveOptimizationFunction(multi_obj_func_1) prob_1 = Optimization.OptimizationProblem(mof_1, u0; lb = lb, ub = ub) sol_1 = solve(prob_1, opt, NumDimensions = 2, - FitnessScheme = ParetoFitnessScheme{2}(is_minimizing = true), callback=cb) + FitnessScheme = ParetoFitnessScheme{2}(is_minimizing = true), + callback=cb) fp1 = fitness_progress_history[1] - @test BlackBoxOptim.best_fitness(fp1.original).orig == fp1.objective - @test length(fp1.objective) == 2 + fp2 = fitness_progress_history_orig[1] + @test fp2.orig == fp1 + @test length(fp1) == 2 @test sol_1 ≠ nothing println("Solution for Sphere and Rastrigin: ", sol_1) From f2946a62d5a65679b493d0a8f9248d2c316897ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Mon, 28 Apr 2025 14:28:21 +0300 Subject: [PATCH 10/12] build(OptimizationNOMAD): bump NOMAD to 2.4.1 --- lib/OptimizationNOMAD/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/OptimizationNOMAD/Project.toml b/lib/OptimizationNOMAD/Project.toml index 86fe4f839..807024bed 100644 --- a/lib/OptimizationNOMAD/Project.toml +++ b/lib/OptimizationNOMAD/Project.toml @@ -10,7 +10,7 @@ Reexport = "189a3867-3050-52da-a836-e630ba90ab69" [compat] julia = "1" -NOMAD = "2" +NOMAD = "2.4.1" Optimization = "4" Reexport = "1.2" From 6fbbe5a3a5aac366e5c37c7b2894949663a63802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Mon, 28 Apr 2025 14:31:50 +0300 Subject: [PATCH 11/12] chore(OptimizationBBO): fix formatting --- lib/OptimizationBBO/src/OptimizationBBO.jl | 1 - lib/OptimizationBBO/test/runtests.jl | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/OptimizationBBO/src/OptimizationBBO.jl b/lib/OptimizationBBO/src/OptimizationBBO.jl index db4ef4d0d..0e203de62 100644 --- a/lib/OptimizationBBO/src/OptimizationBBO.jl +++ b/lib/OptimizationBBO/src/OptimizationBBO.jl @@ -116,7 +116,6 @@ function SciMLBase.__solve(cache::Optimization.OptimizationCache{ P, C } - function _cb(trace) if cache.callback === Optimization.DEFAULT_CALLBACK cb_call = false diff --git a/lib/OptimizationBBO/test/runtests.jl b/lib/OptimizationBBO/test/runtests.jl index 61dd47528..1295465fc 100644 --- a/lib/OptimizationBBO/test/runtests.jl +++ b/lib/OptimizationBBO/test/runtests.jl @@ -95,7 +95,8 @@ using Test fitness_progress_history_orig = [] function cb(state, fitness) push!(fitness_progress_history, state.objective) - push!(fitness_progress_history_orig, BlackBoxOptim.best_fitness(state.original)) + push!(fitness_progress_history_orig, + BlackBoxOptim.best_fitness(state.original)) return false end @@ -103,7 +104,7 @@ using Test prob_1 = Optimization.OptimizationProblem(mof_1, u0; lb = lb, ub = ub) sol_1 = solve(prob_1, opt, NumDimensions = 2, FitnessScheme = ParetoFitnessScheme{2}(is_minimizing = true), - callback=cb) + callback = cb) fp1 = fitness_progress_history[1] fp2 = fitness_progress_history_orig[1] From d357411aca2d85eb681c5f4a44093822c5286bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Miclu=C8=9Ba-C=C3=A2mpeanu?= Date: Tue, 29 Apr 2025 18:06:12 +0300 Subject: [PATCH 12/12] build: bump versions --- lib/OptimizationBBO/Project.toml | 2 +- lib/OptimizationMOI/Project.toml | 2 +- lib/OptimizationNOMAD/Project.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/OptimizationBBO/Project.toml b/lib/OptimizationBBO/Project.toml index 7977ee438..8deb4617f 100644 --- a/lib/OptimizationBBO/Project.toml +++ b/lib/OptimizationBBO/Project.toml @@ -1,7 +1,7 @@ name = "OptimizationBBO" uuid = "3e6eede4-6085-4f62-9a71-46d9bc1eb92b" authors = ["Vaibhav Dixit and contributors"] -version = "0.4.0" +version = "0.4.1" [deps] BlackBoxOptim = "a134a8b2-14d6-55f6-9291-3336d3ab0209" diff --git a/lib/OptimizationMOI/Project.toml b/lib/OptimizationMOI/Project.toml index 18ae43ec1..5a5031c82 100644 --- a/lib/OptimizationMOI/Project.toml +++ b/lib/OptimizationMOI/Project.toml @@ -1,7 +1,7 @@ name = "OptimizationMOI" uuid = "fd9f6733-72f4-499f-8506-86b2bdd0dea1" authors = ["Vaibhav Dixit and contributors"] -version = "0.5.2" +version = "0.5.3" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" diff --git a/lib/OptimizationNOMAD/Project.toml b/lib/OptimizationNOMAD/Project.toml index 807024bed..17e16f1fe 100644 --- a/lib/OptimizationNOMAD/Project.toml +++ b/lib/OptimizationNOMAD/Project.toml @@ -1,7 +1,7 @@ name = "OptimizationNOMAD" uuid = "2cab0595-8222-4775-b714-9828e6a9e01b" authors = ["Vaibhav Dixit and contributors"] -version = "0.3.0" +version = "0.3.1" [deps] Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba"