@hviana/sema 0.2.6 → 0.2.8
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/dist/example/demo.d.ts +1 -0
- package/dist/example/demo.js +39 -0
- package/dist/example/train_base.d.ts +87 -0
- package/dist/example/train_base.js +2252 -0
- package/dist/src/alphabet.d.ts +7 -0
- package/dist/src/alphabet.js +33 -0
- package/dist/src/alu/src/alu.d.ts +185 -0
- package/dist/src/alu/src/alu.js +440 -0
- package/dist/src/alu/src/expr.d.ts +61 -0
- package/dist/src/alu/src/expr.js +318 -0
- package/dist/src/alu/src/index.d.ts +11 -0
- package/dist/src/alu/src/index.js +19 -0
- package/dist/src/alu/src/kernel-arith.d.ts +16 -0
- package/dist/src/alu/src/kernel-arith.js +264 -0
- package/dist/src/alu/src/kernel-bits.d.ts +19 -0
- package/dist/src/alu/src/kernel-bits.js +152 -0
- package/dist/src/alu/src/kernel-logic.d.ts +4 -0
- package/dist/src/alu/src/kernel-logic.js +60 -0
- package/dist/src/alu/src/kernel-nd.d.ts +3 -0
- package/dist/src/alu/src/kernel-nd.js +208 -0
- package/dist/src/alu/src/kernel-numeric.d.ts +54 -0
- package/dist/src/alu/src/kernel-numeric.js +366 -0
- package/dist/src/alu/src/operation.d.ts +168 -0
- package/dist/src/alu/src/operation.js +189 -0
- package/dist/src/alu/src/parser.d.ts +221 -0
- package/dist/src/alu/src/parser.js +577 -0
- package/dist/src/alu/src/resonance.d.ts +55 -0
- package/dist/src/alu/src/resonance.js +126 -0
- package/dist/src/alu/src/text.d.ts +31 -0
- package/dist/src/alu/src/text.js +73 -0
- package/dist/src/alu/src/value.d.ts +109 -0
- package/dist/src/alu/src/value.js +300 -0
- package/dist/src/alu/test/alu.test.d.ts +1 -0
- package/dist/src/alu/test/alu.test.js +764 -0
- package/dist/src/bytes.d.ts +14 -0
- package/dist/src/bytes.js +59 -0
- package/dist/src/canon.d.ts +26 -0
- package/dist/src/canon.js +57 -0
- package/dist/src/config.d.ts +111 -0
- package/dist/src/config.js +91 -0
- package/dist/src/derive/src/deduction.d.ts +125 -0
- package/dist/src/derive/src/deduction.js +155 -0
- package/dist/src/derive/src/index.d.ts +7 -0
- package/dist/src/derive/src/index.js +11 -0
- package/dist/src/derive/src/priority-queue.d.ts +20 -0
- package/dist/src/derive/src/priority-queue.js +73 -0
- package/dist/src/derive/src/rewrite.d.ts +56 -0
- package/dist/src/derive/src/rewrite.js +100 -0
- package/dist/src/derive/src/trie.d.ts +90 -0
- package/dist/src/derive/src/trie.js +217 -0
- package/dist/src/derive/test/derive.test.d.ts +1 -0
- package/dist/src/derive/test/derive.test.js +122 -0
- package/dist/src/extension.d.ts +37 -0
- package/dist/src/extension.js +7 -0
- package/dist/src/geometry.d.ts +202 -0
- package/dist/src/geometry.js +575 -0
- package/dist/src/index.d.ts +16 -0
- package/dist/src/index.js +18 -0
- package/dist/src/ingest-cache.d.ts +41 -0
- package/dist/src/ingest-cache.js +161 -0
- package/dist/src/mind/articulation.d.ts +6 -0
- package/dist/src/mind/articulation.js +99 -0
- package/dist/src/mind/attention.d.ts +405 -0
- package/dist/src/mind/attention.js +1829 -0
- package/dist/src/mind/bridge.d.ts +30 -0
- package/dist/src/mind/bridge.js +569 -0
- package/dist/src/mind/canonical.d.ts +29 -0
- package/dist/src/mind/canonical.js +88 -0
- package/dist/src/mind/graph-search.d.ts +285 -0
- package/dist/src/mind/graph-search.js +956 -0
- package/dist/src/mind/index.d.ts +7 -0
- package/dist/src/mind/index.js +5 -0
- package/dist/src/mind/junction.d.ts +124 -0
- package/dist/src/mind/junction.js +317 -0
- package/dist/src/mind/learning.d.ts +47 -0
- package/dist/src/mind/learning.js +248 -0
- package/dist/src/mind/match.d.ts +126 -0
- package/dist/src/mind/match.js +441 -0
- package/dist/src/mind/mechanisms/alu.d.ts +4 -0
- package/dist/src/mind/mechanisms/alu.js +36 -0
- package/dist/src/mind/mechanisms/cast.d.ts +89 -0
- package/dist/src/mind/mechanisms/cast.js +716 -0
- package/dist/src/mind/mechanisms/confluence.d.ts +24 -0
- package/dist/src/mind/mechanisms/confluence.js +225 -0
- package/dist/src/mind/mechanisms/cover.d.ts +6 -0
- package/dist/src/mind/mechanisms/cover.js +203 -0
- package/dist/src/mind/mechanisms/extraction.d.ts +67 -0
- package/dist/src/mind/mechanisms/extraction.js +387 -0
- package/dist/src/mind/mechanisms/recall.d.ts +13 -0
- package/dist/src/mind/mechanisms/recall.js +325 -0
- package/dist/src/mind/mind.d.ts +276 -0
- package/dist/src/mind/mind.js +572 -0
- package/dist/src/mind/pipeline-mechanism.d.ts +142 -0
- package/dist/src/mind/pipeline-mechanism.js +213 -0
- package/dist/src/mind/pipeline.d.ts +20 -0
- package/dist/src/mind/pipeline.js +217 -0
- package/dist/src/mind/primitives.d.ts +70 -0
- package/dist/src/mind/primitives.js +287 -0
- package/dist/src/mind/rationale.d.ts +139 -0
- package/dist/src/mind/rationale.js +163 -0
- package/dist/src/mind/reasoning.d.ts +34 -0
- package/dist/src/mind/reasoning.js +240 -0
- package/dist/src/mind/recognition.d.ts +20 -0
- package/dist/src/mind/recognition.js +390 -0
- package/dist/src/mind/resonance.d.ts +23 -0
- package/dist/src/mind/resonance.js +0 -0
- package/dist/src/mind/trace.d.ts +15 -0
- package/dist/src/mind/trace.js +73 -0
- package/dist/src/mind/traverse.d.ts +113 -0
- package/dist/src/mind/traverse.js +568 -0
- package/dist/src/mind/types.d.ts +289 -0
- package/dist/src/mind/types.js +130 -0
- 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-ivf/src/index.js +4 -0
- 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 +31 -0
- package/dist/src/sema.js +63 -0
- package/dist/src/store-sqlite.d.ts +184 -0
- package/dist/src/store-sqlite.js +942 -0
- package/dist/src/store.d.ts +663 -0
- package/dist/src/store.js +1618 -0
- package/dist/src/vec.d.ts +31 -0
- package/dist/src/vec.js +109 -0
- package/package.json +1 -1
- package/src/mind/attention.ts +178 -63
- package/test/51-structural-resonance-ladder.test.mjs +6 -3
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** True when two byte spans are equal in length and content. */
|
|
2
|
+
export declare function bytesEqual(a: Uint8Array, b: Uint8Array): boolean;
|
|
3
|
+
/** Concatenate byte arrays. Takes an array rather than rest params so
|
|
4
|
+
* a large segment list can never overflow the call stack via spread. */
|
|
5
|
+
export declare function concatBytes(parts: Uint8Array[]): Uint8Array;
|
|
6
|
+
/** Join two byte spans — the hot two-operand case of {@link concatBytes},
|
|
7
|
+
* fused without the array wrapper for the search's inner fuse loop. */
|
|
8
|
+
export declare function concat2(a: Uint8Array, b: Uint8Array): Uint8Array;
|
|
9
|
+
/** Latin-1 view of a byte span — a stable, lossless string key for chart
|
|
10
|
+
* memoization (every byte 0–255 maps to one code unit). */
|
|
11
|
+
export declare function latin1(b: Uint8Array): string;
|
|
12
|
+
/** First index ≥ `from` at which `needle` occurs in `hay`, or -1. A short naive
|
|
13
|
+
* scan — used only to locate a result span inside a learnt framing form. */
|
|
14
|
+
export declare function indexOf(hay: Uint8Array, needle: Uint8Array, from: number): number;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// bytes.ts — small, pure byte-span utilities.
|
|
2
|
+
//
|
|
3
|
+
// Nothing here knows about Sema, the store, or the search; these are the
|
|
4
|
+
// mechanical operations on Uint8Arrays that the rest of the code leans on, kept
|
|
5
|
+
// together so a reader meets them once and never wonders whether a given helper
|
|
6
|
+
// hides a side effect.
|
|
7
|
+
/** True when two byte spans are equal in length and content. */
|
|
8
|
+
export function bytesEqual(a, b) {
|
|
9
|
+
if (a.length !== b.length)
|
|
10
|
+
return false;
|
|
11
|
+
for (let i = 0; i < a.length; i++)
|
|
12
|
+
if (a[i] !== b[i])
|
|
13
|
+
return false;
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
/** Concatenate byte arrays. Takes an array rather than rest params so
|
|
17
|
+
* a large segment list can never overflow the call stack via spread. */
|
|
18
|
+
export function concatBytes(parts) {
|
|
19
|
+
let total = 0;
|
|
20
|
+
for (const p of parts)
|
|
21
|
+
total += p.length;
|
|
22
|
+
const out = new Uint8Array(total);
|
|
23
|
+
let off = 0;
|
|
24
|
+
for (const p of parts) {
|
|
25
|
+
out.set(p, off);
|
|
26
|
+
off += p.length;
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
/** Join two byte spans — the hot two-operand case of {@link concatBytes},
|
|
31
|
+
* fused without the array wrapper for the search's inner fuse loop. */
|
|
32
|
+
export function concat2(a, b) {
|
|
33
|
+
const out = new Uint8Array(a.length + b.length);
|
|
34
|
+
out.set(a, 0);
|
|
35
|
+
out.set(b, a.length);
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
/** Latin-1 view of a byte span — a stable, lossless string key for chart
|
|
39
|
+
* memoization (every byte 0–255 maps to one code unit). */
|
|
40
|
+
export function latin1(b) {
|
|
41
|
+
let s = "";
|
|
42
|
+
for (let k = 0; k < b.length; k++)
|
|
43
|
+
s += String.fromCharCode(b[k]);
|
|
44
|
+
return s;
|
|
45
|
+
}
|
|
46
|
+
/** First index ≥ `from` at which `needle` occurs in `hay`, or -1. A short naive
|
|
47
|
+
* scan — used only to locate a result span inside a learnt framing form. */
|
|
48
|
+
export function indexOf(hay, needle, from) {
|
|
49
|
+
if (needle.length === 0)
|
|
50
|
+
return from;
|
|
51
|
+
outer: for (let i = Math.max(0, from); i + needle.length <= hay.length; i++) {
|
|
52
|
+
for (let j = 0; j < needle.length; j++) {
|
|
53
|
+
if (hay[i + j] !== needle[j])
|
|
54
|
+
continue outer;
|
|
55
|
+
}
|
|
56
|
+
return i;
|
|
57
|
+
}
|
|
58
|
+
return -1;
|
|
59
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** A content canonicalizer: maps a byte span to the canonical representative
|
|
2
|
+
* of its equivalence class. Must be pure and deterministic. Returning the
|
|
3
|
+
* input unchanged is always sound (the class is then {input}). */
|
|
4
|
+
export type Canon = (bytes: Uint8Array) => Uint8Array;
|
|
5
|
+
/** The TEXT canonicalizer: Unicode-aware equivalence over every character
|
|
6
|
+
* variation that does not change what the text SAYS —
|
|
7
|
+
*
|
|
8
|
+
* • compatibility normalization (NFKC): full-width forms, ligatures,
|
|
9
|
+
* composed vs decomposed accents collapse to one representation;
|
|
10
|
+
* • case folding (locale-independent lowercase after NFKC — the standard
|
|
11
|
+
* simple fold);
|
|
12
|
+
* • whitespace: every INTERIOR run of Unicode whitespace becomes one plain
|
|
13
|
+
* space. EDGE whitespace is preserved verbatim: a span's leading or
|
|
14
|
+
* trailing separator belongs BETWEEN forms, not to the form — trimming
|
|
15
|
+
* it would let a recognised span swallow the boundary byte that
|
|
16
|
+
* separates it from its neighbour (observed: "ice fire" composing to
|
|
17
|
+
* "coldhot" because the span "ice " matched the stored "ice").
|
|
18
|
+
*
|
|
19
|
+
* "WHAT IS", "What is" and "what is" share one canonical form. This is
|
|
20
|
+
* deliberately conservative: punctuation, digits and word order are content
|
|
21
|
+
* and pass through untouched. */
|
|
22
|
+
export declare function textCanon(bytes: Uint8Array): Uint8Array;
|
|
23
|
+
/** 32-bit FNV-1a over a canonical key — the integer the store's canon index
|
|
24
|
+
* is keyed on. Same construction as the node table's content hash; a
|
|
25
|
+
* collision is resolved by verifying canon(stored) === key, never trusted. */
|
|
26
|
+
export declare function canonHash(key: Uint8Array): number;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// canon.ts — content canonicalization for equivalence-class resolution.
|
|
2
|
+
//
|
|
3
|
+
// The store is content-addressed on RAW bytes: "What", "WHAT" and "what" are
|
|
4
|
+
// three different hashes, so a query whose surface form varies from the
|
|
5
|
+
// trained form resolves to nothing even though the CONTENT is the same. A
|
|
6
|
+
// CANONICALIZER maps every surface variant of the same content onto one
|
|
7
|
+
// canonical byte string; the store keeps a small hash index from canonical
|
|
8
|
+
// keys to node ids (see Store.canonAdd/canonFind), and resolution falls back
|
|
9
|
+
// to that index when the exact content-addressed lookup misses.
|
|
10
|
+
//
|
|
11
|
+
// The canonicalizer is MODALITY-SPECIFIC and always INJECTED — nothing in the
|
|
12
|
+
// store or the mind's core knows what "case" or "whitespace" is. The text
|
|
13
|
+
// canonicalizer below is the one `respondText`/`respondTurnText` pass down;
|
|
14
|
+
// a grid or audio modality would supply its own (or none).
|
|
15
|
+
//
|
|
16
|
+
// Canonical keys are equivalence-class LABELS, never content: they are hashed
|
|
17
|
+
// and verified (canon(stored bytes) must equal canon(query bytes) before an
|
|
18
|
+
// id is accepted), so a hash collision costs a read, never a wrong id — the
|
|
19
|
+
// same discipline as the node table's own `h` index.
|
|
20
|
+
const dec = new TextDecoder("utf-8", { fatal: false });
|
|
21
|
+
const enc = new TextEncoder();
|
|
22
|
+
/** The TEXT canonicalizer: Unicode-aware equivalence over every character
|
|
23
|
+
* variation that does not change what the text SAYS —
|
|
24
|
+
*
|
|
25
|
+
* • compatibility normalization (NFKC): full-width forms, ligatures,
|
|
26
|
+
* composed vs decomposed accents collapse to one representation;
|
|
27
|
+
* • case folding (locale-independent lowercase after NFKC — the standard
|
|
28
|
+
* simple fold);
|
|
29
|
+
* • whitespace: every INTERIOR run of Unicode whitespace becomes one plain
|
|
30
|
+
* space. EDGE whitespace is preserved verbatim: a span's leading or
|
|
31
|
+
* trailing separator belongs BETWEEN forms, not to the form — trimming
|
|
32
|
+
* it would let a recognised span swallow the boundary byte that
|
|
33
|
+
* separates it from its neighbour (observed: "ice fire" composing to
|
|
34
|
+
* "coldhot" because the span "ice " matched the stored "ice").
|
|
35
|
+
*
|
|
36
|
+
* "WHAT IS", "What is" and "what is" share one canonical form. This is
|
|
37
|
+
* deliberately conservative: punctuation, digits and word order are content
|
|
38
|
+
* and pass through untouched. */
|
|
39
|
+
export function textCanon(bytes) {
|
|
40
|
+
const s = dec
|
|
41
|
+
.decode(bytes)
|
|
42
|
+
.normalize("NFKC")
|
|
43
|
+
.toLowerCase()
|
|
44
|
+
.replace(/(\S)\s+(?=\S)/g, "$1 ");
|
|
45
|
+
return enc.encode(s);
|
|
46
|
+
}
|
|
47
|
+
/** 32-bit FNV-1a over a canonical key — the integer the store's canon index
|
|
48
|
+
* is keyed on. Same construction as the node table's content hash; a
|
|
49
|
+
* collision is resolved by verifying canon(stored) === key, never trusted. */
|
|
50
|
+
export function canonHash(key) {
|
|
51
|
+
let h = 0x811c9dc5 >>> 0;
|
|
52
|
+
for (let i = 0; i < key.length; i++) {
|
|
53
|
+
h ^= key[i];
|
|
54
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
55
|
+
}
|
|
56
|
+
return h >>> 0;
|
|
57
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export interface AluConfig {
|
|
2
|
+
/** Whether the ALU sub-lib contributes computation rules to the graph search.
|
|
3
|
+
* When false, no operator/operand pre-resolution runs and no ALU rule fires —
|
|
4
|
+
* thinking behaves exactly as it did before ALU existed. */
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
/** Convergence tolerance ε for the numerical limit layer (diff/solve/exp/…). */
|
|
7
|
+
tol: number;
|
|
8
|
+
/** Hard iteration ceiling for any convergence loop, so a non-converging
|
|
9
|
+
* refinement still terminates. */
|
|
10
|
+
maxIter: number;
|
|
11
|
+
/** Decimal places a real result is rounded to before it is encoded to bytes.
|
|
12
|
+
* Determinism here is load-bearing: the search keys an output span by its
|
|
13
|
+
* bytes, so two derivations of the same value must spell it identically. */
|
|
14
|
+
precision: number;
|
|
15
|
+
}
|
|
16
|
+
export interface GeometryConfig {
|
|
17
|
+
/** Maximum siblings per tree fold. */
|
|
18
|
+
maxGroup: number;
|
|
19
|
+
}
|
|
20
|
+
export interface AlphabetConfig {
|
|
21
|
+
/** How different neighbouring byte vectors are from their coarse ancestors
|
|
22
|
+
* (0 = identical, 1 = independent). */
|
|
23
|
+
roughness: number;
|
|
24
|
+
/** Seed XOR mask for the alphabet's PRNG derivation. */
|
|
25
|
+
seedMask: number;
|
|
26
|
+
}
|
|
27
|
+
export interface StoreConfig {
|
|
28
|
+
minHaloMass: number;
|
|
29
|
+
/** Query breadth of the IVF vector indices: clusters probed per query =
|
|
30
|
+
* ceil(efSearch / 4). Inserts have no quality knob — the partitioned
|
|
31
|
+
* index routes and appends, so ingestion cost is flat by construction. */
|
|
32
|
+
efSearch: number;
|
|
33
|
+
/** Compact the in-memory vector indices after this many vectors are written.
|
|
34
|
+
* Compaction rebuilds an index from its live codes to reclaim the slots left
|
|
35
|
+
* by tombstoned (updated/deleted) halo entries; pacing it on write VOLUME
|
|
36
|
+
* (not on a flush count that goes quiet during repeat-heavy training) keeps
|
|
37
|
+
* the index dense and query cost bounded. */
|
|
38
|
+
compactEveryNWrites: number;
|
|
39
|
+
/** Over-fetch factor for vector-index queries (ANN recall cushion). */
|
|
40
|
+
overfetch: number;
|
|
41
|
+
/** Combined buffered-write ceiling before a flush of both vector indices
|
|
42
|
+
* (content + halo). Higher ⇒ fewer, larger flushes into the in-memory
|
|
43
|
+
* indices and fewer write-transaction commits. */
|
|
44
|
+
batchSize: number;
|
|
45
|
+
/** Max entries in the store's exact-content dedup map (bounds RAM on huge
|
|
46
|
+
* corpora; a miss only risks a duplicate node, never incorrectness). */
|
|
47
|
+
dedupCacheMax: number;
|
|
48
|
+
/** Max bytes of reconstructed content cached in memory (regenerable).
|
|
49
|
+
* Large branch nodes cost more budget than small leaves, so the cache
|
|
50
|
+
* naturally favours cheap, frequently-hit entries. */
|
|
51
|
+
bytesCacheMax: number;
|
|
52
|
+
/** Max bytes of node-record cache (avoids repeated SQLite lookups for
|
|
53
|
+
* shared DAG nodes). Each record is ~30-50 bytes. */
|
|
54
|
+
recCacheBytes: number;
|
|
55
|
+
/** Max bytes of ingest-result cache used by {@link CachedIngest}. */
|
|
56
|
+
ingestCacheBytes: number;
|
|
57
|
+
/** Max bytes of captured-but-not-yet-indexed node gists (D·4 each). A node's
|
|
58
|
+
* gist enters the content index lazily, only when it first becomes a
|
|
59
|
+
* resonance target (gains a continuation edge or a halo); until then its gist
|
|
60
|
+
* waits here. A deposit links/pours a node right after interning it, so the
|
|
61
|
+
* working set is one deposit's nodes — a modest budget captures ~all of it.
|
|
62
|
+
* An eviction only means a node is reached by the structural DAG climb instead
|
|
63
|
+
* of by direct resonance — a little recall reach, never correctness. */
|
|
64
|
+
pendingGistBytes: number;
|
|
65
|
+
/** Max bytes of EXACT halo accumulators kept in memory (D·4 each). The
|
|
66
|
+
* durable halo row is 2-bit quantized; this cache keeps the accumulators a
|
|
67
|
+
* session is actively pouring into at full precision, so within-session
|
|
68
|
+
* accumulate-then-compare (concept formation as it happens) never
|
|
69
|
+
* round-trips through the quantizer. An eviction or a reopen reads the
|
|
70
|
+
* 2-bit row — the fidelity every cross-session consumer already gets. */
|
|
71
|
+
haloCacheBytes: number;
|
|
72
|
+
/** Size, in MiB, of each `rabitq-ivf` `VectorDatabase`'s memory budget
|
|
73
|
+
* (forwarded as its `cacheSizeMb` — its SQLite page cache). A PURE latency
|
|
74
|
+
* optimisation — the index reads chunk blobs from SQLite on demand, so its
|
|
75
|
+
* correctness and its per-operation storage-read count are identical with
|
|
76
|
+
* the budget at 0. Exposed so a scaling test can set it to 0 and measure
|
|
77
|
+
* the honest, cache-independent cost. */
|
|
78
|
+
vectorCacheMb: number;
|
|
79
|
+
/** Size, in MiB, of the MAIN DAG database's SQLite page cache. The node /
|
|
80
|
+
* kid / edge / contain tables serve millions of point probes per training
|
|
81
|
+
* session (content-addressed findLeaf/findBranch, parent probes, contain
|
|
82
|
+
* appends); SQLite's default cache (~2 MiB) thrashes once the DB outgrows
|
|
83
|
+
* it, so every probe pays a file read. A PURE latency knob — correctness
|
|
84
|
+
* and result identical at any value. */
|
|
85
|
+
sqliteCacheMb: number;
|
|
86
|
+
/** Max entries in the skipped-interior LRU set. Interiors that
|
|
87
|
+
* {@link Store.indexSubtree} has already visited (indexed or skipped) are
|
|
88
|
+
* remembered here so subsequent calls prune their subtrees. Session-local
|
|
89
|
+
* (regenerable). */
|
|
90
|
+
coveredIdsMax: number;
|
|
91
|
+
/** Max bytes of transparent-chain runs ({@link Store.chainRun}) cached for
|
|
92
|
+
* the store's lifetime (~4 bytes per chain node). Valid until any write
|
|
93
|
+
* could break a node's transparency (a new structural parent or a new
|
|
94
|
+
* continuation edge), when the whole cache is dropped — writes happen in
|
|
95
|
+
* training bursts, reads in read-only query phases, so the cache pays for
|
|
96
|
+
* itself exactly where it matters. Regenerable; a miss re-walks. */
|
|
97
|
+
chainCacheBytes: number;
|
|
98
|
+
}
|
|
99
|
+
export interface MindConfig {
|
|
100
|
+
seed: number;
|
|
101
|
+
recallQueryK: number;
|
|
102
|
+
haloQueryK: number;
|
|
103
|
+
normalizeEpsilon: number;
|
|
104
|
+
cosineEpsilon: number;
|
|
105
|
+
alu: AluConfig;
|
|
106
|
+
geometry: GeometryConfig;
|
|
107
|
+
alphabet: AlphabetConfig;
|
|
108
|
+
store: StoreConfig;
|
|
109
|
+
}
|
|
110
|
+
export declare const DEFAULT_CONFIG: MindConfig;
|
|
111
|
+
export declare function resolveConfig(opts?: Partial<MindConfig>): MindConfig;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// config.ts — the single configuration interface for Sema.
|
|
2
|
+
// Every tunable parameter lives here. Subsystems receive their subset.
|
|
3
|
+
// ── Defaults ──
|
|
4
|
+
export const DEFAULT_CONFIG = {
|
|
5
|
+
seed: 42,
|
|
6
|
+
recallQueryK: 12,
|
|
7
|
+
haloQueryK: 12,
|
|
8
|
+
normalizeEpsilon: 1e-12,
|
|
9
|
+
cosineEpsilon: 1e-12,
|
|
10
|
+
alu: {
|
|
11
|
+
enabled: true,
|
|
12
|
+
tol: 1e-10,
|
|
13
|
+
maxIter: 1000,
|
|
14
|
+
precision: 6,
|
|
15
|
+
},
|
|
16
|
+
geometry: {
|
|
17
|
+
maxGroup: 4,
|
|
18
|
+
},
|
|
19
|
+
alphabet: {
|
|
20
|
+
roughness: 0.65,
|
|
21
|
+
seedMask: 0xa1fa17,
|
|
22
|
+
},
|
|
23
|
+
store: {
|
|
24
|
+
minHaloMass: 1,
|
|
25
|
+
efSearch: 64,
|
|
26
|
+
compactEveryNWrites: 50_000,
|
|
27
|
+
overfetch: 4,
|
|
28
|
+
batchSize: 256,
|
|
29
|
+
dedupCacheMax: 1_000_000,
|
|
30
|
+
bytesCacheMax: 20_000_000,
|
|
31
|
+
recCacheBytes: 10_000_000,
|
|
32
|
+
ingestCacheBytes: 50_000_000,
|
|
33
|
+
pendingGistBytes: 16_000_000,
|
|
34
|
+
haloCacheBytes: 16_000_000,
|
|
35
|
+
vectorCacheMb: 64,
|
|
36
|
+
sqliteCacheMb: 64,
|
|
37
|
+
coveredIdsMax: 100_000,
|
|
38
|
+
chainCacheBytes: 16_000_000,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
// ── Config resolver: partial input + defaults = full config ──
|
|
42
|
+
export function resolveConfig(opts = {}) {
|
|
43
|
+
return {
|
|
44
|
+
seed: opts.seed ?? DEFAULT_CONFIG.seed,
|
|
45
|
+
recallQueryK: opts.recallQueryK ?? DEFAULT_CONFIG.recallQueryK,
|
|
46
|
+
haloQueryK: opts.haloQueryK ?? DEFAULT_CONFIG.haloQueryK,
|
|
47
|
+
normalizeEpsilon: opts.normalizeEpsilon ?? DEFAULT_CONFIG.normalizeEpsilon,
|
|
48
|
+
cosineEpsilon: opts.cosineEpsilon ?? DEFAULT_CONFIG.cosineEpsilon,
|
|
49
|
+
alu: {
|
|
50
|
+
enabled: opts.alu?.enabled ?? DEFAULT_CONFIG.alu.enabled,
|
|
51
|
+
tol: opts.alu?.tol ?? DEFAULT_CONFIG.alu.tol,
|
|
52
|
+
maxIter: opts.alu?.maxIter ?? DEFAULT_CONFIG.alu.maxIter,
|
|
53
|
+
precision: opts.alu?.precision ?? DEFAULT_CONFIG.alu.precision,
|
|
54
|
+
},
|
|
55
|
+
geometry: {
|
|
56
|
+
maxGroup: opts.geometry?.maxGroup ?? DEFAULT_CONFIG.geometry.maxGroup,
|
|
57
|
+
},
|
|
58
|
+
alphabet: {
|
|
59
|
+
roughness: opts.alphabet?.roughness ?? DEFAULT_CONFIG.alphabet.roughness,
|
|
60
|
+
seedMask: opts.alphabet?.seedMask ?? DEFAULT_CONFIG.alphabet.seedMask,
|
|
61
|
+
},
|
|
62
|
+
store: {
|
|
63
|
+
minHaloMass: opts.store?.minHaloMass ?? DEFAULT_CONFIG.store.minHaloMass,
|
|
64
|
+
efSearch: opts.store?.efSearch ?? DEFAULT_CONFIG.store.efSearch,
|
|
65
|
+
compactEveryNWrites: opts.store?.compactEveryNWrites ??
|
|
66
|
+
DEFAULT_CONFIG.store.compactEveryNWrites,
|
|
67
|
+
overfetch: opts.store?.overfetch ?? DEFAULT_CONFIG.store.overfetch,
|
|
68
|
+
batchSize: opts.store?.batchSize ?? DEFAULT_CONFIG.store.batchSize,
|
|
69
|
+
dedupCacheMax: opts.store?.dedupCacheMax ??
|
|
70
|
+
DEFAULT_CONFIG.store.dedupCacheMax,
|
|
71
|
+
bytesCacheMax: opts.store?.bytesCacheMax ??
|
|
72
|
+
DEFAULT_CONFIG.store.bytesCacheMax,
|
|
73
|
+
recCacheBytes: opts.store?.recCacheBytes ??
|
|
74
|
+
DEFAULT_CONFIG.store.recCacheBytes,
|
|
75
|
+
ingestCacheBytes: opts.store?.ingestCacheBytes ??
|
|
76
|
+
DEFAULT_CONFIG.store.ingestCacheBytes,
|
|
77
|
+
pendingGistBytes: opts.store?.pendingGistBytes ??
|
|
78
|
+
DEFAULT_CONFIG.store.pendingGistBytes,
|
|
79
|
+
haloCacheBytes: opts.store?.haloCacheBytes ??
|
|
80
|
+
DEFAULT_CONFIG.store.haloCacheBytes,
|
|
81
|
+
vectorCacheMb: opts.store?.vectorCacheMb ??
|
|
82
|
+
DEFAULT_CONFIG.store.vectorCacheMb,
|
|
83
|
+
sqliteCacheMb: opts.store?.sqliteCacheMb ??
|
|
84
|
+
DEFAULT_CONFIG.store.sqliteCacheMb,
|
|
85
|
+
coveredIdsMax: opts.store?.coveredIdsMax ??
|
|
86
|
+
DEFAULT_CONFIG.store.coveredIdsMax,
|
|
87
|
+
chainCacheBytes: opts.store?.chainCacheBytes ??
|
|
88
|
+
DEFAULT_CONFIG.store.chainCacheBytes,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
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
|
+
* A weighted deduction rule (a hyperedge): the conjunction of `premises`
|
|
43
|
+
* derives `conclusion` at an additional `cost`.
|
|
44
|
+
*/
|
|
45
|
+
export interface Rule<I> {
|
|
46
|
+
premises: readonly I[];
|
|
47
|
+
conclusion: I;
|
|
48
|
+
/** Local (edge) cost added on top of the premises' costs. Non-negative. */
|
|
49
|
+
cost: number;
|
|
50
|
+
/** The combinator this rule's firing uses at its conclusion:
|
|
51
|
+
* • `"min"` (default, omitted) — Knuth/A* proper: the conclusion's cost is
|
|
52
|
+
* the CHEAPEST of any rule that reaches it, every other route discarded.
|
|
53
|
+
* The shortest-path monoid (min, +) that makes the search admissible and
|
|
54
|
+
* output-sensitive.
|
|
55
|
+
* • `"sum"` — evidence pooling: EVERY firing of a sum rule contributes its
|
|
56
|
+
* cost to the SAME conclusion (accumulated in {@link
|
|
57
|
+
* DeductionSystem.pool}), instead of competing to be the one cheapest
|
|
58
|
+
* route. The (+, +) monoid a consensus vote needs — several
|
|
59
|
+
* independent premises corroborating one conclusion — kept deliberately
|
|
60
|
+
* OUT of the min-cost chart: a pooled conclusion is never relaxed into
|
|
61
|
+
* `g`, never enters the agenda, and is never itself a premise — it is a
|
|
62
|
+
* terminal aggregate the caller reads out of `pool` once the search is
|
|
63
|
+
* done. */
|
|
64
|
+
combine?: "min" | "sum";
|
|
65
|
+
}
|
|
66
|
+
/** One rule's contribution to a pooled (`combine: "sum"`) conclusion — the
|
|
67
|
+
* firing rule and the already-finalised derivations of its premises, so a
|
|
68
|
+
* caller can render each contribution exactly as it would a min-cost step. */
|
|
69
|
+
export interface PooledContribution<I> {
|
|
70
|
+
rule: Rule<I>;
|
|
71
|
+
premises: Array<Derivation<I>>;
|
|
72
|
+
}
|
|
73
|
+
/** The running aggregate at one pooled conclusion: every sum-mode rule that
|
|
74
|
+
* has fired for it, accumulated. */
|
|
75
|
+
export interface PooledConclusion<I> {
|
|
76
|
+
item: I;
|
|
77
|
+
cost: number;
|
|
78
|
+
contributions: Array<PooledContribution<I>>;
|
|
79
|
+
}
|
|
80
|
+
/** The problem the solver is given: items, rules, a goal, and a heuristic. */
|
|
81
|
+
export interface DeductionSystem<I> {
|
|
82
|
+
/** Canonical key for chart memoization (the item's boundary signature). */
|
|
83
|
+
key(item: I): string;
|
|
84
|
+
/** Axioms: the atomic items and their base costs (the search's seeds). */
|
|
85
|
+
axioms(): Iterable<{
|
|
86
|
+
item: I;
|
|
87
|
+
cost: number;
|
|
88
|
+
}>;
|
|
89
|
+
/**
|
|
90
|
+
* Lazily generate the demanded rules that have `item` among their premises.
|
|
91
|
+
* Called once, when `item` is finalised. `costOf` returns the finalised cost
|
|
92
|
+
* of any item (Infinity if not yet known) — use it for backward-demand /
|
|
93
|
+
* boundary filtering, e.g. drop a rule whose other premises are still open or
|
|
94
|
+
* whose conclusion can no longer beat the best goal.
|
|
95
|
+
*/
|
|
96
|
+
rules(item: I, costOf: (other: I) => number): Iterable<Rule<I>>;
|
|
97
|
+
/** Whether `item` satisfies the goal. The first finalised goal wins. */
|
|
98
|
+
isGoal(item: I): boolean;
|
|
99
|
+
/** Admissible, consistent lower bound on the cost from `item` to a goal. */
|
|
100
|
+
heuristic?(item: I): number;
|
|
101
|
+
/** Present only on a system that fires `combine: "sum"` rules — supplied
|
|
102
|
+
* empty, populated in place as the search runs, read back once it returns
|
|
103
|
+
* (typically `null`: a pooling system has no goal to reach, it exhausts its
|
|
104
|
+
* axioms instead — see {@link lightestDerivation}). Absent on every
|
|
105
|
+
* ordinary min-cost system, which is what keeps pooling a zero-cost opt-in:
|
|
106
|
+
* `relax` only takes the pooling branch when a rule declares `combine:
|
|
107
|
+
* "sum"` AND this map is present. */
|
|
108
|
+
pool?: Map<string, PooledConclusion<I>>;
|
|
109
|
+
}
|
|
110
|
+
/** A node of the reconstructed derivation tree. */
|
|
111
|
+
export interface Derivation<I> {
|
|
112
|
+
/** The derived item. */
|
|
113
|
+
item: I;
|
|
114
|
+
/** This item's minimum derivation cost (its g value). */
|
|
115
|
+
cost: number;
|
|
116
|
+
/** The rule that produced it, or null if it is an axiom. */
|
|
117
|
+
rule: Rule<I> | null;
|
|
118
|
+
/** Derivations of the rule's premises (empty for an axiom). */
|
|
119
|
+
premises: Array<Derivation<I>>;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Find a lightest derivation of a goal item, or `null` if none exists.
|
|
123
|
+
* `cost` on the returned root is the total derivation cost.
|
|
124
|
+
*/
|
|
125
|
+
export declare function lightestDerivation<I>(system: DeductionSystem<I>): Derivation<I> | null;
|
|
@@ -0,0 +1,155 @@
|
|
|
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) {
|
|
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
|
+
agenda.push(cost + h(item, key), { key, g: cost });
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
for (const { item, cost } of system.axioms())
|
|
89
|
+
relax(item, cost, null);
|
|
90
|
+
while (agenda.size > 0) {
|
|
91
|
+
const { value } = agenda.pop();
|
|
92
|
+
const key = value.key;
|
|
93
|
+
// Lazy deletion: an entry is stale if a cheaper derivation has since been
|
|
94
|
+
// recorded for the same item.
|
|
95
|
+
if (value.g !== g.get(key))
|
|
96
|
+
continue;
|
|
97
|
+
const item = items.get(key);
|
|
98
|
+
if (system.isGoal(item)) {
|
|
99
|
+
return reconstruct(item, system, g, proof);
|
|
100
|
+
}
|
|
101
|
+
for (const rule of system.rules(item, costOf)) {
|
|
102
|
+
let sum = rule.cost;
|
|
103
|
+
let ready = true;
|
|
104
|
+
for (const p of rule.premises) {
|
|
105
|
+
const pc = g.get(system.key(p));
|
|
106
|
+
if (pc === undefined) {
|
|
107
|
+
ready = false;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
sum += pc;
|
|
111
|
+
}
|
|
112
|
+
if (ready)
|
|
113
|
+
relax(rule.conclusion, sum, rule);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
function reconstruct(item, system, g, proof) {
|
|
119
|
+
// Iterative post-order over the derivation hypergraph. In the rewrite
|
|
120
|
+
// search every rule has one premise, so the derivation is a chain whose
|
|
121
|
+
// length equals the number of frontier edges — which, with long inputs,
|
|
122
|
+
// can exceed the call stack. Multi-premise rules (the test-suite bridge
|
|
123
|
+
// case) are handled by the same explicit stack.
|
|
124
|
+
const done = new Map();
|
|
125
|
+
const stack = [item];
|
|
126
|
+
while (stack.length > 0) {
|
|
127
|
+
const cur = stack[stack.length - 1]; // peek
|
|
128
|
+
const key = system.key(cur);
|
|
129
|
+
if (done.has(key)) {
|
|
130
|
+
stack.pop();
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const rule = proof.get(key) ?? null;
|
|
134
|
+
const premises = rule?.premises ?? [];
|
|
135
|
+
// Push any unresolved premises (rightmost first → leftmost resolves first).
|
|
136
|
+
let pending = false;
|
|
137
|
+
for (let i = premises.length - 1; i >= 0; i--) {
|
|
138
|
+
if (!done.has(system.key(premises[i]))) {
|
|
139
|
+
stack.push(premises[i]);
|
|
140
|
+
pending = true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (!pending) {
|
|
144
|
+
stack.pop(); // this item
|
|
145
|
+
const kids = premises.map((p) => done.get(system.key(p)));
|
|
146
|
+
done.set(key, {
|
|
147
|
+
item: cur,
|
|
148
|
+
cost: g.get(key),
|
|
149
|
+
rule,
|
|
150
|
+
premises: kids,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return done.get(system.key(item));
|
|
155
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { lightestDerivation } from "./deduction.js";
|
|
2
|
+
export type { DeductionSystem, Derivation, PooledConclusion, PooledContribution, Rule, } from "./deduction.js";
|
|
3
|
+
export { coverSequence } from "./rewrite.js";
|
|
4
|
+
export type { CandidateSpan, Cover } from "./rewrite.js";
|
|
5
|
+
export { Trie } from "./trie.js";
|
|
6
|
+
export type { Match } from "./trie.js";
|
|
7
|
+
export { MinHeap } from "./priority-queue.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
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";
|