I would like to use a more complex boolean expression similar to the following as the condition in OnlyEnforceIf() for a constraint:
((A == 1) || (A == 2)) && ((B == 0) || (B ==1) || (B == 5)) && (! C)
This is not possible directly in a single step because
- OnlyEnforceIf() takes an ILiteral (e.g. an IntVar) as its argument, and (A == 1) etc. are BoundIntegerExpressions
- There is no && or || operator which can be used with BoundIntegerExpressions
- The expressions like (A == 1) etc. cannot be added directly to the model because then they will constrain the solution.
I found an example of linking an IntVar to the value of an equality comparison (a BoundIntegerExpression) in
// Implement b == (x >= 5).
model.Add(x >= 5).OnlyEnforceIf(b);
model.Add(x < 5).OnlyEnforceIf(b.Not());
This seems to be the only way to do it although it seems awkward.
Elaborating on this for a more complicated expression such as the one above, I've developed this example.
using Google.OrTools.Sat;
namespace GoogleSATMinimalExample
{
class BooleanExpressionExample
{
CpModel model;
public void InitModel() {
model = new CpModel();
IntVar A = model.NewIntVar(-1, 3, "A");
IntVar B = model.NewIntVar(-1, 8, "B");
IntVar C = model.NewBoolVar("C");
// Link a boolan variable to the expression A == 1
IntVar Ais1 = model.NewBoolVar("Ais1");
model.Add(A == 1).OnlyEnforceIf(Ais1);
model.Add(A != 1).OnlyEnforceIf(Ais1.Not());
// Proceed similarly for the other variables
IntVar Ais2 = model.NewBoolVar("Ais2");
model.Add(A == 2).OnlyEnforceIf(Ais2);
model.Add(A != 2).OnlyEnforceIf(Ais2.Not());
IntVar Bis0 = model.NewBoolVar("Bis0");
model.Add(B == 0).OnlyEnforceIf(Bis0);
model.Add(B != 0).OnlyEnforceIf(Bis0.Not());
IntVar Bis1 = model.NewBoolVar("Bis1");
model.Add(B == 1).OnlyEnforceIf(Bis1);
model.Add(B != 1).OnlyEnforceIf(Bis1.Not());
IntVar Bis5 = model.NewBoolVar("Bis5");
model.Add(B == 5).OnlyEnforceIf(Bis5);
model.Add(B != 5).OnlyEnforceIf(Bis5.Not());
// Since no && or || operators are available for BoundIntegerExpressions we need to construct
// IntVars equivalent to the partial expressions
IntVar Ais1or2 = model.NewBoolVar("Ais1or2"); // to become equivalent to ((A ==1) || (A == 2))
model.AddImplication(Ais1or2.Not(), Ais1.Not());
model.AddImplication(Ais1or2.Not(), Ais2.Not());
IntVar Bis0or1or5 = model.NewBoolVar("Bis0or1or5"); // to become equivalent to ((B == 0) || (B == 1) || (B == 5))
model.AddImplication(Bis0or1or5.Not(), Bis0.Not());
model.AddImplication(Bis0or1or5.Not(), Bis1.Not());
model.AddImplication(Bis0or1or5.Not(), Bis5.Not());
// !C can be represented directly as C.Not()
// Now the complete expression ((A == 1) || (A == 2)) && ((B == 0) || (B ==1) || (B == 5)) && (! C)
IntVar expr = model.NewBoolVar("expr");
model.AddImplication(Ais1or2.Not(), expr.Not());
model.AddImplication(Bis0or1or5.Not(), expr.Not());
model.AddImplication(C, expr.Not());
// Now I can use expr as argument to OnlyEnforceIf()
IntVar D = model.NewIntVar(-1, 5, "D");
model.Add(D == 1).OnlyEnforceIf(expr);
}
}
}
Is this type of construction the best or only way to proceed, or is there a simpler way?
Thanks for any help.