Skip to main content

Explanations

The step explanations: one handler per technique, on a replayed grid. Generated from the docstrings by tools/gen_reference_md.py; public names only.

pencilmark.explain​

src/pencilmark/explain/init.py

Teachable explanations for grader moves.

from pencilmark import cbackend as CB, explain as E

steps, ok = CB.solve(p, P, solution=sol)
for i, step, state in E.replay(p, steps): # state = grid BEFORE the step
ex = E.explain(step, p, state)
print(ex["short"]); print(ex["teach"])

explain(step, puzzle, state) -> dict

short one line: "r4c7 = 3: the only place for a 3 in box 6"
teach 2-5 sentences: where to look, why it works, what it achieves
concept {"family", "technique", "name", "lesson"} (reusable across puzzles)
visual cells / units / lines / chain links to highlight in a UI
annotation placeholders for human annotation
justification the structured reason the text was rendered from (schema below)
derived True when the justification was reconstructed here (not supplied by the engine)
verified True when the reconstructed pattern reproduces the move's placement / eliminations

Justification schema (the same keys are read from the engine's step.move.explain dict when the engine exports one -- engine fields override derived ones, so richer data plugs straight in): technique, digits, placement (cell, digit), eliminations [(cell, digit)], base_units / cover_units [group index], base_cells / cover_cells / fins [cell], constraints [{"type", "index", "cells", "params"}], links [{"a": (c, d), "b": (c, d), "strong": bool, "source": "bivalue|bilocal|constraint|relation|cell|house", "unit": gi}], als [{"cells", "digits"}], rcc [digit], z digit, trial {"assume", "events", "contradiction"}.

The reconstruction works on the replayed grid (placed digits + candidates, the C++ engine's own candidate maintenance), so it needs nothing but the step stream.

pencilmark.explain.base​

src/pencilmark/explain/base.py

Shared vocabulary of the explainer: tables, naming (Namer), contexts (Ctx), result helpers. (split from explain.py)

NameWhat it does
class Namernames cells, digits and groups in step text (r1c1, row 3, box 2, ...)
def rel_labelHuman name of the rule(s) linking cells a and b ('' if none).
class Ctxwhat the explainers need about one step: its move, technique, engine explanation, namer and phrasing key
def fmt_elims'r4c1, r4c2 ≠ 3; r5c5 ≠ 1,2'.

class Namer​

source

names cells, digits and groups in step text (r1c1, row 3, box 2, ...)

def cell(self, c)​

source

cell index -> r1c1 (1-based)

def sort(self, cs)​

source

unique cells in board order

def cells(self, cs, conj='and')​

source

cell names in board order, joined with conj

def span(self, cs)​

source

Ordered line cells: 'r1c1-r1c4' (first to last).

def digits(ds, conj='or')​

source

unique digits sorted, joined with conj

def cset(mask)​

source

candidate mask -> {1,2,3}

def unit(self, gi, the=False)​

source

phrase naming group gi (row 3, box 2, the main diagonal, a cage, ...)

def cage(self, gi, cells=True)​

source

phrase naming cage gi (the 12 cage), with its cells when cells is True

def rel_label(p, a, b)​

source

Human name of the rule(s) linking cells a and b ('' if none).

class Ctx​

source

what the explainers need about one step: its move, technique, engine explanation, namer and phrasing key

def pick(self, options, salt='')​

source

one of options, always the same for the same move and salt

def gi(self, name)​

source

index of the group called name, or None

def fmt_elims(nm, elims)​

source

'r4c1, r4c2 ≠ 3; r5c5 ≠ 1,2'.

pencilmark.explain.dispatch​

src/pencilmark/explain/dispatch.py

Handler table and registration, explain(), technique keys, self-checks. (split from explain.py)

NameWhat it does
def explainExplanation dict for one solver step (state = grid before the step; see replay()).
def explain_all
def checkFactual consistency of an explanation with the move and the grid.

def explain(step, puzzle, state)​

source

Explanation dict for one solver step (state = grid before the step; see replay()).

def explain_all(puzzle, steps)​

source

No docstring.

def check(ex, step, puzzle, state)​

source

Factual consistency of an explanation with the move and the grid. -> (ok, problems).

pencilmark.explain.grid​

src/pencilmark/explain/grid.py

The replayed grid: implied houses, derived candidates, State / replay, rules, the no-notes view. (split from explain.py)

NameWhat it does
def implied_housesDerived houses exactly as the engine builds them (csolver model.cpp build_implied): unions of k = 2..4…
def derivedThe puzzle the engine actually solves: p plus its implied houses (same group indices, the implied ones…
class StateGrid before a step: placed digits, candidates (the engine's own maintenance: peers + pair relations on every…
def replayYield (index, step, state BEFORE the step); the state object is copied per step.
class RuleOne multi-cell rule (cage, line, arrow, sum clue ...) as a checkable predicate.
def rulesEvery multi-cell rule of the puzzle, keyed like the engine: ('con', index into p.constraints then detected…
def enum_ruleAll assignments of rule.cells within masks dom that satisfy the rule, the pair relations and distinctness…
def direct_sourcesWhy each digit is excluded at empty cell c from placed digits alone.
def direct_exclmask of the digits excluded at cell c by the placed digits alone
def direct_locksPointing / claiming done in the head (no notes): digit v's directly-possible cells in house B all lie in…
def mental_viewDirect-mode reasoning without notes: placed-digit exclusions, then relation arcs and whole-rule consistency…

def implied_houses(p)​

source

Derived houses exactly as the engine builds them (csolver model.cpp build_implied): unions of k = 2..4 parallel lines minus k-1 disjoint complete houses inside them that leave D cells. -> list of (cells, lines, removed group indices or 'implied#').

def derived(p)​

source

The puzzle the engine actually solves: p plus its implied houses (same group indices, the implied ones appended as 'implied1', 'implied2', ...).

class State​

source

Grid before a step: placed digits, candidates (the engine's own maintenance: peers + pair relations on every placement, eliminations applied), solving mode, and which step last removed each candidate (for 'it lost its last alternative to ...' remarks).

def initial(cls, p)​

source

State of puzzle p with its givens placed

def copy(self)​

source

independent copy of this state

def placed_g(self, gi)​

source

mask of the digits placed in group gi

def place(self, c, v, tag=None)​

source

put digit v in cell c and remove its candidates from the peers and related cells (tag is kept in lost)

def apply(self, step)​

source

apply a step's placement and eliminations (recorded in lost), update the mode, advance idx

def replay(p, steps, state=None)​

source

Yield (index, step, state BEFORE the step); the state object is copied per step.

class Rule​

source

One multi-cell rule (cage, line, arrow, sum clue ...) as a checkable predicate.

def check(self, a)​

source

True if the digits a, one per cell of the rule, satisfy it

def partial(self, a, i, dom)​

source

Prune a prefix a[0..i].

def rules(p)​

source

Every multi-cell rule of the puzzle, keyed like the engine: ('con', index into p.constraints then detected lines), ('cage', group index).

def enum_rule(p, rule, dom, node_limit=150000, keep=400, max_count=None, support=True)​

source

All assignments of rule.cells within masks dom that satisfy the rule, the pair relations and distinctness inside the scope. -> dict(sup, count, sols, complete). max_count: stop as soon as more fillings exist (-> complete False, count None); support=False: no witness search when the enumeration is incomplete (the direct-mode reading mirrors the engine's cap and skips such rules).

def direct_sources(p, st, c, digit=None)​

source

Why each digit is excluded at empty cell c from placed digits alone. -> list of (kind, mask, info). kinds: 'house' (gi, {digit: cell}), 'neq' (cell, digit), 'rel' (cell, digit), 'domain', 'cagesum' (gi).

def direct_excl(p, st, c)​

source

mask of the digits excluded at cell c by the placed digits alone

def direct_locks(p, st)​

source

Pointing / claiming done in the head (no notes): digit v's directly-possible cells in house B all lie in group A -> v excluded from A minus B. -> list of dicts.

def mental_view(p, st, level=3)​

source

Direct-mode reasoning without notes: placed-digit exclusions, then relation arcs and whole-rule consistency (two rounds), mental pairs/triples and pointing, one more round. -> (md masks, reasons {(cell, digit): reason}).

pencilmark.explain.catalog​

src/pencilmark/explain/catalog.py

The technique catalogue: names, aliases and explainer links per technique; shapes; lessons. (split from explain.py)

NameWhat it does
def candidate_urls
def technique_info
def chain_shapeName the shape of a chain: Skyscraper, 2-String Kite, Turbot Fish, Remote Pair, W-Wing, X-Chain / XY-Chain /…
def fish_shape

def candidate_urls()​

source

No docstring.

def technique_info(key)​

source

No docstring.

def chain_shape(p, st, path, place=False)​

source

Name the shape of a chain: Skyscraper, 2-String Kite, Turbot Fish, Remote Pair, W-Wing, X-Chain / XY-Chain / AIC. -> catalog key.

def fish_shape(p, base, cover, fins, U)​

source

No docstring.

pencilmark.explain.singles​

src/pencilmark/explain/singles.py

Explanations of singles, the no-notes layer and n-ary rule steps. (split from explain.py)

pencilmark.explain.combos​

src/pencilmark/explain/combos.py

Innies / outies, combinations, lines, combination locks and leftovers. (split from explain.py)

pencilmark.explain.notes​

src/pencilmark/explain/notes.py

Pencil-mark techniques: locked candidates, subsets, fish, wings, colouring; uniqueness. (split from explain.py)

pencilmark.explain.chains​

src/pencilmark/explain/chains.py

Chains, almost locked sets, death blossom, chains in the head. (split from explain.py)

pencilmark.explain.trial​

src/pencilmark/explain/trial.py

Trial, forcing and case splits; the remaining variant handlers. (split from explain.py)

pencilmark.explain.records​

src/pencilmark/explain/records.py

Techniques that explain from the engine's records (68-82 and later), empty rectangles. (split from explain.py)