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

Conversation

matbesancon
Copy link
Contributor

First draft for the addition of indicator constraints to MOI. There is evidence for multiple solvers handling them:
Gurobi
CPLEX
SCIP

One thing we would want to add (suggestion from @leethargo) is a bridge for solvers supporting SOS1 but not indicator constraints natively. From SCIP documentation:

This constraint is equivalent to a linear constraint ax−s≤b and an SOS1 constraint on z and s (at most one should be nonzero). In the indicator context we can, however, separate more inequalities.

src/sets.jl Show resolved Hide resolved
src/sets.jl Outdated Show resolved Hide resolved
@codecov-io
Copy link

codecov-io commented Apr 11, 2019

Codecov Report

Merging #709 into master will increase coverage by 0.09%.
The diff coverage is 99.04%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #709      +/-   ##
==========================================
+ Coverage   93.73%   93.83%   +0.09%     
==========================================
  Files          54       54              
  Lines        5572     5677     +105     
==========================================
+ Hits         5223     5327     +104     
- Misses        349      350       +1
Impacted Files Coverage Δ
src/Test/intlinear.jl 100% <100%> (ø) ⬆️
src/sets.jl 93.75% <66.66%> (-1.81%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9970dbd...caeb4c4. Read the comment docs.

src/sets.jl Outdated Show resolved Hide resolved
src/sets.jl Outdated
@@ -449,7 +466,7 @@ function Base.copy(set::Union{Reals, Zeros, Nonnegatives, Nonpositives,
PositiveSemidefiniteConeSquare,
LogDetConeTriangle, LogDetConeSquare,
RootDetConeTriangle, RootDetConeSquare,
Integer, ZeroOne, Semicontinuous, Semiinteger})
Integer, ZeroOne, Semicontinuous, Semiinteger, IndicatorSet})
Copy link
Member

Choose a reason for hiding this comment

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

It is not an isbits type since it contains a vector. The copy should probably copy this vector too

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, I'll re-implement copy. Should we make the vector type generic?

src/sets.jl Outdated Show resolved Hide resolved
@rschwarz
Copy link
Contributor

What about having a set parameter for whether y should be 0 or 1 to activate the constraint?

Then the disjunction ax ≤ b v cx ≤ d could be expressed by the two constraints

(y, x) in IndicatorSet(1, a, LessThan(b))
(y, x) in IndicatorSet(0, c, LessThan(d))

reusing the y variable. Otherwise, another z would have to be introduced with y + z == 1?

@matbesancon
Copy link
Contributor Author

Should we make the indicator set generic on vector type?
IndicatorSet{T,S <: ScalarSet,V <: AbstractVector{<:Real}}

@mlubin
Copy link
Member

mlubin commented Apr 12, 2019

What are the implications of having non isbits sets? How does this interact with MOF?

One way to avoid storing the a vector in the set would be to store it in the function, e.g.,

Set of (y, x) that satisfy the indicator constraint:
    y ∈ 𝔹, y = 1 ==> x ∈ S

Then use VAF to write down the constraints, e.g.,

(1.0 * y, 2*x + 3*z) in IndicatorSet(LessThan(b))

It's an error if the first component of the VAF isn't 1.0 times some variable.

@odow
Copy link
Member

odow commented Apr 12, 2019

The SOS sets already have weight vectors, so this isn't breaking new ground. No problem on the MOF front

@matbesancon
Copy link
Contributor Author

see last version for generic vector

@odow
Copy link
Member

odow commented Apr 12, 2019

Oh, wait. I misread miles' concern. Not sure how abstractvector would play with MOF. I'd prefer the vectoraffinefunction in set approach.

This reverts commit 6e57763.
@matbesancon
Copy link
Contributor Author

problem with VectorAffine in Indicator is that it requires Set to hold variables. Also keeping a vector of a is consistent with SOS

@mlubin
Copy link
Member

mlubin commented Apr 12, 2019

problem with VectorAffine in Indicator is that it requires Set to hold variable

What do you mean by this?

@matbesancon
Copy link
Contributor Author

Either VectorAffine has a weird special first term (1.0, y) or the y needs to be specified in the set

@mlubin
Copy link
Member

mlubin commented Apr 12, 2019

The weird special term is fine with me. That's what we do for cones.

We have machinery already to translate JuMP AffExprs into VAF, so today you could write:

@constraint(model, [y, 2x + 3z] in IndicatorSet(LessThan(b)))

OTOH,

@constraint(model, [y, x, z] in IndicatorSet([2.0, 3.0], LessThan(b)))

seems like a regression in modeling because you have to manually create a list of coefficients.

@matbesancon
Copy link
Contributor Author

Ok I see. [y, 2x + 3z] would be specially parsed at the JuMP level? (Meaning, different from [y, 2x, 3z] or [y + 2x + 3z]

@mlubin
Copy link
Member

mlubin commented Apr 12, 2019

[y, 2x + 3z] would be specially parsed at the JuMP level? (Meaning, different from [y, 2x, 3z] or [y + 2x + 3z]

Yes, those are all parsed differently.

@matbesancon
Copy link
Contributor Author

one thing is that the set cannot determine the dimension of the expected function

@mlubin
Copy link
Member

mlubin commented Apr 12, 2019

The dimension of the set I'm proposing (under the MOI definition) would always be 2.

@matbesancon
Copy link
Contributor Author

ok I hadn't thought of counting like this, so the whole affine term a^T x count as dim 1 right?

@odow
Copy link
Member

odow commented Apr 12, 2019

What about

@constraint(model, 2x1 + x2 in MOI.IndicatorSet(y=>1, MOI.GreaterThan(1.0)))

So

struct IndicatorSet{S}
    variable::Pair{MOI.VariableIndex, Bool}
    set::S
end

Then if S is a scalar set, it works for any scalar function, and if S is a vector set, it works for any vector function.

@matbesancon
Copy link
Contributor Author

Ok, one design consideration I tried to keep is to represent the constraint as closely as possible to what solvers take as input

@matbesancon
Copy link
Contributor Author

Fixed most of the comments. @blegat I didn't find anything for testing new sets with MockOptimizer.
SOS constraints are not tested using it (just basic unit tests in /test/sets.jl

src/Test/intlinear.jl Outdated Show resolved Hide resolved
test/Test/intlinear.jl Outdated Show resolved Hide resolved
@matbesancon
Copy link
Contributor Author

tests passing cc @blegat @mlubin

src/sets.jl Outdated Show resolved Hide resolved
src/sets.jl Outdated Show resolved Hide resolved
@blegat blegat self-requested a review April 24, 2019 20:43
Copy link
Member

@blegat blegat left a comment

Choose a reason for hiding this comment

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

Almost there. Missing a (tested) copy implementation that copies the internal set

@matbesancon
Copy link
Contributor Author

Thanks, added in last commit

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

Copy link
Member

@mlubin mlubin left a comment

Choose a reason for hiding this comment

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

Looking good. Just a few minor comments.

src/Test/intlinear.jl Outdated Show resolved Hide resolved
src/Test/intlinear.jl Outdated Show resolved Hide resolved
test/runtests.jl Outdated Show resolved Hide resolved
src/Test/intlinear.jl Show resolved Hide resolved
@blegat blegat merged commit c1d8f6a into jump-dev:master Apr 30, 2019
@matbesancon matbesancon deleted the indicator-cons branch April 30, 2019 11:20
@blegat blegat mentioned this pull request Oct 4, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

6 participants