@hviana/sema 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -5
- package/CITATION.cff +49 -0
- package/HOW_IT_WORKS.md +11 -12
- package/README.md +7 -5
- package/dist/example/demo.js +25 -25
- package/dist/example/train_base.d.ts +19 -21
- package/dist/example/train_base.js +1687 -1952
- package/dist/src/alphabet.d.ts +3 -3
- package/dist/src/alphabet.js +24 -27
- package/dist/src/alu/src/alu.d.ts +169 -194
- package/dist/src/alu/src/alu.js +374 -414
- package/dist/src/alu/src/expr.d.ts +36 -43
- package/dist/src/alu/src/expr.js +252 -278
- package/dist/src/alu/src/index.d.ts +9 -98
- package/dist/src/alu/src/index.js +10 -64
- package/dist/src/alu/src/kernel-arith.d.ts +1 -5
- package/dist/src/alu/src/kernel-arith.js +195 -285
- package/dist/src/alu/src/kernel-bits.d.ts +1 -5
- package/dist/src/alu/src/kernel-bits.js +81 -115
- package/dist/src/alu/src/kernel-logic.js +33 -63
- package/dist/src/alu/src/kernel-nd.js +155 -195
- package/dist/src/alu/src/kernel-numeric.d.ts +16 -80
- package/dist/src/alu/src/kernel-numeric.js +259 -331
- package/dist/src/alu/src/operation.d.ts +112 -132
- package/dist/src/alu/src/operation.js +149 -163
- package/dist/src/alu/src/parser.d.ts +168 -175
- package/dist/src/alu/src/parser.js +382 -421
- package/dist/src/alu/src/resonance.d.ts +16 -25
- package/dist/src/alu/src/resonance.js +49 -56
- package/dist/src/alu/src/text.d.ts +3 -7
- package/dist/src/alu/src/text.js +31 -37
- package/dist/src/alu/src/value.d.ts +14 -14
- package/dist/src/alu/src/value.js +150 -160
- package/dist/src/alu/test/alu.test.js +543 -656
- package/dist/src/bytes.d.ts +1 -5
- package/dist/src/bytes.js +33 -40
- package/dist/src/config.d.ts +99 -102
- package/dist/src/config.js +78 -83
- package/dist/src/derive/src/deduction.d.ts +60 -62
- package/dist/src/derive/src/deduction.js +103 -109
- package/dist/src/derive/src/index.d.ts +1 -7
- package/dist/src/derive/src/priority-queue.d.ts +8 -8
- package/dist/src/derive/src/priority-queue.js +57 -61
- package/dist/src/derive/src/rewrite.d.ts +15 -18
- package/dist/src/derive/src/rewrite.js +59 -67
- package/dist/src/derive/src/trie.d.ts +56 -56
- package/dist/src/derive/src/trie.js +175 -179
- package/dist/src/derive/test/derive.test.js +96 -100
- package/dist/src/extension.d.ts +13 -16
- package/dist/src/geometry.d.ts +17 -42
- package/dist/src/geometry.js +235 -266
- package/dist/src/index.d.ts +2 -17
- package/dist/src/index.js +2 -12
- package/dist/src/ingest-cache.d.ts +25 -30
- package/dist/src/ingest-cache.js +108 -127
- package/dist/src/mind/articulation.d.ts +1 -5
- package/dist/src/mind/articulation.js +77 -112
- package/dist/src/mind/attention.d.ts +40 -108
- package/dist/src/mind/attention.js +749 -871
- package/dist/src/mind/canonical.d.ts +4 -19
- package/dist/src/mind/canonical.js +27 -31
- package/dist/src/mind/graph-search.d.ts +201 -225
- package/dist/src/mind/graph-search.js +742 -821
- package/dist/src/mind/index.d.ts +2 -10
- package/dist/src/mind/junction.d.ts +31 -58
- package/dist/src/mind/junction.js +172 -237
- package/dist/src/mind/learning.d.ts +16 -52
- package/dist/src/mind/learning.js +143 -165
- package/dist/src/mind/match.d.ts +20 -69
- package/dist/src/mind/match.js +259 -318
- package/dist/src/mind/mechanisms/alu.js +16 -16
- package/dist/src/mind/mechanisms/cast.d.ts +9 -13
- package/dist/src/mind/mechanisms/cast.js +363 -456
- package/dist/src/mind/mechanisms/confluence.d.ts +8 -12
- package/dist/src/mind/mechanisms/confluence.js +152 -183
- package/dist/src/mind/mechanisms/cover.d.ts +2 -8
- package/dist/src/mind/mechanisms/cover.js +148 -210
- package/dist/src/mind/mechanisms/extraction.d.ts +8 -34
- package/dist/src/mind/mechanisms/extraction.js +234 -288
- package/dist/src/mind/mechanisms/recall.d.ts +6 -10
- package/dist/src/mind/mechanisms/recall.js +126 -185
- package/dist/src/mind/mind.d.ts +129 -154
- package/dist/src/mind/mind.js +258 -288
- package/dist/src/mind/pipeline-mechanism.d.ts +112 -124
- package/dist/src/mind/pipeline-mechanism.js +161 -172
- package/dist/src/mind/pipeline.d.ts +4 -14
- package/dist/src/mind/pipeline.js +125 -189
- package/dist/src/mind/primitives.d.ts +8 -32
- package/dist/src/mind/primitives.js +99 -117
- package/dist/src/mind/rationale.d.ts +86 -98
- package/dist/src/mind/rationale.js +113 -121
- package/dist/src/mind/reasoning.d.ts +2 -13
- package/dist/src/mind/reasoning.js +129 -166
- package/dist/src/mind/recognition.d.ts +1 -4
- package/dist/src/mind/recognition.js +183 -208
- package/dist/src/mind/resonance.d.ts +5 -22
- package/dist/src/mind/resonance.js +0 -0
- package/dist/src/mind/trace.d.ts +6 -25
- package/dist/src/mind/trace.js +50 -58
- package/dist/src/mind/traverse.d.ts +16 -58
- package/dist/src/mind/traverse.js +304 -357
- package/dist/src/mind/types.d.ts +120 -127
- package/dist/src/mind/types.js +60 -69
- package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
- package/dist/src/rabitq-ivf/src/database.js +201 -0
- package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
- package/dist/src/{rabitq-hnsw → rabitq-ivf}/src/index.js +1 -3
- package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
- package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
- package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
- package/dist/src/rabitq-ivf/src/prng.js +36 -0
- package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
- package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
- package/dist/src/sema.d.ts +9 -13
- package/dist/src/sema.js +26 -40
- package/dist/src/store-sqlite.d.ts +169 -166
- package/dist/src/store-sqlite.js +762 -662
- package/dist/src/store.d.ts +586 -630
- package/dist/src/store.js +1423 -1581
- package/dist/src/vec.d.ts +5 -9
- package/dist/src/vec.js +61 -73
- package/example/train_base.ts +13 -10
- package/package.json +1 -1
- package/src/alu/README.md +1 -1
- package/src/alu/src/index.ts +1 -1
- package/src/config.ts +19 -27
- package/src/index.ts +6 -11
- package/src/rabitq-ivf/README.md +56 -0
- package/src/rabitq-ivf/src/database.ts +276 -0
- package/src/{rabitq-hnsw → rabitq-ivf}/src/index.ts +2 -5
- package/src/rabitq-ivf/src/ivf.ts +1330 -0
- package/src/{rabitq-hnsw → rabitq-ivf}/src/prng.ts +1 -1
- package/src/store-sqlite.ts +196 -9
- package/src/store.ts +8 -32
- package/test/08-storage.test.mjs +3 -3
- package/test/14-scaling.test.mjs +2 -2
- package/test/35-ivf.test.mjs +263 -0
- package/test/36-bloom.test.mjs +123 -0
- package/dist/src/rabitq-hnsw/src/database.d.ts +0 -202
- package/dist/src/rabitq-hnsw/src/database.js +0 -405
- package/dist/src/rabitq-hnsw/src/heap.d.ts +0 -22
- package/dist/src/rabitq-hnsw/src/heap.js +0 -94
- package/dist/src/rabitq-hnsw/src/hnsw.d.ts +0 -125
- package/dist/src/rabitq-hnsw/src/hnsw.js +0 -500
- package/dist/src/rabitq-hnsw/src/index.d.ts +0 -15
- package/dist/src/rabitq-hnsw/src/prng.d.ts +0 -19
- package/dist/src/rabitq-hnsw/src/prng.js +0 -38
- package/dist/src/rabitq-hnsw/src/rabitq.d.ts +0 -95
- package/dist/src/rabitq-hnsw/src/rabitq.js +0 -299
- package/dist/src/rabitq-hnsw/src/store.d.ts +0 -162
- package/dist/src/rabitq-hnsw/src/store.js +0 -916
- package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +0 -1
- package/dist/src/rabitq-hnsw/test/hnsw.test.js +0 -1197
- package/src/rabitq-hnsw/README.md +0 -303
- package/src/rabitq-hnsw/src/database.ts +0 -492
- package/src/rabitq-hnsw/src/heap.ts +0 -90
- package/src/rabitq-hnsw/src/hnsw.ts +0 -514
- package/src/rabitq-hnsw/src/store.ts +0 -994
- package/src/rabitq-hnsw/test/hnsw.test.ts +0 -1213
- /package/src/{rabitq-hnsw → rabitq-ivf}/src/rabitq.ts +0 -0
|
@@ -9,57 +9,50 @@ import { cosine } from "../vec.js";
|
|
|
9
9
|
import { gistOf, read } from "./primitives.js";
|
|
10
10
|
const structCaches = new WeakMap();
|
|
11
11
|
function getStructCache(ctx) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
},
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
return c;
|
|
12
|
+
if (ctx.climbMemo === null)
|
|
13
|
+
return null;
|
|
14
|
+
let c = structCaches.get(ctx.climbMemo);
|
|
15
|
+
if (c === undefined) {
|
|
16
|
+
structCaches.set(ctx.climbMemo, c = {
|
|
17
|
+
hasNext: new Map(),
|
|
18
|
+
prevCount: new Map(),
|
|
19
|
+
hasParents: new Map(),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return c;
|
|
27
23
|
}
|
|
28
24
|
/** Cached {@link Store.hasNext} — pure during one respond(). */
|
|
29
25
|
function cachedHasNext(ctx, id, cache) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return v;
|
|
26
|
+
if (cache === null)
|
|
27
|
+
return ctx.store.hasNext(id);
|
|
28
|
+
let v = cache.hasNext.get(id);
|
|
29
|
+
if (v === undefined) {
|
|
30
|
+
v = ctx.store.hasNext(id);
|
|
31
|
+
cache.hasNext.set(id, v);
|
|
32
|
+
}
|
|
33
|
+
return v;
|
|
39
34
|
}
|
|
40
35
|
/** Cached {@link Store.prevCount} — pure during one respond(). */
|
|
41
36
|
function cachedPrevCount(ctx, id, cache) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return v;
|
|
37
|
+
if (cache === null)
|
|
38
|
+
return ctx.store.prevCount(id);
|
|
39
|
+
let v = cache.prevCount.get(id);
|
|
40
|
+
if (v === undefined) {
|
|
41
|
+
v = ctx.store.prevCount(id);
|
|
42
|
+
cache.prevCount.set(id, v);
|
|
43
|
+
}
|
|
44
|
+
return v;
|
|
51
45
|
}
|
|
52
46
|
/** Cached {@link Store.hasParents} — pure during one respond(). */
|
|
53
47
|
function cachedHasParents(ctx, id, cache) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return v;
|
|
48
|
+
if (cache === null)
|
|
49
|
+
return ctx.store.hasParents(id);
|
|
50
|
+
let v = cache.hasParents.get(id);
|
|
51
|
+
if (v === undefined) {
|
|
52
|
+
v = ctx.store.hasParents(id);
|
|
53
|
+
cache.hasParents.set(id, v);
|
|
54
|
+
}
|
|
55
|
+
return v;
|
|
63
56
|
}
|
|
64
57
|
// ── Graph climbing ───────────────────────────────────────────────────────
|
|
65
58
|
/** Climb the structural DAG from a node to its edge-bearing ancestor contexts.
|
|
@@ -74,165 +67,152 @@ function cachedHasParents(ctx, id, cache) {
|
|
|
74
67
|
* chunk's prefixes — so without the memo every repeat re-pays the full
|
|
75
68
|
* fan-out reads. */
|
|
76
69
|
export function edgeAncestors(ctx, id, contextCount, memo) {
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
ctxSeen.add(p);
|
|
133
|
-
}
|
|
134
|
-
if (ctxSeen.size > bound) {
|
|
135
|
-
return false; // decided
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
const parents = ctx.store.parentsFirst(x, bound + 1);
|
|
139
|
-
if (parents.length > bound) {
|
|
140
|
-
return false; // decided: hub
|
|
141
|
-
}
|
|
142
|
-
let fresh = 0;
|
|
143
|
-
for (const p of parents) {
|
|
144
|
-
if (!seen.has(p)) {
|
|
145
|
-
seen.add(p);
|
|
146
|
-
stack.push(p);
|
|
147
|
-
fresh++;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
if (fresh > 1) {
|
|
151
|
-
lateral += fresh - 1;
|
|
152
|
-
if (lateral > bound) {
|
|
153
|
-
return false; // decided: cone-wide hub
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return true;
|
|
157
|
-
};
|
|
158
|
-
const stack = [];
|
|
159
|
-
const containment = !cachedHasParents(ctx, id, structCache);
|
|
160
|
-
if (!containment) {
|
|
161
|
-
stack.push(id);
|
|
162
|
-
}
|
|
163
|
-
// The containment seed is STREAMED in pages of √N: a distinctive window's
|
|
164
|
-
// containers (which converge on one or two contexts, however many chunks
|
|
165
|
-
// of one deposit repeat it) are walked IN FULL — exact — while a common
|
|
166
|
-
// window's corpus-sized container list is abandoned at the first decision
|
|
167
|
-
// above, after O(√N) pages at most (each page adds containers whose climbs
|
|
168
|
-
// add contexts; √N distinct contexts decide).
|
|
169
|
-
let containerOff = 0;
|
|
170
|
-
let containersExhausted = !containment;
|
|
171
|
-
climb: for (;;) {
|
|
172
|
-
if (stack.length === 0) {
|
|
173
|
-
if (containersExhausted) {
|
|
174
|
-
break;
|
|
175
|
-
}
|
|
176
|
-
const page = ctx.store.containersSlice(id, containerOff, bound);
|
|
177
|
-
containerOff += page.length;
|
|
178
|
-
if (page.length < bound) {
|
|
179
|
-
containersExhausted = true;
|
|
180
|
-
}
|
|
181
|
-
for (const c of page) {
|
|
182
|
-
if (!seen.has(c)) {
|
|
183
|
-
seen.add(c);
|
|
184
|
-
stack.push(c);
|
|
70
|
+
const hit = memo?.get(id);
|
|
71
|
+
if (hit !== undefined)
|
|
72
|
+
return hit;
|
|
73
|
+
const bound = Math.ceil(Math.sqrt(contextCount));
|
|
74
|
+
const roots = [];
|
|
75
|
+
const seen = new Set([id]);
|
|
76
|
+
const ctxSeen = new Set();
|
|
77
|
+
let saturated = false;
|
|
78
|
+
// EXPAND-UNTIL-DECIDED: a reach is consumed either as a VOTE (which needs
|
|
79
|
+
// contextsReached exactly, and only while ≤ √N — beyond that the region is
|
|
80
|
+
// non-discriminative) or as an ABSTENTION (saturated — whose roots and
|
|
81
|
+
// counts no consumer reads). So the climb may STOP the moment the answer
|
|
82
|
+
// is decided:
|
|
83
|
+
// • a node whose prev fan-in alone exceeds √N decides it (its
|
|
84
|
+
// predecessors are √N+ distinct contexts) — no read needed, prevCount
|
|
85
|
+
// is an indexed O(1);
|
|
86
|
+
// • distinct contexts crossing √N decides it;
|
|
87
|
+
// • a node with more than √N parents decides its own expansion (the
|
|
88
|
+
// classic hub guard; the walk aborts rather than continue, which no
|
|
89
|
+
// consumer can distinguish — saturated reaches are never voted).
|
|
90
|
+
// Below every decision threshold the walk is EXACT — identical roots and
|
|
91
|
+
// contexts to the unbounded climb — because prevFirst(√N) IS the full prev
|
|
92
|
+
// list and parentsFirst(√N+1) IS the full parent list whenever they do not
|
|
93
|
+
// decide. Work is bounded by √N contexts × the climb's local structure,
|
|
94
|
+
// never by the corpus.
|
|
95
|
+
const structCache = getStructCache(ctx);
|
|
96
|
+
// LATERAL-BRANCH ACCOUNT — the cumulative dual of the per-node hub guard.
|
|
97
|
+
// Within one deposit the ascent is a CHAIN (each node's first parent);
|
|
98
|
+
// every parent BEYOND a node's first is an entry into another containing
|
|
99
|
+
// structure (hash-consing: a shared subtree's extra parents are other
|
|
100
|
+
// deposits' chunks). The per-node guard already declares a node with more
|
|
101
|
+
// than √N parents non-discriminative; a climb whose ACCUMULATED lateral
|
|
102
|
+
// entries exceed √N has spread across just as many distinct containing
|
|
103
|
+
// structures — the same commonness, distributed along the cone instead of
|
|
104
|
+
// concentrated at one node — and is decided: saturated. A deep chain in
|
|
105
|
+
// ONE structure accrues no laterals, so legitimate deep scaffolding (a
|
|
106
|
+
// fragment far down a long cumulative context) still climbs to its root
|
|
107
|
+
// at any depth; what dies is the cross-structure drift that visited tens
|
|
108
|
+
// of thousands of edge-free interiors (profiled on a 17.7M-node store:
|
|
109
|
+
// ~20K distinct nodes per climb family, >95% unique — not memoisable)
|
|
110
|
+
// while the context account never decided.
|
|
111
|
+
let lateral = 0;
|
|
112
|
+
const visit = (x) => {
|
|
113
|
+
const hasNx = cachedHasNext(ctx, x, structCache);
|
|
114
|
+
const pc = cachedPrevCount(ctx, x, structCache);
|
|
115
|
+
if (hasNx || pc > 0) {
|
|
116
|
+
roots.push(x);
|
|
117
|
+
if (hasNx)
|
|
118
|
+
ctxSeen.add(x);
|
|
119
|
+
if (pc > bound)
|
|
120
|
+
return false; // decided: ≥ pc > √N distinct contexts
|
|
121
|
+
for (const p of ctx.store.prevFirst(x, bound))
|
|
122
|
+
ctxSeen.add(p);
|
|
123
|
+
if (ctxSeen.size > bound)
|
|
124
|
+
return false; // decided
|
|
185
125
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
126
|
+
const parents = ctx.store.parentsFirst(x, bound + 1);
|
|
127
|
+
if (parents.length > bound)
|
|
128
|
+
return false; // decided: hub
|
|
129
|
+
let fresh = 0;
|
|
130
|
+
for (const p of parents) {
|
|
131
|
+
if (!seen.has(p)) {
|
|
132
|
+
seen.add(p);
|
|
133
|
+
stack.push(p);
|
|
134
|
+
fresh++;
|
|
135
|
+
}
|
|
192
136
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
// TRANSPARENT-CHAIN HOP: a node with no edges in or out and exactly one
|
|
198
|
-
// parent contributes nothing here — no root, no context, no lateral
|
|
199
|
-
// entry — so the run to its first non-transparent ancestor is skipped
|
|
200
|
-
// in ONE store read (Store.chainRun) instead of three probes per node.
|
|
201
|
-
// The interior nodes still enter `seen`, exactly as a node-at-a-time
|
|
202
|
-
// ascent would have recorded them at push time, so sibling entries into
|
|
203
|
-
// the same chain keep identical fresh/lateral accounting; and if the
|
|
204
|
-
// terminal was already seen (another chain merged into this one first),
|
|
205
|
-
// it is not visited twice — the same dedup the push-time seen-check
|
|
206
|
-
// used to provide.
|
|
207
|
-
const run = ctx.store.chainRun(x);
|
|
208
|
-
if (run.length > 1) {
|
|
209
|
-
const top = run[run.length - 1];
|
|
210
|
-
const dup = seen.has(top);
|
|
211
|
-
for (let i = 1; i < run.length; i++) {
|
|
212
|
-
seen.add(run[i]);
|
|
137
|
+
if (fresh > 1) {
|
|
138
|
+
lateral += fresh - 1;
|
|
139
|
+
if (lateral > bound)
|
|
140
|
+
return false; // decided: cone-wide hub
|
|
213
141
|
}
|
|
214
|
-
|
|
215
|
-
|
|
142
|
+
return true;
|
|
143
|
+
};
|
|
144
|
+
const stack = [];
|
|
145
|
+
const containment = !cachedHasParents(ctx, id, structCache);
|
|
146
|
+
if (!containment)
|
|
147
|
+
stack.push(id);
|
|
148
|
+
// The containment seed is STREAMED in pages of √N: a distinctive window's
|
|
149
|
+
// containers (which converge on one or two contexts, however many chunks
|
|
150
|
+
// of one deposit repeat it) are walked IN FULL — exact — while a common
|
|
151
|
+
// window's corpus-sized container list is abandoned at the first decision
|
|
152
|
+
// above, after O(√N) pages at most (each page adds containers whose climbs
|
|
153
|
+
// add contexts; √N distinct contexts decide).
|
|
154
|
+
let containerOff = 0;
|
|
155
|
+
let containersExhausted = !containment;
|
|
156
|
+
climb: for (;;) {
|
|
157
|
+
if (stack.length === 0) {
|
|
158
|
+
if (containersExhausted)
|
|
159
|
+
break;
|
|
160
|
+
const page = ctx.store.containersSlice(id, containerOff, bound);
|
|
161
|
+
containerOff += page.length;
|
|
162
|
+
if (page.length < bound)
|
|
163
|
+
containersExhausted = true;
|
|
164
|
+
for (const c of page) {
|
|
165
|
+
if (!seen.has(c)) {
|
|
166
|
+
seen.add(c);
|
|
167
|
+
stack.push(c);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (stack.length === 0) {
|
|
171
|
+
if (containerOff === 0)
|
|
172
|
+
stack.push(id); // no containers at all
|
|
173
|
+
else
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
while (stack.length > 0) {
|
|
178
|
+
let x = stack.pop();
|
|
179
|
+
// TRANSPARENT-CHAIN HOP: a node with no edges in or out and exactly one
|
|
180
|
+
// parent contributes nothing here — no root, no context, no lateral
|
|
181
|
+
// entry — so the run to its first non-transparent ancestor is skipped
|
|
182
|
+
// in ONE store read (Store.chainRun) instead of three probes per node.
|
|
183
|
+
// The interior nodes still enter `seen`, exactly as a node-at-a-time
|
|
184
|
+
// ascent would have recorded them at push time, so sibling entries into
|
|
185
|
+
// the same chain keep identical fresh/lateral accounting; and if the
|
|
186
|
+
// terminal was already seen (another chain merged into this one first),
|
|
187
|
+
// it is not visited twice — the same dedup the push-time seen-check
|
|
188
|
+
// used to provide.
|
|
189
|
+
const run = ctx.store.chainRun(x);
|
|
190
|
+
if (run.length > 1) {
|
|
191
|
+
const top = run[run.length - 1];
|
|
192
|
+
const dup = seen.has(top);
|
|
193
|
+
for (let i = 1; i < run.length; i++)
|
|
194
|
+
seen.add(run[i]);
|
|
195
|
+
if (dup)
|
|
196
|
+
continue;
|
|
197
|
+
x = top;
|
|
198
|
+
}
|
|
199
|
+
if (!visit(x)) {
|
|
200
|
+
saturated = true;
|
|
201
|
+
break climb;
|
|
202
|
+
}
|
|
216
203
|
}
|
|
217
|
-
x = top;
|
|
218
|
-
}
|
|
219
|
-
if (!visit(x)) {
|
|
220
|
-
saturated = true;
|
|
221
|
-
break climb;
|
|
222
|
-
}
|
|
223
204
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return reach;
|
|
205
|
+
const reach = { roots, contextsReached: ctxSeen.size, saturated };
|
|
206
|
+
memo?.set(id, reach);
|
|
207
|
+
return reach;
|
|
228
208
|
}
|
|
229
209
|
/** Convenience: forward edges of a node. */
|
|
230
210
|
export function nextOf(ctx, id) {
|
|
231
|
-
|
|
211
|
+
return ctx.store.next(id);
|
|
232
212
|
}
|
|
233
213
|
/** Convenience: reverse edges of a node. */
|
|
234
214
|
export function prevOf(ctx, id) {
|
|
235
|
-
|
|
215
|
+
return ctx.store.prev(id);
|
|
236
216
|
}
|
|
237
217
|
/** Whether a node LEADS SOMEWHERE — it bears a continuation edge or a halo.
|
|
238
218
|
* The admission predicate recognition filters sites with (HOW_IT_WORKS
|
|
@@ -243,11 +223,10 @@ export function prevOf(ctx, id) {
|
|
|
243
223
|
* indexed point probe per candidate, and the candidates that reach this
|
|
244
224
|
* check have already been filtered by hasNext above in edgeAncestors. */
|
|
245
225
|
export function leadsSomewhere(ctx, id) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
return ctx.store.hasHalo(id);
|
|
226
|
+
const memo = getStructCache(ctx);
|
|
227
|
+
if (cachedHasNext(ctx, id, memo))
|
|
228
|
+
return true;
|
|
229
|
+
return ctx.store.hasHalo(id);
|
|
251
230
|
}
|
|
252
231
|
/** The structural IDF read of ONE node: how many distinct learnt contexts
|
|
253
232
|
* its containment/edge climb reaches, or Infinity when it reaches none or
|
|
@@ -257,25 +236,24 @@ export function leadsSomewhere(ctx, id) {
|
|
|
257
236
|
* reaching a corpus MINORITY of contexts discriminates (an entity, a
|
|
258
237
|
* filler); content reaching a majority is frame scaffolding. */
|
|
259
238
|
export function reachOf(ctx, id, contextCount, memo) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
return Math.max(1, r.contextsReached);
|
|
239
|
+
const r = edgeAncestors(ctx, id, contextCount, memo);
|
|
240
|
+
if (r.saturated || r.roots.length === 0)
|
|
241
|
+
return Infinity;
|
|
242
|
+
return Math.max(1, r.contextsReached);
|
|
265
243
|
}
|
|
266
244
|
/** The corpus scale N — the count of DISTINCT learnt contexts, floored at 2
|
|
267
245
|
* so its derived readings (ln N in the consensus floor, √N in the hub bound)
|
|
268
246
|
* stay meaningful on a near-empty store. The one definition every consumer
|
|
269
247
|
* of "how big is this corpus?" reads. */
|
|
270
248
|
export function corpusN(ctx) {
|
|
271
|
-
|
|
249
|
+
return Math.max(2, ctx.store.edgeSourceCount());
|
|
272
250
|
}
|
|
273
251
|
/** The hub bound √N itself (≥ 2 always, since N is floored at 2) — for
|
|
274
252
|
* consumers that pass it to the store's LIMITed reads instead of capping a
|
|
275
253
|
* materialised list. {@link hubCap} is the list-side reading of the same
|
|
276
254
|
* convention. */
|
|
277
255
|
export function hubBound(ctx) {
|
|
278
|
-
|
|
256
|
+
return Math.ceil(Math.sqrt(corpusN(ctx)));
|
|
279
257
|
}
|
|
280
258
|
/** Cap a candidate list at the hub bound √N (insertion order) — the ONE
|
|
281
259
|
* fan-out convention every walk and disambiguation uses (see HOW_IT_WORKS
|
|
@@ -283,35 +261,32 @@ export function hubBound(ctx) {
|
|
|
283
261
|
* connections carry ~no discriminative information; materialising or scoring
|
|
284
262
|
* them all would make single decisions scale with the corpus. */
|
|
285
263
|
export function hubCap(ctx, ids) {
|
|
286
|
-
|
|
287
|
-
|
|
264
|
+
const bound = hubBound(ctx);
|
|
265
|
+
return ids.length > bound ? ids.slice(0, bound) : ids;
|
|
288
266
|
}
|
|
289
267
|
/** Whether `descendant` lies within `ancestor`'s subtree — a structural DAG
|
|
290
268
|
* relation read off the hash-consed `kids` lists, by a bounded explicit-stack
|
|
291
269
|
* descent. Used by articulation to keep a voice from revoicing a fragment
|
|
292
270
|
* OF that voice. */
|
|
293
271
|
export function contains(ctx, ancestor, descendant) {
|
|
294
|
-
|
|
295
|
-
return true;
|
|
296
|
-
}
|
|
297
|
-
const seen = new Set([ancestor]);
|
|
298
|
-
const stack = [ancestor];
|
|
299
|
-
while (stack.length > 0) {
|
|
300
|
-
const rec = ctx.store.get(stack.pop());
|
|
301
|
-
if (!rec?.kids) {
|
|
302
|
-
continue;
|
|
303
|
-
}
|
|
304
|
-
for (const k of rec.kids) {
|
|
305
|
-
if (k === descendant) {
|
|
272
|
+
if (ancestor === descendant)
|
|
306
273
|
return true;
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
stack.
|
|
311
|
-
|
|
274
|
+
const seen = new Set([ancestor]);
|
|
275
|
+
const stack = [ancestor];
|
|
276
|
+
while (stack.length > 0) {
|
|
277
|
+
const rec = ctx.store.get(stack.pop());
|
|
278
|
+
if (!rec?.kids)
|
|
279
|
+
continue;
|
|
280
|
+
for (const k of rec.kids) {
|
|
281
|
+
if (k === descendant)
|
|
282
|
+
return true;
|
|
283
|
+
if (!seen.has(k)) {
|
|
284
|
+
seen.add(k);
|
|
285
|
+
stack.push(k);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
312
288
|
}
|
|
313
|
-
|
|
314
|
-
return false;
|
|
289
|
+
return false;
|
|
315
290
|
}
|
|
316
291
|
// ── Edge disambiguation (Section 6) ──────────────────────────────────────
|
|
317
292
|
/** The best-scoring item by cosine against `query`, among items scoring at
|
|
@@ -320,61 +295,51 @@ export function contains(ctx, ancestor, descendant) {
|
|
|
320
295
|
* caller needs: `true` keeps the first-seen leader on a tie (`>`), the
|
|
321
296
|
* default lets a later equal score take it (`>=`). */
|
|
322
297
|
export function argmaxBy(items, scoreOf, threshold, strict = false) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
298
|
+
let best = null;
|
|
299
|
+
for (const item of items) {
|
|
300
|
+
const score = scoreOf(item);
|
|
301
|
+
const bar = best?.score ?? threshold;
|
|
302
|
+
if (strict ? score > bar : score >= bar)
|
|
303
|
+
best = { item, score };
|
|
329
304
|
}
|
|
330
|
-
|
|
331
|
-
return best;
|
|
305
|
+
return best;
|
|
332
306
|
}
|
|
333
307
|
export function argmaxCosine(query, items, vecOf, threshold, strict = false) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
return v ? cosine(query, v) : -Infinity;
|
|
339
|
-
},
|
|
340
|
-
threshold,
|
|
341
|
-
strict,
|
|
342
|
-
);
|
|
308
|
+
return argmaxBy(items, (item) => {
|
|
309
|
+
const v = vecOf(item);
|
|
310
|
+
return v ? cosine(query, v) : -Infinity;
|
|
311
|
+
}, threshold, strict);
|
|
343
312
|
}
|
|
344
313
|
/** The guided-or-first continuation of a node, as answer-shaped bytes source:
|
|
345
314
|
* chooseNext under the response guide, falling back to the FIRST-inserted
|
|
346
315
|
* edge — the one no-guide convention chooseNext, project() and the search's
|
|
347
316
|
* formRules all share. undefined when the node has no continuation. */
|
|
348
317
|
export function guidedFirst(ctx, id) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
return nx.length > 0 ? nx[0] : undefined;
|
|
318
|
+
const pick = guidedNext(ctx, id);
|
|
319
|
+
if (pick !== undefined)
|
|
320
|
+
return pick;
|
|
321
|
+
// No guide in flight (or nothing chosen): the first-inserted edge, read
|
|
322
|
+
// with LIMIT 1 — never the full fan-out.
|
|
323
|
+
const nx = ctx.store.nextFirst(id, 1);
|
|
324
|
+
return nx.length > 0 ? nx[0] : undefined;
|
|
357
325
|
}
|
|
358
326
|
export function guidedNext(ctx, node) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
return memo === -1 ? undefined : memo;
|
|
327
|
+
if (ctx._edgeGuide === null)
|
|
328
|
+
return undefined;
|
|
329
|
+
// The pick memo is BYPASSED while a rationale trace is attached — the same
|
|
330
|
+
// policy climbMemo and recogniseMemo follow (every mechanism must emit its
|
|
331
|
+
// own steps; a memo hit would swallow the repeat's `disambiguate` step).
|
|
332
|
+
// Consistency does not need the memo: chooseNext is a pure function of the
|
|
333
|
+
// (read-only) store and the guide, so recomputation yields the same pick.
|
|
334
|
+
if (!ctx.trace) {
|
|
335
|
+
const memo = ctx._edgeChoice.get(node);
|
|
336
|
+
if (memo !== undefined)
|
|
337
|
+
return memo === -1 ? undefined : memo;
|
|
371
338
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
return pick;
|
|
339
|
+
const pick = chooseNext(ctx, node, ctx._edgeGuide);
|
|
340
|
+
if (!ctx.trace)
|
|
341
|
+
ctx._edgeChoice.set(node, pick ?? -1);
|
|
342
|
+
return pick;
|
|
378
343
|
}
|
|
379
344
|
/** Disambiguate among a node's learnt continuations by distributional
|
|
380
345
|
* support. NOTE the `guide` contract: its VALUE is deliberately unused —
|
|
@@ -389,71 +354,61 @@ export function guidedNext(ctx, node) {
|
|
|
389
354
|
* therefore scores by guide cosine. The two directions consult different
|
|
390
355
|
* halves of the evidence on purpose. */
|
|
391
356
|
export function chooseNext(ctx, id, guide) {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
357
|
+
// CAPPED read: only the first √N continuations are ever candidates (the
|
|
358
|
+
// documented hub trade), so only they are read — a hub context's full
|
|
359
|
+
// fan-out is corpus-sized and must never be materialised. hubBound ≥ 2,
|
|
360
|
+
// so the single-continuation fast path below stays exact.
|
|
361
|
+
const nx = ctx.store.nextFirst(id, hubBound(ctx));
|
|
362
|
+
if (nx.length === 0)
|
|
363
|
+
return undefined;
|
|
364
|
+
if (nx.length === 1 || !guide)
|
|
365
|
+
return nx[0];
|
|
366
|
+
// Cap candidates at √N — the same bound the original chooseAmong used.
|
|
367
|
+
// A hub context can accumulate thousands of continuations; the best-fit
|
|
368
|
+
// one is among the first √N by insertion order (edges are never deleted,
|
|
369
|
+
// so the oldest are the most established). A strongly-supported edge
|
|
370
|
+
// inserted beyond the cap is invisible here — the deliberate trade
|
|
371
|
+
// against paying O(fan-out) count reads on every disambiguation.
|
|
372
|
+
const capped = nx; // already the hub-capped prefix, by the read above
|
|
373
|
+
// Distributional-evidence disambiguation, consulting BOTH read-outs of the
|
|
374
|
+
// evidence the training poured:
|
|
375
|
+
// 1. prevCount — how many DISTINCT contexts predict this candidate (one
|
|
376
|
+
// indexed COUNT; never a materialisation — a common continuation's
|
|
377
|
+
// reverse fan-in is corpus-sized). Diversity of independent evidence
|
|
378
|
+
// is the primary signal: three different formulations agreeing beat
|
|
379
|
+
// one formulation repeated.
|
|
380
|
+
// 2. haloMass — how many episode signatures were poured into the
|
|
381
|
+
// candidate's halo (repetition counts). The tie-break among equally
|
|
382
|
+
// diverse candidates: a fact reinforced across many episodes is more
|
|
383
|
+
// corroborated than one seen once, and this is the DIRECT measure of
|
|
384
|
+
// that — consulting only the structural count would leave poured
|
|
385
|
+
// evidence on the table.
|
|
386
|
+
// When both are equal, first-inserted wins (backward compatible).
|
|
387
|
+
let best = capped[0];
|
|
388
|
+
let bestSupport = ctx.store.prevCount(best);
|
|
389
|
+
let bestMass = ctx.store.haloMass(best);
|
|
390
|
+
for (let i = 1; i < capped.length; i++) {
|
|
391
|
+
const support = ctx.store.prevCount(capped[i]);
|
|
392
|
+
if (support < bestSupport)
|
|
393
|
+
continue;
|
|
394
|
+
const mass = ctx.store.haloMass(capped[i]);
|
|
395
|
+
if (support > bestSupport || mass > bestMass) {
|
|
396
|
+
best = capped[i];
|
|
397
|
+
bestSupport = support;
|
|
398
|
+
bestMass = mass;
|
|
399
|
+
}
|
|
431
400
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
401
|
+
// Trace is built lazily — the filter + map below only execute when a
|
|
402
|
+
// trace listener is attached, so the common (no-trace) path pays only
|
|
403
|
+
// for the prevCount calls in the loop above, never for extra rItemShort
|
|
404
|
+
// byte-reads.
|
|
405
|
+
if (ctx.trace) {
|
|
406
|
+
const others = capped.filter((c) => c !== best);
|
|
407
|
+
ctx.trace.step("disambiguate", [rItemShort(ctx, best, "halo-evidence", bestSupport)], others.map((c) => rItemShort(ctx, c, "candidate", ctx.store.prevCount(c))), `${capped.length} continuations — distributional evidence selects ` +
|
|
408
|
+
`the most corroborated (distinct contexts ${bestSupport}, ` +
|
|
409
|
+
`poured mass ${bestMass})`);
|
|
437
410
|
}
|
|
438
|
-
|
|
439
|
-
// Trace is built lazily — the filter + map below only execute when a
|
|
440
|
-
// trace listener is attached, so the common (no-trace) path pays only
|
|
441
|
-
// for the prevCount calls in the loop above, never for extra rItemShort
|
|
442
|
-
// byte-reads.
|
|
443
|
-
if (ctx.trace) {
|
|
444
|
-
const others = capped.filter((c) => c !== best);
|
|
445
|
-
ctx.trace.step(
|
|
446
|
-
"disambiguate",
|
|
447
|
-
[rItemShort(ctx, best, "halo-evidence", bestSupport)],
|
|
448
|
-
others.map((c) =>
|
|
449
|
-
rItemShort(ctx, c, "candidate", ctx.store.prevCount(c))
|
|
450
|
-
),
|
|
451
|
-
`${capped.length} continuations — distributional evidence selects ` +
|
|
452
|
-
`the most corroborated (distinct contexts ${bestSupport}, ` +
|
|
453
|
-
`poured mass ${bestMass})`,
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
|
-
return best;
|
|
411
|
+
return best;
|
|
457
412
|
}
|
|
458
413
|
/** The perceived gist of a candidate node, through the session gist cache.
|
|
459
414
|
* Re-gisting a candidate is a full river fold of its bytes — the measured
|
|
@@ -463,38 +418,30 @@ export function chooseNext(ctx, id, guide) {
|
|
|
463
418
|
* every "score node ids against a guide" decision (chooseAmong here, the
|
|
464
419
|
* bridge's junction pick) so they share ONE cache and one convention. */
|
|
465
420
|
export function candidateGist(ctx, c) {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
ctx._gistCache.set(c, g);
|
|
476
|
-
return g;
|
|
421
|
+
const hit = ctx._gistCache.get(c);
|
|
422
|
+
if (hit !== undefined)
|
|
423
|
+
return hit;
|
|
424
|
+
const b = read(ctx, c);
|
|
425
|
+
if (b.length === 0)
|
|
426
|
+
return null;
|
|
427
|
+
const g = gistOf(ctx, b);
|
|
428
|
+
ctx._gistCache.set(c, g);
|
|
429
|
+
return g;
|
|
477
430
|
}
|
|
478
431
|
export function chooseAmong(ctx, candidates, guide) {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
-Infinity,
|
|
485
|
-
true,
|
|
486
|
-
);
|
|
487
|
-
return found
|
|
488
|
-
? { id: found.item, score: found.score }
|
|
489
|
-
: { id: candidates[0], score: -Infinity };
|
|
432
|
+
const capped = hubCap(ctx, candidates);
|
|
433
|
+
const found = argmaxCosine(guide, capped, (c) => candidateGist(ctx, c), -Infinity, true);
|
|
434
|
+
return found
|
|
435
|
+
? { id: found.item, score: found.score }
|
|
436
|
+
: { id: candidates[0], score: -Infinity };
|
|
490
437
|
}
|
|
491
438
|
// ── Trace shim (used by chooseNext before trace module is loaded) ────────
|
|
492
439
|
import { decodeText } from "./rationale.js";
|
|
493
440
|
function rItemShort(ctx, id, role, score) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
441
|
+
return {
|
|
442
|
+
text: decodeText(read(ctx, id)),
|
|
443
|
+
node: id,
|
|
444
|
+
role,
|
|
445
|
+
score,
|
|
446
|
+
};
|
|
500
447
|
}
|