Skip to content

Commit 19fb9e0

Browse files
odowsandyspiers
andauthored
Add MOA.AllowInnerInterrupt to support interrupting an inner solve (#188)
Co-authored-by: sandyspiers <sandy.spiers@gmail.com>
1 parent 1a25bab commit 19fb9e0

File tree

3 files changed

+73
-1
lines changed

3 files changed

+73
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Each algorithm supports only a subset of the attributes. Consult the algorithm's
8282
docstring for details on which attributes it supports, and how it uses them in
8383
the solution process.
8484

85+
* `MOA.AllowInnerInterrupt()`
8586
* `MOA.EpsilonConstraintStep()`
8687
* `MOA.LexicographicAllPermutations()`
8788
* `MOA.ObjectiveAbsoluteTolerance(index::Int)`

src/MultiObjectiveAlgorithms.jl

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
195195
termination_status::MOI.TerminationStatusCode
196196
silent::Bool
197197
time_limit_sec::Union{Nothing,Float64}
198+
allow_inner_interrupt::Bool
198199
start_time::Float64
199200
solve_time::Float64
200201
ideal_point::Vector{Float64}
@@ -217,6 +218,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
217218
MOI.OPTIMIZE_NOT_CALLED,
218219
false,
219220
nothing,
221+
false,
220222
NaN,
221223
NaN,
222224
Float64[],
@@ -311,6 +313,34 @@ function MOI.set(model::Optimizer, ::MOI.TimeLimitSec, ::Nothing)
311313
return
312314
end
313315

316+
### AllowInnerInterrupt
317+
318+
"""
319+
AllowInnerInterrupt() <: MOI.AbstractOptimizerAttribute
320+
321+
An optimizer attribute that controls whether the SIGINT (`[CTRL]+[C]`) are
322+
enabled during the solve of the inner optimization problems.
323+
324+
By default, this attribute is set to `false`.
325+
326+
!!! warning
327+
This attribute should be used with caution. It is safe to set to `true` only
328+
if the inner optimization has native support for safely interrupting a call
329+
to `optimize!`. It is undefined behavior if you set this attribute to `true`
330+
and you interrupt a solver that does not have support for safely
331+
interrupting a solve.
332+
"""
333+
struct AllowInnerInterrupt <: MOI.AbstractOptimizerAttribute end
334+
335+
MOI.supports(::Optimizer, ::AllowInnerInterrupt) = true
336+
337+
MOI.get(model::Optimizer, ::AllowInnerInterrupt) = model.allow_inner_interrupt
338+
339+
function MOI.set(model::Optimizer, ::AllowInnerInterrupt, value::Bool)
340+
model.allow_inner_interrupt = value
341+
return
342+
end
343+
314344
### SolveTimeSec
315345

316346
function MOI.get(model::Optimizer, ::MOI.SolveTimeSec)
@@ -701,6 +731,13 @@ function MOI.delete(model::Optimizer, ci::MOI.ConstraintIndex)
701731
return
702732
end
703733

734+
function _call_with_sigint_if(f::Function, enable_sigint::Bool)
735+
if enable_sigint
736+
return reenable_sigint(f)
737+
end
738+
return f()
739+
end
740+
704741
"""
705742
optimize_inner!(model::Optimizer)
706743
@@ -715,7 +752,18 @@ packages.
715752
"""
716753
function optimize_inner!(model::Optimizer)
717754
start_time = time()
718-
MOI.optimize!(model.inner)
755+
# If AllowInnerInterrupt=true, there are two cases that can happen:
756+
#
757+
# 1. the interrupt is gracefully caught by the inner optimizer, in which
758+
# case no error is thrown. The interrupt is handled by the regular
759+
# termination status check, just as it would for any other problematic
760+
# solve.
761+
#
762+
# 2. the interrupt is not gracefully caught, and an InterruptException is
763+
# thrown. Bad user. They shouldn't have set AllowInnerInterrupt.
764+
_call_with_sigint_if(MOI.get(model, AllowInnerInterrupt())) do
765+
return MOI.optimize!(model.inner)
766+
end
719767
model.solve_time_inner += time() - start_time
720768
model.subproblem_count += 1
721769
return

test/test_model.jl

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,29 @@ function test_printing_silent_inner()
297297
return
298298
end
299299

300+
function test_AllowInnerInterrupt()
301+
model = MOA.Optimizer(HiGHS.Optimizer)
302+
@test MOI.supports(model, MOA.AllowInnerInterrupt())
303+
@test MOI.get(model, MOA.AllowInnerInterrupt()) == false
304+
MOI.set(model, MOA.AllowInnerInterrupt(), true)
305+
@test MOI.get(model, MOA.AllowInnerInterrupt()) == true
306+
MOI.set(model, MOA.AllowInnerInterrupt(), false)
307+
@test MOI.get(model, MOA.AllowInnerInterrupt()) == false
308+
return
309+
end
310+
311+
function test_call_with_sigint_if()
312+
@test_throws(
313+
ErrorException, # sigatomic error
314+
MOA._call_with_sigint_if(() -> throw(InterruptException()), true),
315+
)
316+
@test_throws(
317+
InterruptException,
318+
MOA._call_with_sigint_if(() -> throw(InterruptException()), false),
319+
)
320+
return
321+
end
322+
300323
end # module
301324

302325
TestModel.run_tests()

0 commit comments

Comments
 (0)