@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
package/dist/src/vec.d.ts CHANGED
@@ -14,21 +14,17 @@ export declare function normalize(v: Vec): Vec;
14
14
  export declare function cosine(a: Vec, b: Vec): number;
15
15
  /** Set vector epsilon thresholds. Called once by Mind at construction. */
16
16
  export declare function setVecConfig(cfg: {
17
- normalizeEpsilon?: number;
18
- cosineEpsilon?: number;
17
+ normalizeEpsilon?: number;
18
+ cosineEpsilon?: number;
19
19
  }): void;
20
20
  export interface Permutation {
21
- fwd: Uint32Array;
22
- inv: Uint32Array;
21
+ fwd: Uint32Array;
22
+ inv: Uint32Array;
23
23
  }
24
24
  /** The keyring: one independent permutation per seat.
25
25
  * Independent keys do not commute, so an address in a tree is the
26
26
  * path itself — "seat 2 inside seat 1" ≠ "seat 1 inside seat 2". */
27
- export declare function makeKeyring(
28
- D: number,
29
- seats: number,
30
- rand: () => number,
31
- ): Permutation[];
27
+ export declare function makeKeyring(D: number, seats: number, rand: () => number): Permutation[];
32
28
  /** Apply permutation: out[i] = v[table[i]]. */
33
29
  export declare function permute(v: Vec, table: Uint32Array): Vec;
34
30
  /** Permute into existing buffer — zero allocation. */
package/dist/src/vec.js CHANGED
@@ -4,49 +4,44 @@
4
4
  // No weights. No gradients.
5
5
  /** Deterministic PRNG (32-bit mixer). */
6
6
  export function rng(seed) {
7
- let s = seed >>> 0;
8
- return () => {
9
- s = (s + 0x6d2b79f5) >>> 0;
10
- let t = s;
11
- t = Math.imul(t ^ (t >>> 15), t | 1);
12
- t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
13
- return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
14
- };
7
+ let s = seed >>> 0;
8
+ return () => {
9
+ s = (s + 0x6d2b79f5) >>> 0;
10
+ let t = s;
11
+ t = Math.imul(t ^ (t >>> 15), t | 1);
12
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
13
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
14
+ };
15
15
  }
16
16
  /** Standard normal sample (Box–Muller). */
17
17
  function gaussian(rand) {
18
- let u = 0, v = 0;
19
- while (u === 0) {
20
- u = rand();
21
- }
22
- while (v === 0) {
23
- v = rand();
24
- }
25
- return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
18
+ let u = 0, v = 0;
19
+ while (u === 0)
20
+ u = rand();
21
+ while (v === 0)
22
+ v = rand();
23
+ return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
26
24
  }
27
25
  export const zeros = (D) => new Float32Array(D);
28
26
  export const copy = (v) => new Float32Array(v);
29
27
  /** Random point on the unit sphere. */
30
28
  export function randomUnit(D, rand) {
31
- const v = zeros(D);
32
- for (let i = 0; i < D; i++) {
33
- v[i] = gaussian(rand);
34
- }
35
- return normalize(v);
29
+ const v = zeros(D);
30
+ for (let i = 0; i < D; i++)
31
+ v[i] = gaussian(rand);
32
+ return normalize(v);
36
33
  }
37
34
  /** target += src * scale (in place). */
38
35
  export function addInto(target, src, scale = 1) {
39
- for (let i = 0; i < target.length; i++) {
40
- target[i] += src[i] * scale;
41
- }
42
- return target;
36
+ for (let i = 0; i < target.length; i++)
37
+ target[i] += src[i] * scale;
38
+ return target;
43
39
  }
44
40
  export function dot(a, b) {
45
- let s = 0;
46
- for (let i = 0; i < a.length; i++) {
47
- s += a[i] * b[i];
48
- }
49
- return s;
41
+ let s = 0;
42
+ for (let i = 0; i < a.length; i++)
43
+ s += a[i] * b[i];
44
+ return s;
50
45
  }
51
46
  const norm = (v) => Math.sqrt(dot(v, v));
52
47
  // Epsilon thresholds (settable once via setVecConfig).
@@ -54,68 +49,61 @@ let _normalizeEpsilon = 1e-12;
54
49
  let _cosineEpsilon = 1e-12;
55
50
  /** In-place normalization. */
56
51
  export function normalize(v) {
57
- const n = norm(v);
58
- if (n > _normalizeEpsilon) {
59
- for (let i = 0; i < v.length; i++) {
60
- v[i] /= n;
52
+ const n = norm(v);
53
+ if (n > _normalizeEpsilon) {
54
+ for (let i = 0; i < v.length; i++)
55
+ v[i] /= n;
61
56
  }
62
- }
63
- return v;
57
+ return v;
64
58
  }
65
59
  /** Resonance: 1 = same, 0 = unrelated. */
66
60
  export function cosine(a, b) {
67
- const na = norm(a), nb = norm(b);
68
- return na > _cosineEpsilon && nb > _cosineEpsilon ? dot(a, b) / (na * nb) : 0;
61
+ const na = norm(a), nb = norm(b);
62
+ return na > _cosineEpsilon && nb > _cosineEpsilon ? dot(a, b) / (na * nb) : 0;
69
63
  }
70
64
  /** Set vector epsilon thresholds. Called once by Mind at construction. */
71
65
  export function setVecConfig(cfg) {
72
- if (cfg.normalizeEpsilon !== undefined) {
73
- _normalizeEpsilon = cfg.normalizeEpsilon;
74
- }
75
- if (cfg.cosineEpsilon !== undefined) {
76
- _cosineEpsilon = cfg.cosineEpsilon;
77
- }
66
+ if (cfg.normalizeEpsilon !== undefined) {
67
+ _normalizeEpsilon = cfg.normalizeEpsilon;
68
+ }
69
+ if (cfg.cosineEpsilon !== undefined)
70
+ _cosineEpsilon = cfg.cosineEpsilon;
78
71
  }
79
72
  /** One fixed random permutation (Fisher–Yates). */
80
73
  function makePermutation(D, rand) {
81
- const fwd = new Uint32Array(D);
82
- for (let i = 0; i < D; i++) {
83
- fwd[i] = i;
84
- }
85
- for (let i = D - 1; i > 0; i--) {
86
- const j = Math.floor(rand() * (i + 1));
87
- const t = fwd[i];
88
- fwd[i] = fwd[j];
89
- fwd[j] = t;
90
- }
91
- const inv = new Uint32Array(D);
92
- for (let i = 0; i < D; i++) {
93
- inv[fwd[i]] = i;
94
- }
95
- return { fwd, inv };
74
+ const fwd = new Uint32Array(D);
75
+ for (let i = 0; i < D; i++)
76
+ fwd[i] = i;
77
+ for (let i = D - 1; i > 0; i--) {
78
+ const j = Math.floor(rand() * (i + 1));
79
+ const t = fwd[i];
80
+ fwd[i] = fwd[j];
81
+ fwd[j] = t;
82
+ }
83
+ const inv = new Uint32Array(D);
84
+ for (let i = 0; i < D; i++)
85
+ inv[fwd[i]] = i;
86
+ return { fwd, inv };
96
87
  }
97
88
  /** The keyring: one independent permutation per seat.
98
89
  * Independent keys do not commute, so an address in a tree is the
99
90
  * path itself — "seat 2 inside seat 1" ≠ "seat 1 inside seat 2". */
100
91
  export function makeKeyring(D, seats, rand) {
101
- const ring = [];
102
- for (let s = 0; s < seats; s++) {
103
- ring.push(makePermutation(D, rand));
104
- }
105
- return ring;
92
+ const ring = [];
93
+ for (let s = 0; s < seats; s++)
94
+ ring.push(makePermutation(D, rand));
95
+ return ring;
106
96
  }
107
97
  /** Apply permutation: out[i] = v[table[i]]. */
108
98
  export function permute(v, table) {
109
- const out = new Float32Array(v.length);
110
- for (let i = 0; i < v.length; i++) {
111
- out[i] = v[table[i]];
112
- }
113
- return out;
99
+ const out = new Float32Array(v.length);
100
+ for (let i = 0; i < v.length; i++)
101
+ out[i] = v[table[i]];
102
+ return out;
114
103
  }
115
104
  /** Permute into existing buffer — zero allocation. */
116
105
  export function permuteInto(out, v, table) {
117
- for (let i = 0; i < v.length; i++) {
118
- out[i] = v[table[i]];
119
- }
120
- return out;
106
+ for (let i = 0; i < v.length; i++)
107
+ out[i] = v[table[i]];
108
+ return out;
121
109
  }
@@ -1610,20 +1610,23 @@ async function saveProgress(store: Store, p: SavedProgress): Promise<void> {
1610
1610
  // ═══════════════════════════════════════════════════════════════════════
1611
1611
 
1612
1612
  async function main(): Promise<void> {
1613
- // The vector indices' single memory knob (MiB) — sizes both the SQLite page
1614
- // cache and the decoded-code LRU of each index. Training is HNSW-insert
1615
- // bound at scale (profiled at >80% of ingest wall-clock on a 5.5M-vector
1616
- // store, nearly all of it cache-missing code reads), and inserts walk the
1617
- // graph, so a cache that covers the indexed codes cuts wall-clock directly
1618
- // (measured ~1. at 1 GB vs 64 MB on a 2M-node store). 1 GB caches ~5.8M
1619
- // D=1024 codes the whole content index of a 350 MB-corpus store — and is
1620
- // the default; override with VECTOR_CACHE_MB (e.g. 256 on a small-RAM
1621
- // machine, 64 for the library default).
1622
- const VECTOR_CACHE_MB = Math.max(0, Number(env("VECTOR_CACHE_MB", "1024")));
1613
+ // The vector indices' memory knob (MiB) — each index's SQLite page cache.
1614
+ // The IVF index routes inserts through a RAM-resident pivot table and
1615
+ // appends to chunk blobs, so this cache mostly serves query-time cluster
1616
+ // scans; 256 MiB comfortably covers the probed working set of a trained
1617
+ // store. Override with VECTOR_CACHE_MB (64 is the library default).
1618
+ const VECTOR_CACHE_MB = Math.max(0, Number(env("VECTOR_CACHE_MB", "256")));
1619
+ // Page cache for the MAIN DAG database (node/kid/edge/contain tables).
1620
+ // Training issues millions of content-addressed point probes per session
1621
+ // against a GB-scale file; the library default (64 MiB) is sized for a
1622
+ // small machine — a training box affords more. Override with
1623
+ // SQLITE_CACHE_MB.
1624
+ const SQLITE_CACHE_MB = Math.max(0, Number(env("SQLITE_CACHE_MB", "256")));
1623
1625
  const store = new SQliteStore({
1624
1626
  path: DB_PATH,
1625
1627
  D,
1626
1628
  vectorCacheMb: VECTOR_CACHE_MB,
1629
+ sqliteCacheMb: SQLITE_CACHE_MB,
1627
1630
  });
1628
1631
 
1629
1632
  // The store IS the model: memories, progress, and metadata all persist in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hviana/sema",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Sema: a non-parametric, instance-based reasoning system.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
package/src/alu/README.md CHANGED
@@ -15,7 +15,7 @@ search as authoritative axioms (at `STEP` cost, like a learned edge).
15
15
 
16
16
  It has no dependency on the rest of the codebase except the pure byte helpers in
17
17
  `../bytes.ts`, and is intended to be reused as a self-contained sublibrary in
18
- the spirit of `derive/` and `rabitq-hnsw/`.
18
+ the spirit of `derive/` and `rabitq-ivf/`.
19
19
 
20
20
  ## The thesis: one tiny kernel, everything else is a rewrite
21
21
 
@@ -1,7 +1,7 @@
1
1
  // alu — the ALU sub-library: a tiny irreducible kernel from which arithmetic,
2
2
  // logic, and numerical computation are all DERIVED.
3
3
  //
4
- // Self-contained in the spirit of derive/ and rabitq-hnsw/: it imports only the
4
+ // Self-contained in the spirit of derive/ and rabitq-ivf/: it imports only the
5
5
  // pure byte helpers (../../bytes.js) and nothing else from SEMA. The host
6
6
  // reaches meaning (operator synonymy, the symbolic inverse) through the injected
7
7
  // AluResonance, pre-resolved into a synchronous snapshot before the search runs.
package/src/config.ts CHANGED
@@ -39,18 +39,9 @@ export interface AlphabetConfig {
39
39
 
40
40
  export interface StoreConfig {
41
41
  minHaloMass: number;
42
- m: number;
43
- efConstruction: number;
44
- /** Construction budget for REACH-ONLY interior gists the ~90% of content
45
- * index inserts that exist so a partial query can resonate a sub-region
46
- * and climb, never as dedup targets. Deposit roots and halo-bearing
47
- * targets always build with the full `efConstruction`. A smaller budget
48
- * here is the one deliberate speed-for-quality trade in ingestion (an
49
- * interior's layer-0 wiring is built from a narrower candidate beam);
50
- * the recall suite (partial recall, multi-topic attention, counterfactual
51
- * anchoring) is the gate for its value. Set equal to `efConstruction`
52
- * to disable the trade. */
53
- efConstructionInterior: number;
42
+ /** Query breadth of the IVF vector indices: clusters probed per query =
43
+ * ceil(efSearch / 4). Inserts have no quality knob — the partitioned
44
+ * index routes and appends, so ingestion cost is flat by construction. */
54
45
  efSearch: number;
55
46
  /** Compact the in-memory vector indices after this many vectors are written.
56
47
  * Compaction rebuilds an index from its live codes to reclaim the slots left
@@ -58,7 +49,7 @@ export interface StoreConfig {
58
49
  * (not on a flush count that goes quiet during repeat-heavy training) keeps
59
50
  * the index dense and query cost bounded. */
60
51
  compactEveryNWrites: number;
61
- /** Over-fetch factor for HNSW queries (ANN recall cushion). */
52
+ /** Over-fetch factor for vector-index queries (ANN recall cushion). */
62
53
  overfetch: number;
63
54
  /** Combined buffered-write ceiling before a flush of both vector indices
64
55
  * (content + halo). Higher ⇒ fewer, larger flushes into the in-memory
@@ -91,14 +82,20 @@ export interface StoreConfig {
91
82
  * round-trips through the quantizer. An eviction or a reopen reads the
92
83
  * 2-bit row — the fidelity every cross-session consumer already gets. */
93
84
  haloCacheBytes: number;
94
- /** Size, in MiB, of each `rabitq-hnsw` `VectorDatabase`'s memory budget
95
- * (forwarded as its `cacheSizeMb`). It is the index's SINGLE memory knob: it
96
- * sizes both SQLite's page cache and the derived immutable-code LRU. A PURE
97
- * latency optimisation the index reads codes from SQLite on demand, so its
98
- * correctness and its asymptotic per-operation storage-read count are identical
99
- * with the budget at 0. Exposed so a scaling test can set it to 0 and measure
85
+ /** Size, in MiB, of each `rabitq-ivf` `VectorDatabase`'s memory budget
86
+ * (forwarded as its `cacheSizeMb` its SQLite page cache). A PURE latency
87
+ * optimisation the index reads chunk blobs from SQLite on demand, so its
88
+ * correctness and its per-operation storage-read count are identical with
89
+ * the budget at 0. Exposed so a scaling test can set it to 0 and measure
100
90
  * the honest, cache-independent cost. */
101
91
  vectorCacheMb: number;
92
+ /** Size, in MiB, of the MAIN DAG database's SQLite page cache. The node /
93
+ * kid / edge / contain tables serve millions of point probes per training
94
+ * session (content-addressed findLeaf/findBranch, parent probes, contain
95
+ * appends); SQLite's default cache (~2 MiB) thrashes once the DB outgrows
96
+ * it, so every probe pays a file read. A PURE latency knob — correctness
97
+ * and result identical at any value. */
98
+ sqliteCacheMb: number;
102
99
  /** Max entries in the skipped-interior LRU set. Interiors that
103
100
  * {@link Store.indexSubtree} has already visited (indexed or skipped) are
104
101
  * remembered here so subsequent calls prune their subtrees. Session-local
@@ -151,9 +148,6 @@ export const DEFAULT_CONFIG: MindConfig = {
151
148
  },
152
149
  store: {
153
150
  minHaloMass: 1,
154
- m: 8,
155
- efConstruction: 64,
156
- efConstructionInterior: 16,
157
151
  efSearch: 64,
158
152
  compactEveryNWrites: 50_000,
159
153
  overfetch: 4,
@@ -165,6 +159,7 @@ export const DEFAULT_CONFIG: MindConfig = {
165
159
  pendingGistBytes: 16_000_000,
166
160
  haloCacheBytes: 16_000_000,
167
161
  vectorCacheMb: 64,
162
+ sqliteCacheMb: 64,
168
163
  coveredIdsMax: 100_000,
169
164
  chainCacheBytes: 16_000_000,
170
165
  },
@@ -194,11 +189,6 @@ export function resolveConfig(opts: Partial<MindConfig> = {}): MindConfig {
194
189
  },
195
190
  store: {
196
191
  minHaloMass: opts.store?.minHaloMass ?? DEFAULT_CONFIG.store.minHaloMass,
197
- m: opts.store?.m ?? DEFAULT_CONFIG.store.m,
198
- efConstruction: opts.store?.efConstruction ??
199
- DEFAULT_CONFIG.store.efConstruction,
200
- efConstructionInterior: opts.store?.efConstructionInterior ??
201
- DEFAULT_CONFIG.store.efConstructionInterior,
202
192
  efSearch: opts.store?.efSearch ?? DEFAULT_CONFIG.store.efSearch,
203
193
  compactEveryNWrites: opts.store?.compactEveryNWrites ??
204
194
  DEFAULT_CONFIG.store.compactEveryNWrites,
@@ -218,6 +208,8 @@ export function resolveConfig(opts: Partial<MindConfig> = {}): MindConfig {
218
208
  DEFAULT_CONFIG.store.haloCacheBytes,
219
209
  vectorCacheMb: opts.store?.vectorCacheMb ??
220
210
  DEFAULT_CONFIG.store.vectorCacheMb,
211
+ sqliteCacheMb: opts.store?.sqliteCacheMb ??
212
+ DEFAULT_CONFIG.store.sqliteCacheMb,
221
213
  coveredIdsMax: opts.store?.coveredIdsMax ??
222
214
  DEFAULT_CONFIG.store.coveredIdsMax,
223
215
  chainCacheBytes: opts.store?.chainCacheBytes ??
package/src/index.ts CHANGED
@@ -13,26 +13,21 @@ export * from "./store-sqlite.js";
13
13
  export * from "./config.js";
14
14
  export * from "./extension.js";
15
15
  export * from "./ingest-cache.js";
16
- // rabitq-hnsw is re-exported selectively: its `Store`, `NodeRec`, and
17
- // `StoreConfig` are internal names that collide with sema's own top-level
18
- // `store.js` / `config.js` exports, and sema code that needs the rabitq ones
19
- // imports them from the subpath directly. Re-export the public vector-DB
20
- // surface under the sema root, omitting the three colliding names.
16
+ // rabitq-ivf: the partitioned (IVF) vector index over 1-bit RaBitQ codes.
21
17
  export {
22
- Heap,
23
- HnswIndex,
18
+ IvfIndex,
24
19
  Prng,
25
20
  RaBitQuantizer,
26
21
  VectorDatabase,
27
- } from "./rabitq-hnsw/src/index.js";
22
+ } from "./rabitq-ivf/src/index.js";
28
23
  export type {
29
24
  DatabaseOptions,
30
25
  ExternalId,
31
- HnswParams,
32
- KnnHit,
26
+ IvfConfig,
27
+ IvfHit,
33
28
  QueryContext,
34
29
  QueryResult,
35
30
  RaBitQOptions,
36
31
  StorageStats,
37
- } from "./rabitq-hnsw/src/index.js";
32
+ } from "./rabitq-ivf/src/index.js";
38
33
  export * from "./derive/src/index.js";
@@ -0,0 +1,56 @@
1
+ # rabitq-ivf
2
+
3
+ A small TypeScript library for **approximate nearest-neighbour search** using an
4
+ **adaptive partitioned (IVF) index** over **1-bit RaBitQ codes**, with
5
+ **cosine** distance. The entire index lives in **SQLite** — the durable copy of
6
+ every code is on disk, resident memory is bounded (the pivot table plus a capped
7
+ page cache, never the whole collection), and the index survives process
8
+ restarts.
9
+
10
+ The vectors are stored **only** as 1-bit RaBitQ codes — the original vectors are
11
+ never kept — so the index is both fast _and_ tiny on disk:
12
+
13
+ ```
14
+ a 1024-d vector:
15
+ Float32 : 1024 × 4 bytes = 4096 bytes
16
+ RaBitQ 1-bit (code) : 1024 × 1 bit = 128 bytes (32× smaller)
17
+ ```
18
+
19
+ The code _is_ the entire per-vector representation. `get` returns the bare code,
20
+ and `insert`, `update`, and `query` all accept either a raw vector or a code —
21
+ detected by length.
22
+
23
+ ## The index
24
+
25
+ - **Clusters, not a graph.** The collection is partitioned into clusters, each
26
+ with a binary pivot code and its member codes packed in fixed-size chunk
27
+ blobs.
28
+ - **Insert = route + append.** Find the nearest pivot (one linear Hamming scan
29
+ of the RAM-resident pivot table) and append to that cluster's tail chunk. No
30
+ beam search, no neighbour rewiring — per-insert cost is essentially flat in
31
+ collection size.
32
+ - **Query = probe + scan.** Rank all pivots with the accurate RaBitQ estimator,
33
+ scan the `ceil(efSearch/4)` nearest clusters with the same estimator, keep the
34
+ top k. Per-query storage reads are bounded by nprobe × chunks-per-cluster —
35
+ constant once the collection has split.
36
+ - **Adaptive, deterministic splits.** A cluster reaching 4096 entries is
37
+ median-split on the margin between two farthest-point seeds (two exact halves,
38
+ cascade-proof), and both halves get fresh majority-bit pivots. There is no
39
+ RNG: the index is a pure function of the insertion sequence.
40
+ - **Same durability discipline as the rest of Sema**: WAL, batched caller-owned
41
+ transactions (`upsertMany`), 1 KiB pages, 64 MiB WAL autocheckpoint.
42
+
43
+ ## Usage
44
+
45
+ ```ts
46
+ import { VectorDatabase } from "./src/index.js";
47
+
48
+ const db = new VectorDatabase({ dbPath: "vectors.db", dim: 1024 });
49
+ db.upsertMany([{ id: 1, vector: v1 }, { id: 2, vector: v2 }]);
50
+ const hits = db.query(q, 10); // [{ id, distance }]
51
+ db.close();
52
+ ```
53
+
54
+ `lastQueryStorageReads` reports the cache-independent storage-read count of the
55
+ most recent query — the honest scalability witness the test suite asserts on
56
+ (`test/35-ivf.test.mjs` at the repo root).