@danielsimonjr/mathts-tensor 0.1.0 → 0.1.2

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