From 1bee9e649bac264f97a0ea23060e34304d504ed9 Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 2 Sep 2021 11:31:33 +1200 Subject: [PATCH] [Test] add indicator tests from Gurobi.jl --- src/Test/test_constraint.jl | 105 ++++++++++++++++++++++++++++++++++++ src/sets.jl | 37 +++++++------ 2 files changed, 127 insertions(+), 15 deletions(-) diff --git a/src/Test/test_constraint.jl b/src/Test/test_constraint.jl index 5251dabc53..7b315a3933 100644 --- a/src/Test/test_constraint.jl +++ b/src/Test/test_constraint.jl @@ -764,3 +764,108 @@ function test_constraint_ConstraintDualStart( @test MOI.get(model, MOI.ConstraintDualStart(), c) === nothing return end + +""" + test_constraint_Indicator_ConstraintName( + model::MOI.ModelLike, + ::Config{T}, + ) where {T} + +Test ConstraintName for indicator sets. +""" +function test_constraint_Indicator_ConstraintName( + model::MOI.ModelLike, + ::Config{T}, +) where {T} + @requires( + MOI.supports_constraint( + model, + MOI.VectorAffineFunction{T}, + MOI.Indicator{MOI.ACTIVATE_ON_ONE,MOI.GreaterThan{T}}, + ), + ) + x = MOI.add_variables(model, 2) + MOI.add_constraint(model, x[1], MOI.ZeroOne()) + f = MOI.VectorAffineFunction( + [ + MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x[1])), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(2.0, x[2])), + ], + [0.0, 0.0], + ) + s = MOI.Indicator{MOI.ACTIVATE_ON_ONE}(MOI.GreaterThan(1.0)) + c = MOI.add_constraint(model, f, s) + MOI.set(model, MOI.ConstraintName(), c, "my_indicator") + @test MOI.get(model, MOI.ConstraintName(), c) == "my_indicator" + return +end + +""" + test_constraint_Indicator_ACTIVATE_ON_ONE( + model::MOI.ModelLike, + ::Config{T}, + ) where {T} + +Test ACTIVATE_ON_ONE for indicator sets. +""" +function test_constraint_Indicator_ACTIVATE_ON_ONE( + model::MOI.ModelLike, + ::Config{T}, +) where {T} + @requires( + MOI.supports_constraint( + model, + MOI.VectorAffineFunction{T}, + MOI.Indicator{MOI.ACTIVATE_ON_ONE,MOI.GreaterThan{T}}, + ), + ) + x = MOI.add_variables(model, 2) + MOI.add_constraint(model, x[1], MOI.ZeroOne()) + f = MOI.VectorAffineFunction( + [ + MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x[1])), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(2.0, x[2])), + ], + [0.0, 0.0], + ) + s = MOI.Indicator{MOI.ACTIVATE_ON_ONE}(MOI.GreaterThan(1.0)) + c = MOI.add_constraint(model, f, s) + @test MOI.get(model, MOI.ConstraintSet(), c) == s + @test isapprox(MOI.get(model, MOI.ConstraintFunction(), c), f) + return +end + +""" + test_constraint_Indicator_ACTIVATE_ON_ZERO( + model::MOI.ModelLike, + ::Config{T}, + ) where {T} + +Test ACTIVATE_ON_ZERO for indicator sets. +""" +function test_constraint_Indicator_ACTIVATE_ON_ZERO( + model::MOI.ModelLike, + ::Config{T}, +) where {T} + @requires( + MOI.supports_constraint( + model, + MOI.VectorAffineFunction{T}, + MOI.Indicator{MOI.ACTIVATE_ON_ONE,MOI.GreaterThan{T}}, + ), + ) + x = MOI.add_variables(model, 2) + MOI.add_constraint(model, x[1], MOI.ZeroOne()) + f = MOI.VectorAffineFunction( + [ + MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, x[1])), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(2.0, x[2])), + ], + [0.0, 0.0], + ) + s = MOI.Indicator{MOI.ACTIVATE_ON_ZERO}(MOI.GreaterThan(1.0)) + c = MOI.add_constraint(model, f, s) + @test MOI.get(model, MOI.ConstraintSet(), c) == s + @test isapprox(MOI.get(model, MOI.ConstraintFunction(), c), f) + return +end diff --git a/src/sets.jl b/src/sets.jl index 9485006643..ec193580b0 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -986,6 +986,7 @@ dimension(s::Union{SOS1,SOS2}) = length(s.weights) ActivationCondition Activation condition for an indicator constraint. + The enum value is used as first type parameter of `Indicator{A,S}`. """ @enum ActivationCondition begin @@ -994,32 +995,38 @@ The enum value is used as first type parameter of `Indicator{A,S}`. end """ - Indicator{A, S <: AbstractScalarSet}(set::S) + Indicator{A<:ActivationCondition,S<:AbstractScalarSet}(set::S) + +The set corresponding to an indicator constraint. +When `A` is `ACTIVATE_ON_ZERO`, this means: ``\\{(y, x) \\in \\{0, 1\\} \\times \\mathbb{R}^n : y = 0 \\implies x \\in set\\}`` -when `A` is `ACTIVATE_ON_ZERO` and + +When `A` is `ACTIVATE_ON_ONE`, this means: ``\\{(y, x) \\in \\{0, 1\\} \\times \\mathbb{R}^n : y = 1 \\implies x \\in set\\}`` -when `A` is `ACTIVATE_ON_ONE`. -`S` has to be a sub-type of `AbstractScalarSet`. -`A` is one of the value of the `ActivationCond` enum. -`Indicator` is used with a `VectorAffineFunction` holding -the indicator variable first. +## Notes -Example: ``\\{(y, x) \\in \\{0, 1\\} \\times \\mathbb{R}^2 : y = 1 \\implies x_1 + x_2 \\leq 9 \\} `` +Most solvers expect that the first row of the function is interpretable as a +variable index `x_i` (e.g., `1.0 * x + 0.0`). An error will be thrown if this is +not the case. +## Example + +The constraint +``\\{(y, x) \\in \\{0, 1\\} \\times \\mathbb{R}^2 : y = 1 \\implies x_1 + x_2 \\leq 9 \\}`` +is defined as ```julia f = MOI.VectorAffineFunction( - [MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, y)), - MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x1)), - MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x2)), + [ + MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, y)), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x1)), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x2)), ], [0.0, 0.0], ) - -indicator_set = MOI.Indicator{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(9.0)) - -MOI.add_constraint(model, f, indicator_set) +s = MOI.Indicator{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(9.0)) +MOI.add_constraint(model, f, s) ``` """ struct Indicator{A,S<:AbstractScalarSet} <: AbstractVectorSet