This year (2022) I decided to finally try to use Z3 to solve an Advent Of Code challenge, Day 21, to be precise.

Z3 is a theorem prover/SAT/SMT solver, which means “*it will solve problems for you*“. This is what computers are supposed to do, and I loved this stuff since I did an Operational Research exam in University.

Z3 can solve a variety of problems, but for this problem we just need to understand how it solves simple equations.

## How to do algebra with Z3/Ruby

The Ruby bindings for Z3 are not as popular as, say, the python ones, but they work just fine. Install Z3 using your favorite tool (mine is MacPorts), and then the gem

```
$ port install z3
... compiling stuff
$ gem install z3
... compiling more stuff
$ ruby -r z3 -e 'p Z3.class'
Module
```

You can start coding now.

To solve an equation with Z3 you define a model composed of variables, values, and constraints, and then ask the solver to fill in the blanks.

The simplest thing you can do is a basic math expression

```
require 'z3'
solver = Z3::Solver.new
# the argument is the name of the variable in the model
a = Z3.Int('a')
b = Z3.Int('b')
x = Z3.Int('x')
solver.assert a == 1
solver.assert b == 3
solver.assert x == a + b
if solver.satisfiable?
p solver.model
else
puts "I can't solve that, Dave"
end
```

running this should give you

```
$ ruby additionz3.rb
Z3::Model<a=1, b=3, x=4>
```

Behold! You can do basic math!

The interesting bit is that `Z3.Int`

is a `Z3::IntExpr`

object, and when you mix it with numbers or other expressions you get back more of the same, you can explore this in `irb`

```
>> require 'z3'
=> true
>> a = Z3.Int('a')
=> Int<a>
>> a.class
=> Z3::IntExpr
>> b = a+1
=> Int<a + 1>
>> b.class
=> Z3::IntExpr
```

You can of course use other operators, such as relational operators. This is how you solve the problem of finding a number between two others

```
require 'z3'
solver = Z3::Solver.new
a = Z3.Int('a')
b = Z3.Int('b')
x = Z3.Int('x')
solver.assert a == 1
solver.assert b == 3
solver.assert x > a
solver.assert x < b
if solver.satisfiable?
p solver.model # Z3::Model<a=1, b=3, x=2>
else
puts "I can't do that Dave"
end
```

you can solve a system of equations in pretty much the same way. The classic puzzle `SEND+MORE=MONEY`

where each letter is a different digit can be solved like this

```
require "z3"
solver = Z3::Solver.new
variables = "sendmoremoney".chars.uniq.each_with_object({}) do |digit, hash|
# All variables are digits
var = Z3.Int(digit)
solver.assert var >= 0
solver.assert var <= 9
hash[digit] = var
end
# define the words in terms of the digits
send = variables["s"] * 1000 + variables["e"] * 100 + variables["n"] * 10 + variables["d"]
more = variables["m"] * 1000 + variables["o"] * 100 + variables["r"] * 10 + variables["e"]
money = variables["m"] * 10000 + variables["o"] * 1000 + variables["n"] * 100 + variables["e"] * 10 + variables["y"]
# the leftmost digit is never zero
solver.assert variables['s'] > 0
solver.assert variables['m'] > 0
# all digits are different
solver.assert Z3.Distinct(*variables.values)
# define the actual expression
solver.assert money == send + more
if solver.satisfiable?
# get the values from the model, indexed by their name
values = solver.model.to_h
# map each letter to the variable and find each variable in the model
"send + more = money".chars.map do |char|
var = variables[char]
print values[var] || char
end
puts
else
p "Impossibru!"
end
```

should print

```
$ ruby smm.rb
9567 + 1085 = 10652
```

## Solving Day 21 with Z3

Spoiler alert: this covers part 2 which is not visible unless you solve part 1.

The problem is: you have a set of monkeys shouting at each other, defined like this:

```
root: pppw = sjmn
dbpl: 5
cczh: sllz + lgvd
zczc: 2
ptdq: humn - dvpt
dvpt: 3
lfqf: 4
humn: ?
ljgn: 2
sjmn: drzm * dbpl
sllz: 4
pppw: cczh / lfqf
lgvd: ljgn * ptdq
drzm: hmdt - zczc
hmdt: 32
```

to the left of the `:`

you have the monkey name, and to the right something they shout. A monkey shouts either a number, or the result of an operation based on numbers shouted by other monkeys, except for two of them: the monkey named `root`

will check if the two numbers are equal, and `humn`

represents you: you need to yell the right number so the equality check returns true.

My Clever Reader will have realized this is a simple algebraic problem, but the real input is large, to solve it you would have to *think*, determine the order of the operations, build a tree.. yeah I got bored already.

But executing instructions is what the Ruby interpreter does. And there is a clear mapping from the input a ruby+z3 instruction. So… let’s just transpile the input to a ruby program!

Each input line becomes an assert, we add a prologue, `eval`

it, and then ask for a solution:

```
require 'z3'
input = <<~INPUT.lines
root: pppw + sjmn
dbpl: 5
cczh: sllz + lgvd
zczc: 2
ptdq: humn - dvpt
dvpt: 3
lfqf: 4
humn: 5
ljgn: 2
sjmn: drzm * dbpl
sllz: 4
pppw: cczh / lfqf
lgvd: ljgn * ptdq
drzm: hmdt - zczc
hmdt: 32
INPUT
# create a hash monkey-name -> variable
# so we can look them up by name
env = Hash.new { |h, k| h[k] = Z3.Int(k) }
# convert the input to a ruby program
prog = input.map do |l|
# root checks equality
l = l.sub(/root: (.*) \+ (.*)\n/, 'solver.assert env["\1"] == env["\2"]' + "\n")
# this is our incognita, just get rid of it
l = l.sub(/humn: (.*)\n/, "\n")
# assert a variable as an exact number
l = l.sub(/(\w+): (\d+)\n/, 'solver.assert(env["\1"] == \2)' + "\n")
# assert a variable as the result of a binary operation
l = l.sub(/(\w+): (\w+) (.) (\w+)\n/, 'solver.assert(env["\1"] == (env["\2"] \3 env["\4"]))' + "\n")
l
end
solver = Z3::Solver.new
# ger the program in here
eval(prog.join)
# solve and show our solution
p solver.satisfiable?
# ask for a solution
p solver.model.to_h[env["humn"]].to_i
```

If you remove the input, this boils down to ~10 lines of code.

Is it ugly? Yes. Is it cheating? Probably. Is it running `eval`

on random input I just downloaded from the internet? You bet it is.

But I had a lot of fun.