@hviana/sema 0.1.4 → 0.1.5
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.js +25 -25
- package/dist/example/train_base.d.ts +21 -19
- package/dist/example/train_base.js +1952 -1684
- package/dist/src/alphabet.d.ts +3 -3
- package/dist/src/alphabet.js +27 -24
- package/dist/src/alu/src/alu.d.ts +194 -169
- package/dist/src/alu/src/alu.js +414 -374
- package/dist/src/alu/src/expr.d.ts +43 -36
- package/dist/src/alu/src/expr.js +278 -252
- package/dist/src/alu/src/index.d.ts +98 -9
- package/dist/src/alu/src/index.js +63 -9
- package/dist/src/alu/src/kernel-arith.d.ts +5 -1
- package/dist/src/alu/src/kernel-arith.js +285 -195
- package/dist/src/alu/src/kernel-bits.d.ts +5 -1
- package/dist/src/alu/src/kernel-bits.js +115 -81
- package/dist/src/alu/src/kernel-logic.js +63 -33
- package/dist/src/alu/src/kernel-nd.js +195 -155
- package/dist/src/alu/src/kernel-numeric.d.ts +80 -16
- package/dist/src/alu/src/kernel-numeric.js +331 -259
- package/dist/src/alu/src/operation.d.ts +132 -112
- package/dist/src/alu/src/operation.js +163 -149
- package/dist/src/alu/src/parser.d.ts +175 -168
- package/dist/src/alu/src/parser.js +421 -382
- package/dist/src/alu/src/resonance.d.ts +25 -16
- package/dist/src/alu/src/resonance.js +56 -49
- package/dist/src/alu/src/text.d.ts +7 -3
- package/dist/src/alu/src/text.js +37 -31
- package/dist/src/alu/src/value.d.ts +14 -14
- package/dist/src/alu/src/value.js +160 -150
- package/dist/src/alu/test/alu.test.js +656 -543
- package/dist/src/bytes.d.ts +5 -1
- package/dist/src/bytes.js +40 -33
- package/dist/src/config.d.ts +102 -102
- package/dist/src/config.js +83 -83
- package/dist/src/derive/src/deduction.d.ts +62 -60
- package/dist/src/derive/src/deduction.js +109 -103
- package/dist/src/derive/src/index.d.ts +7 -1
- package/dist/src/derive/src/priority-queue.d.ts +8 -8
- package/dist/src/derive/src/priority-queue.js +61 -57
- package/dist/src/derive/src/rewrite.d.ts +18 -15
- package/dist/src/derive/src/rewrite.js +67 -59
- package/dist/src/derive/src/trie.d.ts +56 -56
- package/dist/src/derive/src/trie.js +179 -175
- package/dist/src/derive/test/derive.test.js +100 -96
- package/dist/src/extension.d.ts +16 -13
- package/dist/src/geometry.d.ts +42 -17
- package/dist/src/geometry.js +266 -235
- package/dist/src/index.d.ts +17 -2
- package/dist/src/index.js +7 -1
- package/dist/src/ingest-cache.d.ts +30 -25
- package/dist/src/ingest-cache.js +127 -108
- package/dist/src/mind/articulation.d.ts +5 -1
- package/dist/src/mind/articulation.js +112 -77
- package/dist/src/mind/attention.d.ts +108 -40
- package/dist/src/mind/attention.js +871 -749
- package/dist/src/mind/canonical.d.ts +19 -4
- package/dist/src/mind/canonical.js +31 -27
- package/dist/src/mind/graph-search.d.ts +225 -201
- package/dist/src/mind/graph-search.js +821 -742
- package/dist/src/mind/index.d.ts +10 -2
- package/dist/src/mind/junction.d.ts +58 -31
- package/dist/src/mind/junction.js +237 -172
- package/dist/src/mind/learning.d.ts +52 -16
- package/dist/src/mind/learning.js +165 -143
- package/dist/src/mind/match.d.ts +69 -20
- package/dist/src/mind/match.js +318 -259
- package/dist/src/mind/mechanisms/alu.js +16 -16
- package/dist/src/mind/mechanisms/cast.d.ts +13 -9
- package/dist/src/mind/mechanisms/cast.js +456 -363
- package/dist/src/mind/mechanisms/confluence.d.ts +12 -8
- package/dist/src/mind/mechanisms/confluence.js +183 -152
- package/dist/src/mind/mechanisms/cover.d.ts +8 -2
- package/dist/src/mind/mechanisms/cover.js +210 -148
- package/dist/src/mind/mechanisms/extraction.d.ts +34 -8
- package/dist/src/mind/mechanisms/extraction.js +288 -234
- package/dist/src/mind/mechanisms/recall.d.ts +10 -6
- package/dist/src/mind/mechanisms/recall.js +185 -126
- package/dist/src/mind/mind.d.ts +154 -129
- package/dist/src/mind/mind.js +288 -258
- package/dist/src/mind/pipeline-mechanism.d.ts +124 -112
- package/dist/src/mind/pipeline-mechanism.js +172 -161
- package/dist/src/mind/pipeline.d.ts +14 -4
- package/dist/src/mind/pipeline.js +189 -125
- package/dist/src/mind/primitives.d.ts +32 -8
- package/dist/src/mind/primitives.js +117 -99
- package/dist/src/mind/rationale.d.ts +98 -86
- package/dist/src/mind/rationale.js +121 -113
- package/dist/src/mind/reasoning.d.ts +13 -2
- package/dist/src/mind/reasoning.js +166 -129
- package/dist/src/mind/recognition.d.ts +4 -1
- package/dist/src/mind/recognition.js +208 -183
- package/dist/src/mind/resonance.d.ts +22 -5
- package/dist/src/mind/resonance.js +0 -0
- package/dist/src/mind/trace.d.ts +25 -6
- package/dist/src/mind/trace.js +58 -50
- package/dist/src/mind/traverse.d.ts +58 -16
- package/dist/src/mind/traverse.js +357 -304
- package/dist/src/mind/types.d.ts +127 -120
- package/dist/src/mind/types.js +69 -60
- package/dist/src/rabitq-hnsw/src/database.d.ts +179 -177
- package/dist/src/rabitq-hnsw/src/database.js +359 -342
- package/dist/src/rabitq-hnsw/src/heap.d.ts +12 -12
- package/dist/src/rabitq-hnsw/src/heap.js +80 -75
- package/dist/src/rabitq-hnsw/src/hnsw.d.ts +103 -103
- package/dist/src/rabitq-hnsw/src/hnsw.js +454 -428
- package/dist/src/rabitq-hnsw/src/index.d.ts +6 -1
- package/dist/src/rabitq-hnsw/src/prng.d.ts +10 -10
- package/dist/src/rabitq-hnsw/src/prng.js +27 -25
- package/dist/src/rabitq-hnsw/src/rabitq.d.ts +69 -69
- package/dist/src/rabitq-hnsw/src/rabitq.js +262 -246
- package/dist/src/rabitq-hnsw/src/store.d.ts +132 -132
- package/dist/src/rabitq-hnsw/src/store.js +843 -752
- package/dist/src/rabitq-hnsw/test/hnsw.test.js +1054 -805
- package/dist/src/sema.d.ts +13 -9
- package/dist/src/sema.js +40 -26
- package/dist/src/store-sqlite.d.ts +166 -144
- package/dist/src/store-sqlite.js +660 -578
- package/dist/src/store.d.ts +630 -593
- package/dist/src/store.js +1581 -1440
- package/dist/src/vec.d.ts +9 -5
- package/dist/src/vec.js +73 -61
- package/package.json +2 -2
|
@@ -13,29 +13,34 @@ import type { Input } from "./mind/index.js";
|
|
|
13
13
|
* All other Mind methods (respond, save, …) are accessed on `ci.mind`.
|
|
14
14
|
*/
|
|
15
15
|
export declare class CachedIngest {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
16
|
+
readonly mind: Mind;
|
|
17
|
+
/** One unified, content-addressed memo of interned inputs. Each value holds
|
|
18
|
+
* Float32 root vector (D·4 bytes) plus a small id array, so the byte budget
|
|
19
|
+
* is spent on the inputs most worth remembering (LRU). */
|
|
20
|
+
private _memo;
|
|
21
|
+
hits: number;
|
|
22
|
+
misses: number;
|
|
23
|
+
constructor(mind: Mind);
|
|
24
|
+
private newMemo;
|
|
25
|
+
private keyOf;
|
|
26
|
+
ingest(
|
|
27
|
+
input: Input | (Input | [Input, Input])[],
|
|
28
|
+
second?: Input,
|
|
29
|
+
): Promise<
|
|
30
|
+
(Sema & {
|
|
31
|
+
id: number;
|
|
32
|
+
}) | undefined
|
|
33
|
+
>;
|
|
34
|
+
/** Resolve an input to its interned nodes, from the memo when its content has
|
|
35
|
+
* been seen before, else by the Mind's OWN untracked {@link deposit}
|
|
36
|
+
* (perceive + intern + sub-span/containment indexing — identical to the
|
|
37
|
+
* direct path; those writes are durable and idempotent, so a later memo hit
|
|
38
|
+
* legitimately skips them). This is the single expensive operation both
|
|
39
|
+
* ingest paths share; caching it here is what makes every repeated input —
|
|
40
|
+
* in any role — cheap. */
|
|
41
|
+
private resolveInput;
|
|
42
|
+
private ingestPair;
|
|
43
|
+
private ingestOne;
|
|
44
|
+
clear(): void;
|
|
45
|
+
get size(): number;
|
|
41
46
|
}
|
package/dist/src/ingest-cache.js
CHANGED
|
@@ -31,7 +31,11 @@
|
|
|
31
31
|
// and drifted: no indexSubSpans/addContainer, wrong part-link stride). Only
|
|
32
32
|
// the memo layer lives here. Behaviour is identical to mind.ingest() (links
|
|
33
33
|
// and halos are reinforced on every call), just faster when content repeats.
|
|
34
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
deposit,
|
|
36
|
+
dispatchIngest,
|
|
37
|
+
ingestOne as depositOne,
|
|
38
|
+
} from "./mind/learning.js";
|
|
35
39
|
import { bindSeat, companySignature } from "./sema.js";
|
|
36
40
|
import { BoundedMap } from "./store.js";
|
|
37
41
|
/**
|
|
@@ -46,116 +50,131 @@ import { BoundedMap } from "./store.js";
|
|
|
46
50
|
* All other Mind methods (respond, save, …) are accessed on `ci.mind`.
|
|
47
51
|
*/
|
|
48
52
|
export class CachedIngest {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
mind;
|
|
54
|
+
/** One unified, content-addressed memo of interned inputs. Each value holds
|
|
55
|
+
* Float32 root vector (D·4 bytes) plus a small id array, so the byte budget
|
|
56
|
+
* is spent on the inputs most worth remembering (LRU). */
|
|
57
|
+
_memo;
|
|
58
|
+
hits = 0;
|
|
59
|
+
misses = 0;
|
|
60
|
+
constructor(mind) {
|
|
61
|
+
this.mind = mind;
|
|
62
|
+
this._memo = this.newMemo();
|
|
63
|
+
}
|
|
64
|
+
newMemo() {
|
|
65
|
+
return new BoundedMap(
|
|
66
|
+
this.mind.cfg.store.ingestCacheBytes,
|
|
67
|
+
(c) => c.rootV.byteLength + c.partIds.length * 4 + 8 + c.keyBytes,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
// ── cache key ───────────────────────────────────────────────────────────
|
|
71
|
+
// Content key for an input, or null when the input cannot be keyed reliably
|
|
72
|
+
// — in which case it BYPASSES the cache (recomputed every time) rather than
|
|
73
|
+
// risk a collision returning the wrong nodes. A string is its own key; raw
|
|
74
|
+
// bytes hash by FNV-1a (+ length, so a hash collision still needs an equal
|
|
75
|
+
// length to alias); a Grid / Grid[] has no cheap stable key, so it bypasses.
|
|
76
|
+
keyOf(input) {
|
|
77
|
+
if (typeof input === "string") {
|
|
78
|
+
return "S:" + input;
|
|
59
79
|
}
|
|
60
|
-
|
|
61
|
-
|
|
80
|
+
if (input instanceof Uint8Array) {
|
|
81
|
+
// FNV-1a — no spread onto the call stack, safe on large binary inputs.
|
|
82
|
+
let h = 0x811c9dc5 >>> 0;
|
|
83
|
+
for (let i = 0; i < input.length; i++) {
|
|
84
|
+
h ^= input[i];
|
|
85
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
86
|
+
}
|
|
87
|
+
return "B:" + (h >>> 0).toString(16) + ":" + input.length;
|
|
62
88
|
}
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
89
|
+
return null; // Grid / Grid[] — bypass the cache
|
|
90
|
+
}
|
|
91
|
+
// ── public API ────────────────────────────────────────────────────────
|
|
92
|
+
async ingest(input, second) {
|
|
93
|
+
// One shape-reading for both ingest paths — see {@link dispatchIngest}.
|
|
94
|
+
return dispatchIngest(
|
|
95
|
+
input,
|
|
96
|
+
second,
|
|
97
|
+
(i) => this.ingestOne(i),
|
|
98
|
+
(a, b) => this.ingestPair(a, b),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
// ── the one cached step: perceive + intern + index ONE input ──────────────
|
|
102
|
+
/** Resolve an input to its interned nodes, from the memo when its content has
|
|
103
|
+
* been seen before, else by the Mind's OWN untracked {@link deposit}
|
|
104
|
+
* (perceive + intern + sub-span/containment indexing — identical to the
|
|
105
|
+
* direct path; those writes are durable and idempotent, so a later memo hit
|
|
106
|
+
* legitimately skips them). This is the single expensive operation both
|
|
107
|
+
* ingest paths share; caching it here is what makes every repeated input —
|
|
108
|
+
* in any role — cheap. */
|
|
109
|
+
async resolveInput(input) {
|
|
110
|
+
const key = this.keyOf(input);
|
|
111
|
+
if (key !== null) {
|
|
112
|
+
const cached = this._memo.get(key);
|
|
113
|
+
if (cached) {
|
|
114
|
+
this.hits++;
|
|
115
|
+
return cached;
|
|
116
|
+
}
|
|
82
117
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
118
|
+
this.misses++;
|
|
119
|
+
// The full deposit, untracked (a continuation must not break the tracked
|
|
120
|
+
// deposit chain). `partIds` are the root's immediate children, already
|
|
121
|
+
// interned by the deposit — free to capture. Only the root vector and
|
|
122
|
+
// ids are retained; the tree is GC'd.
|
|
123
|
+
const { tree, rootId, ids } = await deposit(this.mind, input, false);
|
|
124
|
+
const partIds = tree.kids ? tree.kids.map((k) => ids.get(k)) : [rootId];
|
|
125
|
+
const entry = {
|
|
126
|
+
rootV: tree.v,
|
|
127
|
+
rootId,
|
|
128
|
+
partIds,
|
|
129
|
+
// ~2 bytes per UTF-16 code unit — the key is retained by the map.
|
|
130
|
+
keyBytes: key !== null ? key.length * 2 : 0,
|
|
131
|
+
};
|
|
132
|
+
if (key !== null) {
|
|
133
|
+
this._memo.set(key, entry);
|
|
87
134
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
rootId,
|
|
117
|
-
partIds,
|
|
118
|
-
// ~2 bytes per UTF-16 code unit — the key is retained by the map.
|
|
119
|
-
keyBytes: key !== null ? key.length * 2 : 0,
|
|
120
|
-
};
|
|
121
|
-
if (key !== null)
|
|
122
|
-
this._memo.set(key, entry);
|
|
123
|
-
return entry;
|
|
124
|
-
}
|
|
125
|
-
// ── pair ingest ───────────────────────────────────────────────────────
|
|
126
|
-
async ingestPair(ctx, cont) {
|
|
127
|
-
// The CONTEXT side is TRACKED (it continues the Mind's deposit chain and
|
|
128
|
-
// is the growing, unique-per-turn side — a cache miss anyway), via the
|
|
129
|
-
// same {@link deposit} the direct path uses; the continuation resolves
|
|
130
|
-
// through the memo.
|
|
131
|
-
const c = await deposit(this.mind, ctx, true);
|
|
132
|
-
const b = await this.resolveInput(cont);
|
|
133
|
-
// EDGE on the FULL context (disambiguates a shared pivot). HALO only on the
|
|
134
|
-
// CHANGED NODES — the coherent new subtrees this turn adds — so a recurring
|
|
135
|
-
// shared prefix never collects halo mass. (See Mind.ingestPair.)
|
|
136
|
-
await this.mind.store.link(c.rootId, b.rootId);
|
|
137
|
-
// Halos pour company SIGNATURES (identity), not gists (content) — see
|
|
138
|
-
// companySignature in sema.ts.
|
|
139
|
-
const contSeat = bindSeat(this.mind.space, companySignature(this.mind.space, b.rootId), 1);
|
|
140
|
-
for (const part of c.changed) {
|
|
141
|
-
const partId = c.ids.get(part);
|
|
142
|
-
await this.mind.store.pourHalo(partId, contSeat);
|
|
143
|
-
await this.mind.store.pourHalo(b.rootId, bindSeat(this.mind.space, companySignature(this.mind.space, partId), 0));
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
// ── one-side ingest ───────────────────────────────────────────────────
|
|
147
|
-
async ingestOne(input) {
|
|
148
|
-
// A bare experience is the direct path verbatim — tracked deposit, root
|
|
149
|
-
// marked a resonance target, part chain linked at the maxGroup stride.
|
|
150
|
-
// (The tracked side is a memo miss by design, so there is nothing for the
|
|
151
|
-
// cache to add here.)
|
|
152
|
-
return depositOne(this.mind, input);
|
|
153
|
-
}
|
|
154
|
-
// ── helpers ───────────────────────────────────────────────────────────
|
|
155
|
-
clear() {
|
|
156
|
-
this._memo = this.newMemo();
|
|
157
|
-
}
|
|
158
|
-
get size() {
|
|
159
|
-
return this._memo.size;
|
|
135
|
+
return entry;
|
|
136
|
+
}
|
|
137
|
+
// ── pair ingest ───────────────────────────────────────────────────────
|
|
138
|
+
async ingestPair(ctx, cont) {
|
|
139
|
+
// The CONTEXT side is TRACKED (it continues the Mind's deposit chain and
|
|
140
|
+
// is the growing, unique-per-turn side — a cache miss anyway), via the
|
|
141
|
+
// same {@link deposit} the direct path uses; the continuation resolves
|
|
142
|
+
// through the memo.
|
|
143
|
+
const c = await deposit(this.mind, ctx, true);
|
|
144
|
+
const b = await this.resolveInput(cont);
|
|
145
|
+
// EDGE on the FULL context (disambiguates a shared pivot). HALO only on the
|
|
146
|
+
// CHANGED NODES — the coherent new subtrees this turn adds — so a recurring
|
|
147
|
+
// shared prefix never collects halo mass. (See Mind.ingestPair.)
|
|
148
|
+
await this.mind.store.link(c.rootId, b.rootId);
|
|
149
|
+
// Halos pour company SIGNATURES (identity), not gists (content) — see
|
|
150
|
+
// companySignature in sema.ts.
|
|
151
|
+
const contSeat = bindSeat(
|
|
152
|
+
this.mind.space,
|
|
153
|
+
companySignature(this.mind.space, b.rootId),
|
|
154
|
+
1,
|
|
155
|
+
);
|
|
156
|
+
for (const part of c.changed) {
|
|
157
|
+
const partId = c.ids.get(part);
|
|
158
|
+
await this.mind.store.pourHalo(partId, contSeat);
|
|
159
|
+
await this.mind.store.pourHalo(
|
|
160
|
+
b.rootId,
|
|
161
|
+
bindSeat(this.mind.space, companySignature(this.mind.space, partId), 0),
|
|
162
|
+
);
|
|
160
163
|
}
|
|
164
|
+
}
|
|
165
|
+
// ── one-side ingest ───────────────────────────────────────────────────
|
|
166
|
+
async ingestOne(input) {
|
|
167
|
+
// A bare experience is the direct path verbatim — tracked deposit, root
|
|
168
|
+
// marked a resonance target, part chain linked at the maxGroup stride.
|
|
169
|
+
// (The tracked side is a memo miss by design, so there is nothing for the
|
|
170
|
+
// cache to add here.)
|
|
171
|
+
return depositOne(this.mind, input);
|
|
172
|
+
}
|
|
173
|
+
// ── helpers ───────────────────────────────────────────────────────────
|
|
174
|
+
clear() {
|
|
175
|
+
this._memo = this.newMemo();
|
|
176
|
+
}
|
|
177
|
+
get size() {
|
|
178
|
+
return this._memo.size;
|
|
179
|
+
}
|
|
161
180
|
}
|
|
@@ -3,4 +3,8 @@ import type { MindContext } from "./types.js";
|
|
|
3
3
|
* in the answer, find a concept-sibling in the query (by halo resonance)
|
|
4
4
|
* and substitute the asker's wording. The search's own cover mechanism
|
|
5
5
|
* splices the substitutes into the answer exactly where the forms sit. */
|
|
6
|
-
export declare function articulate(
|
|
6
|
+
export declare function articulate(
|
|
7
|
+
ctx: MindContext,
|
|
8
|
+
answer: Uint8Array,
|
|
9
|
+
query: Uint8Array | null,
|
|
10
|
+
): Promise<Uint8Array>;
|
|
@@ -13,87 +13,122 @@ import { rItem, rNode, traceDerivation } from "./trace.js";
|
|
|
13
13
|
* and substitute the asker's wording. The search's own cover mechanism
|
|
14
14
|
* splices the substitutes into the answer exactly where the forms sit. */
|
|
15
15
|
export async function articulate(ctx, answer, query) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (!h)
|
|
39
|
-
continue;
|
|
40
|
-
qCandidates.push({
|
|
41
|
-
start: s.start,
|
|
42
|
-
end: s.end,
|
|
43
|
-
payload: {
|
|
44
|
-
halo: h,
|
|
45
|
-
bytes: query.slice(s.start, s.end),
|
|
46
|
-
node: s.payload,
|
|
47
|
-
},
|
|
48
|
-
});
|
|
16
|
+
if (!query || query.length < 2 || answer.length < 2) {
|
|
17
|
+
return answer;
|
|
18
|
+
}
|
|
19
|
+
const store = ctx.store;
|
|
20
|
+
const t = ctx.trace?.enter("articulate", [
|
|
21
|
+
rItem(answer, "answer"),
|
|
22
|
+
rItem(query, "query"),
|
|
23
|
+
]);
|
|
24
|
+
const keep = (note) => {
|
|
25
|
+
t?.done([rItem(answer, "answer")], note);
|
|
26
|
+
return answer;
|
|
27
|
+
};
|
|
28
|
+
const qCandidates = [];
|
|
29
|
+
for (const s of recognise(ctx, query).sites) {
|
|
30
|
+
// payload < 0 is a SINGLE-BYTE leaf (byte leaves occupy the implicit
|
|
31
|
+
// negative id range −256…−1 — see store.ts). A one-byte form's halo is
|
|
32
|
+
// promiscuous (it keeps company with everything it ever neighboured), so
|
|
33
|
+
// admitting it as a voice or a revoicing target lets e.g. the digit "4"
|
|
34
|
+
// be "re-voiced" into an unrelated digit the query happens to contain.
|
|
35
|
+
// Articulation therefore operates on multi-byte forms only.
|
|
36
|
+
if (s.payload < 0) {
|
|
37
|
+
continue;
|
|
49
38
|
}
|
|
50
|
-
|
|
51
|
-
|
|
39
|
+
const h = store.halo(s.payload);
|
|
40
|
+
if (!h) {
|
|
41
|
+
continue;
|
|
52
42
|
}
|
|
53
|
-
|
|
54
|
-
|
|
43
|
+
qCandidates.push({
|
|
44
|
+
start: s.start,
|
|
45
|
+
end: s.end,
|
|
46
|
+
payload: {
|
|
47
|
+
halo: h,
|
|
48
|
+
bytes: query.slice(s.start, s.end),
|
|
49
|
+
node: s.payload,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (qCandidates.length === 0) {
|
|
54
|
+
return keep("no asker concept to revoice — answer unchanged");
|
|
55
|
+
}
|
|
56
|
+
if (coverSequence(query.length, qCandidates).spans.length === 0) {
|
|
57
|
+
return keep("no asker concept to revoice — answer unchanged");
|
|
58
|
+
}
|
|
59
|
+
const voices = qCandidates.map((s) => s.payload);
|
|
60
|
+
const ans = recognise(ctx, answer);
|
|
61
|
+
const substitutions = new Map();
|
|
62
|
+
for (const s of ans.sites) {
|
|
63
|
+
// Same single-byte-leaf exclusion as the query loop above.
|
|
64
|
+
if (s.payload < 0) {
|
|
65
|
+
continue;
|
|
55
66
|
}
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
for (const s of ans.sites) {
|
|
60
|
-
// Same single-byte-leaf exclusion as the query loop above.
|
|
61
|
-
if (s.payload < 0)
|
|
62
|
-
continue;
|
|
63
|
-
const h = store.halo(s.payload);
|
|
64
|
-
if (!h)
|
|
65
|
-
continue;
|
|
66
|
-
const found = bestHaloMate(ctx, h, voices, (v) => v.halo);
|
|
67
|
-
if (!found)
|
|
68
|
-
continue;
|
|
69
|
-
const voice = found.item;
|
|
70
|
-
if (voice.node === s.payload || contains(ctx, voice.node, s.payload)) {
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
substitutions.set(s.payload, voice.bytes);
|
|
67
|
+
const h = store.halo(s.payload);
|
|
68
|
+
if (!h) {
|
|
69
|
+
continue;
|
|
74
70
|
}
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
const found = bestHaloMate(ctx, h, voices, (v) => v.halo);
|
|
72
|
+
if (!found) {
|
|
73
|
+
continue;
|
|
77
74
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
...voicedSites.map((s) => rItem(answer.subarray(s.start, s.end), "form", s.payload, [
|
|
82
|
-
s.start,
|
|
83
|
-
s.end,
|
|
84
|
-
])),
|
|
85
|
-
]);
|
|
86
|
-
const solved = ctx.search.cover(answer.length, voicedSites, new Map(), ans.leaves, ans.splits, substitutions, undefined, undefined, ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined);
|
|
87
|
-
const segs = solved && solved.segs;
|
|
88
|
-
tArtCover?.done(segs === null
|
|
89
|
-
? []
|
|
90
|
-
: segs.map((s) => rItem(s.bytes, s.rec ? "voiced" : "kept", s.node, [s.i, s.j])), segs === null
|
|
91
|
-
? "the revoiced cover did not compose"
|
|
92
|
-
: "lightest derivation of the revoiced answer");
|
|
93
|
-
const result = segs && spliceAll(segs);
|
|
94
|
-
if (result) {
|
|
95
|
-
t?.done([rItem(result, "voiced", ctx.store.findLeaf(result.subarray(0, 1)) ?? undefined)], "splice the asker's wording into the answer where the same concept appears");
|
|
96
|
-
return result;
|
|
75
|
+
const voice = found.item;
|
|
76
|
+
if (voice.node === s.payload || contains(ctx, voice.node, s.payload)) {
|
|
77
|
+
continue;
|
|
97
78
|
}
|
|
98
|
-
|
|
79
|
+
substitutions.set(s.payload, voice.bytes);
|
|
80
|
+
}
|
|
81
|
+
if (substitutions.size === 0) {
|
|
82
|
+
return keep("no answer form is a synonym of an asker concept");
|
|
83
|
+
}
|
|
84
|
+
ctx.trace?.step(
|
|
85
|
+
"substitute",
|
|
86
|
+
[...substitutions.keys()].map((n) => rNode(ctx, n, "answer-form")),
|
|
87
|
+
[...substitutions.values()].map((b) => rItem(b, "asker-voice")),
|
|
88
|
+
`revoice ${substitutions.size} answer form(s) in the asker's own words (synonym splice)`,
|
|
89
|
+
);
|
|
90
|
+
const voicedSites = ans.sites.filter((s) => substitutions.has(s.payload));
|
|
91
|
+
const tArtCover = ctx.trace?.enter("cover", [
|
|
92
|
+
...voicedSites.map((s) =>
|
|
93
|
+
rItem(answer.subarray(s.start, s.end), "form", s.payload, [
|
|
94
|
+
s.start,
|
|
95
|
+
s.end,
|
|
96
|
+
])
|
|
97
|
+
),
|
|
98
|
+
]);
|
|
99
|
+
const solved = ctx.search.cover(
|
|
100
|
+
answer.length,
|
|
101
|
+
voicedSites,
|
|
102
|
+
new Map(),
|
|
103
|
+
ans.leaves,
|
|
104
|
+
ans.splits,
|
|
105
|
+
substitutions,
|
|
106
|
+
undefined,
|
|
107
|
+
undefined,
|
|
108
|
+
ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined,
|
|
109
|
+
);
|
|
110
|
+
const segs = solved && solved.segs;
|
|
111
|
+
tArtCover?.done(
|
|
112
|
+
segs === null
|
|
113
|
+
? []
|
|
114
|
+
: segs.map((s) =>
|
|
115
|
+
rItem(s.bytes, s.rec ? "voiced" : "kept", s.node, [s.i, s.j])
|
|
116
|
+
),
|
|
117
|
+
segs === null
|
|
118
|
+
? "the revoiced cover did not compose"
|
|
119
|
+
: "lightest derivation of the revoiced answer",
|
|
120
|
+
);
|
|
121
|
+
const result = segs && spliceAll(segs);
|
|
122
|
+
if (result) {
|
|
123
|
+
t?.done(
|
|
124
|
+
[rItem(
|
|
125
|
+
result,
|
|
126
|
+
"voiced",
|
|
127
|
+
ctx.store.findLeaf(result.subarray(0, 1)) ?? undefined,
|
|
128
|
+
)],
|
|
129
|
+
"splice the asker's wording into the answer where the same concept appears",
|
|
130
|
+
);
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
return keep("the revoiced cover did not compose — answer unchanged");
|
|
99
134
|
}
|