umap-web 0.2.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.
@@ -0,0 +1,802 @@
1
+ //#region src/types.ts
2
+ const silentLogger = {
3
+ info() {},
4
+ warn() {}
5
+ };
6
+ const consoleLogger = {
7
+ info(msg) {
8
+ console.log(`[umap-web] ${msg}`);
9
+ },
10
+ warn(msg) {
11
+ console.warn(`[umap-web] ${msg}`);
12
+ }
13
+ };
14
+ var UmapError = class extends Error {
15
+ name = "UmapError";
16
+ };
17
+ var UmapValidationError = class extends UmapError {
18
+ name = "UmapValidationError";
19
+ };
20
+ var UmapBackendError = class extends UmapError {
21
+ name = "UmapBackendError";
22
+ };
23
+ /** Surfaced through the logger on convergence problems (spectral fallback etc.). */
24
+ var UmapConvergenceWarning = class extends UmapError {
25
+ name = "UmapConvergenceWarning";
26
+ };
27
+ function abortError() {
28
+ return new DOMException("The operation was aborted.", "AbortError");
29
+ }
30
+ function throwIfAborted(signal) {
31
+ if (signal?.aborted) throw signal.reason ?? abortError();
32
+ }
33
+
34
+ //#endregion
35
+ //#region src/linalg.ts
36
+ /** Cyclic Jacobi for a symmetric n×n matrix (row-major input, not modified). */
37
+ function symmetricEigen(A, n, maxSweeps = 100) {
38
+ const a = A.slice();
39
+ const v = new Float64Array(n * n);
40
+ for (let i = 0; i < n; i++) v[i * n + i] = 1;
41
+ for (let sweep = 0; sweep < maxSweeps; sweep++) {
42
+ let off = 0;
43
+ for (let p = 0; p < n - 1; p++) for (let q = p + 1; q < n; q++) off += a[p * n + q] * a[p * n + q];
44
+ if (off < 1e-30) break;
45
+ for (let p = 0; p < n - 1; p++) for (let q = p + 1; q < n; q++) {
46
+ const apq = a[p * n + q];
47
+ if (Math.abs(apq) < 1e-300) continue;
48
+ const app = a[p * n + p];
49
+ const tau = (a[q * n + q] - app) / (2 * apq);
50
+ const t = Math.sign(tau) / (Math.abs(tau) + Math.sqrt(1 + tau * tau)) || 1 / (tau + Math.sqrt(1 + tau * tau));
51
+ const c = 1 / Math.sqrt(1 + t * t);
52
+ const s = t * c;
53
+ for (let i = 0; i < n; i++) {
54
+ const aip = a[i * n + p];
55
+ const aiq = a[i * n + q];
56
+ a[i * n + p] = c * aip - s * aiq;
57
+ a[i * n + q] = s * aip + c * aiq;
58
+ }
59
+ for (let i = 0; i < n; i++) {
60
+ const api = a[p * n + i];
61
+ const aqi = a[q * n + i];
62
+ a[p * n + i] = c * api - s * aqi;
63
+ a[q * n + i] = s * api + c * aqi;
64
+ }
65
+ for (let i = 0; i < n; i++) {
66
+ const vip = v[i * n + p];
67
+ const viq = v[i * n + q];
68
+ v[i * n + p] = c * vip - s * viq;
69
+ v[i * n + q] = s * vip + c * viq;
70
+ }
71
+ }
72
+ }
73
+ const values = new Float64Array(n);
74
+ for (let i = 0; i < n; i++) values[i] = a[i * n + i];
75
+ const order = Array.from({ length: n }, (_, i) => i).sort((x, y) => values[x] - values[y]);
76
+ const outVal = new Float64Array(n);
77
+ const outVec = new Float64Array(n * n);
78
+ for (let c = 0; c < n; c++) {
79
+ const src = order[c];
80
+ outVal[c] = values[src];
81
+ for (let r = 0; r < n; r++) outVec[c * n + r] = v[r * n + src];
82
+ }
83
+ return {
84
+ values: outVal,
85
+ vectors: outVec
86
+ };
87
+ }
88
+ /**
89
+ * Modified Gram–Schmidt over columns stored contiguously (col-major: X[c*n..c*n+n)).
90
+ * Columns with norm below dropTol (relative to their original norm) are dropped.
91
+ * Returns the number of retained columns (retained columns are compacted in place).
92
+ */
93
+ function mgsOrthonormalize(X, n, cols, dropTol = 1e-10) {
94
+ let kept = 0;
95
+ for (let c = 0; c < cols; c++) {
96
+ const src = c * n;
97
+ if (kept !== c) X.copyWithin(kept * n, src, src + n);
98
+ const base = kept * n;
99
+ let norm0 = 0;
100
+ for (let r = 0; r < n; r++) norm0 += X[base + r] * X[base + r];
101
+ norm0 = Math.sqrt(norm0);
102
+ for (let pass = 0; pass < 2; pass++) for (let p = 0; p < kept; p++) {
103
+ const pb = p * n;
104
+ let dot = 0;
105
+ for (let r = 0; r < n; r++) dot += X[pb + r] * X[base + r];
106
+ for (let r = 0; r < n; r++) X[base + r] = X[base + r] - dot * X[pb + r];
107
+ }
108
+ let norm = 0;
109
+ for (let r = 0; r < n; r++) norm += X[base + r] * X[base + r];
110
+ norm = Math.sqrt(norm);
111
+ if (norm > dropTol * Math.max(norm0, 1e-300) && norm > 1e-300) {
112
+ for (let r = 0; r < n; r++) X[base + r] = X[base + r] / norm;
113
+ kept++;
114
+ }
115
+ }
116
+ return kept;
117
+ }
118
+
119
+ //#endregion
120
+ //#region src/init/pca.ts
121
+ function pcaScores(X, dim, rng, center = true) {
122
+ const n = X.rows;
123
+ const d = X.cols;
124
+ const l = Math.min(d, dim + 7);
125
+ const mean = new Float64Array(d);
126
+ if (center) {
127
+ for (let i = 0; i < n; i++) for (let j = 0; j < d; j++) mean[j] = mean[j] + X.data[i * d + j];
128
+ for (let j = 0; j < d; j++) mean[j] = mean[j] / n;
129
+ }
130
+ const omega = new Float64Array(d * l);
131
+ for (let i = 0; i < omega.length; i++) omega[i] = rng.normal();
132
+ let Q = new Float64Array(n * l);
133
+ for (let i = 0; i < n; i++) for (let c = 0; c < l; c++) {
134
+ let acc = 0;
135
+ for (let j = 0; j < d; j++) acc += (X.data[i * d + j] - mean[j]) * omega[j * l + c];
136
+ Q[c * n + i] = acc;
137
+ }
138
+ orthonormalizeCols(Q, n, l);
139
+ for (let power = 0; power < 2; power++) {
140
+ const Z = new Float64Array(d * l);
141
+ for (let c = 0; c < l; c++) for (let i = 0; i < n; i++) {
142
+ const q = Q[c * n + i];
143
+ if (q === 0) continue;
144
+ for (let j = 0; j < d; j++) Z[c * d + j] = Z[c * d + j] + (X.data[i * d + j] - mean[j]) * q;
145
+ }
146
+ const Q2 = new Float64Array(n * l);
147
+ for (let c = 0; c < l; c++) for (let i = 0; i < n; i++) {
148
+ let acc = 0;
149
+ for (let j = 0; j < d; j++) acc += (X.data[i * d + j] - mean[j]) * Z[c * d + j];
150
+ Q2[c * n + i] = acc;
151
+ }
152
+ Q = Q2;
153
+ orthonormalizeCols(Q, n, l);
154
+ }
155
+ const B = new Float64Array(l * d);
156
+ for (let c = 0; c < l; c++) for (let i = 0; i < n; i++) {
157
+ const q = Q[c * n + i];
158
+ if (q === 0) continue;
159
+ for (let j = 0; j < d; j++) B[c * d + j] = B[c * d + j] + q * (X.data[i * d + j] - mean[j]);
160
+ }
161
+ const BBt = new Float64Array(l * l);
162
+ for (let a = 0; a < l; a++) for (let b = a; b < l; b++) {
163
+ let acc = 0;
164
+ for (let j = 0; j < d; j++) acc += B[a * d + j] * B[b * d + j];
165
+ BBt[a * l + b] = acc;
166
+ BBt[b * l + a] = acc;
167
+ }
168
+ const eig = symmetricEigen(BBt, l);
169
+ const scores = new Float64Array(n * dim);
170
+ for (let c = 0; c < dim; c++) {
171
+ const col = l - 1 - c;
172
+ const sigma = Math.sqrt(Math.max(eig.values[col], 0));
173
+ for (let i = 0; i < n; i++) {
174
+ let acc = 0;
175
+ for (let a = 0; a < l; a++) acc += Q[a * n + i] * eig.vectors[col * l + a];
176
+ scores[i * dim + c] = acc * sigma;
177
+ }
178
+ }
179
+ return scores;
180
+ }
181
+ function orthonormalizeCols(Q, n, cols) {
182
+ for (let c = 0; c < cols; c++) {
183
+ const base = c * n;
184
+ for (let pass = 0; pass < 2; pass++) for (let p = 0; p < c; p++) {
185
+ const pb = p * n;
186
+ let dot = 0;
187
+ for (let r = 0; r < n; r++) dot += Q[pb + r] * Q[base + r];
188
+ for (let r = 0; r < n; r++) Q[base + r] = Q[base + r] - dot * Q[pb + r];
189
+ }
190
+ let norm = 0;
191
+ for (let r = 0; r < n; r++) norm += Q[base + r] * Q[base + r];
192
+ norm = Math.sqrt(norm);
193
+ if (norm > 1e-300) for (let r = 0; r < n; r++) Q[base + r] = Q[base + r] / norm;
194
+ }
195
+ }
196
+
197
+ //#endregion
198
+ //#region src/init/spectral.ts
199
+ /** Connected components via BFS over an undirected CSR graph. */
200
+ function connectedComponents(graph) {
201
+ const n = graph.rows;
202
+ const labels = new Int32Array(n).fill(-1);
203
+ const queue = new Int32Array(n);
204
+ let count = 0;
205
+ for (let s = 0; s < n; s++) {
206
+ if (labels[s] !== -1) continue;
207
+ labels[s] = count;
208
+ let head = 0;
209
+ let tail = 0;
210
+ queue[tail++] = s;
211
+ while (head < tail) {
212
+ const u = queue[head++];
213
+ for (let p = graph.indptr[u]; p < graph.indptr[u + 1]; p++) {
214
+ const v = graph.indices[p];
215
+ if (labels[v] === -1) {
216
+ labels[v] = count;
217
+ queue[tail++] = v;
218
+ }
219
+ }
220
+ }
221
+ count++;
222
+ }
223
+ return {
224
+ count,
225
+ labels
226
+ };
227
+ }
228
+ /**
229
+ * Y = L S for a block S of `cols` column vectors, where
230
+ * L = I − D^{-1/2} A D^{-1/2}. `scaled` holds the pre-scaled off-diagonal
231
+ * values A_ij·dinvsqrt[j] (computed once per solve); dinvsqrt[i] is applied
232
+ * after each row sum — the exact fp op order of the unfused loop, so results
233
+ * are bit-identical to the reference implementation path, per column. All
234
+ * columns share one pass over the CSR arrays (the SpMV is memory-bound; the
235
+ * blocked walk reads `scaled`/`indices` once instead of once per column).
236
+ */
237
+ function laplacianMatVecBlock(graph, scaled, dinvsqrt, S, cols, Y) {
238
+ const n = graph.rows;
239
+ const indptr = graph.indptr;
240
+ const indices = graph.indices;
241
+ if (cols === 3) {
242
+ for (let i = 0; i < n; i++) {
243
+ let a0 = 0;
244
+ let a1 = 0;
245
+ let a2 = 0;
246
+ const end = indptr[i + 1];
247
+ for (let p = indptr[i]; p < end; p++) {
248
+ const v = scaled[p];
249
+ const j = indices[p];
250
+ a0 += v * S[j];
251
+ a1 += v * S[n + j];
252
+ a2 += v * S[2 * n + j];
253
+ }
254
+ const di = dinvsqrt[i];
255
+ Y[i] = S[i] - di * a0;
256
+ Y[n + i] = S[n + i] - di * a1;
257
+ Y[2 * n + i] = S[2 * n + i] - di * a2;
258
+ }
259
+ return;
260
+ }
261
+ const acc = new Float64Array(cols);
262
+ for (let i = 0; i < n; i++) {
263
+ acc.fill(0);
264
+ const end = indptr[i + 1];
265
+ for (let p = indptr[i]; p < end; p++) {
266
+ const v = scaled[p];
267
+ const j = indices[p];
268
+ for (let c = 0; c < cols; c++) acc[c] += v * S[c * n + j];
269
+ }
270
+ const di = dinvsqrt[i];
271
+ for (let c = 0; c < cols; c++) Y[c * n + i] = S[c * n + i] - di * acc[c];
272
+ }
273
+ }
274
+ function scaleLaplacian(graph, dinvsqrt) {
275
+ const scaled = new Float64Array(graph.data.length);
276
+ for (let i = 0; i < graph.rows; i++) for (let p = graph.indptr[i]; p < graph.indptr[i + 1]; p++) scaled[p] = graph.data[p] * dinvsqrt[graph.indices[p]];
277
+ return scaled;
278
+ }
279
+ /**
280
+ * LOBPCG for the m smallest eigenpairs of the normalized Laplacian of `graph`,
281
+ * with diagonal (Jacobi) preconditioner. X0 is col-major n×m.
282
+ */
283
+ function lobpcgSmallest(graph, dinvsqrt, X0, m, tol = 1e-4, maxIter = 200) {
284
+ const n = graph.rows;
285
+ const diag = new Float64Array(n).fill(1);
286
+ for (let i = 0; i < n; i++) for (let p = graph.indptr[i]; p < graph.indptr[i + 1]; p++) if (graph.indices[p] === i) diag[i] = 1 - graph.data[p] * dinvsqrt[i] * dinvsqrt[i];
287
+ const precond = new Float64Array(n);
288
+ for (let i = 0; i < n; i++) precond[i] = Math.abs(diag[i]) > 1e-12 ? 1 / diag[i] : 1;
289
+ const scaled = scaleLaplacian(graph, dinvsqrt);
290
+ let X = X0.slice();
291
+ let P = null;
292
+ let pCols = 0;
293
+ const values = new Float64Array(m);
294
+ const residuals = new Float64Array(m).fill(Infinity);
295
+ let converged = false;
296
+ let iter = 0;
297
+ const LXbuf = new Float64Array(n * m);
298
+ const Rbuf = new Float64Array(n * m);
299
+ const Wbuf = new Float64Array(n * m);
300
+ const Sbuf = new Float64Array(n * 3 * m);
301
+ const LSbuf = new Float64Array(n * 3 * m);
302
+ const xtlx = new Float64Array(m);
303
+ let newX = new Float64Array(n * m);
304
+ let newP = new Float64Array(n * m);
305
+ const applyL = (S, cols, out) => {
306
+ laplacianMatVecBlock(graph, scaled, dinvsqrt, S, cols, out);
307
+ return out;
308
+ };
309
+ for (iter = 0; iter < maxIter; iter++) {
310
+ mgsOrthonormalize(X, n, m);
311
+ const LX$1 = applyL(X, m, LXbuf);
312
+ for (let c = 0; c < m; c++) {
313
+ let dot = 0;
314
+ for (let r = 0; r < n; r++) dot += X[c * n + r] * LX$1[c * n + r];
315
+ xtlx[c] = dot;
316
+ }
317
+ const R = Rbuf;
318
+ let worst = 0;
319
+ for (let c = 0; c < m; c++) {
320
+ let rn = 0;
321
+ for (let r = 0; r < n; r++) {
322
+ const v = LX$1[c * n + r] - xtlx[c] * X[c * n + r];
323
+ R[c * n + r] = v;
324
+ rn += v * v;
325
+ }
326
+ residuals[c] = Math.sqrt(rn);
327
+ if (residuals[c] > worst) worst = residuals[c];
328
+ values[c] = xtlx[c];
329
+ }
330
+ if (worst <= tol) {
331
+ converged = true;
332
+ break;
333
+ }
334
+ const W = Wbuf;
335
+ for (let c = 0; c < m; c++) for (let r = 0; r < n; r++) W[c * n + r] = precond[r] * R[c * n + r];
336
+ const totalCols = m + m + pCols;
337
+ const S = Sbuf;
338
+ S.set(X.subarray(0, n * m), 0);
339
+ S.set(W, n * m);
340
+ if (P && pCols > 0) S.set(P.subarray(0, n * pCols), n * 2 * m);
341
+ const q = mgsOrthonormalize(S, n, totalCols);
342
+ if (q < m) break;
343
+ const LS = applyL(S, q, LSbuf);
344
+ const A$1 = new Float64Array(q * q);
345
+ for (let a = 0; a < q; a++) for (let b = a; b < q; b++) {
346
+ let dot = 0;
347
+ for (let r = 0; r < n; r++) dot += S[a * n + r] * LS[b * n + r];
348
+ A$1[a * q + b] = dot;
349
+ A$1[b * q + a] = dot;
350
+ }
351
+ const eig$1 = symmetricEigen(A$1, q);
352
+ newX.fill(0);
353
+ newP.fill(0);
354
+ for (let c = 0; c < m; c++) for (let a = 0; a < q; a++) {
355
+ const w = eig$1.vectors[c * q + a];
356
+ if (w === 0) continue;
357
+ const sBase = a * n;
358
+ const xBase = c * n;
359
+ for (let r = 0; r < n; r++) newX[xBase + r] = newX[xBase + r] + w * S[sBase + r];
360
+ if (a >= m) for (let r = 0; r < n; r++) newP[xBase + r] = newP[xBase + r] + w * S[sBase + r];
361
+ }
362
+ const oldX = X;
363
+ const oldP = P ?? new Float64Array(n * m);
364
+ X = newX;
365
+ P = newP;
366
+ newX = oldX;
367
+ newP = oldP;
368
+ pCols = m;
369
+ }
370
+ mgsOrthonormalize(X, n, m);
371
+ const LX = applyL(X, m, LXbuf);
372
+ const A = new Float64Array(m * m);
373
+ for (let a = 0; a < m; a++) for (let b = a; b < m; b++) {
374
+ let dot = 0;
375
+ for (let r = 0; r < n; r++) dot += X[a * n + r] * LX[b * n + r];
376
+ A[a * m + b] = dot;
377
+ A[b * m + a] = dot;
378
+ }
379
+ const eig = symmetricEigen(A, m);
380
+ const outVec = new Float64Array(n * m);
381
+ for (let c = 0; c < m; c++) for (let a = 0; a < m; a++) {
382
+ const w = eig.vectors[c * m + a];
383
+ if (w === 0) continue;
384
+ for (let r = 0; r < n; r++) outVec[c * n + r] = outVec[c * n + r] + w * X[a * n + r];
385
+ }
386
+ const LV = applyL(outVec, m, LXbuf);
387
+ for (let c = 0; c < m; c++) {
388
+ let rn = 0;
389
+ for (let r = 0; r < n; r++) {
390
+ const v = LV[c * n + r] - eig.values[c] * outVec[c * n + r];
391
+ rn += v * v;
392
+ }
393
+ residuals[c] = Math.sqrt(rn);
394
+ }
395
+ return {
396
+ values: eig.values,
397
+ vectors: outVec,
398
+ converged,
399
+ iterations: iter,
400
+ residuals
401
+ };
402
+ }
403
+ /**
404
+ * lobpcgSmallest with an injected (possibly asynchronous) L·S operator — used
405
+ * by the WebGPU backend to offload the SpMV. A verbatim duplicate of the sync
406
+ * driver above (kept separate so the validated CPU path stays untouched);
407
+ * only the applyL calls are awaited.
408
+ */
409
+ async function lobpcgSmallestAsync(graph, dinvsqrt, X0, m, tol, maxIter, applyLAsync) {
410
+ const n = graph.rows;
411
+ const diag = new Float64Array(n).fill(1);
412
+ for (let i = 0; i < n; i++) for (let p = graph.indptr[i]; p < graph.indptr[i + 1]; p++) if (graph.indices[p] === i) diag[i] = 1 - graph.data[p] * dinvsqrt[i] * dinvsqrt[i];
413
+ const precond = new Float64Array(n);
414
+ for (let i = 0; i < n; i++) precond[i] = Math.abs(diag[i]) > 1e-12 ? 1 / diag[i] : 1;
415
+ let X = X0.slice();
416
+ let P = null;
417
+ let pCols = 0;
418
+ const values = new Float64Array(m);
419
+ const residuals = new Float64Array(m).fill(Infinity);
420
+ let converged = false;
421
+ let iter = 0;
422
+ const LXbuf = new Float64Array(n * m);
423
+ const Rbuf = new Float64Array(n * m);
424
+ const Wbuf = new Float64Array(n * m);
425
+ const Sbuf = new Float64Array(n * 3 * m);
426
+ const LSbuf = new Float64Array(n * 3 * m);
427
+ const xtlx = new Float64Array(m);
428
+ let newX = new Float64Array(n * m);
429
+ let newP = new Float64Array(n * m);
430
+ for (iter = 0; iter < maxIter; iter++) {
431
+ mgsOrthonormalize(X, n, m);
432
+ const LX$1 = await applyLAsync(X, m, LXbuf);
433
+ for (let c = 0; c < m; c++) {
434
+ let dot = 0;
435
+ for (let r = 0; r < n; r++) dot += X[c * n + r] * LX$1[c * n + r];
436
+ xtlx[c] = dot;
437
+ }
438
+ const R = Rbuf;
439
+ let worst = 0;
440
+ for (let c = 0; c < m; c++) {
441
+ let rn = 0;
442
+ for (let r = 0; r < n; r++) {
443
+ const v = LX$1[c * n + r] - xtlx[c] * X[c * n + r];
444
+ R[c * n + r] = v;
445
+ rn += v * v;
446
+ }
447
+ residuals[c] = Math.sqrt(rn);
448
+ if (residuals[c] > worst) worst = residuals[c];
449
+ values[c] = xtlx[c];
450
+ }
451
+ if (worst <= tol) {
452
+ converged = true;
453
+ break;
454
+ }
455
+ const W = Wbuf;
456
+ for (let c = 0; c < m; c++) for (let r = 0; r < n; r++) W[c * n + r] = precond[r] * R[c * n + r];
457
+ const totalCols = m + m + pCols;
458
+ const S = Sbuf;
459
+ S.set(X.subarray(0, n * m), 0);
460
+ S.set(W, n * m);
461
+ if (P && pCols > 0) S.set(P.subarray(0, n * pCols), n * 2 * m);
462
+ const q = mgsOrthonormalize(S, n, totalCols);
463
+ if (q < m) break;
464
+ const LS = await applyLAsync(S, q, LSbuf);
465
+ const A$1 = new Float64Array(q * q);
466
+ for (let a = 0; a < q; a++) for (let b = a; b < q; b++) {
467
+ let dot = 0;
468
+ for (let r = 0; r < n; r++) dot += S[a * n + r] * LS[b * n + r];
469
+ A$1[a * q + b] = dot;
470
+ A$1[b * q + a] = dot;
471
+ }
472
+ const eig$1 = symmetricEigen(A$1, q);
473
+ newX.fill(0);
474
+ newP.fill(0);
475
+ for (let c = 0; c < m; c++) for (let a = 0; a < q; a++) {
476
+ const w = eig$1.vectors[c * q + a];
477
+ if (w === 0) continue;
478
+ const sBase = a * n;
479
+ const xBase = c * n;
480
+ for (let r = 0; r < n; r++) newX[xBase + r] = newX[xBase + r] + w * S[sBase + r];
481
+ if (a >= m) for (let r = 0; r < n; r++) newP[xBase + r] = newP[xBase + r] + w * S[sBase + r];
482
+ }
483
+ const oldX = X;
484
+ const oldP = P ?? new Float64Array(n * m);
485
+ X = newX;
486
+ P = newP;
487
+ newX = oldX;
488
+ newP = oldP;
489
+ pCols = m;
490
+ }
491
+ mgsOrthonormalize(X, n, m);
492
+ const LX = await applyLAsync(X, m, LXbuf);
493
+ const A = new Float64Array(m * m);
494
+ for (let a = 0; a < m; a++) for (let b = a; b < m; b++) {
495
+ let dot = 0;
496
+ for (let r = 0; r < n; r++) dot += X[a * n + r] * LX[b * n + r];
497
+ A[a * m + b] = dot;
498
+ A[b * m + a] = dot;
499
+ }
500
+ const eig = symmetricEigen(A, m);
501
+ const outVec = new Float64Array(n * m);
502
+ for (let c = 0; c < m; c++) for (let a = 0; a < m; a++) {
503
+ const w = eig.vectors[c * m + a];
504
+ if (w === 0) continue;
505
+ for (let r = 0; r < n; r++) outVec[c * n + r] = outVec[c * n + r] + w * X[a * n + r];
506
+ }
507
+ const LV = await applyLAsync(outVec, m, LXbuf);
508
+ for (let c = 0; c < m; c++) {
509
+ let rn = 0;
510
+ for (let r = 0; r < n; r++) {
511
+ const v = LV[c * n + r] - eig.values[c] * outVec[c * n + r];
512
+ rn += v * v;
513
+ }
514
+ residuals[c] = Math.sqrt(rn);
515
+ }
516
+ return {
517
+ values: eig.values,
518
+ vectors: outVec,
519
+ converged,
520
+ iterations: iter,
521
+ residuals
522
+ };
523
+ }
524
+ /** Degree vector d_i = Σ_j A_ij and its inverse sqrt. */
525
+ function degreeInvSqrt(graph) {
526
+ const n = graph.rows;
527
+ const deg = new Float64Array(n);
528
+ for (let i = 0; i < n; i++) for (let p = graph.indptr[i]; p < graph.indptr[i + 1]; p++) deg[i] = deg[i] + graph.data[p];
529
+ const dinvsqrt = new Float64Array(n);
530
+ for (let i = 0; i < n; i++) dinvsqrt[i] = deg[i] > 0 ? 1 / Math.sqrt(deg[i]) : 0;
531
+ return {
532
+ deg,
533
+ dinvsqrt
534
+ };
535
+ }
536
+ /** Single-component spectral layout: k+1 smallest eigenvectors, drop trivial. */
537
+ function spectralLayoutSingle(graph, dim, rng, logger) {
538
+ const n = graph.rows;
539
+ const m = dim + 1;
540
+ const { deg, dinvsqrt } = degreeInvSqrt(graph);
541
+ const X0 = new Float64Array(n * m);
542
+ for (let c = 0; c < m; c++) for (let r = 0; r < n; r++) X0[c * n + r] = rng.normal();
543
+ let norm = 0;
544
+ for (let r = 0; r < n; r++) norm += deg[r];
545
+ norm = Math.sqrt(norm);
546
+ for (let r = 0; r < n; r++) X0[r] = Math.sqrt(deg[r]) / norm;
547
+ const res = lobpcgSmallest(graph, dinvsqrt, X0, m, 1e-4, 5 * Math.min(n, 60));
548
+ if (!res.converged) {
549
+ logger.warn("UmapConvergenceWarning: Spectral initialisation failed! The eigenvector solver failed. This is likely due to too small an eigengap. Consider adding some noise or jitter to your data. Falling back to random initialisation!");
550
+ const emb = new Float64Array(n * dim);
551
+ for (let i = 0; i < emb.length; i++) emb[i] = rng.uniform(-10, 10);
552
+ return {
553
+ embedding: emb,
554
+ usedFallback: true
555
+ };
556
+ }
557
+ const embedding = new Float64Array(n * dim);
558
+ for (let d = 0; d < dim; d++) {
559
+ const c = d + 1;
560
+ for (let r = 0; r < n; r++) embedding[r * dim + d] = res.vectors[c * n + r];
561
+ }
562
+ return {
563
+ embedding,
564
+ usedFallback: false
565
+ };
566
+ }
567
+ /** exp(−d²) affinity spectral embedding of component centroids (spectral.py:18-142). */
568
+ function componentLayout(data, nComponents, labels, dim, rng) {
569
+ const meta = new Float64Array(nComponents * dim);
570
+ if (!data) {
571
+ for (let i = 0; i < meta.length; i++) meta[i] = rng.nextFloat() * 10;
572
+ return meta;
573
+ }
574
+ const d = data.cols;
575
+ const centroids = new Float64Array(nComponents * d);
576
+ const counts = new Int32Array(nComponents);
577
+ for (let i = 0; i < data.rows; i++) {
578
+ const c = labels[i];
579
+ counts[c]++;
580
+ for (let j = 0; j < d; j++) centroids[c * d + j] = centroids[c * d + j] + data.data[i * d + j];
581
+ }
582
+ for (let c = 0; c < nComponents; c++) for (let j = 0; j < d; j++) centroids[c * d + j] = centroids[c * d + j] / counts[c];
583
+ const aff = new Float64Array(nComponents * nComponents);
584
+ for (let a = 0; a < nComponents; a++) for (let b = 0; b < nComponents; b++) {
585
+ let s = 0;
586
+ for (let j = 0; j < d; j++) {
587
+ const diff = centroids[a * d + j] - centroids[b * d + j];
588
+ s += diff * diff;
589
+ }
590
+ aff[a * nComponents + b] = Math.exp(-s);
591
+ }
592
+ const n = nComponents;
593
+ const deg = new Float64Array(n);
594
+ for (let a = 0; a < n; a++) for (let b = 0; b < n; b++) deg[a] = deg[a] + aff[a * n + b];
595
+ const L = new Float64Array(n * n);
596
+ for (let a = 0; a < n; a++) for (let b = 0; b < n; b++) {
597
+ const norm = Math.sqrt(deg[a] * deg[b]);
598
+ L[a * n + b] = (a === b ? 1 : 0) - (norm > 0 ? aff[a * n + b] / norm : 0);
599
+ }
600
+ const eig = symmetricEigen(L, n);
601
+ let mx = 0;
602
+ for (let c = 0; c < dim; c++) {
603
+ const col = Math.min(c + 1, n - 1);
604
+ for (let a = 0; a < n; a++) {
605
+ const v = eig.vectors[col * n + a] / Math.sqrt(deg[a]);
606
+ meta[a * dim + c] = v;
607
+ if (Math.abs(v) > mx) mx = Math.abs(v);
608
+ }
609
+ }
610
+ let realMax = -Infinity;
611
+ for (let i = 0; i < meta.length; i++) if (meta[i] > realMax) realMax = meta[i];
612
+ if (realMax !== 0 && Number.isFinite(realMax)) for (let i = 0; i < meta.length; i++) meta[i] = meta[i] / realMax;
613
+ return meta;
614
+ }
615
+ /** multi_component_layout (spectral.py:145-260). */
616
+ function multiComponentLayout(data, graph, nComponents, labels, dim, rng, logger) {
617
+ const n = graph.rows;
618
+ const result = new Float64Array(n * dim);
619
+ let meta;
620
+ if (nComponents > 2 * dim) meta = componentLayout(data, nComponents, labels, dim, rng);
621
+ else {
622
+ const k = Math.ceil(nComponents / 2);
623
+ meta = new Float64Array(nComponents * dim);
624
+ for (let c = 0; c < nComponents; c++) if (c < k) {
625
+ if (c < dim) meta[c * dim + c] = 1;
626
+ } else {
627
+ const idx = c - k;
628
+ if (idx < dim) meta[c * dim + idx] = -1;
629
+ }
630
+ }
631
+ let anyFallback = false;
632
+ for (let label = 0; label < nComponents; label++) {
633
+ const verts = [];
634
+ for (let i = 0; i < n; i++) if (labels[i] === label) verts.push(i);
635
+ const size = verts.length;
636
+ let minDist = Infinity;
637
+ for (let other = 0; other < nComponents; other++) {
638
+ if (other === label) continue;
639
+ let s = 0;
640
+ for (let c = 0; c < dim; c++) {
641
+ const diff = meta[label * dim + c] - meta[other * dim + c];
642
+ s += diff * diff;
643
+ }
644
+ const dd = Math.sqrt(s);
645
+ if (dd > 0 && dd < minDist) minDist = dd;
646
+ }
647
+ const dataRange = (Number.isFinite(minDist) ? minDist : 1) / 2;
648
+ if (size < 2 * dim || size <= dim + 1) {
649
+ for (const [vi, v] of verts.entries()) for (let c = 0; c < dim; c++) result[v * dim + c] = rng.uniform(-dataRange, dataRange) + meta[label * dim + c];
650
+ continue;
651
+ }
652
+ const remap = new Int32Array(n).fill(-1);
653
+ verts.forEach((v, i) => {
654
+ remap[v] = i;
655
+ });
656
+ const subIndptr = new Int32Array(size + 1);
657
+ let nnz = 0;
658
+ for (const v of verts) for (let p = graph.indptr[v]; p < graph.indptr[v + 1]; p++) if (remap[graph.indices[p]] >= 0) nnz++;
659
+ const subIndices = new Int32Array(nnz);
660
+ const subData = new Float64Array(nnz);
661
+ let w = 0;
662
+ verts.forEach((v, i) => {
663
+ for (let p = graph.indptr[v]; p < graph.indptr[v + 1]; p++) {
664
+ const t = remap[graph.indices[p]];
665
+ if (t >= 0) {
666
+ subIndices[w] = t;
667
+ subData[w] = graph.data[p];
668
+ w++;
669
+ }
670
+ }
671
+ subIndptr[i + 1] = w;
672
+ });
673
+ const compRes = spectralLayoutSingle({
674
+ indptr: subIndptr,
675
+ indices: subIndices,
676
+ data: subData,
677
+ rows: size,
678
+ cols: size
679
+ }, dim, rng, logger);
680
+ anyFallback ||= compRes.usedFallback;
681
+ let mx = 0;
682
+ for (let i = 0; i < compRes.embedding.length; i++) {
683
+ const v = Math.abs(compRes.embedding[i]);
684
+ if (v > mx) mx = v;
685
+ }
686
+ const expansion = mx > 0 ? dataRange / mx : 1;
687
+ verts.forEach((v, i) => {
688
+ for (let c = 0; c < dim; c++) result[v * dim + c] = compRes.embedding[i * dim + c] * expansion + meta[label * dim + c];
689
+ });
690
+ }
691
+ return {
692
+ embedding: result,
693
+ usedFallback: anyFallback
694
+ };
695
+ }
696
+ /**
697
+ * spectral_layout: normalized-Laplacian eigenmaps of the fuzzy graph with
698
+ * multi-component handling. Returns n×dim float64 (caller scales/noises/casts).
699
+ */
700
+ function spectralLayout(data, graph, dim, rng, logger = silentLogger) {
701
+ const { count, labels } = connectedComponents(graph);
702
+ if (count > 1) return multiComponentLayout(data, graph, count, labels, dim, rng, logger);
703
+ return spectralLayoutSingle(graph, dim, rng, logger);
704
+ }
705
+
706
+ //#endregion
707
+ //#region src/init/index.ts
708
+ /** noisy_scale_coords (umap_.py:928-935): scale abs-max to maxCoord, add N(0, noise). */
709
+ function noisyScaleCoords(coords, rng, maxCoord = 10, noise = 1e-4) {
710
+ let mx = 0;
711
+ for (let i = 0; i < coords.length; i++) {
712
+ const v = Math.abs(coords[i]);
713
+ if (v > mx) mx = v;
714
+ }
715
+ const expansion = mx > 0 ? maxCoord / mx : 1;
716
+ for (let i = 0; i < coords.length; i++) coords[i] = Math.fround(coords[i] * expansion) + rng.normal() * noise;
717
+ }
718
+ /** Final rescale applied to EVERY init (umap_.py:1188-1192): per-dim min-max → [0,10]. */
719
+ function finalInitRescale(embedding, n, dim) {
720
+ const out = new Float32Array(n * dim);
721
+ for (let c = 0; c < dim; c++) {
722
+ let mn = Infinity;
723
+ let mx = -Infinity;
724
+ for (let i = 0; i < n; i++) {
725
+ const v = embedding[i * dim + c];
726
+ if (v < mn) mn = v;
727
+ if (v > mx) mx = v;
728
+ }
729
+ const range = mx - mn;
730
+ for (let i = 0; i < n; i++) out[i * dim + c] = range > 0 ? 10 * (embedding[i * dim + c] - mn) / range : 0;
731
+ }
732
+ return out;
733
+ }
734
+ /**
735
+ * Produce the initial embedding for `graph` (pruned fuzzy graph) and `data`.
736
+ * Data may be null when init doesn't need it (random / spectral without
737
+ * multi-component meta layout data).
738
+ */
739
+ function initializeEmbedding(init, data, graph, dim, rng, { logger = silentLogger } = {}) {
740
+ const n = graph.rows;
741
+ let emb;
742
+ let spectralFallback = false;
743
+ if (typeof init === "object") {
744
+ const e = init;
745
+ if (e.rows !== n || e.cols !== dim) throw new UmapValidationError(`init embedding must be ${n}x${dim}, got ${e.rows}x${e.cols}`);
746
+ emb = new Float64Array(n * dim);
747
+ const seen = /* @__PURE__ */ new Set();
748
+ let hasDup = false;
749
+ for (let i = 0; i < n; i++) {
750
+ const key = Array.from(e.data.subarray(i * dim, (i + 1) * dim)).join(",");
751
+ if (seen.has(key)) {
752
+ hasDup = true;
753
+ break;
754
+ }
755
+ seen.add(key);
756
+ }
757
+ for (let i = 0; i < e.data.length; i++) emb[i] = e.data[i];
758
+ if (hasDup) {
759
+ const sample = Math.min(n, 512);
760
+ let acc = 0;
761
+ let cnt = 0;
762
+ for (let i = 0; i < sample; i++) {
763
+ let best = Infinity;
764
+ for (let j = 0; j < n; j++) {
765
+ if (j === i) continue;
766
+ let s = 0;
767
+ for (let c = 0; c < dim; c++) {
768
+ const d = emb[i * dim + c] - emb[j * dim + c];
769
+ s += d * d;
770
+ }
771
+ if (s > 0 && s < best) best = s;
772
+ }
773
+ if (Number.isFinite(best)) {
774
+ acc += Math.sqrt(best);
775
+ cnt++;
776
+ }
777
+ }
778
+ const nndist = cnt > 0 ? acc / cnt : .001;
779
+ for (let i = 0; i < emb.length; i++) emb[i] = emb[i] + rng.normal() * .001 * nndist;
780
+ }
781
+ } else if (init === "random") {
782
+ emb = new Float64Array(n * dim);
783
+ for (let i = 0; i < emb.length; i++) emb[i] = rng.uniform(-10, 10);
784
+ } else if (init === "pca") {
785
+ if (!data) throw new UmapValidationError("init 'pca' requires dense data");
786
+ emb = pcaScores(data, dim, rng);
787
+ noisyScaleCoords(emb, rng, 10, 1e-4);
788
+ } else if (init === "spectral") {
789
+ const res = spectralLayout(data, graph, dim, rng, logger);
790
+ emb = res.embedding;
791
+ spectralFallback = res.usedFallback;
792
+ if (!res.usedFallback) noisyScaleCoords(emb, rng, 10, 1e-4);
793
+ } else throw new UmapValidationError(`unknown init '${String(init)}'`);
794
+ return {
795
+ embedding: finalInitRescale(emb, n, dim),
796
+ spectralFallback
797
+ };
798
+ }
799
+
800
+ //#endregion
801
+ export { silentLogger as _, degreeInvSqrt as a, spectralLayout as c, symmetricEigen as d, UmapBackendError as f, consoleLogger as g, UmapValidationError as h, connectedComponents as i, pcaScores as l, UmapError as m, initializeEmbedding as n, lobpcgSmallest as o, UmapConvergenceWarning as p, noisyScaleCoords as r, lobpcgSmallestAsync as s, finalInitRescale as t, mgsOrthonormalize as u, throwIfAborted as v };
802
+ //# sourceMappingURL=init-DLiEtGpx.js.map