Skip to content
Vamshi Jandhyala

Books · Solving Puzzles through Mathematical Programming

Chapter 3

The Praxis Rhombus

The Praxis Rhombus is a calendar puzzle with an extra axis. Where the ordinary A-Puzzle-A-Day board carries only a month and a day, the Rhombus board also carries the seven weekdays, and the solver must leave the weekday, the month, and the day uncovered. The board is a diamond of 5050 labelled cells — seven weekdays in the upper-left wedge, twelve months along a lower diagonal, and the thirty-one days filling the interior — and the pieces are ten polyominoes of total area 4747, exactly matching the 5050 cells minus the three that must be revealed.

The puzzle exists because a single additional degree of freedom multiplies the number of distinct daily configurations. The 12×3112 \times 31 grid of (month, day) cells has 372372 entries, of which 366366 are valid dates in a leap year and six (such as 31 February) are not; the 7×12×317 \times 12 \times 31 grid of (weekday, month, day) cells has 26042\,604 entries, of which 7×366=25627 \times 366 = 2\,562 are valid triples, and only about a seventh of those occur in any given year. The tiling problem grows correspondingly: instead of subtracting two cells from a rectangular board, the solver subtracts three from a diamond.

The Praxis Rhombus sits in the same family as the Calendar Puzzle but requires a slightly different model. The board is not rectangular; off-board cells form a staircase along each of the four diagonal boundaries; and the piece set has three tetrominoes mixed in among the pentominoes, so the area-accounting (34+75=473 \cdot 4 + 7 \cdot 5 = 47) is different. Everything else reduces, as before, to exact cover.

The board and the pieces

Embed the rhombus in an 8×88 \times 8 rectangular array and mark fourteen corner cells as off-board. The remaining fifty cells carry labels: Mon, Tue, …, Sun in the upper-left; Jan, Feb, …, Dec along a diagonal band; and the numerals 1,2,,311, 2, \ldots, 31 occupying the interior. A date is chosen by designating one weekday, one month, and one numeral as the reveal, leaving 4747 cells to be tiled.

The ten pieces are three tetrominoes (four cells each) and seven pentominoes (five cells each), for a total of 34+75=473 \cdot 4 + 7 \cdot 5 = 47 cells. As in any polyomino puzzle, each piece may be rotated through 00^\circ, 9090^\circ, 180180^\circ, 270270^\circ and reflected; pieces with internal symmetry have fewer distinct orientations. Two of the three tetrominoes are the familiar L and T; the third is the S (or Z) tetromino. The seven pentominoes are a subset of the twelve named Golomb pieces.

Figure 3.1 is the puzzle for Thursday 23 October and Figure 3.2 shows one tiling.

The Praxis Rhombus for Thursday 23 October: the three highlighted cells Thu, Oct, and 2323 are to be left uncovered.
One valid tiling. The ten pieces are distinguished by colour; the three reveal cells remain uncovered.

The programming model

The formulation is exact cover on the fifty on-board cells. For each piece PiP_i (i=1,,10i = 1, \ldots, 10), enumerate its distinct orientations and, for each orientation, every translation whose cells all land on the board and none of which coincide with a reveal. Each surviving (piece, orientation, translation) triple is a placement.

Placement variables

For each placement pp of piece PiP_i, introduce a Boolean variable xpx_p with xp=1x_p = 1 meaning “this placement is selected.”

One placement per piece

Each of the ten pieces is placed exactly once: p:placement of Pixp  =  1.\sum_{p \,:\, \text{placement of } P_i} x_p \;=\; 1.

Exact cover per cell

For every on-board cell (r,c)(r, c) that is not a reveal, exactly one selected placement covers it: p:(r,c)cells(p)xp  =  1.\sum_{p \,:\, (r, c) \in \text{cells}(p)} x_p \;=\; 1. Off-board cells and reveal cells are excluded from placement enumeration, so no such constraint is needed for them.

A solver in seventy lines

from ortools.sat.python import cp_model

BOARD = [
    ['',    'Mon', 'Wed', '5',  '1',  '',    '',    ''],
    ['Tue', 'Thu', 'Sat', '10', '6',  '2',   '',    ''],
    ['Fri', 'Sun', '15',  '13', '11', '7',   '3',   ''],
    ['23',  '20',  '18',  '16', '14', '12',  '8',   '4'],
    ['28',  '24',  '21',  '19', '17', 'Jan', 'Mar', '9'],
    ['',    '29',  '25',  '22', 'Feb','Apr', 'Jun', 'Aug'],
    ['',    '',    '30',  '26', 'May','Jul', 'Sep', 'Nov'],
    ['',    '',    '',    '31', '27', 'Oct', 'Dec', ''],
]

PIECES = [
    [(0,0),(0,1),(1,1),(1,2)],              # S-tetromino
    [(0,1),(1,0),(1,1),(1,2),(2,1)],        # plus
    [(0,0),(0,1),(1,0),(1,1),(2,1)],        # P
    [(0,0),(0,1),(0,2),(1,0),(2,0)],        # V
    [(0,0),(0,1),(0,2),(1,0),(1,2)],        # U
    [(0,0),(0,1),(0,2),(1,0)],              # L-tetromino
    [(0,0),(0,1),(0,2),(1,1)],              # T-tetromino
    [(0,0),(1,0),(1,1),(2,1),(2,2)],        # W
    [(0,2),(1,0),(1,1),(1,2),(2,1)],        # F
    [(0,0),(0,1),(1,1),(1,2),(1,3)],        # N
]

def norm(p):
    mr = min(r for r, _ in p)
    mc = min(c for _, c in p)
    return tuple(sorted((r - mr, c - mc) for r, c in p))

def orients(p):
    out, cur = set(), list(p)
    for _ in range(4):
        out.add(norm(cur))
        cur = [(c, -r) for r, c in cur]
    cur = [(r, -c) for r, c in p]
    for _ in range(4):
        out.add(norm(cur))
        cur = [(c, -r) for r, c in cur]
    return [list(o) for o in out]

def solve(dow, mon, day):
    H, W = 8, 8
    off = {(r, c) for r in range(H) for c in range(W)
           if BOARD[r][c] == ''
           or BOARD[r][c] in (dow, mon, day)}
    m = cp_model.CpModel()
    pls = []
    for i, piece in enumerate(PIECES):
        for o in orients(piece):
            for dr in range(H):
                for dc in range(W):
                    cells = [(r+dr, c+dc) for r, c in o]
                    if any(r < 0 or r >= H
                           or c < 0 or c >= W
                           or (r, c) in off
                           for r, c in cells):
                        continue
                    pls.append((i, cells))
    v = [m.NewBoolVar("") for _ in pls]
    for i in range(len(PIECES)):
        m.Add(sum(v[k] for k, p in enumerate(pls)
                  if p[0] == i) == 1)
    for r in range(H):
        for c in range(W):
            if (r, c) in off: continue
            m.Add(sum(v[k] for k, p in enumerate(pls)
                      if (r, c) in p[1]) == 1)
    s = cp_model.CpSolver()
    s.Solve(m)
    return [pls[k] for k, x in enumerate(v) if s.Value(x)]

The enumeration phase produces about 11001\,100 placements per date; CP-SAT selects the ten of them that form a valid tiling in roughly 6060 milliseconds on a standard laptop.

A hard instance: Friday 29 February

A given weekday for the leap day (Friday 29 February, say) recurs irregularly, on roughly a twenty-eight-year cycle but with exceptions at the Gregorian century boundaries, and the Rhombus board makes the tightest use of that coincidence. Figure 3.3 shows the board for Friday 29 February, with the three reveal cells far apart: Fri on the left diagonal, Feb in the lower-central band, and 2929 near the bottom-left boundary. All three are near the rhombus’s edges, constraining the tiling more than interior choices would.

The Praxis Rhombus for Friday 29 February.
One valid tiling for Friday 29 February, recovered in about 6060 milliseconds.

Sources. The Praxis Rhombus was released in 20232023 by an independent designer under the trade name Praxis Puzzle; its conceit — adding the weekday as a third selection axis — is a natural extension of the A-Puzzle-A-Day genre. Mathematically, both puzzles are polyomino exact-cover problems on a fixed region with a small reveal set; the Rhombus is distinguished chiefly by its diamond shape, its mixed tetromino/pentomino piece set, and the third dimension of the reveal. Enumerative counts (how many tilings per date, how many dates admit none) remain an attractive computational curiosity: the solver of the preceding section runs through all 25622\,562 valid weekday-month-day triples in well under five minutes and reports a full tiling profile.