relations: tree/DAG queries (common-ancestors, lca, topo-order) in SX + 16 tests
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 50s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 50s
lib/relations/tree.sx over reach/ancestors/rnode — no new Datalog closures. 126/126. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ PRELOADS=(
|
|||||||
lib/relations/api.sx
|
lib/relations/api.sx
|
||||||
lib/relations/explain.sx
|
lib/relations/explain.sx
|
||||||
lib/relations/federation.sx
|
lib/relations/federation.sx
|
||||||
|
lib/relations/tree.sx
|
||||||
)
|
)
|
||||||
|
|
||||||
SUITES=(
|
SUITES=(
|
||||||
@@ -27,4 +28,5 @@ SUITES=(
|
|||||||
"path:lib/relations/tests/path.sx:(relations-path-tests-run!)"
|
"path:lib/relations/tests/path.sx:(relations-path-tests-run!)"
|
||||||
"fed:lib/relations/tests/fed.sx:(relations-fed-tests-run!)"
|
"fed:lib/relations/tests/fed.sx:(relations-fed-tests-run!)"
|
||||||
"shape:lib/relations/tests/shape.sx:(relations-shape-tests-run!)"
|
"shape:lib/relations/tests/shape.sx:(relations-shape-tests-run!)"
|
||||||
|
"tree:lib/relations/tests/tree.sx:(relations-tree-tests-run!)"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
{
|
{
|
||||||
"lang": "relations",
|
"lang": "relations",
|
||||||
"total_passed": 110,
|
"total_passed": 126,
|
||||||
"total_failed": 0,
|
"total_failed": 0,
|
||||||
"total": 110,
|
"total": 126,
|
||||||
"suites": [
|
"suites": [
|
||||||
{"name":"direct","passed":22,"failed":0,"total":22},
|
{"name":"direct","passed":22,"failed":0,"total":22},
|
||||||
{"name":"reach","passed":24,"failed":0,"total":24},
|
{"name":"reach","passed":24,"failed":0,"total":24},
|
||||||
{"name":"path","passed":24,"failed":0,"total":24},
|
{"name":"path","passed":24,"failed":0,"total":24},
|
||||||
{"name":"fed","passed":22,"failed":0,"total":22},
|
{"name":"fed","passed":22,"failed":0,"total":22},
|
||||||
{"name":"shape","passed":18,"failed":0,"total":18}
|
{"name":"shape","passed":18,"failed":0,"total":18},
|
||||||
|
{"name":"tree","passed":16,"failed":0,"total":16}
|
||||||
],
|
],
|
||||||
"generated": "2026-06-07T12:55:58+00:00"
|
"generated": "2026-06-07T13:07:14+00:00"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# relations scoreboard
|
# relations scoreboard
|
||||||
|
|
||||||
**110 / 110 passing** (0 failure(s)).
|
**126 / 126 passing** (0 failure(s)).
|
||||||
|
|
||||||
| Suite | Passed | Total | Status |
|
| Suite | Passed | Total | Status |
|
||||||
|-------|--------|-------|--------|
|
|-------|--------|-------|--------|
|
||||||
@@ -9,3 +9,4 @@
|
|||||||
| path | 24 | 24 | ok |
|
| path | 24 | 24 | ok |
|
||||||
| fed | 22 | 22 | ok |
|
| fed | 22 | 22 | ok |
|
||||||
| shape | 18 | 18 | ok |
|
| shape | 18 | 18 | ok |
|
||||||
|
| tree | 16 | 16 | ok |
|
||||||
|
|||||||
206
lib/relations/tests/tree.sx
Normal file
206
lib/relations/tests/tree.sx
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
;; lib/relations/tests/tree.sx — extension: common ancestors, LCA, topo order.
|
||||||
|
|
||||||
|
(define relations-tt-pass 0)
|
||||||
|
(define relations-tt-fail 0)
|
||||||
|
(define relations-tt-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
relations-tt-check!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! relations-tt-pass (+ relations-tt-pass 1))
|
||||||
|
(do
|
||||||
|
(set! relations-tt-fail (+ relations-tt-fail 1))
|
||||||
|
(append!
|
||||||
|
relations-tt-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
relations-tt-subset?
|
||||||
|
(fn
|
||||||
|
(xs ys)
|
||||||
|
(cond
|
||||||
|
((= (len xs) 0) true)
|
||||||
|
((relations-member? (first xs) ys)
|
||||||
|
(relations-tt-subset? (rest xs) ys))
|
||||||
|
(else false))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
relations-tt-set=?
|
||||||
|
(fn
|
||||||
|
(xs ys)
|
||||||
|
(and
|
||||||
|
(= (len xs) (len ys))
|
||||||
|
(relations-tt-subset? xs ys)
|
||||||
|
(relations-tt-subset? ys xs))))
|
||||||
|
|
||||||
|
;; Is xs a valid topo order? every node appears once and no node precedes one of
|
||||||
|
;; its ancestors. We check the simpler invariant: for each edge u->v (parent),
|
||||||
|
;; u appears before v in the order.
|
||||||
|
(define
|
||||||
|
relations-tt-index-of
|
||||||
|
(fn
|
||||||
|
(x xs i)
|
||||||
|
(cond
|
||||||
|
((= (len xs) 0) -1)
|
||||||
|
((= (first xs) x) i)
|
||||||
|
(else (relations-tt-index-of x (rest xs) (+ i 1))))))
|
||||||
|
|
||||||
|
;; Diamond with an extra branch:
|
||||||
|
;; parent: a->b, a->c, b->d, c->d, b->e
|
||||||
|
;; member (different kind): m->n
|
||||||
|
(define
|
||||||
|
relations-tt-fixture
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(relations-build-db
|
||||||
|
(list
|
||||||
|
(relations-rel (quote a) (quote b) (quote parent))
|
||||||
|
(relations-rel (quote a) (quote c) (quote parent))
|
||||||
|
(relations-rel (quote b) (quote d) (quote parent))
|
||||||
|
(relations-rel (quote c) (quote d) (quote parent))
|
||||||
|
(relations-rel (quote b) (quote e) (quote parent))
|
||||||
|
(relations-rel (quote m) (quote n) (quote member))))))
|
||||||
|
|
||||||
|
;; A cyclic kind, to confirm topo-order refuses it.
|
||||||
|
(define
|
||||||
|
relations-tt-cyc-fixture
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(relations-build-db
|
||||||
|
(list
|
||||||
|
(relations-rel (quote x) (quote y) (quote parent))
|
||||||
|
(relations-rel (quote y) (quote x) (quote parent))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
relations-tt-run-all!
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(let
|
||||||
|
((db (relations-tt-fixture)) (cyc (relations-tt-cyc-fixture)))
|
||||||
|
(do
|
||||||
|
(relations-tt-check!
|
||||||
|
"common ancestors of d and e"
|
||||||
|
(relations-tt-set=?
|
||||||
|
(relations-common-ancestors
|
||||||
|
db
|
||||||
|
(quote d)
|
||||||
|
(quote e)
|
||||||
|
(quote parent))
|
||||||
|
(list (quote a) (quote b)))
|
||||||
|
true)
|
||||||
|
(relations-tt-check!
|
||||||
|
"common ancestors of b and c"
|
||||||
|
(relations-tt-set=?
|
||||||
|
(relations-common-ancestors
|
||||||
|
db
|
||||||
|
(quote b)
|
||||||
|
(quote c)
|
||||||
|
(quote parent))
|
||||||
|
(list (quote a)))
|
||||||
|
true)
|
||||||
|
(relations-tt-check!
|
||||||
|
"no common ancestors across kinds"
|
||||||
|
(relations-common-ancestors db (quote d) (quote n) (quote parent))
|
||||||
|
(list))
|
||||||
|
(relations-tt-check!
|
||||||
|
"lca of d and e is b"
|
||||||
|
(relations-tt-set=?
|
||||||
|
(relations-lca db (quote d) (quote e) (quote parent))
|
||||||
|
(list (quote b)))
|
||||||
|
true)
|
||||||
|
(relations-tt-check!
|
||||||
|
"lca of b and c is a"
|
||||||
|
(relations-tt-set=?
|
||||||
|
(relations-lca db (quote b) (quote c) (quote parent))
|
||||||
|
(list (quote a)))
|
||||||
|
true)
|
||||||
|
(relations-tt-check!
|
||||||
|
"lca of d and d-sibling-path picks deepest"
|
||||||
|
(relations-tt-set=?
|
||||||
|
(relations-lca db (quote d) (quote d) (quote parent))
|
||||||
|
(list (quote b) (quote c)))
|
||||||
|
true)
|
||||||
|
(relations-tt-check!
|
||||||
|
"no lca when unrelated"
|
||||||
|
(relations-lca db (quote a) (quote n) (quote parent))
|
||||||
|
(list))
|
||||||
|
(let
|
||||||
|
((order (relations-topo-order db (quote parent))))
|
||||||
|
(do
|
||||||
|
(relations-tt-check!
|
||||||
|
"topo order covers all nodes"
|
||||||
|
(relations-tt-set=?
|
||||||
|
order
|
||||||
|
(list (quote a) (quote b) (quote c) (quote d) (quote e)))
|
||||||
|
true)
|
||||||
|
(relations-tt-check!
|
||||||
|
"topo: a before b"
|
||||||
|
(<
|
||||||
|
(relations-tt-index-of (quote a) order 0)
|
||||||
|
(relations-tt-index-of (quote b) order 0))
|
||||||
|
true)
|
||||||
|
(relations-tt-check!
|
||||||
|
"topo: b before d"
|
||||||
|
(<
|
||||||
|
(relations-tt-index-of (quote b) order 0)
|
||||||
|
(relations-tt-index-of (quote d) order 0))
|
||||||
|
true)
|
||||||
|
(relations-tt-check!
|
||||||
|
"topo: c before d"
|
||||||
|
(<
|
||||||
|
(relations-tt-index-of (quote c) order 0)
|
||||||
|
(relations-tt-index-of (quote d) order 0))
|
||||||
|
true)
|
||||||
|
(relations-tt-check!
|
||||||
|
"topo: b before e"
|
||||||
|
(<
|
||||||
|
(relations-tt-index-of (quote b) order 0)
|
||||||
|
(relations-tt-index-of (quote e) order 0))
|
||||||
|
true)))
|
||||||
|
(relations-tt-check!
|
||||||
|
"topo order of cyclic kind is nil"
|
||||||
|
(relations-topo-order cyc (quote parent))
|
||||||
|
nil)
|
||||||
|
(do
|
||||||
|
(relations/load!
|
||||||
|
(list
|
||||||
|
(relations-rel (quote r) (quote s) (quote parent))
|
||||||
|
(relations-rel (quote r) (quote t) (quote parent))
|
||||||
|
(relations-rel (quote s) (quote u) (quote parent))
|
||||||
|
(relations-rel (quote t) (quote u) (quote parent))))
|
||||||
|
(relations-tt-check!
|
||||||
|
"api common-ancestors"
|
||||||
|
(relations-tt-set=?
|
||||||
|
(relations/common-ancestors
|
||||||
|
(quote u)
|
||||||
|
(quote u)
|
||||||
|
(quote parent))
|
||||||
|
(list (quote r) (quote s) (quote t)))
|
||||||
|
true)
|
||||||
|
(relations-tt-check!
|
||||||
|
"api lca"
|
||||||
|
(relations-tt-set=?
|
||||||
|
(relations/lca (quote s) (quote t) (quote parent))
|
||||||
|
(list (quote r)))
|
||||||
|
true)
|
||||||
|
(relations-tt-check!
|
||||||
|
"api topo-order covers nodes"
|
||||||
|
(relations-tt-set=?
|
||||||
|
(relations/topo-order (quote parent))
|
||||||
|
(list (quote r) (quote s) (quote t) (quote u)))
|
||||||
|
true)
|
||||||
|
(relations/load! (list)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
relations-tree-tests-run!
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(do
|
||||||
|
(set! relations-tt-pass 0)
|
||||||
|
(set! relations-tt-fail 0)
|
||||||
|
(set! relations-tt-failures (list))
|
||||||
|
(relations-tt-run-all!)
|
||||||
|
{:failures relations-tt-failures :total (+ relations-tt-pass relations-tt-fail) :passed relations-tt-pass :failed relations-tt-fail})))
|
||||||
118
lib/relations/tree.sx
Normal file
118
lib/relations/tree.sx
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
;; lib/relations/tree.sx — tree/DAG queries: common ancestors, LCA, topo order.
|
||||||
|
;;
|
||||||
|
;; All computed in SX over the engine's fast `reach`/`ancestors`/`rnode` queries
|
||||||
|
;; — no new Datalog closures (every dl-query re-saturates, so derived graph
|
||||||
|
;; algorithms stay in SX). Kind-parameterised throughout, like the rest of the
|
||||||
|
;; engine. LCA returns a SET (a DAG may have several lowest common ancestors; a
|
||||||
|
;; tree yields exactly one). topo-order returns nil for a cyclic kind.
|
||||||
|
|
||||||
|
(define
|
||||||
|
relations-tree-any?
|
||||||
|
(fn
|
||||||
|
(pred xs)
|
||||||
|
(cond
|
||||||
|
((= (len xs) 0) false)
|
||||||
|
((pred (first xs)) true)
|
||||||
|
(else (relations-tree-any? pred (rest xs))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
relations-intersect
|
||||||
|
(fn (xs ys) (filter (fn (x) (relations-eng-member? x ys)) xs)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
relations-subset?
|
||||||
|
(fn
|
||||||
|
(xs ys)
|
||||||
|
(cond
|
||||||
|
((= (len xs) 0) true)
|
||||||
|
((relations-eng-member? (first xs) ys)
|
||||||
|
(relations-subset? (rest xs) ys))
|
||||||
|
(else false))))
|
||||||
|
|
||||||
|
;; All nodes touched by a kind (the materialised rnode relation — one query).
|
||||||
|
(define
|
||||||
|
relations-nodes
|
||||||
|
(fn
|
||||||
|
(db kind)
|
||||||
|
(relations-dedup
|
||||||
|
(relations-pluck
|
||||||
|
(dl-query db (list (quote rnode) kind (quote X)))
|
||||||
|
:X))))
|
||||||
|
|
||||||
|
;; Common ancestors of a and b under kind (set intersection of the two
|
||||||
|
;; ancestor sets).
|
||||||
|
(define
|
||||||
|
relations-common-ancestors
|
||||||
|
(fn
|
||||||
|
(db a b kind)
|
||||||
|
(relations-intersect
|
||||||
|
(relations-ancestors db a kind)
|
||||||
|
(relations-ancestors db b kind))))
|
||||||
|
|
||||||
|
;; Lowest common ancestors: common ancestors with no other common ancestor
|
||||||
|
;; strictly below them (none reachable from them). A tree gives a singleton; a
|
||||||
|
;; DAG may give several. Empty when a and b share no ancestor.
|
||||||
|
(define
|
||||||
|
relations-lca
|
||||||
|
(fn
|
||||||
|
(db a b kind)
|
||||||
|
(let
|
||||||
|
((common (relations-common-ancestors db a b kind)))
|
||||||
|
(filter
|
||||||
|
(fn
|
||||||
|
(x)
|
||||||
|
(not
|
||||||
|
(relations-tree-any?
|
||||||
|
(fn
|
||||||
|
(y)
|
||||||
|
(and (not (= x y)) (relations-reachable? db x y kind)))
|
||||||
|
common)))
|
||||||
|
common))))
|
||||||
|
|
||||||
|
;; Kahn-style topological order: repeatedly place every node whose parents are
|
||||||
|
;; all already placed. Returns the node list in topological order, or nil if the
|
||||||
|
;; kind has a cycle.
|
||||||
|
(define
|
||||||
|
relations-topo-kahn
|
||||||
|
(fn
|
||||||
|
(db kind remaining placed)
|
||||||
|
(if
|
||||||
|
(= (len remaining) 0)
|
||||||
|
placed
|
||||||
|
(let
|
||||||
|
((ready (filter (fn (n) (relations-subset? (relations-parents-of db n kind) placed)) remaining)))
|
||||||
|
(if
|
||||||
|
(= (len ready) 0)
|
||||||
|
placed
|
||||||
|
(relations-topo-kahn
|
||||||
|
db
|
||||||
|
kind
|
||||||
|
(filter
|
||||||
|
(fn (n) (not (relations-eng-member? n ready)))
|
||||||
|
remaining)
|
||||||
|
(append placed ready)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
relations-topo-order
|
||||||
|
(fn
|
||||||
|
(db kind)
|
||||||
|
(if
|
||||||
|
(relations-acyclic? db kind)
|
||||||
|
(relations-topo-kahn db kind (relations-nodes db kind) (list))
|
||||||
|
nil)))
|
||||||
|
|
||||||
|
;; --- current-db convenience layer ---
|
||||||
|
|
||||||
|
(define
|
||||||
|
relations/common-ancestors
|
||||||
|
(fn
|
||||||
|
(a b kind)
|
||||||
|
(relations-common-ancestors (relations-ensure-db!) a b kind)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
relations/lca
|
||||||
|
(fn (a b kind) (relations-lca (relations-ensure-db!) a b kind)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
relations/topo-order
|
||||||
|
(fn (kind) (relations-topo-order (relations-ensure-db!) kind)))
|
||||||
@@ -18,7 +18,7 @@ links. Reuses `lib/datalog/` — does not reimplement the engine.
|
|||||||
|
|
||||||
## Status (rolling)
|
## Status (rolling)
|
||||||
|
|
||||||
`bash lib/relations/conformance.sh` → **110/110** (Phases 1–4 complete + extensions)
|
`bash lib/relations/conformance.sh` → **126/126** (Phases 1–4 complete + extensions)
|
||||||
|
|
||||||
## Ground rules
|
## Ground rules
|
||||||
|
|
||||||
@@ -104,9 +104,23 @@ lib/relations/federation.sx
|
|||||||
`in-degree`, weakly-connected `connected?` (undirected reachability). Computed in
|
`in-degree`, weakly-connected `connected?` (undirected reachability). Computed in
|
||||||
SX over the fast direct `erel` queries (BFS) — deliberately NOT added as Datalog
|
SX over the fast direct `erel` queries (BFS) — deliberately NOT added as Datalog
|
||||||
closures, to keep the per-query saturation cheap. `lib/relations/tests/shape.sx`.
|
closures, to keep the per-query saturation cheap. `lib/relations/tests/shape.sx`.
|
||||||
|
- [x] **tree/DAG queries** — `common-ancestors` (ancestor-set intersection), `lca`
|
||||||
|
(lowest common ancestors — a set; tree → singleton, DAG → may be several),
|
||||||
|
`topo-order` (Kahn-style; nil for cyclic kinds). New `lib/relations/tree.sx`,
|
||||||
|
computed in SX over `reach`/`ancestors`/`rnode`. `lib/relations/tests/tree.sx`.
|
||||||
|
|
||||||
## Progress log
|
## Progress log
|
||||||
|
|
||||||
|
- **Extension: tree/DAG queries** (126/126). New `lib/relations/tree.sx`:
|
||||||
|
`relations-common-ancestors` (intersection of the two ancestor sets),
|
||||||
|
`relations-lca` (common ancestors with no other common ancestor reachable below
|
||||||
|
them — a SET, since a DAG can have several lowest common ancestors; a tree gives
|
||||||
|
one), `relations-topo-order` (Kahn-style level-by-level: place every node whose
|
||||||
|
parents are all placed; nil for a cyclic kind) + `relations-nodes` (the `rnode`
|
||||||
|
set) and `relations/...` wrappers. All in SX over the engine's fast queries —
|
||||||
|
again no new Datalog closures. `tree.sx` (16 tests) covers diamond common
|
||||||
|
ancestors, LCA on tree vs converging-DAG, no-common-ancestor, topo validity
|
||||||
|
(parents precede children), and cyclic-kind → nil.
|
||||||
- **Extension: shape queries** (110/110). Added `relations-siblings`,
|
- **Extension: shape queries** (110/110). Added `relations-siblings`,
|
||||||
`relations-out-degree`/`-in-degree`, `relations-connected?` (+ `relations/...`
|
`relations-out-degree`/`-in-degree`, `relations-connected?` (+ `relations/...`
|
||||||
current-db wrappers) and `shape.sx` (18 tests). Design note: an earlier attempt
|
current-db wrappers) and `shape.sx` (18 tests). Design note: an earlier attempt
|
||||||
|
|||||||
Reference in New Issue
Block a user