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
37 changes: 18 additions & 19 deletions src/Test/Test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ mutable struct Config{T<:Real}
rtol::T
supports_optimize::Bool
optimal_status::MOI.TerminationStatusCode
excluded_attributes::Vector{Any}
exclude::Vector{Any}
end

"""
Expand All @@ -23,7 +23,7 @@ end
rtol::Real = Base.rtoldefault(T),
supports_optimize::Bool = true,
optimal_status::MOI.TerminationStatusCode = MOI.OPTIMAL,
excluded_attributes::Vector{Any} = Any[],
exclude::Vector{Any} = Any[],
) where {T}

Return an object that is used to configure various tests.
Expand All @@ -38,6 +38,12 @@ Return an object that is used to configure various tests.
call to [`MOI.optimize!`](@ref)
* `optimal_status = MOI.OPTIMAL`: Set to `MOI.LOCALLY_SOLVED` if the solver
cannot prove global optimality.
* `exclude = Vector{Any}`: Pass attributes or functions to `exclude` to skip
parts of tests that require certain functionality. Common arguments include:
- `MOI.delete` to skip deletion-related tests
- `MOI.ConstraintDual` to skip dual-related tests
- `MOI.VariableName` to skip setting variable names
- `MOI.ConstraintName` to skip setting constraint names

## Examples

Expand All @@ -47,10 +53,11 @@ dual variables or constraint names:
Config(
Float64;
optimal_status = MOI.LOCALLY_SOLVED,
excluded_attributes = Any[
MOI.ConstraintDual(),
MOI.VariableName(),
MOI.ConstraintName(),
exclude = Any[
MOI.ConstraintDual,
MOI.VariableName,
MOI.ConstraintName,
MOI.delete,
],
)
```
Expand All @@ -61,15 +68,9 @@ function Config(
rtol::Real = Base.rtoldefault(T),
supports_optimize::Bool = true,
optimal_status::MOI.TerminationStatusCode = MOI.OPTIMAL,
excluded_attributes::Vector{Any} = Any[],
exclude::Vector{Any} = Any[],
) where {T<:Real}
return Config{T}(
atol,
rtol,
supports_optimize,
optimal_status,
excluded_attributes,
)
return Config{T}(atol, rtol, supports_optimize, optimal_status, exclude)
end

function Base.copy(config::Config{T}) where {T}
Expand All @@ -78,7 +79,7 @@ function Base.copy(config::Config{T}) where {T}
config.rtol,
config.supports_optimize,
config.optimal_status,
copy(config.excluded_attributes),
copy(config.exclude),
)
end

Expand Down Expand Up @@ -274,14 +275,12 @@ This is helpful when writing tests.
## Example

```julia
if MOI.Test._supports(config, MOI.Silent())
if MOI.Test._supports(config, MOI.Silent)
@test MOI.get(model, MOI.Silent()) == true
end
```
"""
function _supports(config::Config, attribute::MOI.AnyAttribute)
return !(attribute in config.excluded_attributes)
end
_supports(config::Config, T::Any)::Bool = !(T in config.exclude)

"""
_test_model_solution(
Expand Down
6 changes: 3 additions & 3 deletions src/Test/test_attribute.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Test that the [`MOI.RawStatusString`](@ref) attribute is implemented for
`model`.
"""
function test_attribute_RawStatusString(model::MOI.ModelLike, config::Config)
if !config.supports_optimize || !_supports(config, MOI.RawStatusString())
if !config.supports_optimize || !_supports(config, MOI.RawStatusString)
return
end
MOI.add_variable(model)
Expand Down Expand Up @@ -94,7 +94,7 @@ end
Test that the [`MOI.SolverName`](@ref) attribute is implemented for `model`.
"""
function test_attribute_SolverName(model::MOI.ModelLike, config::Config)
if _supports(config, MOI.SolverName())
if _supports(config, MOI.SolverName)
@test MOI.get(model, MOI.SolverName()) isa AbstractString
end
return
Expand All @@ -106,7 +106,7 @@ end
Test that the [`MOI.SolveTimeSec`](@ref) attribute is implemented for `model`.
"""
function test_attribute_SolveTimeSec(model::MOI.ModelLike, config::Config)
if !config.supports_optimize || !_supports(config, MOI.SolveTimeSec())
if !config.supports_optimize || !_supports(config, MOI.SolveTimeSec)
return
end
MOI.add_variable(model)
Expand Down
265 changes: 265 additions & 0 deletions src/Test/test_basic_constraint.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
function _function(
::Any,
::Type{MOI.SingleVariable},
x::Vector{MOI.VariableIndex},
)
return MOI.SingleVariable(x[1])
end

function _function(
::Any,
::Type{MOI.VectorOfVariables},
x::Vector{MOI.VariableIndex},
)
return MOI.VectorOfVariables(x)
end

function _function(
::Type{T},
::Type{MOI.ScalarAffineFunction},
x::Vector{MOI.VariableIndex},
) where {T}
return MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(one(T), x), zero(T))
end

function _function(
::Type{T},
::Type{MOI.ScalarQuadraticFunction},
x::Vector{MOI.VariableIndex},
) where {T}
return MOI.ScalarQuadraticFunction(
MOI.ScalarAffineTerm.(one(T), x),
MOI.ScalarQuadraticTerm.(one(T), x, x),
zero(T),
)
end

function _function(
::Type{T},
::Type{MOI.VectorAffineFunction},
x::Vector{MOI.VariableIndex},
) where {T}
return MOI.VectorAffineFunction(
MOI.VectorAffineTerm.(1:length(x), MOI.ScalarAffineTerm.(one(T), x)),
zeros(T, length(x)),
)
end

function _function(
::Type{T},
::Type{MOI.VectorQuadraticFunction},
x::Vector{MOI.VariableIndex},
) where {T}
return MOI.VectorQuadraticFunction(
MOI.VectorAffineTerm.(1:length(x), MOI.ScalarAffineTerm.(one(T), x)),
MOI.VectorQuadraticTerm.(
1:length(x),
MOI.ScalarQuadraticTerm.(one(T), x, x),
),
zeros(T, length(x)),
)
end

# Default fallback.
_set(::Any, ::Type{S}) where {S} = _set(S)

_set(::Type{T}, ::Type{MOI.LessThan}) where {T} = MOI.LessThan(one(T))
_set(::Type{T}, ::Type{MOI.GreaterThan}) where {T} = MOI.GreaterThan(one(T))
_set(::Type{T}, ::Type{MOI.EqualTo}) where {T} = MOI.EqualTo(one(T))
_set(::Type{T}, ::Type{MOI.Interval}) where {T} = MOI.Interval(zero(T), one(T))
_set(::Type{MOI.ZeroOne}) = MOI.ZeroOne()
_set(::Type{MOI.Integer}) = MOI.Integer()
function _set(::Type{T}, ::Type{MOI.Semicontinuous}) where {T}
return MOI.Semicontinuous(zero(T), one(T))
end
function _set(::Type{T}, ::Type{MOI.Semiinteger}) where {T}
return MOI.Semiinteger(zero(T), one(T))
end
_set(::Type{T}, ::Type{MOI.SOS1}) where {T} = MOI.SOS1(convert.(T, 1:2))
_set(::Type{T}, ::Type{MOI.SOS2}) where {T} = MOI.SOS2(convert.(T, 1:2))
_set(::Type{MOI.Zeros}) = MOI.Zeros(2)
_set(::Type{MOI.Nonpositives}) = MOI.Nonpositives(2)
_set(::Type{MOI.Nonnegatives}) = MOI.Nonnegatives(2)
_set(::Type{MOI.NormInfinityCone}) = MOI.NormInfinityCone(3)
_set(::Type{MOI.NormOneCone}) = MOI.NormOneCone(3)
_set(::Type{MOI.SecondOrderCone}) = MOI.SecondOrderCone(3)
_set(::Type{MOI.RotatedSecondOrderCone}) = MOI.RotatedSecondOrderCone(3)
_set(::Type{MOI.GeometricMeanCone}) = MOI.GeometricMeanCone(3)
_set(::Type{MOI.ExponentialCone}) = MOI.ExponentialCone()
_set(::Type{MOI.DualExponentialCone}) = MOI.DualExponentialCone()
_set(::Type{MOI.PowerCone}) = MOI.PowerCone(0.5)
_set(::Type{MOI.DualPowerCone}) = MOI.DualPowerCone(0.5)
_set(::Type{MOI.RelativeEntropyCone}) = MOI.RelativeEntropyCone(3)
_set(::Type{MOI.NormSpectralCone}) = MOI.NormSpectralCone(2, 3)
_set(::Type{MOI.NormNuclearCone}) = MOI.NormNuclearCone(2, 3)
function _set(::Type{MOI.PositiveSemidefiniteConeTriangle})
return MOI.PositiveSemidefiniteConeTriangle(3)
end
function _set(::Type{MOI.PositiveSemidefiniteConeSquare})
return MOI.PositiveSemidefiniteConeSquare(3)
end
_set(::Type{MOI.LogDetConeTriangle}) = MOI.LogDetConeTriangle(3)
_set(::Type{MOI.LogDetConeSquare}) = MOI.LogDetConeSquare(3)
_set(::Type{MOI.RootDetConeTriangle}) = MOI.RootDetConeTriangle(3)
_set(::Type{MOI.RootDetConeSquare}) = MOI.RootDetConeSquare(3)
_set(::Type{MOI.Complements}) = MOI.Complements(2)

# MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(3.0)
# MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}(MOI.GreaterThan(3.0)

function _basic_constraint_test_helper(
model::MOI.ModelLike,
config::Config{T},
::Type{UntypedF},
::Type{UntypedS},
) where {T,UntypedF,UntypedS}
set = _set(T, UntypedS)
N = MOI.dimension(set)
x = MOI.add_variables(model, N)
constraint_function = _function(T, UntypedF, x)
F, S = typeof(constraint_function), typeof(set)
###
### Test MOI.supports_constraint
###
@requires MOI.supports_constraint(model, F, S)
###
### Test MOI.NumberOfConstraints
###
@test MOI.get(model, MOI.NumberOfConstraints{F,S}()) == 0
c = MOI.add_constraint(model, constraint_function, set)
@test MOI.get(model, MOI.NumberOfConstraints{F,S}()) == 1
###
### Test MOI.is_valid
###
@test MOI.is_valid(model, c)
###
### Test MOI.ConstraintName
###
if _supports(config, MOI.ConstraintName)
if F == MOI.SingleVariable
@test_throws(
MOI.SingleVariableConstraintNameError(),
MOI.supports(model, MOI.ConstraintName(), typeof(c)),
)
@test_throws(
MOI.SingleVariableConstraintNameError(),
MOI.set(model, MOI.ConstraintName(), c, "c"),
)
else
@test MOI.get(model, MOI.ConstraintName(), c) == ""
@test MOI.supports(model, MOI.ConstraintName(), typeof(c))
MOI.set(model, MOI.ConstraintName(), c, "c")
@test MOI.get(model, MOI.ConstraintName(), c) == "c"
end
end
###
### Test MOI.ConstraintFunction
###
if _supports(config, MOI.ConstraintFunction)
@test MOI.get(model, MOI.ConstraintFunction(), c) ≈ constraint_function
end
###
### Test MOI.ConstraintSet
###
if _supports(config, MOI.ConstraintSet)
@test MOI.get(model, MOI.ConstraintSet(), c) == set
end
###
### Test MOI.ListOfConstraintIndices
###
c_indices = MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
@test length(c_indices) == 1
###
### Test MOI.add_constraints
###
if F != MOI.SingleVariable && F != MOI.VectorOfVariables
# We can't add multiple variable constraints as these are
# interpreted as bounds etc.
MOI.add_constraints(
model,
[constraint_function, constraint_function],
[set, set],
)
@test MOI.get(model, MOI.NumberOfConstraints{F,S}()) == 3
@test length(MOI.get(model, MOI.ListOfConstraintIndices{F,S}())) == 3
c_indices = MOI.get(model, MOI.ListOfConstraintIndices{F,S}())
@test all(MOI.is_valid.(model, c_indices))
end
###
### Test MOI.delete
###
if _supports(config, MOI.delete)
MOI.delete(model, c_indices[1])
@test MOI.get(model, MOI.NumberOfConstraints{F,S}()) ==
length(c_indices) - 1
@test !MOI.is_valid(model, c_indices[1])
@test_throws(
MOI.InvalidIndex(c_indices[1]),
MOI.delete(model, c_indices[1]),
)
if _supports(config, MOI.ConstraintFunction)
@test_throws(
MOI.InvalidIndex(c_indices[1]),
MOI.get(model, MOI.ConstraintFunction(), c_indices[1]),
)
end
if _supports(config, MOI.ConstraintSet)
@test_throws(
MOI.InvalidIndex(c_indices[1]),
MOI.get(model, MOI.ConstraintSet(), c_indices[1]),
)
end
end
return
end

for s in [
:GreaterThan,
:LessThan,
:EqualTo,
:Interval,
:ZeroOne,
:Semicontinuous,
:Semiinteger,
:SOS1,
:SOS2,
:Zeros,
:Nonpositives,
:Nonnegatives,
:NormInfinityCone,
:NormOneCone,
:SecondOrderCone,
:RotatedSecondOrderCone,
:GeometricMeanCone,
:ExponentialCone,
:DualExponentialCone,
:PowerCone,
:DualPowerCone,
:RelativeEntropyCone,
:NormSpectralCone,
:NormNuclearCone,
:PositiveSemidefiniteConeSquare,
:PositiveSemidefiniteConeTriangle,
:LogDetConeTriangle,
:LogDetConeSquare,
:RootDetConeTriangle,
:RootDetConeSquare,
:Complements,
]
S = getfield(MOI, s)
functions = if S <: MOI.AbstractScalarSet
(:SingleVariable, :ScalarAffineFunction, :ScalarQuadraticFunction)
else
(:VectorOfVariables, :VectorAffineFunction, :VectorQuadraticFunction)
end
for f in functions
func = Symbol("test_basic_$(f)_$(s)")
F = getfield(MOI, f)
@eval begin
function $(func)(model::MOI.ModelLike, config::Config)
_basic_constraint_test_helper(model, config, $F, $S)
return
end
end
end
end
4 changes: 1 addition & 3 deletions src/Test/test_variable.jl
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,7 @@ end
Test getting variables by name.
"""
function test_variable_get_VariableIndex(model::MOI.ModelLike, config::Config)
if !_supports(config, MOI.VariableName())
return
end
@requires MOI.supports(model, MOI.VariableName(), MOI.VariableIndex)
variable = MOI.add_variable(model)
MOI.set(model, MOI.VariableName(), variable, "x")
x = MOI.get(model, MOI.VariableIndex, "x")
Expand Down