Skip to content
Vamshi Jandhyala

Books · Solving Puzzles through Mathematical Programming

Chapter 17

Knights, Knaves, and the Lady

Raymond Smullyan’s puzzle books turn formal logic into story. An island is peopled by knights, who only ever speak the truth, and knaves, who only ever lie; a prisoner faces a row of doors, each bearing a sign that may be true or false, with a lady behind one and a tiger behind another. To solve such a puzzle is to find the one assignment of types and contents under which every statement carries exactly the truth value its speaker is bound to. That is a constraint-satisfaction problem, and its statements are logical propositions, so the question is whether ordinary integer programming, whose native language is the linear inequality, can express “this sentence is true if and only if its speaker is a knight.” It can, and the device that makes it possible is the indicator variable. The puzzles below follow Chlond and Toase’s study of Smullyan in the INFORMS Transactions on Education column.

Indicator variables: logic as linear constraints

The whole chapter rests on one construction. Suppose a linear expression F(x)F(x) over binary variables is known to lie between integer bounds LF(x)UL \le F(x) \le U, and we want a binary variable δ\delta to record whether a linear condition on FF holds. For the condition F(x)nF(x) \ge n, the coupling δ=1    F(x)n\delta = 1 \iff F(x) \ge n is enforced by the pair F(x)(Un+1)δn1,F(x)(nL)δL.\begin{align} F(x) - (U - n + 1)\,\delta &\le n - 1, \\ F(x) - (n - L)\,\delta &\ge L. \end{align} For the opposite condition F(x)nF(x) \le n, the coupling δ=1    F(x)n\delta = 1 \iff F(x) \le n is enforced by F(x)+(n+1L)δn+1,F(x)+(UL)δn+UL.\begin{align} F(x) + (n + 1 - L)\,\delta &\ge n + 1, \\ F(x) + (U - L)\,\delta &\le n + U - L. \end{align} Once propositions have indicator variables attached, the logical connectives between them become linear relations among those indicators. Writing δX\delta_X for the indicator of proposition XX, ¬X:δX=0,XY:δX+δY=2,XY:δX+δY1,\lnot X : \delta_X = 0, \qquad X \land Y : \delta_X + \delta_Y = 2, \qquad X \lor Y : \delta_X + \delta_Y \ge 1, XY:δXδY,XY:δX=δY.X \rightarrow Y : \delta_X \le \delta_Y, \qquad X \leftrightarrow Y : \delta_X = \delta_Y. These equivalences are catalogued in H. P. Williams’s Model Building in Mathematical Programming. A knight-or-knave puzzle is then a small program: one binary per person for the type, one per statement for its truth value, and the rule that a person’s type variable equals the truth variable of what they say.

Werewolves

In Smullyan’s forest, each inhabitant is a knight or a knave, and some are also werewolves; a werewolf may be of either type. We interview three inhabitants, AA, BB, and CC, and are told that exactly one of them is a werewolf. They say:

AA: “I am a werewolf.” BB: “I am a werewolf.” CC: “At most one of the three of us is a knight.”

The task is to classify all three.

Variables

For each person p{A,B,C}p \in \{A, B, C\} let xp=1x_p = 1 if pp is a knight (a truth-teller) and 00 if a knave, and let wp=1w_p = 1 if pp is the werewolf.

Constraints

Exactly one werewolf gives wA+wB+wC=1w_A + w_B + w_C = 1. A person is a knight precisely when their statement is true, so for AA and BB, whose claim “I am a werewolf” has truth value wAw_A and wBw_B, the coupling is the direct equality xA=wA,xB=wB.x_A = w_A, \qquad x_B = w_B. CC’s claim is the proposition “at most one knight,” that is xA+xB+xC1x_A + x_B + x_C \le 1. Taking δ=xC\delta = x_C with F=xA+xB+xCF = x_A + x_B + x_C, bounds L=0L = 0, U=3U = 3, and n=1n = 1, the FnF \le n template gives xA+xB+3xC2,xA+xB+4xC4,x_A + x_B + 3x_C \ge 2, \qquad x_A + x_B + 4x_C \le 4, which force xC=1x_C = 1 exactly when at most one of the three is a knight. There is nothing to optimise; any feasible point is a solution.

A solver in twenty lines

from ortools.sat.python import cp_model

def werewolves_two():
    m = cp_model.CpModel()
    x = {p: m.NewBoolVar(p) for p in "ABC"}        # knight = 1
    w = {p: m.NewBoolVar("w" + p) for p in "ABC"}  # werewolf = 1
    m.Add(sum(w.values()) == 1)
    m.Add(x["A"] == w["A"])                         # A: "I am a werewolf"
    m.Add(x["B"] == w["B"])                         # B: "I am a werewolf"
    # C: "at most one knight"  ->  x_C = 1 iff x_A + x_B + x_C <= 1
    m.Add(x["A"] + x["B"] + 3 * x["C"] >= 2)
    m.Add(x["A"] + x["B"] + 4 * x["C"] <= 4)
    s = cp_model.CpSolver()
    s.Solve(m)
    role = {p: ("knight" if s.Value(x[p]) else "knave") for p in "ABC"}
    wolf = next(p for p in "ABC" if s.Value(w[p]))
    return role, wolf

The solution is unique: AA and BB are knaves and CC is a knight, and the werewolf is CC. The reasoning the program compresses is short. If AA were a knight, his statement would make him the werewolf, and likewise for BB; but then there would be two knights, AA and BB, contradicting CC if CC too were a knight and leaving the werewolf count wrong in every case. The only consistent reading has AA and BB lying about being werewolves, CC telling the truth that at most one of them (namely CC) is a knight, and CC carrying the single werewolf. Figure 17.1 records it.

Werewolves II. Knaves AA and BB are shown pale, the knight CC in copper; the single werewolf is CC.

A companion puzzle, Werewolves IV, keeps the forest but changes the testimony: we are told exactly one of the three is a werewolf and that the werewolf is a knight, and now AA says “at least one of the three of us is a knave” while BB says “CC is a knight.” The werewolf constraint becomes xpwpx_p \ge w_p for each pp; AA’s claim xA+xB+xC2x_A + x_B + x_C \le 2 is linearised by the same FnF \le n template, and BB’s claim is the equality xB=xCx_B = x_C. The model returns a unique answer: AA is a knight, BB and CC are knaves, and AA is the werewolf.

The lady or the tiger

Smullyan’s other world is a prison whose trials Smullyan borrowed from Frank Stockton. A prisoner faces several doors; behind each is a lady or a tiger; he would rather find the lady. Each door bears a sign, and in any given trial a rule fixes how the signs relate to the truth. In the second trial there are two rooms, and the rule is that the two signs are either both true or both false. The signs read:

Door 11: “At least one of these rooms holds a lady.” Door 22: “A tiger is in the other room.”

Variables and constraints

Let xi=1x_i = 1 if room ii holds the lady and 00 if it holds the tiger, and let ti=1t_i = 1 if the sign on door ii is true. Door 11 asserts x1+x21x_1 + x_2 \ge 1, so with δ=t1\delta = t_1, F=x1+x2F = x_1 + x_2, L=0L = 0, U=2U = 2, n=1n = 1, the FnF \ge n template gives x1+x22t10,x1+x2t10.x_1 + x_2 - 2t_1 \le 0, \qquad x_1 + x_2 - t_1 \ge 0. Door 22 asserts that the other room, room 11, holds the tiger, which is 1x11 - x_1, so t2=1x1t_2 = 1 - x_1. The trial’s rule is the biconditional t1=t2t_1 = t_2.

A solver in fifteen lines

from ortools.sat.python import cp_model

def second_trial():
    m = cp_model.CpModel()
    x = {i: m.NewBoolVar(f"lady{i}") for i in (1, 2)}  # lady = 1
    t = {i: m.NewBoolVar(f"t{i}") for i in (1, 2)}     # sign true = 1
    # Door 1: "at least one lady"  ->  t1 = 1 iff x1 + x2 >= 1
    m.Add(x[1] + x[2] - 2 * t[1] <= 0)
    m.Add(x[1] + x[2] - t[1] >= 0)
    # Door 2: "a tiger is in the other room" = room 1 holds the tiger
    m.Add(t[2] == 1 - x[1])
    m.Add(t[1] == t[2])             # this trial: both signs share a truth value
    s = cp_model.CpSolver()
    s.Solve(m)
    return {i: ("lady" if s.Value(x[i]) else "tiger") for i in (1, 2)}

The unique solution puts the tiger in room 11 and the lady in room 22, with both signs true, so the prisoner should open door 22 (Figure 17.2). The trap the puzzle sets is the reading in which both signs are false; the model shows that reading is infeasible, because a false door 11 sign would mean neither room holds the lady, and then door 22’s sign, also false, would deny the tiger in room 11, leaving room 11 with neither occupant.

The second trial. Both signs are true; the lady is behind door 22.

A logical labyrinth

The same machinery scales to Smullyan’s hardest trial in this vein, a labyrinth of nine doors. Each room now holds a lady, a tiger, or nothing; only one room holds the lady; the sign on the lady’s door is true; the signs on the tiger doors are false; and the signs on empty rooms may be either. The nine signs refer to one another and to the room contents, so the model carries a content variable xi,jx_{i, j} for door ii and outcome j{lady,tiger,empty}j \in \{\text{lady}, \text{tiger}, \text{empty}\} and a truth variable tit_i for each sign, with every sign linearised by the templates above.

What makes the labyrinth worth the climb is not its size but a twist that integer programming states exactly. As posed, the puzzle has more than one feasible solution, so the prisoner cannot deduce where the lady is. He can only when he is also told that room 88 is not empty. Adding the single constraint x8,empty=0x_{8, \text{empty}} = 0 collapses the feasible region to one point. The missing fact is, quite literally, a missing constraint, and the puzzle is the rare riddle whose punchline is that information and constraint are the same thing. The full nine-door model is a chapter of its own; here it stands as the destination the indicator variable was built to reach.

Sources. The integer-programming treatment of these puzzles follows Martin J. Chlond and Cath M. Toase, IP Modeling and the Logical Puzzles of Raymond Smullyan, INFORMS Transactions on Education volume 33, number 33 (20032003), pages 111212. The werewolf puzzles are from Raymond Smullyan, What Is the Name of This Book? (Prentice-Hall, 19781978); the lady-or-tiger trials are from Smullyan, The Lady or the Tiger? (Alfred A. Knopf, 19821982), which builds on Frank R. Stockton’s 18821882 story of that name. The indicator-variable couplings and the linear forms of the logical connectives are catalogued in H. P. Williams, Model Building in Mathematical Programming (Wiley, 19991999).