; Phase 4 — Execute: effect interpreter + content-addressed memo + incremental. (define ex-RT (artdag/op-table-runner {:in (fn (params inputs) (get params :v)) :add (fn (params inputs) (+ (nth inputs 0) (nth inputs 1))) :inc (fn (params inputs) (+ 1 (first inputs)))})) ; two-leaf diamond: p,q leaves; b=inc(p); c=inc(q); d=add(b,c) (define ex-D1 (artdag/build (list (list "p" "in" (list) {:v 10}) (list "q" "in" (list) {:v 20}) (list "b" "inc" (list "p") {}) (list "c" "inc" (list "q") {}) (list "d" "add" (list "b" "c") {} true)))) ; same shape, leaf q changed (20 -> 21) (define ex-D2 (artdag/build (list (list "p" "in" (list) {:v 10}) (list "q" "in" (list) {:v 21}) (list "b" "inc" (list "p") {}) (list "c" "inc" (list "q") {}) (list "d" "add" (list "b" "c") {} true)))) ; a different dag that shares the p->b subgraph with ex-D1, plus z=inc(b) (define ex-D3 (artdag/build (list (list "p" "in" (list) {:v 10}) (list "b" "inc" (list "p") {}) (list "z" "inc" (list "b") {})))) ; ---- full execution ---- (artdag-test "full run: result is correct" (let ((cache (persist/open))) (artdag/result-of (artdag/run ex-D1 ex-RT cache) (artdag/dag-id ex-D1 "d"))) 32) (artdag-test "full run: cold cache recomputes every node" (let ((cache (persist/open))) (artdag/recompute-count (artdag/run ex-D1 ex-RT cache))) 5) (artdag-test "full run: cold cache has no hits" (let ((cache (persist/open))) (artdag/hit-count (artdag/run ex-D1 ex-RT cache))) 0) ; ---- memoization ---- (artdag-test "re-run unchanged: zero recomputes" (let ((cache (persist/open))) (begin (artdag/run ex-D1 ex-RT cache) (artdag/recompute-count (artdag/run ex-D1 ex-RT cache)))) 0) (artdag-test "re-run unchanged: all cache hits" (let ((cache (persist/open))) (begin (artdag/run ex-D1 ex-RT cache) (artdag/hit-count (artdag/run ex-D1 ex-RT cache)))) 5) (artdag-test "re-run unchanged: result preserved" (let ((cache (persist/open))) (begin (artdag/run ex-D1 ex-RT cache) (artdag/result-of (artdag/run ex-D1 ex-RT cache) (artdag/dag-id ex-D1 "d")))) 32) ; ---- incremental recompute (the keystone) ---- (artdag-test "leaf change recomputes only the dirty closure (count)" (let ((cache (persist/open))) (begin (artdag/run ex-D1 ex-RT cache) (artdag/recompute-count (artdag/run ex-D2 ex-RT cache)))) 3) (artdag-test "leaf change: unchanged nodes are cache hits" (let ((cache (persist/open))) (begin (artdag/run ex-D1 ex-RT cache) (artdag/hit-count (artdag/run ex-D2 ex-RT cache)))) 2) (artdag-test "leaf change: recomputed set is exactly q,c,d" (let ((cache (persist/open))) (begin (artdag/run ex-D1 ex-RT cache) (artdag/recomputed (artdag/run ex-D2 ex-RT cache)))) (artdag/sort-strings (list (artdag/dag-id ex-D2 "q") (artdag/dag-id ex-D2 "c") (artdag/dag-id ex-D2 "d")))) (artdag-test "leaf change: untouched sibling p is reused" (let ((cache (persist/open))) (begin (artdag/run ex-D1 ex-RT cache) (artdag/member? (artdag/dag-id ex-D2 "p") (get (artdag/run ex-D2 ex-RT cache) :hits)))) true) (artdag-test "leaf change: new result is correct" (let ((cache (persist/open))) (begin (artdag/run ex-D1 ex-RT cache) (artdag/result-of (artdag/run ex-D2 ex-RT cache) (artdag/dag-id ex-D2 "d")))) 33) ; ---- explicit dirty-only execution ---- (artdag-test "run-dirty: schedules only the changed closure" (let ((cache (persist/open))) (begin (artdag/run ex-D1 ex-RT cache) (artdag/recompute-count (artdag/run-dirty ex-D2 (list (artdag/dag-id ex-D2 "q")) ex-RT cache)))) 3) ; ---- cross-dag cache sharing (content addressing) ---- (artdag-test "shared subgraph hits cache across different dags" (let ((cache (persist/open))) (begin (artdag/run ex-D1 ex-RT cache) (artdag/recompute-count (artdag/run ex-D3 ex-RT cache)))) 1) (artdag-test "shared subgraph: p and b reused across dags" (let ((cache (persist/open))) (begin (artdag/run ex-D1 ex-RT cache) (artdag/hit-count (artdag/run ex-D3 ex-RT cache)))) 2) (artdag-test "shared subgraph: z still computes correctly" (let ((cache (persist/open))) (begin (artdag/run ex-D1 ex-RT cache) (artdag/result-of (artdag/run ex-D3 ex-RT cache) (artdag/dag-id ex-D3 "z")))) 12)