Books · Solving Puzzles through Mathematical Programming
Chapter 55
Reading the Rule
Every other puzzle in this book is handed a rule and asked for an arrangement. This one runs the other way. We are shown two photographs of a row of cells, one a moment after the other, and asked for the rule that turned the first into the second. It is an inverse problem, the kind a scientist faces with the world: not to work out what a known law produces, but to read the law off what it has already produced.
A line that ticks
The setting is a one-dimensional cellular automaton, the simplest thing that can be said to compute. A row of cells, each black or white, ticks forward in steps. At each step every cell looks at itself and its two neighbours, three cells in all, and turns black or white according to a fixed rule. Three cells, each two ways, make eight possible neighbourhoods, and the rule is just a verdict, black or white, for each of the eight. So a rule is eight bits, and there are of them, the family Wolfram numbered to .
Number the eight neighbourhoods by reading them as binary, the left neighbour worth , the cell itself , the right neighbour . Then a neighbourhood is a number , and the rule is a bit for each, the cell’s next colour. The rule’s own number is .
The inference
Let be the first row and the second, with cells off the ends read as white. For a cell its neighbourhood number is the constant and whatever rule produced the second row must answer that neighbourhood with the colour actually seen there. So each observed cell pins one bit of the rule: The unknowns are the eight bits ; the constraints read them off the data. If the row is long enough that all eight neighbourhoods appear, every bit is pinned and the rule is known; where a neighbourhood never occurs, its bit is free, and more than one rule fits.
from ortools.sat.python import cp_model
def neighbourhood(s, j):
a = s[j - 1] if j > 0 else 0
b = s[j]
c = s[j + 1] if j + 1 < len(s) else 0
return 4 * a + 2 * b + c
def infer_rules(s1, s2, cells):
m = cp_model.CpModel()
r = [m.NewBoolVar("") for _ in range(8)]
for j in cells:
m.Add(r[neighbourhood(s1, j)] == s2[j])
rules = []
class Collect(cp_model.CpSolverSolutionCallback):
def __init__(self):
cp_model.CpSolverSolutionCallback.__init__(self)
def on_solution_callback(self):
rules.append(sum(self.Value(r[k]) << k
for k in range(8)))
s = cp_model.CpSolver()
s.parameters.enumerate_all_solutions = True
s.Solve(m, Collect())
return sorted(rules)
Here are the two rows, black for one and white for zero: Across the eight inner cells the neighbourhoods come out as , all eight of them once each, so every bit of the rule is fixed. The solver returns one rule and no other: number , the bits read from down to . That is the rule Wolfram showed can compute anything a computer can, recovered here from a single tick of ten cells.
When the data is not enough
Now suppose we had seen less, only the cells whose neighbourhoods were among the first four, to . Those fix the four low bits of the rule and say nothing about the rest, so the four high bits are free and rules account for everything observed, rule among them. The solver, asked the same question of the smaller evidence, returns all sixteen. This is the article’s quiet point, and it is the scientist’s predicament in miniature: when several laws fit the data equally, no amount of staring at what you have will choose between them. Only a fresh observation, a neighbourhood not yet seen, can rule a candidate out. The model does not hide the ambiguity or pick a favourite; it reports the whole set of survivors, which is the honest answer.
Sources. The cellular-automaton rule-inference model is from Martin J. Chlond, A New Kind of IP, INFORMS Transactions on Education volume , number (), pages –, whose title puns on Stephen Wolfram’s A New Kind of Science (Wolfram Media, ), the source of the rule numbering and of the result that rule is computationally universal. Chlond infers rule from generated data and notes, as here, that partial data may leave several rules consistent; the ten-cell instance, its unique rule , and the sixteen-rule partial reading are the present chapter’s own, each produced and checked by the model above. The one-dimensional automaton and its notation are Wolfram’s; the best-known two-dimensional example, Conway’s Game of Life, is older, from .