Books · Solving Puzzles through Mathematical Programming
Chapter 45
The Fractions Puzzle
Nine different non-zero digits go in the nine boxes of where is shorthand for the two-digit number , and and likewise. Each of the digits through is used once. There is, up to the order of the three fractions, a single solution, and the eye can confirm it: is three-quarters, and , the remaining quarter. This is problem forty-one of the CSPLib, the standard library of constraint benchmarks, and it generalises in a way that turns a one-line riddle into a question still partly open.
From three fractions to
Replace three fractions by and ask for non-zero digits with Each digit from to may now be reused, between once and times, so that the stock of nine digits keeps pace with the boxes. At the ceiling is one, every digit appears exactly once, and this is the original puzzle. Because each fraction is at least , no more than of them can sum to , so the family is finite. The question is the largest for which the digits can be found.
What makes it hard is not the combinatorics but the arithmetic. The obvious program reads the equation as written and adds the fractions in floating-point, and from about rounding error starts to certify sums that are not really . The obvious repair, clearing denominators by multiplying through by the product , replaces rounding error with overflow: that product passes a -bit integer at and a -bit one not long after. The puzzle is a small monument to the gap between a model that is correct on paper and one that is correct in a machine.
Clearing the denominators
The cure is to clear the denominators with their least common multiple rather than their product. The two agree only when the denominators are coprime; in every solution they share factors heavily, and the least common multiple stays small where the product explodes. The whole model is built around one extra variable that carries it.
Variables
For each fraction three digit variables give its numerator and the two figures of its denominator,
One linear equation
The constraint divides by unknown denominators, which no integer solver will accept. Clear them all at once. Let be a positive integer that every divides, and let be the corresponding cofactor. Then , and the sum-to-one condition becomes a single linear equation, The cofactors and the divisibility are pinned by one family of product relations, which simultaneously force each to divide and define . Any common multiple would serve, since the equation is unchanged by scaling and every together; minimising picks out the least common multiple, the smallest honest common denominator, and keeps the numbers as small as the puzzle allows. CP-SAT carries the handful of products and directly, which is all the model needs beyond linear constraints.
Counting and symmetry
Two families of constraints remain. The counting rule asks that each digit value be taken by between one and of the digit variables. And the fractions may be permuted freely, a symmetry that multiplies every solution by copies; ordering the fractions by value removes it. Since with a common , ordering by value is just on the scaled numerators the model already has.
A solver
from ortools.sat.python import cp_model
from math import ceil
def n_fractions(n, lmax=200_000):
m = cp_model.CpModel()
x = [m.NewIntVar(1, 9, "") for _ in range(n)] # numerators
y = [m.NewIntVar(1, 9, "") for _ in range(n)] # denom. tens
z = [m.NewIntVar(1, 9, "") for _ in range(n)] # denom. ones
d = [m.NewIntVar(11, 99, "") for _ in range(n)]
q = [m.NewIntVar(1, lmax, "") for _ in range(n)]
p = [m.NewIntVar(1, lmax, "") for _ in range(n)]
L = m.NewIntVar(1, lmax, "")
for i in range(n):
m.Add(d[i] == 10 * y[i] + z[i])
m.AddMultiplicationEquality(L, d[i], q[i])
m.AddMultiplicationEquality(p[i], x[i], q[i])
m.Add(sum(p) == L) # the sum equals 1
for v in range(1, 10): # digit v: 1..ceil(n/3)
used = [m.NewBoolVar("") for _ in range(3 * n)]
for b, g in zip(used, x + y + z):
m.Add(g == v).OnlyEnforceIf(b)
m.Add(g != v).OnlyEnforceIf(b.Not())
m.AddLinearConstraint(sum(used), 1, ceil(n / 3))
for i in range(n - 1):
m.Add(p[i] <= p[i + 1]) # order fractions (symmetry)
m.Minimize(L) # least common denominator
s = cp_model.CpSolver()
if s.Solve(m) not in (cp_model.OPTIMAL, cp_model.FEASIBLE):
return None
val = s.Value
return [(val(x[i]), val(y[i]), val(z[i])) for i in range(n)]
For the model returns the three fractions , , and nothing else: with the ceiling equal to one the counting rule forces all nine digits distinct, the symmetry order fixes their arrangement, and the common denominator comes out at , against which confirms the sum. The same code runs on without change. It finds twelve fractions inside a common denominator of only , a vivid measure of how far the least common multiple lags the product, which by then would have hundreds of digits. Past the middle teens the search rather than the size is what slows, and reaching the frontier needs the sharper machinery below.
Where it stops
Two results bracket the answer. From above, the puzzle is impossible once is large enough, and the reason is a counting bound. To make the sum as small as it can be, put the smallest digits on top and the largest underneath, with each denominator’s tens figure no less than its ones figure (a larger denominator where it costs nothing). Even this most favourable arrangement has a sum that grows with , and once it passes the equation cannot hold. Writing , the minimal sum is at least and these cross at , , and respectively, so the bound clears for every . The fractions puzzle therefore has no solution from on.
From below, a constraint search closes almost everything beneath that ceiling. Solutions exist for every from to and for , , and ; the largest fractions puzzle anyone has solved is the -fraction one. Six cases, , were neither solved nor ruled out and remain open, the multiples of three among them resisting longest because their tighter counting rule, every digit used exactly times, leaves the least room to manoeuvre. The gap between the largest solved puzzle and the proven ceiling is those six numbers, and closing it, finding a fraction or proving there is none, is the puzzle’s standing open problem.
Sources. The fractions puzzle is problem of the CSPLib, the constraint-satisfaction benchmark library, contributed by Alan Frisch, Christopher Jefferson, Ian Miguel, and Toby Walsh in ; its -fraction generalisation, the counting rule, and the symmetry-breaking orders trace through Christian Schulte and Gert Smolka and through Alan Frisch and collaborators’ studies of implied constraints. The least-common- multiple model used here, the proof that the puzzle is unsatisfiable for , and the search that solves it up to fractions while leaving open are due to Arnaud Malapert and Julien Provillard, Puzzle—Solving the -Fractions Puzzle as a Constraint Programming Problem, INFORMS Transactions on Education volume , number (), pages –, whose models reach the same frontier with a precomputed prime factorisation of each denominator in place of the bounded common multiple taken here. The unique three-fraction solution and the twelve-fraction instance are produced by the model above and re-checked in exact rational arithmetic.