Skip to content

Commit 7f4a628

Browse files
Fix OptimizationEvolutionary multi-objective core tests
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
1 parent 59bdb75 commit 7f4a628

2 files changed

Lines changed: 68 additions & 58 deletions

File tree

lib/OptimizationEvolutionary/src/OptimizationEvolutionary.jl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,7 @@ function SciMLBase.__solve(cache::OptimizationCache{O}) where {
157157
# Passing the initial point causes the population to be copies of that point,
158158
# which prevents proper exploration of the search space.
159159
if isa(f, MultiObjectiveOptimizationFunction)
160-
opt_res = Evolutionary.optimize(
161-
_loss, _loss(cache.u0), cons, cache.opt, opt_args
162-
)
160+
opt_res = Evolutionary.optimize(_loss, cons, cache.opt, opt_args)
163161
else
164162
opt_res = Evolutionary.optimize(_loss, cons, cache.opt, opt_args)
165163
end

lib/OptimizationEvolutionary/test/core_tests.jl

Lines changed: 67 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -68,26 +68,38 @@ Random.seed!(1234)
6868
@test haskey(sol.original.trace[end].metadata, "TESTVAL") &&
6969
haskey(sol.original.trace[end].metadata, "curr_u")
7070

71-
# Test Suite for Different Multi-Objective Functions
72-
function test_multi_objective(func, initial_guess)
73-
# Define the gradient function using ForwardDiff
74-
function gradient_multi_objective(x, p = nothing)
75-
ForwardDiff.jacobian(func, x)
76-
end
77-
78-
# Create an instance of MultiObjectiveOptimizationFunction
79-
obj_func = MultiObjectiveOptimizationFunction(func, jac = gradient_multi_objective)
80-
81-
# Set up the evolutionary algorithm (e.g., NSGA2)
71+
# NSGA2 returns stochastic Pareto candidates; assert wrapper invariants instead of
72+
# exact population indices, which can change across Evolutionary releases.
73+
function test_multi_objective(func, initial_guess; seed, lb = nothing, ub = nothing)
74+
Random.seed!(seed)
75+
obj_func = MultiObjectiveOptimizationFunction(func)
8276
algorithm = OptimizationEvolutionary.NSGA2()
77+
problem = if lb === nothing && ub === nothing
78+
OptimizationProblem(obj_func, initial_guess)
79+
else
80+
OptimizationProblem(obj_func, initial_guess; lb = lb, ub = ub)
81+
end
82+
return solve(problem, algorithm)
83+
end
8384

84-
# Define the optimization problem
85-
problem = OptimizationProblem(obj_func, initial_guess)
86-
87-
# Solve the optimization problem
88-
result = solve(problem, algorithm)
89-
90-
return result
85+
function check_multi_objective_result(
86+
result, func, initial_guess; lb = nothing, ub = nothing, min_population = 1
87+
)
88+
@test result !== nothing
89+
@test result.u isa AbstractVector
90+
@test !isempty(result.u)
91+
@test length(result.u) >= min_population
92+
@test length(unique(result.u)) >= min_population
93+
@test all(u -> u isa AbstractVector && length(u) == length(initial_guess), result.u)
94+
95+
objective_values = [func(u, nothing) for u in result.u]
96+
@test result.objective == objective_values[1]
97+
@test all(obj -> length(obj) == length(result.objective), objective_values)
98+
@test all(obj -> all(isfinite, obj), objective_values)
99+
100+
if lb !== nothing && ub !== nothing
101+
@test all(u -> all((lb .<= u) .& (u .<= ub)), result.u)
102+
end
91103
end
92104

93105
@testset "Multi-Objective Optimization Tests" begin
@@ -99,13 +111,8 @@ Random.seed!(1234)
99111
f2 = sum(x .^ 2 .- 10 .* cos.(2π .* x) .+ 10) # Rastrigin function
100112
return [f1, f2]
101113
end
102-
result = test_multi_objective(multi_objective_1, [0.0, 1.0])
103-
@test result nothing
104-
println("Solution for Sphere and Rastrigin: ", result)
105-
@test result.u[1][1] 7.88866e-5 atol = 1.0e-3
106-
@test result.u[1][2] 4.96471e-5 atol = 1.0e-3
107-
@test result.objective[1] 8.6879e-9 atol = 1.0e-3
108-
@test result.objective[2] 1.48875349381683e-6 atol = 1.0e-3
114+
result = test_multi_objective(multi_objective_1, [0.0, 1.0]; seed = 1101)
115+
check_multi_objective_result(result, multi_objective_1, [0.0, 1.0])
109116
end
110117

111118
# Test 2: Rosenbrock and Ackley Functions
@@ -116,31 +123,30 @@ Random.seed!(1234)
116123
exp(0.5 * (cos(2π * x[1]) + cos(2π * x[2]))) + exp(1) + 20.0 # Ackley function
117124
return [f1, f2]
118125
end
119-
result = test_multi_objective(multi_objective_2, [0.1, 1.0])
120-
@test result nothing
121-
println("Solution for Rosenbrock and Ackley: ", result)
122-
@test result.u[1][1] 0.003993274873103834 atol = 1.0e-3
123-
@test result.u[1][2] 0.001433311246712721 atol = 1.0e-3
124-
@test result.objective[1] 0.9922302888530358 atol = 1.0e-3
125-
@test result.objective[2] 0.012479470703588902 atol = 1.0e-3
126+
result = test_multi_objective(multi_objective_2, [0.1, 1.0]; seed = 1102)
127+
check_multi_objective_result(
128+
result, multi_objective_2, [0.1, 1.0]; min_population = 2
129+
)
126130
end
127131

128132
# Test 3: ZDT1 Function
129133
@testset "ZDT1 Function" begin
130134
function multi_objective_3(x, p = nothing)::Vector{Float64}
131135
f1 = x[1]
132136
g = 1 + 9 * sum(x[2:end]) / (length(x) - 1)
133-
sqrt_arg = f1 / g
134-
f2 = g * (1 - (sqrt_arg >= 0 ? sqrt(sqrt_arg) : NaN))
137+
f2 = g * (1 - sqrt(f1 / g))
135138
return [f1, f2]
136139
end
137-
result = test_multi_objective(multi_objective_3, [0.25, 1.5])
138-
@test result nothing
139-
println("Solution for ZDT1: ", result)
140-
@test result.u[1][1] -0.365434 atol = 1.0e-3
141-
@test result.u[1][2] 1.22128 atol = 1.0e-3
142-
@test result.objective[1] -0.365434 atol = 1.0e-3
143-
@test isnan(result.objective[2])
140+
lb = zeros(2)
141+
ub = ones(2)
142+
initial_guess = [0.25, 0.75]
143+
result = test_multi_objective(
144+
multi_objective_3, initial_guess; seed = 1103, lb = lb, ub = ub
145+
)
146+
check_multi_objective_result(
147+
result, multi_objective_3, initial_guess; lb = lb, ub = ub,
148+
min_population = 2
149+
)
144150
end
145151

146152
# Test 4: DTLZ2 Function
@@ -150,13 +156,16 @@ Random.seed!(1234)
150156
f2 = (1 + sum(x[2:end] .^ 2)) * sin(x[1] * π / 2)
151157
return [f1, f2]
152158
end
153-
result = test_multi_objective(multi_objective_4, [0.25, 0.75])
154-
@test result nothing
155-
println("Solution for DTLZ2: ", result)
156-
@test result.u[1][1] 0.899183 atol = 1.0e-3
157-
@test result.u[2][1] 0.713992 atol = 1.0e-3
158-
@test result.objective[1] 0.1599915 atol = 1.0e-3
159-
@test result.objective[2] 1.001824893932647 atol = 1.0e-3
159+
lb = zeros(2)
160+
ub = ones(2)
161+
initial_guess = [0.25, 0.75]
162+
result = test_multi_objective(
163+
multi_objective_4, initial_guess; seed = 1104, lb = lb, ub = ub
164+
)
165+
check_multi_objective_result(
166+
result, multi_objective_4, initial_guess; lb = lb, ub = ub,
167+
min_population = 2
168+
)
160169
end
161170

162171
# Test 5: Schaffer Function N.2
@@ -166,13 +175,16 @@ Random.seed!(1234)
166175
f2 = (x[1] - 2)^2
167176
return [f1, f2]
168177
end
169-
result = test_multi_objective(multi_objective_5, [1.0])
170-
@test result nothing
171-
println("Solution for Schaffer N.2: ", result)
172-
@test result.u[19][1] 0.252635 atol = 1.0e-3
173-
@test result.u[9][1] 1.0 atol = 1.0e-3
174-
@test result.objective[1] 1.0 atol = 1.0e-3
175-
@test result.objective[2] 1.0 atol = 1.0e-3
178+
lb = [0.0]
179+
ub = [2.0]
180+
initial_guess = [1.0]
181+
result = test_multi_objective(
182+
multi_objective_5, initial_guess; seed = 1105, lb = lb, ub = ub
183+
)
184+
check_multi_objective_result(
185+
result, multi_objective_5, initial_guess; lb = lb, ub = ub,
186+
min_population = 2
187+
)
176188
end
177189
end
178190
end

0 commit comments

Comments
 (0)