@hviana/sema 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/AGENTS.md +6 -5
  2. package/CITATION.cff +49 -0
  3. package/HOW_IT_WORKS.md +11 -12
  4. package/README.md +7 -5
  5. package/dist/example/demo.js +25 -25
  6. package/dist/example/train_base.d.ts +19 -21
  7. package/dist/example/train_base.js +1687 -1952
  8. package/dist/src/alphabet.d.ts +3 -3
  9. package/dist/src/alphabet.js +24 -27
  10. package/dist/src/alu/src/alu.d.ts +169 -194
  11. package/dist/src/alu/src/alu.js +374 -414
  12. package/dist/src/alu/src/expr.d.ts +36 -43
  13. package/dist/src/alu/src/expr.js +252 -278
  14. package/dist/src/alu/src/index.d.ts +9 -98
  15. package/dist/src/alu/src/index.js +10 -64
  16. package/dist/src/alu/src/kernel-arith.d.ts +1 -5
  17. package/dist/src/alu/src/kernel-arith.js +195 -285
  18. package/dist/src/alu/src/kernel-bits.d.ts +1 -5
  19. package/dist/src/alu/src/kernel-bits.js +81 -115
  20. package/dist/src/alu/src/kernel-logic.js +33 -63
  21. package/dist/src/alu/src/kernel-nd.js +155 -195
  22. package/dist/src/alu/src/kernel-numeric.d.ts +16 -80
  23. package/dist/src/alu/src/kernel-numeric.js +259 -331
  24. package/dist/src/alu/src/operation.d.ts +112 -132
  25. package/dist/src/alu/src/operation.js +149 -163
  26. package/dist/src/alu/src/parser.d.ts +168 -175
  27. package/dist/src/alu/src/parser.js +382 -421
  28. package/dist/src/alu/src/resonance.d.ts +16 -25
  29. package/dist/src/alu/src/resonance.js +49 -56
  30. package/dist/src/alu/src/text.d.ts +3 -7
  31. package/dist/src/alu/src/text.js +31 -37
  32. package/dist/src/alu/src/value.d.ts +14 -14
  33. package/dist/src/alu/src/value.js +150 -160
  34. package/dist/src/alu/test/alu.test.js +543 -656
  35. package/dist/src/bytes.d.ts +1 -5
  36. package/dist/src/bytes.js +33 -40
  37. package/dist/src/config.d.ts +99 -102
  38. package/dist/src/config.js +78 -83
  39. package/dist/src/derive/src/deduction.d.ts +60 -62
  40. package/dist/src/derive/src/deduction.js +103 -109
  41. package/dist/src/derive/src/index.d.ts +1 -7
  42. package/dist/src/derive/src/priority-queue.d.ts +8 -8
  43. package/dist/src/derive/src/priority-queue.js +57 -61
  44. package/dist/src/derive/src/rewrite.d.ts +15 -18
  45. package/dist/src/derive/src/rewrite.js +59 -67
  46. package/dist/src/derive/src/trie.d.ts +56 -56
  47. package/dist/src/derive/src/trie.js +175 -179
  48. package/dist/src/derive/test/derive.test.js +96 -100
  49. package/dist/src/extension.d.ts +13 -16
  50. package/dist/src/geometry.d.ts +17 -42
  51. package/dist/src/geometry.js +235 -266
  52. package/dist/src/index.d.ts +2 -17
  53. package/dist/src/index.js +2 -12
  54. package/dist/src/ingest-cache.d.ts +25 -30
  55. package/dist/src/ingest-cache.js +108 -127
  56. package/dist/src/mind/articulation.d.ts +1 -5
  57. package/dist/src/mind/articulation.js +77 -112
  58. package/dist/src/mind/attention.d.ts +40 -108
  59. package/dist/src/mind/attention.js +749 -871
  60. package/dist/src/mind/canonical.d.ts +4 -19
  61. package/dist/src/mind/canonical.js +27 -31
  62. package/dist/src/mind/graph-search.d.ts +201 -225
  63. package/dist/src/mind/graph-search.js +742 -821
  64. package/dist/src/mind/index.d.ts +2 -10
  65. package/dist/src/mind/junction.d.ts +31 -58
  66. package/dist/src/mind/junction.js +172 -237
  67. package/dist/src/mind/learning.d.ts +16 -52
  68. package/dist/src/mind/learning.js +143 -165
  69. package/dist/src/mind/match.d.ts +20 -69
  70. package/dist/src/mind/match.js +259 -318
  71. package/dist/src/mind/mechanisms/alu.js +16 -16
  72. package/dist/src/mind/mechanisms/cast.d.ts +9 -13
  73. package/dist/src/mind/mechanisms/cast.js +363 -456
  74. package/dist/src/mind/mechanisms/confluence.d.ts +8 -12
  75. package/dist/src/mind/mechanisms/confluence.js +152 -183
  76. package/dist/src/mind/mechanisms/cover.d.ts +2 -8
  77. package/dist/src/mind/mechanisms/cover.js +148 -210
  78. package/dist/src/mind/mechanisms/extraction.d.ts +8 -34
  79. package/dist/src/mind/mechanisms/extraction.js +234 -288
  80. package/dist/src/mind/mechanisms/recall.d.ts +6 -10
  81. package/dist/src/mind/mechanisms/recall.js +126 -185
  82. package/dist/src/mind/mind.d.ts +129 -154
  83. package/dist/src/mind/mind.js +258 -288
  84. package/dist/src/mind/pipeline-mechanism.d.ts +112 -124
  85. package/dist/src/mind/pipeline-mechanism.js +161 -172
  86. package/dist/src/mind/pipeline.d.ts +4 -14
  87. package/dist/src/mind/pipeline.js +125 -189
  88. package/dist/src/mind/primitives.d.ts +8 -32
  89. package/dist/src/mind/primitives.js +99 -117
  90. package/dist/src/mind/rationale.d.ts +86 -98
  91. package/dist/src/mind/rationale.js +113 -121
  92. package/dist/src/mind/reasoning.d.ts +2 -13
  93. package/dist/src/mind/reasoning.js +129 -166
  94. package/dist/src/mind/recognition.d.ts +1 -4
  95. package/dist/src/mind/recognition.js +183 -208
  96. package/dist/src/mind/resonance.d.ts +5 -22
  97. package/dist/src/mind/resonance.js +0 -0
  98. package/dist/src/mind/trace.d.ts +6 -25
  99. package/dist/src/mind/trace.js +50 -58
  100. package/dist/src/mind/traverse.d.ts +16 -58
  101. package/dist/src/mind/traverse.js +304 -357
  102. package/dist/src/mind/types.d.ts +120 -127
  103. package/dist/src/mind/types.js +60 -69
  104. package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
  105. package/dist/src/rabitq-ivf/src/database.js +201 -0
  106. package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
  107. package/dist/src/{rabitq-hnsw → rabitq-ivf}/src/index.js +1 -3
  108. package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
  109. package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
  110. package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
  111. package/dist/src/rabitq-ivf/src/prng.js +36 -0
  112. package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
  113. package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
  114. package/dist/src/sema.d.ts +9 -13
  115. package/dist/src/sema.js +26 -40
  116. package/dist/src/store-sqlite.d.ts +169 -166
  117. package/dist/src/store-sqlite.js +762 -662
  118. package/dist/src/store.d.ts +586 -630
  119. package/dist/src/store.js +1423 -1581
  120. package/dist/src/vec.d.ts +5 -9
  121. package/dist/src/vec.js +61 -73
  122. package/example/train_base.ts +13 -10
  123. package/package.json +1 -1
  124. package/src/alu/README.md +1 -1
  125. package/src/alu/src/index.ts +1 -1
  126. package/src/config.ts +19 -27
  127. package/src/index.ts +6 -11
  128. package/src/rabitq-ivf/README.md +56 -0
  129. package/src/rabitq-ivf/src/database.ts +276 -0
  130. package/src/{rabitq-hnsw → rabitq-ivf}/src/index.ts +2 -5
  131. package/src/rabitq-ivf/src/ivf.ts +1330 -0
  132. package/src/{rabitq-hnsw → rabitq-ivf}/src/prng.ts +1 -1
  133. package/src/store-sqlite.ts +196 -9
  134. package/src/store.ts +8 -32
  135. package/test/08-storage.test.mjs +3 -3
  136. package/test/14-scaling.test.mjs +2 -2
  137. package/test/35-ivf.test.mjs +263 -0
  138. package/test/36-bloom.test.mjs +123 -0
  139. package/dist/src/rabitq-hnsw/src/database.d.ts +0 -202
  140. package/dist/src/rabitq-hnsw/src/database.js +0 -405
  141. package/dist/src/rabitq-hnsw/src/heap.d.ts +0 -22
  142. package/dist/src/rabitq-hnsw/src/heap.js +0 -94
  143. package/dist/src/rabitq-hnsw/src/hnsw.d.ts +0 -125
  144. package/dist/src/rabitq-hnsw/src/hnsw.js +0 -500
  145. package/dist/src/rabitq-hnsw/src/index.d.ts +0 -15
  146. package/dist/src/rabitq-hnsw/src/prng.d.ts +0 -19
  147. package/dist/src/rabitq-hnsw/src/prng.js +0 -38
  148. package/dist/src/rabitq-hnsw/src/rabitq.d.ts +0 -95
  149. package/dist/src/rabitq-hnsw/src/rabitq.js +0 -299
  150. package/dist/src/rabitq-hnsw/src/store.d.ts +0 -162
  151. package/dist/src/rabitq-hnsw/src/store.js +0 -916
  152. package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +0 -1
  153. package/dist/src/rabitq-hnsw/test/hnsw.test.js +0 -1197
  154. package/src/rabitq-hnsw/README.md +0 -303
  155. package/src/rabitq-hnsw/src/database.ts +0 -492
  156. package/src/rabitq-hnsw/src/heap.ts +0 -90
  157. package/src/rabitq-hnsw/src/hnsw.ts +0 -514
  158. package/src/rabitq-hnsw/src/store.ts +0 -994
  159. package/src/rabitq-hnsw/test/hnsw.test.ts +0 -1213
  160. /package/src/{rabitq-hnsw → rabitq-ivf}/src/rabitq.ts +0 -0
@@ -0,0 +1,276 @@
1
+ import { IvfHit, IvfIndex } from "./ivf.js";
2
+
3
+ /** External ids are integers only. */
4
+ export type ExternalId = number;
5
+
6
+ export interface DatabaseOptions {
7
+ /** Path to the SQLite database file (use ":memory:" for a transient store). */
8
+ dbPath: string;
9
+ /** Vector dimensionality. Required for a new database; ignored when reopening. */
10
+ dim?: number;
11
+ /** Query breadth: clusters probed per query = ceil(efSearch / 4). Default
12
+ * 64 (16 probes). Tunable at runtime and at reopen. */
13
+ efSearch?: number;
14
+ /** Bits used to scalar-quantise the query for the RaBitQ estimator.
15
+ * Query-side only (stored codes are independent of it), so it is tunable
16
+ * at reopen, like efSearch. Default 8. */
17
+ queryBits?: number;
18
+ /** Number of sign-flip + Hadamard rounds in the random rotation. Default 3. */
19
+ rotationRounds?: number;
20
+ /** Seed for the rotation. Default fixed. */
21
+ seed?: number;
22
+ /** Optional centroid the vectors are centered by before quantisation. */
23
+ centroid?: ArrayLike<number>;
24
+ /** SQLite page-cache budget in MiB. Purely a latency knob: correctness and
25
+ * the per-operation storage-read count are identical with it at 0. */
26
+ cacheSizeMb?: number;
27
+ }
28
+
29
+ export interface QueryResult {
30
+ id: ExternalId;
31
+ /** Estimated cosine distance (1 - cosine). */
32
+ distance: number;
33
+ }
34
+
35
+ export interface StorageStats {
36
+ float32BytesPerVector: number;
37
+ codeBytesPerVector: number;
38
+ bytesPerVector: number;
39
+ compressionRatio: number;
40
+ }
41
+
42
+ /**
43
+ * A persistent vector database: an adaptive PARTITIONED (IVF) index over
44
+ * 1-bit RaBitQ codes (cosine), stored entirely in SQLite at `dbPath`. See
45
+ * ivf.ts for the index design. The original float vectors are never
46
+ * retained — only the sign codes — so a 1024-d vector costs 128 bytes.
47
+ *
48
+ * Inserting is route-and-append: cost is essentially FLAT in collection size
49
+ * (one RAM scan of the pivot table + one chunk append — no per-insert graph
50
+ * walk). Query cost is bounded by nprobe × cluster size — constant in N
51
+ * once the collection is past its first splits.
52
+ */
53
+ export class VectorDatabase {
54
+ private readonly index: IvfIndex;
55
+
56
+ constructor(options: DatabaseOptions) {
57
+ if (
58
+ !options || typeof options.dbPath !== "string" ||
59
+ options.dbPath.length === 0
60
+ ) {
61
+ throw new Error("DatabaseOptions.dbPath (string) is required");
62
+ }
63
+ this.index = new IvfIndex(options.dbPath, options);
64
+ }
65
+
66
+ get dim(): number {
67
+ return this.index.dim;
68
+ }
69
+
70
+ /** Number of live (non-deleted) vectors. */
71
+ get size(): number {
72
+ return this.index.size;
73
+ }
74
+
75
+ /** Physical slot count including tombstones from deletes/updates. */
76
+ get physicalSize(): number {
77
+ return this.index.physicalSize;
78
+ }
79
+
80
+ /** Number of clusters (partitions) currently in the index. */
81
+ get clusterCount(): number {
82
+ return this.index.clusterCount;
83
+ }
84
+
85
+ get efSearch(): number {
86
+ return this.index.efSearch;
87
+ }
88
+ set efSearch(value: number) {
89
+ this.index.efSearch = value;
90
+ }
91
+
92
+ /** Distance computations performed during the most recent query. */
93
+ get lastQueryDistanceComputations(): number {
94
+ return this.index.lastQueryDistComps;
95
+ }
96
+
97
+ /** Storage row reads issued by the most recent query — the honest,
98
+ * cache-independent scalability metric. */
99
+ get lastQueryStorageReads(): number {
100
+ return this.index.lastQueryStorageReads;
101
+ }
102
+
103
+ get storage(): StorageStats {
104
+ const f32 = this.dim * 4;
105
+ const bpv = this.index.bytesPerVector;
106
+ return {
107
+ float32BytesPerVector: f32,
108
+ codeBytesPerVector: bpv,
109
+ bytesPerVector: bpv,
110
+ compressionRatio: f32 / bpv,
111
+ };
112
+ }
113
+
114
+ private checkId(id: ExternalId): number {
115
+ // 32-bit signed range: chunk blobs store exts as int32 — a wider id
116
+ // would silently truncate, so it is rejected at the door instead.
117
+ if (!Number.isInteger(id) || id > 0x7fffffff || id < -0x80000000) {
118
+ throw new Error(`External id must be a 32-bit integer, got ${id}`);
119
+ }
120
+ return id;
121
+ }
122
+
123
+ /** Convert a value to code bytes, selecting by length: `codeWords`
124
+ * elements → an existing 1-bit code; otherwise a raw `dim`-vector. */
125
+ private toCodeBytes(value: ArrayLike<number>): Uint8Array {
126
+ if (value.length === this.index.codeWords) {
127
+ return this.index.codeToBytes(value);
128
+ }
129
+ if (value.length !== this.dim) {
130
+ throw new Error(
131
+ `Vector dimension mismatch: expected ${this.dim}, got ${value.length}`,
132
+ );
133
+ }
134
+ return this.index.encodeToBytes(value);
135
+ }
136
+
137
+ has(id: ExternalId): boolean {
138
+ return this.index.has(this.checkId(id));
139
+ }
140
+
141
+ /** Stream every live external id (bounded memory). */
142
+ *keys(): IterableIterator<ExternalId> {
143
+ yield* this.index.keys();
144
+ }
145
+
146
+ /** Stream live entries whose INTERNAL id is > `after` — a durable
147
+ * incremental watermark (internal ids are monotone at insert and preserved
148
+ * by update and compact). */
149
+ *keysSince(
150
+ after: number,
151
+ ): IterableIterator<{ ext: ExternalId; internal: number }> {
152
+ yield* this.index.keysSince(after);
153
+ }
154
+
155
+ /** Read the stored 1-bit code for an id (a copy as a Uint32Array), or null. */
156
+ get(id: ExternalId): Uint32Array | null {
157
+ const bytes = this.index.codeOf(this.checkId(id));
158
+ return bytes ? this.index.bytesToCode(bytes) : null;
159
+ }
160
+
161
+ insert(id: ExternalId, value: ArrayLike<number>): void {
162
+ this.index.begin();
163
+ try {
164
+ this.index.insert(this.checkId(id), this.toCodeBytes(value));
165
+ this.index.commit();
166
+ } catch (e) {
167
+ this.index.rollback();
168
+ throw e;
169
+ }
170
+ }
171
+
172
+ update(id: ExternalId, value: ArrayLike<number>): void {
173
+ this.index.begin();
174
+ try {
175
+ this.index.update(this.checkId(id), this.toCodeBytes(value));
176
+ this.index.commit();
177
+ } catch (e) {
178
+ this.index.rollback();
179
+ throw e;
180
+ }
181
+ }
182
+
183
+ upsert(id: ExternalId, value: ArrayLike<number>): void {
184
+ this.upsertMany([{ id, vector: value }]);
185
+ }
186
+
187
+ /**
188
+ * Upsert many vectors under ONE transaction — one WAL commit for the whole
189
+ * batch instead of one per vector, with chunk appends to the same cluster
190
+ * coalesced in the index's write-back buffer.
191
+ */
192
+ upsertMany(
193
+ entries: Array<{ id: ExternalId; vector: ArrayLike<number> }>,
194
+ ): void {
195
+ if (entries.length === 0) return;
196
+ this.index.begin();
197
+ try {
198
+ for (const e of entries) {
199
+ // One shared vmap probe decides insert vs update — the per-row point
200
+ // query is the dominant bulk-load cost, so it is never duplicated.
201
+ this.index.upsert(this.checkId(e.id), this.toCodeBytes(e.vector));
202
+ }
203
+ this.index.commit();
204
+ } catch (e) {
205
+ this.index.rollback();
206
+ throw e;
207
+ }
208
+ }
209
+
210
+ /** Delete many ids under ONE transaction. Absent ids are skipped.
211
+ * Returns the number of vectors actually removed. */
212
+ deleteMany(ids: ExternalId[]): number {
213
+ if (ids.length === 0) return 0;
214
+ let removed = 0;
215
+ this.index.begin();
216
+ try {
217
+ for (const id of ids) {
218
+ if (this.index.remove(this.checkId(id))) removed++;
219
+ }
220
+ this.index.commit();
221
+ } catch (e) {
222
+ this.index.rollback();
223
+ throw e;
224
+ }
225
+ return removed;
226
+ }
227
+
228
+ /** Delete the vector bound to an id. Returns false if absent. */
229
+ delete(id: ExternalId): boolean {
230
+ return this.deleteMany([id]) === 1;
231
+ }
232
+
233
+ /** Heat the SQLite page cache with sequential scans (latency only). */
234
+ warmCache(): number {
235
+ return this.index.warmCache();
236
+ }
237
+
238
+ /** Reclaim tombstoned slots by rewriting the clusters that carry any, then
239
+ * VACUUM. Internal ids and cluster assignment are preserved. */
240
+ compact(): void {
241
+ this.index.compact();
242
+ }
243
+
244
+ /**
245
+ * k-NN search. The argument's length selects the mode:
246
+ * - `dim` elements → a raw vector (accurate estimator)
247
+ * - `codeWords` elements → a 1-bit code, by sign-bit Hamming
248
+ */
249
+ query(
250
+ query: ArrayLike<number>,
251
+ k = 10,
252
+ opts?: { ef?: number },
253
+ ): QueryResult[] {
254
+ let hits: IvfHit[];
255
+ if (query.length === this.index.codeWords) {
256
+ hits = this.index.searchKnnByCode(
257
+ this.index.codeToBytes(query),
258
+ k,
259
+ opts?.ef,
260
+ );
261
+ } else {
262
+ if (query.length !== this.dim) {
263
+ throw new Error(
264
+ `Vector dimension mismatch: expected ${this.dim}, got ${query.length}`,
265
+ );
266
+ }
267
+ hits = this.index.searchKnn(query, k, opts?.ef);
268
+ }
269
+ return hits;
270
+ }
271
+
272
+ /** Close the underlying database. The instance must not be used afterwards. */
273
+ close(): void {
274
+ this.index.close();
275
+ }
276
+ }
@@ -5,11 +5,8 @@ export type {
5
5
  QueryResult,
6
6
  StorageStats,
7
7
  } from "./database.js";
8
- export { HnswIndex } from "./hnsw.js";
9
- export type { HnswParams, KnnHit } from "./hnsw.js";
8
+ export { IvfIndex } from "./ivf.js";
9
+ export type { IvfConfig, IvfHit } from "./ivf.js";
10
10
  export { RaBitQuantizer } from "./rabitq.js";
11
11
  export type { QueryContext, RaBitQOptions } from "./rabitq.js";
12
- export { Store } from "./store.js";
13
- export type { GlobalState, NodeRec, StoreConfig } from "./store.js";
14
- export { Heap } from "./heap.js";
15
12
  export { Prng } from "./prng.js";