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.
package/dist/worker.js ADDED
@@ -0,0 +1,1095 @@
1
+ //#region src/knn/heap.ts
2
+ function makeHeap(n, size) {
3
+ return {
4
+ indices: new Int32Array(n * size).fill(-1),
5
+ distances: new Float32Array(n * size).fill(Infinity),
6
+ flags: new Uint8Array(n * size),
7
+ n,
8
+ size
9
+ };
10
+ }
11
+ /**
12
+ * checked_flagged_heap_push (utils.py:471-533). Operates on one row given by base
13
+ * offset. Rejects p >= root (ties dropped) and duplicates. Returns 1 if pushed.
14
+ */
15
+ function checkedFlaggedHeapPush(heap, row, p, n, f) {
16
+ const size = heap.size;
17
+ const base = row * size;
18
+ const priorities = heap.distances;
19
+ const indices = heap.indices;
20
+ const flags = heap.flags;
21
+ if (p >= priorities[base]) return 0;
22
+ for (let i$1 = 0; i$1 < size; i$1++) if (indices[base + i$1] === n) return 0;
23
+ let i = 0;
24
+ for (;;) {
25
+ const ic1 = 2 * i + 1;
26
+ const ic2 = ic1 + 1;
27
+ let iSwap;
28
+ if (ic1 >= size) break;
29
+ if (ic2 >= size) if (priorities[base + ic1] > p) iSwap = ic1;
30
+ else break;
31
+ else if (priorities[base + ic1] >= priorities[base + ic2]) if (p < priorities[base + ic1]) iSwap = ic1;
32
+ else break;
33
+ else if (p < priorities[base + ic2]) iSwap = ic2;
34
+ else break;
35
+ priorities[base + i] = priorities[base + iSwap];
36
+ indices[base + i] = indices[base + iSwap];
37
+ flags[base + i] = flags[base + iSwap];
38
+ i = iSwap;
39
+ }
40
+ priorities[base + i] = p;
41
+ indices[base + i] = n;
42
+ flags[base + i] = f;
43
+ return 1;
44
+ }
45
+
46
+ //#endregion
47
+ //#region src/types.ts
48
+ var UmapError = class extends Error {
49
+ name = "UmapError";
50
+ };
51
+ var UmapValidationError = class extends UmapError {
52
+ name = "UmapValidationError";
53
+ };
54
+
55
+ //#endregion
56
+ //#region src/metrics.ts
57
+ function euclidean(x, y) {
58
+ let result = 0;
59
+ for (let i = 0; i < x.length; i++) {
60
+ const d = x[i] - y[i];
61
+ result += d * d;
62
+ }
63
+ return Math.sqrt(result);
64
+ }
65
+ /** Squared euclidean — used internally by the optimizer (rdist). */
66
+ function sqeuclidean(x, y) {
67
+ let result = 0;
68
+ for (let i = 0; i < x.length; i++) {
69
+ const d = x[i] - y[i];
70
+ result += d * d;
71
+ }
72
+ return result;
73
+ }
74
+ function standardisedEuclidean(x, y, sigma) {
75
+ let result = 0;
76
+ for (let i = 0; i < x.length; i++) {
77
+ const d = x[i] - y[i];
78
+ result += d * d / sigma[i];
79
+ }
80
+ return Math.sqrt(result);
81
+ }
82
+ function manhattan(x, y) {
83
+ let result = 0;
84
+ for (let i = 0; i < x.length; i++) result += Math.abs(x[i] - y[i]);
85
+ return result;
86
+ }
87
+ function chebyshev(x, y) {
88
+ let result = 0;
89
+ for (let i = 0; i < x.length; i++) {
90
+ const v = Math.abs(x[i] - y[i]);
91
+ if (v > result) result = v;
92
+ }
93
+ return result;
94
+ }
95
+ function minkowski(x, y, p) {
96
+ let result = 0;
97
+ for (let i = 0; i < x.length; i++) result += Math.abs(x[i] - y[i]) ** p;
98
+ return result ** (1 / p);
99
+ }
100
+ function weightedMinkowski(x, y, w, p) {
101
+ let result = 0;
102
+ for (let i = 0; i < x.length; i++) result += w[i] * Math.abs(x[i] - y[i]) ** p;
103
+ return result ** (1 / p);
104
+ }
105
+ function mahalanobis(x, y, vinv) {
106
+ const n = x.length;
107
+ let result = 0;
108
+ const diff = new Float64Array(n);
109
+ for (let i = 0; i < n; i++) diff[i] = x[i] - y[i];
110
+ for (let i = 0; i < n; i++) {
111
+ let tmp = 0;
112
+ for (let j = 0; j < n; j++) tmp += vinv[i * n + j] * diff[j];
113
+ result += tmp * diff[i];
114
+ }
115
+ return Math.sqrt(result);
116
+ }
117
+ function hamming(x, y) {
118
+ let result = 0;
119
+ for (let i = 0; i < x.length; i++) if (x[i] !== y[i]) result += 1;
120
+ return result / x.length;
121
+ }
122
+ function canberra(x, y) {
123
+ let result = 0;
124
+ for (let i = 0; i < x.length; i++) {
125
+ const denominator = Math.abs(x[i]) + Math.abs(y[i]);
126
+ if (denominator > 0) result += Math.abs(x[i] - y[i]) / denominator;
127
+ }
128
+ return result;
129
+ }
130
+ function brayCurtis(x, y) {
131
+ let numerator = 0;
132
+ let denominator = 0;
133
+ for (let i = 0; i < x.length; i++) {
134
+ numerator += Math.abs(x[i] - y[i]);
135
+ denominator += Math.abs(x[i] + y[i]);
136
+ }
137
+ return denominator > 0 ? numerator / denominator : 0;
138
+ }
139
+ function jaccard(x, y) {
140
+ let numNonZero = 0;
141
+ let numEqual = 0;
142
+ for (let i = 0; i < x.length; i++) {
143
+ const xt = x[i] !== 0;
144
+ const yt = y[i] !== 0;
145
+ numNonZero += xt || yt ? 1 : 0;
146
+ numEqual += xt && yt ? 1 : 0;
147
+ }
148
+ return numNonZero === 0 ? 0 : (numNonZero - numEqual) / numNonZero;
149
+ }
150
+ function matching(x, y) {
151
+ let numNotEqual = 0;
152
+ for (let i = 0; i < x.length; i++) if (x[i] !== 0 !== (y[i] !== 0)) numNotEqual += 1;
153
+ return numNotEqual / x.length;
154
+ }
155
+ function dice(x, y) {
156
+ let numTrueTrue = 0;
157
+ let numNotEqual = 0;
158
+ for (let i = 0; i < x.length; i++) {
159
+ const xt = x[i] !== 0;
160
+ const yt = y[i] !== 0;
161
+ numTrueTrue += xt && yt ? 1 : 0;
162
+ numNotEqual += xt !== yt ? 1 : 0;
163
+ }
164
+ return numNotEqual === 0 ? 0 : numNotEqual / (2 * numTrueTrue + numNotEqual);
165
+ }
166
+ function kulsinski(x, y) {
167
+ let numTrueTrue = 0;
168
+ let numNotEqual = 0;
169
+ for (let i = 0; i < x.length; i++) {
170
+ const xt = x[i] !== 0;
171
+ const yt = y[i] !== 0;
172
+ numTrueTrue += xt && yt ? 1 : 0;
173
+ numNotEqual += xt !== yt ? 1 : 0;
174
+ }
175
+ return numNotEqual === 0 ? 0 : (numNotEqual - numTrueTrue + x.length) / (numNotEqual + x.length);
176
+ }
177
+ function rogersTanimoto(x, y) {
178
+ let numNotEqual = 0;
179
+ for (let i = 0; i < x.length; i++) if (x[i] !== 0 !== (y[i] !== 0)) numNotEqual += 1;
180
+ return 2 * numNotEqual / (x.length + numNotEqual);
181
+ }
182
+ function russellrao(x, y) {
183
+ let numTrueTrue = 0;
184
+ let xNonZero = 0;
185
+ let yNonZero = 0;
186
+ for (let i = 0; i < x.length; i++) {
187
+ const xt = x[i] !== 0;
188
+ const yt = y[i] !== 0;
189
+ numTrueTrue += xt && yt ? 1 : 0;
190
+ xNonZero += xt ? 1 : 0;
191
+ yNonZero += yt ? 1 : 0;
192
+ }
193
+ if (numTrueTrue === xNonZero && numTrueTrue === yNonZero) return 0;
194
+ return (x.length - numTrueTrue) / x.length;
195
+ }
196
+ function sokalMichener(x, y) {
197
+ return rogersTanimoto(x, y);
198
+ }
199
+ function sokalSneath(x, y) {
200
+ let numTrueTrue = 0;
201
+ let numNotEqual = 0;
202
+ for (let i = 0; i < x.length; i++) {
203
+ const xt = x[i] !== 0;
204
+ const yt = y[i] !== 0;
205
+ numTrueTrue += xt && yt ? 1 : 0;
206
+ numNotEqual += xt !== yt ? 1 : 0;
207
+ }
208
+ return numNotEqual === 0 ? 0 : numNotEqual / (.5 * numTrueTrue + numNotEqual);
209
+ }
210
+ function yule(x, y) {
211
+ let tt = 0;
212
+ let tf = 0;
213
+ let ft = 0;
214
+ for (let i = 0; i < x.length; i++) {
215
+ const xt = x[i] !== 0;
216
+ const yt = y[i] !== 0;
217
+ tt += xt && yt ? 1 : 0;
218
+ tf += xt && !yt ? 1 : 0;
219
+ ft += !xt && yt ? 1 : 0;
220
+ }
221
+ const ff = x.length - tt - tf - ft;
222
+ if (tf === 0 || ft === 0) return 0;
223
+ return 2 * tf * ft / (tt * ff + tf * ft);
224
+ }
225
+ function cosine(x, y) {
226
+ let result = 0;
227
+ let normX = 0;
228
+ let normY = 0;
229
+ for (let i = 0; i < x.length; i++) {
230
+ result += x[i] * y[i];
231
+ normX += x[i] * x[i];
232
+ normY += y[i] * y[i];
233
+ }
234
+ if (normX === 0 && normY === 0) return 0;
235
+ if (normX === 0 || normY === 0) return 1;
236
+ return 1 - result / Math.sqrt(normX * normY);
237
+ }
238
+ function correlation(x, y) {
239
+ let muX = 0;
240
+ let muY = 0;
241
+ let normX = 0;
242
+ let normY = 0;
243
+ let dot = 0;
244
+ const n = x.length;
245
+ for (let i = 0; i < n; i++) {
246
+ muX += x[i];
247
+ muY += y[i];
248
+ }
249
+ muX /= n;
250
+ muY /= n;
251
+ for (let i = 0; i < n; i++) {
252
+ const sx = x[i] - muX;
253
+ const sy = y[i] - muY;
254
+ normX += sx * sx;
255
+ normY += sy * sy;
256
+ dot += sx * sy;
257
+ }
258
+ if (normX === 0 && normY === 0) return 0;
259
+ if (dot === 0) return 1;
260
+ return 1 - dot / Math.sqrt(normX * normY);
261
+ }
262
+ function hellinger(x, y) {
263
+ let result = 0;
264
+ let l1x = 0;
265
+ let l1y = 0;
266
+ for (let i = 0; i < x.length; i++) {
267
+ result += Math.sqrt(x[i] * y[i]);
268
+ l1x += x[i];
269
+ l1y += y[i];
270
+ }
271
+ if (l1x === 0 && l1y === 0) return 0;
272
+ if (l1x === 0 || l1y === 0) return 1;
273
+ return Math.sqrt(1 - result / Math.sqrt(l1x * l1y));
274
+ }
275
+ function approxLogGamma(x) {
276
+ if (x === 1) return 0;
277
+ return x * Math.log(x) - x + .5 * Math.log(2 * Math.PI / x) + 1 / (x * 12);
278
+ }
279
+ function logBeta(x, y) {
280
+ const a = Math.min(x, y);
281
+ const b = Math.max(x, y);
282
+ if (b < 5) {
283
+ let value = -Math.log(b);
284
+ for (let i = 1; i < Math.trunc(a); i++) value += Math.log(i) - Math.log(b + i);
285
+ return value;
286
+ }
287
+ return approxLogGamma(x) + approxLogGamma(y) - approxLogGamma(x + y);
288
+ }
289
+ function logSingleBeta(x) {
290
+ return Math.log(2) * (-2 * x + .5) + .5 * Math.log(2 * Math.PI / x) + .125 / x;
291
+ }
292
+ function llDirichlet(data1, data2) {
293
+ let n1 = 0;
294
+ let n2 = 0;
295
+ for (let i = 0; i < data1.length; i++) n1 += data1[i];
296
+ for (let i = 0; i < data2.length; i++) n2 += data2[i];
297
+ let logB = 0;
298
+ let selfDenom1 = 0;
299
+ let selfDenom2 = 0;
300
+ for (let i = 0; i < data1.length; i++) {
301
+ const d1 = data1[i];
302
+ const d2 = data2[i];
303
+ if (d1 * d2 > .9) {
304
+ logB += logBeta(d1, d2);
305
+ selfDenom1 += logSingleBeta(d1);
306
+ selfDenom2 += logSingleBeta(d2);
307
+ } else {
308
+ if (d1 > .9) selfDenom1 += logSingleBeta(d1);
309
+ if (d2 > .9) selfDenom2 += logSingleBeta(d2);
310
+ }
311
+ }
312
+ return Math.sqrt(1 / n2 * (logB - logBeta(n1, n2) - (selfDenom2 - logSingleBeta(n2))) + 1 / n1 * (logB - logBeta(n2, n1) - (selfDenom1 - logSingleBeta(n1))));
313
+ }
314
+ /**
315
+ * Symmetrized KL divergence. NOTE: the Python source mutates its inputs (adds z,
316
+ * normalizes in place); we copy to preserve the same numerics without side effects.
317
+ */
318
+ function symmetricKl(x, y, z = 1e-11) {
319
+ const n = x.length;
320
+ let xSum = 0;
321
+ let ySum = 0;
322
+ const xs = new Float64Array(n);
323
+ const ys = new Float64Array(n);
324
+ for (let i = 0; i < n; i++) {
325
+ xs[i] = x[i] + z;
326
+ xSum += xs[i];
327
+ ys[i] = y[i] + z;
328
+ ySum += ys[i];
329
+ }
330
+ for (let i = 0; i < n; i++) {
331
+ xs[i] = xs[i] / xSum;
332
+ ys[i] = ys[i] / ySum;
333
+ }
334
+ let kl1 = 0;
335
+ let kl2 = 0;
336
+ for (let i = 0; i < n; i++) {
337
+ kl1 += xs[i] * Math.log(xs[i] / ys[i]);
338
+ kl2 += ys[i] * Math.log(ys[i] / xs[i]);
339
+ }
340
+ return (kl1 + kl2) / 2;
341
+ }
342
+ function haversine(x, y) {
343
+ if (x.length !== 2) throw new UmapValidationError("haversine is only defined for 2 dimensional data");
344
+ const sinLat = Math.sin(.5 * (x[0] - y[0]));
345
+ const sinLong = Math.sin(.5 * (x[1] - y[1]));
346
+ const result = Math.sqrt(sinLat * sinLat + Math.cos(x[0]) * Math.cos(y[0]) * sinLong * sinLong);
347
+ return 2 * Math.asin(result);
348
+ }
349
+ function simple(fn, canonical) {
350
+ return {
351
+ canonical,
352
+ resolve: () => fn
353
+ };
354
+ }
355
+ const SPECS = {
356
+ euclidean: simple(euclidean, "euclidean"),
357
+ l2: simple(euclidean, "euclidean"),
358
+ sqeuclidean: simple(sqeuclidean, "sqeuclidean"),
359
+ manhattan: simple(manhattan, "manhattan"),
360
+ taxicab: simple(manhattan, "manhattan"),
361
+ l1: simple(manhattan, "manhattan"),
362
+ chebyshev: simple(chebyshev, "chebyshev"),
363
+ linfinity: simple(chebyshev, "chebyshev"),
364
+ linfty: simple(chebyshev, "chebyshev"),
365
+ linf: simple(chebyshev, "chebyshev"),
366
+ minkowski: {
367
+ canonical: "minkowski",
368
+ needs: ["p"],
369
+ resolve: (o) => {
370
+ const p = o.p ?? 2;
371
+ return (x, y) => minkowski(x, y, p);
372
+ }
373
+ },
374
+ seuclidean: {
375
+ canonical: "seuclidean",
376
+ needs: ["V"],
377
+ resolve: (o) => {
378
+ if (!o.V) throw new UmapValidationError("metric 'seuclidean' requires metricOptions.V");
379
+ const V = o.V;
380
+ return (x, y) => standardisedEuclidean(x, y, V);
381
+ }
382
+ },
383
+ standardised_euclidean: {
384
+ canonical: "seuclidean",
385
+ needs: ["V"],
386
+ resolve: (o) => {
387
+ if (!o.V) throw new UmapValidationError("metric 'standardised_euclidean' requires metricOptions.V");
388
+ const V = o.V;
389
+ return (x, y) => standardisedEuclidean(x, y, V);
390
+ }
391
+ },
392
+ wminkowski: {
393
+ canonical: "wminkowski",
394
+ needs: ["w", "p"],
395
+ resolve: (o) => {
396
+ if (!o.w) throw new UmapValidationError("metric 'wminkowski' requires metricOptions.w");
397
+ const w = o.w;
398
+ const p = o.p ?? 2;
399
+ return (x, y) => weightedMinkowski(x, y, w, p);
400
+ }
401
+ },
402
+ weighted_minkowski: {
403
+ canonical: "wminkowski",
404
+ needs: ["w", "p"],
405
+ resolve: (o) => {
406
+ if (!o.w) throw new UmapValidationError("metric 'weighted_minkowski' requires metricOptions.w");
407
+ const w = o.w;
408
+ const p = o.p ?? 2;
409
+ return (x, y) => weightedMinkowski(x, y, w, p);
410
+ }
411
+ },
412
+ mahalanobis: {
413
+ canonical: "mahalanobis",
414
+ needs: ["VInv"],
415
+ resolve: (o) => {
416
+ if (!o.VInv) throw new UmapValidationError("metric 'mahalanobis' requires metricOptions.VInv");
417
+ const VInv = o.VInv;
418
+ return (x, y) => mahalanobis(x, y, VInv);
419
+ }
420
+ },
421
+ canberra: simple(canberra, "canberra"),
422
+ braycurtis: simple(brayCurtis, "braycurtis"),
423
+ cosine: simple(cosine, "cosine"),
424
+ correlation: simple(correlation, "correlation"),
425
+ haversine: simple(haversine, "haversine"),
426
+ hamming: simple(hamming, "hamming"),
427
+ jaccard: simple(jaccard, "jaccard"),
428
+ dice: simple(dice, "dice"),
429
+ matching: simple(matching, "matching"),
430
+ kulsinski: simple(kulsinski, "kulsinski"),
431
+ rogerstanimoto: simple(rogersTanimoto, "rogerstanimoto"),
432
+ russellrao: simple(russellrao, "russellrao"),
433
+ sokalmichener: simple(sokalMichener, "sokalmichener"),
434
+ sokalsneath: simple(sokalSneath, "sokalsneath"),
435
+ yule: simple(yule, "yule"),
436
+ hellinger: simple(hellinger, "hellinger"),
437
+ ll_dirichlet: simple(llDirichlet, "ll_dirichlet"),
438
+ symmetric_kl: simple(symmetricKl, "symmetric_kl")
439
+ };
440
+ const NAMED_METRICS = Object.keys(SPECS);
441
+ function resolveMetric(name, options = {}) {
442
+ const spec = SPECS[name];
443
+ if (!spec) throw new UmapValidationError(`unknown metric '${name}' — named metrics: ${NAMED_METRICS.join(", ")}`);
444
+ return spec.resolve(options);
445
+ }
446
+
447
+ //#endregion
448
+ //#region src/knn/internal-metrics.ts
449
+ const FLOAT32_MAX = 34028234663852886e22;
450
+ function squaredEuclidean(x, y) {
451
+ let r = 0;
452
+ const n = x.length;
453
+ let i = 0;
454
+ const n4 = n - n % 4;
455
+ for (; i < n4; i += 4) {
456
+ const d0 = x[i] - y[i];
457
+ const d1 = x[i + 1] - y[i + 1];
458
+ const d2 = x[i + 2] - y[i + 2];
459
+ const d3 = x[i + 3] - y[i + 3];
460
+ r += d0 * d0 + d1 * d1 + d2 * d2 + d3 * d3;
461
+ }
462
+ for (; i < n; i++) {
463
+ const d = x[i] - y[i];
464
+ r += d * d;
465
+ }
466
+ return r;
467
+ }
468
+ /** alternative_cosine (distances.py:583-630). */
469
+ function alternativeCosine(x, y) {
470
+ let dot = 0;
471
+ let nx = 0;
472
+ let ny = 0;
473
+ for (let i = 0; i < x.length; i++) {
474
+ dot += x[i] * y[i];
475
+ nx += x[i] * x[i];
476
+ ny += y[i] * y[i];
477
+ }
478
+ if (nx === 0 && ny === 0) return 0;
479
+ if (nx === 0 || ny === 0) return FLOAT32_MAX;
480
+ if (dot <= 0) return FLOAT32_MAX;
481
+ return Math.log2(Math.sqrt(nx * ny) / dot);
482
+ }
483
+ /** alternative_hellinger (distances.py:1387-1416). */
484
+ function alternativeHellinger(x, y) {
485
+ let result = 0;
486
+ let l1x = 0;
487
+ let l1y = 0;
488
+ for (let i = 0; i < x.length; i++) {
489
+ result += Math.sqrt(x[i] * y[i]);
490
+ l1x += x[i];
491
+ l1y += y[i];
492
+ }
493
+ if (l1x === 0 && l1y === 0) return 0;
494
+ if (l1x === 0 || l1y === 0) return FLOAT32_MAX;
495
+ if (result <= 0) return FLOAT32_MAX;
496
+ return Math.log2(Math.sqrt(l1x * l1y) / result);
497
+ }
498
+ /** alternative_jaccard (distances.py:259-336 family). */
499
+ function alternativeJaccard(x, y) {
500
+ let numNonZero = 0;
501
+ let numEqual = 0;
502
+ for (let i = 0; i < x.length; i++) {
503
+ const xt = x[i] !== 0;
504
+ const yt = y[i] !== 0;
505
+ numNonZero += xt || yt ? 1 : 0;
506
+ numEqual += xt && yt ? 1 : 0;
507
+ }
508
+ if (numNonZero === 0) return 0;
509
+ if (numEqual === 0) return FLOAT32_MAX;
510
+ return -Math.log2(numEqual / numNonZero);
511
+ }
512
+ const identity = (d) => d;
513
+ /** Metric names that switch NNDescent to angular trees (pynndescent_.py:1059-1077). */
514
+ const ANGULAR_TREE_METRICS = new Set([
515
+ "cosine",
516
+ "dot",
517
+ "correlation",
518
+ "dice",
519
+ "jaccard",
520
+ "hellinger",
521
+ "hamming"
522
+ ]);
523
+ /**
524
+ * Resolve the internal metric + correction for NN-descent
525
+ * (fast_distance_alternatives, distances.py:2170-2188).
526
+ */
527
+ function resolveInternalMetric(name, options) {
528
+ const angularTrees = ANGULAR_TREE_METRICS.has(name);
529
+ switch (name) {
530
+ case "euclidean":
531
+ case "l2": return {
532
+ dist: squaredEuclidean,
533
+ correction: (d) => Math.sqrt(d),
534
+ angularTrees
535
+ };
536
+ case "cosine": return {
537
+ dist: alternativeCosine,
538
+ correction: (d) => 1 - 2 ** -d,
539
+ angularTrees
540
+ };
541
+ case "hellinger": return {
542
+ dist: alternativeHellinger,
543
+ correction: (d) => Math.sqrt(1 - 2 ** -d),
544
+ angularTrees
545
+ };
546
+ case "jaccard": return {
547
+ dist: alternativeJaccard,
548
+ correction: (d) => 1 - 2 ** -d,
549
+ angularTrees
550
+ };
551
+ default: return {
552
+ dist: resolveMetric(name, options),
553
+ correction: identity,
554
+ angularTrees
555
+ };
556
+ }
557
+ }
558
+
559
+ //#endregion
560
+ //#region src/rng.ts
561
+ /**
562
+ * taus88 combined Tausworthe step, structurally identical to umap's `tau_rand_int`
563
+ * but on u32 words (Python runs it on int64 with 32-bit truncations). Used for the
564
+ * SGD negative-sampling stream; parity with Python is statistical, not bitwise (§5.5).
565
+ * Returns a signed int32.
566
+ */
567
+ function tauRandInt(state$1) {
568
+ const s0 = state$1[0] >>> 0;
569
+ const s1 = state$1[1] >>> 0;
570
+ const s2 = state$1[2] >>> 0;
571
+ state$1[0] = (s0 & 4294967294) << 12 ^ (s0 << 13 >>> 0 ^ s0) >>> 19 | 0;
572
+ state$1[1] = (s1 & 4294967288) << 4 ^ (s1 << 2 >>> 0 ^ s1) >>> 25 | 0;
573
+ state$1[2] = (s2 & 4294967280) << 17 ^ (s2 << 3 >>> 0 ^ s2) >>> 11 | 0;
574
+ return state$1[0] ^ state$1[1] ^ state$1[2] | 0;
575
+ }
576
+ /** Floor-mod (Python `%` semantics) of a signed int by a positive int. */
577
+ function floorMod(a, n) {
578
+ const r = a % n;
579
+ return r < 0 ? r + n : r;
580
+ }
581
+
582
+ //#endregion
583
+ //#region src/knn/nndescent.ts
584
+ /** init_rp_tree: all pairs within each leaf. */
585
+ function initRpTree(nLeaves, leafCols, leafArray, heap, dist, leafAcceptance = "max") {
586
+ const k = heap.size;
587
+ const distances = heap.distances;
588
+ const either = leafAcceptance === "either";
589
+ for (let leaf = 0; leaf < nLeaves; leaf++) {
590
+ const base = leaf * leafCols;
591
+ for (let a = 0; a < leafCols; a++) {
592
+ const p = leafArray[base + a];
593
+ if (p < 0) break;
594
+ for (let b = a + 1; b < leafCols; b++) {
595
+ const q = leafArray[base + b];
596
+ if (q < 0) break;
597
+ const d = dist(p, q);
598
+ if (either ? d < distances[p * k] || d < distances[q * k] : d < Math.max(distances[p * k], distances[q * k])) {
599
+ checkedFlaggedHeapPush(heap, p, d, q, 1);
600
+ checkedFlaggedHeapPush(heap, q, d, p, 1);
601
+ }
602
+ }
603
+ }
604
+ }
605
+ }
606
+
607
+ //#endregion
608
+ //#region src/knn/rpforest.ts
609
+ const RP_EPS = 1e-8;
610
+ function defaultLeafSize(nNeighbors) {
611
+ return Math.max(60, Math.min(256, 5 * nNeighbors));
612
+ }
613
+ function partition(indices, side, nLeft, nRight) {
614
+ const left = new Int32Array(nLeft);
615
+ const right = new Int32Array(nRight);
616
+ let il = 0;
617
+ let ir = 0;
618
+ for (let i = 0; i < indices.length; i++) if (side[i] === 0) left[il++] = indices[i];
619
+ else right[ir++] = indices[i];
620
+ return {
621
+ left,
622
+ right
623
+ };
624
+ }
625
+ function euclideanSplit(data, dim, indices, rngState) {
626
+ const n = indices.length;
627
+ const leftIndex = floorMod(tauRandInt(rngState), n);
628
+ let rightIndex = floorMod(tauRandInt(rngState), n);
629
+ if (leftIndex === rightIndex) rightIndex += 1;
630
+ rightIndex = floorMod(rightIndex, n);
631
+ const l = indices[leftIndex];
632
+ const r = indices[rightIndex];
633
+ const hyperplane = new Float32Array(dim);
634
+ let offset = 0;
635
+ for (let d = 0; d < dim; d++) {
636
+ const hv = data[l * dim + d] - data[r * dim + d];
637
+ hyperplane[d] = hv;
638
+ offset -= hv * (data[l * dim + d] + data[r * dim + d]) / 2;
639
+ }
640
+ const side = new Uint8Array(n);
641
+ let nLeft = 0;
642
+ let nRight = 0;
643
+ for (let i = 0; i < n; i++) {
644
+ let margin = offset;
645
+ const p = indices[i];
646
+ for (let d = 0; d < dim; d++) margin += hyperplane[d] * data[p * dim + d];
647
+ if (Math.abs(margin) < RP_EPS) side[i] = Math.abs(tauRandInt(rngState)) % 2;
648
+ else if (margin > 0) side[i] = 0;
649
+ else side[i] = 1;
650
+ if (side[i] === 0) nLeft++;
651
+ else nRight++;
652
+ }
653
+ if (nLeft === 0 || nRight === 0) {
654
+ nLeft = 0;
655
+ nRight = 0;
656
+ for (let i = 0; i < n; i++) {
657
+ side[i] = floorMod(tauRandInt(rngState), 2);
658
+ if (side[i] === 0) nLeft++;
659
+ else nRight++;
660
+ }
661
+ }
662
+ const { left, right } = partition(indices, side, nLeft, nRight);
663
+ return {
664
+ left,
665
+ right,
666
+ hyperplane,
667
+ offset
668
+ };
669
+ }
670
+ function angularSplit(data, dim, indices, rngState) {
671
+ const n = indices.length;
672
+ const leftIndex = floorMod(tauRandInt(rngState), n);
673
+ let rightIndex = floorMod(tauRandInt(rngState), n);
674
+ if (leftIndex === rightIndex) rightIndex += 1;
675
+ rightIndex = floorMod(rightIndex, n);
676
+ const l = indices[leftIndex];
677
+ const r = indices[rightIndex];
678
+ let leftNorm = 0;
679
+ let rightNorm = 0;
680
+ for (let d = 0; d < dim; d++) {
681
+ leftNorm += data[l * dim + d] * data[l * dim + d];
682
+ rightNorm += data[r * dim + d] * data[r * dim + d];
683
+ }
684
+ leftNorm = Math.sqrt(leftNorm);
685
+ rightNorm = Math.sqrt(rightNorm);
686
+ if (Math.abs(leftNorm) < RP_EPS) leftNorm = 1;
687
+ if (Math.abs(rightNorm) < RP_EPS) rightNorm = 1;
688
+ const hyperplane = new Float32Array(dim);
689
+ let hnorm = 0;
690
+ for (let d = 0; d < dim; d++) {
691
+ const hv = data[l * dim + d] / leftNorm - data[r * dim + d] / rightNorm;
692
+ hyperplane[d] = hv;
693
+ hnorm += hv * hv;
694
+ }
695
+ hnorm = Math.sqrt(hnorm);
696
+ if (Math.abs(hnorm) < RP_EPS) hnorm = 1;
697
+ for (let d = 0; d < dim; d++) hyperplane[d] = hyperplane[d] / hnorm;
698
+ const side = new Uint8Array(n);
699
+ let nLeft = 0;
700
+ let nRight = 0;
701
+ for (let i = 0; i < n; i++) {
702
+ let margin = 0;
703
+ const p = indices[i];
704
+ for (let d = 0; d < dim; d++) margin += hyperplane[d] * data[p * dim + d];
705
+ if (Math.abs(margin) < RP_EPS) side[i] = floorMod(tauRandInt(rngState), 2);
706
+ else if (margin > 0) side[i] = 0;
707
+ else side[i] = 1;
708
+ if (side[i] === 0) nLeft++;
709
+ else nRight++;
710
+ }
711
+ if (nLeft === 0 || nRight === 0) {
712
+ nLeft = 0;
713
+ nRight = 0;
714
+ for (let i = 0; i < n; i++) {
715
+ side[i] = floorMod(tauRandInt(rngState), 2);
716
+ if (side[i] === 0) nLeft++;
717
+ else nRight++;
718
+ }
719
+ }
720
+ const { left, right } = partition(indices, side, nLeft, nRight);
721
+ return {
722
+ left,
723
+ right,
724
+ hyperplane,
725
+ offset: 0
726
+ };
727
+ }
728
+ function buildTree(data, dim, indices, leafSize, maxDepth, angular, rngState) {
729
+ if (indices.length > leafSize && maxDepth > 0) {
730
+ const { left, right, hyperplane, offset } = angular ? angularSplit(data, dim, indices, rngState) : euclideanSplit(data, dim, indices, rngState);
731
+ return {
732
+ left: buildTree(data, dim, left, leafSize, maxDepth - 1, angular, rngState),
733
+ right: buildTree(data, dim, right, leafSize, maxDepth - 1, angular, rngState),
734
+ hyperplane,
735
+ offset,
736
+ points: null
737
+ };
738
+ }
739
+ return {
740
+ left: null,
741
+ right: null,
742
+ hyperplane: null,
743
+ offset: 0,
744
+ points: indices
745
+ };
746
+ }
747
+ /** Flatten to preorder (convert_tree_format, rp_trees.py:3019-3049). */
748
+ function flattenTree(root, nPoints, dim) {
749
+ let nNodes = 0;
750
+ let maxLeaf = 0;
751
+ const count = (node) => {
752
+ nNodes++;
753
+ if (node.points) {
754
+ if (node.points.length > maxLeaf) maxLeaf = node.points.length;
755
+ } else {
756
+ count(node.left);
757
+ count(node.right);
758
+ }
759
+ };
760
+ count(root);
761
+ const hyperplanes = new Float32Array(nNodes * dim);
762
+ const offsets = new Float32Array(nNodes);
763
+ const children = new Int32Array(nNodes * 2).fill(-1);
764
+ const indices = new Int32Array(nPoints).fill(-1);
765
+ let nodeNum = 0;
766
+ let leafStart = 0;
767
+ const walk = (node) => {
768
+ const my = nodeNum;
769
+ if (node.points) {
770
+ children[my * 2] = -leafStart;
771
+ children[my * 2 + 1] = -(leafStart + node.points.length);
772
+ indices.set(node.points, leafStart);
773
+ leafStart += node.points.length;
774
+ return;
775
+ }
776
+ hyperplanes.set(node.hyperplane, my * dim);
777
+ offsets[my] = node.offset;
778
+ nodeNum++;
779
+ children[my * 2] = nodeNum;
780
+ walk(node.left);
781
+ nodeNum++;
782
+ children[my * 2 + 1] = nodeNum;
783
+ walk(node.right);
784
+ };
785
+ walk(root);
786
+ return {
787
+ hyperplanes,
788
+ offsets,
789
+ children,
790
+ indices,
791
+ leafSize: maxLeaf,
792
+ nNodes,
793
+ dim
794
+ };
795
+ }
796
+ /** make_forest: per-tree tau states drawn from the master PRNG. */
797
+ function makeForest(X, nNeighbors, nTrees, treeStates, { leafSize, angular = false, maxDepth = 200 } = {}) {
798
+ const ls = leafSize ?? defaultLeafSize(nNeighbors);
799
+ const all = new Int32Array(X.rows);
800
+ for (let i = 0; i < X.rows; i++) all[i] = i;
801
+ const forest = [];
802
+ for (let t = 0; t < nTrees; t++) {
803
+ const root = buildTree(X.data, X.cols, all.slice(), ls, maxDepth, angular, treeStates[t]);
804
+ forest.push(flattenTree(root, X.rows, X.cols));
805
+ }
806
+ return forest;
807
+ }
808
+ /** rptree_leaf_array: rows = leaves, padded with -1 to the forest-wide max leaf size. */
809
+ function leafArrayFromForest(forest) {
810
+ if (forest.length === 0) return {
811
+ data: new Int32Array([-1]),
812
+ rows: 1,
813
+ cols: 1
814
+ };
815
+ let maxLeafSize = 0;
816
+ for (const t of forest) maxLeafSize = Math.max(maxLeafSize, t.leafSize);
817
+ const rows = [];
818
+ for (const t of forest) for (let node = 0; node < t.nNodes; node++) {
819
+ const c0 = t.children[node * 2];
820
+ if (c0 <= 0) {
821
+ const start = -c0;
822
+ const end = -t.children[node * 2 + 1];
823
+ const row = new Int32Array(maxLeafSize).fill(-1);
824
+ row.set(t.indices.subarray(start, end));
825
+ rows.push(row);
826
+ }
827
+ }
828
+ const out = new Int32Array(rows.length * maxLeafSize);
829
+ rows.forEach((r, i) => {
830
+ out.set(r, i * maxLeafSize);
831
+ });
832
+ return {
833
+ data: out,
834
+ rows: rows.length,
835
+ cols: maxLeafSize
836
+ };
837
+ }
838
+
839
+ //#endregion
840
+ //#region src/layout/sgd.ts
841
+ function clip(val) {
842
+ if (val > 4) return 4;
843
+ if (val < -4) return -4;
844
+ return val;
845
+ }
846
+ /** Per-vertex tau streams: base rng_state + bit-pattern of the vertex's first coord. */
847
+ function perVertexRngStates(n, rngState, embedding, dim) {
848
+ const states = new Int32Array(n * 3);
849
+ const buf = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(8));
850
+ for (let j = 0; j < n; j++) {
851
+ buf.setFloat64(0, embedding[j * dim], true);
852
+ const lo = buf.getInt32(0, true);
853
+ const hi = buf.getInt32(4, true);
854
+ states[j * 3] = rngState[0] + lo | 0;
855
+ states[j * 3 + 1] = rngState[1] + hi | 0;
856
+ states[j * 3 + 2] = rngState[2] + (lo ^ hi) | 0;
857
+ }
858
+ return states;
859
+ }
860
+
861
+ //#endregion
862
+ //#region src/backends/cpu/worker.ts
863
+ const state = {};
864
+ const handlers = {
865
+ init(args) {
866
+ state.data = args.data;
867
+ state.rows = args.rows;
868
+ state.cols = args.cols;
869
+ state.internal = resolveInternalMetric(args.metricName, args.metricOptions);
870
+ return { result: true };
871
+ },
872
+ buildTrees(args) {
873
+ const leaves = leafArrayFromForest(makeForest({
874
+ data: state.data,
875
+ rows: state.rows,
876
+ cols: state.cols
877
+ }, args.nNeighbors, args.treeStates.length, args.treeStates.map((s) => Int32Array.from(s)), {
878
+ leafSize: defaultLeafSize(args.nNeighbors),
879
+ angular: args.angular
880
+ }));
881
+ return {
882
+ result: {
883
+ data: leaves.data,
884
+ rows: leaves.rows,
885
+ cols: leaves.cols
886
+ },
887
+ transfers: [leaves.data.buffer]
888
+ };
889
+ },
890
+ leafUpdates(args) {
891
+ const dist = state.internal.dist;
892
+ const dim = state.cols;
893
+ const data = state.data;
894
+ const out = [];
895
+ for (let leaf = args.rowStart; leaf < args.rowEnd; leaf++) {
896
+ const base = leaf * args.cols;
897
+ for (let a = 0; a < args.cols; a++) {
898
+ const p = args.leafArray[base + a];
899
+ if (p < 0) break;
900
+ for (let b = a + 1; b < args.cols; b++) {
901
+ const q = args.leafArray[base + b];
902
+ if (q < 0) break;
903
+ const d = dist(data.subarray(p * dim, (p + 1) * dim), data.subarray(q * dim, (q + 1) * dim));
904
+ if (d < Math.max(args.thresholds[p], args.thresholds[q])) out.push(p, q, d);
905
+ }
906
+ }
907
+ }
908
+ const triples = Float64Array.from(out);
909
+ return {
910
+ result: triples,
911
+ transfers: [triples.buffer]
912
+ };
913
+ },
914
+ joinUpdates(args) {
915
+ const dist = state.internal.dist;
916
+ const dim = state.cols;
917
+ const data = state.data;
918
+ const mc = args.maxCandidates;
919
+ const out = [];
920
+ for (let i = args.vStart; i < args.vEnd; i++) {
921
+ const base = i * mc;
922
+ for (let j = 0; j < mc; j++) {
923
+ const p = args.newIdx[base + j];
924
+ if (p < 0) continue;
925
+ const pRow = data.subarray(p * dim, (p + 1) * dim);
926
+ for (let t = j; t < mc; t++) {
927
+ const q = args.newIdx[base + t];
928
+ if (q < 0) continue;
929
+ const d = dist(pRow, data.subarray(q * dim, (q + 1) * dim));
930
+ if (d <= Math.max(args.thresholds[p], args.thresholds[q])) out.push(p, q, d);
931
+ }
932
+ for (let t = 0; t < mc; t++) {
933
+ const q = args.oldIdx[base + t];
934
+ if (q < 0) continue;
935
+ const d = dist(pRow, data.subarray(q * dim, (q + 1) * dim));
936
+ if (d <= Math.max(args.thresholds[p], args.thresholds[q])) out.push(p, q, d);
937
+ }
938
+ }
939
+ }
940
+ const triples = Float64Array.from(out);
941
+ return {
942
+ result: triples,
943
+ transfers: [triples.buffer]
944
+ };
945
+ },
946
+ leafInitHeap(args) {
947
+ const heap = makeHeap(state.rows, args.nNeighbors);
948
+ const dist = (i, j) => state.internal.dist(state.data.subarray(i * state.cols, (i + 1) * state.cols), state.data.subarray(j * state.cols, (j + 1) * state.cols));
949
+ initRpTree(args.rows, args.cols, args.leafArray, heap, dist);
950
+ return {
951
+ result: {
952
+ indices: heap.indices,
953
+ distances: heap.distances,
954
+ flags: heap.flags
955
+ },
956
+ transfers: [
957
+ heap.indices.buffer,
958
+ heap.distances.buffer,
959
+ heap.flags.buffer
960
+ ]
961
+ };
962
+ },
963
+ layoutInit(args) {
964
+ state.embedding = args.embedding;
965
+ state.tailEmbedding = args.tailEmbedding ?? args.embedding;
966
+ state.head = args.head;
967
+ state.tail = args.tail;
968
+ state.epochsPerSample = args.epochsPerSample;
969
+ const nEdges = args.epochsPerSample.length;
970
+ const eons = new Float64Array(nEdges);
971
+ const epns = new Float64Array(nEdges);
972
+ for (let i = 0; i < nEdges; i++) {
973
+ eons[i] = args.epochsPerSample[i];
974
+ epns[i] = args.epochsPerSample[i] / args.params.negativeSampleRate;
975
+ }
976
+ state.epochOfNextSample = eons;
977
+ state.epochsPerNegativeSample = epns;
978
+ state.epochOfNextNegativeSample = epns.slice();
979
+ state.layoutParams = args.params;
980
+ state.vertexRng = perVertexRngStates(args.embedding.length / args.params.dim, args.rngState, args.embedding, args.params.dim);
981
+ return { result: true };
982
+ },
983
+ layoutEpoch(args) {
984
+ const p = state.layoutParams;
985
+ const head = state.head;
986
+ const tail = state.tail;
987
+ const eps = state.epochsPerSample;
988
+ const eons = state.epochOfNextSample;
989
+ const epns = state.epochsPerNegativeSample;
990
+ const eonns = state.epochOfNextNegativeSample;
991
+ const vertexRng = state.vertexRng;
992
+ const dim = p.dim;
993
+ const { a, b, gamma, negativeSampleRate } = p;
994
+ const alpha = args.alpha;
995
+ const n = args.epoch;
996
+ let emb = state.embedding;
997
+ let deltas = null;
998
+ let tailEmb = state.tailEmbedding;
999
+ if (!p.sharedEmbedding) {
1000
+ emb = args.embedding;
1001
+ tailEmb = p.moveOther ? emb : state.tailEmbedding;
1002
+ deltas = new Float32Array(emb.length);
1003
+ }
1004
+ const rngRow = new Int32Array(3);
1005
+ const write = (idx, v) => {
1006
+ if (deltas) deltas[idx] = deltas[idx] + v;
1007
+ else emb[idx] = emb[idx] + v;
1008
+ };
1009
+ for (let i = p.edgeStart; i < p.edgeEnd; i++) {
1010
+ if (eons[i] > n) continue;
1011
+ const j = head[i];
1012
+ let k = tail[i];
1013
+ const jBase = j * dim;
1014
+ let kBase = k * dim;
1015
+ let d2 = 0;
1016
+ for (let d = 0; d < dim; d++) {
1017
+ const diff = emb[jBase + d] - tailEmb[kBase + d];
1018
+ d2 += diff * diff;
1019
+ }
1020
+ let gradCoeff = 0;
1021
+ if (d2 > 0) gradCoeff = -2 * a * b * d2 ** (b - 1) / (a * d2 ** b + 1);
1022
+ for (let d = 0; d < dim; d++) {
1023
+ const diff = emb[jBase + d] - tailEmb[kBase + d];
1024
+ const gradD = clip(gradCoeff * diff);
1025
+ write(jBase + d, gradD * alpha);
1026
+ if (p.moveOther) write(kBase + d, -gradD * alpha);
1027
+ }
1028
+ eons[i] = eons[i] + eps[i];
1029
+ const nNeg = Math.trunc((n - eonns[i]) / epns[i]);
1030
+ rngRow[0] = vertexRng[j * 3];
1031
+ rngRow[1] = vertexRng[j * 3 + 1];
1032
+ rngRow[2] = vertexRng[j * 3 + 2];
1033
+ for (let s = 0; s < nNeg; s++) {
1034
+ const r = tauRandInt(rngRow);
1035
+ k = r % p.nVertices + (r % p.nVertices < 0 ? p.nVertices : 0);
1036
+ kBase = k * dim;
1037
+ let dn = 0;
1038
+ for (let d = 0; d < dim; d++) {
1039
+ const diff = emb[jBase + d] - tailEmb[kBase + d];
1040
+ dn += diff * diff;
1041
+ }
1042
+ let repCoeff = 0;
1043
+ if (dn > 0) repCoeff = 2 * gamma * b / ((.001 + dn) * (a * dn ** b + 1));
1044
+ else if (j === k) continue;
1045
+ for (let d = 0; d < dim; d++) if (repCoeff > 0) {
1046
+ const diff = emb[jBase + d] - tailEmb[kBase + d];
1047
+ write(jBase + d, clip(repCoeff * diff) * alpha);
1048
+ }
1049
+ }
1050
+ vertexRng[j * 3] = rngRow[0];
1051
+ vertexRng[j * 3 + 1] = rngRow[1];
1052
+ vertexRng[j * 3 + 2] = rngRow[2];
1053
+ eonns[i] = eonns[i] + nNeg * epns[i];
1054
+ }
1055
+ if (deltas) return {
1056
+ result: deltas,
1057
+ transfers: [deltas.buffer]
1058
+ };
1059
+ return { result: null };
1060
+ }
1061
+ };
1062
+ function handle(msg, post) {
1063
+ const h = handlers[msg.method];
1064
+ if (!h) {
1065
+ post({
1066
+ id: msg.id,
1067
+ error: `unknown method ${msg.method}`
1068
+ });
1069
+ return;
1070
+ }
1071
+ try {
1072
+ const { result, transfers } = h(msg.args);
1073
+ post({
1074
+ id: msg.id,
1075
+ result
1076
+ }, transfers);
1077
+ } catch (e) {
1078
+ post({
1079
+ id: msg.id,
1080
+ error: e instanceof Error ? e.stack ?? e.message : String(e)
1081
+ });
1082
+ }
1083
+ }
1084
+ if (typeof process !== "undefined" && !!process.versions?.node) (async () => {
1085
+ const { parentPort } = await import("node:worker_threads");
1086
+ parentPort.on("message", (msg) => handle(msg, (m, t) => parentPort.postMessage(m, t ?? [])));
1087
+ })();
1088
+ else {
1089
+ const scope = globalThis;
1090
+ scope.onmessage = (e) => handle(e.data, (m, t) => scope.postMessage(m, { transfer: t ?? [] }));
1091
+ }
1092
+
1093
+ //#endregion
1094
+ export { };
1095
+ //# sourceMappingURL=worker.js.map