Skip to content

Commit 94393c8

Browse files
authored
Simplify algorithms by allowing them to assume minimization (#116)
1 parent 418d6c9 commit 94393c8

12 files changed

+71
-94
lines changed

src/MultiObjectiveAlgorithms.jl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,30 @@ function _compute_ideal_point(model::Optimizer, start_time)
579579
return
580580
end
581581

582+
function optimize_multiobjective!(
583+
algorithm::AbstractAlgorithm,
584+
model::Optimizer,
585+
)
586+
sense = MOI.get(model.inner, MOI.ObjectiveSense())
587+
if sense == MOI.FEASIBILITY_SENSE
588+
return MOI.INVALID_MODEL, nothing
589+
elseif sense == MOI.MAX_SENSE
590+
old_obj = copy(model.f)
591+
neg_obj = MOI.Utilities.operate(-, Float64, model.f)
592+
MOI.set(model, MOI.ObjectiveFunction{typeof(neg_obj)}(), neg_obj)
593+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
594+
status, solutions = minimize_multiobjective!(algorithm, model)
595+
MOI.set(model, MOI.ObjectiveFunction{typeof(old_obj)}(), old_obj)
596+
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
597+
if solutions !== nothing
598+
solutions = [SolutionPoint(s.x, -s.y) for s in solutions]
599+
end
600+
model.ideal_point *= -1
601+
return status, solutions
602+
end
603+
return minimize_multiobjective!(algorithm, model)
604+
end
605+
582606
function MOI.optimize!(model::Optimizer)
583607
start_time = time()
584608
empty!(model.solutions)

src/algorithms/Chalmet.jl

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,12 @@ function _solve_constrained_model(
4040
return status, SolutionPoint(X, Y)
4141
end
4242

43-
function optimize_multiobjective!(algorithm::Chalmet, model::Optimizer)
43+
function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer)
44+
@assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE
4445
start_time = time()
4546
if MOI.output_dimension(model.f) != 2
4647
error("Chalmet requires exactly two objectives")
4748
end
48-
sense = MOI.get(model.inner, MOI.ObjectiveSense())
49-
if sense == MOI.MAX_SENSE
50-
old_obj, neg_obj = copy(model.f), -model.f
51-
MOI.set(model, MOI.ObjectiveFunction{typeof(neg_obj)}(), neg_obj)
52-
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
53-
status, solutions = optimize_multiobjective!(algorithm, model)
54-
MOI.set(model, MOI.ObjectiveFunction{typeof(old_obj)}(), old_obj)
55-
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
56-
if solutions !== nothing
57-
solutions = [SolutionPoint(s.x, -s.y) for s in solutions]
58-
end
59-
return status, solutions
60-
end
6149
solutions = SolutionPoint[]
6250
E = Tuple{Int,Int}[]
6351
Q = Tuple{Int,Int}[]

src/algorithms/DominguezRios.jl

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -142,22 +142,9 @@ function _update!(
142142
return
143143
end
144144

145-
function optimize_multiobjective!(algorithm::DominguezRios, model::Optimizer)
145+
function minimize_multiobjective!(algorithm::DominguezRios, model::Optimizer)
146+
@assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE
146147
start_time = time()
147-
sense = MOI.get(model.inner, MOI.ObjectiveSense())
148-
if sense == MOI.MAX_SENSE
149-
old_obj, neg_obj = copy(model.f), -model.f
150-
MOI.set(model, MOI.ObjectiveFunction{typeof(neg_obj)}(), neg_obj)
151-
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
152-
status, solutions = optimize_multiobjective!(algorithm, model)
153-
MOI.set(model, MOI.ObjectiveFunction{typeof(old_obj)}(), old_obj)
154-
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
155-
if solutions !== nothing
156-
solutions = [SolutionPoint(s.x, -s.y) for s in solutions]
157-
end
158-
model.ideal_point .*= -1
159-
return status, solutions
160-
end
161148
n = MOI.output_dimension(model.f)
162149
L = [_DominguezRiosBox[] for i in 1:n]
163150
scalars = MOI.Utilities.scalarize(model.f)
@@ -166,7 +153,7 @@ function optimize_multiobjective!(algorithm::DominguezRios, model::Optimizer)
166153
# Ideal and Nadir point estimation
167154
for (i, f_i) in enumerate(scalars)
168155
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f_i)}(), f_i)
169-
MOI.set(model.inner, MOI.ObjectiveSense(), sense)
156+
MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MIN_SENSE)
170157
MOI.optimize!(model.inner)
171158
status = MOI.get(model.inner, MOI.TerminationStatus())
172159
if !_is_scalar_status_optimal(status)
@@ -175,18 +162,17 @@ function optimize_multiobjective!(algorithm::DominguezRios, model::Optimizer)
175162
_, Y = _compute_point(model, variables, f_i)
176163
yI[i] = Y
177164
model.ideal_point[i] = Y
178-
rev_sense = sense == MOI.MIN_SENSE ? MOI.MAX_SENSE : MOI.MIN_SENSE
179-
MOI.set(model.inner, MOI.ObjectiveSense(), rev_sense)
165+
MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MAX_SENSE)
180166
MOI.optimize!(model.inner)
181167
status = MOI.get(model.inner, MOI.TerminationStatus())
182168
if !_is_scalar_status_optimal(status)
183-
_warn_on_nonfinite_anti_ideal(algorithm, sense, i)
169+
_warn_on_nonfinite_anti_ideal(algorithm, MOI.MIN_SENSE, i)
184170
return status, nothing
185171
end
186172
_, Y = _compute_point(model, variables, f_i)
187173
yN[i] = Y + 1
188174
end
189-
MOI.set(model.inner, MOI.ObjectiveSense(), sense)
175+
MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MIN_SENSE)
190176
ϵ = 1 / (2 * n * (maximum(yN - yI) - 1))
191177
# If ϵ is small, then the scalar objectives can contain terms that fall
192178
# below the tolerance level of the solver. To fix this, we rescale the

src/algorithms/EpsilonConstraint.jl

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,11 @@ function MOI.get(alg::EpsilonConstraint, ::ObjectiveAbsoluteTolerance)
6565
return MOI.get(alg, EpsilonConstraintStep())
6666
end
6767

68-
function optimize_multiobjective!(
68+
function minimize_multiobjective!(
6969
algorithm::EpsilonConstraint,
7070
model::Optimizer,
7171
)
72+
@assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE
7273
start_time = time()
7374
if MOI.output_dimension(model.f) != 2
7475
error("EpsilonConstraint requires exactly two objectives")
@@ -87,12 +88,7 @@ function optimize_multiobjective!(
8788
end
8889
a, b = solution_1[1].y[1], solution_2[1].y[1]
8990
left, right = min(a, b), max(a, b)
90-
sense = MOI.get(model.inner, MOI.ObjectiveSense())
91-
if sense == MOI.MIN_SENSE
92-
model.ideal_point .= min.(solution_1[1].y, solution_2[1].y)
93-
else
94-
model.ideal_point .= max.(solution_1[1].y, solution_2[1].y)
95-
end
91+
model.ideal_point .= min.(solution_1[1].y, solution_2[1].y)
9692
# Compute the epsilon that we will be incrementing by each iteration
9793
ε = MOI.get(algorithm, EpsilonConstraintStep())
9894
n_points = MOI.get(algorithm, SolutionLimit())
@@ -104,16 +100,12 @@ function optimize_multiobjective!(
104100
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f2)}(), f2)
105101
# Add epsilon constraint
106102
variables = MOI.get(model.inner, MOI.ListOfVariableIndices())
107-
SetType, bound = if sense == MOI.MIN_SENSE
108-
MOI.LessThan{Float64}, right - ε
109-
else
110-
MOI.GreaterThan{Float64}, left + ε
111-
end
103+
bound = right - ε
112104
constant = MOI.constant(f1, Float64)
113105
ci = MOI.Utilities.normalize_and_add_constraint(
114106
model,
115107
f1,
116-
SetType(bound);
108+
MOI.LessThan{Float64}(bound);
117109
allow_modify_function = true,
118110
)
119111
bound -= constant
@@ -123,7 +115,7 @@ function optimize_multiobjective!(
123115
status = MOI.TIME_LIMIT
124116
break
125117
end
126-
MOI.set(model, MOI.ConstraintSet(), ci, SetType(bound))
118+
MOI.set(model, MOI.ConstraintSet(), ci, MOI.LessThan{Float64}(bound))
127119
MOI.optimize!(model.inner)
128120
if !_is_scalar_status_optimal(model)
129121
break
@@ -132,12 +124,8 @@ function optimize_multiobjective!(
132124
if isempty(solutions) || !(Y solutions[end].y)
133125
push!(solutions, SolutionPoint(X, Y))
134126
end
135-
if sense == MOI.MIN_SENSE
136-
bound = min(Y[1] - constant - ε, bound - ε)
137-
else
138-
bound = max(Y[1] - constant + ε, bound + ε)
139-
end
127+
bound = min(Y[1] - constant - ε, bound - ε)
140128
end
141129
MOI.delete(model, ci)
142-
return status, filter_nondominated(sense, solutions)
130+
return status, filter_nondominated(MOI.MIN_SENSE, solutions)
143131
end

src/algorithms/Hierarchical.jl

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ function _sorted_priorities(priorities::Vector{Int})
8585
return [findall(isequal(u), priorities) for u in unique_priorities]
8686
end
8787

88-
function optimize_multiobjective!(algorithm::Hierarchical, model::Optimizer)
88+
function minimize_multiobjective!(algorithm::Hierarchical, model::Optimizer)
89+
@assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE
8990
objectives = MOI.Utilities.eachscalar(model.f)
9091
N = length(objectives)
9192
variables = MOI.get(model.inner, MOI.ListOfVariableIndices())
@@ -109,14 +110,9 @@ function optimize_multiobjective!(algorithm::Hierarchical, model::Optimizer)
109110
end
110111
# Add tolerance constraints
111112
X, Y = _compute_point(model, variables, new_vector_f)
112-
sense = MOI.get(model.inner, MOI.ObjectiveSense())
113113
for (i, fi) in enumerate(MOI.Utilities.eachscalar(new_vector_f))
114114
rtol = MOI.get(algorithm, ObjectiveRelativeTolerance(i))
115-
set = if sense == MOI.MIN_SENSE
116-
MOI.LessThan(Y[i] + rtol * abs(Y[i]))
117-
else
118-
MOI.GreaterThan(Y[i] - rtol * abs(Y[i]))
119-
end
115+
set = MOI.LessThan(Y[i] + rtol * abs(Y[i]))
120116
ci = MOI.Utilities.normalize_and_add_constraint(model, fi, set)
121117
push!(constraints, ci)
122118
end

src/algorithms/KirlikSayin.jl

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -74,23 +74,9 @@ function _update_list(L::Vector{_Rectangle}, f::Vector{Float64})
7474
return L_new
7575
end
7676

77-
function optimize_multiobjective!(algorithm::KirlikSayin, model::Optimizer)
77+
function minimize_multiobjective!(algorithm::KirlikSayin, model::Optimizer)
78+
@assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE
7879
start_time = time()
79-
sense = MOI.get(model.inner, MOI.ObjectiveSense())
80-
if sense == MOI.MAX_SENSE
81-
old_obj, neg_obj = copy(model.f), -model.f
82-
MOI.set(model, MOI.ObjectiveFunction{typeof(neg_obj)}(), neg_obj)
83-
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
84-
status, solutions = optimize_multiobjective!(algorithm, model)
85-
MOI.set(model, MOI.ObjectiveFunction{typeof(old_obj)}(), old_obj)
86-
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
87-
if solutions !== nothing
88-
solutions = [SolutionPoint(s.x, -s.y) for s in solutions]
89-
end
90-
model.ideal_point .*= -1
91-
return status, solutions
92-
end
93-
@assert sense == MOI.MIN_SENSE
9480
solutions = SolutionPoint[]
9581
# Problem with p objectives.
9682
# Set k = 1, meaning the nondominated points will get projected

src/algorithms/TambyVanderpooten.jl

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -81,25 +81,12 @@ function _select_search_zone(
8181
return k_star, upper_bounds[j_star]
8282
end
8383

84-
function optimize_multiobjective!(
84+
function minimize_multiobjective!(
8585
algorithm::TambyVanderpooten,
8686
model::Optimizer,
8787
)
88+
@assert MOI.get(model.inner, MOI.ObjectiveSense()) == MOI.MIN_SENSE
8889
start_time = time()
89-
sense = MOI.get(model.inner, MOI.ObjectiveSense())
90-
if sense == MOI.MAX_SENSE
91-
old_obj, neg_obj = copy(model.f), -model.f
92-
MOI.set(model, MOI.ObjectiveFunction{typeof(neg_obj)}(), neg_obj)
93-
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
94-
status, solutions = optimize_multiobjective!(algorithm, model)
95-
MOI.set(model, MOI.ObjectiveFunction{typeof(old_obj)}(), old_obj)
96-
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
97-
if solutions !== nothing
98-
solutions = [SolutionPoint(s.x, -s.y) for s in solutions]
99-
end
100-
model.ideal_point .*= -1
101-
return status, solutions
102-
end
10390
warm_start_supported = false
10491
if MOI.supports(model, MOI.VariablePrimalStart(), MOI.VariableIndex)
10592
warm_start_supported = true
@@ -111,7 +98,7 @@ function optimize_multiobjective!(
11198
scalars = MOI.Utilities.scalarize(model.f)
11299
for (i, f_i) in enumerate(scalars)
113100
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f_i)}(), f_i)
114-
MOI.set(model.inner, MOI.ObjectiveSense(), sense)
101+
MOI.set(model.inner, MOI.ObjectiveSense(), MOI.MIN_SENSE)
115102
MOI.optimize!(model.inner)
116103
status = MOI.get(model.inner, MOI.TerminationStatus())
117104
if !_is_scalar_status_optimal(status)
@@ -124,7 +111,7 @@ function optimize_multiobjective!(
124111
MOI.optimize!(model.inner)
125112
status = MOI.get(model.inner, MOI.TerminationStatus())
126113
if !_is_scalar_status_optimal(status)
127-
_warn_on_nonfinite_anti_ideal(algorithm, sense, i)
114+
_warn_on_nonfinite_anti_ideal(algorithm, MOI.MIN_SENSE, i)
128115
return status, nothing
129116
end
130117
_, Y = _compute_point(model, variables, f_i)

test/algorithms/Chalmet.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,31 @@ function test_unbounded()
165165
return
166166
end
167167

168+
function test_invalid_feasibility()
169+
model = MOA.Optimizer(HiGHS.Optimizer)
170+
MOI.set(model, MOA.Algorithm(), MOA.Chalmet())
171+
MOI.set(model, MOI.Silent(), true)
172+
x = MOI.add_variables(model, 2)
173+
MOI.add_constraint.(model, x, MOI.GreaterThan(0.0))
174+
MOI.add_constraint(model, 1.0 * x[1] + 1.0 * x[2], MOI.LessThan(-1.0))
175+
MOI.set(model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE)
176+
f = MOI.Utilities.operate(vcat, Float64, 1.0 .* x...)
177+
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
178+
MOI.optimize!(model)
179+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.INVALID_MODEL
180+
@test MOI.get(model, MOI.PrimalStatus()) == MOI.NO_SOLUTION
181+
@test MOI.get(model, MOI.DualStatus()) == MOI.NO_SOLUTION
182+
return
183+
end
184+
168185
function test_infeasible()
169186
model = MOA.Optimizer(HiGHS.Optimizer)
170187
MOI.set(model, MOA.Algorithm(), MOA.Chalmet())
171188
MOI.set(model, MOI.Silent(), true)
172189
x = MOI.add_variables(model, 2)
173190
MOI.add_constraint.(model, x, MOI.GreaterThan(0.0))
174191
MOI.add_constraint(model, 1.0 * x[1] + 1.0 * x[2], MOI.LessThan(-1.0))
192+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
175193
f = MOI.Utilities.operate(vcat, Float64, 1.0 .* x...)
176194
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
177195
MOI.optimize!(model)

test/algorithms/DominguezRios.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ function test_infeasible()
4242
x = MOI.add_variables(model, 2)
4343
MOI.add_constraint.(model, x, MOI.GreaterThan(0.0))
4444
MOI.add_constraint(model, 1.0 * x[1] + 1.0 * x[2], MOI.LessThan(-1.0))
45+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
4546
f = MOI.Utilities.operate(vcat, Float64, 1.0 .* x...)
4647
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
4748
MOI.optimize!(model)

test/algorithms/EpsilonConstraint.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ function test_infeasible()
245245
x = MOI.add_variables(model, 2)
246246
MOI.add_constraint.(model, x, MOI.GreaterThan(0.0))
247247
MOI.add_constraint(model, 1.0 * x[1] + 1.0 * x[2], MOI.LessThan(-1.0))
248+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
248249
f = MOI.Utilities.operate(vcat, Float64, 1.0 .* x...)
249250
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
250251
MOI.optimize!(model)

test/algorithms/Hierarchical.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ function test_infeasible()
8888
x = MOI.add_variables(model, 2)
8989
MOI.add_constraint.(model, x, MOI.GreaterThan(0.0))
9090
MOI.add_constraint(model, 1.0 * x[1] + 1.0 * x[2], MOI.LessThan(-1.0))
91+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
9192
f = MOI.Utilities.operate(vcat, Float64, 1.0 .* x...)
9293
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
9394
MOI.optimize!(model)

test/algorithms/TambyVanderpooten.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ function test_infeasible()
3939
x = MOI.add_variables(model, 2)
4040
MOI.add_constraint.(model, x, MOI.GreaterThan(0.0))
4141
MOI.add_constraint(model, 1.0 * x[1] + 1.0 * x[2], MOI.LessThan(-1.0))
42+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
4243
f = MOI.Utilities.operate(vcat, Float64, 1.0 .* x...)
4344
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
4445
MOI.optimize!(model)

0 commit comments

Comments
 (0)