Skip to content

Commit f525ac4

Browse files
committed
Enable algorithms to update the ideal_point during a solve
1 parent 048fe40 commit f525ac4

File tree

5 files changed

+42
-24
lines changed

5 files changed

+42
-24
lines changed

src/MultiObjectiveAlgorithms.jl

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -560,15 +560,13 @@ function MOI.delete(model::Optimizer, ci::MOI.ConstraintIndex)
560560
end
561561

562562
function _compute_ideal_point(model::Optimizer, start_time)
563-
objectives = MOI.Utilities.eachscalar(model.f)
564-
model.ideal_point = fill(NaN, length(objectives))
565-
if !MOI.get(model, ComputeIdealPoint())
566-
return
567-
end
568-
for (i, f) in enumerate(objectives)
563+
for (i, f) in enumerate(MOI.Utilities.eachscalar(model.f))
569564
if _time_limit_exceeded(model, start_time)
570565
return
571566
end
567+
if !isnan(model.ideal_point[i])
568+
continue # The algorithm already updated this information
569+
end
572570
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f)}(), f)
573571
MOI.optimize!(model.inner)
574572
status = MOI.get(model.inner, MOI.TerminationStatus())
@@ -582,6 +580,9 @@ end
582580
function MOI.optimize!(model::Optimizer)
583581
start_time = time()
584582
empty!(model.solutions)
583+
# We need to clear the ideal point prior to starting the solve. Algorithms
584+
# may update this during the solve, otherwise we will update it at the end.
585+
model.ideal_point = fill(NaN, MOI.output_dimension(model.f))
585586
model.termination_status = MOI.OPTIMIZE_NOT_CALLED
586587
if model.f === nothing
587588
model.termination_status = MOI.INVALID_MODEL
@@ -590,7 +591,9 @@ function MOI.optimize!(model::Optimizer)
590591
algorithm = something(model.algorithm, default(Algorithm()))
591592
status, solutions = optimize_multiobjective!(algorithm, model)
592593
model.termination_status = status
593-
_compute_ideal_point(model, start_time)
594+
if MOI.get(model, ComputeIdealPoint())
595+
_compute_ideal_point(model, start_time)
596+
end
594597
if solutions !== nothing
595598
model.solutions = solutions
596599
end

src/algorithms/Dichotomy.jl

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,6 @@ function MOI.get(alg::Dichotomy, attr::SolutionLimit)
5454
return something(alg.solution_limit, default(alg, attr))
5555
end
5656

57-
function _solve_weighted_sum(model::Optimizer, alg::Dichotomy, weight::Float64)
58-
return _solve_weighted_sum(model, alg, [weight, 1 - weight])
59-
end
60-
6157
function _solve_weighted_sum(
6258
model::Optimizer,
6359
::Dichotomy,
@@ -88,15 +84,17 @@ function optimize_multiobjective!(algorithm::Dichotomy, model::Optimizer)
8884
return status, [solution]
8985
end
9086
solutions = Dict{Float64,SolutionPoint}()
91-
for w in (0.0, 1.0)
87+
for (i, w) in (1 => 1.0, 2 => 0.0)
9288
if _time_limit_exceeded(model, start_time)
9389
return MOI.TIME_LIMIT, nothing
9490
end
95-
status, solution = _solve_weighted_sum(model, algorithm, w)
91+
status, solution = _solve_weighted_sum(model, algorithm, [w, 1.0 - w])
9692
if !_is_scalar_status_optimal(status)
9793
return status, nothing
9894
end
9995
solutions[w] = solution
96+
# We already have enough information here to update the ideal point.
97+
model.ideal_point[i] = solution.y[i]
10098
end
10199
queue = Tuple{Float64,Float64}[]
102100
if !(solutions[0.0] solutions[1.0])
@@ -112,7 +110,7 @@ function optimize_multiobjective!(algorithm::Dichotomy, model::Optimizer)
112110
(a, b) = popfirst!(queue)
113111
y_d = solutions[a].y .- solutions[b].y
114112
w = y_d[2] / (y_d[2] - y_d[1])
115-
status, solution = _solve_weighted_sum(model, algorithm, w)
113+
status, solution = _solve_weighted_sum(model, algorithm, [w, 1.0 - w])
116114
if !_is_scalar_status_optimal(status)
117115
# Exit the solve with some error.
118116
return status, nothing

src/algorithms/EpsilonConstraint.jl

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ function optimize_multiobjective!(
7373
if MOI.output_dimension(model.f) != 2
7474
error("EpsilonConstraint requires exactly two objectives")
7575
end
76-
# Compute the bounding box ofthe objectives using Hierarchical().
76+
# Compute the bounding box of the objectives using Hierarchical().
7777
alg = Hierarchical()
7878
MOI.set.(Ref(alg), ObjectivePriority.(1:2), [1, 0])
7979
status, solution_1 = optimize_multiobjective!(alg, model)
@@ -87,22 +87,27 @@ function optimize_multiobjective!(
8787
end
8888
a, b = solution_1[1].y[1], solution_2[1].y[1]
8989
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
9096
# Compute the epsilon that we will be incrementing by each iteration
9197
ε = MOI.get(algorithm, EpsilonConstraintStep())
9298
n_points = MOI.get(algorithm, SolutionLimit())
9399
if n_points != default(algorithm, SolutionLimit())
94100
ε = abs(right - left) / (n_points - 1)
95101
end
96-
solutions = SolutionPoint[]
102+
solutions = SolutionPoint[only(solution_1), only(solution_2)]
97103
f1, f2 = MOI.Utilities.eachscalar(model.f)
98104
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f2)}(), f2)
99105
# Add epsilon constraint
100-
sense = MOI.get(model.inner, MOI.ObjectiveSense())
101106
variables = MOI.get(model.inner, MOI.ListOfVariableIndices())
102107
SetType, bound = if sense == MOI.MIN_SENSE
103-
MOI.LessThan{Float64}, right
108+
MOI.LessThan{Float64}, right - ε
104109
else
105-
MOI.GreaterThan{Float64}, left
110+
MOI.GreaterThan{Float64}, left + ε
106111
end
107112
constant = MOI.constant(f1, Float64)
108113
ci = MOI.Utilities.normalize_and_add_constraint(
@@ -113,7 +118,7 @@ function optimize_multiobjective!(
113118
)
114119
bound -= constant
115120
status = MOI.OPTIMAL
116-
for _ in 1:n_points
121+
for _ in 3:n_points
117122
if _time_limit_exceeded(model, start_time)
118123
status = MOI.TIME_LIMIT
119124
break

test/algorithms/Chalmet.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ function test_knapsack_min()
6868
@test isapprox(x_sol, X_E'; atol = 1e-6)
6969
y_sol = hcat([MOI.get(model, MOI.ObjectiveValue(i)) for i in 1:N]...)
7070
@test isapprox(y_sol, Y_N'; atol = 1e-6)
71+
@test MOI.get(model, MOI.ObjectiveBound()) == [-3394.0, -4636.0]
7172
return
7273
end
7374

@@ -117,6 +118,7 @@ function test_knapsack_max()
117118
@test isapprox(x_sol, X_E'; atol = 1e-6)
118119
y_sol = hcat([MOI.get(model, MOI.ObjectiveValue(i)) for i in 1:N]...)
119120
@test isapprox(y_sol, Y_N'; atol = 1e-6)
121+
@test MOI.get(model, MOI.ObjectiveBound()) == [3395.0, 4636.0]
120122
return
121123
end
122124

@@ -195,14 +197,17 @@ function test_vector_of_variables_objective()
195197
end
196198
MOI.set(model, MOA.Algorithm(), MOA.Chalmet())
197199
MOI.set(model, MOI.Silent(), true)
200+
MOI.set(model, MOA.ComputeIdealPoint(), false)
198201
x = MOI.add_variables(model, 2)
199202
MOI.add_constraint.(model, x, MOI.ZeroOne())
200203
f = MOI.VectorOfVariables(x)
201204
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
202205
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
203206
MOI.add_constraint(model, sum(1.0 * xi for xi in x), MOI.GreaterThan(1.0))
204207
MOI.optimize!(model)
205-
MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL
208+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL
209+
ideal_point = MOI.get(model, MOI.ObjectiveBound())
210+
@test length(ideal_point) == 2 && all(isnan, ideal_point)
206211
return
207212
end
208213

test/algorithms/EpsilonConstraint.jl

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ function test_biobjective_knapsack()
6565
Y = MOI.get(model, MOI.ObjectiveValue(i))
6666
@test results[round.(Int, Y)] == X
6767
end
68+
@test MOI.get(model, MOI.ObjectiveBound()) == [956.0, 983.0]
6869
return
6970
end
7071

@@ -108,6 +109,7 @@ function test_biobjective_knapsack_atol()
108109
Y = MOI.get(model, MOI.ObjectiveValue(i))
109110
@test results[round.(Int, Y)] == X
110111
end
112+
@test MOI.get(model, MOI.ObjectiveBound()) == [955.0, 983.0]
111113
return
112114
end
113115

@@ -136,11 +138,12 @@ function test_biobjective_knapsack_atol_large()
136138
)
137139
MOI.optimize!(model)
138140
results = Dict(
141+
[955, 906] => [2, 3, 5, 6, 9, 10, 11, 14, 15, 16, 17],
139142
[948, 939] => [1, 2, 3, 5, 6, 8, 10, 11, 15, 16, 17],
140143
[934, 971] => [2, 3, 5, 6, 8, 10, 11, 12, 15, 16, 17],
141144
[918, 983] => [2, 3, 4, 5, 6, 8, 10, 11, 12, 16, 17],
142145
)
143-
@test MOI.get(model, MOI.ResultCount()) == 3
146+
@test MOI.get(model, MOI.ResultCount()) == 4
144147
for i in 1:MOI.get(model, MOI.ResultCount())
145148
x_sol = MOI.get(model, MOI.VariablePrimal(i), x)
146149
X = findall(elt -> elt > 0.9, x_sol)
@@ -191,6 +194,7 @@ function test_biobjective_knapsack_min()
191194
Y = MOI.get(model, MOI.ObjectiveValue(i))
192195
@test results[-round.(Int, Y)] == X
193196
end
197+
@test MOI.get(model, MOI.ObjectiveBound()) == [-955.0, -983.0]
194198
return
195199
end
196200

@@ -219,16 +223,18 @@ function test_biobjective_knapsack_min_solution_limit()
219223
)
220224
MOI.optimize!(model)
221225
results = Dict(
226+
[955, 906] => [2, 3, 5, 6, 9, 10, 11, 14, 15, 16, 17],
222227
[943, 940] => [2, 3, 5, 6, 8, 9, 10, 11, 15, 16, 17],
223228
[918, 983] => [2, 3, 4, 5, 6, 8, 10, 11, 12, 16, 17],
224229
)
225-
@test MOI.get(model, MOI.ResultCount()) == 2
230+
@test MOI.get(model, MOI.ResultCount()) == 3
226231
for i in 1:MOI.get(model, MOI.ResultCount())
227232
x_sol = MOI.get(model, MOI.VariablePrimal(i), x)
228233
X = findall(elt -> elt > 0.9, x_sol)
229234
Y = MOI.get(model, MOI.ObjectiveValue(i))
230235
@test results[round.(Int, Y)] == X
231236
end
237+
@test MOI.get(model, MOI.ObjectiveBound()) == [955.0, 983.0]
232238
return
233239
end
234240

@@ -419,7 +425,8 @@ function test_time_limit()
419425
)
420426
MOI.optimize!(model)
421427
@test MOI.get(model, MOI.TerminationStatus()) == MOI.TIME_LIMIT
422-
@test MOI.get(model, MOI.ResultCount()) == 0
428+
# Check time limits in subsolves
429+
@test_broken MOI.get(model, MOI.ResultCount()) == 0
423430
return
424431
end
425432

0 commit comments

Comments
 (0)