Skip to content

Commit 1e3032c

Browse files
authored
Support MOI.TimeLimitSec (#54)
1 parent d3f45d9 commit 1e3032c

File tree

13 files changed

+268
-8
lines changed

13 files changed

+268
-8
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,4 @@ the solution process.
6767
* `MOA.ObjectiveRelativeTolerance(index::Int)`
6868
* `MOA.ObjectiveWeight(index::Int)`
6969
* `MOA.SolutionLimit()`
70+
* `MOI.TimeLimitSec()`

src/MultiObjectiveAlgorithms.jl

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
module MultiObjectiveAlgorithms
77

88
import Combinatorics
9-
import MathOptInterface
10-
11-
const MOI = MathOptInterface
9+
import MathOptInterface as MOI
1210

1311
struct SolutionPoint
1412
x::Dict{MOI.VariableIndex,Float64}
@@ -110,6 +108,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
110108
f::Union{Nothing,MOI.AbstractVectorFunction}
111109
solutions::Vector{SolutionPoint}
112110
termination_status::MOI.TerminationStatusCode
111+
time_limit_sec::Union{Nothing,Float64}
113112

114113
function Optimizer(optimizer_factory)
115114
return new(
@@ -118,6 +117,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
118117
nothing,
119118
SolutionPoint[],
120119
MOI.OPTIMIZE_NOT_CALLED,
120+
nothing,
121121
)
122122
end
123123
end
@@ -143,6 +143,34 @@ function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)
143143
return MOI.Utilities.default_copy_to(dest, src)
144144
end
145145

146+
### TimeLimitSec
147+
148+
function MOI.supports(model::Optimizer, attr::MOI.TimeLimitSec)
149+
return MOI.supports(model.inner, attr)
150+
end
151+
152+
MOI.get(model::Optimizer, ::MOI.TimeLimitSec) = model.time_limit_sec
153+
154+
function MOI.set(model::Optimizer, ::MOI.TimeLimitSec, value::Real)
155+
model.time_limit_sec = Float64(value)
156+
return
157+
end
158+
159+
function MOI.set(model::Optimizer, ::MOI.TimeLimitSec, ::Nothing)
160+
model.time_limit_sec = nothing
161+
return
162+
end
163+
164+
function _time_limit_exceeded(model::Optimizer, start_time::Float64)
165+
time_limit = MOI.get(model, MOI.TimeLimitSec())
166+
if time_limit === nothing
167+
return false
168+
end
169+
return time() - start_time >= time_limit
170+
end
171+
172+
### ObjectiveFunction
173+
146174
function MOI.supports(
147175
::Optimizer,
148176
::MOI.ObjectiveFunction{<:MOI.AbstractScalarFunction},

src/algorithms/Chalmet.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
Chalmet, L.G., and Lemonidis, L., and Elzinga, D.J. (1986). An algorithm for the
1212
bi-criterion integer programming problem. European Journal of Operational
1313
Research. 25(2), 292-300
14+
15+
## Supported optimizer attributes
16+
17+
* `MOI.TimeLimitSec()`: terminate if the time limit is exceeded and return the
18+
list of current solutions.
1419
"""
1520
mutable struct Chalmet <: AbstractAlgorithm end
1621

@@ -37,6 +42,7 @@ function _solve_constrained_model(
3742
end
3843

3944
function optimize_multiobjective!(algorithm::Chalmet, model::Optimizer)
45+
start_time = time()
4046
if MOI.output_dimension(model.f) != 2
4147
error("Chalmet requires exactly two objectives")
4248
end
@@ -91,6 +97,9 @@ function optimize_multiobjective!(algorithm::Chalmet, model::Optimizer)
9197
push!(Q, (1, 2))
9298
t = 3
9399
while !isempty(Q)
100+
if _time_limit_exceeded(model, start_time)
101+
return MOI.TIME_LIMIT, solutions
102+
end
94103
r, s = pop!(Q)
95104
yr, ys = solutions[r].y, solutions[s].y
96105
rhs = [max(yr[1], ys[1]), max(yr[2], ys[2])]

src/algorithms/Dichotomy.jl

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ Science 25(1), 73-78.
1313
1414
## Supported optimizer attributes
1515
16-
* `MOA.SolutionLimit()`
16+
* `MOI.TimeLimitSec()`: terminate if the time limit is exceeded and return the
17+
list of current solutions.
18+
19+
* `MOA.SolutionLimit()`: terminate once this many solutions have been found.
1720
"""
1821
mutable struct Dichotomy <: AbstractAlgorithm
1922
solution_limit::Union{Nothing,Int}
@@ -73,6 +76,7 @@ function _solve_weighted_sum(
7376
end
7477

7578
function optimize_multiobjective!(algorithm::Dichotomy, model::Optimizer)
79+
start_time = time()
7680
if MOI.output_dimension(model.f) > 2
7781
error("Only scalar or bi-objective problems supported.")
7882
end
@@ -93,7 +97,12 @@ function optimize_multiobjective!(algorithm::Dichotomy, model::Optimizer)
9397
push!(queue, (0.0, 1.0))
9498
end
9599
limit = MOI.get(algorithm, SolutionLimit())
100+
status = MOI.OPTIMAL
96101
while length(queue) > 0 && length(solutions) < limit
102+
if _time_limit_exceeded(model, start_time)
103+
status = MOI.TIME_LIMIT
104+
break
105+
end
97106
(a, b) = popfirst!(queue)
98107
y_d = solutions[a].y .- solutions[b].y
99108
w = y_d[2] / (y_d[2] - y_d[1])
@@ -114,5 +123,5 @@ function optimize_multiobjective!(algorithm::Dichotomy, model::Optimizer)
114123
end
115124
solution_list =
116125
[solutions[w] for w in sort(collect(keys(solutions)); rev = true)]
117-
return MOI.OPTIMAL, solution_list
126+
return status, solution_list
118127
end

src/algorithms/DominguezRios.jl

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
Dominguez-Rios, M.A. & Chicano, F., & Alba, E. (2021). Effective anytime
1212
algorithm for multiobjective combinatorial optimization problems. Information
1313
Sciences, 565(7), 210-228.
14+
15+
## Supported optimizer attributes
16+
17+
* `MOI.TimeLimitSec()`: terminate if the time limit is exceeded and return the
18+
list of current solutions.
1419
"""
1520
mutable struct DominguezRios <: AbstractAlgorithm end
1621

@@ -138,6 +143,7 @@ function _update!(
138143
end
139144

140145
function optimize_multiobjective!(algorithm::DominguezRios, model::Optimizer)
146+
start_time = time()
141147
sense = MOI.get(model.inner, MOI.ObjectiveSense())
142148
if sense == MOI.MAX_SENSE
143149
old_obj, neg_obj = copy(model.f), -model.f
@@ -187,7 +193,12 @@ function optimize_multiobjective!(algorithm::DominguezRios, model::Optimizer)
187193
t_max = MOI.add_variable(model.inner)
188194
solutions = SolutionPoint[]
189195
k = 0
196+
status = MOI.OPTIMAL
190197
while any(!isempty(l) for l in L)
198+
if _time_limit_exceeded(model, start_time)
199+
status = MOI.TIME_LIMIT
200+
break
201+
end
191202
i, k = _select_next_box(L, k)
192203
B = L[k][i]
193204
w = 1 ./ max.(1, B.u - yI)
@@ -214,5 +225,5 @@ function optimize_multiobjective!(algorithm::DominguezRios, model::Optimizer)
214225
MOI.delete.(model.inner, constraints)
215226
end
216227
MOI.delete(model.inner, t_max)
217-
return MOI.OPTIMAL, solutions
228+
return status, solutions
218229
end

src/algorithms/EpsilonConstraint.jl

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ function optimize_multiobjective!(
6969
algorithm::EpsilonConstraint,
7070
model::Optimizer,
7171
)
72+
start_time = time()
7273
if MOI.output_dimension(model.f) != 2
7374
error("EpsilonConstraint requires exactly two objectives")
7475
end
@@ -104,7 +105,12 @@ function optimize_multiobjective!(
104105
MOI.GreaterThan{Float64}, left
105106
end
106107
ci = MOI.add_constraint(model, f1, SetType(bound))
108+
status = MOI.OPTIMAL
107109
while true
110+
if _time_limit_exceeded(model, start_time)
111+
status = MOI.TIME_LIMIT
112+
break
113+
end
108114
MOI.set(model, MOI.ConstraintSet(), ci, SetType(bound))
109115
MOI.optimize!(model.inner)
110116
if !_is_scalar_status_optimal(model)
@@ -121,5 +127,5 @@ function optimize_multiobjective!(
121127
end
122128
end
123129
MOI.delete(model, ci)
124-
return MOI.OPTIMAL, filter_nondominated(sense, solutions)
130+
return status, filter_nondominated(sense, solutions)
125131
end

src/algorithms/KirlikSayin.jl

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ This is an algorithm to generate all nondominated solutions for multi-objective
1616
discrete optimization problems. The algorithm maintains `(p-1)`-dimensional
1717
rectangle regions in the solution space, and a two-stage optimization problem
1818
is solved for each rectangle.
19+
20+
## Supported optimizer attributes
21+
22+
* `MOI.TimeLimitSec()`: terminate if the time limit is exceeded and return the
23+
list of current solutions.
1924
"""
2025
mutable struct KirlikSayin <: AbstractAlgorithm end
2126

@@ -74,6 +79,7 @@ end
7479
_volume(r::_Rectangle, l::Vector{Float64}) = prod(r.u - l)
7580

7681
function optimize_multiobjective!(algorithm::KirlikSayin, model::Optimizer)
82+
start_time = time()
7783
sense = MOI.get(model.inner, MOI.ObjectiveSense())
7884
if sense == MOI.MAX_SENSE
7985
old_obj, neg_obj = copy(model.f), -model.f
@@ -134,7 +140,12 @@ function optimize_multiobjective!(algorithm::KirlikSayin, model::Optimizer)
134140
MOI.LessThan{Float64},
135141
MOI.GreaterThan{Float64},
136142
)
143+
status = MOI.OPTIMAL
137144
while !isempty(L)
145+
if _time_limit_exceeded(model, start_time)
146+
status = MOI.TIME_LIMIT
147+
break
148+
end
138149
Rᵢ = L[argmax([_volume(Rᵢ, _project(yI, k)) for Rᵢ in L])]
139150
lᵢ, uᵢ = Rᵢ.l, Rᵢ.u
140151
# Solving the first stage model: P_k(ε)
@@ -182,5 +193,5 @@ function optimize_multiobjective!(algorithm::KirlikSayin, model::Optimizer)
182193
end
183194
_remove_rectangle(L, _Rectangle(Y_proj, uᵢ))
184195
end
185-
return MOI.OPTIMAL, solutions
196+
return status, solutions
186197
end

test/algorithms/Chalmet.jl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,43 @@ function test_knapsack_max()
121121
return
122122
end
123123

124+
function test_time_limit()
125+
n = 10
126+
W = 2137.0
127+
C = Float64[
128+
566 611 506 180 817 184 585 423 26 317
129+
62 84 977 979 874 54 269 93 881 563
130+
]
131+
w = Float64[557, 898, 148, 63, 78, 964, 246, 662, 386, 272]
132+
model = MOA.Optimizer(HiGHS.Optimizer)
133+
MOI.set(model, MOA.Algorithm(), MOA.Chalmet())
134+
MOI.set(model, MOI.Silent(), true)
135+
MOI.set(model, MOI.TimeLimitSec(), 0.0)
136+
x = MOI.add_variables(model, n)
137+
MOI.add_constraint.(model, x, MOI.ZeroOne())
138+
MOI.add_constraint(
139+
model,
140+
MOI.ScalarAffineFunction(
141+
[MOI.ScalarAffineTerm(w[j], x[j]) for j in 1:n],
142+
0.0,
143+
),
144+
MOI.LessThan(W),
145+
)
146+
f = MOI.VectorAffineFunction(
147+
[
148+
MOI.VectorAffineTerm(i, MOI.ScalarAffineTerm(C[i, j], x[j])) for
149+
i in 1:2 for j in 1:n
150+
],
151+
[0.0, 0.0],
152+
)
153+
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
154+
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
155+
MOI.optimize!(model)
156+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.TIME_LIMIT
157+
@test MOI.get(model, MOI.ResultCount()) > 0
158+
return
159+
end
160+
124161
function test_unbounded()
125162
model = MOA.Optimizer(HiGHS.Optimizer)
126163
MOI.set(model, MOA.Algorithm(), MOA.Chalmet())

test/algorithms/Dichotomy.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,37 @@ function test_biobjective_knapsack()
235235
return
236236
end
237237

238+
function test_time_limit()
239+
p1 = [77, 94, 71, 63, 96, 82, 85, 75, 72, 91, 99, 63, 84, 87, 79, 94, 90]
240+
p2 = [65, 90, 90, 77, 95, 84, 70, 94, 66, 92, 74, 97, 60, 60, 65, 97, 93]
241+
w = [80, 87, 68, 72, 66, 77, 99, 85, 70, 93, 98, 72, 100, 89, 67, 86, 91]
242+
f = MOI.OptimizerWithAttributes(
243+
() -> MOA.Optimizer(HiGHS.Optimizer),
244+
MOA.Algorithm() => MOA.Dichotomy(),
245+
)
246+
model = MOI.instantiate(f)
247+
MOI.set(model, MOI.Silent(), true)
248+
MOI.set(model, MOI.TimeLimitSec(), 0.0)
249+
x = MOI.add_variables(model, length(w))
250+
MOI.add_constraint.(model, x, MOI.ZeroOne())
251+
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)
252+
f = MOI.Utilities.operate(
253+
vcat,
254+
Float64,
255+
[sum(1.0 * p[i] * x[i] for i in 1:length(w)) for p in [p1, p2]]...,
256+
)
257+
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
258+
MOI.add_constraint(
259+
model,
260+
sum(1.0 * w[i] * x[i] for i in 1:length(w)),
261+
MOI.LessThan(900.0),
262+
)
263+
MOI.optimize!(model)
264+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.TIME_LIMIT
265+
@test MOI.get(model, MOI.ResultCount()) > 0
266+
return
267+
end
268+
238269
function test_infeasible()
239270
model = MOA.Optimizer(HiGHS.Optimizer)
240271
MOI.set(model, MOA.Algorithm(), MOA.Dichotomy())

test/algorithms/DominguezRios.jl

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,45 @@ function test_no_bounding_box()
544544
return
545545
end
546546

547+
function test_time_limit()
548+
p = 3
549+
n = 10
550+
W = 2137.0
551+
C = Float64[
552+
566 611 506 180 817 184 585 423 26 317
553+
62 84 977 979 874 54 269 93 881 563
554+
664 982 962 140 224 215 12 869 332 537
555+
]
556+
w = Float64[557, 898, 148, 63, 78, 964, 246, 662, 386, 272]
557+
model = MOA.Optimizer(HiGHS.Optimizer)
558+
MOI.set(model, MOA.Algorithm(), MOA.DominguezRios())
559+
MOI.set(model, MOI.TimeLimitSec(), 0.0)
560+
MOI.set(model, MOI.Silent(), true)
561+
x = MOI.add_variables(model, n)
562+
MOI.add_constraint.(model, x, MOI.ZeroOne())
563+
MOI.add_constraint(
564+
model,
565+
MOI.ScalarAffineFunction(
566+
[MOI.ScalarAffineTerm(w[j], x[j]) for j in 1:n],
567+
0.0,
568+
),
569+
MOI.LessThan(W),
570+
)
571+
f = MOI.VectorAffineFunction(
572+
[
573+
MOI.VectorAffineTerm(i, MOI.ScalarAffineTerm(-C[i, j], x[j]))
574+
for i in 1:p for j in 1:n
575+
],
576+
fill(0.0, p),
577+
)
578+
MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE)
579+
MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f)
580+
MOI.optimize!(model)
581+
@test MOI.get(model, MOI.TerminationStatus()) == MOI.TIME_LIMIT
582+
@test MOI.get(model, MOI.ResultCount()) == 0
583+
return
584+
end
585+
547586
end
548587

549588
TestDominguezRios.run_tests()

0 commit comments

Comments
 (0)