FindInstance
can be incredibly slow to solve a boolean program, but it can be significantly improved by using BooleanConvert
to convert the constraints to "conjunctive normal form".
For example, here is a boolean program with some random constraints. My laptop can't solve it in 10 seconds.
vars = Array[x, 50];
SeedRandom[1234];
const = And @@ Table[Or[
Xor[RandomChoice[vars], RandomChoice[vars]],
Xor[RandomChoice[vars], RandomChoice[vars]]], 100];
TimeConstrained[FindInstance[const, vars, Booleans], 10, "Timeout!"]
(* Timeout! *)
Converting the constraints to "conjunctive normal form" helps FindInstance
find a solution almost instantly:
AbsoluteTiming@FindInstance[ BooleanConvert[const,"CNF"], vars, Booleans ] // Short
(* {0.014715, {{x[1] -> False, x[2] -> False, <<46>>, x[49] -> False, x[50] -> False}}} *)
Wolfram's 4-color map-coloring example uses this trick; without BooleanConvert
, it takes forever to run.
Does anyone know why this is so? I wish the FindInstance
documentation would've mentioned this trick.
Update: A colleague pointed out that "conjunctive normal form" has the appealing property that it allows short-circuiting, which allows the satisfiability solver to prune away large branches of athe tree of candidate variable choices. For example,
BooleanConvert[Nand[x, y]~And~Xor[y, z], "CNF"]
converts the expression into the AND of a bunch of OR expressions:
(! x || ! y) && (! y || ! z) && (y || z)
When testing a candidate set of values for x,y,z
, the solver can evaluate each OR expression in turn and stop as soon as it encounters one that evaluates to FALSE. For example, once it concludes that x=TRUE, y=TRUE
makes the first OR clause FALSE, it can prune off the lower levels of the tree and doesn't need to try either value of z
.