Skip to content

Commit 04fdfeb

Browse files
authored
Fix comparison for non-dominated to account for tolerances (#118)
1 parent 94393c8 commit 04fdfeb

File tree

2 files changed

+43
-10
lines changed

2 files changed

+43
-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: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,27 @@ 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]), MOA.SolutionPoint(x, [2, 1])]
113+
new_solutions = MOA.filter_nondominated(MOI.MAX_SENSE, copy(solutions))
114+
@test new_solutions == solutions[2:2]
115+
solutions =
116+
[MOA.SolutionPoint(x, [1, 1 + 9e-5]), MOA.SolutionPoint(x, [2, 1])]
117+
new_solutions = MOA.filter_nondominated(MOI.MAX_SENSE, copy(solutions))
118+
@test new_solutions == solutions
119+
solutions =
120+
[MOA.SolutionPoint(x, [-1, -1 - 1e-6]), MOA.SolutionPoint(x, [-2, -1])]
121+
new_solutions = MOA.filter_nondominated(MOI.MIN_SENSE, copy(solutions))
122+
@test new_solutions == solutions[2:2]
123+
solutions =
124+
[MOA.SolutionPoint(x, [-1, -1 - 9e-5]), MOA.SolutionPoint(x, [-2, -1])]
125+
new_solutions = MOA.filter_nondominated(MOI.MIN_SENSE, copy(solutions))
126+
@test new_solutions == reverse(solutions)
127+
return
128+
end
129+
109130
end
110131

111132
TestUtilities.run_tests()

0 commit comments

Comments
 (0)