@danielsimonjr/mathts-tensor 0.2.23 → 0.2.25

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