@hviana/sema 0.1.3 → 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.
Files changed (123) hide show
  1. package/dist/example/demo.js +25 -25
  2. package/dist/example/train_base.d.ts +21 -19
  3. package/dist/example/train_base.js +1952 -1684
  4. package/dist/src/alphabet.d.ts +3 -3
  5. package/dist/src/alphabet.js +27 -24
  6. package/dist/src/alu/src/alu.d.ts +194 -169
  7. package/dist/src/alu/src/alu.js +414 -374
  8. package/dist/src/alu/src/expr.d.ts +43 -36
  9. package/dist/src/alu/src/expr.js +278 -252
  10. package/dist/src/alu/src/index.d.ts +98 -9
  11. package/dist/src/alu/src/index.js +63 -9
  12. package/dist/src/alu/src/kernel-arith.d.ts +5 -1
  13. package/dist/src/alu/src/kernel-arith.js +285 -195
  14. package/dist/src/alu/src/kernel-bits.d.ts +5 -1
  15. package/dist/src/alu/src/kernel-bits.js +115 -81
  16. package/dist/src/alu/src/kernel-logic.js +63 -33
  17. package/dist/src/alu/src/kernel-nd.js +195 -155
  18. package/dist/src/alu/src/kernel-numeric.d.ts +80 -16
  19. package/dist/src/alu/src/kernel-numeric.js +331 -259
  20. package/dist/src/alu/src/operation.d.ts +132 -112
  21. package/dist/src/alu/src/operation.js +163 -149
  22. package/dist/src/alu/src/parser.d.ts +175 -168
  23. package/dist/src/alu/src/parser.js +421 -382
  24. package/dist/src/alu/src/resonance.d.ts +25 -16
  25. package/dist/src/alu/src/resonance.js +56 -49
  26. package/dist/src/alu/src/text.d.ts +7 -3
  27. package/dist/src/alu/src/text.js +37 -31
  28. package/dist/src/alu/src/value.d.ts +14 -14
  29. package/dist/src/alu/src/value.js +160 -150
  30. package/dist/src/alu/test/alu.test.js +656 -543
  31. package/dist/src/bytes.d.ts +5 -1
  32. package/dist/src/bytes.js +40 -33
  33. package/dist/src/config.d.ts +102 -102
  34. package/dist/src/config.js +83 -83
  35. package/dist/src/derive/src/deduction.d.ts +62 -60
  36. package/dist/src/derive/src/deduction.js +109 -103
  37. package/dist/src/derive/src/index.d.ts +7 -1
  38. package/dist/src/derive/src/priority-queue.d.ts +8 -8
  39. package/dist/src/derive/src/priority-queue.js +61 -57
  40. package/dist/src/derive/src/rewrite.d.ts +18 -15
  41. package/dist/src/derive/src/rewrite.js +67 -59
  42. package/dist/src/derive/src/trie.d.ts +56 -56
  43. package/dist/src/derive/src/trie.js +179 -175
  44. package/dist/src/derive/test/derive.test.js +100 -96
  45. package/dist/src/extension.d.ts +16 -13
  46. package/dist/src/geometry.d.ts +42 -17
  47. package/dist/src/geometry.js +266 -235
  48. package/dist/src/index.d.ts +17 -2
  49. package/dist/src/index.js +7 -1
  50. package/dist/src/ingest-cache.d.ts +30 -25
  51. package/dist/src/ingest-cache.js +127 -108
  52. package/dist/src/mind/articulation.d.ts +5 -1
  53. package/dist/src/mind/articulation.js +112 -77
  54. package/dist/src/mind/attention.d.ts +108 -40
  55. package/dist/src/mind/attention.js +871 -749
  56. package/dist/src/mind/canonical.d.ts +19 -4
  57. package/dist/src/mind/canonical.js +31 -27
  58. package/dist/src/mind/graph-search.d.ts +225 -201
  59. package/dist/src/mind/graph-search.js +821 -742
  60. package/dist/src/mind/index.d.ts +10 -2
  61. package/dist/src/mind/junction.d.ts +58 -31
  62. package/dist/src/mind/junction.js +237 -172
  63. package/dist/src/mind/learning.d.ts +52 -16
  64. package/dist/src/mind/learning.js +165 -143
  65. package/dist/src/mind/match.d.ts +69 -20
  66. package/dist/src/mind/match.js +318 -259
  67. package/dist/src/mind/mechanisms/alu.js +16 -16
  68. package/dist/src/mind/mechanisms/cast.d.ts +13 -9
  69. package/dist/src/mind/mechanisms/cast.js +456 -363
  70. package/dist/src/mind/mechanisms/confluence.d.ts +12 -8
  71. package/dist/src/mind/mechanisms/confluence.js +183 -152
  72. package/dist/src/mind/mechanisms/cover.d.ts +8 -2
  73. package/dist/src/mind/mechanisms/cover.js +210 -148
  74. package/dist/src/mind/mechanisms/extraction.d.ts +34 -8
  75. package/dist/src/mind/mechanisms/extraction.js +288 -234
  76. package/dist/src/mind/mechanisms/recall.d.ts +10 -6
  77. package/dist/src/mind/mechanisms/recall.js +185 -126
  78. package/dist/src/mind/mind.d.ts +154 -129
  79. package/dist/src/mind/mind.js +288 -258
  80. package/dist/src/mind/pipeline-mechanism.d.ts +124 -112
  81. package/dist/src/mind/pipeline-mechanism.js +172 -161
  82. package/dist/src/mind/pipeline.d.ts +14 -4
  83. package/dist/src/mind/pipeline.js +189 -125
  84. package/dist/src/mind/primitives.d.ts +32 -8
  85. package/dist/src/mind/primitives.js +117 -99
  86. package/dist/src/mind/rationale.d.ts +98 -86
  87. package/dist/src/mind/rationale.js +121 -113
  88. package/dist/src/mind/reasoning.d.ts +13 -2
  89. package/dist/src/mind/reasoning.js +166 -129
  90. package/dist/src/mind/recognition.d.ts +4 -1
  91. package/dist/src/mind/recognition.js +208 -183
  92. package/dist/src/mind/resonance.d.ts +22 -5
  93. package/dist/src/mind/resonance.js +0 -0
  94. package/dist/src/mind/trace.d.ts +25 -6
  95. package/dist/src/mind/trace.js +58 -50
  96. package/dist/src/mind/traverse.d.ts +58 -16
  97. package/dist/src/mind/traverse.js +357 -304
  98. package/dist/src/mind/types.d.ts +127 -120
  99. package/dist/src/mind/types.js +69 -60
  100. package/dist/src/rabitq-hnsw/src/database.d.ts +179 -177
  101. package/dist/src/rabitq-hnsw/src/database.js +359 -342
  102. package/dist/src/rabitq-hnsw/src/heap.d.ts +12 -12
  103. package/dist/src/rabitq-hnsw/src/heap.js +80 -75
  104. package/dist/src/rabitq-hnsw/src/hnsw.d.ts +103 -103
  105. package/dist/src/rabitq-hnsw/src/hnsw.js +454 -428
  106. package/dist/src/rabitq-hnsw/src/index.d.ts +6 -1
  107. package/dist/src/rabitq-hnsw/src/prng.d.ts +10 -10
  108. package/dist/src/rabitq-hnsw/src/prng.js +27 -25
  109. package/dist/src/rabitq-hnsw/src/rabitq.d.ts +69 -69
  110. package/dist/src/rabitq-hnsw/src/rabitq.js +262 -246
  111. package/dist/src/rabitq-hnsw/src/store.d.ts +132 -132
  112. package/dist/src/rabitq-hnsw/src/store.js +843 -752
  113. package/dist/src/rabitq-hnsw/test/hnsw.test.js +1054 -805
  114. package/dist/src/sema.d.ts +35 -0
  115. package/dist/src/sema.js +77 -0
  116. package/dist/src/store-sqlite.d.ts +166 -144
  117. package/dist/src/store-sqlite.js +660 -578
  118. package/dist/src/store.d.ts +630 -593
  119. package/dist/src/store.js +1581 -1440
  120. package/dist/src/vec.d.ts +9 -5
  121. package/dist/src/vec.js +73 -61
  122. package/package.json +2 -2
  123. package/src/sema.ts +102 -0
@@ -1,14 +1,16 @@
1
1
  import { Prng } from "./prng.js";
2
2
  function nextPow2(n) {
3
- let p = 1;
4
- while (p < n)
5
- p <<= 1;
6
- return p;
3
+ let p = 1;
4
+ while (p < n) {
5
+ p <<= 1;
6
+ }
7
+ return p;
7
8
  }
8
9
  /** Set-bit count for every byte value, for counting code bits during the byte scan. */
9
10
  const POPCOUNT8 = new Uint8Array(256);
10
- for (let i = 1; i < 256; i++)
11
- POPCOUNT8[i] = POPCOUNT8[i >> 1] + (i & 1);
11
+ for (let i = 1; i < 256; i++) {
12
+ POPCOUNT8[i] = POPCOUNT8[i >> 1] + (i & 1);
13
+ }
12
14
  /**
13
15
  * 1-bit RaBitQ quantizer (cosine) -- the ONLY representation of a vector kept by
14
16
  * the index. A D-dimensional vector collapses to ceil(D/32) 32-bit words of sign
@@ -30,254 +32,268 @@ for (let i = 1; i < 256; i++)
30
32
  * Theoretical Error Bound for Approximate Nearest Neighbor Search", SIGMOD 2024.
31
33
  */
32
34
  export class RaBitQuantizer {
33
- dim;
34
- paddedDim;
35
- codeWords;
36
- queryBits;
37
- rounds;
38
- seed;
39
- centroid;
40
- sqrtD;
41
- maxQInt;
42
- signs;
43
- scratch;
44
- /** Fixed cosine inner-product scale (= 1 / E[L1] of a rotated unit vector). */
45
- cosFactor;
46
- // byte-LUT machinery for the query/code estimator
47
- nbytes;
48
- /** coordinate index sitting at byte position p, bit k -> bitCoord[p*8 + k]. */
49
- bitCoord;
50
- /** true when the largest possible LUT entry overflows a Uint8. */
51
- lutWide;
52
- constructor(dim, opts = {}) {
53
- this.dim = dim;
54
- this.paddedDim = nextPow2(dim);
55
- this.codeWords = Math.ceil(this.paddedDim / 32);
56
- this.queryBits = opts.queryBits ?? 8;
57
- this.rounds = opts.rounds ?? 3;
58
- this.seed = (opts.seed ?? 0x1234abcd) >>> 0;
59
- this.sqrtD = Math.sqrt(this.paddedDim);
60
- this.cosFactor = Math.sqrt(Math.PI / (2 * this.paddedDim));
61
- this.maxQInt = (1 << this.queryBits) - 1;
62
- this.centroid = new Float64Array(dim);
63
- if (opts.centroid) {
64
- for (let i = 0; i < dim; i++)
65
- this.centroid[i] = opts.centroid[i] ?? 0;
66
- }
67
- const prng = new Prng(this.seed);
68
- this.signs = [];
69
- for (let r = 0; r < this.rounds; r++) {
70
- const s = new Float64Array(this.paddedDim);
71
- for (let i = 0; i < this.paddedDim; i++) {
72
- s[i] = prng.next() < 0.5 ? -1 : 1;
73
- }
74
- this.signs.push(s);
75
- }
76
- this.scratch = new Float64Array(this.paddedDim);
77
- // Map (code-byte position, bit-in-byte) -> coordinate, honouring the host
78
- // byte order so the Uint8 view of the (Uint32) code buffer is interpreted
79
- // correctly on both little- and big-endian platforms.
80
- this.nbytes = this.paddedDim >>> 3;
81
- const littleEndian = new Uint8Array(new Uint32Array([1]).buffer)[0] === 1;
82
- this.bitCoord = new Int32Array(this.nbytes * 8);
83
- for (let p = 0; p < this.nbytes; p++) {
84
- const word = p >>> 2;
85
- const localByte = littleEndian ? p & 3 : 3 - (p & 3);
86
- const bitBase = word * 32 + localByte * 8;
87
- for (let k = 0; k < 8; k++)
88
- this.bitCoord[p * 8 + k] = bitBase + k;
89
- }
90
- this.lutWide = 8 * this.maxQInt > 255;
35
+ dim;
36
+ paddedDim;
37
+ codeWords;
38
+ queryBits;
39
+ rounds;
40
+ seed;
41
+ centroid;
42
+ sqrtD;
43
+ maxQInt;
44
+ signs;
45
+ scratch;
46
+ /** Fixed cosine inner-product scale (= 1 / E[L1] of a rotated unit vector). */
47
+ cosFactor;
48
+ // byte-LUT machinery for the query/code estimator
49
+ nbytes;
50
+ /** coordinate index sitting at byte position p, bit k -> bitCoord[p*8 + k]. */
51
+ bitCoord;
52
+ /** true when the largest possible LUT entry overflows a Uint8. */
53
+ lutWide;
54
+ constructor(dim, opts = {}) {
55
+ this.dim = dim;
56
+ this.paddedDim = nextPow2(dim);
57
+ this.codeWords = Math.ceil(this.paddedDim / 32);
58
+ this.queryBits = opts.queryBits ?? 8;
59
+ this.rounds = opts.rounds ?? 3;
60
+ this.seed = (opts.seed ?? 0x1234abcd) >>> 0;
61
+ this.sqrtD = Math.sqrt(this.paddedDim);
62
+ this.cosFactor = Math.sqrt(Math.PI / (2 * this.paddedDim));
63
+ this.maxQInt = (1 << this.queryBits) - 1;
64
+ this.centroid = new Float64Array(dim);
65
+ if (opts.centroid) {
66
+ for (let i = 0; i < dim; i++) {
67
+ this.centroid[i] = opts.centroid[i] ?? 0;
68
+ }
91
69
  }
92
- /** In-place fast Walsh-Hadamard transform; `a.length` must be a power of two. */
93
- fwht(a) {
94
- const n = a.length;
95
- for (let len = 1; len < n; len <<= 1) {
96
- const span = len << 1;
97
- for (let i = 0; i < n; i += span) {
98
- for (let j = i; j < i + len; j++) {
99
- const u = a[j];
100
- const v = a[j + len];
101
- a[j] = u + v;
102
- a[j + len] = u - v;
103
- }
104
- }
105
- }
70
+ const prng = new Prng(this.seed);
71
+ this.signs = [];
72
+ for (let r = 0; r < this.rounds; r++) {
73
+ const s = new Float64Array(this.paddedDim);
74
+ for (let i = 0; i < this.paddedDim; i++) {
75
+ s[i] = prng.next() < 0.5 ? -1 : 1;
76
+ }
77
+ this.signs.push(s);
106
78
  }
107
- /** Apply the orthogonal rotation in place (a.length === paddedDim). */
108
- rotate(a) {
109
- const n = this.paddedDim;
110
- const inv = 1 / this.sqrtD;
111
- for (let r = 0; r < this.rounds; r++) {
112
- const s = this.signs[r];
113
- for (let i = 0; i < n; i++)
114
- a[i] *= s[i];
115
- this.fwht(a);
116
- for (let i = 0; i < n; i++)
117
- a[i] *= inv;
118
- }
79
+ this.scratch = new Float64Array(this.paddedDim);
80
+ // Map (code-byte position, bit-in-byte) -> coordinate, honouring the host
81
+ // byte order so the Uint8 view of the (Uint32) code buffer is interpreted
82
+ // correctly on both little- and big-endian platforms.
83
+ this.nbytes = this.paddedDim >>> 3;
84
+ const littleEndian = new Uint8Array(new Uint32Array([1]).buffer)[0] === 1;
85
+ this.bitCoord = new Int32Array(this.nbytes * 8);
86
+ for (let p = 0; p < this.nbytes; p++) {
87
+ const word = p >>> 2;
88
+ const localByte = littleEndian ? p & 3 : 3 - (p & 3);
89
+ const bitBase = word * 32 + localByte * 8;
90
+ for (let k = 0; k < 8; k++) {
91
+ this.bitCoord[p * 8 + k] = bitBase + k;
92
+ }
119
93
  }
120
- /** Encode a raw vector into its 1-bit sign code (the whole representation). */
121
- encode(vec) {
122
- const dim = this.dim;
123
- const pd = this.paddedDim;
124
- const buf = this.scratch;
125
- let sq = 0;
126
- for (let i = 0; i < dim; i++) {
127
- const v = vec[i] - this.centroid[i];
128
- buf[i] = v;
129
- sq += v * v;
130
- }
131
- for (let i = dim; i < pd; i++)
132
- buf[i] = 0;
133
- const code = new Uint32Array(this.codeWords);
134
- if (sq === 0) {
135
- return code;
136
- }
137
- const invNorm = 1 / Math.sqrt(sq);
138
- for (let i = 0; i < dim; i++)
139
- buf[i] *= invNorm; // unit residual; padded dims stay 0
140
- this.rotate(buf);
141
- for (let i = 0; i < pd; i++) {
142
- if (buf[i] > 0)
143
- code[i >>> 5] |= 1 << (i & 31);
94
+ this.lutWide = 8 * this.maxQInt > 255;
95
+ }
96
+ /** In-place fast Walsh-Hadamard transform; `a.length` must be a power of two. */
97
+ fwht(a) {
98
+ const n = a.length;
99
+ for (let len = 1; len < n; len <<= 1) {
100
+ const span = len << 1;
101
+ for (let i = 0; i < n; i += span) {
102
+ for (let j = i; j < i + len; j++) {
103
+ const u = a[j];
104
+ const v = a[j + len];
105
+ a[j] = u + v;
106
+ a[j + len] = u - v;
144
107
  }
145
- return code;
108
+ }
146
109
  }
147
- /** Pre-process a full-precision query into the structure consumed by `estimate`. */
148
- prepareQuery(vec) {
149
- const dim = this.dim;
150
- const pd = this.paddedDim;
151
- const nb = this.nbytes;
152
- const buf = this.scratch;
153
- let sq = 0;
154
- for (let i = 0; i < dim; i++) {
155
- const v = vec[i] - this.centroid[i];
156
- buf[i] = v;
157
- sq += v * v;
158
- }
159
- for (let i = dim; i < pd; i++)
160
- buf[i] = 0;
161
- const qNorm = Math.sqrt(sq);
162
- const qlut = this.lutWide
163
- ? new Uint16Array(nb * 256)
164
- : new Uint8Array(nb * 256);
165
- if (qNorm === 0) {
166
- return { vmin: 0, delta: 0, sumQInt: 0, qlut, nbytes: nb, zero: true };
167
- }
168
- const invNorm = 1 / qNorm;
169
- for (let i = 0; i < dim; i++)
170
- buf[i] *= invNorm;
171
- this.rotate(buf);
172
- let vmin = Infinity;
173
- let vmax = -Infinity;
174
- for (let i = 0; i < pd; i++) {
175
- const x = buf[i];
176
- if (x < vmin)
177
- vmin = x;
178
- if (x > vmax)
179
- vmax = x;
180
- }
181
- const range = vmax - vmin;
182
- const delta = range > 0 ? range / this.maxQInt : 0;
183
- const invDelta = delta > 0 ? 1 / delta : 0;
184
- // Quantise each (rotated) query coordinate to queryBits bits.
185
- const qint = buf; // reuse: write the integer code back over the float buffer
186
- let sumQInt = 0;
187
- for (let i = 0; i < pd; i++) {
188
- let q = delta > 0 ? Math.round((buf[i] - vmin) * invDelta) : 0;
189
- if (q < 0)
190
- q = 0;
191
- else if (q > this.maxQInt)
192
- q = this.maxQInt;
193
- qint[i] = q;
194
- sumQInt += q;
195
- }
196
- // Build the byte LUT: qlut[p*256 + v] = sum of qint at coords whose bit is
197
- // set in v, grown incrementally as qlut[..(v with lowest set bit cleared)..]
198
- // plus the contribution of that lowest set bit.
199
- const bitCoord = this.bitCoord;
200
- for (let p = 0; p < nb; p++) {
201
- const base = p << 8;
202
- const cb = p << 3;
203
- for (let v = 1; v < 256; v++) {
204
- const low = v & -v;
205
- const k = 31 - Math.clz32(low);
206
- qlut[base + v] = qlut[base + (v & (v - 1))] + qint[bitCoord[cb + k]];
207
- }
208
- }
209
- return { vmin, delta, sumQInt, qlut, nbytes: nb, zero: false };
110
+ }
111
+ /** Apply the orthogonal rotation in place (a.length === paddedDim). */
112
+ rotate(a) {
113
+ const n = this.paddedDim;
114
+ const inv = 1 / this.sqrtD;
115
+ for (let r = 0; r < this.rounds; r++) {
116
+ const s = this.signs[r];
117
+ for (let i = 0; i < n; i++) {
118
+ a[i] *= s[i];
119
+ }
120
+ this.fwht(a);
121
+ for (let i = 0; i < n; i++) {
122
+ a[i] *= inv;
123
+ }
210
124
  }
211
- /**
212
- * Estimate the cosine distance (1 - cosine) between a stored code and a
213
- * full-precision query, reading the code's bytes against the query's byte LUT.
214
- * The code's set-bit count is tallied in the same byte scan, so nothing beyond
215
- * the code itself is needed.
216
- *
217
- * `codeBytes` is a Uint8 view of the packed code buffer and `byteOffset` is the
218
- * code's start byte (id * paddedDim/8).
219
- */
220
- estimate(codeBytes, byteOffset, q) {
221
- if (q.zero) {
222
- return 1;
223
- }
224
- const nb = q.nbytes;
225
- const lut = q.qlut;
226
- let dot = 0;
227
- let popcount = 0;
228
- for (let p = 0; p < nb; p++) {
229
- const b = codeBytes[byteOffset + p];
230
- dot += lut[(p << 8) + b];
231
- popcount += POPCOUNT8[b];
232
- }
233
- // A = sum_i sign_i * q_rot_i, recovered from the quantised query.
234
- const A = q.vmin * (2 * popcount - this.paddedDim) +
235
- q.delta * (2 * dot - q.sumQInt);
236
- return 1 - this.cosFactor * A;
125
+ }
126
+ /** Encode a raw vector into its 1-bit sign code (the whole representation). */
127
+ encode(vec) {
128
+ const dim = this.dim;
129
+ const pd = this.paddedDim;
130
+ const buf = this.scratch;
131
+ let sq = 0;
132
+ for (let i = 0; i < dim; i++) {
133
+ const v = vec[i] - this.centroid[i];
134
+ buf[i] = v;
135
+ sq += v * v;
237
136
  }
238
- /**
239
- * Cosine distance (1 - cosine, in [0, 2]) between two packed codes, computed
240
- * directly from the BLOB bytes via their sign-bit Hamming distance — no word
241
- * reinterpretation, so it is endianness-agnostic. Identical codes score 0.
242
- *
243
- * This is the distance used to build the graph (code vs code) and to answer a
244
- * query given an already-quantized code. It is coarser than `estimate`, where
245
- * one side is full precision.
246
- */
247
- codeDistanceBytes(a, b) {
248
- const nb = this.nbytes;
249
- let ham = 0;
250
- // 32-bit-word Hamming: this is the hot arithmetic of graph construction
251
- // (every candidate/prune comparison), so fold 4 bytes into one word and
252
- // popcount it ~4× fewer loop iterations than a byte-LUT scan, with no
253
- // allocation and no dependence on the buffers' alignment or endianness
254
- // (both sides are composed identically, so XOR is order-agnostic).
255
- let p = 0;
256
- for (const n4 = nb & ~3; p < n4; p += 4) {
257
- let x = (a[p] ^ b[p]) |
258
- ((a[p + 1] ^ b[p + 1]) << 8) |
259
- ((a[p + 2] ^ b[p + 2]) << 16) |
260
- ((a[p + 3] ^ b[p + 3]) << 24);
261
- x -= (x >>> 1) & 0x55555555;
262
- x = (x & 0x33333333) + ((x >>> 2) & 0x33333333);
263
- x = (x + (x >>> 4)) & 0x0f0f0f0f;
264
- ham += Math.imul(x, 0x01010101) >>> 24;
265
- }
266
- for (; p < nb; p++)
267
- ham += POPCOUNT8[a[p] ^ b[p]];
268
- return (2 * ham) / this.paddedDim;
137
+ for (let i = dim; i < pd; i++) {
138
+ buf[i] = 0;
139
+ }
140
+ const code = new Uint32Array(this.codeWords);
141
+ if (sq === 0) {
142
+ return code;
143
+ }
144
+ const invNorm = 1 / Math.sqrt(sq);
145
+ for (let i = 0; i < dim; i++) {
146
+ buf[i] *= invNorm; // unit residual; padded dims stay 0
147
+ }
148
+ this.rotate(buf);
149
+ for (let i = 0; i < pd; i++) {
150
+ if (buf[i] > 0) {
151
+ code[i >>> 5] |= 1 << (i & 31);
152
+ }
153
+ }
154
+ return code;
155
+ }
156
+ /** Pre-process a full-precision query into the structure consumed by `estimate`. */
157
+ prepareQuery(vec) {
158
+ const dim = this.dim;
159
+ const pd = this.paddedDim;
160
+ const nb = this.nbytes;
161
+ const buf = this.scratch;
162
+ let sq = 0;
163
+ for (let i = 0; i < dim; i++) {
164
+ const v = vec[i] - this.centroid[i];
165
+ buf[i] = v;
166
+ sq += v * v;
167
+ }
168
+ for (let i = dim; i < pd; i++) {
169
+ buf[i] = 0;
170
+ }
171
+ const qNorm = Math.sqrt(sq);
172
+ const qlut = this.lutWide
173
+ ? new Uint16Array(nb * 256)
174
+ : new Uint8Array(nb * 256);
175
+ if (qNorm === 0) {
176
+ return { vmin: 0, delta: 0, sumQInt: 0, qlut, nbytes: nb, zero: true };
177
+ }
178
+ const invNorm = 1 / qNorm;
179
+ for (let i = 0; i < dim; i++) {
180
+ buf[i] *= invNorm;
181
+ }
182
+ this.rotate(buf);
183
+ let vmin = Infinity;
184
+ let vmax = -Infinity;
185
+ for (let i = 0; i < pd; i++) {
186
+ const x = buf[i];
187
+ if (x < vmin) {
188
+ vmin = x;
189
+ }
190
+ if (x > vmax) {
191
+ vmax = x;
192
+ }
193
+ }
194
+ const range = vmax - vmin;
195
+ const delta = range > 0 ? range / this.maxQInt : 0;
196
+ const invDelta = delta > 0 ? 1 / delta : 0;
197
+ // Quantise each (rotated) query coordinate to queryBits bits.
198
+ const qint = buf; // reuse: write the integer code back over the float buffer
199
+ let sumQInt = 0;
200
+ for (let i = 0; i < pd; i++) {
201
+ let q = delta > 0 ? Math.round((buf[i] - vmin) * invDelta) : 0;
202
+ if (q < 0) {
203
+ q = 0;
204
+ } else if (q > this.maxQInt) {
205
+ q = this.maxQInt;
206
+ }
207
+ qint[i] = q;
208
+ sumQInt += q;
209
+ }
210
+ // Build the byte LUT: qlut[p*256 + v] = sum of qint at coords whose bit is
211
+ // set in v, grown incrementally as qlut[..(v with lowest set bit cleared)..]
212
+ // plus the contribution of that lowest set bit.
213
+ const bitCoord = this.bitCoord;
214
+ for (let p = 0; p < nb; p++) {
215
+ const base = p << 8;
216
+ const cb = p << 3;
217
+ for (let v = 1; v < 256; v++) {
218
+ const low = v & -v;
219
+ const k = 31 - Math.clz32(low);
220
+ qlut[base + v] = qlut[base + (v & (v - 1))] + qint[bitCoord[cb + k]];
221
+ }
222
+ }
223
+ return { vmin, delta, sumQInt, qlut, nbytes: nb, zero: false };
224
+ }
225
+ /**
226
+ * Estimate the cosine distance (1 - cosine) between a stored code and a
227
+ * full-precision query, reading the code's bytes against the query's byte LUT.
228
+ * The code's set-bit count is tallied in the same byte scan, so nothing beyond
229
+ * the code itself is needed.
230
+ *
231
+ * `codeBytes` is a Uint8 view of the packed code buffer and `byteOffset` is the
232
+ * code's start byte (id * paddedDim/8).
233
+ */
234
+ estimate(codeBytes, byteOffset, q) {
235
+ if (q.zero) {
236
+ return 1;
237
+ }
238
+ const nb = q.nbytes;
239
+ const lut = q.qlut;
240
+ let dot = 0;
241
+ let popcount = 0;
242
+ for (let p = 0; p < nb; p++) {
243
+ const b = codeBytes[byteOffset + p];
244
+ dot += lut[(p << 8) + b];
245
+ popcount += POPCOUNT8[b];
246
+ }
247
+ // A = sum_i sign_i * q_rot_i, recovered from the quantised query.
248
+ const A = q.vmin * (2 * popcount - this.paddedDim) +
249
+ q.delta * (2 * dot - q.sumQInt);
250
+ return 1 - this.cosFactor * A;
251
+ }
252
+ /**
253
+ * Cosine distance (1 - cosine, in [0, 2]) between two packed codes, computed
254
+ * directly from the BLOB bytes via their sign-bit Hamming distance — no word
255
+ * reinterpretation, so it is endianness-agnostic. Identical codes score 0.
256
+ *
257
+ * This is the distance used to build the graph (code vs code) and to answer a
258
+ * query given an already-quantized code. It is coarser than `estimate`, where
259
+ * one side is full precision.
260
+ */
261
+ codeDistanceBytes(a, b) {
262
+ const nb = this.nbytes;
263
+ let ham = 0;
264
+ // 32-bit-word Hamming: this is the hot arithmetic of graph construction
265
+ // (every candidate/prune comparison), so fold 4 bytes into one word and
266
+ // popcount it — ~4× fewer loop iterations than a byte-LUT scan, with no
267
+ // allocation and no dependence on the buffers' alignment or endianness
268
+ // (both sides are composed identically, so XOR is order-agnostic).
269
+ let p = 0;
270
+ for (const n4 = nb & ~3; p < n4; p += 4) {
271
+ let x = (a[p] ^ b[p]) |
272
+ ((a[p + 1] ^ b[p + 1]) << 8) |
273
+ ((a[p + 2] ^ b[p + 2]) << 16) |
274
+ ((a[p + 3] ^ b[p + 3]) << 24);
275
+ x -= (x >>> 1) & 0x55555555;
276
+ x = (x & 0x33333333) + ((x >>> 2) & 0x33333333);
277
+ x = (x + (x >>> 4)) & 0x0f0f0f0f;
278
+ ham += Math.imul(x, 0x01010101) >>> 24;
269
279
  }
270
- /** Pack a code (codeWords 32-bit words) into its little-endian BLOB bytes. */
271
- codeToBytes(code) {
272
- const u = new Uint32Array(this.codeWords);
273
- for (let i = 0; i < this.codeWords; i++)
274
- u[i] = code[i];
275
- return new Uint8Array(u.buffer, 0, u.byteLength);
280
+ for (; p < nb; p++) {
281
+ ham += POPCOUNT8[a[p] ^ b[p]];
276
282
  }
277
- /** Reinterpret a code BLOB as a copy of codeWords 32-bit words. */
278
- bytesToCode(bytes) {
279
- const u = new Uint32Array(this.codeWords);
280
- new Uint8Array(u.buffer).set(bytes);
281
- return u;
283
+ return (2 * ham) / this.paddedDim;
284
+ }
285
+ /** Pack a code (codeWords 32-bit words) into its little-endian BLOB bytes. */
286
+ codeToBytes(code) {
287
+ const u = new Uint32Array(this.codeWords);
288
+ for (let i = 0; i < this.codeWords; i++) {
289
+ u[i] = code[i];
282
290
  }
291
+ return new Uint8Array(u.buffer, 0, u.byteLength);
292
+ }
293
+ /** Reinterpret a code BLOB as a copy of codeWords 32-bit words. */
294
+ bytesToCode(bytes) {
295
+ const u = new Uint32Array(this.codeWords);
296
+ new Uint8Array(u.buffer).set(bytes);
297
+ return u;
298
+ }
283
299
  }