hdavid16 / DisjunctiveProgramming.jl

A JuMP extension for Generalized Disjunctive Programming
MIT License
27 stars 3 forks source link

Having a set of logical variables Y, is it possible to constraint them all to be equal? #107

Closed schlichtanders closed 2 months ago

schlichtanders commented 6 months ago

I know that one can do @constraint(m, Y[1] == Y[2] == ... == Y[n] := true), but I am wondering whether this is also possible without explicitly naming all single Y.

hdavid16 commented 6 months ago

This would be a something like the all_equal constraint from constraint programming. This would be something that could be added to the package so that we can use something like: @constraint(m, Y in AllEqual()). We would need to discuss how this would be reformulated.

You can also use @constraint(m, [i in 1:n-1], Y[i] == Y[i+1] := true) to avoid writing all Y variables. This is an alternate formulation for what you have referenced above.

pulsipher commented 6 months ago

You can just use splatting with the logical_and function:

@constraint(m, logical_and(Y...) := true)

Other useful functions you can use include logical_or, logical_not, implies, and iff.

I don't think any feature additions are needed, but we should better document these functions.

schlichtanders commented 6 months ago

Thank you both for your solutions and your help! Really cool that this already exists.

(I tried @constraint(m, reduce(==, Y) :=) which didn't seem to work)

schlichtanders commented 6 months ago

reopened for tracking the documentation request

hdavid16 commented 6 months ago

You can just use splatting with the logical_and function:

@constraint(m, logical_and(Y...) := true)

@pulsipher , thank you for pointing out the splatting. The correct splatting based on @schlichtanders request would be:

@constraint(m, iff(Y...) := true)

However, I will point out that neither of these are correct if you want to "constraint them all to be equal". Here is why: 1) Y[1] == Y[2] == Y[3] := true can be accomplished with and odd number of true values: e.g., Y[1] = true, Y[2] = false, and Y[3] = false. 2) Y[1] and Y[2] and Y[3] := true forces all of them to be true, but ignores the possibility of all of them being equal to false.

Therefore, only @constraint(m, [i in 1:n-1], Y[i] == Y[i+1] := true) will accomplish the "all equal" requirement by linking the variables sequentially. In the 3 variable example, this would be equivalent to Y[1] == Y[2] := true and Y[2] == Y[3] := true. These two constraints can only be satisfied if all of the variables are true or if all of them are false, meaning they are "all equal".

pulsipher commented 6 months ago

@hdavid16 To make them all equal (either true or false) with one constraint, couldn't you do the following:

@constraint(m, logical_and(Y...) || ¬logical_and(Y...) := true)

Which forces the variables to all be true or all be false. I suppose this doubles the number of terms in the constraint, but it should work.

What's cool is that the framework is flexible enough to do all these different ways of formulating the constraint.

hdavid16 commented 6 months ago

@pulsipher yes that is another valid approach. However, you would need to have the negation inside the second logical_and so that you have that all are false:

@constraint(m, logical_and(Y...) || logical_and(logical_not.(Y)...) := true)

I suspect my sequential 2 term constraint set is more efficient, but I agree that it's great that the flexibility is there to test alternate representations.