diff --git a/docs/src/manual/models.md b/docs/src/manual/models.md index 1a4e5ba3bb..f31ba77f1e 100644 --- a/docs/src/manual/models.md +++ b/docs/src/manual/models.md @@ -101,3 +101,4 @@ The following attributes are available: * [`SolveTimeSec`](@ref) * [`TimeLimitSec`](@ref) * [`ObjectiveLimit`](@ref) + * [`SolutionLimit`](@ref) diff --git a/docs/src/reference/models.md b/docs/src/reference/models.md index c36eafebfd..1429c51a65 100644 --- a/docs/src/reference/models.md +++ b/docs/src/reference/models.md @@ -78,6 +78,7 @@ SolverVersion Silent TimeLimitSec ObjectiveLimit +SolutionLimit RawOptimizerAttribute NumberOfThreads RawSolver diff --git a/docs/src/tutorials/implementing.md b/docs/src/tutorials/implementing.md index 49976d119d..ec859d118f 100644 --- a/docs/src/tutorials/implementing.md +++ b/docs/src/tutorials/implementing.md @@ -334,6 +334,7 @@ method for each attribute. | [`Silent`](@ref) | Yes | Yes | Yes | | [`TimeLimitSec`](@ref) | Yes | Yes | Yes | | [`ObjectiveLimit`](@ref) | Yes | Yes | Yes | +| [`SolutionLimit`](@ref) | Yes | Yes | Yes | | [`RawOptimizerAttribute`](@ref) | Yes | Yes | Yes | | [`NumberOfThreads`](@ref) | Yes | Yes | Yes | | [`AbsoluteGapTolerance`](@ref) | Yes | Yes | Yes | diff --git a/src/Test/test_attribute.jl b/src/Test/test_attribute.jl index 300020492d..ed388d04c0 100644 --- a/src/Test/test_attribute.jl +++ b/src/Test/test_attribute.jl @@ -216,6 +216,32 @@ function setup_test( return end +function test_attribute_SolutionLimit(model::MOI.AbstractOptimizer, ::Config) + @requires MOI.supports(model, MOI.SolutionLimit()) + # Get the current value to restore it at the end of the test + value = MOI.get(model, MOI.SolutionLimit()) + MOI.set(model, MOI.SolutionLimit(), 3) + @test MOI.get(model, MOI.SolutionLimit()) == 3 + MOI.set(model, MOI.SolutionLimit(), nothing) + @test MOI.get(model, MOI.SolutionLimit()) === nothing + MOI.set(model, MOI.SolutionLimit(), 1) + @test MOI.get(model, MOI.SolutionLimit()) == 1 + MOI.set(model, MOI.SolutionLimit(), value) + _test_attribute_value_type(model, MOI.SolutionLimit()) + return +end + +test_attribute_SolutionLimit(::MOI.ModelLike, ::Config) = nothing + +function setup_test( + ::typeof(test_attribute_SolutionLimit), + model::MOIU.MockOptimizer, + ::Config, +) + MOI.set(model, MOI.SolutionLimit(), nothing) + return +end + """ test_attribute_AbsoluteGapTolerance(model::MOI.AbstractOptimizer, config::Config) diff --git a/src/attributes.jl b/src/attributes.jl index 454164a9ac..7331d0f363 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -867,6 +867,35 @@ the [`TerminationStatus`](@ref) should be `OBJECTIVE_LIMIT`. """ struct ObjectiveLimit <: AbstractOptimizerAttribute end +""" + SolutionLimit() + +An optimizer attribute for setting a limit on the number of available feasible +solutions. + +## Default values + +The provided limit must be a `Union{Nothing,Int}`. + +When `set` to `nothing`, the limit reverts to the solver's default. + +The default value is `nothing`. + +## Termination criteria + +The solver may stop when the [`ResultCount`](@ref) is larger than or equal to +the `SolutionLimit`. If stopped because of this attribute, the +[`TerminationStatus`](@ref) must be `SOLUTION_LIMIT`. + +## Solution quality + +The quality of the available solutions is solver-dependent. The set of resulting +solutions is not guaranteed to contain an optimal solution. +""" +struct SolutionLimit <: AbstractOptimizerAttribute end + +attribute_value_type(::SolutionLimit) = Union{Nothing,Int} + """ RawOptimizerAttribute(name::String)