diff --git a/ext/MultiObjectiveAlgorithmsPolyhedraExt.jl b/ext/MultiObjectiveAlgorithmsPolyhedraExt.jl index d61a6df..39b6fd5 100644 --- a/ext/MultiObjectiveAlgorithmsPolyhedraExt.jl +++ b/ext/MultiObjectiveAlgorithmsPolyhedraExt.jl @@ -101,8 +101,9 @@ function MOA.minimize_multiobjective!( H = _halfspaces(IPS) count = 0 while !isempty(H) - if MOA._time_limit_exceeded(model, start_time) - status = MOI.TIME_LIMIT + ret = MOA._check_premature_termination(model, start_time) + if ret !== nothing + status = ret break end count += 1 diff --git a/src/MultiObjectiveAlgorithms.jl b/src/MultiObjectiveAlgorithms.jl index 7a178ae..8c2fb3b 100644 --- a/src/MultiObjectiveAlgorithms.jl +++ b/src/MultiObjectiveAlgorithms.jl @@ -185,21 +185,6 @@ function MOI.set(model::Optimizer, ::MOI.TimeLimitSec, ::Nothing) return end -function _time_limit_exceeded(model::Optimizer, start_time::Float64) - time_limit = MOI.get(model, MOI.TimeLimitSec()) - if time_limit === nothing - return false - end - time_remaining = time_limit - (time() - start_time) - if time_remaining <= 0 - return true - end - if MOI.supports(model.inner, MOI.TimeLimitSec()) - MOI.set(model.inner, MOI.TimeLimitSec(), time_remaining) - end - return false -end - ### SolveTimeSec function MOI.get(model::Optimizer, ::MOI.SolveTimeSec) @@ -604,7 +589,7 @@ end function _compute_ideal_point(model::Optimizer, start_time) for (i, f) in enumerate(MOI.Utilities.eachscalar(model.f)) - if _time_limit_exceeded(model, start_time) + if _check_premature_termination(model, start_time) !== nothing return end if !isnan(model.ideal_point[i]) @@ -644,7 +629,39 @@ function optimize_multiobjective!( return minimize_multiobjective!(algorithm, model) end +function _check_interrupt(f) + try + return reenable_sigint(f) + catch ex + if !(ex isa InterruptException) + rethrow(ex) + end + return MOI.INTERRUPTED + end +end + +function _check_premature_termination(model::Optimizer, start_time::Float64) + return _check_interrupt() do + time_limit = MOI.get(model, MOI.TimeLimitSec()) + if time_limit !== nothing + time_remaining = time_limit - (time() - start_time) + if time_remaining <= 0 + return MOI.TIME_LIMIT + end + if MOI.supports(model.inner, MOI.TimeLimitSec()) + MOI.set(model.inner, MOI.TimeLimitSec(), time_remaining) + end + end + return + end +end + function MOI.optimize!(model::Optimizer) + disable_sigint(() -> _optimize!(model)) + return +end + +function _optimize!(model::Optimizer) start_time = time() empty!(model.solutions) model.termination_status = MOI.OPTIMIZE_NOT_CALLED diff --git a/src/algorithms/Chalmet.jl b/src/algorithms/Chalmet.jl index b4e61cf..77d4a80 100644 --- a/src/algorithms/Chalmet.jl +++ b/src/algorithms/Chalmet.jl @@ -101,8 +101,8 @@ function minimize_multiobjective!(algorithm::Chalmet, model::Optimizer) push!(Q, (1, 2)) t = 3 while !isempty(Q) - if _time_limit_exceeded(model, start_time) - return MOI.TIME_LIMIT, solutions + if (ret = _check_premature_termination(model, start_time)) !== nothing + return ret, solutions end r, s = pop!(Q) yr, ys = solutions[r].y, solutions[s].y diff --git a/src/algorithms/Dichotomy.jl b/src/algorithms/Dichotomy.jl index d357056..47aec29 100644 --- a/src/algorithms/Dichotomy.jl +++ b/src/algorithms/Dichotomy.jl @@ -77,16 +77,16 @@ function optimize_multiobjective!(algorithm::Dichotomy, model::Optimizer) error("Only scalar or bi-objective problems supported.") end if MOI.output_dimension(model.f) == 1 - if _time_limit_exceeded(model, start_time) - return MOI.TIME_LIMIT, nothing + if (ret = _check_premature_termination(model, start_time)) !== nothing + return ret, nothing end status, solution = _solve_weighted_sum(model, algorithm, [1.0]) return status, [solution] end solutions = Dict{Float64,SolutionPoint}() for (i, w) in (1 => 1.0, 2 => 0.0) - if _time_limit_exceeded(model, start_time) - return MOI.TIME_LIMIT, nothing + if (ret = _check_premature_termination(model, start_time)) !== nothing + return ret, nothing end status, solution = _solve_weighted_sum(model, algorithm, [w, 1.0 - w]) if !_is_scalar_status_optimal(status) @@ -103,8 +103,8 @@ function optimize_multiobjective!(algorithm::Dichotomy, model::Optimizer) limit = MOI.get(algorithm, SolutionLimit()) status = MOI.OPTIMAL while length(queue) > 0 && length(solutions) < limit - if _time_limit_exceeded(model, start_time) - status = MOI.TIME_LIMIT + if (ret = _check_premature_termination(model, start_time)) !== nothing + status = ret break end (a, b) = popfirst!(queue) diff --git a/src/algorithms/DominguezRios.jl b/src/algorithms/DominguezRios.jl index 566b582..8ca05d9 100644 --- a/src/algorithms/DominguezRios.jl +++ b/src/algorithms/DominguezRios.jl @@ -183,8 +183,8 @@ function minimize_multiobjective!(algorithm::DominguezRios, model::Optimizer) k = 0 status = MOI.OPTIMAL while any(!isempty(l) for l in L) - if _time_limit_exceeded(model, start_time) - status = MOI.TIME_LIMIT + if (ret = _check_premature_termination(model, start_time)) !== nothing + status = ret break end i, k = _select_next_box(L, k) diff --git a/src/algorithms/EpsilonConstraint.jl b/src/algorithms/EpsilonConstraint.jl index e1ed5f0..74dc5b0 100644 --- a/src/algorithms/EpsilonConstraint.jl +++ b/src/algorithms/EpsilonConstraint.jl @@ -111,8 +111,8 @@ function minimize_multiobjective!( bound -= constant status = MOI.OPTIMAL for _ in 3:n_points - if _time_limit_exceeded(model, start_time) - status = MOI.TIME_LIMIT + if (ret = _check_premature_termination(model, start_time)) !== nothing + status = ret break end MOI.set(model, MOI.ConstraintSet(), ci, MOI.LessThan{Float64}(bound)) diff --git a/src/algorithms/KirlikSayin.jl b/src/algorithms/KirlikSayin.jl index c5f242e..0fb3e4b 100644 --- a/src/algorithms/KirlikSayin.jl +++ b/src/algorithms/KirlikSayin.jl @@ -117,8 +117,9 @@ function minimize_multiobjective!(algorithm::KirlikSayin, model::Optimizer) L = [_Rectangle(_project(yI, k), _project(yN, k))] status = MOI.OPTIMAL while !isempty(L) - if _time_limit_exceeded(model, start_time) - return MOI.TIME_LIMIT, solutions + if (ret = _check_premature_termination(model, start_time)) !== nothing + status = ret + break end max_volume_index = argmax([_volume(Rᵢ, _project(yI, k)) for Rᵢ in L]) uᵢ = L[max_volume_index].u diff --git a/src/algorithms/Lexicographic.jl b/src/algorithms/Lexicographic.jl index 5f105a9..049c8b6 100644 --- a/src/algorithms/Lexicographic.jl +++ b/src/algorithms/Lexicographic.jl @@ -124,8 +124,8 @@ function _solve_in_sequence( solution = SolutionPoint[] status = MOI.OPTIMAL for i in sequence - if _time_limit_exceeded(model, start_time) - status = MOI.TIME_LIMIT + if (ret = _check_premature_termination(model, start_time)) !== nothing + status = ret break end f = scalars[i] diff --git a/src/algorithms/RandomWeighting.jl b/src/algorithms/RandomWeighting.jl index 2d2f8ef..6563b0a 100644 --- a/src/algorithms/RandomWeighting.jl +++ b/src/algorithms/RandomWeighting.jl @@ -60,8 +60,9 @@ function optimize_multiobjective!(algorithm::RandomWeighting, model::Optimizer) # * then the outer loop goes again while length(solutions) < MOI.get(algorithm, SolutionLimit()) while length(solutions) < MOI.get(algorithm, SolutionLimit()) - if _time_limit_exceeded(model, start_time) - return MOI.TIME_LIMIT, filter_nondominated(sense, solutions) + ret = _check_premature_termination(model, start_time) + if ret !== nothing + return ret, filter_nondominated(sense, solutions) end weights = rand(P) f = _scalarise(model.f, weights) diff --git a/src/algorithms/TambyVanderpooten.jl b/src/algorithms/TambyVanderpooten.jl index 0bd5b32..48128b4 100644 --- a/src/algorithms/TambyVanderpooten.jl +++ b/src/algorithms/TambyVanderpooten.jl @@ -123,8 +123,8 @@ function minimize_multiobjective!( U_N[yN] = [[_get_child(yN, yI, k)] for k in 1:n] status = MOI.OPTIMAL while !isempty(U_N) - if _time_limit_exceeded(model, start_time) - status = MOI.TIME_LIMIT + if (ret = _check_premature_termination(model, start_time)) !== nothing + status = ret break end k, u = _select_search_zone(U_N, yI, yN) diff --git a/test/test_model.jl b/test/test_model.jl index 3c1f310..db61d86 100644 --- a/test/test_model.jl +++ b/test/test_model.jl @@ -225,6 +225,15 @@ function test_SubproblemCount() return end +function test_check_interrupt() + function _test_check_interrupt(err) + return disable_sigint(() -> MOA._check_interrupt(() -> throw(err))) + end + @test _test_check_interrupt(InterruptException()) == MOI.INTERRUPTED + @test_throws ArgumentError("") _test_check_interrupt(ArgumentError("")) + return +end + end # module TestModel.run_tests()