Here is an integer program with a bunch of inequality and equality constraints. FindInstance takes 2 seconds to solve it.
vars = Array[x, 20]
ineq = 1 < # < 10 & /@ vars
eq = (#1 == #2 || (#2 != #3)) & @@@ Table[RandomSample[vars, 3], 10]
eq2 = BooleanConvert[And @@ eq, "CNF"]
{time, soln} =
RepeatedTiming[
First@FindInstance[And @@ Flatten[{ineq, eq2}], vars, Integers]];
time
vars /. soln
(* 2.02 *)
(* {2, 2, 2, 2, 2, 2, 2, 4, 2, 3, 2, 2, 4, 2, 6, 2, 3, 2, 3, 2} *)
I used the BooleanConvert trick described here to try to pre-simplify the equality constraints for the solver.
Surprisingly, the solve time can be reduced to about 0.005 seconds by converting it to a boolean program in the following way.
The main idea is to express each integer variable as a set of boolean variables -- the bits of the integer. For example, the integer x[1] is replaced by the boolean variables x[1][i], i=1,2,3,4. (The inequality constraints require each variable to be between 1 and 10, so each integer variable requires 4 bits.)
We replace each integer equality constraint with an equivalent boolean expression that constrains the bits of the integers. For example,
x[1] == x[2]becomes the logical AND ofx[1][i]==x[2][i]overi=1,2,3,4.We convert each integer inequality constraint like
1<x[1]<10into the equivalent logical OR of the integer equalitiesx[1]==koverk=2,3,...,9. Then we then express each of these integer equalities as boolean constraints using the method in the previous bullet. Expressing1<x<10asx==2 OR x==3 OR ... OR x==9is totally brute-force, yet somehow still runs faster.
Here is the code:
nbits = 4;
bitvars = Array[#, nbits] & /@ vars;
equal[x_, int_Integer] := Module[{xbits = Array[x, nbits]},
And @@ MapThread[If[#1 == 1, #2, Not@#2] &,
{Reverse@IntegerDigits[int, 2, Length@xbits], xbits}]]
equal[x_, y_] :=
Module[{xbits = Array[x, nbits], ybits = Array[y, nbits]},
Inner[Xnor, xbits, ybits, And]]
less[lo_Integer, x_, hi_Integer] := (Or @@ Table[equal[x, i], {i, lo, hi}])
ineqBOOL = ineq /. {Less[lo_Integer, x_, hi_Integer] :> less[lo, x, hi]};
eqBOOL = eq /. {Equal[x_, y_] :> equal[x, y], Unequal[x_, y_] :> Not[equal[x, y]]};
ineqBOOL2 = BooleanConvert[#, "CNF"] & /@ ineqBOOL;
eqBOOL2 = BooleanConvert[#, "CNF"] & /@ eqBOOL;
consts = And @@ Flatten[{ineqBOOL2, eqBOOL2}];
{time, bitsoln} = RepeatedTiming[
First@FindInstance[consts, Flatten@bitvars, Booleans]];
time
mysoln = bitvars /. bitsoln /. {False -> 0, True -> 1} // Map[FromDigits[#, 2] &, #] &
(* 0.0050 *)
(* {4, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8} *)
I guess my question is: Is there some way I can get similar performance out of FindInstance without having to resort to this weird trick of transforming my integer program into a boolean program?