Skip to content

Commit 0aa6a21

Browse files
committed
Fix comparison for non-dominated to account for tolerances
1 parent 94393c8 commit 0aa6a21

File tree

2 files changed

+51
-10
lines changed

2 files changed

+51
-10
lines changed

src/MultiObjectiveAlgorithms.jl

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,41 @@ end
2020
Base.:(==)(a::SolutionPoint, b::SolutionPoint) = a.y == b.y
2121

2222
"""
23-
dominates(sense, a::SolutionPoint, b::SolutionPoint)
23+
dominates(sense, a::SolutionPoint, b::SolutionPoint; atol::Float64)
2424
2525
Returns `true` if point `a` dominates point `b`.
2626
"""
27-
function dominates(sense, a::SolutionPoint, b::SolutionPoint)
28-
if a.y == b.y
29-
return false
30-
elseif sense == MOI.MIN_SENSE
31-
return all(a.y .<= b.y)
27+
function dominates(
28+
sense::MOI.OptimizationSense,
29+
a::SolutionPoint,
30+
b::SolutionPoint;
31+
atol::Float64 = 1e-6,
32+
)
33+
l, u = extrema(a.y - b.y)
34+
if sense == MOI.MIN_SENSE
35+
# At least one element must be strictly better => l < -atol
36+
# No element can be structly worse => u <= atol
37+
return l < -atol && u <= atol
3238
else
33-
return all(a.y .>= b.y)
39+
# At least one element must be strictly better => u > atol
40+
# No element can be structly worse => l >= -atol
41+
return u > atol && l >= -atol
3442
end
3543
end
3644

3745
_sort!(solutions::Vector{SolutionPoint}) = sort!(solutions; by = x -> x.y)
3846

39-
function filter_nondominated(sense, solutions::Vector{SolutionPoint})
47+
function filter_nondominated(
48+
sense,
49+
solutions::Vector{SolutionPoint};
50+
atol::Float64 = 1e-6,
51+
)
4052
_sort!(solutions)
4153
nondominated_solutions = SolutionPoint[]
4254
for candidate in solutions
43-
if any(test -> dominates(sense, test, candidate), solutions)
55+
if any(test -> dominates(sense, test, candidate; atol), solutions)
4456
# Point is dominated. Don't add
45-
elseif any(test -> test.y candidate.y, nondominated_solutions)
57+
elseif any(test -> (test.y, candidate.y; atol), nondominated_solutions)
4658
# Point already added to nondominated solutions. Don't add
4759
else
4860
push!(nondominated_solutions, candidate)

test/test_utilities.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,35 @@ function test_filter_nondominated_triple()
106106
return
107107
end
108108

109+
function test_filter_epsilon()
110+
x = Dict{MOI.VariableIndex,Float64}()
111+
solutions = [
112+
MOA.SolutionPoint(x, [1, 1 + 1e-6]),
113+
MOA.SolutionPoint(x, [2, 1]),
114+
]
115+
new_solutions = MOA.filter_nondominated(MOI.MAX_SENSE, copy(solutions))
116+
@test new_solutions == solutions[2:2]
117+
solutions = [
118+
MOA.SolutionPoint(x, [1, 1 + 9e-5]),
119+
MOA.SolutionPoint(x, [2, 1]),
120+
]
121+
new_solutions = MOA.filter_nondominated(MOI.MAX_SENSE, copy(solutions))
122+
@test new_solutions == solutions
123+
solutions = [
124+
MOA.SolutionPoint(x, [-1, -1 - 1e-6]),
125+
MOA.SolutionPoint(x, [-2, -1]),
126+
]
127+
new_solutions = MOA.filter_nondominated(MOI.MIN_SENSE, copy(solutions))
128+
@test new_solutions == solutions[2:2]
129+
solutions = [
130+
MOA.SolutionPoint(x, [-1, -1 - 9e-5]),
131+
MOA.SolutionPoint(x, [-2, -1]),
132+
]
133+
new_solutions = MOA.filter_nondominated(MOI.MIN_SENSE, copy(solutions))
134+
@test new_solutions == reverse(solutions)
135+
return
136+
end
137+
109138
end
110139

111140
TestUtilities.run_tests()

0 commit comments

Comments
 (0)