Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/src/manual/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,4 @@ The following attributes are available:
* [`SolverVersion`](@ref)
* [`SolveTimeSec`](@ref)
* [`TimeLimitSec`](@ref)
* [`ObjectiveLimit`](@ref)
1 change: 1 addition & 0 deletions docs/src/reference/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ SolverName
SolverVersion
Silent
TimeLimitSec
ObjectiveLimit
RawOptimizerAttribute
NumberOfThreads
RawSolver
Expand Down
1 change: 1 addition & 0 deletions docs/src/tutorials/implementing.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ method for each attribute.
| [`Name`](@ref) | Yes | Yes | Yes |
| [`Silent`](@ref) | Yes | Yes | Yes |
| [`TimeLimitSec`](@ref) | Yes | Yes | Yes |
| [`ObjectiveLimit`](@ref) | Yes | Yes | Yes |
| [`RawOptimizerAttribute`](@ref) | Yes | Yes | Yes |
| [`NumberOfThreads`](@ref) | Yes | Yes | Yes |
| [`AbsoluteGapTolerance`](@ref) | Yes | Yes | Yes |
Expand Down
26 changes: 26 additions & 0 deletions src/Test/test_attribute.jl
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,32 @@ function setup_test(
return
end

function test_attribute_ObjectiveLimit(model::MOI.AbstractOptimizer, ::Config)
@requires MOI.supports(model, MOI.ObjectiveLimit())
# Get the current value to restore it at the end of the test
value = MOI.get(model, MOI.ObjectiveLimit())
MOI.set(model, MOI.ObjectiveLimit(), 0.0)
@test MOI.get(model, MOI.ObjectiveLimit()) == 0.0
MOI.set(model, MOI.ObjectiveLimit(), nothing)
@test MOI.get(model, MOI.ObjectiveLimit()) === nothing
MOI.set(model, MOI.ObjectiveLimit(), 1.0)
@test MOI.get(model, MOI.ObjectiveLimit()) == 1.0
MOI.set(model, MOI.ObjectiveLimit(), value)
@test value == MOI.get(model, MOI.ObjectiveLimit()) # Equality should hold
_test_attribute_value_type(model, MOI.ObjectiveLimit())
return
end
test_attribute_ObjectiveLimit(::MOI.ModelLike, ::Config) = nothing

function setup_test(
::typeof(test_attribute_ObjectiveLimit),
model::MOIU.MockOptimizer,
::Config,
)
MOI.set(model, MOI.ObjectiveLimit(), nothing)
return
end

"""
test_attribute_AbsoluteGapTolerance(model::MOI.AbstractOptimizer, config::Config)

Expand Down
17 changes: 17 additions & 0 deletions src/attributes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,23 @@ struct TimeLimitSec <: AbstractOptimizerAttribute end

attribute_value_type(::TimeLimitSec) = Union{Nothing,Float64}

"""
ObjectiveLimit()

An optimizer attribute for setting a limit on the objective value.

The provided limit must be a `Union{Real,Nothing}`.

When `set` to `nothing`, the limit reverts to the solver's default.

The default value is `nothing`.

The solver may stop when the [`ObjectiveValue`](@ref) is better (lower for
minimization, higher for maximization) than the `ObjectiveLimit`. If stopped,
the [`TerminationStatus`](@ref) should be `OBJECTIVE_LIMIT`.
"""
struct ObjectiveLimit <: AbstractOptimizerAttribute end

"""
RawOptimizerAttribute(name::String)

Expand Down
9 changes: 9 additions & 0 deletions test/Utilities/cachingoptimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ function test_default_attributes()
MOI.SolverName(),
MOI.Silent(),
MOI.TimeLimitSec(),
MOI.ObjectiveLimit(),
MOI.NumberOfThreads(),
MOI.ResultCount(),
)
Expand All @@ -209,22 +210,28 @@ function test_copyable_solver_attributes()
cached = MOIU.CachingOptimizer(cache, MOIU.MANUAL)
MOI.set(cached, MOI.Silent(), true)
MOI.set(cached, MOI.TimeLimitSec(), 0.0)
MOI.set(cached, MOI.ObjectiveLimit(), 42.0)
MOI.set(cached, MOI.NumberOfThreads(), 1)
mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}()))
MOIU.reset_optimizer(cached, mock)
@test MOI.get(mock, MOI.Silent())
@test MOI.get(cached, MOI.Silent())
@test MOI.get(mock, MOI.TimeLimitSec()) == 0.0
@test MOI.get(cached, MOI.TimeLimitSec()) == 0.0
@test MOI.get(mock, MOI.ObjectiveLimit()) == 42.0
@test MOI.get(cached, MOI.ObjectiveLimit()) == 42.0
@test MOI.get(mock, MOI.NumberOfThreads()) == 1
@test MOI.get(cached, MOI.NumberOfThreads()) == 1
MOI.set(cached, MOI.Silent(), false)
MOI.set(cached, MOI.TimeLimitSec(), 1.0)
MOI.set(cached, MOI.ObjectiveLimit(), 1.0)
MOI.set(cached, MOI.NumberOfThreads(), 2)
@test !MOI.get(mock, MOI.Silent())
@test !MOI.get(cached, MOI.Silent())
@test MOI.get(mock, MOI.TimeLimitSec()) ≈ 1.0
@test MOI.get(mock, MOI.ObjectiveLimit()) ≈ 1.0
@test MOI.get(cached, MOI.TimeLimitSec()) ≈ 1.0
@test MOI.get(cached, MOI.ObjectiveLimit()) ≈ 1.0
@test MOI.get(mock, MOI.NumberOfThreads()) == 2
@test MOI.get(cached, MOI.NumberOfThreads()) == 2
mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}()))
Expand All @@ -233,6 +240,8 @@ function test_copyable_solver_attributes()
@test !MOI.get(cached, MOI.Silent())
@test MOI.get(mock, MOI.TimeLimitSec()) ≈ 1.0
@test MOI.get(cached, MOI.TimeLimitSec()) ≈ 1.0
@test MOI.get(mock, MOI.ObjectiveLimit()) ≈ 1.0
@test MOI.get(cached, MOI.ObjectiveLimit()) ≈ 1.0
@test MOI.get(mock, MOI.NumberOfThreads()) == 2
@test MOI.get(cached, MOI.NumberOfThreads()) == 2
MOI.set(cached, MOI.Silent(), true)
Expand Down
1 change: 1 addition & 0 deletions test/Utilities/universalfallback.jl
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ function test_missing_attribute()
model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
@test MOI.get(model, MOI.Test.UnknownModelAttribute()) === nothing
@test MOI.get(model, MOI.TimeLimitSec()) === nothing
@test MOI.get(model, MOI.ObjectiveLimit()) === nothing
return
end

Expand Down