Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Indicator constraint support #709

Merged
merged 35 commits into from
Apr 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
049e4cb
first draft indicator cons
matbesancon Apr 11, 2019
a550983
fixed docstring
matbesancon Apr 11, 2019
d17ba8e
fixed LessThan
matbesancon Apr 11, 2019
3bce12e
fix import
matbesancon Apr 11, 2019
2ca54d9
last SmallerThan
matbesancon Apr 12, 2019
c3c2fa6
copy correct impl
matbesancon Apr 12, 2019
6e57763
generic vec
matbesancon Apr 12, 2019
9db213a
Revert "generic vec"
matbesancon Apr 12, 2019
9c0e8b6
changed dim and doc
matbesancon Apr 12, 2019
255c409
ActiveCond type parameter
matbesancon Apr 13, 2019
d741b61
enums + constraint
matbesancon Apr 14, 2019
232d5bf
capital enum values
matbesancon Apr 15, 2019
f940ea8
relax type constraint on S
matbesancon Apr 16, 2019
6ce7a65
latex escaping, naming
matbesancon Apr 17, 2019
18e9168
explicit set name
matbesancon Apr 18, 2019
8621773
test 1 indicator
matbesancon Apr 18, 2019
c10e1b9
penalized version
matbesancon Apr 18, 2019
e7bb25b
add reference, fixed doc, tests
matbesancon Apr 19, 2019
4d889be
Merge branch 'master' into indicator-cons
matbesancon Apr 19, 2019
ab42f21
fix test formatting and errors
matbesancon Apr 23, 2019
8da75e0
fix conflict
matbesancon Apr 23, 2019
d15fd9a
test in main, example usage
matbesancon Apr 23, 2019
4bc4d9e
split tests
matbesancon Apr 23, 2019
24af034
fix test 2
matbesancon Apr 23, 2019
f60c3c8
added test
matbesancon Apr 23, 2019
1560400
remove unsupported tests, names
matbesancon Apr 24, 2019
74836af
test passing
matbesancon Apr 24, 2019
e89d734
fix support
matbesancon Apr 24, 2019
d9565d6
fix style
matbesancon Apr 24, 2019
3c0155e
copy implementation and test
matbesancon Apr 24, 2019
c7f926e
fix comments, added test for ACTIVE_ON_ZERO
matbesancon Apr 25, 2019
9d85fee
test support indicator
matbesancon Apr 25, 2019
82d3d56
explicit comment
matbesancon Apr 25, 2019
01f8690
fix conflict
matbesancon Apr 28, 2019
caeb4c4
add mutable test
matbesancon Apr 29, 2019
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
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
Unicode = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[compat]
julia = "1"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]
test = []
1 change: 1 addition & 0 deletions docs/src/apireference.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ Semicontinuous
Semiinteger
SOS1
SOS2
IndicatorSet
```

Functions for getting and setting properties of sets.
Expand Down
235 changes: 234 additions & 1 deletion src/Test/intlinear.jl
Original file line number Diff line number Diff line change
Expand Up @@ -356,9 +356,242 @@ function knapsacktest(model::MOI.ModelLike, config::TestConfig)
end
end

function indicator1_test(model::MOI.ModelLike, config::TestConfig)
atol = config.atol
rtol = config.rtol
# linear problem with indicator constraint
# max 2x1 + 3x2
# s.t. x1 + x2 <= 10
# z1 ==> x2 <= 8
# z2 ==> x2 + x1/5 <= 9
# z1 + z2 >= 1

MOI.empty!(model)
@test MOI.is_empty(model)

@test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}())
@test MOI.supports(model, MOI.ObjectiveSense())
@test MOI.supports_constraint(model, MOI.SingleVariable, MOI.ZeroOne)
@test MOI.supports_constraint(model, MOI.SingleVariable, MOI.Interval{Float64})
@test MOI.supports_constraint(model, MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64})
@test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE, MOI.LessThan{Float64}})
x1 = MOI.add_variable(model)
x2 = MOI.add_variable(model)
z1 = MOI.add_variable(model)
z2 = MOI.add_variable(model)
MOI.add_constraint(model, z1, MOI.ZeroOne())
MOI.add_constraint(model, z2, MOI.ZeroOne())
f1 = MOI.VectorAffineFunction(
[MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, z1)),
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x2)),
],
[0.0, 0.0]
)
iset1 = MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(8.0))
MOI.add_constraint(model, f1, iset1)

f2 = MOI.VectorAffineFunction(
[MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, z2)),
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(0.2, x1)),
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x2)),
],
[0.0, 0.0],
)
iset2 = MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(9.0))

MOI.add_constraint(model, f2, iset2)

# Additional regular constraint.
MOI.add_constraint(model,
MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x1), MOI.ScalarAffineTerm(1.0, x2)], 0.0),
MOI.LessThan(10.0),
)

# Disjunction z1 ⋁ z2
MOI.add_constraint(model,
MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, z1), MOI.ScalarAffineTerm(1.0, z2)], 0.0),
MOI.GreaterThan(1.0),
)

MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(),
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2.0, 3.0], [x1, x2]), 0.0)
)
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)

if config.solve
@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

MOI.optimize!(model)

@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL
@test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
@test MOI.get(model, MOI.ObjectiveValue()) ≈ 28.75 atol=atol rtol=rtol
@test MOI.get(model, MOI.VariablePrimal(), x1) ≈ 1.25 atol=atol rtol=rtol
@test MOI.get(model, MOI.VariablePrimal(), x2) ≈ 8.75 atol=atol rtol=rtol
@test MOI.get(model, MOI.VariablePrimal(), z1) ≈ 0.0 atol=atol rtol=rtol
@test MOI.get(model, MOI.VariablePrimal(), z2) ≈ 1.0 atol=atol rtol=rtol
end
end

function indicator2_test(model::MOI.ModelLike, config::TestConfig)
atol = config.atol
rtol = config.rtol
# linear problem with indicator constraint
# max 2x1 + 3x2 - 30 z2
# s.t. x1 + x2 <= 10
# z1 ==> x2 <= 8
# z2 ==> x2 + x1/5 <= 9
# z1 + z2 >= 1

MOI.empty!(model)
matbesancon marked this conversation as resolved.
Show resolved Hide resolved
@test MOI.is_empty(model)

# This is the same model as indicator_test1, except that the penalty on z2 forces z1 to be 1.

x1 = MOI.add_variable(model)
x2 = MOI.add_variable(model)
z1 = MOI.add_variable(model)
z2 = MOI.add_variable(model)
MOI.add_constraint(model, z1, MOI.ZeroOne())
MOI.add_constraint(model, z2, MOI.ZeroOne())
f1 = MOI.VectorAffineFunction(
[MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, z1)),
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x2)),
],
[0.0, 0.0]
)
iset1 = MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(8.0))
MOI.add_constraint(model, f1, iset1)

f2 = MOI.VectorAffineFunction(
[MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, z2)),
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(0.2, x1)),
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x2)),
],
[0.0, 0.0],
)
iset2 = MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(9.0))

MOI.add_constraint(model, f2, iset2)

# additional regular constraint
MOI.add_constraint(model,
MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x1), MOI.ScalarAffineTerm(1.0, x2)], 0.0),
MOI.LessThan(10.0),
)

# disjunction z1 ⋁ z2
MOI.add_constraint(model,
MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, z1), MOI.ScalarAffineTerm(1.0, z2)], 0.0),
MOI.GreaterThan(1.0),
)

# objective penalized on z2
MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(),
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2.0, 3.0, -30.0], [x1, x2, z2]), 0.0)
)
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)

if config.solve
@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

MOI.optimize!(model)

@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL
@test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
@test MOI.get(model, MOI.ObjectiveValue()) ≈ 28.0 atol=atol rtol=rtol
@test MOI.get(model, MOI.VariablePrimal(), x1) ≈ 2.0 atol=atol rtol=rtol
@test MOI.get(model, MOI.VariablePrimal(), x2) ≈ 8.0 atol=atol rtol=rtol
@test MOI.get(model, MOI.VariablePrimal(), z1) ≈ 1.0 atol=atol rtol=rtol
@test MOI.get(model, MOI.VariablePrimal(), z2) ≈ 0.0 atol=atol rtol=rtol
end
end
blegat marked this conversation as resolved.
Show resolved Hide resolved

function indicator3_test(model::MOI.ModelLike, config::TestConfig)
atol = config.atol
rtol = config.rtol
# linear problem with indicator constraint
# similar to indicator1_test with reversed z1
# max 2x1 + 3x2
# s.t. x1 + x2 <= 10
# z1 == 0 ==> x2 <= 8
# z2 == 1 ==> x2 + x1/5 <= 9
# (1-z1) + z2 >= 1 <=> z2 - z1 >= 0

MOI.empty!(model)
@test MOI.is_empty(model)

@test MOI.supports(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}())
@test MOI.supports(model, MOI.ObjectiveSense())
@test MOI.supports_constraint(model, MOI.SingleVariable, MOI.ZeroOne)
@test MOI.supports_constraint(model, MOI.SingleVariable, MOI.Interval{Float64})
@test MOI.supports_constraint(model, MOI.ScalarAffineFunction{Float64}, MOI.Interval{Float64})
@test MOI.supports_constraint(model, MOI.VectorAffineFunction{Float64}, MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE, MOI.LessThan{Float64}})
x1 = MOI.add_variable(model)
x2 = MOI.add_variable(model)
z1 = MOI.add_variable(model)
z2 = MOI.add_variable(model)
MOI.add_constraint(model, z1, MOI.ZeroOne())
MOI.add_constraint(model, z2, MOI.ZeroOne())
f1 = MOI.VectorAffineFunction(
[MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, z1)),
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x2)),
],
[0.0, 0.0]
)
iset1 = MOI.IndicatorSet{MOI.ACTIVATE_ON_ZERO}(MOI.LessThan(8.0))
MOI.add_constraint(model, f1, iset1)

f2 = MOI.VectorAffineFunction(
[MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, z2)),
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(0.2, x1)),
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x2)),
],
[0.0, 0.0],
)
iset2 = MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(9.0))

MOI.add_constraint(model, f2, iset2)

# Additional regular constraint.
MOI.add_constraint(model,
MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x1), MOI.ScalarAffineTerm(1.0, x2)], 0.0),
MOI.LessThan(10.0),
)

# Disjunction (1-z1) ⋁ z2
MOI.add_constraint(model,
MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(-1.0, z1), MOI.ScalarAffineTerm(1.0, z2)], 0.0),
MOI.GreaterThan(0.0),
)

MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(),
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2.0, 3.0], [x1, x2]), 0.0)
)
MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE)

if config.solve
@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMIZE_NOT_CALLED

MOI.optimize!(model)

@test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL
@test MOI.get(model, MOI.PrimalStatus()) == MOI.FEASIBLE_POINT
@test MOI.get(model, MOI.ObjectiveValue()) ≈ 28.75 atol=atol rtol=rtol
@test MOI.get(model, MOI.VariablePrimal(), x1) ≈ 1.25 atol=atol rtol=rtol
@test MOI.get(model, MOI.VariablePrimal(), x2) ≈ 8.75 atol=atol rtol=rtol
@test MOI.get(model, MOI.VariablePrimal(), z1) ≈ 1.0 atol=atol rtol=rtol
@test MOI.get(model, MOI.VariablePrimal(), z2) ≈ 1.0 atol=atol rtol=rtol
end
end

const intlineartests = Dict("knapsack" => knapsacktest,
"int1" => int1test,
"int2" => int2test,
"int3" => int3test)
"int3" => int3test,
"indicator1" => indicator1_test,
"indicator2" => indicator2_test,
"indicator3" => indicator3_test,
)

@moitestset intlinear
51 changes: 51 additions & 0 deletions src/sets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,57 @@ Base.isapprox(a::T, b::T; kwargs...) where {T <: Union{SOS1, SOS2}} = isapprox(a

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 `IndicatorSet{A,S}`.
"""
@enum ActivationCondition begin
ACTIVATE_ON_ZERO
ACTIVATE_ON_ONE
end

"""
IndicatorSet{A, S <: AbstractScalarSet}(set::S)

``\\{((y, x) \\in \\{0, 1\\} \\times \\mathbb{R}^n : y = 0 \\implies x \\in set\\}``
when `A` is `ACTIVATE_ON_ZERO` and
``\\{((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.
`IndicatorSet` is used with a `VectorAffineFunction` holding
the indicator variable first.
matbesancon marked this conversation as resolved.
Show resolved Hide resolved

Example: ``\\{(y, x) \\in \\{0, 1\\} \\times \\mathbb{R}^2 : y = 1 \\implies x_1 + x_2 \\leq 9 \\} ``

```julia
f = MOI.VectorAffineFunction(
[MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, z)),
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(0.2, x1)),
MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, x2)),
],
[0.0, 0.0],
)

indicator_set = MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(9.0))

MOI.add_constraint(model, f, indicator_set)
```
"""
struct IndicatorSet{A, S <: AbstractScalarSet} <: AbstractVectorSet
set::S
IndicatorSet{A}(set::S) where {A, S <: AbstractScalarSet} = new{A,S}(set)
end
odow marked this conversation as resolved.
Show resolved Hide resolved
blegat marked this conversation as resolved.
Show resolved Hide resolved

dimension(::IndicatorSet) = 2

function Base.copy(set::IndicatorSet{A,S}) where {A,S}
return IndicatorSet{A}(copy(set.set))
end

# isbits types, nothing to copy
function Base.copy(set::Union{Reals, Zeros, Nonnegatives, Nonpositives,
GreaterThan, LessThan, EqualTo, Interval,
Expand Down
12 changes: 12 additions & 0 deletions test/Test/intlinear.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,15 @@ MOIT.int3test(mock, config)
MOIU.set_mock_optimize!(mock,
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1, 0, 0, 1, 1]))
MOIT.knapsacktest(mock, config)
MOIU.set_mock_optimize!(mock,
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.25, 8.75, 0., 1.])
)
MOIT.indicator1_test(mock, config)
MOIU.set_mock_optimize!(mock,
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [2.0, 8.0, 1., 0.])
)
MOIT.indicator2_test(mock, config)
MOIU.set_mock_optimize!(mock,
(mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.25, 8.75, 1., 1.])
)
MOIT.indicator3_test(mock, config)
7 changes: 6 additions & 1 deletion test/model.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@

const LessThanIndicatorSetOne{T} = MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE, MOI.LessThan{T}}
const LessThanIndicatorSetZero{T} = MOI.IndicatorSet{MOI.ACTIVATE_ON_ZERO, MOI.LessThan{T}}

# Needed by test spread over several files, defining it here make it easier to comment out tests
# Model supporting every MOI functions and sets

MOIU.@model(Model,
(MOI.ZeroOne, MOI.Integer),
(MOI.EqualTo, MOI.GreaterThan, MOI.LessThan, MOI.Interval,
Expand All @@ -10,7 +15,7 @@ MOIU.@model(Model,
MOI.PositiveSemidefiniteConeTriangle, MOI.PositiveSemidefiniteConeSquare,
MOI.RootDetConeTriangle, MOI.RootDetConeSquare, MOI.LogDetConeTriangle,
MOI.LogDetConeSquare),
(MOI.PowerCone, MOI.DualPowerCone, MOI.SOS1, MOI.SOS2),
(MOI.PowerCone, MOI.DualPowerCone, MOI.SOS1, MOI.SOS2, LessThanIndicatorSetOne, LessThanIndicatorSetZero),
(MOI.SingleVariable,),
(MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction),
(MOI.VectorOfVariables,),
Expand Down
28 changes: 28 additions & 0 deletions test/sets.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
"""
MutLessThan{T<:Real} <: MOI.AbstractScalarSet

A mutable `LessThan`-like set to test `copy` of indicator set
"""
mutable struct MutLessThan{T<:Real} <: MOI.AbstractScalarSet
upper::T
MutLessThan(v::T) where {T<:Real} = new{T}(v)
end

Base.copy(mlt::MutLessThan) = MutLessThan(Base.copy(mlt.upper))

@testset "Sets" begin
@testset "Copy" begin
@testset "for $S" for S in [MOI.SOS1, MOI.SOS2]
Expand All @@ -6,5 +18,21 @@
s_copy.weights[1] = 2.0
@test s.weights[1] == 1.0
end
@testset "IndicatorSet" begin
s1 = MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(4.0))
s2 = MOI.IndicatorSet{MOI.ACTIVATE_ON_ZERO}(MOI.GreaterThan(4.0))
s1_copy = copy(s1)
s2_copy = copy(s2)
@test s1_copy isa MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}
@test s1 == s1_copy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check that modifying the set in s1 does not modify the set in s1_copy. You will need a scalar set which is mutable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we even have one? I understand the importance of testing it does not happen, but we are calling copy on the subtest, so if error there is, it's because copy is not properly implemented on it

Copy link
Member

@blegat blegat Apr 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matbesancon LessThan{BigInt} is mutable because BigInt is mutable. You call also create a dummy mutable coefficient type or a dummy mutable set just for the test.

we are calling copy on the subtest

That's what we want to test. If you remove this copy on the subset, the tests still pass while they shouldn't

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@blegat I played a bit with this idea. Fundamentally, this is breaking an assumption about numbers that will be everywhere in MOI, this PR doesn't change that. BigInt being mutable is just a constraint for their construction as far as I know, not supposed to be used.

The implementation from src/sets:

# isbits types, nothing to copy
function Base.copy(set::Union{Reals, Zeros, Nonnegatives, Nonpositives,
                              GreaterThan, LessThan, EqualTo, Interval,
                              SecondOrderCone, RotatedSecondOrderCone,
                              GeometricMeanCone, ExponentialCone,
                              DualExponentialCone, PowerCone, DualPowerCone,
                              PositiveSemidefiniteConeTriangle,
                              PositiveSemidefiniteConeSquare,
                              LogDetConeTriangle, LogDetConeSquare,
                              RootDetConeTriangle, RootDetConeSquare,
                              Integer, ZeroOne, Semicontinuous, Semiinteger})
    return set
end

Is then false because it should check isbits on the set

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. We could defer the question of whether we should copy the field of EqualTo... to a separate issue.
Still you might be able to create a mutable abstract set with Float64 to test the fact we ce copy the set for IndicatorSet

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@blegat see issue #721, and tests added here for IndicatorSet copy

@test s2_copy isa MOI.IndicatorSet{MOI.ACTIVATE_ON_ZERO}
@test s2 == s2_copy
s3 = MOI.IndicatorSet{MOI.ACTIVATE_ON_ZERO}(MutLessThan(4.0))
s3_copy = copy(s3)
@test s3.set.upper ≈ 4.0
s3_copy.set.upper = 5.0
@test s3.set.upper ≈ 4.0
@test s3_copy.set.upper ≈ 5.0
end
end
end