Skip to content

Commit c6f9e90

Browse files
Merge pull request #787 from ParasPuneetSingh/master
Updating solver __solve function for MOO
2 parents 8b55419 + 0cc91e8 commit c6f9e90

File tree

4 files changed

+240
-17
lines changed

4 files changed

+240
-17
lines changed

lib/OptimizationBBO/src/OptimizationBBO.jl

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module OptimizationBBO
33
using Reexport
44
import Optimization
55
import BlackBoxOptim, Optimization.SciMLBase
6+
import Optimization.SciMLBase: MultiObjectiveOptimizationFunction
67

78
abstract type BBO end
89

@@ -15,6 +16,11 @@ for j in string.(BlackBoxOptim.SingleObjectiveMethodNames)
1516
eval(Meta.parse("export BBO_" * j))
1617
end
1718

19+
Base.@kwdef struct BBO_borg_moea <: BBO
20+
method = :borg_moea
21+
end
22+
export BBO_borg_moea
23+
1824
function decompose_trace(opt::BlackBoxOptim.OptRunController, progress)
1925
if progress
2026
maxiters = opt.max_steps
@@ -142,17 +148,32 @@ function SciMLBase.__solve(cache::Optimization.OptimizationCache{
142148
maxtime = Optimization._check_and_convert_maxtime(cache.solver_args.maxtime)
143149

144150
_loss = function (θ)
145-
if cache.callback === Optimization.DEFAULT_CALLBACK &&
146-
cache.data === Optimization.DEFAULT_DATA
147-
return first(cache.f(θ, cache.p))
148-
elseif cache.callback === Optimization.DEFAULT_CALLBACK
149-
return first(cache.f(θ, cache.p, cur...))
150-
elseif cache.data !== Optimization.DEFAULT_DATA
151-
x = cache.f(θ, cache.p)
152-
return first(x)
151+
if isa(cache.f, MultiObjectiveOptimizationFunction)
152+
if cache.callback === Optimization.DEFAULT_CALLBACK &&
153+
cache.data === Optimization.DEFAULT_DATA
154+
return cache.f(θ, cache.p)
155+
elseif cache.callback === Optimization.DEFAULT_CALLBACK
156+
return cache.f(θ, cache.p, cur...)
157+
elseif cache.data !== Optimization.DEFAULT_DATA
158+
x = cache.f(θ, cache.p)
159+
return x
160+
else
161+
x = cache.f(θ, cache.p, cur...)
162+
return first(x)
163+
end
153164
else
154-
x = cache.f(θ, cache.p, cur...)
155-
return first(x)
165+
if cache.callback === Optimization.DEFAULT_CALLBACK &&
166+
cache.data === Optimization.DEFAULT_DATA
167+
return first(cache.f(θ, cache.p))
168+
elseif cache.callback === Optimization.DEFAULT_CALLBACK
169+
return first(cache.f(θ, cache.p, cur...))
170+
elseif cache.data !== Optimization.DEFAULT_DATA
171+
x = cache.f(θ, cache.p)
172+
return first(x)
173+
else
174+
x = cache.f(θ, cache.p, cur...)
175+
return first(x)
176+
end
156177
end
157178
end
158179

lib/OptimizationBBO/test/runtests.jl

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using OptimizationBBO, Optimization
1+
using OptimizationBBO, Optimization, BlackBoxOptim
2+
using Optimization.SciMLBase: MultiObjectiveOptimizationFunction
23
using Test
34

45
@testset "OptimizationBBO.jl" begin
@@ -46,4 +47,70 @@ using Test
4647
progress = true,
4748
maxtime = 5)
4849
end
50+
51+
# Define the initial guess and bounds
52+
u0 = [0.25, 0.25]
53+
lb = [0.0, 0.0]
54+
ub = [2.0, 2.0]
55+
56+
# Define the optimizer
57+
opt = OptimizationBBO.BBO_borg_moea()
58+
59+
@testset "Multi-Objective Optimization Tests" begin
60+
61+
# Test 1: Sphere and Rastrigin Functions
62+
@testset "Sphere and Rastrigin Functions" begin
63+
function multi_obj_func_1(x, p)
64+
f1 = sum(x .^ 2) # Sphere function
65+
f2 = sum(x .^ 2 .- 10 .* cos.(2π .* x) .+ 10) # Rastrigin function
66+
return (f1, f2)
67+
end
68+
69+
mof_1 = MultiObjectiveOptimizationFunction(multi_obj_func_1)
70+
prob_1 = Optimization.OptimizationProblem(mof_1, u0; lb=lb, ub=ub)
71+
sol_1 = solve(prob_1, opt, NumDimensions=2, FitnessScheme=ParetoFitnessScheme{2}(is_minimizing=true))
72+
73+
@test sol_1 nothing
74+
println("Solution for Sphere and Rastrigin: ", sol_1)
75+
@test sol_1.objective[1] 6.9905986e-18 atol=1e-3
76+
@test sol_1.objective[2] 1.7763568e-15 atol=1e-3
77+
end
78+
79+
# Test 2: Rosenbrock and Ackley Functions
80+
@testset "Rosenbrock and Ackley Functions" begin
81+
function multi_obj_func_2(x, p)
82+
f1 = (1.0 - x[1])^2 + 100.0 * (x[2] - x[1]^2)^2 # Rosenbrock function
83+
f2 = -20.0 * exp(-0.2 * sqrt(0.5 * (x[1]^2 + x[2]^2))) - exp(0.5 * (cos(2π * x[1]) + cos(2π * x[2]))) + exp(1) + 20.0 # Ackley function
84+
return (f1, f2)
85+
end
86+
87+
mof_2 = MultiObjectiveOptimizationFunction(multi_obj_func_2)
88+
prob_2 = Optimization.OptimizationProblem(mof_2, u0; lb=lb, ub=ub)
89+
sol_2 = solve(prob_2, opt, NumDimensions=2, FitnessScheme=ParetoFitnessScheme{2}(is_minimizing=true))
90+
91+
@test sol_2 nothing
92+
println("Solution for Rosenbrock and Ackley: ", sol_2)
93+
@test sol_2.objective[1] 0.97438 atol=1e-3
94+
@test sol_2.objective[2] 0.04088 atol=1e-3
95+
end
96+
97+
# Test 3: ZDT1 Function
98+
@testset "ZDT1 Function" begin
99+
function multi_obj_func_3(x, p)
100+
f1 = x[1]
101+
g = 1 + 9 * sum(x[2:end]) / (length(x) - 1)
102+
f2 = g * (1 - sqrt(f1 / g))
103+
return (f1, f2)
104+
end
105+
106+
mof_3 = MultiObjectiveOptimizationFunction(multi_obj_func_3)
107+
prob_3 = Optimization.OptimizationProblem(mof_3, u0; lb=lb, ub=ub)
108+
sol_3 = solve(prob_3, opt, NumDimensions=2, FitnessScheme=ParetoFitnessScheme{2}(is_minimizing=true))
109+
110+
@test sol_3 nothing
111+
println("Solution for ZDT1: ", sol_3)
112+
@test sol_3.objective[1] 0.273445 atol=1e-3
113+
@test sol_3.objective[2] 0.477079 atol=1e-3
114+
end
115+
end
49116
end

lib/OptimizationEvolutionary/src/OptimizationEvolutionary.jl

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ SciMLBase.allowsbounds(opt::Evolutionary.AbstractOptimizer) = true
88
SciMLBase.allowsconstraints(opt::Evolutionary.AbstractOptimizer) = true
99
SciMLBase.supports_opt_cache_interface(opt::Evolutionary.AbstractOptimizer) = true
1010
SciMLBase.requiresgradient(opt::Evolutionary.AbstractOptimizer) = false
11+
SciMLBase.requiresgradient(opt::Evolutionary.NSGA2) = false
1112
SciMLBase.requireshessian(opt::Evolutionary.AbstractOptimizer) = false
1213
SciMLBase.requiresconsjac(opt::Evolutionary.AbstractOptimizer) = false
1314
SciMLBase.requiresconshess(opt::Evolutionary.AbstractOptimizer) = false
@@ -125,8 +126,13 @@ function SciMLBase.__solve(cache::OptimizationCache{
125126
f = cache.f
126127

127128
_loss = function (θ)
128-
x = f(θ, cache.p, cur...)
129-
return first(x)
129+
if isa(f, MultiObjectiveOptimizationFunction)
130+
x = f(θ, cache.p, cur...)
131+
return x
132+
else
133+
x = f(θ, cache.p, cur...)
134+
return first(x)
135+
end
130136
end
131137

132138
opt_args = __map_optimizer_args(cache, cache.opt; callback = _cb, cache.solver_args...,
@@ -139,9 +145,17 @@ function SciMLBase.__solve(cache::OptimizationCache{
139145
c = x -> (res = zeros(length(cache.lcons)); f.cons(res, x); res)
140146
cons = WorstFitnessConstraints(Float64[], Float64[], cache.lcons, cache.ucons,
141147
c)
142-
opt_res = Evolutionary.optimize(_loss, cons, cache.u0, cache.opt, opt_args)
148+
if isa(f, MultiObjectiveOptimizationFunction)
149+
opt_res = Evolutionary.optimize(_loss, _loss(cache.u0), cons, cache.u0, cache.opt, opt_args)
150+
else
151+
opt_res = Evolutionary.optimize(_loss, cons, cache.u0, cache.opt, opt_args)
152+
end
143153
else
144-
opt_res = Evolutionary.optimize(_loss, cache.u0, cache.opt, opt_args)
154+
if isa(f, MultiObjectiveOptimizationFunction)
155+
opt_res = Evolutionary.optimize(_loss, _loss(cache.u0), cache.u0, cache.opt, opt_args)
156+
else
157+
opt_res = Evolutionary.optimize(_loss, cache.u0, cache.opt, opt_args)
158+
end
145159
end
146160
else
147161
if !isnothing(f.cons)
@@ -150,17 +164,30 @@ function SciMLBase.__solve(cache::OptimizationCache{
150164
else
151165
cons = BoxConstraints(cache.lb, cache.ub)
152166
end
153-
opt_res = Evolutionary.optimize(_loss, cons, cache.u0, cache.opt, opt_args)
167+
if isa(f, MultiObjectiveOptimizationFunction)
168+
opt_res = Evolutionary.optimize(_loss, _loss(cache.u0), cons, cache.u0, cache.opt, opt_args)
169+
else
170+
opt_res = Evolutionary.optimize(_loss, cons, cache.u0, cache.opt, opt_args)
171+
end
154172
end
155173
t1 = time()
156174
opt_ret = Symbol(Evolutionary.converged(opt_res))
157175
stats = Optimization.OptimizationStats(; iterations = opt_res.iterations,
158176
time = t1 - t0, fevals = opt_res.f_calls)
159-
SciMLBase.build_solution(cache, cache.opt,
177+
if !isa(f, MultiObjectiveOptimizationFunction)
178+
SciMLBase.build_solution(cache, cache.opt,
160179
Evolutionary.minimizer(opt_res),
161180
Evolutionary.minimum(opt_res); original = opt_res,
162181
retcode = opt_ret,
163182
stats = stats)
183+
else
184+
ans = Evolutionary.minimizer(opt_res)
185+
SciMLBase.build_solution(cache, cache.opt,
186+
ans,
187+
_loss(ans[1]); original = opt_res,
188+
retcode = opt_ret,
189+
stats = stats)
190+
end
164191
end
165192

166193
end

lib/OptimizationEvolutionary/test/runtests.jl

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using OptimizationEvolutionary, Optimization, Random
2+
using Optimization.SciMLBase: MultiObjectiveOptimizationFunction
23
using Test
34

45
Random.seed!(1234)
@@ -56,4 +57,111 @@ Random.seed!(1234)
5657
# Make sure that both the user's trace record value, as well as `curr_u` are stored in the trace.
5758
@test haskey(sol.original.trace[end].metadata, "TESTVAL") &&
5859
haskey(sol.original.trace[end].metadata, "curr_u")
60+
61+
# Test Suite for Different Multi-Objective Functions
62+
function test_multi_objective(func, initial_guess)
63+
# Define the gradient function using ForwardDiff
64+
function gradient_multi_objective(x, p=nothing)
65+
ForwardDiff.jacobian(func, x)
66+
end
67+
68+
# Create an instance of MultiObjectiveOptimizationFunction
69+
obj_func = MultiObjectiveOptimizationFunction(func, jac=gradient_multi_objective)
70+
71+
# Set up the evolutionary algorithm (e.g., NSGA2)
72+
algorithm = OptimizationEvolutionary.NSGA2()
73+
74+
# Define the optimization problem
75+
problem = OptimizationProblem(obj_func, initial_guess)
76+
77+
# Solve the optimization problem
78+
result = solve(problem, algorithm)
79+
80+
return result
81+
end
82+
83+
@testset "Multi-Objective Optimization Tests" begin
84+
85+
# Test 1: Sphere and Rastrigin Functions
86+
@testset "Sphere and Rastrigin Functions" begin
87+
function multi_objective_1(x, p=nothing)::Vector{Float64}
88+
f1 = sum(x .^ 2) # Sphere function
89+
f2 = sum(x .^ 2 .- 10 .* cos.(2π .* x) .+ 10) # Rastrigin function
90+
return [f1, f2]
91+
end
92+
result = test_multi_objective(multi_objective_1, [0.0, 1.0])
93+
@test result nothing
94+
println("Solution for Sphere and Rastrigin: ", result)
95+
@test result.u[1][1] 7.88866e-5 atol=1e-3
96+
@test result.u[1][2] 4.96471e-5 atol=1e-3
97+
@test result.objective[1] 8.6879e-9 atol=1e-3
98+
@test result.objective[2] 1.48875349381683e-6 atol=1e-3
99+
end
100+
101+
# Test 2: Rosenbrock and Ackley Functions
102+
@testset "Rosenbrock and Ackley Functions" begin
103+
function multi_objective_2(x, p=nothing)::Vector{Float64}
104+
f1 = (1.0 - x[1])^2 + 100.0 * (x[2] - x[1]^2)^2 # Rosenbrock function
105+
f2 = -20.0 * exp(-0.2 * sqrt(0.5 * (x[1]^2 + x[2]^2))) - exp(0.5 * (cos(2π * x[1]) + cos(2π * x[2]))) + exp(1) + 20.0 # Ackley function
106+
return [f1, f2]
107+
end
108+
result = test_multi_objective(multi_objective_2, [0.1, 1.0])
109+
@test result nothing
110+
println("Solution for Rosenbrock and Ackley: ", result)
111+
@test result.u[1][1] 0.003993274873103834 atol=1e-3
112+
@test result.u[1][2] 0.001433311246712721 atol=1e-3
113+
@test result.objective[1] 0.9922302888530358 atol=1e-3
114+
@test result.objective[2] 0.012479470703588902 atol=1e-3
115+
end
116+
117+
# Test 3: ZDT1 Function
118+
@testset "ZDT1 Function" begin
119+
function multi_objective_3(x, p=nothing)::Vector{Float64}
120+
f1 = x[1]
121+
g = 1 + 9 * sum(x[2:end]) / (length(x) - 1)
122+
sqrt_arg = f1 / g
123+
f2 = g * (1 - (sqrt_arg >= 0 ? sqrt(sqrt_arg) : NaN))
124+
return [f1, f2]
125+
end
126+
result = test_multi_objective(multi_objective_3, [0.25, 1.5])
127+
@test result nothing
128+
println("Solution for ZDT1: ", result)
129+
@test result.u[1][1] -0.365434 atol=1e-3
130+
@test result.u[1][2] 1.22128 atol=1e-3
131+
@test result.objective[1] -0.365434 atol=1e-3
132+
@test isnan(result.objective[2])
133+
end
134+
135+
# Test 4: DTLZ2 Function
136+
@testset "DTLZ2 Function" begin
137+
function multi_objective_4(x, p=nothing)::Vector{Float64}
138+
f1 = (1 + sum(x[2:end] .^ 2)) * cos(x[1] * π / 2)
139+
f2 = (1 + sum(x[2:end] .^ 2)) * sin(x[1] * π / 2)
140+
return [f1, f2]
141+
end
142+
result = test_multi_objective(multi_objective_4, [0.25, 0.75])
143+
@test result nothing
144+
println("Solution for DTLZ2: ", result)
145+
@test result.u[1][1] 0.899183 atol=1e-3
146+
@test result.u[2][1] 0.713992 atol=1e-3
147+
@test result.objective[1] 0.1599915 atol=1e-3
148+
@test result.objective[2] 1.001824893932647 atol=1e-3
149+
end
150+
151+
# Test 5: Schaffer Function N.2
152+
@testset "Schaffer Function N.2" begin
153+
function multi_objective_5(x, p=nothing)::Vector{Float64}
154+
f1 = x[1]^2
155+
f2 = (x[1] - 2)^2
156+
return [f1, f2]
157+
end
158+
result = test_multi_objective(multi_objective_5, [1.0])
159+
@test result nothing
160+
println("Solution for Schaffer N.2: ", result)
161+
@test result.u[19][1] 0.252635 atol=1e-3
162+
@test result.u[9][1] 1.0 atol=1e-3
163+
@test result.objective[1] 1.0 atol=1e-3
164+
@test result.objective[2] 1.0 atol=1e-3
165+
end
166+
end
59167
end

0 commit comments

Comments
 (0)