Skip to content

Commit cf37181

Browse files
authored
Add ComputeIdealPoint attribute (#96)
1 parent af5eb49 commit cf37181

File tree

3 files changed

+88
-9
lines changed

3 files changed

+88
-9
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,13 @@ the solution process.
7979
* `MOA.ObjectiveWeight(index::Int)`
8080
* `MOA.SolutionLimit()`
8181
* `MOI.TimeLimitSec()`
82+
83+
## Ideal point
84+
85+
By default, MOA will compute the ideal point, which can be queried using the
86+
`MOI.ObjectiveBound` attribute.
87+
88+
Computing the ideal point requires as many solves as the dimension of the
89+
objective function. Thus, if you do not need the ideal point information, you
90+
can improve the performance of MOA by setting the `MOA.ComputeIdealPoint()`
91+
attribute to `false`.

src/MultiObjectiveAlgorithms.jl

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
108108
time_limit_sec::Union{Nothing,Float64}
109109
solve_time::Float64
110110
ideal_point::Vector{Float64}
111+
compute_ideal_point::Bool
111112

112113
function Optimizer(optimizer_factory)
113114
return new(
@@ -119,6 +120,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
119120
nothing,
120121
NaN,
121122
Float64[],
123+
default(ComputeIdealPoint()),
122124
)
123125
end
124126
end
@@ -130,6 +132,7 @@ function MOI.empty!(model::Optimizer)
130132
model.termination_status = MOI.OPTIMIZE_NOT_CALLED
131133
model.solve_time = NaN
132134
empty!(model.ideal_point)
135+
model.compute_ideal_point = default(ComputeIdealPoint())
133136
return
134137
end
135138

@@ -139,7 +142,8 @@ function MOI.is_empty(model::Optimizer)
139142
isempty(model.solutions) &&
140143
model.termination_status == MOI.OPTIMIZE_NOT_CALLED &&
141144
isnan(model.solve_time) &&
142-
isempty(model.ideal_point)
145+
isempty(model.ideal_point) &&
146+
model.compute_ideal_point == default(ComputeIdealPoint())
143147
end
144148

145149
MOI.supports_incremental_interface(::Optimizer) = true
@@ -351,6 +355,33 @@ struct LexicographicAllPermutations <: AbstractAlgorithmAttribute end
351355

352356
default(::LexicographicAllPermutations) = true
353357

358+
"""
359+
ComputeIdealPoint <: AbstractOptimizerAttribute -> Bool
360+
361+
Controls whether to compute the ideal point.
362+
363+
Defaults to true`.
364+
365+
If this attribute is set to `true`, the ideal point can be queried using the
366+
`MOI.ObjectiveBound` attribute.
367+
368+
Computing the ideal point requires as many solves as the dimension of the
369+
objective function. Thus, if you do not need the ideal point information, you
370+
can improve the performance of MOA by setting this attribute to `false`.
371+
"""
372+
struct ComputeIdealPoint <: MOI.AbstractOptimizerAttribute end
373+
374+
default(::ComputeIdealPoint) = true
375+
376+
MOI.supports(::Optimizer, ::ComputeIdealPoint) = true
377+
378+
function MOI.set(model::Optimizer, ::ComputeIdealPoint, value::Bool)
379+
model.compute_ideal_point = value
380+
return
381+
end
382+
383+
MOI.get(model::Optimizer, ::ComputeIdealPoint) = model.compute_ideal_point
384+
354385
### RawOptimizerAttribute
355386

356387
function MOI.supports(model::Optimizer, attr::MOI.RawOptimizerAttribute)
@@ -530,16 +561,12 @@ function MOI.delete(model::Optimizer, ci::MOI.ConstraintIndex)
530561
return
531562
end
532563

533-
function MOI.optimize!(model::Optimizer)
534-
start_time = time()
535-
empty!(model.solutions)
536-
model.termination_status = MOI.OPTIMIZE_NOT_CALLED
537-
if model.f === nothing
538-
model.termination_status = MOI.INVALID_MODEL
539-
return
540-
end
564+
function _compute_ideal_point(model::Optimizer)
541565
objectives = MOI.Utilities.eachscalar(model.f)
542566
model.ideal_point = fill(NaN, length(objectives))
567+
if !MOI.get(model, ComputeIdealPoint())
568+
return
569+
end
543570
for (i, f) in enumerate(objectives)
544571
MOI.set(model.inner, MOI.ObjectiveFunction{typeof(f)}(), f)
545572
MOI.optimize!(model.inner)
@@ -548,6 +575,18 @@ function MOI.optimize!(model::Optimizer)
548575
model.ideal_point[i] = MOI.get(model.inner, MOI.ObjectiveValue())
549576
end
550577
end
578+
return
579+
end
580+
581+
function MOI.optimize!(model::Optimizer)
582+
start_time = time()
583+
empty!(model.solutions)
584+
model.termination_status = MOI.OPTIMIZE_NOT_CALLED
585+
if model.f === nothing
586+
model.termination_status = MOI.INVALID_MODEL
587+
return
588+
end
589+
_compute_ideal_point(model)
551590
algorithm = something(model.algorithm, default(Algorithm()))
552591
status, solutions = optimize_multiobjective!(algorithm, model)
553592
model.termination_status = status

test/test_model.jl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,36 @@ function test_scalarise()
173173
return
174174
end
175175

176+
function test_ideal_point()
177+
for (flag, result) in (true => [0.0, -9.0], false => [NaN, NaN])
178+
model = MOA.Optimizer(HiGHS.Optimizer)
179+
MOI.set(model, MOI.Silent(), true)
180+
x = MOI.add_variables(model, 2)
181+
MOI.add_constraint.(model, x, MOI.GreaterThan(0.0))
182+
MOI.add_constraint(model, x[2], MOI.LessThan(3.0))
183+
MOI.add_constraint(model, 3.0 * x[1] - 1.0 * x[2], MOI.LessThan(6.0))
184+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
185+
f = MOI.Utilities.vectorize([
186+
3.0 * x[1] + x[2],
187+
-1.0 * x[1] - 2.0 * x[2],
188+
])
189+
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
190+
@test MOI.supports(model, MOA.ComputeIdealPoint())
191+
@test MOI.get(model, MOA.ComputeIdealPoint())
192+
@test MOI.set(model, MOA.ComputeIdealPoint(), flag) === nothing
193+
@test MOI.get(model, MOA.ComputeIdealPoint()) == flag
194+
MOI.optimize!(model)
195+
point = MOI.get(model, MOI.ObjectiveBound())
196+
@test length(point) == 2
197+
if flag
198+
@test point result
199+
else
200+
@test all(isnan, point)
201+
end
202+
end
203+
return
176204
end
177205

206+
end # module
207+
178208
TestModel.run_tests()

0 commit comments

Comments
 (0)