1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
;;; Copyright (c) 2026 Nicholas Vermeulen
;;; SPDX-License-Identifier: AGPL-3.0-or-later
;; csp.lisp — finite-domain constraint solving. Pure Lisp, zero interpreter
;; changes, same library pattern as fsm.lisp / prover.lisp / synth.lisp.
;;
;; A CSP is DATA:
;; (csp-make vars domains constraints)
;; vars — a list of variable names (any equal?-comparable value)
;; domains — an alist, one entry (var val1 val2 …) per variable
;; constraints — a list of (scope pred) where scope is a list of vars and
;; pred is a function of those vars' values, in scope order,
;; returning a boolean.
;;
;; Solving is complete BACKTRACKING search in the DECLARED variable order,
;; trying each variable's domain in DECLARED order, checking every constraint
;; whose scope has just become fully assigned. Because the search explores
;; the entire tree (pruning only branches PROVEN inconsistent), the results
;; are exact over the declared domains:
;;
;; * `csp-solutions` returns EVERY solution, in a stable lexicographic order.
;; * `csp-unsat?` #t means the whole tree was exhausted with no solution —
;; a PROOF of unsatisfiability on the declared domains, not a sample.
;;
;; Two independent checks pin that the smart solver is honest:
;; * `csp-verify` cross-checks backtracking against the BRUTE cartesian
;; enumeration (the dumb exhaustive solver) — same solutions, same order.
;; * `csp-prove-unsat` hands the negated constraints to `check-exhaustive`
;; over the domains: 'verified is a real proof of unsatisfiability; on a
;; satisfiable instance the counterexamples ARE the solutions.
;;
;; Claim discipline: "solved / proven unsatisfiable over the DECLARED finite
;; domains", per the bounded-verification rule — never "no solution exists"
;; in some larger space, and never "optimal".
;; ── Construction + accessors ─────────────────────────────────────────────
; (var v0 v1 …) -> (v0 v1 …)
;; ── Assignments are alists of (var value) pairs ──────────────────────────
;; A constraint is satisfied by a (possibly partial) assignment when its
;; scope isn't fully assigned yet (nothing to check), or it is and the
;; predicate holds. Boolean-strict: only #t counts as holding.
;; ── append-all (flatten one level, order-preserving) ─────────────────────
;; ── Complete backtracking search: EVERY solution, stable order ───────────
;; ── Brute cartesian baseline: the dumb exhaustive solver ─────────────────
;; Cross-check: backtracking agrees with brute enumeration, count and order.
;; ── Prove unsatisfiability with check-exhaustive ─────────────────────────
;; Property over the domains' cartesian product: "this tuple is NOT a
;; solution". 'verified => proven unsatisfiable on the declared domains
;; (exhausted, never sampled). Otherwise the counterexamples are exactly
;; the satisfying assignments (as ((tuple) "false") entries).