From 2abef7ebac3d30cd29ae9ca30a76fa5ad1f7b72d Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 19 Sep 2023 12:40:32 +1200 Subject: [PATCH 1/5] Add support for MOI.TimeLimitSec and MOI.SolveTimeSec --- src/optimize.jl | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/optimize.jl b/src/optimize.jl index 2a5f6d0..3dacfd5 100644 --- a/src/optimize.jl +++ b/src/optimize.jl @@ -27,13 +27,24 @@ mutable struct Optimizer{T} <: MOI.AbstractOptimizer primal_objective::T primal_solution::Dict{MOI.VariableIndex,T} options::Dict{String,Any} + time_limit_sec::Union{Nothing,Float64} + solve_time_sec::Float64 function Optimizer{T}(solver::String) where {T} if solver == "chuffed" solver = Chuffed() end primal_solution = Dict{MOI.VariableIndex,T}() options = Dict{String,Any}("model_filename" => "") - return new(solver, Model{T}(), "", zero(T), primal_solution, options) + return new( + solver, + Model{T}(), + "", + zero(T), + primal_solution, + options, + nothing, + NaN, + ) end end @@ -66,8 +77,12 @@ function _run_minizinc(dest::Optimizer) end output = joinpath(dir, "model.ozn") _stdout = joinpath(dir, "_stdout.txt") + time_limit = "" + if dest.time_limit_sec !== nothing + time_limit = "--time-limit $(1_000 * dest.time_limit_sec)" + end _minizinc_exe() do exe - cmd = `$(exe) --solver $(dest.solver) --output-objective -o $(output) $(filename)` + cmd = `$(exe) --solver $(dest.solver) --output-objective $time_limit -o $(output) $(filename)` return run(pipeline(cmd, stdout = _stdout)) end if isfile(output) @@ -90,6 +105,7 @@ function MOI.empty!(model::Optimizer{T}) where {T} model.solver_status = "" model.primal_objective = zero(T) empty!(model.primal_solution) + model.solve_time_sec = NaN return end @@ -106,6 +122,20 @@ function MOI.set(model::Optimizer, attr::MOI.RawOptimizerAttribute, value) return end +MOI.supports(::Optimizer, ::MOI.TimeLimitSec) = true + +MOI.get(model::Optimizer, ::MOI.TimeLimitSec) = model.time_limit_sec + +function MOI.set(model::Optimizer, ::MOI.TimeLimitSec, value::Real) + model.time_limit_sec = convert(Float64, value) + return +end + +function MOI.set(model::Optimizer, ::MOI.TimeLimitSec, ::Nothing) + model.time_limit_sec = nothing + return +end + function MOI.supports_constraint( model::Optimizer, ::Type{F}, @@ -129,6 +159,7 @@ function _parse_result(::Type{T}, s::AbstractString) where {T} end function MOI.optimize!(dest::Optimizer{T}, src::MOI.ModelLike) where {T} + time_start = time() MOI.empty!(dest.inner) empty!(dest.primal_solution) index_map = MOI.copy_to(dest.inner, src) @@ -157,6 +188,7 @@ function MOI.optimize!(dest::Optimizer{T}, src::MOI.ModelLike) where {T} end end end + dest.solve_time_sec = time() - time_start return index_map, false end From 681e5b271b65aa6a184d4a2764b1e1df5b3b4886 Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 19 Sep 2023 12:42:02 +1200 Subject: [PATCH 2/5] Update --- src/optimize.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/optimize.jl b/src/optimize.jl index 3dacfd5..13f7434 100644 --- a/src/optimize.jl +++ b/src/optimize.jl @@ -241,3 +241,5 @@ function MOI.get( MOI.throw_if_not_valid(model, x) return model.primal_solution[x] end + +MOI.get(model::Optimizer, ::MOI.SolveTimeSec) = model.solve_time_sec From daaa2179b5e040d1d6770da83afc3580984dca8f Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 19 Sep 2023 12:50:04 +1200 Subject: [PATCH 3/5] update --- src/optimize.jl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/optimize.jl b/src/optimize.jl index 13f7434..6c0e537 100644 --- a/src/optimize.jl +++ b/src/optimize.jl @@ -77,12 +77,13 @@ function _run_minizinc(dest::Optimizer) end output = joinpath(dir, "model.ozn") _stdout = joinpath(dir, "_stdout.txt") - time_limit = "" - if dest.time_limit_sec !== nothing - time_limit = "--time-limit $(1_000 * dest.time_limit_sec)" - end _minizinc_exe() do exe - cmd = `$(exe) --solver $(dest.solver) --output-objective $time_limit -o $(output) $(filename)` + cmd = if dest.time_limit_sec !== nothing + limit = 1_000 * dest.time_limit_sec::Float64 + `$(exe) --solver $(dest.solver) --output-objective --time-limit $limit -o $(output) $(filename)` + else + `$(exe) --solver $(dest.solver) --output-objective -o $(output) $(filename)` + end return run(pipeline(cmd, stdout = _stdout)) end if isfile(output) From 7fe5629ef90f5c63ad55a835a2a6e77940253b61 Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 19 Sep 2023 13:12:45 +1200 Subject: [PATCH 4/5] Add tests --- src/optimize.jl | 2 +- test/runtests.jl | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/optimize.jl b/src/optimize.jl index 6c0e537..359a3d2 100644 --- a/src/optimize.jl +++ b/src/optimize.jl @@ -79,7 +79,7 @@ function _run_minizinc(dest::Optimizer) _stdout = joinpath(dir, "_stdout.txt") _minizinc_exe() do exe cmd = if dest.time_limit_sec !== nothing - limit = 1_000 * dest.time_limit_sec::Float64 + limit = round(Int, 1_000 * dest.time_limit_sec::Float64) `$(exe) --solver $(dest.solver) --output-objective --time-limit $limit -o $(output) $(filename)` else `$(exe) --solver $(dest.solver) --output-objective -o $(output) $(filename)` diff --git a/test/runtests.jl b/test/runtests.jl index ce0e6c1..15b8d1b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1410,6 +1410,32 @@ function test_highs_optimization() return end +function test_time_limit_sec() + solver = MiniZinc.Optimizer{Int}("highs") + @test MOI.supports(solver, MOI.TimeLimitSec()) + @test MOI.get(solver, MOI.TimeLimitSec()) === nothing + MOI.set(solver, MOI.TimeLimitSec(), 1) + @test MOI.get(solver, MOI.TimeLimitSec()) == 1.0 + MOI.set(solver, MOI.TimeLimitSec(), nothing) + @test MOI.get(solver, MOI.TimeLimitSec()) === nothing + return +end + +function test_highs_optimization_time_limit() + model = MOI.Utilities.Model{Float64}() + x, _ = MOI.add_constrained_variable(model, MOI.Integer()) + MOI.add_constraint(model, x, MOI.Interval(1.0, 10.0)) + MOI.set(model, MOI.ObjectiveFunction{typeof(x)}(), x) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + solver = MiniZinc.Optimizer{Float64}("highs") + @test isnan(MOI.get(solver, MOI.SolveTimeSec())) + MOI.set(solver, MOI.TimeLimitSec(), 9e-4) + index_map, _ = MOI.optimize!(solver, model) + @test !isnan(MOI.get(solver, MOI.SolveTimeSec())) + @test MOI.get(solver, MOI.TerminationStatus()) === MOI.OPTIMAL + return +end + end TestMiniZinc.runtests() From b7cc11505f18f9359ab7e8a7b79c30d578aa4a8c Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Tue, 19 Sep 2023 14:29:32 +1200 Subject: [PATCH 5/5] Update runtests.jl --- test/runtests.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index 15b8d1b..65e83d6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1429,9 +1429,13 @@ function test_highs_optimization_time_limit() MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) solver = MiniZinc.Optimizer{Float64}("highs") @test isnan(MOI.get(solver, MOI.SolveTimeSec())) - MOI.set(solver, MOI.TimeLimitSec(), 9e-4) + MOI.set(solver, MOI.TimeLimitSec(), 9e-4) # Very small limit index_map, _ = MOI.optimize!(solver, model) @test !isnan(MOI.get(solver, MOI.SolveTimeSec())) + @test MOI.get(solver, MOI.TerminationStatus()) === MOI.OTHER_ERROR + solver = MiniZinc.Optimizer{Float64}("highs") + MOI.set(solver, MOI.TimeLimitSec(), 100) + index_map, _ = MOI.optimize!(solver, model) @test MOI.get(solver, MOI.TerminationStatus()) === MOI.OPTIMAL return end