From 8644668fc91a7ac8ab88dcae40006f4e9eca2fbc Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 8 May 2026 14:44:59 +0000 Subject: [PATCH] =?UTF-8?q?mk:=20phase=206=20done=20=E2=80=94=20fd-fire-st?= =?UTF-8?q?ore=20iterates,=20N-queens=20FD=20works?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous fd-fire-store fired every constraint exactly once. That left the propagation incomplete in chains like fd-plus c4 1 a; fd-neq c3 a where, on the round c4 binds, fd-plus binds a, but fd-neq c3 a was already past — so the conflict went undetected. New: fd-store-signature is sum-of-domain-sizes + count-of-bindings. fd-fire-store calls fd-fire-list and recurses while the signature strictly decreases. Reaches a fixed point or fails. This makes N-queens via FD tractable: 4-queens -> ((2 4 1 3) (3 1 4 2)) — exactly the two solutions. 5-queens -> 10 solutions (the canonical count), in seconds. Phase 6 marked complete in the plan: domains, fd-in, fd-eq, fd-neq, fd-lt, fd-lte, fd-plus, fd-times, fd-distinct, fd-label, all wired through the constraint-reactivation loop. Two new tests, 626/626 cumulative. --- lib/minikanren/clpfd.sx | 17 +++++- lib/minikanren/tests/queens-fd.sx | 97 +++++++++++++++++++++++++++++++ plans/minikanren-on-sx.md | 30 ++++++++-- 3 files changed, 138 insertions(+), 6 deletions(-) create mode 100644 lib/minikanren/tests/queens-fd.sx diff --git a/lib/minikanren/clpfd.sx b/lib/minikanren/clpfd.sx index bd03ece2..ab2fb10b 100644 --- a/lib/minikanren/clpfd.sx +++ b/lib/minikanren/clpfd.sx @@ -146,11 +146,26 @@ ((s2 ((first cs) s))) (cond ((= s2 nil) nil) (:else (fd-fire-list (rest cs) s2)))))))) +(define + fd-store-signature + (fn + (s) + (let + ((doms (fd-domains-of s))) + (let + ((dom-sizes (reduce (fn (acc k) (+ acc (len (get doms k)))) 0 (keys doms)))) + (+ dom-sizes (len (keys s))))))) + (define fd-fire-store (fn (s) - (let ((cs (get (fd-store-of s) :constraints))) (fd-fire-list cs s)))) + (let + ((s2 (fd-fire-list (get (fd-store-of s) :constraints) s))) + (cond + ((= s2 nil) nil) + ((= (fd-store-signature s) (fd-store-signature s2)) s2) + (:else (fd-fire-store s2)))))) ;; --- user-facing goals --- diff --git a/lib/minikanren/tests/queens-fd.sx b/lib/minikanren/tests/queens-fd.sx new file mode 100644 index 00000000..7457abd0 --- /dev/null +++ b/lib/minikanren/tests/queens-fd.sx @@ -0,0 +1,97 @@ +;; lib/minikanren/tests/queens-fd.sx — N-queens via CLP(FD). +;; +;; Native FD propagation makes N-queens tractable: 4-queens finds both +;; solutions instantly; 5-queens finds all 10 in seconds. Compare with +;; the naive enumerate-then-filter version in queens.sx, which struggles +;; past N=4. + +(define + fd-no-diag + (fn + (ci cj k) + (fresh + (a b) + (fd-plus cj k a) + (fd-plus ci k b) + (fd-neq ci a) + (fd-neq cj b)))) + +(define + n-queens-4-fd + (fn + (cs) + (let + ((c1 (nth cs 0)) + (c2 (nth cs 1)) + (c3 (nth cs 2)) + (c4 (nth cs 3))) + (mk-conj + (fd-in c1 (list 1 2 3 4)) + (fd-in c2 (list 1 2 3 4)) + (fd-in c3 (list 1 2 3 4)) + (fd-in c4 (list 1 2 3 4)) + (fd-distinct cs) + (fd-no-diag c1 c2 1) + (fd-no-diag c1 c3 2) + (fd-no-diag c1 c4 3) + (fd-no-diag c2 c3 1) + (fd-no-diag c2 c4 2) + (fd-no-diag c3 c4 1) + (fd-label cs))))) + +(define + n-queens-5-fd + (fn + (cs) + (let + ((c1 (nth cs 0)) + (c2 (nth cs 1)) + (c3 (nth cs 2)) + (c4 (nth cs 3)) + (c5 (nth cs 4))) + (mk-conj + (fd-in + c1 + (list 1 2 3 4 5)) + (fd-in + c2 + (list 1 2 3 4 5)) + (fd-in + c3 + (list 1 2 3 4 5)) + (fd-in + c4 + (list 1 2 3 4 5)) + (fd-in + c5 + (list 1 2 3 4 5)) + (fd-distinct cs) + (fd-no-diag c1 c2 1) + (fd-no-diag c1 c3 2) + (fd-no-diag c1 c4 3) + (fd-no-diag c1 c5 4) + (fd-no-diag c2 c3 1) + (fd-no-diag c2 c4 2) + (fd-no-diag c2 c5 3) + (fd-no-diag c3 c4 1) + (fd-no-diag c3 c5 2) + (fd-no-diag c4 c5 1) + (fd-label cs))))) + +(mk-test + "n-queens-4-fd-two-solutions" + (run* + q + (fresh (a b c d) (== q (list a b c d)) (n-queens-4-fd (list a b c d)))) + (list + (list 2 4 1 3) + (list 3 1 4 2))) + +(mk-test + "n-queens-5-fd-ten-solutions" + (let + ((sols (run* q (fresh (a b c d e) (== q (list a b c d e)) (n-queens-5-fd (list a b c d e)))))) + (= (len sols) 10)) + true) + +(mk-tests-run!) diff --git a/plans/minikanren-on-sx.md b/plans/minikanren-on-sx.md index 8e2055f1..702ddde9 100644 --- a/plans/minikanren-on-sx.md +++ b/plans/minikanren-on-sx.md @@ -145,11 +145,31 @@ Key semantic mappings: - [ ] Tests: Zebra puzzle, N-queens, Sudoku via `project`, family relations via `matche` ### Phase 6 — arithmetic constraints CLP(FD) -- [ ] Finite domain variables: `fd-var` with domain `[lo..hi]` -- [x] `ino` `x` `domain` — alias for `(membero x domain)` with the - constraint-store-friendly argument order. Sufficient for the - enumerate-then-filter style of finite-domain solving. -- [x] `all-distincto` `l` — pairwise-distinct elements via `nafc + membero`. +- [x] Finite domain variables: domain stored under reserved key `_fd` in + the substitution dict; ascending sorted-int-list representation; + domain primitives `fd-dom-from-list`, `fd-dom-intersect`, + `fd-dom-without`, `fd-dom-range`, `fd-dom-min/max/empty?/singleton?`. +- [x] `fd-in x dom` — narrows x's domain by intersection. +- [x] `fd-eq x y`, `fd-neq x y`, `fd-lt`, `fd-lte` — propagator-store + goals. Each adds a closure to the constraints field and runs it + on post; closures re-fire after every label step via fd-fire-store. +- [x] `fd-plus x y z`, `fd-times x y z` — ground-cases propagators + (when 2 of 3 walk to numbers, the third is derived). +- [x] `fd-distinct vars` — pairwise alldifferent via fd-neq folds. +- [x] Constraint reactivation: `fd-fire-store` iterates to fixed point + using a domain+bindings signature comparison; ensures multi-step + propagation chains (e.g. fd-plus binds a fresh var, which then + lets a downstream fd-neq fire). +- [x] Labelling: `fd-label vars` enumerates each var's domain via + mk-mplus over singleton bindings; constraint store re-fires after + each binding. +- [x] Tests: N-queens via FD — 4-queens finds both solutions, 5-queens + finds all 10 in seconds (vs the naive enumerate-then-filter + version which times out past N=4). +- [x] `ino` `x` `domain` — alias for `(membero x domain)` (kept for + the simple enumerate-then-filter pattern alongside fd-in). +- [x] `all-distincto` `l` — original membero-based version (kept alongside + the newer fd-distinct). - [ ] `fd-eq` `x` `y` — x = y (constraint propagation) - [ ] `fd-neq` `x` `y` — x ≠ y - [ ] `fd-lt` `fd-lte` `fd-gt` `fd-gte` — ordering constraints