sweet-search 0.0.1 → 2.3.0
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/LICENSE +190 -0
- package/NOTICE +23 -0
- package/core/cli.js +51 -0
- package/core/config.js +27 -0
- package/core/embedding/embedding-cache.js +467 -0
- package/core/embedding/embedding-local-model.js +845 -0
- package/core/embedding/embedding-remote.js +492 -0
- package/core/embedding/embedding-service.js +712 -0
- package/core/embedding/embedding-telemetry.js +219 -0
- package/core/embedding/index.js +40 -0
- package/core/graph/community-detector.js +294 -0
- package/core/graph/graph-expansion.js +839 -0
- package/core/graph/graph-extractor.js +2304 -0
- package/core/graph/graph-search.js +2148 -0
- package/core/graph/hcgs-generator.js +666 -0
- package/core/graph/index.js +16 -0
- package/core/graph/leiden-algorithm.js +547 -0
- package/core/graph/relationship-resolver.js +366 -0
- package/core/graph/repo-map.js +408 -0
- package/core/graph/summary-manager.js +549 -0
- package/core/indexing/artifact-builder.js +1054 -0
- package/core/indexing/ast-chunker.js +709 -0
- package/core/indexing/chunking/chunk-builder.js +170 -0
- package/core/indexing/chunking/markdown-chunker.js +503 -0
- package/core/indexing/chunking/plaintext-chunker.js +104 -0
- package/core/indexing/dedup/dedup-phase.js +159 -0
- package/core/indexing/dedup/exemplar-selector.js +65 -0
- package/core/indexing/document-chunker.js +56 -0
- package/core/indexing/incremental-parser.js +390 -0
- package/core/indexing/incremental-tracker.js +761 -0
- package/core/indexing/index-codebase-v21.js +472 -0
- package/core/indexing/index-maintainer.mjs +1674 -0
- package/core/indexing/index.js +90 -0
- package/core/indexing/indexer-ann.js +1077 -0
- package/core/indexing/indexer-build.js +742 -0
- package/core/indexing/indexer-phases.js +800 -0
- package/core/indexing/indexer-pool.js +764 -0
- package/core/indexing/indexer-sparse-gram.js +98 -0
- package/core/indexing/indexer-utils.js +536 -0
- package/core/indexing/indexer-worker.js +148 -0
- package/core/indexing/li-skip-policy.js +225 -0
- package/core/indexing/merkle-tracker.js +244 -0
- package/core/indexing/model-pool.js +166 -0
- package/core/infrastructure/code-graph-repository.js +120 -0
- package/core/infrastructure/codebase-repository.js +131 -0
- package/core/infrastructure/config/dedup.js +54 -0
- package/core/infrastructure/config/embedding.js +298 -0
- package/core/infrastructure/config/graph.js +80 -0
- package/core/infrastructure/config/index.js +82 -0
- package/core/infrastructure/config/indexing.js +8 -0
- package/core/infrastructure/config/platform.js +254 -0
- package/core/infrastructure/config/ranking.js +221 -0
- package/core/infrastructure/config/search.js +396 -0
- package/core/infrastructure/config/translation.js +89 -0
- package/core/infrastructure/config/vector-store.js +114 -0
- package/core/infrastructure/constants.js +86 -0
- package/core/infrastructure/coreml-cascade.js +909 -0
- package/core/infrastructure/coreml-cascade.json +46 -0
- package/core/infrastructure/coreml-provider.js +81 -0
- package/core/infrastructure/db-utils.js +69 -0
- package/core/infrastructure/dedup-hashing.js +83 -0
- package/core/infrastructure/hardware-capability.js +332 -0
- package/core/infrastructure/index.js +104 -0
- package/core/infrastructure/language-patterns/maps.js +121 -0
- package/core/infrastructure/language-patterns/registry-core.js +323 -0
- package/core/infrastructure/language-patterns/registry-data-query.js +155 -0
- package/core/infrastructure/language-patterns/registry-object-oriented.js +285 -0
- package/core/infrastructure/language-patterns/registry-tooling.js +240 -0
- package/core/infrastructure/language-patterns/registry-web-style.js +143 -0
- package/core/infrastructure/language-patterns/registry.js +19 -0
- package/core/infrastructure/language-patterns.js +141 -0
- package/core/infrastructure/llm-provider.js +733 -0
- package/core/infrastructure/manifest.json +46 -0
- package/core/infrastructure/maxsim.wasm +0 -0
- package/core/infrastructure/model-fetcher.js +423 -0
- package/core/infrastructure/model-registry.js +214 -0
- package/core/infrastructure/native-inference.js +587 -0
- package/core/infrastructure/native-resolver.js +187 -0
- package/core/infrastructure/native-sparse-gram.js +257 -0
- package/core/infrastructure/native-tokenizer.js +160 -0
- package/core/infrastructure/onnx-mutex.js +45 -0
- package/core/infrastructure/onnx-session-utils.js +261 -0
- package/core/infrastructure/ort-pipeline.js +111 -0
- package/core/infrastructure/project-detector.js +102 -0
- package/core/infrastructure/quantization.js +410 -0
- package/core/infrastructure/simd-distance.js +502 -0
- package/core/infrastructure/simd-distance.wasm +0 -0
- package/core/infrastructure/tree-sitter-provider.js +665 -0
- package/core/infrastructure/webgpu-maxsim.js +222 -0
- package/core/query/index.js +35 -0
- package/core/query/intent-detector.js +201 -0
- package/core/query/intent-router.js +156 -0
- package/core/query/query-router-catboost.js +222 -0
- package/core/query/query-router-ml.js +266 -0
- package/core/query/query-router.js +213 -0
- package/core/ranking/cascaded-scorer.js +379 -0
- package/core/ranking/flashrank.js +810 -0
- package/core/ranking/index.js +49 -0
- package/core/ranking/late-interaction-index.js +2383 -0
- package/core/ranking/late-interaction-model.js +812 -0
- package/core/ranking/local-reranker.js +374 -0
- package/core/ranking/mmr.js +379 -0
- package/core/ranking/quality-scorer.js +363 -0
- package/core/search/context-expander.js +1167 -0
- package/core/search/dedup/sibling-expander.js +327 -0
- package/core/search/index.js +16 -0
- package/core/search/search-boost.js +259 -0
- package/core/search/search-cli.js +544 -0
- package/core/search/search-format.js +282 -0
- package/core/search/search-fusion.js +327 -0
- package/core/search/search-hybrid.js +204 -0
- package/core/search/search-pattern-chunks.js +337 -0
- package/core/search/search-pattern-planner.js +439 -0
- package/core/search/search-pattern-prefilter.js +412 -0
- package/core/search/search-pattern-ripgrep.js +663 -0
- package/core/search/search-pattern.js +463 -0
- package/core/search/search-postprocess.js +452 -0
- package/core/search/search-semantic.js +706 -0
- package/core/search/search-server.js +554 -0
- package/core/search/session-daemon-prewarm.mjs +164 -0
- package/core/search/session-warmup.js +595 -0
- package/core/search/sweet-search.js +632 -0
- package/core/search/warmup-metrics.js +532 -0
- package/core/start-server.js +6 -0
- package/core/training/query-router/features/extractor.js +762 -0
- package/core/training/query-router/features/multilingual-patterns.js +431 -0
- package/core/training/query-router/features/text-segmenter.js +303 -0
- package/core/training/query-router/features/unicode-utils.js +383 -0
- package/core/training/query-router/output/v45_router_d4.js +11521 -0
- package/core/training/query-router/output/v46_router_d4.js +11498 -0
- package/core/vector-store/binary-heap.js +227 -0
- package/core/vector-store/binary-hnsw-index.js +1004 -0
- package/core/vector-store/float-vector-store.js +234 -0
- package/core/vector-store/hnsw-index.js +580 -0
- package/core/vector-store/index.js +39 -0
- package/core/vector-store/seismic-index.js +498 -0
- package/core/vocabulary/index.js +84 -0
- package/core/vocabulary/vocab-constants.js +20 -0
- package/core/vocabulary/vocab-miner-extractors.js +375 -0
- package/core/vocabulary/vocab-miner-nl.js +404 -0
- package/core/vocabulary/vocab-miner-utils.js +146 -0
- package/core/vocabulary/vocab-miner.js +574 -0
- package/core/vocabulary/vocab-prewarm-cli.js +110 -0
- package/core/vocabulary/vocab-ranker.js +492 -0
- package/core/vocabulary/vocab-warmer.js +523 -0
- package/core/vocabulary/vocab-warmup-orchestrator.js +425 -0
- package/core/vocabulary/vocabulary-utils.js +704 -0
- package/crates/wasm-router/pkg/package.json +13 -0
- package/crates/wasm-router/pkg/query_router_wasm.d.ts +36 -0
- package/crates/wasm-router/pkg/query_router_wasm.js +271 -0
- package/crates/wasm-router/pkg/query_router_wasm_bg.wasm +0 -0
- package/crates/wasm-router/pkg/query_router_wasm_bg.wasm.d.ts +19 -0
- package/mcp/config-gen.js +121 -0
- package/mcp/server.js +335 -0
- package/mcp/tool-handlers.js +476 -0
- package/package.json +131 -9
- package/scripts/benchmark-harness.js +794 -0
- package/scripts/init.js +1058 -0
- package/scripts/smoke-test.js +435 -0
- package/scripts/uninstall.js +478 -0
- package/scripts/verify-runtime.js +176 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quantization & Distance Utilities — pure math functions shared across domains.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from embedding-service.js during DDD boundary cleanup.
|
|
5
|
+
* No domain dependencies — only pure computation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// BINARY QUANTIZATION
|
|
10
|
+
// =============================================================================
|
|
11
|
+
|
|
12
|
+
export function floatToBinary(embedding) {
|
|
13
|
+
const numBytes = Math.ceil(embedding.length / 8);
|
|
14
|
+
const binary = new Uint8Array(numBytes);
|
|
15
|
+
for (let i = 0; i < embedding.length; i++) {
|
|
16
|
+
if (embedding[i] > 0) {
|
|
17
|
+
binary[Math.floor(i / 8)] |= (1 << (7 - (i % 8)));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return binary;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// CENTROID & ROTATION
|
|
25
|
+
// =============================================================================
|
|
26
|
+
|
|
27
|
+
export function computeCentroid(embeddings) {
|
|
28
|
+
if (!embeddings || embeddings.length === 0) return null;
|
|
29
|
+
const dim = embeddings[0].length;
|
|
30
|
+
const centroid = new Float64Array(dim);
|
|
31
|
+
for (const emb of embeddings) {
|
|
32
|
+
for (let i = 0; i < dim; i++) centroid[i] += emb[i];
|
|
33
|
+
}
|
|
34
|
+
const n = embeddings.length;
|
|
35
|
+
for (let i = 0; i < dim; i++) centroid[i] /= n;
|
|
36
|
+
return new Float32Array(centroid);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function generateSignVector(dim, seed = 42) {
|
|
40
|
+
const signs = new Float32Array(dim);
|
|
41
|
+
let s = seed | 0;
|
|
42
|
+
for (let i = 0; i < dim; i++) {
|
|
43
|
+
s = (s + 0x6D2B79F5) | 0;
|
|
44
|
+
let t = Math.imul(s ^ (s >>> 15), 1 | s);
|
|
45
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
46
|
+
signs[i] = ((t ^ (t >>> 14)) >>> 31) ? 1.0 : -1.0;
|
|
47
|
+
}
|
|
48
|
+
return signs;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function nextPow2(n) {
|
|
52
|
+
let p = 1;
|
|
53
|
+
while (p < n) p <<= 1;
|
|
54
|
+
return p;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Standard Walsh-Hadamard Transform (natural / Hadamard ordering).
|
|
59
|
+
* This is the default used by all existing indexes. Do NOT change
|
|
60
|
+
* the output ordering — it would silently break every index on disk.
|
|
61
|
+
*/
|
|
62
|
+
export function walshHadamardTransform(v) {
|
|
63
|
+
const n = v.length;
|
|
64
|
+
for (let len = 1; len < n; len <<= 1) {
|
|
65
|
+
for (let i = 0; i < n; i += len << 1) {
|
|
66
|
+
for (let j = 0; j < len; j++) {
|
|
67
|
+
const u = v[i + j];
|
|
68
|
+
const w = v[i + j + len];
|
|
69
|
+
v[i + j] = u + w;
|
|
70
|
+
v[i + j + len] = u - w;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const scale = 1.0 / Math.sqrt(n);
|
|
75
|
+
for (let i = 0; i < n; i++) v[i] *= scale;
|
|
76
|
+
return v;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* CRA-4: Sequency-ordered Walsh-Hadamard Transform.
|
|
81
|
+
*
|
|
82
|
+
* Same butterfly as walshHadamardTransform, then permutes to sequency (Walsh)
|
|
83
|
+
* ordering. Clusters similar-frequency components together, reducing
|
|
84
|
+
* quantization error (GSR, arXiv 2505.03810).
|
|
85
|
+
*
|
|
86
|
+
* MUST be opt-in via whtOrdering='sequency' — never default.
|
|
87
|
+
* Indexes built with sequency ordering are NOT compatible with natural-order indexes.
|
|
88
|
+
*/
|
|
89
|
+
export function walshHadamardTransformSequency(v) {
|
|
90
|
+
// Step 1: standard butterfly (natural order)
|
|
91
|
+
walshHadamardTransform(v);
|
|
92
|
+
|
|
93
|
+
// Step 2: permute from natural to sequency order
|
|
94
|
+
const n = v.length;
|
|
95
|
+
const bits = 31 - Math.clz32(n);
|
|
96
|
+
if (n <= 1 || bits === 0) return v;
|
|
97
|
+
|
|
98
|
+
const tmp = new Float32Array(n);
|
|
99
|
+
for (let i = 0; i < n; i++) {
|
|
100
|
+
const g = i ^ (i >>> 1);
|
|
101
|
+
let r = 0, x = g;
|
|
102
|
+
for (let b = 0; b < bits; b++) { r = (r << 1) | (x & 1); x >>>= 1; }
|
|
103
|
+
tmp[i] = v[r];
|
|
104
|
+
}
|
|
105
|
+
v.set(tmp);
|
|
106
|
+
return v;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function fastRotate(v, signs, sequency = false) {
|
|
110
|
+
const origDim = v.length;
|
|
111
|
+
const padDim = nextPow2(origDim);
|
|
112
|
+
const buf = new Float32Array(padDim);
|
|
113
|
+
for (let i = 0; i < origDim; i++) buf[i] = v[i] * signs[i];
|
|
114
|
+
if (sequency) {
|
|
115
|
+
walshHadamardTransformSequency(buf);
|
|
116
|
+
} else {
|
|
117
|
+
walshHadamardTransform(buf);
|
|
118
|
+
}
|
|
119
|
+
return padDim === origDim ? buf : buf.subarray(0, origDim);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// =============================================================================
|
|
123
|
+
// FAST PSEUDO-RANDOM ROTATION (CRA-14)
|
|
124
|
+
// =============================================================================
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* CRA-14: Weaviate-style fast pseudo-random rotation using a sequence of
|
|
128
|
+
* random Givens (pairwise plane) rotations. Achieves variance equalization
|
|
129
|
+
* comparable to WHT at O(d × numRotations) cost, which is cheaper than
|
|
130
|
+
* WHT's O(d log d) for high dimensions (d >= 768).
|
|
131
|
+
*
|
|
132
|
+
* For our d=128 this is similar speed to WHT. Provided for future model
|
|
133
|
+
* scaling and A/B testing against WHT.
|
|
134
|
+
*
|
|
135
|
+
* @param {Float32Array} v - Input vector (modified in-place)
|
|
136
|
+
* @param {number} seed - Deterministic seed for rotation angles and pairs
|
|
137
|
+
* @param {number} [numRounds=3] - Number of rotation rounds (more = better equalization)
|
|
138
|
+
* @returns {Float32Array} Rotated vector (same reference as input)
|
|
139
|
+
*/
|
|
140
|
+
export function fastPseudoRandomRotate(v, seed, numRounds = 3) {
|
|
141
|
+
const dim = v.length;
|
|
142
|
+
if (dim < 2) return v;
|
|
143
|
+
|
|
144
|
+
// Deterministic PRNG from seed
|
|
145
|
+
let s = seed | 0;
|
|
146
|
+
const prng = () => {
|
|
147
|
+
s = (s + 0x6D2B79F5) | 0;
|
|
148
|
+
let t = Math.imul(s ^ (s >>> 15), 1 | s);
|
|
149
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
150
|
+
return ((t ^ (t >>> 14)) >>> 0) / 0x100000000;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Apply numRounds passes of dim/2 random Givens rotations.
|
|
154
|
+
// Each pass randomly pairs all dimensions and applies a random rotation angle.
|
|
155
|
+
const indices = new Uint32Array(dim);
|
|
156
|
+
for (let i = 0; i < dim; i++) indices[i] = i;
|
|
157
|
+
|
|
158
|
+
for (let round = 0; round < numRounds; round++) {
|
|
159
|
+
// Fisher-Yates shuffle for random pairing
|
|
160
|
+
for (let i = dim - 1; i > 0; i--) {
|
|
161
|
+
const j = (prng() * (i + 1)) | 0;
|
|
162
|
+
const tmp = indices[i]; indices[i] = indices[j]; indices[j] = tmp;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Apply Givens rotation to each pair
|
|
166
|
+
for (let p = 0; p < dim - 1; p += 2) {
|
|
167
|
+
const i = indices[p];
|
|
168
|
+
const j = indices[p + 1];
|
|
169
|
+
const theta = prng() * 2 * Math.PI;
|
|
170
|
+
const c = Math.cos(theta);
|
|
171
|
+
const sn = Math.sin(theta);
|
|
172
|
+
const vi = v[i];
|
|
173
|
+
const vj = v[j];
|
|
174
|
+
v[i] = c * vi - sn * vj;
|
|
175
|
+
v[j] = sn * vi + c * vj;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Normalize to preserve L2 norm (Givens rotations are orthogonal, but
|
|
180
|
+
// accumulated floating-point error can cause small drift)
|
|
181
|
+
let normSq = 0;
|
|
182
|
+
for (let i = 0; i < dim; i++) normSq += v[i] * v[i];
|
|
183
|
+
if (normSq > 0) {
|
|
184
|
+
const scale = Math.sqrt(normSq);
|
|
185
|
+
// Only renormalize if drift exceeds epsilon
|
|
186
|
+
if (Math.abs(scale - 1.0) > 1e-6) {
|
|
187
|
+
for (let i = 0; i < dim; i++) v[i] /= scale;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return v;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// =============================================================================
|
|
195
|
+
// WUSH CALIBRATED ROTATION (CRA-3)
|
|
196
|
+
// =============================================================================
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Calibrate a WUSH transform from sample embeddings.
|
|
200
|
+
* Computes the covariance matrix, eigendecomposition, and produces
|
|
201
|
+
* the calibration scaling vector S^(-1/2) and rotation matrix U.
|
|
202
|
+
*
|
|
203
|
+
* The full WUSH transform is: T = H × diag(S^(-1/2)) × U^T
|
|
204
|
+
* where H is the Walsh-Hadamard matrix, and U,S come from the
|
|
205
|
+
* eigendecomposition of the covariance matrix.
|
|
206
|
+
*
|
|
207
|
+
* @param {Float32Array[]} samples - Sample embedding vectors (L2-normalized)
|
|
208
|
+
* @param {number} dim - Embedding dimension
|
|
209
|
+
* @returns {{ eigenVecs: Float64Array, invSqrtEigenVals: Float64Array }} Calibration data
|
|
210
|
+
*/
|
|
211
|
+
export function calibrateWUSH(samples, dim) {
|
|
212
|
+
const n = samples.length;
|
|
213
|
+
if (n < dim) throw new Error(`WUSH calibration requires >= ${dim} samples, got ${n}`);
|
|
214
|
+
|
|
215
|
+
// Compute covariance matrix C = (1/N) × X^T × X (symmetric, dim × dim)
|
|
216
|
+
const C = new Float64Array(dim * dim);
|
|
217
|
+
for (let s = 0; s < n; s++) {
|
|
218
|
+
const x = samples[s];
|
|
219
|
+
for (let i = 0; i < dim; i++) {
|
|
220
|
+
const xi = x[i];
|
|
221
|
+
for (let j = i; j < dim; j++) {
|
|
222
|
+
C[i * dim + j] += xi * x[j];
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// Symmetrize and normalize
|
|
227
|
+
for (let i = 0; i < dim; i++) {
|
|
228
|
+
C[i * dim + i] /= n;
|
|
229
|
+
for (let j = i + 1; j < dim; j++) {
|
|
230
|
+
const v = C[i * dim + j] / n;
|
|
231
|
+
C[i * dim + j] = v;
|
|
232
|
+
C[j * dim + i] = v;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Jacobi eigendecomposition of symmetric C.
|
|
237
|
+
// eigenVecs columns are eigenvectors (stored row-major as V[i*dim+j]).
|
|
238
|
+
const V = new Float64Array(dim * dim);
|
|
239
|
+
for (let i = 0; i < dim; i++) V[i * dim + i] = 1.0; // identity
|
|
240
|
+
|
|
241
|
+
const maxIter = 100;
|
|
242
|
+
for (let iter = 0; iter < maxIter; iter++) {
|
|
243
|
+
// Find largest off-diagonal element
|
|
244
|
+
let maxVal = 0, p = 0, q = 0;
|
|
245
|
+
for (let i = 0; i < dim; i++) {
|
|
246
|
+
for (let j = i + 1; j < dim; j++) {
|
|
247
|
+
const absVal = Math.abs(C[i * dim + j]);
|
|
248
|
+
if (absVal > maxVal) { maxVal = absVal; p = i; q = j; }
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (maxVal < 1e-10) break; // converged
|
|
252
|
+
|
|
253
|
+
// Jacobi rotation for (p, q)
|
|
254
|
+
const cpp = C[p * dim + p];
|
|
255
|
+
const cqq = C[q * dim + q];
|
|
256
|
+
const cpq = C[p * dim + q];
|
|
257
|
+
|
|
258
|
+
const theta = 0.5 * Math.atan2(2 * cpq, cpp - cqq);
|
|
259
|
+
const c = Math.cos(theta);
|
|
260
|
+
const s2 = Math.sin(theta);
|
|
261
|
+
|
|
262
|
+
// Update C: rotate rows/cols p and q
|
|
263
|
+
for (let i = 0; i < dim; i++) {
|
|
264
|
+
if (i === p || i === q) continue;
|
|
265
|
+
const cip = C[i * dim + p];
|
|
266
|
+
const ciq = C[i * dim + q];
|
|
267
|
+
C[i * dim + p] = C[p * dim + i] = c * cip + s2 * ciq;
|
|
268
|
+
C[i * dim + q] = C[q * dim + i] = -s2 * cip + c * ciq;
|
|
269
|
+
}
|
|
270
|
+
C[p * dim + p] = c * c * cpp + 2 * c * s2 * cpq + s2 * s2 * cqq;
|
|
271
|
+
C[q * dim + q] = s2 * s2 * cpp - 2 * c * s2 * cpq + c * c * cqq;
|
|
272
|
+
C[p * dim + q] = C[q * dim + p] = 0;
|
|
273
|
+
|
|
274
|
+
// Update eigenvector matrix V
|
|
275
|
+
for (let i = 0; i < dim; i++) {
|
|
276
|
+
const vip = V[i * dim + p];
|
|
277
|
+
const viq = V[i * dim + q];
|
|
278
|
+
V[i * dim + p] = c * vip + s2 * viq;
|
|
279
|
+
V[i * dim + q] = -s2 * vip + c * viq;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Extract eigenvalues (diagonal of C) and compute S^(-1/2)
|
|
284
|
+
const invSqrtEigenVals = new Float64Array(dim);
|
|
285
|
+
for (let i = 0; i < dim; i++) {
|
|
286
|
+
const ev = Math.max(C[i * dim + i], 1e-12);
|
|
287
|
+
invSqrtEigenVals[i] = 1.0 / Math.sqrt(ev);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return { eigenVecs: V, invSqrtEigenVals };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Apply the WUSH-calibrated rotation: T_wush × v = H × diag(S^(-1/2)) × U^T × v
|
|
295
|
+
*
|
|
296
|
+
* Steps:
|
|
297
|
+
* 1. Project: w = U^T × v (rotate into eigenbasis)
|
|
298
|
+
* 2. Scale: w[i] *= S^(-1/2)[i] (equalize dimension variance)
|
|
299
|
+
* 3. WHT: w = H × w (spread information across dimensions)
|
|
300
|
+
*
|
|
301
|
+
* @param {Float32Array} v - Input vector
|
|
302
|
+
* @param {Float64Array} eigenVecs - Eigenvector matrix (dim×dim, row-major)
|
|
303
|
+
* @param {Float64Array} invSqrtEigenVals - S^(-1/2) diagonal
|
|
304
|
+
* @param {Float32Array} signs - Sign-flip vector (for randomization)
|
|
305
|
+
* @returns {Float32Array} Rotated vector
|
|
306
|
+
*/
|
|
307
|
+
export function wushRotate(v, eigenVecs, invSqrtEigenVals, signs) {
|
|
308
|
+
const dim = v.length;
|
|
309
|
+
|
|
310
|
+
// Step 1: w = U^T × v (U stored row-major in eigenVecs, so U^T × v = transpose-multiply)
|
|
311
|
+
const w = new Float32Array(dim);
|
|
312
|
+
for (let i = 0; i < dim; i++) {
|
|
313
|
+
let sum = 0;
|
|
314
|
+
for (let j = 0; j < dim; j++) {
|
|
315
|
+
sum += eigenVecs[j * dim + i] * v[j]; // column i of V = row j, col i
|
|
316
|
+
}
|
|
317
|
+
// Step 2: scale by S^(-1/2)
|
|
318
|
+
w[i] = sum * invSqrtEigenVals[i];
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Step 3: Sign-flip + WHT (reuses existing infrastructure)
|
|
322
|
+
for (let i = 0; i < dim; i++) w[i] *= signs[i];
|
|
323
|
+
walshHadamardTransform(w);
|
|
324
|
+
|
|
325
|
+
return w;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// =============================================================================
|
|
329
|
+
// ASYMMETRIC QUANTIZATION
|
|
330
|
+
// =============================================================================
|
|
331
|
+
|
|
332
|
+
export function asymmetricDocEncode(embedding, centroid, signs) {
|
|
333
|
+
const dim = embedding.length;
|
|
334
|
+
const centered = new Float32Array(dim);
|
|
335
|
+
for (let i = 0; i < dim; i++) centered[i] = embedding[i] - centroid[i];
|
|
336
|
+
const rotated = fastRotate(centered, signs);
|
|
337
|
+
return floatToBinary(rotated);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function asymmetricQueryEncode(embedding, centroid, signs) {
|
|
341
|
+
const dim = embedding.length;
|
|
342
|
+
const centered = new Float32Array(dim);
|
|
343
|
+
for (let i = 0; i < dim; i++) centered[i] = embedding[i] - centroid[i];
|
|
344
|
+
const rotated = fastRotate(centered, signs);
|
|
345
|
+
let maxAbs = 0;
|
|
346
|
+
for (let i = 0; i < dim; i++) {
|
|
347
|
+
const abs = Math.abs(rotated[i]);
|
|
348
|
+
if (abs > maxAbs) maxAbs = abs;
|
|
349
|
+
}
|
|
350
|
+
const int4 = new Int8Array(dim);
|
|
351
|
+
if (maxAbs > 0) {
|
|
352
|
+
const scale = 7.0 / maxAbs;
|
|
353
|
+
for (let i = 0; i < dim; i++) {
|
|
354
|
+
int4[i] = Math.round(Math.max(-7, Math.min(7, rotated[i] * scale)));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
let norm = 0;
|
|
358
|
+
for (let i = 0; i < dim; i++) norm += rotated[i] * rotated[i];
|
|
359
|
+
return { int4, norm };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// =============================================================================
|
|
363
|
+
// INT8 QUANTIZATION
|
|
364
|
+
// =============================================================================
|
|
365
|
+
|
|
366
|
+
export function floatToInt8(embedding) {
|
|
367
|
+
const int8 = new Int8Array(embedding.length);
|
|
368
|
+
let maxAbs = 0;
|
|
369
|
+
for (let i = 0; i < embedding.length; i++) {
|
|
370
|
+
const abs = Math.abs(embedding[i]);
|
|
371
|
+
if (abs > maxAbs) maxAbs = abs;
|
|
372
|
+
}
|
|
373
|
+
if (maxAbs === 0) return int8;
|
|
374
|
+
const scale = 127 / maxAbs;
|
|
375
|
+
for (let i = 0; i < embedding.length; i++) {
|
|
376
|
+
int8[i] = Math.round(Math.max(-127, Math.min(127, embedding[i] * scale)));
|
|
377
|
+
}
|
|
378
|
+
return int8;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function normalizedFloatToInt8(embedding) {
|
|
382
|
+
const int8 = new Int8Array(embedding.length);
|
|
383
|
+
for (let i = 0; i < embedding.length; i++) {
|
|
384
|
+
const clamped = Math.max(-1, Math.min(1, embedding[i]));
|
|
385
|
+
int8[i] = Math.round(clamped * 127);
|
|
386
|
+
}
|
|
387
|
+
return int8;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// =============================================================================
|
|
391
|
+
// TRUNCATION & SHUFFLE
|
|
392
|
+
// =============================================================================
|
|
393
|
+
|
|
394
|
+
export function truncateForHNSW(embedding, targetDim) {
|
|
395
|
+
if (embedding.length <= targetDim) return embedding;
|
|
396
|
+
const truncated = embedding.slice(0, targetDim);
|
|
397
|
+
let norm = 0;
|
|
398
|
+
for (let i = 0; i < truncated.length; i++) norm += truncated[i] * truncated[i];
|
|
399
|
+
norm = Math.sqrt(norm);
|
|
400
|
+
if (norm > 0) for (let i = 0; i < truncated.length; i++) truncated[i] /= norm;
|
|
401
|
+
return truncated;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export function fisherYatesShuffle(arr) {
|
|
405
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
406
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
407
|
+
const tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp;
|
|
408
|
+
}
|
|
409
|
+
return arr;
|
|
410
|
+
}
|