In an effort to learn Z3 I tried solving one of my favorite Advent of Code problems (a particularly difficult one, 2018 day 23, part 2) using the Haskell bindings sbv
. Spoilers in code ahead...
module Lib
( solve
) where
import Data.SBV
puzzle :: [((SInteger, SInteger, SInteger), SInteger)]
puzzle = (\((x, y, z), r) -> ((literal x, literal y, literal z), literal r)) <$> [
((60118729,58965711,8716524), 71245377),
{- 999 more values that are large like the first one... -}
]
manhattan (a1, a2, a3) (b1, b2, b3) =
abs (a1 - b1) + abs (a2 - b2) + abs (a3 - b3)
countInRange pos =
foldr (\(nb, r) -> (+) $ oneIf (manhattan nb pos .<= r)) (0 :: SInteger) puzzle
answer = optimize Lexicographic $ do
x <- sInteger "x"
y <- sInteger "y"
z <- sInteger "z"
maximize "in-range" $ countInRange (x, y, z)
minimize "distance-from-origin" $ abs x + abs y + abs z
solve =
answer >>= print
Now, this question is not really a sbv
question, nor a Haskell question and there's nothing really wrong with the above code (it solves 1000-value-puzzle
lists, with huge X,Y and Z coordinates in little over a minute on my machine which is good enough for me). This question is about (0 :: SInteger)
found in countInRange
.
If I change (0 :: SInteger)
to (0 :: SInt32)
it causes the solution to take a very very long time (I kicked it off when I started typing this question and that was 16 minutes ago and counting).
So, what gives? Why is SInt32
so much slower in this case? Is it because I'm mixing domains (using SInteger
elsewhere)? I would have thought that the unbounded SInteger
representation would be slower than the bounded Int32
.
Note that the symbolic type in question is only used for counting matching values from puzzle
(thus it will always be <= 1000, i.e. the length of puzzle
).
UPDATE
I killed the Int32
solution after 40 minutes of running.
SInt32
. – Compliancemanhattan
may overflow withSInt32
but I haven't done the analysis to prove that. – KreisSInt32
seems slower as well. It's been running for 3 minutes as of this comment. – KreisoptimizeWith z3{verbose=True}
. This'll show you when SBV sends the query to z3 and waits for the answer. (You'll see a line[SEND] (check-sat)
and SBV waiting for an answer from z3.) Where is the slow down? Is it that SBV doesn't send the(check-sat)
request as quickly, or is it the solver that's taking much longer to respond tocheck-sat
? – MatteSInt32
the solver has to cope with overflow, which makes the constraints harder to solve, due to the more complex underlying math. – Afar