@danielsimonjr/mathts-tensor 0.2.24 → 0.2.26
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/README.md +4 -2
- package/dist/Tensor.d.ts +10 -0
- package/dist/Tensor.d.ts.map +1 -1
- package/dist/index.js +1806 -2483
- package/dist/operations/eig.d.ts.map +1 -1
- package/dist/operations/random.d.ts.map +1 -1
- package/dist/operations/reshape-axes.d.ts +27 -0
- package/dist/operations/reshape-axes.d.ts.map +1 -0
- package/dist/operations/svd.d.ts.map +1 -1
- package/package.json +18 -7
package/dist/index.js
CHANGED
|
@@ -1,2563 +1,1886 @@
|
|
|
1
|
-
|
|
2
|
-
import { DenseMatrix, backendManager } from "@danielsimonjr/mathts-matrix";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
for (let si = 0; si < selfRank; si++) {
|
|
447
|
-
if (!selfContracted.has(si)) {
|
|
448
|
-
free.push({ operand: 0, axis: si });
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
for (let oi = 0; oi < otherRank; oi++) {
|
|
452
|
-
if (!otherContracted.has(oi)) {
|
|
453
|
-
free.push({ operand: 1, axis: oi });
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
const spec = { contractions, free };
|
|
457
|
-
const result = _Tensor.einsum(spec, this, other);
|
|
458
|
-
let resultLabels;
|
|
459
|
-
if (this.axisLabels !== void 0 || other.axisLabels !== void 0) {
|
|
460
|
-
if (this.axisLabels !== void 0 && other.axisLabels !== void 0) {
|
|
461
|
-
resultLabels = [];
|
|
462
|
-
for (let si = 0; si < selfRank; si++) {
|
|
463
|
-
if (!selfContracted.has(si)) {
|
|
464
|
-
resultLabels.push(this.axisLabels[si]);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
for (let oi = 0; oi < otherRank; oi++) {
|
|
468
|
-
if (!otherContracted.has(oi)) {
|
|
469
|
-
resultLabels.push(other.axisLabels[oi]);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
return new _Tensor([...result.shape], result.data, resultLabels);
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Normalise an `axis` argument into a sorted list of axis indices.
|
|
478
|
-
* `undefined` ⇒ all axes (reduce everything to a rank-0 scalar).
|
|
479
|
-
*/
|
|
480
|
-
resolveAxes(axis) {
|
|
481
|
-
if (axis === void 0) {
|
|
482
|
-
return Array.from({ length: this.shape.length }, (_, i) => i);
|
|
483
|
-
}
|
|
484
|
-
if (typeof axis === "number") {
|
|
485
|
-
return [axis];
|
|
486
|
-
}
|
|
487
|
-
return [...axis];
|
|
488
|
-
}
|
|
489
|
-
static forEachIndex(shape, visit) {
|
|
490
|
-
if (shape.length === 0) {
|
|
491
|
-
visit([]);
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
const idx2 = new Array(shape.length).fill(0);
|
|
495
|
-
const total = _Tensor.sizeOf(shape);
|
|
496
|
-
for (let n = 0; n < total; n++) {
|
|
497
|
-
visit(idx2);
|
|
498
|
-
for (let k = shape.length - 1; k >= 0; k--) {
|
|
499
|
-
if (++idx2[k] < shape[k]) break;
|
|
500
|
-
idx2[k] = 0;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
static flatIndex(idx2, strides) {
|
|
505
|
-
let f = 0;
|
|
506
|
-
for (let k = 0; k < idx2.length; k++) f += idx2[k] * strides[k];
|
|
507
|
-
return f;
|
|
508
|
-
}
|
|
509
|
-
reshape(shape) {
|
|
510
|
-
if (_Tensor.sizeOf(shape) !== this.data.length) {
|
|
511
|
-
throw new Error(`Tensor.reshape: size mismatch [${this.shape}] -> [${shape}]`);
|
|
512
|
-
}
|
|
513
|
-
return new _Tensor([...shape], this.data.slice());
|
|
514
|
-
}
|
|
515
|
-
transpose(perm) {
|
|
516
|
-
const rank = this.shape.length;
|
|
517
|
-
const p = perm ?? Array.from({ length: rank }, (_, i) => rank - 1 - i);
|
|
518
|
-
if (p.length !== rank)
|
|
519
|
-
throw new Error(`Tensor.transpose: perm length ${p.length} != rank ${rank}`);
|
|
520
|
-
const outShape = p.map((axis) => this.shape[axis]);
|
|
521
|
-
const inStrides = _Tensor.rowMajorStrides(this.shape);
|
|
522
|
-
const outStrides = _Tensor.rowMajorStrides(outShape);
|
|
523
|
-
const out = new Float64Array(this.data.length);
|
|
524
|
-
_Tensor.forEachIndex(outShape, (outIdx) => {
|
|
525
|
-
const inIdx = new Array(rank);
|
|
526
|
-
for (let k = 0; k < rank; k++) inIdx[p[k]] = outIdx[k];
|
|
527
|
-
out[_Tensor.flatIndex(outIdx, outStrides)] = this.data[_Tensor.flatIndex(inIdx, inStrides)];
|
|
528
|
-
});
|
|
529
|
-
return new _Tensor(outShape, out);
|
|
530
|
-
}
|
|
531
|
-
matMul(other) {
|
|
532
|
-
if (this.shape.length !== 2 || other.shape.length !== 2) {
|
|
533
|
-
throw new Error(`Tensor.matMul: both operands must be rank-2`);
|
|
534
|
-
}
|
|
535
|
-
const [m, k] = this.shape;
|
|
536
|
-
const [k2, n] = other.shape;
|
|
537
|
-
if (k !== k2) throw new Error(`Tensor.matMul: inner dimension mismatch ${k} != ${k2}`);
|
|
538
|
-
const c = backendManager.multiply(
|
|
539
|
-
new DenseMatrix(m, k, this.data),
|
|
540
|
-
new DenseMatrix(k, n, other.data)
|
|
541
|
-
);
|
|
542
|
-
return new _Tensor([m, n], c.toFloat64Array());
|
|
543
|
-
}
|
|
544
|
-
static einsum(spec, ...operands) {
|
|
545
|
-
const inStrides = operands.map((o) => _Tensor.rowMajorStrides(o.shape));
|
|
546
|
-
const freeSizes = spec.free.map((fa) => operands[fa.operand].shape[fa.axis]);
|
|
547
|
-
const contractSizes = spec.contractions.map((c) => {
|
|
548
|
-
const [[oa, axa], [ob, axb]] = c.pair;
|
|
549
|
-
const sa = operands[oa]?.shape[axa];
|
|
550
|
-
const sb = operands[ob]?.shape[axb];
|
|
551
|
-
if (sa === void 0 || sb === void 0 || sa !== sb) {
|
|
552
|
-
throw new Error(`Tensor.einsum: contraction pair size mismatch`);
|
|
553
|
-
}
|
|
554
|
-
return sa;
|
|
555
|
-
});
|
|
556
|
-
const outStrides = _Tensor.rowMajorStrides(freeSizes);
|
|
557
|
-
const out = new Float64Array(_Tensor.sizeOf(freeSizes));
|
|
558
|
-
for (let o = 0; o < operands.length; o++) {
|
|
559
|
-
if (operands[o].shape.length === 0) continue;
|
|
560
|
-
const covered = /* @__PURE__ */ new Set();
|
|
561
|
-
spec.free.forEach((fa) => {
|
|
562
|
-
if (fa.operand === o) covered.add(fa.axis);
|
|
563
|
-
});
|
|
564
|
-
spec.contractions.forEach((c) => {
|
|
565
|
-
const [[oa, axa], [ob, axb]] = c.pair;
|
|
566
|
-
if (oa === o) covered.add(axa);
|
|
567
|
-
if (ob === o) covered.add(axb);
|
|
568
|
-
});
|
|
569
|
-
if (covered.size !== operands[o].shape.length) {
|
|
570
|
-
throw new Error(`Tensor.einsum: operand ${o} has axes not referenced by the spec`);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
const operandFlatIndex = (opIndex, freeVals, contractVals) => {
|
|
574
|
-
const idx2 = new Array(operands[opIndex].shape.length).fill(0);
|
|
575
|
-
spec.free.forEach((fa, v) => {
|
|
576
|
-
if (fa.operand === opIndex) idx2[fa.axis] = freeVals[v];
|
|
577
|
-
});
|
|
578
|
-
spec.contractions.forEach((c, v) => {
|
|
579
|
-
const [[oa, axa], [ob, axb]] = c.pair;
|
|
580
|
-
if (oa === opIndex) idx2[axa] = contractVals[v];
|
|
581
|
-
if (ob === opIndex) idx2[axb] = contractVals[v];
|
|
582
|
-
});
|
|
583
|
-
return _Tensor.flatIndex(idx2, inStrides[opIndex]);
|
|
584
|
-
};
|
|
585
|
-
_Tensor.forEachIndex(freeSizes, (freeVals) => {
|
|
586
|
-
let acc = 0;
|
|
587
|
-
_Tensor.forEachIndex(contractSizes, (contractVals) => {
|
|
588
|
-
let product = 1;
|
|
589
|
-
for (let o = 0; o < operands.length; o++) {
|
|
590
|
-
product *= operands[o].data[operandFlatIndex(o, freeVals, contractVals)];
|
|
591
|
-
}
|
|
592
|
-
acc += product;
|
|
593
|
-
});
|
|
594
|
-
out[_Tensor.flatIndex(freeVals, outStrides)] = acc;
|
|
595
|
-
});
|
|
596
|
-
return new _Tensor(freeSizes, out);
|
|
597
|
-
}
|
|
598
|
-
// -------------------------------------------------------------------------
|
|
599
|
-
// Named-index API (opt-in via axisLabels)
|
|
600
|
-
// -------------------------------------------------------------------------
|
|
601
|
-
/**
|
|
602
|
-
* Return the axis position (0-based) of a labelled index.
|
|
603
|
-
* Throws if the index is not found in `axisLabels`.
|
|
604
|
-
*/
|
|
605
|
-
axisOf(index) {
|
|
606
|
-
if (this.axisLabels === void 0) {
|
|
607
|
-
throw new Error("Tensor.axisOf: this tensor has no axisLabels");
|
|
608
|
-
}
|
|
609
|
-
const pos = this.axisLabels.findIndex((lbl) => lbl.matches(index));
|
|
610
|
-
if (pos === -1) {
|
|
611
|
-
throw new Error(`Tensor.axisOf: index ${index.toString()} not found in axisLabels`);
|
|
612
|
-
}
|
|
613
|
-
return pos;
|
|
614
|
-
}
|
|
615
|
-
/**
|
|
616
|
-
* Return a new Tensor with the same data and shape but with `oldIndex`
|
|
617
|
-
* swapped for `newIndex` in `axisLabels`.
|
|
618
|
-
* Throws if `oldIndex` is not found.
|
|
619
|
-
*/
|
|
620
|
-
replaceIndex(oldIndex, newIndex) {
|
|
621
|
-
if (this.axisLabels === void 0) {
|
|
622
|
-
throw new Error("Tensor.replaceIndex: this tensor has no axisLabels");
|
|
623
|
-
}
|
|
624
|
-
const pos = this.axisLabels.findIndex((lbl) => lbl.matches(oldIndex));
|
|
625
|
-
if (pos === -1) {
|
|
626
|
-
throw new Error(`Tensor.replaceIndex: index ${oldIndex.toString()} not found in axisLabels`);
|
|
627
|
-
}
|
|
628
|
-
const newLabels = [...this.axisLabels];
|
|
629
|
-
newLabels[pos] = newIndex;
|
|
630
|
-
return new _Tensor([...this.shape], this.data.slice(), newLabels);
|
|
631
|
-
}
|
|
632
|
-
/**
|
|
633
|
-
* Contract this tensor with `other` over every shared index (matched by
|
|
634
|
-
* `Index.matches`). Both operands must carry `axisLabels`.
|
|
635
|
-
*
|
|
636
|
-
* The result tensor carries the non-shared axes from `this` followed by
|
|
637
|
-
* the non-shared axes from `other` as its `axisLabels`.
|
|
638
|
-
*
|
|
639
|
-
* Throws:
|
|
640
|
-
* - If either operand lacks `axisLabels`.
|
|
641
|
-
* - If there are no shared indices (use `einsum` or an explicit outer product).
|
|
642
|
-
* - If a shared index has mismatched dimensions on the two operands.
|
|
643
|
-
*/
|
|
644
|
-
contract(other) {
|
|
645
|
-
if (this.axisLabels === void 0 || other.axisLabels === void 0) {
|
|
646
|
-
throw new Error("Tensor.contract requires axisLabels on both operands");
|
|
647
|
-
}
|
|
648
|
-
const sharedPairs = [];
|
|
649
|
-
for (let si = 0; si < this.axisLabels.length; si++) {
|
|
650
|
-
for (let oi = 0; oi < other.axisLabels.length; oi++) {
|
|
651
|
-
if (this.axisLabels[si].matches(other.axisLabels[oi])) {
|
|
652
|
-
sharedPairs.push({ selfAxis: si, otherAxis: oi });
|
|
653
|
-
break;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
if (sharedPairs.length === 0) {
|
|
658
|
-
throw new Error(
|
|
659
|
-
"Tensor.contract: no shared indices to contract; use einsum or an explicit outer product"
|
|
660
|
-
);
|
|
661
|
-
}
|
|
662
|
-
for (const { selfAxis, otherAxis } of sharedPairs) {
|
|
663
|
-
const dSelf = this.shape[selfAxis];
|
|
664
|
-
const dOther = other.shape[otherAxis];
|
|
665
|
-
if (dSelf !== dOther) {
|
|
666
|
-
throw new Error(
|
|
667
|
-
`Tensor.contract: shared index ${this.axisLabels[selfAxis].toString()} has dimension ${dSelf} on 'this' but ${dOther} on 'other'`
|
|
668
|
-
);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
const sharedSelfAxes = new Set(sharedPairs.map((p) => p.selfAxis));
|
|
672
|
-
const sharedOtherAxes = new Set(sharedPairs.map((p) => p.otherAxis));
|
|
673
|
-
let letterCount = 0;
|
|
674
|
-
const selfLetters = new Array(this.shape.length).fill(-1);
|
|
675
|
-
const otherLetters = new Array(other.shape.length).fill(-1);
|
|
676
|
-
for (const { selfAxis, otherAxis } of sharedPairs) {
|
|
677
|
-
const letter = letterCount++;
|
|
678
|
-
selfLetters[selfAxis] = letter;
|
|
679
|
-
otherLetters[otherAxis] = letter;
|
|
680
|
-
}
|
|
681
|
-
for (let si = 0; si < this.shape.length; si++) {
|
|
682
|
-
if (!sharedSelfAxes.has(si)) {
|
|
683
|
-
selfLetters[si] = letterCount++;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
for (let oi = 0; oi < other.shape.length; oi++) {
|
|
687
|
-
if (!sharedOtherAxes.has(oi)) {
|
|
688
|
-
otherLetters[oi] = letterCount++;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
const contractions = sharedPairs.map(
|
|
692
|
-
({ selfAxis, otherAxis }) => ({
|
|
693
|
-
pair: [
|
|
694
|
-
[0, selfAxis],
|
|
695
|
-
[1, otherAxis]
|
|
696
|
-
]
|
|
697
|
-
})
|
|
698
|
-
);
|
|
699
|
-
const free = [];
|
|
700
|
-
for (let si = 0; si < this.shape.length; si++) {
|
|
701
|
-
if (!sharedSelfAxes.has(si)) {
|
|
702
|
-
free.push({ operand: 0, axis: si });
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
for (let oi = 0; oi < other.shape.length; oi++) {
|
|
706
|
-
if (!sharedOtherAxes.has(oi)) {
|
|
707
|
-
free.push({ operand: 1, axis: oi });
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
const spec = { contractions, free };
|
|
711
|
-
const result = _Tensor.einsum(spec, this, other);
|
|
712
|
-
const resultLabels = [];
|
|
713
|
-
for (let si = 0; si < this.axisLabels.length; si++) {
|
|
714
|
-
if (!sharedSelfAxes.has(si)) {
|
|
715
|
-
resultLabels.push(this.axisLabels[si]);
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
for (let oi = 0; oi < other.axisLabels.length; oi++) {
|
|
719
|
-
if (!sharedOtherAxes.has(oi)) {
|
|
720
|
-
resultLabels.push(other.axisLabels[oi]);
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
return new _Tensor([...result.shape], result.data, resultLabels);
|
|
724
|
-
}
|
|
1
|
+
import { boxMuller, mulberry32, rowMajorStrides } from "@danielsimonjr/mathts-core";
|
|
2
|
+
import { DenseMatrix, backendManager, cholesky, eig, eigWasm, lu, qr, svd, svdWasm } from "@danielsimonjr/mathts-matrix";
|
|
3
|
+
//#region src/Tensor.ts
|
|
4
|
+
var Tensor = class Tensor {
|
|
5
|
+
shape;
|
|
6
|
+
data;
|
|
7
|
+
axisLabels;
|
|
8
|
+
constructor(shape, data, axisLabels) {
|
|
9
|
+
if (data.length !== Tensor.sizeOf(shape)) throw new Error(`Tensor: data length ${data.length} does not match shape [${shape}] (size ${Tensor.sizeOf(shape)})`);
|
|
10
|
+
if (axisLabels !== void 0 && axisLabels.length !== shape.length) throw new Error(`Tensor: axisLabels length ${axisLabels.length} does not match rank ${shape.length}`);
|
|
11
|
+
this.shape = shape;
|
|
12
|
+
this.data = data;
|
|
13
|
+
if (axisLabels !== void 0) this.axisLabels = [...axisLabels];
|
|
14
|
+
}
|
|
15
|
+
static sizeOf(shape) {
|
|
16
|
+
return shape.reduce((a, b) => a * b, 1);
|
|
17
|
+
}
|
|
18
|
+
static rowMajorStrides(shape) {
|
|
19
|
+
return rowMajorStrides(shape);
|
|
20
|
+
}
|
|
21
|
+
static fromNested(data, shape) {
|
|
22
|
+
const size = Tensor.sizeOf(shape);
|
|
23
|
+
const out = new Float64Array(size);
|
|
24
|
+
let cursor = 0;
|
|
25
|
+
const walk = (node, depth) => {
|
|
26
|
+
if (depth === shape.length) {
|
|
27
|
+
if (typeof node !== "number") throw new Error(`Tensor.fromNested: expected a number at depth ${depth}`);
|
|
28
|
+
out[cursor++] = node;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (!Array.isArray(node) || node.length !== shape[depth]) throw new Error(`Tensor.fromNested: shape mismatch at depth ${depth}`);
|
|
32
|
+
for (const child of node) walk(child, depth + 1);
|
|
33
|
+
};
|
|
34
|
+
walk(data, 0);
|
|
35
|
+
if (cursor !== size) throw new Error(`Tensor.fromNested: filled ${cursor} of ${size}`);
|
|
36
|
+
return new Tensor([...shape], out);
|
|
37
|
+
}
|
|
38
|
+
static identity(n) {
|
|
39
|
+
const out = new Float64Array(n * n);
|
|
40
|
+
for (let i = 0; i < n; i++) out[i * n + i] = 1;
|
|
41
|
+
return new Tensor([n, n], out);
|
|
42
|
+
}
|
|
43
|
+
toNested() {
|
|
44
|
+
if (this.shape.length === 0) return this.data[0];
|
|
45
|
+
const strides = Tensor.rowMajorStrides(this.shape);
|
|
46
|
+
const build = (depth, offset) => {
|
|
47
|
+
if (depth === this.shape.length - 1) {
|
|
48
|
+
const row = [];
|
|
49
|
+
for (let i = 0; i < this.shape[depth]; i++) row.push(this.data[offset + i]);
|
|
50
|
+
return row;
|
|
51
|
+
}
|
|
52
|
+
const out = [];
|
|
53
|
+
for (let i = 0; i < this.shape[depth]; i++) out.push(build(depth + 1, offset + i * strides[depth]));
|
|
54
|
+
return out;
|
|
55
|
+
};
|
|
56
|
+
return build(0, 0);
|
|
57
|
+
}
|
|
58
|
+
static fromDenseMatrix(m) {
|
|
59
|
+
const nested = m.toArray();
|
|
60
|
+
const rows = nested.length;
|
|
61
|
+
const cols = rows > 0 ? nested[0].length : 0;
|
|
62
|
+
return Tensor.fromNested(nested, [rows, cols]);
|
|
63
|
+
}
|
|
64
|
+
toDenseMatrix() {
|
|
65
|
+
if (this.shape.length !== 2) throw new Error(`Tensor.toDenseMatrix: expected a rank-2 tensor, got rank ${this.shape.length}`);
|
|
66
|
+
return DenseMatrix.fromArray(this.toNested());
|
|
67
|
+
}
|
|
68
|
+
sameShape(other) {
|
|
69
|
+
return this.shape.length === other.shape.length && this.shape.every((v, i) => v === other.shape[i]);
|
|
70
|
+
}
|
|
71
|
+
elementwise(other, op, f) {
|
|
72
|
+
if (!this.sameShape(other)) throw new Error(`Tensor.${op}: shape mismatch [${this.shape}] vs [${other.shape}]`);
|
|
73
|
+
const out = new Float64Array(this.data.length);
|
|
74
|
+
for (let i = 0; i < this.data.length; i++) out[i] = f(this.data[i], other.data[i]);
|
|
75
|
+
return new Tensor(this.shape, out);
|
|
76
|
+
}
|
|
77
|
+
add(other) {
|
|
78
|
+
if (typeof other === "number") {
|
|
79
|
+
const out = new Float64Array(this.data.length);
|
|
80
|
+
for (let i = 0; i < this.data.length; i++) out[i] = this.data[i] + other;
|
|
81
|
+
return new Tensor(this.shape, out, this.axisLabels);
|
|
82
|
+
}
|
|
83
|
+
if (this.sameShape(other)) return this.elementwise(other, "add", (x, y) => x + y);
|
|
84
|
+
return Tensor.broadcastOp(this, other, "add", (x, y) => x + y);
|
|
85
|
+
}
|
|
86
|
+
sub(other) {
|
|
87
|
+
if (typeof other === "number") {
|
|
88
|
+
const out = new Float64Array(this.data.length);
|
|
89
|
+
for (let i = 0; i < this.data.length; i++) out[i] = this.data[i] - other;
|
|
90
|
+
return new Tensor(this.shape, out, this.axisLabels);
|
|
91
|
+
}
|
|
92
|
+
if (this.sameShape(other)) return this.elementwise(other, "sub", (x, y) => x - y);
|
|
93
|
+
return Tensor.broadcastOp(this, other, "sub", (x, y) => x - y);
|
|
94
|
+
}
|
|
95
|
+
mul(other) {
|
|
96
|
+
if (typeof other === "number") return this.scale(other);
|
|
97
|
+
if (this.sameShape(other)) return this.elementwise(other, "mul", (x, y) => x * y);
|
|
98
|
+
return Tensor.broadcastOp(this, other, "mul", (x, y) => x * y);
|
|
99
|
+
}
|
|
100
|
+
scale(k) {
|
|
101
|
+
const out = new Float64Array(this.data.length);
|
|
102
|
+
for (let i = 0; i < this.data.length; i++) out[i] = this.data[i] * k;
|
|
103
|
+
return new Tensor(this.shape, out);
|
|
104
|
+
}
|
|
105
|
+
normInf() {
|
|
106
|
+
let max = 0;
|
|
107
|
+
for (let i = 0; i < this.data.length; i++) {
|
|
108
|
+
const a = Math.abs(this.data[i]);
|
|
109
|
+
if (a > max) max = a;
|
|
110
|
+
}
|
|
111
|
+
return max;
|
|
112
|
+
}
|
|
113
|
+
static broadcastShape(a, b) {
|
|
114
|
+
const rank = Math.max(a.length, b.length);
|
|
115
|
+
const out = new Array(rank);
|
|
116
|
+
for (let i = 0; i < rank; i++) {
|
|
117
|
+
const ai = a.length - rank + i;
|
|
118
|
+
const bi = b.length - rank + i;
|
|
119
|
+
const da = ai >= 0 ? a[ai] : 1;
|
|
120
|
+
const db = bi >= 0 ? b[bi] : 1;
|
|
121
|
+
if (da === db) out[i] = da;
|
|
122
|
+
else if (da === 1) out[i] = db;
|
|
123
|
+
else if (db === 1) out[i] = da;
|
|
124
|
+
else throw new Error(`shapes [${a}] and [${b}] cannot be broadcast: dimension mismatch ${da} vs ${db} at axis ${i} of the aligned shape`);
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
static broadcastOp(a, b, _opName, f) {
|
|
129
|
+
const outShape = Tensor.broadcastShape(a.shape, b.shape);
|
|
130
|
+
const rank = outShape.length;
|
|
131
|
+
const outSize = Tensor.sizeOf(outShape);
|
|
132
|
+
const aStrides = Tensor.rowMajorStrides(a.shape);
|
|
133
|
+
const bStrides = Tensor.rowMajorStrides(b.shape);
|
|
134
|
+
const aRankOffset = rank - a.shape.length;
|
|
135
|
+
const bRankOffset = rank - b.shape.length;
|
|
136
|
+
const aEffStrides = new Array(rank).fill(0);
|
|
137
|
+
const bEffStrides = new Array(rank).fill(0);
|
|
138
|
+
for (let i = 0; i < rank; i++) {
|
|
139
|
+
const ai = i - aRankOffset;
|
|
140
|
+
if (ai >= 0 && a.shape[ai] !== 1) aEffStrides[i] = aStrides[ai];
|
|
141
|
+
const bi = i - bRankOffset;
|
|
142
|
+
if (bi >= 0 && b.shape[bi] !== 1) bEffStrides[i] = bStrides[bi];
|
|
143
|
+
}
|
|
144
|
+
const out = new Float64Array(outSize);
|
|
145
|
+
const coords = new Array(rank).fill(0);
|
|
146
|
+
for (let n = 0; n < outSize; n++) {
|
|
147
|
+
let aFlat = 0;
|
|
148
|
+
let bFlat = 0;
|
|
149
|
+
for (let k = 0; k < rank; k++) {
|
|
150
|
+
aFlat += coords[k] * aEffStrides[k];
|
|
151
|
+
bFlat += coords[k] * bEffStrides[k];
|
|
152
|
+
}
|
|
153
|
+
out[n] = f(a.data[aFlat], b.data[bFlat]);
|
|
154
|
+
for (let k = rank - 1; k >= 0; k--) {
|
|
155
|
+
if (++coords[k] < outShape[k]) break;
|
|
156
|
+
coords[k] = 0;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return new Tensor(outShape, out);
|
|
160
|
+
}
|
|
161
|
+
reduceAxes(axes, keepDims, init, combine, finalise) {
|
|
162
|
+
const rank = this.shape.length;
|
|
163
|
+
const axisSet = new Set(axes);
|
|
164
|
+
for (const ax of axes) if (!Number.isInteger(ax) || ax < 0 || ax >= rank) throw new Error(`Tensor.reduce: axis ${ax} out of range for rank-${rank} tensor`);
|
|
165
|
+
const outShape = [];
|
|
166
|
+
for (let k = 0; k < rank; k++) if (axisSet.has(k)) {
|
|
167
|
+
if (keepDims) outShape.push(1);
|
|
168
|
+
} else outShape.push(this.shape[k]);
|
|
169
|
+
const reduceCount = axes.reduce((prod, ax) => prod * this.shape[ax], 1);
|
|
170
|
+
const outSize = Tensor.sizeOf(outShape);
|
|
171
|
+
const out = new Float64Array(outSize).fill(init);
|
|
172
|
+
const outStrides = Tensor.rowMajorStrides(outShape.length > 0 ? outShape : [1]);
|
|
173
|
+
const inCoords = new Array(rank).fill(0);
|
|
174
|
+
const inSize = this.data.length;
|
|
175
|
+
for (let n = 0; n < inSize; n++) {
|
|
176
|
+
let outFlat = 0;
|
|
177
|
+
let outDim = 0;
|
|
178
|
+
for (let k = 0; k < rank; k++) if (axisSet.has(k)) {
|
|
179
|
+
if (keepDims) outDim++;
|
|
180
|
+
} else {
|
|
181
|
+
outFlat += inCoords[k] * outStrides[outDim];
|
|
182
|
+
outDim++;
|
|
183
|
+
}
|
|
184
|
+
out[outFlat] = combine(out[outFlat], this.data[n]);
|
|
185
|
+
for (let k = rank - 1; k >= 0; k--) {
|
|
186
|
+
if (++inCoords[k] < this.shape[k]) break;
|
|
187
|
+
inCoords[k] = 0;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (finalise !== void 0) for (let i = 0; i < out.length; i++) out[i] = finalise(out[i], reduceCount);
|
|
191
|
+
let resultLabels;
|
|
192
|
+
if (!keepDims && this.axisLabels !== void 0) {
|
|
193
|
+
const surviving = [];
|
|
194
|
+
for (let k = 0; k < rank; k++) if (!axisSet.has(k)) surviving.push(this.axisLabels[k]);
|
|
195
|
+
if (surviving.length === outShape.length) resultLabels = surviving;
|
|
196
|
+
}
|
|
197
|
+
return new Tensor(outShape, out, resultLabels);
|
|
198
|
+
}
|
|
199
|
+
sum(axis, opts) {
|
|
200
|
+
const axes = this.resolveAxes(axis);
|
|
201
|
+
const keepDims = opts?.keepDims ?? false;
|
|
202
|
+
return this.reduceAxes(axes, keepDims, 0, (acc, v) => acc + v);
|
|
203
|
+
}
|
|
204
|
+
mean(axis, opts) {
|
|
205
|
+
const axes = this.resolveAxes(axis);
|
|
206
|
+
const keepDims = opts?.keepDims ?? false;
|
|
207
|
+
return this.reduceAxes(axes, keepDims, 0, (acc, v) => acc + v, (acc, count) => acc / count);
|
|
208
|
+
}
|
|
209
|
+
max(axis, opts) {
|
|
210
|
+
return this.extremum(axis, opts, -Infinity, Math.max);
|
|
211
|
+
}
|
|
212
|
+
min(axis, opts) {
|
|
213
|
+
return this.extremum(axis, opts, Infinity, Math.min);
|
|
214
|
+
}
|
|
215
|
+
extremum(axis, opts, init, pick) {
|
|
216
|
+
const axes = this.resolveAxes(axis);
|
|
217
|
+
const keepDims = opts?.keepDims ?? false;
|
|
218
|
+
return this.reduceAxes(axes, keepDims, init, (acc, v) => isNaN(v) ? v : isNaN(acc) ? acc : pick(acc, v));
|
|
219
|
+
}
|
|
220
|
+
prod(axis, opts) {
|
|
221
|
+
const axes = this.resolveAxes(axis);
|
|
222
|
+
const keepDims = opts?.keepDims ?? false;
|
|
223
|
+
return this.reduceAxes(axes, keepDims, 1, (acc, v) => acc * v);
|
|
224
|
+
}
|
|
225
|
+
norm(opts) {
|
|
226
|
+
const p = opts?.p ?? 2;
|
|
227
|
+
const keepDims = opts?.keepDims ?? false;
|
|
228
|
+
const axes = opts?.axis !== void 0 ? [opts.axis] : this.resolveAxes(void 0);
|
|
229
|
+
if (p === "fro" || p === 2) {
|
|
230
|
+
const sqSum = this.reduceAxes(axes, keepDims, 0, (acc, v) => acc + v * v);
|
|
231
|
+
const sqrtData = new Float64Array(sqSum.data.length);
|
|
232
|
+
for (let i = 0; i < sqSum.data.length; i++) sqrtData[i] = Math.sqrt(sqSum.data[i]);
|
|
233
|
+
return new Tensor(sqSum.shape, sqrtData, sqSum.axisLabels);
|
|
234
|
+
}
|
|
235
|
+
if (p === "inf") return this.reduceAxes(axes, keepDims, 0, (acc, v) => Math.max(acc, Math.abs(v)));
|
|
236
|
+
if (p === "-inf") return this.reduceAxes(axes, keepDims, Infinity, (acc, v) => Math.min(acc, Math.abs(v)));
|
|
237
|
+
const pNum = p;
|
|
238
|
+
const powSum = this.reduceAxes(axes, keepDims, 0, (acc, v) => acc + Math.pow(Math.abs(v), pNum));
|
|
239
|
+
const invP = 1 / pNum;
|
|
240
|
+
const result = new Float64Array(powSum.data.length);
|
|
241
|
+
for (let i = 0; i < powSum.data.length; i++) result[i] = Math.pow(powSum.data[i], invP);
|
|
242
|
+
return new Tensor(powSum.shape, result, powSum.axisLabels);
|
|
243
|
+
}
|
|
244
|
+
tensordot(other, axes) {
|
|
245
|
+
const selfRank = this.shape.length;
|
|
246
|
+
const otherRank = other.shape.length;
|
|
247
|
+
const selfContracted = /* @__PURE__ */ new Set();
|
|
248
|
+
const otherContracted = /* @__PURE__ */ new Set();
|
|
249
|
+
for (const [a, b] of axes) {
|
|
250
|
+
if (!Number.isInteger(a) || a < 0 || a >= selfRank) throw new Error(`Tensor.tensordot: axis ${a} of 'this' (rank ${selfRank}) is out of range`);
|
|
251
|
+
if (!Number.isInteger(b) || b < 0 || b >= otherRank) throw new Error(`Tensor.tensordot: axis ${b} of 'other' (rank ${otherRank}) is out of range`);
|
|
252
|
+
if (selfContracted.has(a)) throw new Error(`Tensor.tensordot: duplicate axis ${a} in self contraction list`);
|
|
253
|
+
if (otherContracted.has(b)) throw new Error(`Tensor.tensordot: duplicate axis ${b} in other contraction list`);
|
|
254
|
+
if (this.shape[a] !== other.shape[b]) throw new Error(`Tensor.tensordot: dimension mismatch on contracted axes: this.shape[${a}]=${this.shape[a]} vs other.shape[${b}]=${other.shape[b]}`);
|
|
255
|
+
selfContracted.add(a);
|
|
256
|
+
otherContracted.add(b);
|
|
257
|
+
}
|
|
258
|
+
const contractions = axes.map(([a, b]) => ({ pair: [[0, a], [1, b]] }));
|
|
259
|
+
const free = [];
|
|
260
|
+
for (let si = 0; si < selfRank; si++) if (!selfContracted.has(si)) free.push({
|
|
261
|
+
operand: 0,
|
|
262
|
+
axis: si
|
|
263
|
+
});
|
|
264
|
+
for (let oi = 0; oi < otherRank; oi++) if (!otherContracted.has(oi)) free.push({
|
|
265
|
+
operand: 1,
|
|
266
|
+
axis: oi
|
|
267
|
+
});
|
|
268
|
+
const spec = {
|
|
269
|
+
contractions,
|
|
270
|
+
free
|
|
271
|
+
};
|
|
272
|
+
const result = Tensor.einsum(spec, this, other);
|
|
273
|
+
let resultLabels;
|
|
274
|
+
if (this.axisLabels !== void 0 || other.axisLabels !== void 0) {
|
|
275
|
+
if (this.axisLabels !== void 0 && other.axisLabels !== void 0) {
|
|
276
|
+
resultLabels = [];
|
|
277
|
+
for (let si = 0; si < selfRank; si++) if (!selfContracted.has(si)) resultLabels.push(this.axisLabels[si]);
|
|
278
|
+
for (let oi = 0; oi < otherRank; oi++) if (!otherContracted.has(oi)) resultLabels.push(other.axisLabels[oi]);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return new Tensor([...result.shape], result.data, resultLabels);
|
|
282
|
+
}
|
|
283
|
+
resolveAxes(axis) {
|
|
284
|
+
if (axis === void 0) return Array.from({ length: this.shape.length }, (_, i) => i);
|
|
285
|
+
if (typeof axis === "number") return [axis];
|
|
286
|
+
return [...axis];
|
|
287
|
+
}
|
|
288
|
+
static forEachIndex(shape, visit) {
|
|
289
|
+
if (shape.length === 0) {
|
|
290
|
+
visit([]);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const idx = new Array(shape.length).fill(0);
|
|
294
|
+
const total = Tensor.sizeOf(shape);
|
|
295
|
+
for (let n = 0; n < total; n++) {
|
|
296
|
+
visit(idx);
|
|
297
|
+
for (let k = shape.length - 1; k >= 0; k--) {
|
|
298
|
+
if (++idx[k] < shape[k]) break;
|
|
299
|
+
idx[k] = 0;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
static flatIndex(idx, strides) {
|
|
304
|
+
let f = 0;
|
|
305
|
+
for (let k = 0; k < idx.length; k++) f += idx[k] * strides[k];
|
|
306
|
+
return f;
|
|
307
|
+
}
|
|
308
|
+
reshape(shape) {
|
|
309
|
+
if (Tensor.sizeOf(shape) !== this.data.length) throw new Error(`Tensor.reshape: size mismatch [${this.shape}] -> [${shape}]`);
|
|
310
|
+
return new Tensor([...shape], this.data.slice());
|
|
311
|
+
}
|
|
312
|
+
transpose(perm) {
|
|
313
|
+
const rank = this.shape.length;
|
|
314
|
+
const p = perm ?? Array.from({ length: rank }, (_, i) => rank - 1 - i);
|
|
315
|
+
if (p.length !== rank) throw new Error(`Tensor.transpose: perm length ${p.length} != rank ${rank}`);
|
|
316
|
+
const outShape = p.map((axis) => this.shape[axis]);
|
|
317
|
+
const inStrides = Tensor.rowMajorStrides(this.shape);
|
|
318
|
+
const outStrides = Tensor.rowMajorStrides(outShape);
|
|
319
|
+
const out = new Float64Array(this.data.length);
|
|
320
|
+
Tensor.forEachIndex(outShape, (outIdx) => {
|
|
321
|
+
const inIdx = new Array(rank);
|
|
322
|
+
for (let k = 0; k < rank; k++) inIdx[p[k]] = outIdx[k];
|
|
323
|
+
out[Tensor.flatIndex(outIdx, outStrides)] = this.data[Tensor.flatIndex(inIdx, inStrides)];
|
|
324
|
+
});
|
|
325
|
+
return new Tensor(outShape, out);
|
|
326
|
+
}
|
|
327
|
+
matMul(other) {
|
|
328
|
+
if (this.shape.length !== 2 || other.shape.length !== 2) throw new Error(`Tensor.matMul: both operands must be rank-2`);
|
|
329
|
+
const [m, k] = this.shape;
|
|
330
|
+
const [k2, n] = other.shape;
|
|
331
|
+
if (k !== k2) throw new Error(`Tensor.matMul: inner dimension mismatch ${k} != ${k2}`);
|
|
332
|
+
const c = backendManager.multiply(new DenseMatrix(m, k, this.data), new DenseMatrix(k, n, other.data));
|
|
333
|
+
return new Tensor([m, n], c.toFloat64Array());
|
|
334
|
+
}
|
|
335
|
+
static einsum(spec, ...operands) {
|
|
336
|
+
const inStrides = operands.map((o) => Tensor.rowMajorStrides(o.shape));
|
|
337
|
+
const freeSizes = spec.free.map((fa) => operands[fa.operand].shape[fa.axis]);
|
|
338
|
+
const contractSizes = spec.contractions.map((c) => {
|
|
339
|
+
const [[oa, axa], [ob, axb]] = c.pair;
|
|
340
|
+
const sa = operands[oa]?.shape[axa];
|
|
341
|
+
const sb = operands[ob]?.shape[axb];
|
|
342
|
+
if (sa === void 0 || sb === void 0 || sa !== sb) throw new Error(`Tensor.einsum: contraction pair size mismatch`);
|
|
343
|
+
return sa;
|
|
344
|
+
});
|
|
345
|
+
const outStrides = Tensor.rowMajorStrides(freeSizes);
|
|
346
|
+
const out = new Float64Array(Tensor.sizeOf(freeSizes));
|
|
347
|
+
for (let o = 0; o < operands.length; o++) {
|
|
348
|
+
if (operands[o].shape.length === 0) continue;
|
|
349
|
+
const covered = /* @__PURE__ */ new Set();
|
|
350
|
+
spec.free.forEach((fa) => {
|
|
351
|
+
if (fa.operand === o) covered.add(fa.axis);
|
|
352
|
+
});
|
|
353
|
+
spec.contractions.forEach((c) => {
|
|
354
|
+
const [[oa, axa], [ob, axb]] = c.pair;
|
|
355
|
+
if (oa === o) covered.add(axa);
|
|
356
|
+
if (ob === o) covered.add(axb);
|
|
357
|
+
});
|
|
358
|
+
if (covered.size !== operands[o].shape.length) throw new Error(`Tensor.einsum: operand ${o} has axes not referenced by the spec`);
|
|
359
|
+
}
|
|
360
|
+
const operandFlatIndex = (opIndex, freeVals, contractVals) => {
|
|
361
|
+
const idx = new Array(operands[opIndex].shape.length).fill(0);
|
|
362
|
+
spec.free.forEach((fa, v) => {
|
|
363
|
+
if (fa.operand === opIndex) idx[fa.axis] = freeVals[v];
|
|
364
|
+
});
|
|
365
|
+
spec.contractions.forEach((c, v) => {
|
|
366
|
+
const [[oa, axa], [ob, axb]] = c.pair;
|
|
367
|
+
if (oa === opIndex) idx[axa] = contractVals[v];
|
|
368
|
+
if (ob === opIndex) idx[axb] = contractVals[v];
|
|
369
|
+
});
|
|
370
|
+
return Tensor.flatIndex(idx, inStrides[opIndex]);
|
|
371
|
+
};
|
|
372
|
+
Tensor.forEachIndex(freeSizes, (freeVals) => {
|
|
373
|
+
let acc = 0;
|
|
374
|
+
Tensor.forEachIndex(contractSizes, (contractVals) => {
|
|
375
|
+
let product = 1;
|
|
376
|
+
for (let o = 0; o < operands.length; o++) product *= operands[o].data[operandFlatIndex(o, freeVals, contractVals)];
|
|
377
|
+
acc += product;
|
|
378
|
+
});
|
|
379
|
+
out[Tensor.flatIndex(freeVals, outStrides)] = acc;
|
|
380
|
+
});
|
|
381
|
+
return new Tensor(freeSizes, out);
|
|
382
|
+
}
|
|
383
|
+
axisOf(index) {
|
|
384
|
+
return this.axisPosition(index, "axisOf");
|
|
385
|
+
}
|
|
386
|
+
replaceIndex(oldIndex, newIndex) {
|
|
387
|
+
const pos = this.axisPosition(oldIndex, "replaceIndex");
|
|
388
|
+
const newLabels = [...this.axisLabels];
|
|
389
|
+
newLabels[pos] = newIndex;
|
|
390
|
+
return new Tensor([...this.shape], this.data.slice(), newLabels);
|
|
391
|
+
}
|
|
392
|
+
axisPosition(index, op) {
|
|
393
|
+
if (this.axisLabels === void 0) throw new Error(`Tensor.${op}: this tensor has no axisLabels`);
|
|
394
|
+
const pos = this.axisLabels.findIndex((lbl) => lbl.matches(index));
|
|
395
|
+
if (pos === -1) throw new Error(`Tensor.${op}: index ${index.toString()} not found in axisLabels`);
|
|
396
|
+
return pos;
|
|
397
|
+
}
|
|
398
|
+
contract(other) {
|
|
399
|
+
if (this.axisLabels === void 0 || other.axisLabels === void 0) throw new Error("Tensor.contract requires axisLabels on both operands");
|
|
400
|
+
const sharedPairs = [];
|
|
401
|
+
for (let si = 0; si < this.axisLabels.length; si++) for (let oi = 0; oi < other.axisLabels.length; oi++) if (this.axisLabels[si].matches(other.axisLabels[oi])) {
|
|
402
|
+
sharedPairs.push({
|
|
403
|
+
selfAxis: si,
|
|
404
|
+
otherAxis: oi
|
|
405
|
+
});
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
if (sharedPairs.length === 0) throw new Error("Tensor.contract: no shared indices to contract; use einsum or an explicit outer product");
|
|
409
|
+
for (const { selfAxis, otherAxis } of sharedPairs) {
|
|
410
|
+
const dSelf = this.shape[selfAxis];
|
|
411
|
+
const dOther = other.shape[otherAxis];
|
|
412
|
+
if (dSelf !== dOther) throw new Error(`Tensor.contract: shared index ${this.axisLabels[selfAxis].toString()} has dimension ${dSelf} on 'this' but ${dOther} on 'other'`);
|
|
413
|
+
}
|
|
414
|
+
const sharedSelfAxes = new Set(sharedPairs.map((p) => p.selfAxis));
|
|
415
|
+
const sharedOtherAxes = new Set(sharedPairs.map((p) => p.otherAxis));
|
|
416
|
+
let letterCount = 0;
|
|
417
|
+
const selfLetters = new Array(this.shape.length).fill(-1);
|
|
418
|
+
const otherLetters = new Array(other.shape.length).fill(-1);
|
|
419
|
+
for (const { selfAxis, otherAxis } of sharedPairs) {
|
|
420
|
+
const letter = letterCount++;
|
|
421
|
+
selfLetters[selfAxis] = letter;
|
|
422
|
+
otherLetters[otherAxis] = letter;
|
|
423
|
+
}
|
|
424
|
+
for (let si = 0; si < this.shape.length; si++) if (!sharedSelfAxes.has(si)) selfLetters[si] = letterCount++;
|
|
425
|
+
for (let oi = 0; oi < other.shape.length; oi++) if (!sharedOtherAxes.has(oi)) otherLetters[oi] = letterCount++;
|
|
426
|
+
const contractions = sharedPairs.map(({ selfAxis, otherAxis }) => ({ pair: [[0, selfAxis], [1, otherAxis]] }));
|
|
427
|
+
const free = [];
|
|
428
|
+
for (let si = 0; si < this.shape.length; si++) if (!sharedSelfAxes.has(si)) free.push({
|
|
429
|
+
operand: 0,
|
|
430
|
+
axis: si
|
|
431
|
+
});
|
|
432
|
+
for (let oi = 0; oi < other.shape.length; oi++) if (!sharedOtherAxes.has(oi)) free.push({
|
|
433
|
+
operand: 1,
|
|
434
|
+
axis: oi
|
|
435
|
+
});
|
|
436
|
+
const spec = {
|
|
437
|
+
contractions,
|
|
438
|
+
free
|
|
439
|
+
};
|
|
440
|
+
const result = Tensor.einsum(spec, this, other);
|
|
441
|
+
const resultLabels = [];
|
|
442
|
+
for (let si = 0; si < this.axisLabels.length; si++) if (!sharedSelfAxes.has(si)) resultLabels.push(this.axisLabels[si]);
|
|
443
|
+
for (let oi = 0; oi < other.axisLabels.length; oi++) if (!sharedOtherAxes.has(oi)) resultLabels.push(other.axisLabels[oi]);
|
|
444
|
+
return new Tensor([...result.shape], result.data, resultLabels);
|
|
445
|
+
}
|
|
725
446
|
};
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
var Index = class
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
/**
|
|
780
|
-
* Return a new Index with `tag` removed from its tag list (no-op if absent).
|
|
781
|
-
*/
|
|
782
|
-
removeTag(tag) {
|
|
783
|
-
const next = this.tags.filter((t) => t !== tag);
|
|
784
|
-
if (next.length === this.tags.length) return this;
|
|
785
|
-
return _Index._from(this.id, this.dim, this.name, next, this.primeLevel);
|
|
786
|
-
}
|
|
787
|
-
/**
|
|
788
|
-
* True if `tag` is in this index's tag list.
|
|
789
|
-
*/
|
|
790
|
-
hasTag(tag) {
|
|
791
|
-
return this.tags.includes(tag);
|
|
792
|
-
}
|
|
793
|
-
/**
|
|
794
|
-
* Two indices match iff they share the same `id` AND the same `primeLevel`.
|
|
795
|
-
* Dimension is NOT used for matching — it is validated at contraction time.
|
|
796
|
-
*/
|
|
797
|
-
matches(other) {
|
|
798
|
-
return this.id === other.id && this.primeLevel === other.primeLevel;
|
|
799
|
-
}
|
|
800
|
-
toString() {
|
|
801
|
-
const primeStr = this.primeLevel > 0 ? `'`.repeat(this.primeLevel) : "";
|
|
802
|
-
const nameStr = this.name ?? this.id.toString();
|
|
803
|
-
const tagStr = this.tags.length > 0 ? ` [${this.tags.join(",")}]` : "";
|
|
804
|
-
return `Index(${nameStr}${primeStr}, dim=${this.dim}${tagStr})`;
|
|
805
|
-
}
|
|
447
|
+
//#endregion
|
|
448
|
+
//#region src/named-index.ts
|
|
449
|
+
var Index = class Index {
|
|
450
|
+
id;
|
|
451
|
+
dim;
|
|
452
|
+
name;
|
|
453
|
+
tags;
|
|
454
|
+
primeLevel;
|
|
455
|
+
constructor(dim, opts) {
|
|
456
|
+
if (!Number.isInteger(dim) || dim < 1) throw new Error(`Index: dim must be a positive integer, got ${dim}`);
|
|
457
|
+
this.id = Symbol(opts?.name ?? "");
|
|
458
|
+
this.dim = dim;
|
|
459
|
+
this.name = opts?.name;
|
|
460
|
+
this.tags = opts?.tags ? [...opts.tags] : [];
|
|
461
|
+
this.primeLevel = opts?.primeLevel ?? 0;
|
|
462
|
+
}
|
|
463
|
+
static _from(id, dim, name, tags, primeLevel) {
|
|
464
|
+
const ix = Object.create(Index.prototype);
|
|
465
|
+
ix.id = id;
|
|
466
|
+
ix.dim = dim;
|
|
467
|
+
ix.name = name;
|
|
468
|
+
ix.tags = [...tags];
|
|
469
|
+
ix.primeLevel = primeLevel;
|
|
470
|
+
return ix;
|
|
471
|
+
}
|
|
472
|
+
prime(by = 1) {
|
|
473
|
+
if (!Number.isInteger(by) || by < 1) throw new Error(`Index.prime: 'by' must be a positive integer, got ${by}`);
|
|
474
|
+
return Index._from(this.id, this.dim, this.name, this.tags, this.primeLevel + by);
|
|
475
|
+
}
|
|
476
|
+
noprime() {
|
|
477
|
+
return Index._from(this.id, this.dim, this.name, this.tags, 0);
|
|
478
|
+
}
|
|
479
|
+
addTag(tag) {
|
|
480
|
+
if (this.tags.includes(tag)) return this;
|
|
481
|
+
return Index._from(this.id, this.dim, this.name, [...this.tags, tag], this.primeLevel);
|
|
482
|
+
}
|
|
483
|
+
removeTag(tag) {
|
|
484
|
+
const next = this.tags.filter((t) => t !== tag);
|
|
485
|
+
if (next.length === this.tags.length) return this;
|
|
486
|
+
return Index._from(this.id, this.dim, this.name, next, this.primeLevel);
|
|
487
|
+
}
|
|
488
|
+
hasTag(tag) {
|
|
489
|
+
return this.tags.includes(tag);
|
|
490
|
+
}
|
|
491
|
+
matches(other) {
|
|
492
|
+
return this.id === other.id && this.primeLevel === other.primeLevel;
|
|
493
|
+
}
|
|
494
|
+
toString() {
|
|
495
|
+
const primeStr = this.primeLevel > 0 ? `'`.repeat(this.primeLevel) : "";
|
|
496
|
+
const nameStr = this.name ?? this.id.toString();
|
|
497
|
+
const tagStr = this.tags.length > 0 ? ` [${this.tags.join(",")}]` : "";
|
|
498
|
+
return `Index(${nameStr}${primeStr}, dim=${this.dim}${tagStr})`;
|
|
499
|
+
}
|
|
806
500
|
};
|
|
807
501
|
function idx(dim, name, opts) {
|
|
808
|
-
|
|
502
|
+
return new Index(dim, {
|
|
503
|
+
name,
|
|
504
|
+
...opts
|
|
505
|
+
});
|
|
809
506
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
507
|
+
//#endregion
|
|
508
|
+
//#region src/operations/reshape-axes.ts
|
|
509
|
+
function reshapeByRowAxes(t, rowAxes, messages) {
|
|
510
|
+
const rank = t.shape.length;
|
|
511
|
+
for (const ax of rowAxes) if (!Number.isInteger(ax) || ax < 0 || ax >= rank) throw new RangeError(messages.badAxis(ax, rank));
|
|
512
|
+
const rowSet = new Set(rowAxes);
|
|
513
|
+
const colAxes = [];
|
|
514
|
+
for (let i = 0; i < rank; i++) if (!rowSet.has(i)) colAxes.push(i);
|
|
515
|
+
if (colAxes.length === 0) throw new Error(messages.noColAxis);
|
|
516
|
+
if (rowAxes.length === 0) throw new Error(messages.noRowAxis);
|
|
517
|
+
const rowDims = rowAxes.map((ax) => t.shape[ax]);
|
|
518
|
+
const colDims = colAxes.map((ax) => t.shape[ax]);
|
|
519
|
+
const numRows = rowDims.reduce((a, b) => a * b, 1);
|
|
520
|
+
const numCols = colDims.reduce((a, b) => a * b, 1);
|
|
521
|
+
const perm = [...rowAxes, ...colAxes];
|
|
522
|
+
const permuted = t.transpose(perm);
|
|
523
|
+
return {
|
|
524
|
+
permuted,
|
|
525
|
+
rowDims,
|
|
526
|
+
colDims,
|
|
527
|
+
numRows,
|
|
528
|
+
numCols,
|
|
529
|
+
matrix: Array.from({ length: numRows }, (_, i) => Array.from({ length: numCols }, (_, j) => permuted.data[i * numCols + j]))
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
//#endregion
|
|
533
|
+
//#region src/operations/svd.ts
|
|
813
534
|
function prepareSvdMatrix(t, rowAxes) {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
}
|
|
827
|
-
if (colAxes.length === 0) {
|
|
828
|
-
throw new Error("tensorSvd: rowAxes covers all axes \u2014 at least one axis must remain for V");
|
|
829
|
-
}
|
|
830
|
-
if (rowAxes.length === 0) {
|
|
831
|
-
throw new Error("tensorSvd: rowAxes is empty \u2014 at least one axis must be assigned to U");
|
|
832
|
-
}
|
|
833
|
-
const perm = [...rowAxes, ...colAxes];
|
|
834
|
-
const permuted = t.transpose(perm);
|
|
835
|
-
const rowDims = rowAxes.map((ax) => t.shape[ax]);
|
|
836
|
-
const colDims = colAxes.map((ax) => t.shape[ax]);
|
|
837
|
-
const numRows = rowDims.reduce((a, b) => a * b, 1);
|
|
838
|
-
const numCols = colDims.reduce((a, b) => a * b, 1);
|
|
839
|
-
const matrix = Array.from(
|
|
840
|
-
{ length: numRows },
|
|
841
|
-
(_, i) => Array.from({ length: numCols }, (_2, j) => permuted.data[i * numCols + j])
|
|
842
|
-
);
|
|
843
|
-
return { matrix, rowDims, colDims, numRows, numCols };
|
|
535
|
+
const { matrix, rowDims, colDims, numRows, numCols } = reshapeByRowAxes(t, rowAxes, {
|
|
536
|
+
badAxis: (ax, rank) => `tensorSvd: rowAxes contains invalid axis ${ax} for tensor of rank ${rank}`,
|
|
537
|
+
noColAxis: "tensorSvd: rowAxes covers all axes — at least one axis must remain for V",
|
|
538
|
+
noRowAxis: "tensorSvd: rowAxes is empty — at least one axis must be assigned to U"
|
|
539
|
+
});
|
|
540
|
+
return {
|
|
541
|
+
matrix,
|
|
542
|
+
rowDims,
|
|
543
|
+
colDims,
|
|
544
|
+
numRows,
|
|
545
|
+
numCols
|
|
546
|
+
};
|
|
844
547
|
}
|
|
845
548
|
function finishSvd(result, prep, opts) {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
const vTensor = new Tensor([k, ...colDims], vData);
|
|
874
|
-
const sData = new Float64Array(k);
|
|
875
|
-
for (let j = 0; j < k; j++) {
|
|
876
|
-
sData[j] = allS[j];
|
|
877
|
-
}
|
|
878
|
-
const sTensor = new Tensor([k], sData);
|
|
879
|
-
return {
|
|
880
|
-
U: uTensor,
|
|
881
|
-
S: sTensor,
|
|
882
|
-
V: vTensor,
|
|
883
|
-
truncatedDim,
|
|
884
|
-
truncationError
|
|
885
|
-
};
|
|
549
|
+
const { rowDims, colDims, numRows, numCols } = prep;
|
|
550
|
+
const { U: fullU, S: allS, V: fullV } = result;
|
|
551
|
+
const cutoff = opts?.cutoff ?? 0;
|
|
552
|
+
const maxdim = opts?.maxdim;
|
|
553
|
+
let k = allS.filter((s) => s >= cutoff).length;
|
|
554
|
+
if (maxdim !== void 0) k = Math.min(k, maxdim);
|
|
555
|
+
k = Math.min(k, allS.length);
|
|
556
|
+
let truncationError = 0;
|
|
557
|
+
for (let i = k; i < allS.length; i++) truncationError += allS[i] * allS[i];
|
|
558
|
+
const truncatedDim = k;
|
|
559
|
+
const uData = new Float64Array(numRows * k);
|
|
560
|
+
for (let i = 0; i < numRows; i++) for (let j = 0; j < k; j++) uData[i * k + j] = fullU[i][j];
|
|
561
|
+
const uTensor = new Tensor([...rowDims, k], uData);
|
|
562
|
+
const vData = new Float64Array(k * numCols);
|
|
563
|
+
for (let j = 0; j < k; j++) for (let i = 0; i < numCols; i++) vData[j * numCols + i] = fullV[i][j];
|
|
564
|
+
const vTensor = new Tensor([k, ...colDims], vData);
|
|
565
|
+
const sData = new Float64Array(k);
|
|
566
|
+
for (let j = 0; j < k; j++) sData[j] = allS[j];
|
|
567
|
+
return {
|
|
568
|
+
U: uTensor,
|
|
569
|
+
S: new Tensor([k], sData),
|
|
570
|
+
V: vTensor,
|
|
571
|
+
truncatedDim,
|
|
572
|
+
truncationError
|
|
573
|
+
};
|
|
886
574
|
}
|
|
887
575
|
function tensorSvd(t, rowAxes, opts) {
|
|
888
|
-
|
|
889
|
-
|
|
576
|
+
const prep = prepareSvdMatrix(t, rowAxes);
|
|
577
|
+
return finishSvd(svd(prep.matrix, { tolerance: 1e-12 }), prep, opts);
|
|
890
578
|
}
|
|
891
579
|
async function tensorSvdWasm(t, rowAxes, opts) {
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
// src/operations/random.ts
|
|
897
|
-
import { DenseMatrix as DenseMatrix2, qr as matrixQr } from "@danielsimonjr/mathts-matrix";
|
|
898
|
-
function makeMulberry32(seed) {
|
|
899
|
-
let s = seed | 0;
|
|
900
|
-
return () => {
|
|
901
|
-
s = s + 1831565813 | 0;
|
|
902
|
-
let z = s;
|
|
903
|
-
z = Math.imul(z ^ z >>> 15, z | 1);
|
|
904
|
-
z ^= z + Math.imul(z ^ z >>> 7, z | 61);
|
|
905
|
-
z = (z ^ z >>> 14) >>> 0;
|
|
906
|
-
return z / 4294967296;
|
|
907
|
-
};
|
|
908
|
-
}
|
|
909
|
-
function boxMuller(u1, u2) {
|
|
910
|
-
const clamped = u1 === 0 ? Number.EPSILON : u1;
|
|
911
|
-
const r = Math.sqrt(-2 * Math.log(clamped));
|
|
912
|
-
const theta = 2 * Math.PI * u2;
|
|
913
|
-
return [r * Math.cos(theta), r * Math.sin(theta)];
|
|
580
|
+
const prep = prepareSvdMatrix(t, rowAxes);
|
|
581
|
+
return finishSvd(await svdWasm(prep.matrix, { tolerance: 1e-12 }), prep, opts);
|
|
914
582
|
}
|
|
583
|
+
//#endregion
|
|
584
|
+
//#region src/operations/random.ts
|
|
915
585
|
function thinQViaMatrixQr(data, rows, cols) {
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
586
|
+
const matA = new DenseMatrix(rows, cols, data);
|
|
587
|
+
const { Q: matQ } = qr(matA, { mode: "reduced" });
|
|
588
|
+
return matQ.toFloat64Array();
|
|
919
589
|
}
|
|
920
590
|
function randomTensor(shape, opts) {
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
if (i < size) {
|
|
960
|
-
const [z0] = boxMuller(rng(), rng());
|
|
961
|
-
data[i] = z0;
|
|
962
|
-
}
|
|
963
|
-
} else {
|
|
964
|
-
const _exhaustive = distribution;
|
|
965
|
-
throw new Error(`randomTensor: unknown distribution '${String(_exhaustive)}'`);
|
|
966
|
-
}
|
|
967
|
-
return new Tensor([...shape], data, axisLabels);
|
|
591
|
+
const distribution = opts?.distribution ?? "uniform";
|
|
592
|
+
const axisLabels = opts?.axisLabels;
|
|
593
|
+
const size = shape.reduce((acc, d) => acc * d, 1);
|
|
594
|
+
const rng = opts?.seed !== void 0 ? mulberry32(opts.seed) : () => Math.random();
|
|
595
|
+
if (distribution === "orthogonal") {
|
|
596
|
+
if (shape.length !== 2) throw new Error("randomTensor: 'orthogonal' distribution requires a rank-2 shape");
|
|
597
|
+
const [rows, cols] = shape;
|
|
598
|
+
const totalNormal = rows * cols;
|
|
599
|
+
const normalData = new Float64Array(totalNormal);
|
|
600
|
+
let idx = 0;
|
|
601
|
+
while (idx < totalNormal - 1) {
|
|
602
|
+
const [z0, z1] = boxMuller(rng(), rng());
|
|
603
|
+
normalData[idx++] = z0;
|
|
604
|
+
normalData[idx++] = z1;
|
|
605
|
+
}
|
|
606
|
+
if (idx < totalNormal) {
|
|
607
|
+
const [z0] = boxMuller(rng(), rng());
|
|
608
|
+
normalData[idx] = z0;
|
|
609
|
+
}
|
|
610
|
+
const qCols = Math.min(rows, cols);
|
|
611
|
+
const qData = thinQViaMatrixQr(normalData, rows, qCols);
|
|
612
|
+
return new Tensor([rows, qCols], qData, axisLabels);
|
|
613
|
+
}
|
|
614
|
+
const data = new Float64Array(size);
|
|
615
|
+
if (distribution === "uniform") for (let i = 0; i < size; i++) data[i] = rng();
|
|
616
|
+
else if (distribution === "normal") {
|
|
617
|
+
let i = 0;
|
|
618
|
+
while (i < size - 1) {
|
|
619
|
+
const [z0, z1] = boxMuller(rng(), rng());
|
|
620
|
+
data[i++] = z0;
|
|
621
|
+
data[i++] = z1;
|
|
622
|
+
}
|
|
623
|
+
if (i < size) {
|
|
624
|
+
const [z0] = boxMuller(rng(), rng());
|
|
625
|
+
data[i] = z0;
|
|
626
|
+
}
|
|
627
|
+
} else throw new Error(`randomTensor: unknown distribution '${String(distribution)}'`);
|
|
628
|
+
return new Tensor([...shape], data, axisLabels);
|
|
968
629
|
}
|
|
969
|
-
|
|
970
|
-
|
|
630
|
+
//#endregion
|
|
631
|
+
//#region src/contraction-sequence.ts
|
|
971
632
|
function pairwise(a, b) {
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
const resultSize = dFreeLeft * dFreeRight;
|
|
1003
|
-
return { cost, resultShape: { indices: free }, resultSize };
|
|
633
|
+
const aIsShared = new Array(a.indices.length).fill(false);
|
|
634
|
+
const bIsShared = new Array(b.indices.length).fill(false);
|
|
635
|
+
let dShared = 1;
|
|
636
|
+
for (let i = 0; i < a.indices.length; i++) for (let j = 0; j < b.indices.length; j++) {
|
|
637
|
+
if (bIsShared[j]) continue;
|
|
638
|
+
if (a.indices[i].matches(b.indices[j])) {
|
|
639
|
+
aIsShared[i] = true;
|
|
640
|
+
bIsShared[j] = true;
|
|
641
|
+
dShared *= a.indices[i].dim;
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
let dFreeLeft = 1;
|
|
646
|
+
const free = [];
|
|
647
|
+
for (let i = 0; i < a.indices.length; i++) if (!aIsShared[i]) {
|
|
648
|
+
dFreeLeft *= a.indices[i].dim;
|
|
649
|
+
free.push(a.indices[i]);
|
|
650
|
+
}
|
|
651
|
+
let dFreeRight = 1;
|
|
652
|
+
for (let j = 0; j < b.indices.length; j++) if (!bIsShared[j]) {
|
|
653
|
+
dFreeRight *= b.indices[j].dim;
|
|
654
|
+
free.push(b.indices[j]);
|
|
655
|
+
}
|
|
656
|
+
const cost = dShared * dFreeLeft * dFreeRight;
|
|
657
|
+
const resultSize = dFreeLeft * dFreeRight;
|
|
658
|
+
return {
|
|
659
|
+
cost,
|
|
660
|
+
resultShape: { indices: free },
|
|
661
|
+
resultSize
|
|
662
|
+
};
|
|
1004
663
|
}
|
|
1005
664
|
function netShape(shapes) {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
return { indices: seen.filter((e) => e.count === 1).map((e) => e.index) };
|
|
665
|
+
const seen = [];
|
|
666
|
+
for (const sh of shapes) for (const ix of sh.indices) {
|
|
667
|
+
let found = false;
|
|
668
|
+
for (const entry of seen) if (entry.index.matches(ix)) {
|
|
669
|
+
entry.count++;
|
|
670
|
+
found = true;
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
if (!found) seen.push({
|
|
674
|
+
index: ix,
|
|
675
|
+
count: 1
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
return { indices: seen.filter((e) => e.count === 1).map((e) => e.index) };
|
|
1021
679
|
}
|
|
1022
680
|
function extractShapes(tensors) {
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
}
|
|
1031
|
-
return shapes;
|
|
681
|
+
const shapes = [];
|
|
682
|
+
for (let i = 0; i < tensors.length; i++) {
|
|
683
|
+
const t = tensors[i];
|
|
684
|
+
if (t.axisLabels === void 0) throw new Error(`contractNetwork: tensor at index ${i} lacks axisLabels`);
|
|
685
|
+
shapes.push({ indices: t.axisLabels });
|
|
686
|
+
}
|
|
687
|
+
return shapes;
|
|
1032
688
|
}
|
|
1033
689
|
function buildCanonicalTable(shapes) {
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
xorSafe,
|
|
1070
|
-
uniqueIndexCount: dimsByKey.length
|
|
1071
|
-
};
|
|
690
|
+
const keyByIndex = [];
|
|
691
|
+
const dimsByKey = [];
|
|
692
|
+
const globalCount = [];
|
|
693
|
+
const perInputMaskLo = [];
|
|
694
|
+
const perInputMaskHi = [];
|
|
695
|
+
const lookupOrInsert = (ix) => {
|
|
696
|
+
for (const entry of keyByIndex) if (entry.index.matches(ix)) return entry.key;
|
|
697
|
+
const newKey = dimsByKey.length;
|
|
698
|
+
dimsByKey.push(ix.dim);
|
|
699
|
+
globalCount.push(0);
|
|
700
|
+
keyByIndex.push({
|
|
701
|
+
index: ix,
|
|
702
|
+
key: newKey
|
|
703
|
+
});
|
|
704
|
+
return newKey;
|
|
705
|
+
};
|
|
706
|
+
for (const sh of shapes) {
|
|
707
|
+
let maskLo = 0;
|
|
708
|
+
let maskHi = 0;
|
|
709
|
+
for (const ix of sh.indices) {
|
|
710
|
+
const key = lookupOrInsert(ix);
|
|
711
|
+
if (key < 30) maskLo |= 1 << key;
|
|
712
|
+
else maskHi |= 1 << key - 30;
|
|
713
|
+
globalCount[key]++;
|
|
714
|
+
}
|
|
715
|
+
perInputMaskLo.push(maskLo);
|
|
716
|
+
perInputMaskHi.push(maskHi);
|
|
717
|
+
}
|
|
718
|
+
return {
|
|
719
|
+
dimsByKey,
|
|
720
|
+
perInputMaskLo,
|
|
721
|
+
perInputMaskHi,
|
|
722
|
+
xorSafe: globalCount.every((c) => c <= 2),
|
|
723
|
+
uniqueIndexCount: dimsByKey.length
|
|
724
|
+
};
|
|
1072
725
|
}
|
|
1073
726
|
function solveExact(shapes, maxIntermediateSize) {
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
727
|
+
const N = shapes.length;
|
|
728
|
+
if (N > 30) throw new Error(`contractNetwork (exact): N=${N} exceeds bitmask cap of 30; use algorithm: 'greedy'`);
|
|
729
|
+
const table = buildCanonicalTable(shapes);
|
|
730
|
+
if (!table.xorSafe || table.uniqueIndexCount > 60) return solveExactSlow(shapes, maxIntermediateSize);
|
|
731
|
+
const dims = table.dimsByKey;
|
|
732
|
+
const dp = new Array(1 << N);
|
|
733
|
+
const maskLoOf = new Int32Array(1 << N);
|
|
734
|
+
const maskHiOf = new Int32Array(1 << N);
|
|
735
|
+
for (let i = 0; i < N; i++) {
|
|
736
|
+
const single = 1 << i;
|
|
737
|
+
dp[single] = {
|
|
738
|
+
cost: 0,
|
|
739
|
+
split: null
|
|
740
|
+
};
|
|
741
|
+
maskLoOf[single] = table.perInputMaskLo[i];
|
|
742
|
+
maskHiOf[single] = table.perInputMaskHi[i];
|
|
743
|
+
}
|
|
744
|
+
const full = (1 << N) - 1;
|
|
745
|
+
const masksByPopcount = [];
|
|
746
|
+
for (let k = 0; k <= N; k++) masksByPopcount.push([]);
|
|
747
|
+
for (let mask = 1; mask <= full; mask++) {
|
|
748
|
+
let pc = 0;
|
|
749
|
+
for (let m = mask; m > 0; m &= m - 1) pc++;
|
|
750
|
+
masksByPopcount[pc].push(mask);
|
|
751
|
+
}
|
|
752
|
+
for (let pc = 2; pc <= N; pc++) {
|
|
753
|
+
const buckets = masksByPopcount[pc];
|
|
754
|
+
for (let bi = 0; bi < buckets.length; bi++) {
|
|
755
|
+
const mask = buckets[bi];
|
|
756
|
+
let bestCost = Infinity;
|
|
757
|
+
let bestS1 = -1;
|
|
758
|
+
let bestS2 = -1;
|
|
759
|
+
let bestMaskLo = 0;
|
|
760
|
+
let bestMaskHi = 0;
|
|
761
|
+
for (let sub = mask - 1 & mask; sub > 0; sub = sub - 1 & mask) {
|
|
762
|
+
const comp = mask ^ sub;
|
|
763
|
+
if (sub > comp) continue;
|
|
764
|
+
const left = dp[sub];
|
|
765
|
+
const right = dp[comp];
|
|
766
|
+
if (!left || !right) continue;
|
|
767
|
+
const lLo = maskLoOf[sub];
|
|
768
|
+
const lHi = maskHiOf[sub];
|
|
769
|
+
const rLo = maskLoOf[comp];
|
|
770
|
+
const rHi = maskHiOf[comp];
|
|
771
|
+
const sharedLo = lLo & rLo;
|
|
772
|
+
const sharedHi = lHi & rHi;
|
|
773
|
+
const leftOnlyLo = lLo & ~rLo;
|
|
774
|
+
const leftOnlyHi = lHi & ~rHi;
|
|
775
|
+
const rightOnlyLo = rLo & ~lLo;
|
|
776
|
+
const rightOnlyHi = rHi & ~lHi;
|
|
777
|
+
let dShared = 1;
|
|
778
|
+
let m1 = sharedLo;
|
|
779
|
+
while (m1 !== 0) {
|
|
780
|
+
const lsb = m1 & -m1;
|
|
781
|
+
dShared *= dims[31 - Math.clz32(lsb)];
|
|
782
|
+
m1 ^= lsb;
|
|
783
|
+
}
|
|
784
|
+
let m2 = sharedHi;
|
|
785
|
+
while (m2 !== 0) {
|
|
786
|
+
const lsb = m2 & -m2;
|
|
787
|
+
dShared *= dims[61 - Math.clz32(lsb)];
|
|
788
|
+
m2 ^= lsb;
|
|
789
|
+
}
|
|
790
|
+
let dFreeLeft = 1;
|
|
791
|
+
m1 = leftOnlyLo;
|
|
792
|
+
while (m1 !== 0) {
|
|
793
|
+
const lsb = m1 & -m1;
|
|
794
|
+
dFreeLeft *= dims[31 - Math.clz32(lsb)];
|
|
795
|
+
m1 ^= lsb;
|
|
796
|
+
}
|
|
797
|
+
m2 = leftOnlyHi;
|
|
798
|
+
while (m2 !== 0) {
|
|
799
|
+
const lsb = m2 & -m2;
|
|
800
|
+
dFreeLeft *= dims[61 - Math.clz32(lsb)];
|
|
801
|
+
m2 ^= lsb;
|
|
802
|
+
}
|
|
803
|
+
let dFreeRight = 1;
|
|
804
|
+
m1 = rightOnlyLo;
|
|
805
|
+
while (m1 !== 0) {
|
|
806
|
+
const lsb = m1 & -m1;
|
|
807
|
+
dFreeRight *= dims[31 - Math.clz32(lsb)];
|
|
808
|
+
m1 ^= lsb;
|
|
809
|
+
}
|
|
810
|
+
m2 = rightOnlyHi;
|
|
811
|
+
while (m2 !== 0) {
|
|
812
|
+
const lsb = m2 & -m2;
|
|
813
|
+
dFreeRight *= dims[61 - Math.clz32(lsb)];
|
|
814
|
+
m2 ^= lsb;
|
|
815
|
+
}
|
|
816
|
+
const stepCost = dShared * dFreeLeft * dFreeRight;
|
|
817
|
+
if (dFreeLeft * dFreeRight > maxIntermediateSize) continue;
|
|
818
|
+
const total = left.cost + right.cost + stepCost;
|
|
819
|
+
if (total < bestCost || total === bestCost && (sub < bestS1 || sub === bestS1 && comp < bestS2)) {
|
|
820
|
+
bestCost = total;
|
|
821
|
+
bestS1 = sub;
|
|
822
|
+
bestS2 = comp;
|
|
823
|
+
bestMaskLo = lLo ^ rLo;
|
|
824
|
+
bestMaskHi = lHi ^ rHi;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (bestS1 !== -1) {
|
|
828
|
+
dp[mask] = {
|
|
829
|
+
cost: bestCost,
|
|
830
|
+
split: [bestS1, bestS2]
|
|
831
|
+
};
|
|
832
|
+
maskLoOf[mask] = bestMaskLo;
|
|
833
|
+
maskHiOf[mask] = bestMaskHi;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
if (!dp[full]) return null;
|
|
838
|
+
const steps = [];
|
|
839
|
+
const slotFor = /* @__PURE__ */ new Map();
|
|
840
|
+
for (let i = 0; i < N; i++) slotFor.set(1 << i, i);
|
|
841
|
+
let nextSlot = N;
|
|
842
|
+
const stepFor = (s1, s2) => {
|
|
843
|
+
const lLo = maskLoOf[s1];
|
|
844
|
+
const lHi = maskHiOf[s1];
|
|
845
|
+
const rLo = maskLoOf[s2];
|
|
846
|
+
const rHi = maskHiOf[s2];
|
|
847
|
+
const sharedLo = lLo & rLo;
|
|
848
|
+
const sharedHi = lHi & rHi;
|
|
849
|
+
const leftOnlyLo = lLo & ~rLo;
|
|
850
|
+
const leftOnlyHi = lHi & ~rHi;
|
|
851
|
+
const rightOnlyLo = rLo & ~lLo;
|
|
852
|
+
const rightOnlyHi = rHi & ~lHi;
|
|
853
|
+
let dShared = 1;
|
|
854
|
+
for (let m = sharedLo; m !== 0;) {
|
|
855
|
+
const lsb = m & -m;
|
|
856
|
+
dShared *= dims[31 - Math.clz32(lsb)];
|
|
857
|
+
m ^= lsb;
|
|
858
|
+
}
|
|
859
|
+
for (let m = sharedHi; m !== 0;) {
|
|
860
|
+
const lsb = m & -m;
|
|
861
|
+
dShared *= dims[61 - Math.clz32(lsb)];
|
|
862
|
+
m ^= lsb;
|
|
863
|
+
}
|
|
864
|
+
let dFL = 1;
|
|
865
|
+
for (let m = leftOnlyLo; m !== 0;) {
|
|
866
|
+
const lsb = m & -m;
|
|
867
|
+
dFL *= dims[31 - Math.clz32(lsb)];
|
|
868
|
+
m ^= lsb;
|
|
869
|
+
}
|
|
870
|
+
for (let m = leftOnlyHi; m !== 0;) {
|
|
871
|
+
const lsb = m & -m;
|
|
872
|
+
dFL *= dims[61 - Math.clz32(lsb)];
|
|
873
|
+
m ^= lsb;
|
|
874
|
+
}
|
|
875
|
+
let dFR = 1;
|
|
876
|
+
for (let m = rightOnlyLo; m !== 0;) {
|
|
877
|
+
const lsb = m & -m;
|
|
878
|
+
dFR *= dims[31 - Math.clz32(lsb)];
|
|
879
|
+
m ^= lsb;
|
|
880
|
+
}
|
|
881
|
+
for (let m = rightOnlyHi; m !== 0;) {
|
|
882
|
+
const lsb = m & -m;
|
|
883
|
+
dFR *= dims[61 - Math.clz32(lsb)];
|
|
884
|
+
m ^= lsb;
|
|
885
|
+
}
|
|
886
|
+
return {
|
|
887
|
+
cost: dShared * dFL * dFR,
|
|
888
|
+
resultSize: dFL * dFR
|
|
889
|
+
};
|
|
890
|
+
};
|
|
891
|
+
const stack = [{
|
|
892
|
+
mask: full,
|
|
893
|
+
visited: false
|
|
894
|
+
}];
|
|
895
|
+
while (stack.length > 0) {
|
|
896
|
+
const frame = stack[stack.length - 1];
|
|
897
|
+
if (frame.visited) {
|
|
898
|
+
stack.pop();
|
|
899
|
+
if (slotFor.has(frame.mask)) continue;
|
|
900
|
+
const [s1, s2] = dp[frame.mask].split;
|
|
901
|
+
const step = stepFor(s1, s2);
|
|
902
|
+
const slot1 = slotFor.get(s1);
|
|
903
|
+
const slot2 = slotFor.get(s2);
|
|
904
|
+
const orderedPair = slot1 <= slot2 ? [slot1, slot2] : [slot2, slot1];
|
|
905
|
+
steps.push({
|
|
906
|
+
pair: orderedPair,
|
|
907
|
+
cost: step.cost,
|
|
908
|
+
resultSize: step.resultSize
|
|
909
|
+
});
|
|
910
|
+
slotFor.set(frame.mask, nextSlot++);
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
frame.visited = true;
|
|
914
|
+
if (slotFor.has(frame.mask)) continue;
|
|
915
|
+
const entry = dp[frame.mask];
|
|
916
|
+
if (!entry.split) continue;
|
|
917
|
+
const [s1, s2] = entry.split;
|
|
918
|
+
stack.push({
|
|
919
|
+
mask: s2,
|
|
920
|
+
visited: false
|
|
921
|
+
});
|
|
922
|
+
stack.push({
|
|
923
|
+
mask: s1,
|
|
924
|
+
visited: false
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
return { steps };
|
|
1263
928
|
}
|
|
1264
929
|
function solveExactSlow(shapes, maxIntermediateSize) {
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
930
|
+
const N = shapes.length;
|
|
931
|
+
const dp = /* @__PURE__ */ new Map();
|
|
932
|
+
for (let i = 0; i < N; i++) dp.set(1 << i, {
|
|
933
|
+
cost: 0,
|
|
934
|
+
split: null,
|
|
935
|
+
shape: shapes[i]
|
|
936
|
+
});
|
|
937
|
+
const full = (1 << N) - 1;
|
|
938
|
+
const masksByPopcount = [];
|
|
939
|
+
for (let k = 0; k <= N; k++) masksByPopcount.push([]);
|
|
940
|
+
for (let mask = 1; mask <= full; mask++) {
|
|
941
|
+
let pc = 0;
|
|
942
|
+
for (let m = mask; m > 0; m &= m - 1) pc++;
|
|
943
|
+
masksByPopcount[pc].push(mask);
|
|
944
|
+
}
|
|
945
|
+
for (let pc = 2; pc <= N; pc++) for (const mask of masksByPopcount[pc]) {
|
|
946
|
+
let best = null;
|
|
947
|
+
for (let sub = mask - 1 & mask; sub > 0; sub = sub - 1 & mask) {
|
|
948
|
+
const comp = mask ^ sub;
|
|
949
|
+
if (sub > comp) continue;
|
|
950
|
+
const left = dp.get(sub);
|
|
951
|
+
const right = dp.get(comp);
|
|
952
|
+
if (!left || !right) continue;
|
|
953
|
+
const step = pairwise(left.shape, right.shape);
|
|
954
|
+
if (step.resultSize > maxIntermediateSize) continue;
|
|
955
|
+
const total = left.cost + right.cost + step.cost;
|
|
956
|
+
if (best === null || total < best.cost || total === best.cost && tieBreakBetter(sub, comp, best.split)) best = {
|
|
957
|
+
cost: total,
|
|
958
|
+
split: [sub, comp],
|
|
959
|
+
shape: step.resultShape
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
if (best !== null) dp.set(mask, {
|
|
963
|
+
cost: best.cost,
|
|
964
|
+
split: best.split,
|
|
965
|
+
shape: best.shape
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
if (!dp.get(full)) return null;
|
|
969
|
+
const steps = [];
|
|
970
|
+
const slotFor = /* @__PURE__ */ new Map();
|
|
971
|
+
for (let i = 0; i < N; i++) slotFor.set(1 << i, i);
|
|
972
|
+
let nextSlot = N;
|
|
973
|
+
const stack = [{
|
|
974
|
+
mask: full,
|
|
975
|
+
visited: false
|
|
976
|
+
}];
|
|
977
|
+
while (stack.length > 0) {
|
|
978
|
+
const frame = stack[stack.length - 1];
|
|
979
|
+
if (frame.visited) {
|
|
980
|
+
stack.pop();
|
|
981
|
+
if (slotFor.has(frame.mask)) continue;
|
|
982
|
+
const [s1, s2] = dp.get(frame.mask).split;
|
|
983
|
+
const left = dp.get(s1);
|
|
984
|
+
const right = dp.get(s2);
|
|
985
|
+
const step = pairwise(left.shape, right.shape);
|
|
986
|
+
const slot1 = slotFor.get(s1);
|
|
987
|
+
const slot2 = slotFor.get(s2);
|
|
988
|
+
const orderedPair = slot1 <= slot2 ? [slot1, slot2] : [slot2, slot1];
|
|
989
|
+
steps.push({
|
|
990
|
+
pair: orderedPair,
|
|
991
|
+
cost: step.cost,
|
|
992
|
+
resultSize: step.resultSize
|
|
993
|
+
});
|
|
994
|
+
slotFor.set(frame.mask, nextSlot++);
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
frame.visited = true;
|
|
998
|
+
if (slotFor.has(frame.mask)) continue;
|
|
999
|
+
const entry = dp.get(frame.mask);
|
|
1000
|
+
if (!entry.split) continue;
|
|
1001
|
+
const [s1, s2] = entry.split;
|
|
1002
|
+
stack.push({
|
|
1003
|
+
mask: s2,
|
|
1004
|
+
visited: false
|
|
1005
|
+
});
|
|
1006
|
+
stack.push({
|
|
1007
|
+
mask: s1,
|
|
1008
|
+
visited: false
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
return { steps };
|
|
1332
1012
|
}
|
|
1333
1013
|
function tieBreakBetter(a, b, prev) {
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1014
|
+
if (!prev) return true;
|
|
1015
|
+
if (a < prev[0]) return true;
|
|
1016
|
+
if (a > prev[0]) return false;
|
|
1017
|
+
return b < prev[1];
|
|
1338
1018
|
}
|
|
1339
1019
|
function solveGreedy(shapes, maxIntermediateSize) {
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1020
|
+
const N = shapes.length;
|
|
1021
|
+
const working = [];
|
|
1022
|
+
for (let i = 0; i < N; i++) working.push({
|
|
1023
|
+
slot: i,
|
|
1024
|
+
shape: shapes[i]
|
|
1025
|
+
});
|
|
1026
|
+
let nextSlot = N;
|
|
1027
|
+
const steps = [];
|
|
1028
|
+
while (working.length > 1) {
|
|
1029
|
+
let bestI = -1;
|
|
1030
|
+
let bestJ = -1;
|
|
1031
|
+
let bestCost = Infinity;
|
|
1032
|
+
let bestStep = null;
|
|
1033
|
+
let bestSharedCount = -1;
|
|
1034
|
+
for (let i = 0; i < working.length; i++) for (let j = i + 1; j < working.length; j++) {
|
|
1035
|
+
const step = pairwise(working[i].shape, working[j].shape);
|
|
1036
|
+
if (step.resultSize > maxIntermediateSize) continue;
|
|
1037
|
+
let shared = 0;
|
|
1038
|
+
for (const ix of working[i].shape.indices) for (const jx of working[j].shape.indices) if (ix.matches(jx)) {
|
|
1039
|
+
shared++;
|
|
1040
|
+
break;
|
|
1041
|
+
}
|
|
1042
|
+
if (step.cost < bestCost || step.cost === bestCost && shared > bestSharedCount || step.cost === bestCost && shared === bestSharedCount && (i < bestI || i === bestI && j < bestJ)) {
|
|
1043
|
+
bestI = i;
|
|
1044
|
+
bestJ = j;
|
|
1045
|
+
bestCost = step.cost;
|
|
1046
|
+
bestStep = step;
|
|
1047
|
+
bestSharedCount = shared;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
if (bestStep === null || bestI === -1) return null;
|
|
1051
|
+
const slotI = working[bestI].slot;
|
|
1052
|
+
const slotJ = working[bestJ].slot;
|
|
1053
|
+
const orderedPair = slotI <= slotJ ? [slotI, slotJ] : [slotJ, slotI];
|
|
1054
|
+
steps.push({
|
|
1055
|
+
pair: orderedPair,
|
|
1056
|
+
cost: bestStep.cost,
|
|
1057
|
+
resultSize: bestStep.resultSize
|
|
1058
|
+
});
|
|
1059
|
+
working.splice(bestJ, 1);
|
|
1060
|
+
working.splice(bestI, 1);
|
|
1061
|
+
working.push({
|
|
1062
|
+
slot: nextSlot++,
|
|
1063
|
+
shape: bestStep.resultShape
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
return { steps };
|
|
1386
1067
|
}
|
|
1387
1068
|
function executePlan(inputs, plan) {
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
return { result, intermediateSizes };
|
|
1069
|
+
const slots = /* @__PURE__ */ new Map();
|
|
1070
|
+
for (let i = 0; i < inputs.length; i++) slots.set(i, inputs[i]);
|
|
1071
|
+
let nextSlot = inputs.length;
|
|
1072
|
+
const intermediateSizes = [];
|
|
1073
|
+
for (const step of plan) {
|
|
1074
|
+
const [i, j] = step.pair;
|
|
1075
|
+
const a = slots.get(i);
|
|
1076
|
+
const b = slots.get(j);
|
|
1077
|
+
if (!a || !b) throw new Error(`contractNetwork: plan references unknown virtual slot (${i}, ${j}); plan is corrupted`);
|
|
1078
|
+
const out = a.contract(b);
|
|
1079
|
+
intermediateSizes.push(out.data.length);
|
|
1080
|
+
slots.set(nextSlot++, out);
|
|
1081
|
+
}
|
|
1082
|
+
const finalSlot = nextSlot - 1;
|
|
1083
|
+
const result = slots.get(finalSlot);
|
|
1084
|
+
if (!result) throw new Error("contractNetwork: empty plan produced no result");
|
|
1085
|
+
return {
|
|
1086
|
+
result,
|
|
1087
|
+
intermediateSizes
|
|
1088
|
+
};
|
|
1409
1089
|
}
|
|
1410
1090
|
function contractNetwork(tensors, opts) {
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
}
|
|
1433
|
-
void netShape(shapes);
|
|
1434
|
-
const { result, intermediateSizes } = executePlan(tensors, planResult.steps);
|
|
1435
|
-
const contractionOrder = planResult.steps.map((s) => s.pair);
|
|
1436
|
-
const totalFlops = planResult.steps.reduce((acc, s) => acc + s.cost, 0);
|
|
1437
|
-
return {
|
|
1438
|
-
result,
|
|
1439
|
-
contractionOrder,
|
|
1440
|
-
totalFlops,
|
|
1441
|
-
intermediateSizes
|
|
1442
|
-
};
|
|
1091
|
+
if (tensors.length === 0) throw new Error("contractNetwork: tensors array is empty");
|
|
1092
|
+
if (tensors.length === 1) return {
|
|
1093
|
+
result: tensors[0],
|
|
1094
|
+
contractionOrder: [],
|
|
1095
|
+
totalFlops: 0,
|
|
1096
|
+
intermediateSizes: []
|
|
1097
|
+
};
|
|
1098
|
+
const shapes = extractShapes(tensors);
|
|
1099
|
+
const maxIntermediateSize = opts?.maxIntermediateSize ?? 2 ** 31;
|
|
1100
|
+
const algorithmRaw = opts?.algorithm ?? "auto";
|
|
1101
|
+
const N = tensors.length;
|
|
1102
|
+
const planResult = (algorithmRaw === "auto" ? N <= 16 ? "exact" : "greedy" : algorithmRaw) === "exact" ? solveExact(shapes, maxIntermediateSize) : solveGreedy(shapes, maxIntermediateSize);
|
|
1103
|
+
if (planResult === null) throw new Error(`contractNetwork: no valid contraction order found under maxIntermediateSize=${maxIntermediateSize}`);
|
|
1104
|
+
netShape(shapes);
|
|
1105
|
+
const { result, intermediateSizes } = executePlan(tensors, planResult.steps);
|
|
1106
|
+
return {
|
|
1107
|
+
result,
|
|
1108
|
+
contractionOrder: planResult.steps.map((s) => s.pair),
|
|
1109
|
+
totalFlops: planResult.steps.reduce((acc, s) => acc + s.cost, 0),
|
|
1110
|
+
intermediateSizes
|
|
1111
|
+
};
|
|
1443
1112
|
}
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
import { DenseMatrix as DenseMatrix3, qr as matrixQR } from "@danielsimonjr/mathts-matrix";
|
|
1113
|
+
//#endregion
|
|
1114
|
+
//#region src/operations/qr.ts
|
|
1447
1115
|
function tensorQr(t, rowAxes, opts) {
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
const rData = rMat.toFloat64Array();
|
|
1483
|
-
let qLabels;
|
|
1484
|
-
let rLabels;
|
|
1485
|
-
if (t.axisLabels !== void 0) {
|
|
1486
|
-
const joiningDim = qCols;
|
|
1487
|
-
const joiningIndex = new Index(joiningDim, {
|
|
1488
|
-
name: opts?.joiningIndexName ?? "qr"
|
|
1489
|
-
});
|
|
1490
|
-
const rowLabels = rowAxes.map((ax) => t.axisLabels[ax]);
|
|
1491
|
-
const colLabels = colAxes.map((ax) => t.axisLabels[ax]);
|
|
1492
|
-
qLabels = [...rowLabels, joiningIndex];
|
|
1493
|
-
rLabels = [joiningIndex, ...colLabels];
|
|
1494
|
-
}
|
|
1495
|
-
const qTensor = new Tensor([...rowDims, qCols], qData, qLabels);
|
|
1496
|
-
const rTensor = new Tensor([rRows, ...colDims], rData, rLabels);
|
|
1497
|
-
return { Q: qTensor, R: rTensor };
|
|
1116
|
+
const rank = t.shape.length;
|
|
1117
|
+
const mode = opts?.mode ?? "reduced";
|
|
1118
|
+
for (const ax of rowAxes) if (!Number.isInteger(ax) || ax < 0 || ax >= rank) throw new RangeError(`tensorQr: rowAxes contains invalid axis ${ax} for tensor of rank ${rank}`);
|
|
1119
|
+
const rowSet = new Set(rowAxes);
|
|
1120
|
+
if (rowSet.size !== rowAxes.length) throw new Error("tensorQr: rowAxes contains duplicate axes");
|
|
1121
|
+
const colAxes = [];
|
|
1122
|
+
for (let i = 0; i < rank; i++) if (!rowSet.has(i)) colAxes.push(i);
|
|
1123
|
+
if (colAxes.length === 0) throw new Error("tensorQr: rowAxes covers all axes — at least one axis must remain for R");
|
|
1124
|
+
if (rowAxes.length === 0) throw new Error("tensorQr: rowAxes is empty — at least one axis must be assigned to Q");
|
|
1125
|
+
const perm = [...rowAxes, ...colAxes];
|
|
1126
|
+
const permuted = t.transpose(perm);
|
|
1127
|
+
const rowDims = rowAxes.map((ax) => t.shape[ax]);
|
|
1128
|
+
const colDims = colAxes.map((ax) => t.shape[ax]);
|
|
1129
|
+
const numRows = rowDims.reduce((a, b) => a * b, 1);
|
|
1130
|
+
const numCols = colDims.reduce((a, b) => a * b, 1);
|
|
1131
|
+
const mat = new DenseMatrix(numRows, numCols, permuted.data.slice());
|
|
1132
|
+
const { Q: qMat, R: rMat } = qr(mat, { mode });
|
|
1133
|
+
const qCols = qMat.cols;
|
|
1134
|
+
const rRows = rMat.rows;
|
|
1135
|
+
const qData = qMat.toFloat64Array();
|
|
1136
|
+
const rData = rMat.toFloat64Array();
|
|
1137
|
+
let qLabels;
|
|
1138
|
+
let rLabels;
|
|
1139
|
+
if (t.axisLabels !== void 0) {
|
|
1140
|
+
const joiningIndex = new Index(qCols, { name: opts?.joiningIndexName ?? "qr" });
|
|
1141
|
+
const rowLabels = rowAxes.map((ax) => t.axisLabels[ax]);
|
|
1142
|
+
const colLabels = colAxes.map((ax) => t.axisLabels[ax]);
|
|
1143
|
+
qLabels = [...rowLabels, joiningIndex];
|
|
1144
|
+
rLabels = [joiningIndex, ...colLabels];
|
|
1145
|
+
}
|
|
1146
|
+
return {
|
|
1147
|
+
Q: new Tensor([...rowDims, qCols], qData, qLabels),
|
|
1148
|
+
R: new Tensor([rRows, ...colDims], rData, rLabels)
|
|
1149
|
+
};
|
|
1498
1150
|
}
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
import { DenseMatrix as DenseMatrix4 } from "@danielsimonjr/mathts-matrix";
|
|
1502
|
-
import { lu as matrixLU } from "@danielsimonjr/mathts-matrix";
|
|
1151
|
+
//#endregion
|
|
1152
|
+
//#region src/operations/lu.ts
|
|
1503
1153
|
function tensorLU(t, rowAxes, opts) {
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
parity = parity === 1 ? -1 : 1;
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
const lData = matL.toFloat64Array();
|
|
1560
|
-
const uData = matU.toFloat64Array();
|
|
1561
|
-
let lLabels;
|
|
1562
|
-
let uLabels;
|
|
1563
|
-
if (t.axisLabels !== void 0) {
|
|
1564
|
-
const joiningIndex = new Index(n, {
|
|
1565
|
-
name: opts?.joiningIndexName ?? "lu"
|
|
1566
|
-
});
|
|
1567
|
-
const rowLabels = rowAxes.map((ax) => t.axisLabels[ax]);
|
|
1568
|
-
const colLabels = colAxes.map((ax) => t.axisLabels[ax]);
|
|
1569
|
-
lLabels = [...rowLabels, joiningIndex];
|
|
1570
|
-
uLabels = [joiningIndex, ...colLabels];
|
|
1571
|
-
}
|
|
1572
|
-
const L = new Tensor([...rowDims, n], lData, lLabels);
|
|
1573
|
-
const U = new Tensor([n, ...colDims], uData, uLabels);
|
|
1574
|
-
return { L, U, P, parity };
|
|
1154
|
+
const rank = t.shape.length;
|
|
1155
|
+
for (const ax of rowAxes) if (!Number.isInteger(ax) || ax < 0 || ax >= rank) throw new RangeError(`tensorLU: rowAxes contains invalid axis ${ax} for tensor of rank ${rank}`);
|
|
1156
|
+
const rowSet = new Set(rowAxes);
|
|
1157
|
+
if (rowSet.size !== rowAxes.length) throw new Error("tensorLU: rowAxes contains duplicate axes");
|
|
1158
|
+
const colAxes = [];
|
|
1159
|
+
for (let i = 0; i < rank; i++) if (!rowSet.has(i)) colAxes.push(i);
|
|
1160
|
+
if (colAxes.length === 0) throw new Error("tensorLU: rowAxes covers all axes — at least one axis must remain for U");
|
|
1161
|
+
if (rowAxes.length === 0) throw new Error("tensorLU: rowAxes is empty — at least one axis must be assigned to L");
|
|
1162
|
+
const rowDims = rowAxes.map((ax) => t.shape[ax]);
|
|
1163
|
+
const colDims = colAxes.map((ax) => t.shape[ax]);
|
|
1164
|
+
const numRows = rowDims.reduce((a, b) => a * b, 1);
|
|
1165
|
+
const numCols = colDims.reduce((a, b) => a * b, 1);
|
|
1166
|
+
if (numRows !== numCols) throw new Error(`tensorLU: reshaped matrix must be square (got ${numRows}×${numCols}); LU decomposition requires equal row and column dimensions`);
|
|
1167
|
+
const perm = [...rowAxes, ...colAxes];
|
|
1168
|
+
const permuted = t.transpose(perm);
|
|
1169
|
+
const n = numRows;
|
|
1170
|
+
const matA = new DenseMatrix(n, n, new Float64Array(permuted.data));
|
|
1171
|
+
const { L: matL, U: matU, P: matP } = lu(matA);
|
|
1172
|
+
const P = new Int32Array(matP);
|
|
1173
|
+
let parity = 1;
|
|
1174
|
+
const visited = new Uint8Array(n);
|
|
1175
|
+
for (let i = 0; i < n; i++) {
|
|
1176
|
+
if (visited[i] || P[i] === i) {
|
|
1177
|
+
visited[i] = 1;
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
let cycleLen = 0;
|
|
1181
|
+
let j = i;
|
|
1182
|
+
while (!visited[j]) {
|
|
1183
|
+
visited[j] = 1;
|
|
1184
|
+
j = P[j];
|
|
1185
|
+
cycleLen++;
|
|
1186
|
+
}
|
|
1187
|
+
if ((cycleLen - 1) % 2 !== 0) parity = parity === 1 ? -1 : 1;
|
|
1188
|
+
}
|
|
1189
|
+
const lData = matL.toFloat64Array();
|
|
1190
|
+
const uData = matU.toFloat64Array();
|
|
1191
|
+
let lLabels;
|
|
1192
|
+
let uLabels;
|
|
1193
|
+
if (t.axisLabels !== void 0) {
|
|
1194
|
+
const joiningIndex = new Index(n, { name: opts?.joiningIndexName ?? "lu" });
|
|
1195
|
+
const rowLabels = rowAxes.map((ax) => t.axisLabels[ax]);
|
|
1196
|
+
const colLabels = colAxes.map((ax) => t.axisLabels[ax]);
|
|
1197
|
+
lLabels = [...rowLabels, joiningIndex];
|
|
1198
|
+
uLabels = [joiningIndex, ...colLabels];
|
|
1199
|
+
}
|
|
1200
|
+
return {
|
|
1201
|
+
L: new Tensor([...rowDims, n], lData, lLabels),
|
|
1202
|
+
U: new Tensor([n, ...colDims], uData, uLabels),
|
|
1203
|
+
P,
|
|
1204
|
+
parity
|
|
1205
|
+
};
|
|
1575
1206
|
}
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
import { DenseMatrix as DenseMatrix5 } from "@danielsimonjr/mathts-matrix";
|
|
1579
|
-
import { cholesky as matrixCholesky } from "@danielsimonjr/mathts-matrix";
|
|
1207
|
+
//#endregion
|
|
1208
|
+
//#region src/operations/cholesky.ts
|
|
1580
1209
|
function transposeSquare(data, n) {
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
out[j * n + i] = data[i * n + j];
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
return out;
|
|
1210
|
+
const out = new Float64Array(n * n);
|
|
1211
|
+
for (let i = 0; i < n; i++) for (let j = 0; j < n; j++) out[j * n + i] = data[i * n + j];
|
|
1212
|
+
return out;
|
|
1588
1213
|
}
|
|
1589
1214
|
function tensorCholesky(t, rowAxes, opts) {
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
try {
|
|
1627
|
-
const matA = new DenseMatrix5(n, n, new Float64Array(permuted.data));
|
|
1628
|
-
const { L: matL } = matrixCholesky(matA);
|
|
1629
|
-
lData = matL.toFloat64Array();
|
|
1630
|
-
} catch (err) {
|
|
1631
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1632
|
-
throw new Error(msg.replace(/^cholesky:/, "tensorCholesky:"), { cause: err });
|
|
1633
|
-
}
|
|
1634
|
-
const factorData = lower ? lData : transposeSquare(lData, n);
|
|
1635
|
-
let factorLabels;
|
|
1636
|
-
if (t.axisLabels !== void 0) {
|
|
1637
|
-
const joiningIndex = new Index(n, {
|
|
1638
|
-
name: opts?.joiningIndexName ?? "cholesky"
|
|
1639
|
-
});
|
|
1640
|
-
const rowLabels = rowAxes.map((ax) => t.axisLabels[ax]);
|
|
1641
|
-
const colLabels = colAxes.map((ax) => t.axisLabels[ax]);
|
|
1642
|
-
if (lower) {
|
|
1643
|
-
factorLabels = [...rowLabels, joiningIndex];
|
|
1644
|
-
} else {
|
|
1645
|
-
factorLabels = [joiningIndex, ...colLabels];
|
|
1646
|
-
}
|
|
1647
|
-
}
|
|
1648
|
-
const factorShape = lower ? [...rowDims, n] : [n, ...colDims];
|
|
1649
|
-
const factor = new Tensor(factorShape, factorData, factorLabels);
|
|
1650
|
-
return { L: factor };
|
|
1215
|
+
const rank = t.shape.length;
|
|
1216
|
+
const lower = opts?.lower ?? true;
|
|
1217
|
+
for (const ax of rowAxes) if (!Number.isInteger(ax) || ax < 0 || ax >= rank) throw new RangeError(`tensorCholesky: rowAxes contains invalid axis ${ax} for tensor of rank ${rank}`);
|
|
1218
|
+
const rowSet = new Set(rowAxes);
|
|
1219
|
+
if (rowSet.size !== rowAxes.length) throw new Error("tensorCholesky: rowAxes contains duplicate axes");
|
|
1220
|
+
const colAxes = [];
|
|
1221
|
+
for (let i = 0; i < rank; i++) if (!rowSet.has(i)) colAxes.push(i);
|
|
1222
|
+
if (colAxes.length === 0) throw new Error("tensorCholesky: rowAxes covers all axes — at least one axis must remain for the column factor");
|
|
1223
|
+
if (rowAxes.length === 0) throw new Error("tensorCholesky: rowAxes is empty — at least one axis must be assigned");
|
|
1224
|
+
const rowDims = rowAxes.map((ax) => t.shape[ax]);
|
|
1225
|
+
const colDims = colAxes.map((ax) => t.shape[ax]);
|
|
1226
|
+
const numRows = rowDims.reduce((a, b) => a * b, 1);
|
|
1227
|
+
const numCols = colDims.reduce((a, b) => a * b, 1);
|
|
1228
|
+
if (numRows !== numCols) throw new Error(`tensorCholesky: reshaped matrix must be square (got ${numRows}×${numCols})`);
|
|
1229
|
+
const perm = [...rowAxes, ...colAxes];
|
|
1230
|
+
const permuted = t.transpose(perm);
|
|
1231
|
+
const n = numRows;
|
|
1232
|
+
let lData;
|
|
1233
|
+
try {
|
|
1234
|
+
const matA = new DenseMatrix(n, n, new Float64Array(permuted.data));
|
|
1235
|
+
const { L: matL } = cholesky(matA);
|
|
1236
|
+
lData = matL.toFloat64Array();
|
|
1237
|
+
} catch (err) {
|
|
1238
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1239
|
+
throw new Error(msg.replace(/^cholesky:/, "tensorCholesky:"), { cause: err });
|
|
1240
|
+
}
|
|
1241
|
+
const factorData = lower ? lData : transposeSquare(lData, n);
|
|
1242
|
+
let factorLabels;
|
|
1243
|
+
if (t.axisLabels !== void 0) {
|
|
1244
|
+
const joiningIndex = new Index(n, { name: opts?.joiningIndexName ?? "cholesky" });
|
|
1245
|
+
const rowLabels = rowAxes.map((ax) => t.axisLabels[ax]);
|
|
1246
|
+
const colLabels = colAxes.map((ax) => t.axisLabels[ax]);
|
|
1247
|
+
if (lower) factorLabels = [...rowLabels, joiningIndex];
|
|
1248
|
+
else factorLabels = [joiningIndex, ...colLabels];
|
|
1249
|
+
}
|
|
1250
|
+
return { L: new Tensor(lower ? [...rowDims, n] : [n, ...colDims], factorData, factorLabels) };
|
|
1651
1251
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
import { eig as matrixEig, eigWasm } from "@danielsimonjr/mathts-matrix";
|
|
1252
|
+
//#endregion
|
|
1253
|
+
//#region src/operations/eig.ts
|
|
1655
1254
|
function prepareEigMatrix(t, rowAxes, symmetricHint) {
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
throw new Error("tensorEig: rowAxes is empty \u2014 at least one axis must be assigned");
|
|
1677
|
-
}
|
|
1678
|
-
const rowDims = rowAxes.map((ax) => t.shape[ax]);
|
|
1679
|
-
const colDims = colAxes.map((ax) => t.shape[ax]);
|
|
1680
|
-
const numRows = rowDims.reduce((a, b) => a * b, 1);
|
|
1681
|
-
const numCols = colDims.reduce((a, b) => a * b, 1);
|
|
1682
|
-
if (numRows !== numCols) {
|
|
1683
|
-
throw new Error(`tensorEig: reshaped matrix must be square (got ${numRows}\xD7${numCols})`);
|
|
1684
|
-
}
|
|
1685
|
-
const perm = [...rowAxes, ...colAxes];
|
|
1686
|
-
const permuted = t.transpose(perm);
|
|
1687
|
-
const n = numRows;
|
|
1688
|
-
const matrix2D = Array.from(
|
|
1689
|
-
{ length: n },
|
|
1690
|
-
(_, i) => Array.from({ length: n }, (_2, j) => permuted.data[i * n + j])
|
|
1691
|
-
);
|
|
1692
|
-
if (symmetricHint) {
|
|
1693
|
-
for (let i = 0; i < n; i++) {
|
|
1694
|
-
for (let j = i + 1; j < n; j++) {
|
|
1695
|
-
const avg = (matrix2D[i][j] + matrix2D[j][i]) / 2;
|
|
1696
|
-
matrix2D[i][j] = avg;
|
|
1697
|
-
matrix2D[j][i] = avg;
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
}
|
|
1701
|
-
return { matrix2D, n };
|
|
1255
|
+
const rank = t.shape.length;
|
|
1256
|
+
for (const ax of rowAxes) if (!Number.isInteger(ax) || ax < 0 || ax >= rank) throw new RangeError(`tensorEig: rowAxes contains invalid axis ${ax} for tensor of rank ${rank}`);
|
|
1257
|
+
if (new Set(rowAxes).size !== rowAxes.length) throw new Error("tensorEig: rowAxes contains duplicate axes");
|
|
1258
|
+
const { numRows, numCols, matrix } = reshapeByRowAxes(t, rowAxes, {
|
|
1259
|
+
badAxis: (ax, rank) => `tensorEig: rowAxes contains invalid axis ${ax} for tensor of rank ${rank}`,
|
|
1260
|
+
noColAxis: "tensorEig: rowAxes covers all axes — at least one axis must remain",
|
|
1261
|
+
noRowAxis: "tensorEig: rowAxes is empty — at least one axis must be assigned"
|
|
1262
|
+
});
|
|
1263
|
+
if (numRows !== numCols) throw new Error(`tensorEig: reshaped matrix must be square (got ${numRows}×${numCols})`);
|
|
1264
|
+
const n = numRows;
|
|
1265
|
+
const matrix2D = matrix;
|
|
1266
|
+
if (symmetricHint) for (let i = 0; i < n; i++) for (let j = i + 1; j < n; j++) {
|
|
1267
|
+
const avg = (matrix2D[i][j] + matrix2D[j][i]) / 2;
|
|
1268
|
+
matrix2D[i][j] = avg;
|
|
1269
|
+
matrix2D[j][i] = avg;
|
|
1270
|
+
}
|
|
1271
|
+
return {
|
|
1272
|
+
matrix2D,
|
|
1273
|
+
n
|
|
1274
|
+
};
|
|
1702
1275
|
}
|
|
1703
1276
|
function finishEig(eigResult, n, computeVectors, symmetricHint) {
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
}
|
|
1725
|
-
}
|
|
1726
|
-
result.eigenvectors = new Tensor([n, n], vData);
|
|
1727
|
-
}
|
|
1728
|
-
return result;
|
|
1277
|
+
const { values, vectors } = eigResult;
|
|
1278
|
+
const reData = new Float64Array(n);
|
|
1279
|
+
const imData = new Float64Array(n);
|
|
1280
|
+
let anyImaginary = false;
|
|
1281
|
+
for (let i = 0; i < n; i++) {
|
|
1282
|
+
reData[i] = values[i].re;
|
|
1283
|
+
imData[i] = values[i].im;
|
|
1284
|
+
if (values[i].im !== 0) anyImaginary = true;
|
|
1285
|
+
}
|
|
1286
|
+
const result = { eigenvalues: new Tensor([n], reData) };
|
|
1287
|
+
if (anyImaginary && !symmetricHint) result.eigenvaluesImaginary = new Tensor([n], imData);
|
|
1288
|
+
if (computeVectors) {
|
|
1289
|
+
const vData = new Float64Array(n * n);
|
|
1290
|
+
for (let i = 0; i < n; i++) {
|
|
1291
|
+
const v = vectors[i];
|
|
1292
|
+
for (let r = 0; r < n; r++) vData[r * n + i] = v[r];
|
|
1293
|
+
}
|
|
1294
|
+
result.eigenvectors = new Tensor([n, n], vData);
|
|
1295
|
+
}
|
|
1296
|
+
return result;
|
|
1729
1297
|
}
|
|
1730
1298
|
function tensorEig(t, rowAxes, opts) {
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1299
|
+
const computeVectors = opts?.computeVectors ?? true;
|
|
1300
|
+
const symmetricHint = opts?.symmetric ?? false;
|
|
1301
|
+
const { matrix2D, n } = prepareEigMatrix(t, rowAxes, symmetricHint);
|
|
1302
|
+
return finishEig(eig(matrix2D, { computeVectors }), n, computeVectors, symmetricHint);
|
|
1735
1303
|
}
|
|
1736
1304
|
async function tensorEigWasm(t, rowAxes, opts) {
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1305
|
+
const computeVectors = opts?.computeVectors ?? true;
|
|
1306
|
+
const symmetricHint = opts?.symmetric ?? false;
|
|
1307
|
+
const { matrix2D, n } = prepareEigMatrix(t, rowAxes, symmetricHint);
|
|
1308
|
+
return finishEig(await eigWasm(matrix2D, { computeVectors }), n, computeVectors, symmetricHint);
|
|
1741
1309
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
import { svd as svd2 } from "@danielsimonjr/mathts-matrix";
|
|
1310
|
+
//#endregion
|
|
1311
|
+
//#region src/operations/pinv.ts
|
|
1745
1312
|
function tensorPinv(t, rowAxes, opts) {
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
const rowDims = resolvedRowAxes.map((ax) => t.shape[ax]);
|
|
1787
|
-
const colDims = colAxes.map((ax) => t.shape[ax]);
|
|
1788
|
-
const numRows = rowDims.reduce((a, b) => a * b, 1);
|
|
1789
|
-
const numCols = colDims.reduce((a, b) => a * b, 1);
|
|
1790
|
-
const matrix2D = Array.from(
|
|
1791
|
-
{ length: numRows },
|
|
1792
|
-
(_, i) => Array.from({ length: numCols }, (_2, j) => permuted.data[i * numCols + j])
|
|
1793
|
-
);
|
|
1794
|
-
const { U, S, V } = svd2(matrix2D, { tolerance: 1e-12 });
|
|
1795
|
-
const maxS = S.length > 0 ? S[0] : 0;
|
|
1796
|
-
const Sinv = S.map((s) => s > rcond * maxS ? 1 / s : 0);
|
|
1797
|
-
const k = S.length;
|
|
1798
|
-
const pinvData = new Float64Array(numCols * numRows);
|
|
1799
|
-
for (let i = 0; i < numCols; i++) {
|
|
1800
|
-
for (let j = 0; j < numRows; j++) {
|
|
1801
|
-
let sum = 0;
|
|
1802
|
-
for (let l = 0; l < k; l++) {
|
|
1803
|
-
sum += V[i][l] * Sinv[l] * U[j][l];
|
|
1804
|
-
}
|
|
1805
|
-
pinvData[i * numRows + j] = sum;
|
|
1806
|
-
}
|
|
1807
|
-
}
|
|
1808
|
-
const outShape = [...colDims, ...rowDims];
|
|
1809
|
-
let outLabels;
|
|
1810
|
-
if (t.axisLabels !== void 0) {
|
|
1811
|
-
const rowLabels = resolvedRowAxes.map((ax) => t.axisLabels[ax]);
|
|
1812
|
-
const colLabels = colAxes.map((ax) => t.axisLabels[ax]);
|
|
1813
|
-
outLabels = [...colLabels, ...rowLabels];
|
|
1814
|
-
}
|
|
1815
|
-
return new Tensor(outShape, pinvData, outLabels);
|
|
1313
|
+
const rcond = opts?.rcond ?? 1e-10;
|
|
1314
|
+
const rank = t.shape.length;
|
|
1315
|
+
const resolvedRowAxes = rowAxes.map((ax) => {
|
|
1316
|
+
if (typeof ax === "number") return ax;
|
|
1317
|
+
if (t.axisLabels === void 0) throw new Error("tensorPinv: received Index in rowAxes but tensor has no axisLabels");
|
|
1318
|
+
const pos = t.axisLabels.findIndex((lbl) => lbl.matches(ax));
|
|
1319
|
+
if (pos === -1) throw new Error(`tensorPinv: Index "${ax.name ?? ax.id.toString()}" not found in tensor axisLabels`);
|
|
1320
|
+
return pos;
|
|
1321
|
+
});
|
|
1322
|
+
for (const ax of resolvedRowAxes) if (!Number.isInteger(ax) || ax < 0 || ax >= rank) throw new RangeError(`tensorPinv: rowAxes contains invalid axis ${ax} for tensor of rank ${rank}`);
|
|
1323
|
+
const rowSet = new Set(resolvedRowAxes);
|
|
1324
|
+
if (rowSet.size !== resolvedRowAxes.length) throw new Error("tensorPinv: rowAxes contains duplicate axes");
|
|
1325
|
+
const colAxes = [];
|
|
1326
|
+
for (let i = 0; i < rank; i++) if (!rowSet.has(i)) colAxes.push(i);
|
|
1327
|
+
if (colAxes.length === 0) throw new Error("tensorPinv: rowAxes covers all axes — at least one col axis is required");
|
|
1328
|
+
if (resolvedRowAxes.length === 0) throw new Error("tensorPinv: rowAxes is empty — at least one row axis is required");
|
|
1329
|
+
const perm = [...resolvedRowAxes, ...colAxes];
|
|
1330
|
+
const permuted = t.transpose(perm);
|
|
1331
|
+
const rowDims = resolvedRowAxes.map((ax) => t.shape[ax]);
|
|
1332
|
+
const colDims = colAxes.map((ax) => t.shape[ax]);
|
|
1333
|
+
const numRows = rowDims.reduce((a, b) => a * b, 1);
|
|
1334
|
+
const numCols = colDims.reduce((a, b) => a * b, 1);
|
|
1335
|
+
const matrix2D = Array.from({ length: numRows }, (_, i) => Array.from({ length: numCols }, (_, j) => permuted.data[i * numCols + j]));
|
|
1336
|
+
const { U, S, V } = svd(matrix2D, { tolerance: 1e-12 });
|
|
1337
|
+
const maxS = S.length > 0 ? S[0] : 0;
|
|
1338
|
+
const Sinv = S.map((s) => s > rcond * maxS ? 1 / s : 0);
|
|
1339
|
+
const k = S.length;
|
|
1340
|
+
const pinvData = new Float64Array(numCols * numRows);
|
|
1341
|
+
for (let i = 0; i < numCols; i++) for (let j = 0; j < numRows; j++) {
|
|
1342
|
+
let sum = 0;
|
|
1343
|
+
for (let l = 0; l < k; l++) sum += V[i][l] * Sinv[l] * U[j][l];
|
|
1344
|
+
pinvData[i * numRows + j] = sum;
|
|
1345
|
+
}
|
|
1346
|
+
const outShape = [...colDims, ...rowDims];
|
|
1347
|
+
let outLabels;
|
|
1348
|
+
if (t.axisLabels !== void 0) {
|
|
1349
|
+
const rowLabels = resolvedRowAxes.map((ax) => t.axisLabels[ax]);
|
|
1350
|
+
outLabels = [...colAxes.map((ax) => t.axisLabels[ax]), ...rowLabels];
|
|
1351
|
+
}
|
|
1352
|
+
return new Tensor(outShape, pinvData, outLabels);
|
|
1816
1353
|
}
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
import { DenseMatrix as DenseMatrix6 } from "@danielsimonjr/mathts-matrix";
|
|
1820
|
-
import { lu as matrixLU2 } from "@danielsimonjr/mathts-matrix";
|
|
1354
|
+
//#endregion
|
|
1355
|
+
//#region src/operations/solve.ts
|
|
1821
1356
|
function resolveAxes(axes, t, label) {
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
throw new Error(
|
|
1830
|
-
`${label}: Index "${ax.name ?? ax.id.toString()}" not found in tensor axisLabels`
|
|
1831
|
-
);
|
|
1832
|
-
}
|
|
1833
|
-
return pos;
|
|
1834
|
-
});
|
|
1357
|
+
return axes.map((ax) => {
|
|
1358
|
+
if (typeof ax === "number") return ax;
|
|
1359
|
+
if (t.axisLabels === void 0) throw new Error(`${label}: received Index in axes but tensor has no axisLabels`);
|
|
1360
|
+
const pos = t.axisLabels.findIndex((lbl) => lbl.id === ax.id);
|
|
1361
|
+
if (pos === -1) throw new Error(`${label}: Index "${ax.name ?? ax.id.toString()}" not found in tensor axisLabels`);
|
|
1362
|
+
return pos;
|
|
1363
|
+
});
|
|
1835
1364
|
}
|
|
1836
1365
|
function forwardSub(L, Pb, n, nRHS) {
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
y[i * nRHS + r] = sum;
|
|
1845
|
-
}
|
|
1846
|
-
}
|
|
1847
|
-
return y;
|
|
1366
|
+
const y = new Float64Array(n * nRHS);
|
|
1367
|
+
for (let i = 0; i < n; i++) for (let r = 0; r < nRHS; r++) {
|
|
1368
|
+
let sum = Pb[i * nRHS + r];
|
|
1369
|
+
for (let j = 0; j < i; j++) sum -= L[i * n + j] * y[j * nRHS + r];
|
|
1370
|
+
y[i * nRHS + r] = sum;
|
|
1371
|
+
}
|
|
1372
|
+
return y;
|
|
1848
1373
|
}
|
|
1849
1374
|
function backSub(U, y, n, nRHS) {
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
x[i * nRHS + r] = sum / U[i * n + i];
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
1860
|
-
return x;
|
|
1375
|
+
const x = new Float64Array(n * nRHS);
|
|
1376
|
+
for (let i = n - 1; i >= 0; i--) for (let r = 0; r < nRHS; r++) {
|
|
1377
|
+
let sum = y[i * nRHS + r];
|
|
1378
|
+
for (let j = i + 1; j < n; j++) sum -= U[i * n + j] * x[j * nRHS + r];
|
|
1379
|
+
x[i * nRHS + r] = sum / U[i * n + i];
|
|
1380
|
+
}
|
|
1381
|
+
return x;
|
|
1861
1382
|
}
|
|
1862
1383
|
function tensorSolve(A, b, opts) {
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
}
|
|
1931
|
-
const rowDimsA = rowAxesA.map((ax) => A.shape[ax]);
|
|
1932
|
-
const colDimsA = colAxesA.map((ax) => A.shape[ax]);
|
|
1933
|
-
const rowDimsB = rowAxesB.map((ax) => b.shape[ax]);
|
|
1934
|
-
const colDimsB = colAxesB.map((ax) => b.shape[ax]);
|
|
1935
|
-
const numRowsA = rowDimsA.reduce((a, v) => a * v, 1);
|
|
1936
|
-
const numColsA = colDimsA.reduce((a, v) => a * v, 1);
|
|
1937
|
-
const numRowsB = rowDimsB.reduce((a, v) => a * v, 1);
|
|
1938
|
-
const numColsB = colDimsB.length > 0 ? colDimsB.reduce((a, v) => a * v, 1) : 1;
|
|
1939
|
-
if (numRowsA !== numColsA) {
|
|
1940
|
-
throw new Error(
|
|
1941
|
-
`tensorSolve: reshaped A must be square (got ${numRowsA}\xD7${numColsA}); non-square systems are not supported \u2014 use tensorPinv for least-squares`
|
|
1942
|
-
);
|
|
1943
|
-
}
|
|
1944
|
-
const n = numRowsA;
|
|
1945
|
-
if (numRowsB !== n) {
|
|
1946
|
-
throw new Error(
|
|
1947
|
-
`tensorSolve: b's contracting dimension (${numRowsB}) does not match A's dimension (${n})`
|
|
1948
|
-
);
|
|
1949
|
-
}
|
|
1950
|
-
const permA = [...rowAxesA, ...colAxesA];
|
|
1951
|
-
const permutedA = A.transpose(permA);
|
|
1952
|
-
const matA = new DenseMatrix6(n, n, new Float64Array(permutedA.data));
|
|
1953
|
-
const { L, U, P } = matrixLU2(matA);
|
|
1954
|
-
const lFlat = L.toFloat64Array();
|
|
1955
|
-
const uFlat = U.toFloat64Array();
|
|
1956
|
-
const permB = [...rowAxesB, ...colAxesB];
|
|
1957
|
-
const permutedB = b.transpose(permB);
|
|
1958
|
-
const bFlat = permutedB.data;
|
|
1959
|
-
const Pb = new Float64Array(n * numColsB);
|
|
1960
|
-
for (let i = 0; i < n; i++) {
|
|
1961
|
-
for (let r = 0; r < numColsB; r++) {
|
|
1962
|
-
Pb[i * numColsB + r] = bFlat[P[i] * numColsB + r];
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
const y = forwardSub(lFlat, Pb, n, numColsB);
|
|
1966
|
-
const xFlat = backSub(uFlat, y, n, numColsB);
|
|
1967
|
-
const outShape = [...colDimsA, ...colDimsB];
|
|
1968
|
-
const finalShape = outShape.length > 0 ? outShape : [n];
|
|
1969
|
-
let outLabels;
|
|
1970
|
-
if (A.axisLabels !== void 0 || b.axisLabels !== void 0) {
|
|
1971
|
-
const aColLabels = A.axisLabels ? colAxesA.map((ax) => A.axisLabels[ax]) : [];
|
|
1972
|
-
const bColLabels = b.axisLabels ? colAxesB.map((ax) => b.axisLabels[ax]) : [];
|
|
1973
|
-
if (aColLabels.length + bColLabels.length === finalShape.length) {
|
|
1974
|
-
outLabels = [...aColLabels, ...bColLabels];
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
const xTensor = new Tensor(finalShape, xFlat, outLabels?.length ? outLabels : void 0);
|
|
1978
|
-
return { x: xTensor };
|
|
1384
|
+
const rankA = A.shape.length;
|
|
1385
|
+
const rankB = b.shape.length;
|
|
1386
|
+
let rowAxesA;
|
|
1387
|
+
if (opts?.rowAxesA !== void 0) rowAxesA = resolveAxes(opts.rowAxesA, A, "tensorSolve(A)");
|
|
1388
|
+
else if (A.axisLabels !== void 0 && b.axisLabels !== void 0) {
|
|
1389
|
+
rowAxesA = [];
|
|
1390
|
+
for (let i = 0; i < rankA; i++) {
|
|
1391
|
+
const lbl = A.axisLabels[i];
|
|
1392
|
+
if (b.axisLabels.some((blbl) => blbl.id === lbl.id)) rowAxesA.push(i);
|
|
1393
|
+
}
|
|
1394
|
+
if (rowAxesA.length === 0) {
|
|
1395
|
+
const half = Math.ceil(rankA / 2);
|
|
1396
|
+
rowAxesA = Array.from({ length: half }, (_, i) => i);
|
|
1397
|
+
}
|
|
1398
|
+
} else {
|
|
1399
|
+
const half = Math.ceil(rankA / 2);
|
|
1400
|
+
rowAxesA = Array.from({ length: half }, (_, i) => i);
|
|
1401
|
+
}
|
|
1402
|
+
let rowAxesB;
|
|
1403
|
+
if (opts?.rowAxesB !== void 0) rowAxesB = resolveAxes(opts.rowAxesB, b, "tensorSolve(b)");
|
|
1404
|
+
else if (A.axisLabels !== void 0 && b.axisLabels !== void 0) {
|
|
1405
|
+
const aRowIds = new Set(rowAxesA.map((ax) => A.axisLabels[ax].id));
|
|
1406
|
+
rowAxesB = [];
|
|
1407
|
+
for (let i = 0; i < rankB; i++) if (aRowIds.has(b.axisLabels[i].id)) rowAxesB.push(i);
|
|
1408
|
+
if (rowAxesB.length === 0) rowAxesB = [0];
|
|
1409
|
+
} else rowAxesB = [0];
|
|
1410
|
+
for (const ax of rowAxesA) if (!Number.isInteger(ax) || ax < 0 || ax >= rankA) throw new RangeError(`tensorSolve: rowAxesA contains invalid axis ${ax} for tensor A of rank ${rankA}`);
|
|
1411
|
+
const rowSetA = new Set(rowAxesA);
|
|
1412
|
+
if (rowSetA.size !== rowAxesA.length) throw new Error("tensorSolve: rowAxesA contains duplicate axes");
|
|
1413
|
+
for (const ax of rowAxesB) if (!Number.isInteger(ax) || ax < 0 || ax >= rankB) throw new RangeError(`tensorSolve: rowAxesB contains invalid axis ${ax} for tensor b of rank ${rankB}`);
|
|
1414
|
+
const rowSetB = new Set(rowAxesB);
|
|
1415
|
+
if (rowSetB.size !== rowAxesB.length) throw new Error("tensorSolve: rowAxesB contains duplicate axes");
|
|
1416
|
+
const colAxesA = [];
|
|
1417
|
+
for (let i = 0; i < rankA; i++) if (!rowSetA.has(i)) colAxesA.push(i);
|
|
1418
|
+
const colAxesB = [];
|
|
1419
|
+
for (let i = 0; i < rankB; i++) if (!rowSetB.has(i)) colAxesB.push(i);
|
|
1420
|
+
const rowDimsA = rowAxesA.map((ax) => A.shape[ax]);
|
|
1421
|
+
const colDimsA = colAxesA.map((ax) => A.shape[ax]);
|
|
1422
|
+
const rowDimsB = rowAxesB.map((ax) => b.shape[ax]);
|
|
1423
|
+
const colDimsB = colAxesB.map((ax) => b.shape[ax]);
|
|
1424
|
+
const numRowsA = rowDimsA.reduce((a, v) => a * v, 1);
|
|
1425
|
+
const numColsA = colDimsA.reduce((a, v) => a * v, 1);
|
|
1426
|
+
const numRowsB = rowDimsB.reduce((a, v) => a * v, 1);
|
|
1427
|
+
const numColsB = colDimsB.length > 0 ? colDimsB.reduce((a, v) => a * v, 1) : 1;
|
|
1428
|
+
if (numRowsA !== numColsA) throw new Error(`tensorSolve: reshaped A must be square (got ${numRowsA}×${numColsA}); non-square systems are not supported — use tensorPinv for least-squares`);
|
|
1429
|
+
const n = numRowsA;
|
|
1430
|
+
if (numRowsB !== n) throw new Error(`tensorSolve: b's contracting dimension (${numRowsB}) does not match A's dimension (${n})`);
|
|
1431
|
+
const permA = [...rowAxesA, ...colAxesA];
|
|
1432
|
+
const permutedA = A.transpose(permA);
|
|
1433
|
+
const matA = new DenseMatrix(n, n, new Float64Array(permutedA.data));
|
|
1434
|
+
const { L, U, P } = lu(matA);
|
|
1435
|
+
const lFlat = L.toFloat64Array();
|
|
1436
|
+
const uFlat = U.toFloat64Array();
|
|
1437
|
+
const permB = [...rowAxesB, ...colAxesB];
|
|
1438
|
+
const bFlat = b.transpose(permB).data;
|
|
1439
|
+
const Pb = new Float64Array(n * numColsB);
|
|
1440
|
+
for (let i = 0; i < n; i++) for (let r = 0; r < numColsB; r++) Pb[i * numColsB + r] = bFlat[P[i] * numColsB + r];
|
|
1441
|
+
const xFlat = backSub(uFlat, forwardSub(lFlat, Pb, n, numColsB), n, numColsB);
|
|
1442
|
+
const outShape = [...colDimsA, ...colDimsB];
|
|
1443
|
+
const finalShape = outShape.length > 0 ? outShape : [n];
|
|
1444
|
+
let outLabels;
|
|
1445
|
+
if (A.axisLabels !== void 0 || b.axisLabels !== void 0) {
|
|
1446
|
+
const aColLabels = A.axisLabels ? colAxesA.map((ax) => A.axisLabels[ax]) : [];
|
|
1447
|
+
const bColLabels = b.axisLabels ? colAxesB.map((ax) => b.axisLabels[ax]) : [];
|
|
1448
|
+
if (aColLabels.length + bColLabels.length === finalShape.length) outLabels = [...aColLabels, ...bColLabels];
|
|
1449
|
+
}
|
|
1450
|
+
return { x: new Tensor(finalShape, xFlat, outLabels?.length ? outLabels : void 0) };
|
|
1979
1451
|
}
|
|
1980
|
-
|
|
1981
|
-
|
|
1452
|
+
//#endregion
|
|
1453
|
+
//#region src/operations/kron.ts
|
|
1982
1454
|
function tensorKron(a, b, opts) {
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
}
|
|
2040
|
-
if (k >= bPadded) {
|
|
2041
|
-
flatB += bIdx * bOrigStrides[k - bPadded];
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
result[flatOut] = aData[flatA] * bData[flatB];
|
|
2045
|
-
}
|
|
2046
|
-
let outLabels;
|
|
2047
|
-
if (aLabels !== void 0 && bLabels !== void 0) {
|
|
2048
|
-
outLabels = aLabels.map((al, k) => {
|
|
2049
|
-
const bl = bLabels[k];
|
|
2050
|
-
const aName = al.name ?? `a${k}`;
|
|
2051
|
-
const bName = bl.name ?? `b${k}`;
|
|
2052
|
-
const outDim = outShape[k];
|
|
2053
|
-
return new Index(outDim, { name: `${aName}${separator}${bName}` });
|
|
2054
|
-
});
|
|
2055
|
-
}
|
|
2056
|
-
return new Tensor(outShape, result, outLabels);
|
|
1455
|
+
const separator = opts?.separator ?? "_X_";
|
|
1456
|
+
let aShape = [...a.shape];
|
|
1457
|
+
let bShape = [...b.shape];
|
|
1458
|
+
let aLabels = a.axisLabels ? [...a.axisLabels] : void 0;
|
|
1459
|
+
let bLabels = b.axisLabels ? [...b.axisLabels] : void 0;
|
|
1460
|
+
const rankA = aShape.length;
|
|
1461
|
+
const rankB = bShape.length;
|
|
1462
|
+
const outRank = Math.max(rankA, rankB);
|
|
1463
|
+
if (rankA < outRank) {
|
|
1464
|
+
const pad = outRank - rankA;
|
|
1465
|
+
aShape = [...new Array(pad).fill(1), ...aShape];
|
|
1466
|
+
if (aLabels !== void 0) aLabels = [...Array.from({ length: pad }, (_, i) => new Index(1, { name: `kron_pad_a_${i}` })), ...aLabels];
|
|
1467
|
+
}
|
|
1468
|
+
if (rankB < outRank) {
|
|
1469
|
+
const pad = outRank - rankB;
|
|
1470
|
+
bShape = [...new Array(pad).fill(1), ...bShape];
|
|
1471
|
+
if (bLabels !== void 0) bLabels = [...Array.from({ length: pad }, (_, i) => new Index(1, { name: `kron_pad_b_${i}` })), ...bLabels];
|
|
1472
|
+
}
|
|
1473
|
+
const outShape = aShape.map((d, k) => d * bShape[k]);
|
|
1474
|
+
const outSize = outShape.reduce((acc, d) => acc * d, 1);
|
|
1475
|
+
const outStrides = Tensor.rowMajorStrides(outShape);
|
|
1476
|
+
const aData = a.data;
|
|
1477
|
+
const bData = b.data;
|
|
1478
|
+
const aOrigStrides = Tensor.rowMajorStrides(aShape.slice(outRank - rankA));
|
|
1479
|
+
const bOrigStrides = Tensor.rowMajorStrides(bShape.slice(outRank - rankB));
|
|
1480
|
+
const result = new Float64Array(outSize);
|
|
1481
|
+
const outIdx = new Array(outRank).fill(0);
|
|
1482
|
+
for (let flatOut = 0; flatOut < outSize; flatOut++) {
|
|
1483
|
+
{
|
|
1484
|
+
let rem = flatOut;
|
|
1485
|
+
for (let k = 0; k < outRank; k++) {
|
|
1486
|
+
outIdx[k] = Math.floor(rem / outStrides[k]);
|
|
1487
|
+
rem -= outIdx[k] * outStrides[k];
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
let flatA = 0;
|
|
1491
|
+
let flatB = 0;
|
|
1492
|
+
for (let k = 0; k < outRank; k++) {
|
|
1493
|
+
const aIdx = Math.floor(outIdx[k] / bShape[k]);
|
|
1494
|
+
const bIdx = outIdx[k] % bShape[k];
|
|
1495
|
+
const aPadded = outRank - rankA;
|
|
1496
|
+
const bPadded = outRank - rankB;
|
|
1497
|
+
if (k >= aPadded) flatA += aIdx * aOrigStrides[k - aPadded];
|
|
1498
|
+
if (k >= bPadded) flatB += bIdx * bOrigStrides[k - bPadded];
|
|
1499
|
+
}
|
|
1500
|
+
result[flatOut] = aData[flatA] * bData[flatB];
|
|
1501
|
+
}
|
|
1502
|
+
let outLabels;
|
|
1503
|
+
if (aLabels !== void 0 && bLabels !== void 0) outLabels = aLabels.map((al, k) => {
|
|
1504
|
+
const bl = bLabels[k];
|
|
1505
|
+
const aName = al.name ?? `a${k}`;
|
|
1506
|
+
const bName = bl.name ?? `b${k}`;
|
|
1507
|
+
const outDim = outShape[k];
|
|
1508
|
+
return new Index(outDim, { name: `${aName}${separator}${bName}` });
|
|
1509
|
+
});
|
|
1510
|
+
return new Tensor(outShape, result, outLabels);
|
|
2057
1511
|
}
|
|
2058
|
-
|
|
2059
|
-
|
|
1512
|
+
//#endregion
|
|
1513
|
+
//#region src/operations/slice.ts
|
|
2060
1514
|
function slice(t, ranges) {
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
const newDim = outShape[k];
|
|
2110
|
-
return new Index(Math.max(1, newDim), { name: lbl.name, tags: [...lbl.tags] });
|
|
2111
|
-
});
|
|
2112
|
-
}
|
|
2113
|
-
return new Tensor(outShape, outData, outLabels);
|
|
1515
|
+
const rank = t.shape.length;
|
|
1516
|
+
if (ranges.length !== rank) throw new Error(`slice: ranges length ${ranges.length} does not match tensor rank ${rank}`);
|
|
1517
|
+
const resolved = ranges.map((r, axis) => {
|
|
1518
|
+
const dim = t.shape[axis];
|
|
1519
|
+
const step = r?.step ?? 1;
|
|
1520
|
+
if (!Number.isInteger(step) || step < 1) throw new RangeError(`slice: step must be a positive integer (axis ${axis}), got ${step}`);
|
|
1521
|
+
let start = r?.start ?? 0;
|
|
1522
|
+
let stop = r?.stop ?? dim;
|
|
1523
|
+
if (start < 0) start = dim + start;
|
|
1524
|
+
if (stop < 0) stop = dim + stop;
|
|
1525
|
+
if (start < 0 || start > dim) throw new RangeError(`slice: start ${r?.start} is out of range for axis ${axis} with dim ${dim}`);
|
|
1526
|
+
stop = Math.max(0, Math.min(stop, dim));
|
|
1527
|
+
return {
|
|
1528
|
+
start,
|
|
1529
|
+
stop,
|
|
1530
|
+
step
|
|
1531
|
+
};
|
|
1532
|
+
});
|
|
1533
|
+
const outShape = resolved.map(({ start, stop, step }) => start >= stop ? 0 : Math.ceil((stop - start) / step));
|
|
1534
|
+
const outSize = outShape.reduce((a, b) => a * b, 1);
|
|
1535
|
+
const outData = new Float64Array(outSize);
|
|
1536
|
+
if (outSize > 0) {
|
|
1537
|
+
const inStrides = Tensor.rowMajorStrides(t.shape);
|
|
1538
|
+
const outStrides = Tensor.rowMajorStrides(outShape);
|
|
1539
|
+
const outIdx = new Array(rank).fill(0);
|
|
1540
|
+
for (let flatOut = 0; flatOut < outSize; flatOut++) {
|
|
1541
|
+
let rem = flatOut;
|
|
1542
|
+
for (let k = 0; k < rank; k++) {
|
|
1543
|
+
outIdx[k] = Math.floor(rem / outStrides[k]);
|
|
1544
|
+
rem -= outIdx[k] * outStrides[k];
|
|
1545
|
+
}
|
|
1546
|
+
let flatIn = 0;
|
|
1547
|
+
for (let k = 0; k < rank; k++) {
|
|
1548
|
+
const inK = resolved[k].start + outIdx[k] * resolved[k].step;
|
|
1549
|
+
flatIn += inK * inStrides[k];
|
|
1550
|
+
}
|
|
1551
|
+
outData[flatOut] = t.data[flatIn];
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
let outLabels;
|
|
1555
|
+
if (t.axisLabels !== void 0) outLabels = t.axisLabels.map((lbl, k) => {
|
|
1556
|
+
const newDim = outShape[k];
|
|
1557
|
+
return new Index(Math.max(1, newDim), {
|
|
1558
|
+
name: lbl.name,
|
|
1559
|
+
tags: [...lbl.tags]
|
|
1560
|
+
});
|
|
1561
|
+
});
|
|
1562
|
+
return new Tensor(outShape, outData, outLabels);
|
|
2114
1563
|
}
|
|
2115
|
-
|
|
2116
|
-
|
|
1564
|
+
//#endregion
|
|
1565
|
+
//#region src/operations/gather.ts
|
|
2117
1566
|
function gather(t, axis, indices) {
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
let outLabels;
|
|
2154
|
-
if (t.axisLabels !== void 0) {
|
|
2155
|
-
outLabels = t.axisLabels.map((lbl, k) => {
|
|
2156
|
-
if (k === ax) {
|
|
2157
|
-
return lbl.prime();
|
|
2158
|
-
}
|
|
2159
|
-
return lbl;
|
|
2160
|
-
});
|
|
2161
|
-
}
|
|
2162
|
-
return new Tensor(outShape, outData, outLabels);
|
|
1567
|
+
const rank = t.shape.length;
|
|
1568
|
+
const ax = axis < 0 ? rank + axis : axis;
|
|
1569
|
+
if (ax < 0 || ax >= rank) throw new Error(`gather: axis ${axis} is out of range for rank ${rank}`);
|
|
1570
|
+
const dimAx = t.shape[ax];
|
|
1571
|
+
for (let i = 0; i < indices.length; i++) if (!Number.isInteger(indices[i]) || indices[i] < 0 || indices[i] >= dimAx) throw new RangeError(`gather: index ${indices[i]} at position ${i} is out of range [0, ${dimAx}) for axis ${ax}`);
|
|
1572
|
+
const outShape = [...t.shape];
|
|
1573
|
+
outShape[ax] = indices.length;
|
|
1574
|
+
const outSize = outShape.reduce((a, b) => a * b, 1);
|
|
1575
|
+
const outData = new Float64Array(outSize);
|
|
1576
|
+
if (outSize > 0) {
|
|
1577
|
+
const inStrides = Tensor.rowMajorStrides(t.shape);
|
|
1578
|
+
const outStrides = Tensor.rowMajorStrides(outShape);
|
|
1579
|
+
const outIdx = new Array(rank).fill(0);
|
|
1580
|
+
for (let flatOut = 0; flatOut < outSize; flatOut++) {
|
|
1581
|
+
let rem = flatOut;
|
|
1582
|
+
for (let k = 0; k < rank; k++) {
|
|
1583
|
+
outIdx[k] = Math.floor(rem / outStrides[k]);
|
|
1584
|
+
rem -= outIdx[k] * outStrides[k];
|
|
1585
|
+
}
|
|
1586
|
+
let flatIn = 0;
|
|
1587
|
+
for (let k = 0; k < rank; k++) {
|
|
1588
|
+
const inK = k === ax ? indices[outIdx[k]] : outIdx[k];
|
|
1589
|
+
flatIn += inK * inStrides[k];
|
|
1590
|
+
}
|
|
1591
|
+
outData[flatOut] = t.data[flatIn];
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
let outLabels;
|
|
1595
|
+
if (t.axisLabels !== void 0) outLabels = t.axisLabels.map((lbl, k) => {
|
|
1596
|
+
if (k === ax) return lbl.prime();
|
|
1597
|
+
return lbl;
|
|
1598
|
+
});
|
|
1599
|
+
return new Tensor(outShape, outData, outLabels);
|
|
2163
1600
|
}
|
|
2164
|
-
|
|
2165
|
-
|
|
1601
|
+
//#endregion
|
|
1602
|
+
//#region src/operations/stack.ts
|
|
2166
1603
|
function stack(tensors, axis, opts) {
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
for (let k = ax; k < inRank; k++) flatOut += inIdx[k] * outStrides[k + 1];
|
|
2214
|
-
outData[flatOut] = inData[flatIn];
|
|
2215
|
-
}
|
|
2216
|
-
}
|
|
2217
|
-
let outLabels;
|
|
2218
|
-
if (first.axisLabels !== void 0) {
|
|
2219
|
-
const newLabel = opts?.newAxisLabel ?? new Index(tensors.length, { name: void 0 });
|
|
2220
|
-
const labels = [];
|
|
2221
|
-
for (let k = 0; k < ax; k++) labels.push(first.axisLabels[k]);
|
|
2222
|
-
labels.push(newLabel);
|
|
2223
|
-
for (let k = ax; k < inRank; k++) labels.push(first.axisLabels[k]);
|
|
2224
|
-
outLabels = labels;
|
|
2225
|
-
} else if (opts?.newAxisLabel !== void 0) {
|
|
2226
|
-
}
|
|
2227
|
-
return new Tensor(outShape, outData, outLabels);
|
|
1604
|
+
if (tensors.length === 0) throw new Error("stack: tensors array must be non-empty");
|
|
1605
|
+
const first = tensors[0];
|
|
1606
|
+
const inRank = first.shape.length;
|
|
1607
|
+
const outRank = inRank + 1;
|
|
1608
|
+
const ax = axis < 0 ? outRank + axis : axis;
|
|
1609
|
+
if (ax < 0 || ax > inRank) throw new Error(`stack: axis ${axis} is out of range for output rank ${outRank}`);
|
|
1610
|
+
for (let t = 1; t < tensors.length; t++) {
|
|
1611
|
+
const s = tensors[t].shape;
|
|
1612
|
+
if (s.length !== inRank) throw new Error(`stack: tensors[${t}] has rank ${s.length} but tensors[0] has rank ${inRank}`);
|
|
1613
|
+
for (let k = 0; k < inRank; k++) if (s[k] !== first.shape[k]) throw new Error(`stack: shape mismatch at axis ${k}: tensors[${t}].shape[${k}]=${s[k]} vs tensors[0].shape[${k}]=${first.shape[k]}`);
|
|
1614
|
+
}
|
|
1615
|
+
const outShape = [];
|
|
1616
|
+
for (let k = 0; k < ax; k++) outShape.push(first.shape[k]);
|
|
1617
|
+
outShape.push(tensors.length);
|
|
1618
|
+
for (let k = ax; k < inRank; k++) outShape.push(first.shape[k]);
|
|
1619
|
+
const outSize = outShape.reduce((a, b) => a * b, 1);
|
|
1620
|
+
const outData = new Float64Array(outSize);
|
|
1621
|
+
const outStrides = Tensor.rowMajorStrides(outShape);
|
|
1622
|
+
const inStrides = Tensor.rowMajorStrides(first.shape);
|
|
1623
|
+
for (let ti = 0; ti < tensors.length; ti++) {
|
|
1624
|
+
const inData = tensors[ti].data;
|
|
1625
|
+
const inSize = first.shape.reduce((a, b) => a * b, 1);
|
|
1626
|
+
const inIdx = new Array(inRank).fill(0);
|
|
1627
|
+
for (let flatIn = 0; flatIn < inSize; flatIn++) {
|
|
1628
|
+
let rem = flatIn;
|
|
1629
|
+
for (let k = 0; k < inRank; k++) {
|
|
1630
|
+
inIdx[k] = Math.floor(rem / inStrides[k]);
|
|
1631
|
+
rem -= inIdx[k] * inStrides[k];
|
|
1632
|
+
}
|
|
1633
|
+
let flatOut = 0;
|
|
1634
|
+
for (let k = 0; k < ax; k++) flatOut += inIdx[k] * outStrides[k];
|
|
1635
|
+
flatOut += ti * outStrides[ax];
|
|
1636
|
+
for (let k = ax; k < inRank; k++) flatOut += inIdx[k] * outStrides[k + 1];
|
|
1637
|
+
outData[flatOut] = inData[flatIn];
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
let outLabels;
|
|
1641
|
+
if (first.axisLabels !== void 0) {
|
|
1642
|
+
const newLabel = opts?.newAxisLabel ?? new Index(tensors.length, { name: void 0 });
|
|
1643
|
+
const labels = [];
|
|
1644
|
+
for (let k = 0; k < ax; k++) labels.push(first.axisLabels[k]);
|
|
1645
|
+
labels.push(newLabel);
|
|
1646
|
+
for (let k = ax; k < inRank; k++) labels.push(first.axisLabels[k]);
|
|
1647
|
+
outLabels = labels;
|
|
1648
|
+
} else if (opts?.newAxisLabel !== void 0) {}
|
|
1649
|
+
return new Tensor(outShape, outData, outLabels);
|
|
2228
1650
|
}
|
|
2229
|
-
|
|
2230
|
-
|
|
1651
|
+
//#endregion
|
|
1652
|
+
//#region src/operations/concatenate.ts
|
|
2231
1653
|
function concatenate(tensors, axis) {
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
}
|
|
2280
|
-
outData[flatOut] = t.data[flatIn];
|
|
2281
|
-
}
|
|
2282
|
-
joinOffset += inShape[ax];
|
|
2283
|
-
}
|
|
2284
|
-
let outLabels;
|
|
2285
|
-
if (first.axisLabels !== void 0) {
|
|
2286
|
-
outLabels = first.axisLabels.map((lbl, k) => {
|
|
2287
|
-
if (k === ax) {
|
|
2288
|
-
return new Index(joinDim, { name: lbl.name, tags: [...lbl.tags] });
|
|
2289
|
-
}
|
|
2290
|
-
return lbl;
|
|
2291
|
-
});
|
|
2292
|
-
}
|
|
2293
|
-
return new Tensor(outShape, outData, outLabels);
|
|
1654
|
+
if (tensors.length === 0) throw new Error("concatenate: tensors array must be non-empty");
|
|
1655
|
+
const first = tensors[0];
|
|
1656
|
+
const rank = first.shape.length;
|
|
1657
|
+
const ax = axis < 0 ? rank + axis : axis;
|
|
1658
|
+
if (ax < 0 || ax >= rank) throw new Error(`concatenate: axis ${axis} is out of range for rank ${rank}`);
|
|
1659
|
+
for (let t = 1; t < tensors.length; t++) {
|
|
1660
|
+
const s = tensors[t].shape;
|
|
1661
|
+
if (s.length !== rank) throw new Error(`concatenate: tensors[${t}] has rank ${s.length} but tensors[0] has rank ${rank}`);
|
|
1662
|
+
for (let k = 0; k < rank; k++) if (k !== ax && s[k] !== first.shape[k]) throw new Error(`concatenate: shape mismatch on non-join axis ${k}: tensors[${t}].shape[${k}]=${s[k]} vs tensors[0].shape[${k}]=${first.shape[k]}`);
|
|
1663
|
+
}
|
|
1664
|
+
const joinDim = tensors.reduce((sum, t) => sum + t.shape[ax], 0);
|
|
1665
|
+
const outShape = [...first.shape];
|
|
1666
|
+
outShape[ax] = joinDim;
|
|
1667
|
+
const outSize = outShape.reduce((a, b) => a * b, 1);
|
|
1668
|
+
const outData = new Float64Array(outSize);
|
|
1669
|
+
const outStrides = Tensor.rowMajorStrides(outShape);
|
|
1670
|
+
let joinOffset = 0;
|
|
1671
|
+
for (let ti = 0; ti < tensors.length; ti++) {
|
|
1672
|
+
const t = tensors[ti];
|
|
1673
|
+
const inShape = t.shape;
|
|
1674
|
+
const inSize = inShape.reduce((a, b) => a * b, 1);
|
|
1675
|
+
const inStrides = Tensor.rowMajorStrides(inShape);
|
|
1676
|
+
const inIdx = new Array(rank).fill(0);
|
|
1677
|
+
for (let flatIn = 0; flatIn < inSize; flatIn++) {
|
|
1678
|
+
let rem = flatIn;
|
|
1679
|
+
for (let k = 0; k < rank; k++) {
|
|
1680
|
+
inIdx[k] = Math.floor(rem / inStrides[k]);
|
|
1681
|
+
rem -= inIdx[k] * inStrides[k];
|
|
1682
|
+
}
|
|
1683
|
+
let flatOut = 0;
|
|
1684
|
+
for (let k = 0; k < rank; k++) {
|
|
1685
|
+
const outK = k === ax ? inIdx[k] + joinOffset : inIdx[k];
|
|
1686
|
+
flatOut += outK * outStrides[k];
|
|
1687
|
+
}
|
|
1688
|
+
outData[flatOut] = t.data[flatIn];
|
|
1689
|
+
}
|
|
1690
|
+
joinOffset += inShape[ax];
|
|
1691
|
+
}
|
|
1692
|
+
let outLabels;
|
|
1693
|
+
if (first.axisLabels !== void 0) outLabels = first.axisLabels.map((lbl, k) => {
|
|
1694
|
+
if (k === ax) return new Index(joinDim, {
|
|
1695
|
+
name: lbl.name,
|
|
1696
|
+
tags: [...lbl.tags]
|
|
1697
|
+
});
|
|
1698
|
+
return lbl;
|
|
1699
|
+
});
|
|
1700
|
+
return new Tensor(outShape, outData, outLabels);
|
|
2294
1701
|
}
|
|
2295
|
-
|
|
2296
|
-
|
|
1702
|
+
//#endregion
|
|
1703
|
+
//#region src/operations/scatter.ts
|
|
2297
1704
|
function scatter(t, axis, indices, updates, opts) {
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
if (indices.length > 0) {
|
|
2334
|
-
const inStrides = Tensor.rowMajorStrides(t.shape);
|
|
2335
|
-
const updStrides = Tensor.rowMajorStrides(updates.shape);
|
|
2336
|
-
const updSize = updates.shape.reduce((a, b) => a * b, 1);
|
|
2337
|
-
const updIdx = new Array(rank).fill(0);
|
|
2338
|
-
for (let flatUpd = 0; flatUpd < updSize; flatUpd++) {
|
|
2339
|
-
let rem = flatUpd;
|
|
2340
|
-
for (let k = 0; k < rank; k++) {
|
|
2341
|
-
updIdx[k] = Math.floor(rem / updStrides[k]);
|
|
2342
|
-
rem -= updIdx[k] * updStrides[k];
|
|
2343
|
-
}
|
|
2344
|
-
const targetAxIdx = indices[updIdx[ax]];
|
|
2345
|
-
let flatOut = 0;
|
|
2346
|
-
for (let k = 0; k < rank; k++) {
|
|
2347
|
-
const idxK = k === ax ? targetAxIdx : updIdx[k];
|
|
2348
|
-
flatOut += idxK * inStrides[k];
|
|
2349
|
-
}
|
|
2350
|
-
if (reduce === "add") {
|
|
2351
|
-
outData[flatOut] += updates.data[flatUpd];
|
|
2352
|
-
} else {
|
|
2353
|
-
outData[flatOut] = updates.data[flatUpd];
|
|
2354
|
-
}
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
|
-
let outLabels;
|
|
2358
|
-
if (t.axisLabels !== void 0) {
|
|
2359
|
-
outLabels = [...t.axisLabels];
|
|
2360
|
-
}
|
|
2361
|
-
return new Tensor([...t.shape], outData, outLabels);
|
|
1705
|
+
const rank = t.shape.length;
|
|
1706
|
+
const reduce = opts?.reduce ?? "overwrite";
|
|
1707
|
+
const ax = axis < 0 ? rank + axis : axis;
|
|
1708
|
+
if (ax < 0 || ax >= rank) throw new Error(`scatter: axis ${axis} is out of range for rank ${rank}`);
|
|
1709
|
+
const dimAx = t.shape[ax];
|
|
1710
|
+
for (let i = 0; i < indices.length; i++) if (!Number.isInteger(indices[i]) || indices[i] < 0 || indices[i] >= dimAx) throw new RangeError(`scatter: index ${indices[i]} at position ${i} is out of range [0, ${dimAx}) for axis ${ax}`);
|
|
1711
|
+
if (updates.shape.length !== rank) throw new Error(`scatter: updates rank ${updates.shape.length} does not match tensor rank ${rank}`);
|
|
1712
|
+
for (let k = 0; k < rank; k++) if (k === ax) {
|
|
1713
|
+
if (updates.shape[k] !== indices.length) throw new Error(`scatter: updates.shape[${k}] = ${updates.shape[k]} but indices.length = ${indices.length}`);
|
|
1714
|
+
} else if (updates.shape[k] !== t.shape[k]) throw new Error(`scatter: updates.shape[${k}] = ${updates.shape[k]} does not match t.shape[${k}] = ${t.shape[k]}`);
|
|
1715
|
+
const outData = new Float64Array(t.data);
|
|
1716
|
+
if (indices.length > 0) {
|
|
1717
|
+
const inStrides = Tensor.rowMajorStrides(t.shape);
|
|
1718
|
+
const updStrides = Tensor.rowMajorStrides(updates.shape);
|
|
1719
|
+
const updSize = updates.shape.reduce((a, b) => a * b, 1);
|
|
1720
|
+
const updIdx = new Array(rank).fill(0);
|
|
1721
|
+
for (let flatUpd = 0; flatUpd < updSize; flatUpd++) {
|
|
1722
|
+
let rem = flatUpd;
|
|
1723
|
+
for (let k = 0; k < rank; k++) {
|
|
1724
|
+
updIdx[k] = Math.floor(rem / updStrides[k]);
|
|
1725
|
+
rem -= updIdx[k] * updStrides[k];
|
|
1726
|
+
}
|
|
1727
|
+
const targetAxIdx = indices[updIdx[ax]];
|
|
1728
|
+
let flatOut = 0;
|
|
1729
|
+
for (let k = 0; k < rank; k++) {
|
|
1730
|
+
const idxK = k === ax ? targetAxIdx : updIdx[k];
|
|
1731
|
+
flatOut += idxK * inStrides[k];
|
|
1732
|
+
}
|
|
1733
|
+
if (reduce === "add") outData[flatOut] += updates.data[flatUpd];
|
|
1734
|
+
else outData[flatOut] = updates.data[flatUpd];
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
let outLabels;
|
|
1738
|
+
if (t.axisLabels !== void 0) outLabels = [...t.axisLabels];
|
|
1739
|
+
return new Tensor([...t.shape], outData, outLabels);
|
|
2362
1740
|
}
|
|
2363
|
-
|
|
2364
|
-
|
|
1741
|
+
//#endregion
|
|
1742
|
+
//#region src/operations/pad.ts
|
|
2365
1743
|
function pad(t, padWidths, opts) {
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
} else if (mode === "edge") {
|
|
2425
|
-
iIdx = dim - 1;
|
|
2426
|
-
} else {
|
|
2427
|
-
const dist = oIdx - (before + dim - 1);
|
|
2428
|
-
iIdx = dim - 1 - dist;
|
|
2429
|
-
}
|
|
2430
|
-
} else {
|
|
2431
|
-
iIdx = oIdx - before;
|
|
2432
|
-
}
|
|
2433
|
-
flatIn += iIdx * inStrides[k];
|
|
2434
|
-
}
|
|
2435
|
-
if (inBounds) {
|
|
2436
|
-
outData[flatOut] = t.data[flatIn];
|
|
2437
|
-
}
|
|
2438
|
-
}
|
|
2439
|
-
let outLabels;
|
|
2440
|
-
if (t.axisLabels !== void 0) {
|
|
2441
|
-
outLabels = [...t.axisLabels];
|
|
2442
|
-
}
|
|
2443
|
-
return new Tensor(outShape, outData, outLabels);
|
|
1744
|
+
const rank = t.shape.length;
|
|
1745
|
+
const mode = opts?.mode ?? "constant";
|
|
1746
|
+
const fillValue = opts?.constantValue ?? 0;
|
|
1747
|
+
if (padWidths.length !== rank) throw new Error(`pad: padWidths length ${padWidths.length} does not match tensor rank ${rank}`);
|
|
1748
|
+
for (let k = 0; k < rank; k++) {
|
|
1749
|
+
const [before, after] = padWidths[k];
|
|
1750
|
+
if (!Number.isInteger(before) || before < 0) throw new Error(`pad: padWidths[${k}][0] must be a non-negative integer, got ${before}`);
|
|
1751
|
+
if (!Number.isInteger(after) || after < 0) throw new Error(`pad: padWidths[${k}][1] must be a non-negative integer, got ${after}`);
|
|
1752
|
+
if (mode === "reflect") {
|
|
1753
|
+
const dim = t.shape[k];
|
|
1754
|
+
if (before >= dim || after >= dim) throw new Error(`pad: reflect mode requires pad amounts < axis size; axis ${k} has size ${dim} but pad is [${before}, ${after}]`);
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
const outShape = t.shape.map((d, k) => d + padWidths[k][0] + padWidths[k][1]);
|
|
1758
|
+
const outSize = outShape.reduce((a, b) => a * b, 1);
|
|
1759
|
+
const outData = new Float64Array(outSize);
|
|
1760
|
+
if (mode === "constant") {
|
|
1761
|
+
if (fillValue !== 0) outData.fill(fillValue);
|
|
1762
|
+
}
|
|
1763
|
+
const inStrides = Tensor.rowMajorStrides(t.shape);
|
|
1764
|
+
const outStrides = Tensor.rowMajorStrides(outShape);
|
|
1765
|
+
const outIdx = new Array(rank).fill(0);
|
|
1766
|
+
for (let flatOut = 0; flatOut < outSize; flatOut++) {
|
|
1767
|
+
let rem = flatOut;
|
|
1768
|
+
for (let k = 0; k < rank; k++) {
|
|
1769
|
+
outIdx[k] = Math.floor(rem / outStrides[k]);
|
|
1770
|
+
rem -= outIdx[k] * outStrides[k];
|
|
1771
|
+
}
|
|
1772
|
+
let inBounds = true;
|
|
1773
|
+
let flatIn = 0;
|
|
1774
|
+
for (let k = 0; k < rank; k++) {
|
|
1775
|
+
const before = padWidths[k][0];
|
|
1776
|
+
const dim = t.shape[k];
|
|
1777
|
+
const oIdx = outIdx[k];
|
|
1778
|
+
let iIdx;
|
|
1779
|
+
if (oIdx < before) {
|
|
1780
|
+
if (mode === "constant") {
|
|
1781
|
+
inBounds = false;
|
|
1782
|
+
break;
|
|
1783
|
+
} else if (mode === "edge") iIdx = 0;
|
|
1784
|
+
else iIdx = before - oIdx;
|
|
1785
|
+
} else if (oIdx >= before + dim) {
|
|
1786
|
+
if (mode === "constant") {
|
|
1787
|
+
inBounds = false;
|
|
1788
|
+
break;
|
|
1789
|
+
} else if (mode === "edge") iIdx = dim - 1;
|
|
1790
|
+
else {
|
|
1791
|
+
const dist = oIdx - (before + dim - 1);
|
|
1792
|
+
iIdx = dim - 1 - dist;
|
|
1793
|
+
}
|
|
1794
|
+
} else iIdx = oIdx - before;
|
|
1795
|
+
flatIn += iIdx * inStrides[k];
|
|
1796
|
+
}
|
|
1797
|
+
if (inBounds) outData[flatOut] = t.data[flatIn];
|
|
1798
|
+
}
|
|
1799
|
+
let outLabels;
|
|
1800
|
+
if (t.axisLabels !== void 0) outLabels = [...t.axisLabels];
|
|
1801
|
+
return new Tensor(outShape, outData, outLabels);
|
|
2444
1802
|
}
|
|
2445
|
-
|
|
2446
|
-
|
|
1803
|
+
//#endregion
|
|
1804
|
+
//#region src/operations/roll.ts
|
|
2447
1805
|
function roll(t, shifts, axes) {
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
for (let k = 0; k < rank; k++) {
|
|
2494
|
-
const dim = t.shape[k];
|
|
2495
|
-
const srcK = ((outIdx[k] - axisShift[k]) % dim + dim) % dim;
|
|
2496
|
-
flatIn += srcK * inStrides[k];
|
|
2497
|
-
}
|
|
2498
|
-
outData[flatOut] = t.data[flatIn];
|
|
2499
|
-
}
|
|
2500
|
-
let outLabels;
|
|
2501
|
-
if (t.axisLabels !== void 0) outLabels = [...t.axisLabels];
|
|
2502
|
-
return new Tensor([...t.shape], outData, outLabels);
|
|
1806
|
+
const rank = t.shape.length;
|
|
1807
|
+
const totalSize = t.shape.reduce((a, b) => a * b, 1);
|
|
1808
|
+
if (axes === void 0) {
|
|
1809
|
+
if (Array.isArray(shifts)) throw new Error("roll: when axes is omitted, shifts must be a single number");
|
|
1810
|
+
const shift = shifts;
|
|
1811
|
+
const outData = new Float64Array(totalSize);
|
|
1812
|
+
const n = totalSize;
|
|
1813
|
+
for (let i = 0; i < n; i++) {
|
|
1814
|
+
const srcIdx = ((i - shift) % n + n) % n;
|
|
1815
|
+
outData[i] = t.data[srcIdx];
|
|
1816
|
+
}
|
|
1817
|
+
let outLabels;
|
|
1818
|
+
if (t.axisLabels !== void 0) outLabels = [...t.axisLabels];
|
|
1819
|
+
return new Tensor([...t.shape], outData, outLabels);
|
|
1820
|
+
}
|
|
1821
|
+
const shiftArr = Array.isArray(shifts) ? [...shifts] : [shifts];
|
|
1822
|
+
if (shiftArr.length !== axes.length) throw new Error(`roll: shifts length ${shiftArr.length} does not match axes length ${axes.length}`);
|
|
1823
|
+
const normAxes = axes.map((ax) => {
|
|
1824
|
+
const a = ax < 0 ? rank + ax : ax;
|
|
1825
|
+
if (a < 0 || a >= rank) throw new Error(`roll: axis ${ax} is out of range for rank ${rank}`);
|
|
1826
|
+
return a;
|
|
1827
|
+
});
|
|
1828
|
+
const axisShift = new Array(rank).fill(0);
|
|
1829
|
+
for (let i = 0; i < normAxes.length; i++) axisShift[normAxes[i]] = shiftArr[i];
|
|
1830
|
+
const outData = new Float64Array(totalSize);
|
|
1831
|
+
const inStrides = Tensor.rowMajorStrides(t.shape);
|
|
1832
|
+
const outStrides = Tensor.rowMajorStrides(t.shape);
|
|
1833
|
+
const outIdx = new Array(rank).fill(0);
|
|
1834
|
+
for (let flatOut = 0; flatOut < totalSize; flatOut++) {
|
|
1835
|
+
let rem = flatOut;
|
|
1836
|
+
for (let k = 0; k < rank; k++) {
|
|
1837
|
+
outIdx[k] = Math.floor(rem / outStrides[k]);
|
|
1838
|
+
rem -= outIdx[k] * outStrides[k];
|
|
1839
|
+
}
|
|
1840
|
+
let flatIn = 0;
|
|
1841
|
+
for (let k = 0; k < rank; k++) {
|
|
1842
|
+
const dim = t.shape[k];
|
|
1843
|
+
const srcK = ((outIdx[k] - axisShift[k]) % dim + dim) % dim;
|
|
1844
|
+
flatIn += srcK * inStrides[k];
|
|
1845
|
+
}
|
|
1846
|
+
outData[flatOut] = t.data[flatIn];
|
|
1847
|
+
}
|
|
1848
|
+
let outLabels;
|
|
1849
|
+
if (t.axisLabels !== void 0) outLabels = [...t.axisLabels];
|
|
1850
|
+
return new Tensor([...t.shape], outData, outLabels);
|
|
2503
1851
|
}
|
|
2504
|
-
|
|
2505
|
-
|
|
1852
|
+
//#endregion
|
|
1853
|
+
//#region src/operations/flip.ts
|
|
2506
1854
|
function flip(t, axes) {
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
if (t.axisLabels !== void 0) outLabels = [...t.axisLabels];
|
|
2537
|
-
return new Tensor([...t.shape], outData, outLabels);
|
|
1855
|
+
const rank = t.shape.length;
|
|
1856
|
+
const totalSize = t.shape.reduce((a, b) => a * b, 1);
|
|
1857
|
+
const normAxesSet = /* @__PURE__ */ new Set();
|
|
1858
|
+
for (const ax of axes) {
|
|
1859
|
+
const a = ax < 0 ? rank + ax : ax;
|
|
1860
|
+
if (a < 0 || a >= rank) throw new Error(`flip: axis ${ax} is out of range for rank ${rank}`);
|
|
1861
|
+
normAxesSet.add(a);
|
|
1862
|
+
}
|
|
1863
|
+
const flipSet = normAxesSet;
|
|
1864
|
+
const outData = new Float64Array(totalSize);
|
|
1865
|
+
const inStrides = Tensor.rowMajorStrides(t.shape);
|
|
1866
|
+
const outStrides = Tensor.rowMajorStrides(t.shape);
|
|
1867
|
+
const outIdx = new Array(rank).fill(0);
|
|
1868
|
+
for (let flatOut = 0; flatOut < totalSize; flatOut++) {
|
|
1869
|
+
let rem = flatOut;
|
|
1870
|
+
for (let k = 0; k < rank; k++) {
|
|
1871
|
+
outIdx[k] = Math.floor(rem / outStrides[k]);
|
|
1872
|
+
rem -= outIdx[k] * outStrides[k];
|
|
1873
|
+
}
|
|
1874
|
+
let flatIn = 0;
|
|
1875
|
+
for (let k = 0; k < rank; k++) {
|
|
1876
|
+
const iIdx = flipSet.has(k) ? t.shape[k] - 1 - outIdx[k] : outIdx[k];
|
|
1877
|
+
flatIn += iIdx * inStrides[k];
|
|
1878
|
+
}
|
|
1879
|
+
outData[flatOut] = t.data[flatIn];
|
|
1880
|
+
}
|
|
1881
|
+
let outLabels;
|
|
1882
|
+
if (t.axisLabels !== void 0) outLabels = [...t.axisLabels];
|
|
1883
|
+
return new Tensor([...t.shape], outData, outLabels);
|
|
2538
1884
|
}
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
Tensor,
|
|
2542
|
-
concatenate,
|
|
2543
|
-
contractNetwork,
|
|
2544
|
-
flip,
|
|
2545
|
-
gather,
|
|
2546
|
-
idx,
|
|
2547
|
-
pad,
|
|
2548
|
-
randomTensor,
|
|
2549
|
-
roll,
|
|
2550
|
-
scatter,
|
|
2551
|
-
slice,
|
|
2552
|
-
stack,
|
|
2553
|
-
tensorCholesky,
|
|
2554
|
-
tensorEig,
|
|
2555
|
-
tensorEigWasm,
|
|
2556
|
-
tensorKron,
|
|
2557
|
-
tensorLU,
|
|
2558
|
-
tensorPinv,
|
|
2559
|
-
tensorQr,
|
|
2560
|
-
tensorSolve,
|
|
2561
|
-
tensorSvd,
|
|
2562
|
-
tensorSvdWasm
|
|
2563
|
-
};
|
|
1885
|
+
//#endregion
|
|
1886
|
+
export { Index, Tensor, concatenate, contractNetwork, flip, gather, idx, pad, randomTensor, roll, scatter, slice, stack, tensorCholesky, tensorEig, tensorEigWasm, tensorKron, tensorLU, tensorPinv, tensorQr, tensorSolve, tensorSvd, tensorSvdWasm };
|