3
$\begingroup$

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 of x[1][i]==x[2][i] over i=1,2,3,4.

  • We convert each integer inequality constraint like 1<x[1]<10 into the equivalent logical OR of the integer equalities x[1]==k over k=2,3,...,9. Then we then express each of these integer equalities as boolean constraints using the method in the previous bullet. Expressing 1<x<10 as x==2 OR x==3 OR ... OR x==9 is 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?

$\endgroup$
1
  • $\begingroup$ As a paid program, Mathematica should in fact be better than this :/ $\endgroup$ – LowFieldTheory Jun 25 '19 at 20:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.