Day 10 (z3 version)

This commit is contained in:
2025-12-17 19:56:01 -08:00
parent 6590efa91c
commit 02ea9dad95
5 changed files with 238 additions and 0 deletions

126
aoc2025/Day10.newt Normal file
View File

@@ -0,0 +1,126 @@
module Day10
import Prelude
import Node
import Aoc
import Parser
import Data.Vect
import Data.Fin
import Z3
instance a b. {{Show a}} {{Show b}} Show (Either a b) where
show (Left a) = "Left \{show a}"
show (Right b) = "Right \{show b}"
record Machine where
goal : Int
buttons : List Int
jolts : List Int
infixr 2 _**_
abs : Int Int
abs x = if x < 0 then 0 - x else x
data Σ : (a : U) (a U) U where
_**_ : A. {0 B : A U} (x : A) (_ : B x) Σ A B
instance Show Machine where
show m = "Mach{goal=\{show m.goal}, buttons=\{show m.buttons}, goal=\{show m.jolts}}"
parseGroup : Char Char Parser (List Int)
parseGroup start end = do
match start
nums <- some $ number <* optional (match ',')
match end
ws
pure nums
pfunc pow : Int Int Int := `(x,y) => x ** y`
pfunc xor : Int Int Int := `(x,y) => x ^ y`
parseMachine : Parser Machine
parseMachine = do
match '['
marks <- some $ match '.' <|> match '#'
match ']'
ws
buttons <- some $ parseGroup '(' ')'
costs <- parseGroup '{' '}'
pure $ MkMachine (mkgoal marks) (map mkbutton buttons) costs
where
mkbutton : List Int Int
mkbutton (n :: ns) = pow 2 n + mkbutton ns
mkbutton Nil = 0
mkgoal : List Char Int
mkgoal ('#' :: xs) = 1 + 2 * mkgoal xs
mkgoal (_ :: xs) = 2 * mkgoal xs
mkgoal Nil = 0
parseFile : Parser (List Machine)
parseFile = some $ parseMachine <* match '\n'
solve : Machine Int
solve m = go 0 m.buttons
where
go : Int List Int Int
go st Nil = if st == m.goal then 0 else 999
go st (b :: bs) =
if st == m.goal
then 0
else min (1 + go (xor st b) bs) (go st bs)
infixl 7 _&_
pfunc _&_ : Int Int Int := `(x,y) => x & y`
part2z3 : {{Context}} Machine Promise Int
part2z3 {{context}} mach = do
let rows = length mach.jolts
let (Just todo) = toVect rows mach.jolts | _ => fatalError "jolt/rows length"
let vars = map (\i => z3const "x\{show $ fst i}") $ enumerate mach.buttons
let solver = newOptimizer context
traverse_ (constrain solver $ zip vars mach.buttons) (enumerate mach.jolts)
for_ vars $ \v => z3add solver $ z3ge v (z3int 0)
z3minimize solver $ sum vars
"sat" <- z3check solver | res => fatalError "GOT \{res}"
model <- getModel solver
pure $ foldl _+_ 0 $ map getInt vars
where
sum : List Arith Arith
sum Nil = z3int 0
sum (a :: as) = foldl _+_ a as
constrain : b. Solver b List (Arith × Int) (Nat × Int) Promise Unit
constrain solver bs (ix, goal) =
let mask = pow 2 $ cast ix in
z3add solver $ z3eq (z3int goal) $ row mask bs (z3int 0)
where
row : Int List (Arith × Int) Arith Arith
row mask Nil acc = acc
row mask ((x , b) :: bs) acc = if b & mask == 0 then row mask bs acc else row mask bs (acc + x)
button2List : rows. Vect rows Int Int Int Vect rows Int
button2List VNil _ _ = VNil
button2List (_ :- rest) mask b =
(if (b & mask) == 0 then 0 else 1) :- button2List rest (mask * 2) b
run : String -> Promise Unit
run fn = do
putStrLn {Promise} fn
text <- liftIO {Promise} $ readFile fn
let (Right (probs,_)) = parseFile (unpack text)
| Left msg => putStrLn "ERR: \{msg}"
let part1 = foldl _+_ 0 $ map solve probs
putStrLn $ "part1 \{show part1}"
z3 <- initZ3
let context = initContext z3 "main"
p2s <- traverse part2z3 probs
let p2 = foldl _+_ 0 p2s
putStrLn "part2 \{show p2}"
main : IO Unit
main = runPromise $ do
run "aoc2025/day10/eg.txt"
run "aoc2025/day10/input.txt"

View File

@@ -2,6 +2,31 @@ module Node
import Prelude import Prelude
-- Promise has some sequencing issues with IO
ptype Promise : U U
pfunc promise_bind : a b. Promise a (a Promise b) Promise b := `(a,b, ma, mab) => ma.then(v => mab(v))`
pfunc promise_pure : a. a Promise a := `(_, a) => Promise.resolve(a)`
-- The potential issue here is that fa runs before it is even passed in!
pfunc promise_app : a b. Promise (a b) Promise a Promise b := `(a,b,fab,fa) => fab.then(ab => fa.then(a => Promise.resolve(ab(a))))`
-- This runs everything immediately...
pfunc promise_lift : a. IO a Promise a := `(a,f) => Promise.resolve(f('WORLD').h1)`
instance Monad Promise where
bind = promise_bind
pure = promise_pure
instance Applicative Promise where
return = pure
fab <*> fa = promise_app fab fa
instance HasIO Promise where
liftIO = promise_lift
pfunc fs : JSObject := `require('fs')` pfunc fs : JSObject := `require('fs')`
pfunc getArgs : List String := `arrayToList(String, process.argv)` pfunc getArgs : List String := `arrayToList(String, process.argv)`
pfunc readFile uses (MkIORes) : (fn : String) -> IO String := `(fn) => (w) => Prelude_MkIORes(require('fs').readFileSync(fn, 'utf8'), w)` pfunc readFile uses (MkIORes) : (fn : String) -> IO String := `(fn) => (w) => Prelude_MkIORes(require('fs').readFileSync(fn, 'utf8'), w)`
pfunc runPromise uses (MkIORes MkUnit) : a. Promise a IO Unit := `(a,p) => (w) => {
// p(w)
return Prelude_MkIORes(Prelude_MkUnit, w)
}`

46
aoc2025/Z3.newt Normal file
View File

@@ -0,0 +1,46 @@
module Z3
import Prelude
import Node
ptype Context
-- Flag if optimizer or solver
ptype Solver : Bool U
ptype Arith
ptype Z3Bool
ptype Model
ptype Z3
pfunc initZ3 : Promise Z3 := `require('z3-solver').init()`
pfunc z3ResetMemory : Promise Z3 := `(z3) => z3.Z3.reset_memory()`
pfunc initContext : Z3 String Context := `(z3, name) => z3.Context(name)`
pfunc newSolver : Context Solver False := `(Context) => new Context.Solver()`
pfunc newOptimizer : Context Solver True := `(Context) => new Context.Optimize()`
-- These probably should be IO or Promise
pfunc freshInt : {{Context}} Promise Arith := `(Context) => Promise.resolve(Context.Int.fresh())`
pfunc z3const : {{Context}} String Arith := `(Context, name) => Context.Int.const(name)`
pfunc arith_add : Arith Arith Arith := `(a,b) => a.add(b)`
pfunc z3int : {{Context}} Int Arith := `(Context,a) => Context.Int.val(a)`
-- We can't overload operators for these because of the return type
pfunc z3eq : Arith Arith Z3Bool := `(a,b) => a.eq(b)`
pfunc z3ge : Arith Arith Z3Bool := `(a,b) => a.ge(b)`
pfunc z3add : b. Solver b Z3Bool Promise Unit := `(_, solver, exp) => Promise.resolve(solver.add(exp))`
pfunc z3minimize : Solver True Arith Promise Unit := `(solver, exp) => Promise.resolve(solver.minimize(exp))`
pfunc z3check : b. Solver b Promise String := `(b, solver) => solver.check()`
pfunc getModel : b. Solver b Promise Model := `(b, solver) => Promise.resolve(solver.model())`
pfunc getInt : {{Model}} Arith Int := `(model, exp) => {
let n = +(model.eval(exp).toString())
return isNaN(n) ? 0 : n
}`
instance Add Arith where
a + b = arith_add a b

38
aoc2025/Z3Test.newt Normal file
View File

@@ -0,0 +1,38 @@
module Z3Test
import Prelude
import Node
import Z3
test : Promise Unit
test = do
context <- initZ3 "main"
x0 <- freshInt
x1 <- freshInt
x2 <- freshInt
x3 <- freshInt
x4 <- freshInt
x5 <- freshInt
let solver = newOptimizer context
z3add solver $ z3ge x0 $ z3int 0
z3add solver $ z3ge x1 $ z3int 0
z3add solver $ z3ge x2 $ z3int 0
z3add solver $ z3ge x3 $ z3int 0
z3add solver $ z3ge x4 $ z3int 0
z3add solver $ z3ge x5 $ z3int 0
z3add solver $ z3eq (x4 + x5) (z3int 3)
z3add solver $ z3eq (x1 + x5) (z3int 5)
z3add solver $ z3eq (x2 + x3 + x4) (z3int 4)
z3add solver $ z3eq (x0 + x1 + x3) (z3int 7)
z3minimize solver $ x0 + x1 + x2 + x3 + x4 + x5
res <- z3check solver
liftIO $ putStrLn "GOT \{res}"
-- switch to promise mode
main : IO (Promise Unit)
main = pure test

3
aoc2025/day10/eg.txt Normal file
View File

@@ -0,0 +1,3 @@
[.##.] (3) (1,3) (2) (2,3) (0,2) (0,1) {3,5,4,7}
[...#.] (0,2,3,4) (2,3) (0,4) (0,1,2) (1,2,3,4) {7,5,12,7,2}
[.###.#] (0,1,2,3,4) (0,3,4) (0,1,2,4,5) (1,2) {10,11,11,5,10,5}