diff --git a/src/optimize.jl b/src/optimize.jl index 2a5f6d0..359a3d2 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 @@ -67,7 +78,12 @@ function _run_minizinc(dest::Optimizer) output = joinpath(dir, "model.ozn") _stdout = joinpath(dir, "_stdout.txt") _minizinc_exe() do exe - cmd = `$(exe) --solver $(dest.solver) --output-objective -o $(output) $(filename)` + cmd = if dest.time_limit_sec !== nothing + 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)` + end return run(pipeline(cmd, stdout = _stdout)) end if isfile(output) @@ -90,6 +106,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 +123,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 +160,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 +189,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 @@ -209,3 +242,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 diff --git a/test/runtests.jl b/test/runtests.jl index ce0e6c1..65e83d6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1410,6 +1410,36 @@ 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) # 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 + end TestMiniZinc.runtests()