@hviana/sema 0.1.0
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 +470 -0
- package/AUTHORS.md +12 -0
- package/COMMERCIAL-LICENSE.md +19 -0
- package/CONTRIBUTING.md +15 -0
- package/HOW_IT_WORKS.md +4210 -0
- package/LICENSE.md +117 -0
- package/README.md +290 -0
- package/TRADEMARKS.md +19 -0
- package/example/demo.ts +46 -0
- package/example/train_base.ts +2675 -0
- package/index.html +893 -0
- package/package.json +25 -0
- package/src/alphabet.ts +34 -0
- package/src/alu/README.md +332 -0
- package/src/alu/src/alu.ts +541 -0
- package/src/alu/src/expr.ts +339 -0
- package/src/alu/src/index.ts +115 -0
- package/src/alu/src/kernel-arith.ts +377 -0
- package/src/alu/src/kernel-bits.ts +199 -0
- package/src/alu/src/kernel-logic.ts +102 -0
- package/src/alu/src/kernel-nd.ts +235 -0
- package/src/alu/src/kernel-numeric.ts +466 -0
- package/src/alu/src/operation.ts +344 -0
- package/src/alu/src/parser.ts +574 -0
- package/src/alu/src/resonance.ts +161 -0
- package/src/alu/src/text.ts +83 -0
- package/src/alu/src/value.ts +327 -0
- package/src/alu/test/alu.test.ts +1004 -0
- package/src/bytes.ts +62 -0
- package/src/config.ts +227 -0
- package/src/derive/README.md +295 -0
- package/src/derive/src/deduction.ts +263 -0
- package/src/derive/src/index.ts +24 -0
- package/src/derive/src/priority-queue.ts +70 -0
- package/src/derive/src/rewrite.ts +127 -0
- package/src/derive/src/trie.ts +242 -0
- package/src/derive/test/derive.test.ts +137 -0
- package/src/extension.ts +42 -0
- package/src/geometry.ts +511 -0
- package/src/index.ts +38 -0
- package/src/ingest-cache.ts +224 -0
- package/src/mind/articulation.ts +137 -0
- package/src/mind/attention.ts +1061 -0
- package/src/mind/canonical.ts +107 -0
- package/src/mind/graph-search.ts +1100 -0
- package/src/mind/index.ts +18 -0
- package/src/mind/junction.ts +347 -0
- package/src/mind/learning.ts +246 -0
- package/src/mind/match.ts +507 -0
- package/src/mind/mechanisms/alu.ts +33 -0
- package/src/mind/mechanisms/cast.ts +568 -0
- package/src/mind/mechanisms/confluence.ts +268 -0
- package/src/mind/mechanisms/cover.ts +248 -0
- package/src/mind/mechanisms/extraction.ts +422 -0
- package/src/mind/mechanisms/recall.ts +241 -0
- package/src/mind/mind.ts +483 -0
- package/src/mind/pipeline-mechanism.ts +326 -0
- package/src/mind/pipeline.ts +300 -0
- package/src/mind/primitives.ts +201 -0
- package/src/mind/rationale.ts +275 -0
- package/src/mind/reasoning.ts +198 -0
- package/src/mind/recognition.ts +234 -0
- package/src/mind/resonance.ts +0 -0
- package/src/mind/trace.ts +108 -0
- package/src/mind/traverse.ts +556 -0
- package/src/mind/types.ts +281 -0
- package/src/rabitq-hnsw/README.md +303 -0
- package/src/rabitq-hnsw/src/database.ts +492 -0
- package/src/rabitq-hnsw/src/heap.ts +90 -0
- package/src/rabitq-hnsw/src/hnsw.ts +514 -0
- package/src/rabitq-hnsw/src/index.ts +15 -0
- package/src/rabitq-hnsw/src/prng.ts +39 -0
- package/src/rabitq-hnsw/src/rabitq.ts +308 -0
- package/src/rabitq-hnsw/src/store.ts +994 -0
- package/src/rabitq-hnsw/test/hnsw.test.ts +1213 -0
- package/src/store-sqlite.ts +928 -0
- package/src/store.ts +2148 -0
- package/src/vec.ts +124 -0
- package/test/00-extract.test.mjs +151 -0
- package/test/01-floor.test.mjs +20 -0
- package/test/02-roundtrip.test.mjs +83 -0
- package/test/03-recall.test.mjs +98 -0
- package/test/04-think.test.mjs +627 -0
- package/test/05-concepts.test.mjs +84 -0
- package/test/08-storage.test.mjs +948 -0
- package/test/09-edges.test.mjs +65 -0
- package/test/11-universality.test.mjs +180 -0
- package/test/13-conversation.test.mjs +228 -0
- package/test/14-scaling.test.mjs +503 -0
- package/test/15-decomposition-gap.test.mjs +0 -0
- package/test/16-bridge.test.mjs +250 -0
- package/test/17-intelligence.test.mjs +240 -0
- package/test/18-alu.test.mjs +209 -0
- package/test/19-nd.test.mjs +254 -0
- package/test/20-stability.test.mjs +489 -0
- package/test/21-partial.test.mjs +158 -0
- package/test/22-multihop.test.mjs +130 -0
- package/test/23-rationale.test.mjs +316 -0
- package/test/24-generalization.test.mjs +416 -0
- package/test/25-embedding.test.mjs +75 -0
- package/test/26-cooperative.test.mjs +89 -0
- package/test/27-saturation-drop.test.mjs +637 -0
- package/test/28-unknowable.test.mjs +88 -0
- package/test/29-counterfactual.test.mjs +476 -0
- package/test/30-conflict-resolution.test.mjs +166 -0
- package/test/31-audit.test.mjs +288 -0
- package/test/32-confluence.test.mjs +173 -0
- package/test/33-multi-candidate.test.mjs +222 -0
- package/test/34-cross-region.test.mjs +252 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,263 @@
|
|
|
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
|
+
|
|
42
|
+
import { MinHeap } from "./priority-queue.js";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A weighted deduction rule (a hyperedge): the conjunction of `premises`
|
|
46
|
+
* derives `conclusion` at an additional `cost`.
|
|
47
|
+
*/
|
|
48
|
+
export interface Rule<I> {
|
|
49
|
+
premises: readonly I[];
|
|
50
|
+
conclusion: I;
|
|
51
|
+
/** Local (edge) cost added on top of the premises' costs. Non-negative. */
|
|
52
|
+
cost: number;
|
|
53
|
+
/** The combinator this rule's firing uses at its conclusion:
|
|
54
|
+
* • `"min"` (default, omitted) — Knuth/A* proper: the conclusion's cost is
|
|
55
|
+
* the CHEAPEST of any rule that reaches it, every other route discarded.
|
|
56
|
+
* The shortest-path monoid (min, +) that makes the search admissible and
|
|
57
|
+
* output-sensitive.
|
|
58
|
+
* • `"sum"` — evidence pooling: EVERY firing of a sum rule contributes its
|
|
59
|
+
* cost to the SAME conclusion (accumulated in {@link
|
|
60
|
+
* DeductionSystem.pool}), instead of competing to be the one cheapest
|
|
61
|
+
* route. The (+, +) monoid a consensus vote needs — several
|
|
62
|
+
* independent premises corroborating one conclusion — kept deliberately
|
|
63
|
+
* OUT of the min-cost chart: a pooled conclusion is never relaxed into
|
|
64
|
+
* `g`, never enters the agenda, and is never itself a premise — it is a
|
|
65
|
+
* terminal aggregate the caller reads out of `pool` once the search is
|
|
66
|
+
* done. */
|
|
67
|
+
combine?: "min" | "sum";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** One rule's contribution to a pooled (`combine: "sum"`) conclusion — the
|
|
71
|
+
* firing rule and the already-finalised derivations of its premises, so a
|
|
72
|
+
* caller can render each contribution exactly as it would a min-cost step. */
|
|
73
|
+
export interface PooledContribution<I> {
|
|
74
|
+
rule: Rule<I>;
|
|
75
|
+
premises: Array<Derivation<I>>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The running aggregate at one pooled conclusion: every sum-mode rule that
|
|
79
|
+
* has fired for it, accumulated. */
|
|
80
|
+
export interface PooledConclusion<I> {
|
|
81
|
+
item: I;
|
|
82
|
+
cost: number;
|
|
83
|
+
contributions: Array<PooledContribution<I>>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The problem the solver is given: items, rules, a goal, and a heuristic. */
|
|
87
|
+
export interface DeductionSystem<I> {
|
|
88
|
+
/** Canonical key for chart memoization (the item's boundary signature). */
|
|
89
|
+
key(item: I): string;
|
|
90
|
+
|
|
91
|
+
/** Axioms: the atomic items and their base costs (the search's seeds). */
|
|
92
|
+
axioms(): Iterable<{ item: I; cost: number }>;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Lazily generate the demanded rules that have `item` among their premises.
|
|
96
|
+
* Called once, when `item` is finalised. `costOf` returns the finalised cost
|
|
97
|
+
* of any item (Infinity if not yet known) — use it for backward-demand /
|
|
98
|
+
* boundary filtering, e.g. drop a rule whose other premises are still open or
|
|
99
|
+
* whose conclusion can no longer beat the best goal.
|
|
100
|
+
*/
|
|
101
|
+
rules(item: I, costOf: (other: I) => number): Iterable<Rule<I>>;
|
|
102
|
+
|
|
103
|
+
/** Whether `item` satisfies the goal. The first finalised goal wins. */
|
|
104
|
+
isGoal(item: I): boolean;
|
|
105
|
+
|
|
106
|
+
/** Admissible, consistent lower bound on the cost from `item` to a goal. */
|
|
107
|
+
heuristic?(item: I): number;
|
|
108
|
+
|
|
109
|
+
/** Present only on a system that fires `combine: "sum"` rules — supplied
|
|
110
|
+
* empty, populated in place as the search runs, read back once it returns
|
|
111
|
+
* (typically `null`: a pooling system has no goal to reach, it exhausts its
|
|
112
|
+
* axioms instead — see {@link lightestDerivation}). Absent on every
|
|
113
|
+
* ordinary min-cost system, which is what keeps pooling a zero-cost opt-in:
|
|
114
|
+
* `relax` only takes the pooling branch when a rule declares `combine:
|
|
115
|
+
* "sum"` AND this map is present. */
|
|
116
|
+
pool?: Map<string, PooledConclusion<I>>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** A node of the reconstructed derivation tree. */
|
|
120
|
+
export interface Derivation<I> {
|
|
121
|
+
/** The derived item. */
|
|
122
|
+
item: I;
|
|
123
|
+
/** This item's minimum derivation cost (its g value). */
|
|
124
|
+
cost: number;
|
|
125
|
+
/** The rule that produced it, or null if it is an axiom. */
|
|
126
|
+
rule: Rule<I> | null;
|
|
127
|
+
/** Derivations of the rule's premises (empty for an axiom). */
|
|
128
|
+
premises: Array<Derivation<I>>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Find a lightest derivation of a goal item, or `null` if none exists.
|
|
133
|
+
* `cost` on the returned root is the total derivation cost.
|
|
134
|
+
*/
|
|
135
|
+
export function lightestDerivation<I>(
|
|
136
|
+
system: DeductionSystem<I>,
|
|
137
|
+
): Derivation<I> | null {
|
|
138
|
+
const g = new Map<string, number>(); // best known cost per item
|
|
139
|
+
const proof = new Map<string, Rule<I> | null>(); // producing rule per item
|
|
140
|
+
const items = new Map<string, I>(); // key → the item it stands for
|
|
141
|
+
const hCache = new Map<string, number>();
|
|
142
|
+
const agenda = new MinHeap<{ key: string; g: number }>();
|
|
143
|
+
|
|
144
|
+
const heuristic = system.heuristic;
|
|
145
|
+
const h = (item: I, key: string): number => {
|
|
146
|
+
if (!heuristic) return 0;
|
|
147
|
+
let v = hCache.get(key);
|
|
148
|
+
if (v === undefined) {
|
|
149
|
+
v = heuristic(item);
|
|
150
|
+
hCache.set(key, v);
|
|
151
|
+
}
|
|
152
|
+
return v;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const costOf = (item: I): number => g.get(system.key(item)) ?? Infinity;
|
|
156
|
+
|
|
157
|
+
const relax = (item: I, cost: number, rule: Rule<I> | null): void => {
|
|
158
|
+
const key = system.key(item);
|
|
159
|
+
if (rule?.combine === "sum" && system.pool) {
|
|
160
|
+
// Evidence pooling: accumulate this firing rather than compete for the
|
|
161
|
+
// cheapest — see {@link Rule.combine}. The premises are already
|
|
162
|
+
// finalised (the caller only relaxes a rule once every premise's cost
|
|
163
|
+
// is known), so their derivations can be read back immediately.
|
|
164
|
+
const premises = rule.premises.map((p) =>
|
|
165
|
+
reconstruct(p, system, g, proof)
|
|
166
|
+
);
|
|
167
|
+
const prior = system.pool.get(key);
|
|
168
|
+
system.pool.set(key, {
|
|
169
|
+
item,
|
|
170
|
+
cost: (prior?.cost ?? 0) + cost,
|
|
171
|
+
contributions: [...(prior?.contributions ?? []), { rule, premises }],
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const current = g.get(key);
|
|
176
|
+
if (current === undefined || cost < current) {
|
|
177
|
+
g.set(key, cost);
|
|
178
|
+
proof.set(key, rule);
|
|
179
|
+
items.set(key, item);
|
|
180
|
+
agenda.push(cost + h(item, key), { key, g: cost });
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
for (const { item, cost } of system.axioms()) relax(item, cost, null);
|
|
185
|
+
|
|
186
|
+
while (agenda.size > 0) {
|
|
187
|
+
const { value } = agenda.pop() as { value: { key: string; g: number } };
|
|
188
|
+
const key = value.key;
|
|
189
|
+
// Lazy deletion: an entry is stale if a cheaper derivation has since been
|
|
190
|
+
// recorded for the same item.
|
|
191
|
+
if (value.g !== g.get(key)) continue;
|
|
192
|
+
|
|
193
|
+
const item = items.get(key) as I;
|
|
194
|
+
if (system.isGoal(item)) {
|
|
195
|
+
return reconstruct(item, system, g, proof);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
for (const rule of system.rules(item, costOf)) {
|
|
199
|
+
let sum = rule.cost;
|
|
200
|
+
let ready = true;
|
|
201
|
+
for (const p of rule.premises) {
|
|
202
|
+
const pc = g.get(system.key(p));
|
|
203
|
+
if (pc === undefined) {
|
|
204
|
+
ready = false;
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
sum += pc;
|
|
208
|
+
}
|
|
209
|
+
if (ready) relax(rule.conclusion, sum, rule);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function reconstruct<I>(
|
|
216
|
+
item: I,
|
|
217
|
+
system: DeductionSystem<I>,
|
|
218
|
+
g: Map<string, number>,
|
|
219
|
+
proof: Map<string, Rule<I> | null>,
|
|
220
|
+
): Derivation<I> {
|
|
221
|
+
// Iterative post-order over the derivation hypergraph. In the rewrite
|
|
222
|
+
// search every rule has one premise, so the derivation is a chain whose
|
|
223
|
+
// length equals the number of frontier edges — which, with long inputs,
|
|
224
|
+
// can exceed the call stack. Multi-premise rules (the test-suite bridge
|
|
225
|
+
// case) are handled by the same explicit stack.
|
|
226
|
+
const done = new Map<string, Derivation<I>>();
|
|
227
|
+
const stack: I[] = [item];
|
|
228
|
+
|
|
229
|
+
while (stack.length > 0) {
|
|
230
|
+
const cur = stack[stack.length - 1]; // peek
|
|
231
|
+
const key = system.key(cur);
|
|
232
|
+
|
|
233
|
+
if (done.has(key)) {
|
|
234
|
+
stack.pop();
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const rule = proof.get(key) ?? null;
|
|
239
|
+
const premises = rule?.premises ?? [];
|
|
240
|
+
|
|
241
|
+
// Push any unresolved premises (rightmost first → leftmost resolves first).
|
|
242
|
+
let pending = false;
|
|
243
|
+
for (let i = premises.length - 1; i >= 0; i--) {
|
|
244
|
+
if (!done.has(system.key(premises[i]))) {
|
|
245
|
+
stack.push(premises[i]);
|
|
246
|
+
pending = true;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!pending) {
|
|
251
|
+
stack.pop(); // this item
|
|
252
|
+
const kids = premises.map((p) => done.get(system.key(p))!);
|
|
253
|
+
done.set(key, {
|
|
254
|
+
item: cur,
|
|
255
|
+
cost: g.get(key) as number,
|
|
256
|
+
rule,
|
|
257
|
+
premises: kids,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return done.get(system.key(item))!;
|
|
263
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
|
|
9
|
+
export { lightestDerivation } from "./deduction.js";
|
|
10
|
+
export type {
|
|
11
|
+
DeductionSystem,
|
|
12
|
+
Derivation,
|
|
13
|
+
PooledConclusion,
|
|
14
|
+
PooledContribution,
|
|
15
|
+
Rule,
|
|
16
|
+
} from "./deduction.js";
|
|
17
|
+
|
|
18
|
+
export { coverSequence } from "./rewrite.js";
|
|
19
|
+
export type { CandidateSpan, Cover } from "./rewrite.js";
|
|
20
|
+
|
|
21
|
+
export { Trie } from "./trie.js";
|
|
22
|
+
export type { Match } from "./trie.js";
|
|
23
|
+
|
|
24
|
+
export { MinHeap } from "./priority-queue.js";
|
|
@@ -0,0 +1,70 @@
|
|
|
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<T> {
|
|
12
|
+
private readonly keys: number[] = [];
|
|
13
|
+
private readonly vals: T[] = [];
|
|
14
|
+
|
|
15
|
+
get size(): number {
|
|
16
|
+
return this.keys.length;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
push(priority: number, value: T): void {
|
|
20
|
+
const keys = this.keys;
|
|
21
|
+
const vals = this.vals;
|
|
22
|
+
let i = keys.length;
|
|
23
|
+
keys.push(priority);
|
|
24
|
+
vals.push(value);
|
|
25
|
+
while (i > 0) {
|
|
26
|
+
const parent = (i - 1) >> 1;
|
|
27
|
+
if (keys[i] < keys[parent]) {
|
|
28
|
+
const tk = keys[i];
|
|
29
|
+
keys[i] = keys[parent];
|
|
30
|
+
keys[parent] = tk;
|
|
31
|
+
const tv = vals[i];
|
|
32
|
+
vals[i] = vals[parent];
|
|
33
|
+
vals[parent] = tv;
|
|
34
|
+
i = parent;
|
|
35
|
+
} else break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pop(): { priority: number; value: T } | undefined {
|
|
40
|
+
const keys = this.keys;
|
|
41
|
+
const vals = this.vals;
|
|
42
|
+
const n = keys.length;
|
|
43
|
+
if (n === 0) return undefined;
|
|
44
|
+
const top = { priority: keys[0], value: vals[0] };
|
|
45
|
+
const lastKey = keys.pop() as number;
|
|
46
|
+
const lastVal = vals.pop() as T;
|
|
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]) best = left;
|
|
57
|
+
if (right < m && keys[right] < keys[best]) best = right;
|
|
58
|
+
if (best === i) break;
|
|
59
|
+
const tk = keys[i];
|
|
60
|
+
keys[i] = keys[best];
|
|
61
|
+
keys[best] = tk;
|
|
62
|
+
const tv = vals[i];
|
|
63
|
+
vals[i] = vals[best];
|
|
64
|
+
vals[best] = tv;
|
|
65
|
+
i = best;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return top;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
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
|
+
|
|
23
|
+
import { type DeductionSystem, lightestDerivation } from "./deduction.js";
|
|
24
|
+
|
|
25
|
+
/** A candidate span over a sequence, carrying caller payload. */
|
|
26
|
+
export interface CandidateSpan<P> {
|
|
27
|
+
/** Start offset (inclusive). */
|
|
28
|
+
start: number;
|
|
29
|
+
/** End offset (exclusive); must be > start. */
|
|
30
|
+
end: number;
|
|
31
|
+
/** Relative cost of using this span (default 1). Lower wins ties. */
|
|
32
|
+
weight?: number;
|
|
33
|
+
/** Caller data, returned on the chosen spans. */
|
|
34
|
+
payload: P;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface Cover<P> {
|
|
38
|
+
/** Chosen non-overlapping spans, left to right. */
|
|
39
|
+
spans: Array<CandidateSpan<P>>;
|
|
40
|
+
/** Symbols covered by the chosen spans. */
|
|
41
|
+
covered: number;
|
|
42
|
+
/** Symbols left uncovered. */
|
|
43
|
+
uncovered: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The lightest non-overlapping cover of `[0, length)` drawn from `candidates`.
|
|
48
|
+
*
|
|
49
|
+
* Primary objective: cover the most symbols (fewest left uncovered). Secondary:
|
|
50
|
+
* least total span weight — with the default unit weight this prefers fewer,
|
|
51
|
+
* longer spans, the optimal analogue of greedy longest-match, but it can return
|
|
52
|
+
* a pair of shorter spans when together they cover more than one long one (which
|
|
53
|
+
* greedy cannot). Modelled as a shortest path over frontier positions: from a
|
|
54
|
+
* position you either leave one symbol uncovered (costly) or take a candidate
|
|
55
|
+
* that starts there (cheap), reaching `length`.
|
|
56
|
+
*
|
|
57
|
+
* Cost: O((length + |candidates|) · log length) — the frontier has `length + 1`
|
|
58
|
+
* items, each finalised once, with one "skip" edge plus the candidates that
|
|
59
|
+
* start there. No quadratic span structure.
|
|
60
|
+
*/
|
|
61
|
+
export function coverSequence<P>(
|
|
62
|
+
length: number,
|
|
63
|
+
candidates: ReadonlyArray<CandidateSpan<P>>,
|
|
64
|
+
): Cover<P> {
|
|
65
|
+
if (length <= 0) return { spans: [], covered: 0, uncovered: 0 };
|
|
66
|
+
|
|
67
|
+
// Candidates indexed by where they start, and by their exact (start,end) edge
|
|
68
|
+
// for reconstruction. Both are O(|candidates|) to build and to read.
|
|
69
|
+
const byStart: Array<Array<CandidateSpan<P>>> = Array.from(
|
|
70
|
+
{ length },
|
|
71
|
+
() => [],
|
|
72
|
+
);
|
|
73
|
+
const byEdge = new Map<number, CandidateSpan<P>>();
|
|
74
|
+
let maxWeight = 1;
|
|
75
|
+
for (const c of candidates) {
|
|
76
|
+
if (c.end <= c.start || c.start < 0 || c.end > length) continue;
|
|
77
|
+
const w = c.weight ?? 1;
|
|
78
|
+
if (w > maxWeight) maxWeight = w;
|
|
79
|
+
byStart[c.start].push(c);
|
|
80
|
+
const edge = c.start * (length + 1) + c.end;
|
|
81
|
+
const prev = byEdge.get(edge);
|
|
82
|
+
if (!prev || w < (prev.weight ?? 1)) byEdge.set(edge, c);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// One uncovered symbol must outweigh any sum of span weights, so coverage is
|
|
86
|
+
// strictly the primary objective and weight only breaks ties.
|
|
87
|
+
const skipCost = maxWeight * length + 1;
|
|
88
|
+
|
|
89
|
+
const system: DeductionSystem<number> = {
|
|
90
|
+
key: (p) => "" + p,
|
|
91
|
+
axioms: () => [{ item: 0, cost: 0 }],
|
|
92
|
+
isGoal: (p) => p === length,
|
|
93
|
+
// No nonzero admissible bound is available here: a single candidate can
|
|
94
|
+
// cover the whole remainder for unit cost, so any per-symbol estimate would
|
|
95
|
+
// overestimate. With h = 0 this is exact Knuth/Dijkstra over the frontier —
|
|
96
|
+
// optimal, and linear in the positions. (The A* outside bound is for systems
|
|
97
|
+
// whose remaining cost can be genuinely lower-bounded; see the engine.)
|
|
98
|
+
*rules(p) {
|
|
99
|
+
if (p >= length) return;
|
|
100
|
+
yield { premises: [p], conclusion: p + 1, cost: skipCost }; // leave uncovered
|
|
101
|
+
for (const c of byStart[p]) {
|
|
102
|
+
yield { premises: [p], conclusion: c.end, cost: c.weight ?? 1 };
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const best = lightestDerivation(system);
|
|
108
|
+
if (!best) return { spans: [], covered: 0, uncovered: length };
|
|
109
|
+
|
|
110
|
+
// Walk the frontier chain back to the axiom; an edge p→q is a chosen span iff
|
|
111
|
+
// a candidate spans exactly [p, q) (a covering edge is always cheaper than the
|
|
112
|
+
// skips it replaces, so it appears on the optimal path wherever it is used).
|
|
113
|
+
const spans: Array<CandidateSpan<P>> = [];
|
|
114
|
+
let node: typeof best | undefined = best;
|
|
115
|
+
while (node && node.rule) {
|
|
116
|
+
const to = node.item;
|
|
117
|
+
const from = node.premises[0].item;
|
|
118
|
+
const span = byEdge.get(from * (length + 1) + to);
|
|
119
|
+
if (span) spans.push(span);
|
|
120
|
+
node = node.premises[0];
|
|
121
|
+
}
|
|
122
|
+
spans.reverse();
|
|
123
|
+
|
|
124
|
+
let covered = 0;
|
|
125
|
+
for (const s of spans) covered += s.end - s.start;
|
|
126
|
+
return { spans, covered, uncovered: length - covered };
|
|
127
|
+
}
|