@hviana/sema 0.5.2 → 0.5.4

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.
Files changed (157) hide show
  1. package/AGENTS.md +115 -53
  2. package/HOW_IT_WORKS.md +291 -184
  3. package/jsr.json +6 -0
  4. package/package.json +1 -1
  5. package/src/mind/bridge.ts +10 -104
  6. package/src/mind/match.ts +497 -7
  7. package/src/mind/{prefix-completion.ts → mechanisms/prefix-completion.ts} +66 -92
  8. package/src/mind/mechanisms/recall.ts +38 -129
  9. package/src/mind/mechanisms/reference.ts +343 -0
  10. package/src/mind/mind.ts +12 -8
  11. package/src/mind/pipeline-mechanism.ts +120 -3
  12. package/src/mind/pipeline.ts +16 -2
  13. package/src/mind/traverse.ts +92 -1
  14. package/src/store.ts +13 -4
  15. package/test/33-multi-candidate.test.mjs +21 -11
  16. package/test/70-prefix-completion.test.mjs +1 -1
  17. package/test/72-prefix-candidate-supply.test.mjs +7 -9
  18. package/test/74-prefix-trap-not-sprung-early.test.mjs +1 -1
  19. package/test/76-reference-binding.test.mjs +543 -0
  20. package/dist/example/demo.d.ts +0 -1
  21. package/dist/example/demo.js +0 -39
  22. package/dist/example/train_base.d.ts +0 -93
  23. package/dist/example/train_base.js +0 -2269
  24. package/dist/src/alphabet.d.ts +0 -7
  25. package/dist/src/alphabet.js +0 -33
  26. package/dist/src/alu/src/alu.d.ts +0 -185
  27. package/dist/src/alu/src/alu.js +0 -440
  28. package/dist/src/alu/src/expr.d.ts +0 -61
  29. package/dist/src/alu/src/expr.js +0 -318
  30. package/dist/src/alu/src/index.d.ts +0 -11
  31. package/dist/src/alu/src/index.js +0 -19
  32. package/dist/src/alu/src/kernel-arith.d.ts +0 -16
  33. package/dist/src/alu/src/kernel-arith.js +0 -264
  34. package/dist/src/alu/src/kernel-bits.d.ts +0 -19
  35. package/dist/src/alu/src/kernel-bits.js +0 -152
  36. package/dist/src/alu/src/kernel-logic.d.ts +0 -4
  37. package/dist/src/alu/src/kernel-logic.js +0 -60
  38. package/dist/src/alu/src/kernel-nd.d.ts +0 -3
  39. package/dist/src/alu/src/kernel-nd.js +0 -208
  40. package/dist/src/alu/src/kernel-numeric.d.ts +0 -54
  41. package/dist/src/alu/src/kernel-numeric.js +0 -366
  42. package/dist/src/alu/src/operation.d.ts +0 -168
  43. package/dist/src/alu/src/operation.js +0 -189
  44. package/dist/src/alu/src/parser.d.ts +0 -221
  45. package/dist/src/alu/src/parser.js +0 -577
  46. package/dist/src/alu/src/resonance.d.ts +0 -55
  47. package/dist/src/alu/src/resonance.js +0 -126
  48. package/dist/src/alu/src/text.d.ts +0 -31
  49. package/dist/src/alu/src/text.js +0 -73
  50. package/dist/src/alu/src/value.d.ts +0 -109
  51. package/dist/src/alu/src/value.js +0 -300
  52. package/dist/src/alu/test/alu.test.d.ts +0 -1
  53. package/dist/src/alu/test/alu.test.js +0 -764
  54. package/dist/src/bytes.d.ts +0 -14
  55. package/dist/src/bytes.js +0 -59
  56. package/dist/src/canon.d.ts +0 -45
  57. package/dist/src/canon.js +0 -85
  58. package/dist/src/config.d.ts +0 -111
  59. package/dist/src/config.js +0 -91
  60. package/dist/src/derive/src/deduction.d.ts +0 -136
  61. package/dist/src/derive/src/deduction.js +0 -159
  62. package/dist/src/derive/src/index.d.ts +0 -8
  63. package/dist/src/derive/src/index.js +0 -11
  64. package/dist/src/derive/src/priority-queue.d.ts +0 -20
  65. package/dist/src/derive/src/priority-queue.js +0 -73
  66. package/dist/src/derive/src/rewrite.d.ts +0 -56
  67. package/dist/src/derive/src/rewrite.js +0 -100
  68. package/dist/src/derive/src/trie.d.ts +0 -90
  69. package/dist/src/derive/src/trie.js +0 -217
  70. package/dist/src/derive/test/derive.test.d.ts +0 -1
  71. package/dist/src/derive/test/derive.test.js +0 -122
  72. package/dist/src/extension.d.ts +0 -37
  73. package/dist/src/extension.js +0 -7
  74. package/dist/src/geometry.d.ts +0 -233
  75. package/dist/src/geometry.js +0 -1089
  76. package/dist/src/index.d.ts +0 -17
  77. package/dist/src/index.js +0 -19
  78. package/dist/src/ingest-cache.d.ts +0 -41
  79. package/dist/src/ingest-cache.js +0 -165
  80. package/dist/src/meter.d.ts +0 -176
  81. package/dist/src/meter.js +0 -274
  82. package/dist/src/mind/articulation.d.ts +0 -6
  83. package/dist/src/mind/articulation.js +0 -99
  84. package/dist/src/mind/attention.d.ts +0 -430
  85. package/dist/src/mind/attention.js +0 -2418
  86. package/dist/src/mind/bridge.d.ts +0 -39
  87. package/dist/src/mind/bridge.js +0 -998
  88. package/dist/src/mind/canonical.d.ts +0 -34
  89. package/dist/src/mind/canonical.js +0 -93
  90. package/dist/src/mind/frame-filler.d.ts +0 -15
  91. package/dist/src/mind/frame-filler.js +0 -535
  92. package/dist/src/mind/graph-search.d.ts +0 -294
  93. package/dist/src/mind/graph-search.js +0 -996
  94. package/dist/src/mind/index.d.ts +0 -9
  95. package/dist/src/mind/index.js +0 -5
  96. package/dist/src/mind/junction.d.ts +0 -137
  97. package/dist/src/mind/junction.js +0 -358
  98. package/dist/src/mind/learning.d.ts +0 -75
  99. package/dist/src/mind/learning.js +0 -265
  100. package/dist/src/mind/match.d.ts +0 -192
  101. package/dist/src/mind/match.js +0 -666
  102. package/dist/src/mind/mechanisms/alu.d.ts +0 -4
  103. package/dist/src/mind/mechanisms/alu.js +0 -36
  104. package/dist/src/mind/mechanisms/cast.d.ts +0 -89
  105. package/dist/src/mind/mechanisms/cast.js +0 -1186
  106. package/dist/src/mind/mechanisms/confluence.d.ts +0 -24
  107. package/dist/src/mind/mechanisms/confluence.js +0 -271
  108. package/dist/src/mind/mechanisms/cover.d.ts +0 -6
  109. package/dist/src/mind/mechanisms/cover.js +0 -232
  110. package/dist/src/mind/mechanisms/extraction.d.ts +0 -33
  111. package/dist/src/mind/mechanisms/extraction.js +0 -327
  112. package/dist/src/mind/mechanisms/recall.d.ts +0 -16
  113. package/dist/src/mind/mechanisms/recall.js +0 -558
  114. package/dist/src/mind/mind.d.ts +0 -386
  115. package/dist/src/mind/mind.js +0 -754
  116. package/dist/src/mind/pipeline-mechanism.d.ts +0 -183
  117. package/dist/src/mind/pipeline-mechanism.js +0 -521
  118. package/dist/src/mind/pipeline.d.ts +0 -49
  119. package/dist/src/mind/pipeline.js +0 -331
  120. package/dist/src/mind/prefix-completion.d.ts +0 -59
  121. package/dist/src/mind/prefix-completion.js +0 -270
  122. package/dist/src/mind/primitives.d.ts +0 -85
  123. package/dist/src/mind/primitives.js +0 -333
  124. package/dist/src/mind/rationale.d.ts +0 -139
  125. package/dist/src/mind/rationale.js +0 -163
  126. package/dist/src/mind/reasoning.d.ts +0 -42
  127. package/dist/src/mind/reasoning.js +0 -305
  128. package/dist/src/mind/recognition.d.ts +0 -20
  129. package/dist/src/mind/recognition.js +0 -696
  130. package/dist/src/mind/resonance.d.ts +0 -35
  131. package/dist/src/mind/resonance.js +0 -365
  132. package/dist/src/mind/trace.d.ts +0 -15
  133. package/dist/src/mind/trace.js +0 -73
  134. package/dist/src/mind/traverse.d.ts +0 -158
  135. package/dist/src/mind/traverse.js +0 -716
  136. package/dist/src/mind/types.d.ts +0 -418
  137. package/dist/src/mind/types.js +0 -179
  138. package/dist/src/rabitq-ivf/src/database.d.ts +0 -113
  139. package/dist/src/rabitq-ivf/src/database.js +0 -201
  140. package/dist/src/rabitq-ivf/src/index.d.ts +0 -7
  141. package/dist/src/rabitq-ivf/src/index.js +0 -4
  142. package/dist/src/rabitq-ivf/src/ivf.d.ts +0 -200
  143. package/dist/src/rabitq-ivf/src/ivf.js +0 -1165
  144. package/dist/src/rabitq-ivf/src/prng.d.ts +0 -19
  145. package/dist/src/rabitq-ivf/src/prng.js +0 -36
  146. package/dist/src/rabitq-ivf/src/rabitq.d.ts +0 -95
  147. package/dist/src/rabitq-ivf/src/rabitq.js +0 -313
  148. package/dist/src/sema.d.ts +0 -41
  149. package/dist/src/sema.js +0 -77
  150. package/dist/src/store-sqlite.d.ts +0 -184
  151. package/dist/src/store-sqlite.js +0 -942
  152. package/dist/src/store.d.ts +0 -762
  153. package/dist/src/store.js +0 -1823
  154. package/dist/src/vec.d.ts +0 -31
  155. package/dist/src/vec.js +0 -109
  156. package/src/mind/frame-filler.ts +0 -604
  157. package/test/69-frame-filler.test.mjs +0 -115
@@ -1,159 +0,0 @@
1
- /**
2
- * Knuth's lightest-derivation algorithm with an A* outside bound.
3
- *
4
- * A *weighted deduction system* (equivalently an implicit AND-OR hypergraph) is
5
- * a set of items combined by inference rules
6
- *
7
- * premise₁ ∧ … ∧ premiseₖ --localCost--> conclusion
8
- *
9
- * where a derivation's cost is the sum of the local costs of the rules used.
10
- * {@link lightestDerivation} finds a minimum-cost derivation of a goal item.
11
- * The engine is the Dijkstra-like core of Knuth (1977) — an item's cost is
12
- * final the moment it is popped — extended with an admissible heuristic so that
13
- * partial derivations which cannot lead cheaply to the goal are never expanded
14
- * (A* parsing). It is completely generic: it knows nothing of what items are,
15
- * only how to canonicalise them, enumerate their rules, score them, and test
16
- * the goal.
17
- *
18
- * The four reductions the search relies on:
19
- * 1. **Canonical chart memoization** — items are keyed by {@link
20
- * DeductionSystem.key}; equivalent partial derivations collapse to one
21
- * chart entry, the cheapest.
22
- * 2. **Backward demand filtering** — {@link DeductionSystem.rules} only emits
23
- * rules whose conclusion can still reach the goal, so work unrelated to the
24
- * goal is never generated.
25
- * 3. **A* lower-bound pruning** — {@link DeductionSystem.heuristic} keeps the
26
- * agenda ordered by g + h, so only competitive items are expanded.
27
- * 4. **Lazy hyperedge generation** — rules (including bridges) are produced by
28
- * `rules` only when one of their premises is finalised, never up front.
29
- *
30
- * Correctness conditions (the caller must uphold these):
31
- * - Local costs are non-negative (more generally, monotone / superior).
32
- * - The heuristic never overestimates the remaining cost to a goal
33
- * (admissible) and is hyperedge-consistent:
34
- * h(conclusion) ≤ ruleCost + Σ h(premiseᵢ)
35
- * i.e. relaxing a rule cannot decrease f. The default heuristic (0) is
36
- * trivially consistent and turns the search into plain Knuth/Dijkstra.
37
- * - {@link DeductionSystem.key} preserves every part of an item that can
38
- * affect how it later combines (its "boundary signature"); anything the key
39
- * drops is asserted to be irrelevant to future composition.
40
- */
41
- import { MinHeap } from "./priority-queue.js";
42
- /**
43
- * Find a lightest derivation of a goal item, or `null` if none exists.
44
- * `cost` on the returned root is the total derivation cost.
45
- */
46
- export function lightestDerivation(system, stats) {
47
- const g = new Map(); // best known cost per item
48
- const proof = new Map(); // producing rule per item
49
- const items = new Map(); // key → the item it stands for
50
- const hCache = new Map();
51
- const agenda = new MinHeap();
52
- const heuristic = system.heuristic;
53
- const h = (item, key) => {
54
- if (!heuristic)
55
- return 0;
56
- let v = hCache.get(key);
57
- if (v === undefined) {
58
- v = heuristic(item);
59
- hCache.set(key, v);
60
- }
61
- return v;
62
- };
63
- const costOf = (item) => g.get(system.key(item)) ?? Infinity;
64
- const relax = (item, cost, rule) => {
65
- const key = system.key(item);
66
- if (rule?.combine === "sum" && system.pool) {
67
- // Evidence pooling: accumulate this firing rather than compete for the
68
- // cheapest — see {@link Rule.combine}. The premises are already
69
- // finalised (the caller only relaxes a rule once every premise's cost
70
- // is known), so their derivations can be read back immediately.
71
- const premises = rule.premises.map((p) => reconstruct(p, system, g, proof));
72
- const prior = system.pool.get(key);
73
- system.pool.set(key, {
74
- item,
75
- cost: (prior?.cost ?? 0) + cost,
76
- contributions: [...(prior?.contributions ?? []), { rule, premises }],
77
- });
78
- return;
79
- }
80
- const current = g.get(key);
81
- if (current === undefined || cost < current) {
82
- g.set(key, cost);
83
- proof.set(key, rule);
84
- items.set(key, item);
85
- if (stats)
86
- stats.pushes++;
87
- agenda.push(cost + h(item, key), { key, g: cost });
88
- }
89
- };
90
- for (const { item, cost } of system.axioms())
91
- relax(item, cost, null);
92
- while (agenda.size > 0) {
93
- const { value } = agenda.pop();
94
- if (stats)
95
- stats.pops++;
96
- const key = value.key;
97
- // Lazy deletion: an entry is stale if a cheaper derivation has since been
98
- // recorded for the same item.
99
- if (value.g !== g.get(key))
100
- continue;
101
- const item = items.get(key);
102
- if (system.isGoal(item)) {
103
- return reconstruct(item, system, g, proof);
104
- }
105
- for (const rule of system.rules(item, costOf)) {
106
- let sum = rule.cost;
107
- let ready = true;
108
- for (const p of rule.premises) {
109
- const pc = g.get(system.key(p));
110
- if (pc === undefined) {
111
- ready = false;
112
- break;
113
- }
114
- sum += pc;
115
- }
116
- if (ready)
117
- relax(rule.conclusion, sum, rule);
118
- }
119
- }
120
- return null;
121
- }
122
- function reconstruct(item, system, g, proof) {
123
- // Iterative post-order over the derivation hypergraph. In the rewrite
124
- // search every rule has one premise, so the derivation is a chain whose
125
- // length equals the number of frontier edges — which, with long inputs,
126
- // can exceed the call stack. Multi-premise rules (the test-suite bridge
127
- // case) are handled by the same explicit stack.
128
- const done = new Map();
129
- const stack = [item];
130
- while (stack.length > 0) {
131
- const cur = stack[stack.length - 1]; // peek
132
- const key = system.key(cur);
133
- if (done.has(key)) {
134
- stack.pop();
135
- continue;
136
- }
137
- const rule = proof.get(key) ?? null;
138
- const premises = rule?.premises ?? [];
139
- // Push any unresolved premises (rightmost first → leftmost resolves first).
140
- let pending = false;
141
- for (let i = premises.length - 1; i >= 0; i--) {
142
- if (!done.has(system.key(premises[i]))) {
143
- stack.push(premises[i]);
144
- pending = true;
145
- }
146
- }
147
- if (!pending) {
148
- stack.pop(); // this item
149
- const kids = premises.map((p) => done.get(system.key(p)));
150
- done.set(key, {
151
- item: cur,
152
- cost: g.get(key),
153
- rule,
154
- premises: kids,
155
- });
156
- }
157
- }
158
- return done.get(system.key(item));
159
- }
@@ -1,8 +0,0 @@
1
- export { lightestDerivation } from "./deduction.js";
2
- export type { SearchStats } from "./deduction.js";
3
- export type { DeductionSystem, Derivation, PooledConclusion, PooledContribution, Rule, } from "./deduction.js";
4
- export { coverSequence } from "./rewrite.js";
5
- export type { CandidateSpan, Cover } from "./rewrite.js";
6
- export { Trie } from "./trie.js";
7
- export type { Match } from "./trie.js";
8
- export { MinHeap } from "./priority-queue.js";
@@ -1,11 +0,0 @@
1
- // derive — A* lightest-derivation rewrite search.
2
- //
3
- // A small, self-contained library for finding minimum-cost derivations in a
4
- // weighted deduction system (an implicit AND-OR hypergraph), with on-demand
5
- // (lazy) rule generation and an admissible A* outside bound. It has no
6
- // dependency on the rest of the codebase and is reusable for any
7
- // symbolic-rewriting mechanism. See ./../README.md for the design.
8
- export { lightestDerivation } from "./deduction.js";
9
- export { coverSequence } from "./rewrite.js";
10
- export { Trie } from "./trie.js";
11
- export { MinHeap } from "./priority-queue.js";
@@ -1,20 +0,0 @@
1
- /**
2
- * Binary min-heap keyed by a numeric priority, carrying an arbitrary payload.
3
- *
4
- * It is the agenda of the lightest-derivation search, where the priority is the
5
- * estimate f = g + h. Stale entries are tolerated by the consumer (lazy
6
- * deletion), so there is no decrease-key: when an item's cost improves it is
7
- * simply pushed again, and the older, higher-priority copy is recognised as
8
- * stale and discarded when it surfaces. Parallel arrays (rather than an array
9
- * of objects) keep the hot path allocation-free.
10
- */
11
- export declare class MinHeap<T> {
12
- private readonly keys;
13
- private readonly vals;
14
- get size(): number;
15
- push(priority: number, value: T): void;
16
- pop(): {
17
- priority: number;
18
- value: T;
19
- } | undefined;
20
- }
@@ -1,73 +0,0 @@
1
- /**
2
- * Binary min-heap keyed by a numeric priority, carrying an arbitrary payload.
3
- *
4
- * It is the agenda of the lightest-derivation search, where the priority is the
5
- * estimate f = g + h. Stale entries are tolerated by the consumer (lazy
6
- * deletion), so there is no decrease-key: when an item's cost improves it is
7
- * simply pushed again, and the older, higher-priority copy is recognised as
8
- * stale and discarded when it surfaces. Parallel arrays (rather than an array
9
- * of objects) keep the hot path allocation-free.
10
- */
11
- export class MinHeap {
12
- keys = [];
13
- vals = [];
14
- get size() {
15
- return this.keys.length;
16
- }
17
- push(priority, value) {
18
- const keys = this.keys;
19
- const vals = this.vals;
20
- let i = keys.length;
21
- keys.push(priority);
22
- vals.push(value);
23
- while (i > 0) {
24
- const parent = (i - 1) >> 1;
25
- if (keys[i] < keys[parent]) {
26
- const tk = keys[i];
27
- keys[i] = keys[parent];
28
- keys[parent] = tk;
29
- const tv = vals[i];
30
- vals[i] = vals[parent];
31
- vals[parent] = tv;
32
- i = parent;
33
- }
34
- else
35
- break;
36
- }
37
- }
38
- pop() {
39
- const keys = this.keys;
40
- const vals = this.vals;
41
- const n = keys.length;
42
- if (n === 0)
43
- return undefined;
44
- const top = { priority: keys[0], value: vals[0] };
45
- const lastKey = keys.pop();
46
- const lastVal = vals.pop();
47
- const m = keys.length;
48
- if (m > 0) {
49
- keys[0] = lastKey;
50
- vals[0] = lastVal;
51
- let i = 0;
52
- for (;;) {
53
- const left = 2 * i + 1;
54
- const right = left + 1;
55
- let best = i;
56
- if (left < m && keys[left] < keys[best])
57
- best = left;
58
- if (right < m && keys[right] < keys[best])
59
- best = right;
60
- if (best === i)
61
- break;
62
- const tk = keys[i];
63
- keys[i] = keys[best];
64
- keys[best] = tk;
65
- const tv = vals[i];
66
- vals[i] = vals[best];
67
- vals[best] = tv;
68
- i = best;
69
- }
70
- }
71
- return top;
72
- }
73
- }
@@ -1,56 +0,0 @@
1
- /**
2
- * Sequence segmentation on the lightest-derivation engine.
3
- *
4
- * The only structure here is the **frontier**: the items are the positions
5
- * `0…length` of a sequence, and a derivation is a path through them. That is
6
- * what keeps the search linear in the input rather than quadratic — there is no
7
- * enumeration of span *pairs* and no chart of O(n²) sub-sequences.
8
- *
9
- * Candidate spans are produced **on demand**: when the search finalises a
10
- * position it asks a {@link Trie} which learned forms begin there. The trie walk
11
- * stops exactly where the data's patterns stop — a position with nothing learned
12
- * dead-ends immediately, a position inside a long form walks precisely that far.
13
- * Nothing is scanned ahead of demand and no length bound is imposed; the
14
- * structure of what was learned is the bound.
15
- *
16
- * {@link coverSequence} is the segmentation primitive: the lightest set of
17
- * non-overlapping spans covering a sequence. It is the principled,
18
- * corpus-independent replacement for "scan the whole stream with an automaton,
19
- * then greedily keep the longest non-overlapping matches" — same linear cost,
20
- * but the *optimal* cover and only the work the goal demands.
21
- */
22
- /** A candidate span over a sequence, carrying caller payload. */
23
- export interface CandidateSpan<P> {
24
- /** Start offset (inclusive). */
25
- start: number;
26
- /** End offset (exclusive); must be > start. */
27
- end: number;
28
- /** Relative cost of using this span (default 1). Lower wins ties. */
29
- weight?: number;
30
- /** Caller data, returned on the chosen spans. */
31
- payload: P;
32
- }
33
- export interface Cover<P> {
34
- /** Chosen non-overlapping spans, left to right. */
35
- spans: Array<CandidateSpan<P>>;
36
- /** Symbols covered by the chosen spans. */
37
- covered: number;
38
- /** Symbols left uncovered. */
39
- uncovered: number;
40
- }
41
- /**
42
- * The lightest non-overlapping cover of `[0, length)` drawn from `candidates`.
43
- *
44
- * Primary objective: cover the most symbols (fewest left uncovered). Secondary:
45
- * least total span weight — with the default unit weight this prefers fewer,
46
- * longer spans, the optimal analogue of greedy longest-match, but it can return
47
- * a pair of shorter spans when together they cover more than one long one (which
48
- * greedy cannot). Modelled as a shortest path over frontier positions: from a
49
- * position you either leave one symbol uncovered (costly) or take a candidate
50
- * that starts there (cheap), reaching `length`.
51
- *
52
- * Cost: O((length + |candidates|) · log length) — the frontier has `length + 1`
53
- * items, each finalised once, with one "skip" edge plus the candidates that
54
- * start there. No quadratic span structure.
55
- */
56
- export declare function coverSequence<P>(length: number, candidates: ReadonlyArray<CandidateSpan<P>>): Cover<P>;
@@ -1,100 +0,0 @@
1
- /**
2
- * Sequence segmentation on the lightest-derivation engine.
3
- *
4
- * The only structure here is the **frontier**: the items are the positions
5
- * `0…length` of a sequence, and a derivation is a path through them. That is
6
- * what keeps the search linear in the input rather than quadratic — there is no
7
- * enumeration of span *pairs* and no chart of O(n²) sub-sequences.
8
- *
9
- * Candidate spans are produced **on demand**: when the search finalises a
10
- * position it asks a {@link Trie} which learned forms begin there. The trie walk
11
- * stops exactly where the data's patterns stop — a position with nothing learned
12
- * dead-ends immediately, a position inside a long form walks precisely that far.
13
- * Nothing is scanned ahead of demand and no length bound is imposed; the
14
- * structure of what was learned is the bound.
15
- *
16
- * {@link coverSequence} is the segmentation primitive: the lightest set of
17
- * non-overlapping spans covering a sequence. It is the principled,
18
- * corpus-independent replacement for "scan the whole stream with an automaton,
19
- * then greedily keep the longest non-overlapping matches" — same linear cost,
20
- * but the *optimal* cover and only the work the goal demands.
21
- */
22
- import { lightestDerivation } from "./deduction.js";
23
- /**
24
- * The lightest non-overlapping cover of `[0, length)` drawn from `candidates`.
25
- *
26
- * Primary objective: cover the most symbols (fewest left uncovered). Secondary:
27
- * least total span weight — with the default unit weight this prefers fewer,
28
- * longer spans, the optimal analogue of greedy longest-match, but it can return
29
- * a pair of shorter spans when together they cover more than one long one (which
30
- * greedy cannot). Modelled as a shortest path over frontier positions: from a
31
- * position you either leave one symbol uncovered (costly) or take a candidate
32
- * that starts there (cheap), reaching `length`.
33
- *
34
- * Cost: O((length + |candidates|) · log length) — the frontier has `length + 1`
35
- * items, each finalised once, with one "skip" edge plus the candidates that
36
- * start there. No quadratic span structure.
37
- */
38
- export function coverSequence(length, candidates) {
39
- if (length <= 0)
40
- return { spans: [], covered: 0, uncovered: 0 };
41
- // Candidates indexed by where they start, and by their exact (start,end) edge
42
- // for reconstruction. Both are O(|candidates|) to build and to read.
43
- const byStart = Array.from({ length }, () => []);
44
- const byEdge = new Map();
45
- let maxWeight = 1;
46
- for (const c of candidates) {
47
- if (c.end <= c.start || c.start < 0 || c.end > length)
48
- continue;
49
- const w = c.weight ?? 1;
50
- if (w > maxWeight)
51
- maxWeight = w;
52
- byStart[c.start].push(c);
53
- const edge = c.start * (length + 1) + c.end;
54
- const prev = byEdge.get(edge);
55
- if (!prev || w < (prev.weight ?? 1))
56
- byEdge.set(edge, c);
57
- }
58
- // One uncovered symbol must outweigh any sum of span weights, so coverage is
59
- // strictly the primary objective and weight only breaks ties.
60
- const skipCost = maxWeight * length + 1;
61
- const system = {
62
- key: (p) => "" + p,
63
- axioms: () => [{ item: 0, cost: 0 }],
64
- isGoal: (p) => p === length,
65
- // No nonzero admissible bound is available here: a single candidate can
66
- // cover the whole remainder for unit cost, so any per-symbol estimate would
67
- // overestimate. With h = 0 this is exact Knuth/Dijkstra over the frontier —
68
- // optimal, and linear in the positions. (The A* outside bound is for systems
69
- // whose remaining cost can be genuinely lower-bounded; see the engine.)
70
- *rules(p) {
71
- if (p >= length)
72
- return;
73
- yield { premises: [p], conclusion: p + 1, cost: skipCost }; // leave uncovered
74
- for (const c of byStart[p]) {
75
- yield { premises: [p], conclusion: c.end, cost: c.weight ?? 1 };
76
- }
77
- },
78
- };
79
- const best = lightestDerivation(system);
80
- if (!best)
81
- return { spans: [], covered: 0, uncovered: length };
82
- // Walk the frontier chain back to the axiom; an edge p→q is a chosen span iff
83
- // a candidate spans exactly [p, q) (a covering edge is always cheaper than the
84
- // skips it replaces, so it appears on the optimal path wherever it is used).
85
- const spans = [];
86
- let node = best;
87
- while (node && node.rule) {
88
- const to = node.item;
89
- const from = node.premises[0].item;
90
- const span = byEdge.get(from * (length + 1) + to);
91
- if (span)
92
- spans.push(span);
93
- node = node.premises[0];
94
- }
95
- spans.reverse();
96
- let covered = 0;
97
- for (const s of spans)
98
- covered += s.end - s.start;
99
- return { spans, covered, uncovered: length - covered };
100
- }
@@ -1,90 +0,0 @@
1
- /**
2
- * Forward prefix trie over integer symbols — the lazy site matcher.
3
- *
4
- * This is the matching primitive the lightest-derivation search consults *on
5
- * demand*: given a position in a sequence, {@link Trie.matchesAt} walks forward
6
- * from the root and reports every stored pattern that begins there, in
7
- * O(longest matching pattern). Nothing is scanned that the search never asks
8
- * about — there is no global automaton, no failure links, no precomputed match
9
- * table. That is the whole point: rewrite sites are *materialised only when
10
- * demanded* (the "lazy hyperedge generation" of the rewrite search), so the
11
- * matcher itself stays trivial and the search decides what to look at.
12
- *
13
- * It is fully generic and self-contained: symbols are non-negative integers
14
- * (bytes 0–255, Unicode code points, opcodes, …); patterns are any
15
- * `ArrayLike<number>` (`Uint8Array` or `number[]`); each pattern carries an
16
- * arbitrary `payload` returned on every match. The trie also exposes a tiny
17
- * cursor API ({@link Trie.root}, {@link Trie.step}, {@link Trie.terminal}) so a
18
- * caller can extend a partial match symbol-by-symbol — e.g. to ask "could this
19
- * span still grow into a known form?" while composing.
20
- *
21
- * ## Memory
22
- *
23
- * Most states (99.7 % in typical use) have exactly one outgoing transition.
24
- * Storing a full `Map` per state would cost ~88 bytes each. Instead, a singleton
25
- * transition is packed inline into two typed arrays — `_nxt` (Int32Array, 4
26
- * bytes) and `_sym` (Uint8Array, 1 byte) — for ~5 bytes per state, a 17×
27
- * reduction. Only the rare multi-transition state (~0.3 %) allocates a `Map`,
28
- * kept in a sparse `_multi` table keyed by state number.
29
- */
30
- /** One pattern occurrence found by the trie. */
31
- export interface Match<P> {
32
- /** Start offset in the searched sequence (inclusive). */
33
- start: number;
34
- /** End offset (exclusive); `end - start === length`. */
35
- end: number;
36
- /** Length of the matched pattern. */
37
- length: number;
38
- /** Pattern id, assigned in insertion order (0-based). */
39
- id: number;
40
- /** Payload registered with the pattern. */
41
- payload: P;
42
- }
43
- export declare class Trie<P = undefined> {
44
- private _nxt;
45
- private _sym;
46
- private _multi;
47
- private _end;
48
- private readonly _lens;
49
- private readonly _vals;
50
- private _len;
51
- constructor();
52
- /** The root state, for cursor walks. */
53
- get root(): number;
54
- /** Number of distinct patterns stored. */
55
- get size(): number;
56
- /** Allocate a fresh state, growing the typed arrays by a fixed increment
57
- * when full. No power-of-2 doubling — the transient double-memory spike
58
- * during growth is bounded to the increment. */
59
- private _state;
60
- /** Follow symbol `c` from state `s`. Returns the next state, or -1. */
61
- private _follow;
62
- /** Follow one symbol from `state`; returns the next state or -1 if none. */
63
- step(state: number, symbol: number): number;
64
- /** The pattern ending exactly at `state`, or null. */
65
- terminal(state: number): {
66
- id: number;
67
- payload: P;
68
- } | null;
69
- /** Length of the pattern with this id. */
70
- lengthOf(id: number): number;
71
- /**
72
- * Insert a pattern, returning its id. Inserting the same symbol-sequence
73
- * twice returns the first id and keeps the first payload (patterns are keyed
74
- * by content). Empty patterns are ignored and return -1.
75
- */
76
- insert(pattern: ArrayLike<number>, payload: P): number;
77
- /**
78
- * Every stored pattern that begins exactly at `pos` in `seq`, shortest first.
79
- * Walks forward from the root in O(longest match); reports nothing about any
80
- * other position. This is the on-demand probe the search uses.
81
- */
82
- matchesAt(seq: ArrayLike<number>, pos: number): Array<Match<P>>;
83
- /**
84
- * Every occurrence of every pattern anywhere in `seq` (eager form, the union
85
- * of {@link matchesAt} over all start positions). O(seq · longest pattern),
86
- * independent of how many patterns are stored. Use {@link matchesAt} when the
87
- * search only needs the sites at a particular position.
88
- */
89
- scan(seq: ArrayLike<number>): Array<Match<P>>;
90
- }