@hoox-sh/pynets 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE +661 -0
  3. package/NOTICE +46 -0
  4. package/README.md +327 -0
  5. package/dist/browser.js +44 -0
  6. package/dist/index.d.ts +348 -0
  7. package/dist/index.js +41741 -0
  8. package/package.json +77 -0
  9. package/src/ast/builder.ts +1002 -0
  10. package/src/ast/dump.ts +43 -0
  11. package/src/ast/helper.ts +77 -0
  12. package/src/ast/nodes.ts +570 -0
  13. package/src/ast/unparse.ts +360 -0
  14. package/src/cli/rich.ts +378 -0
  15. package/src/cli.ts +627 -0
  16. package/src/generated/PinescriptLexer.interp +267 -0
  17. package/src/generated/PinescriptLexer.tokens +131 -0
  18. package/src/generated/PinescriptLexer.ts +673 -0
  19. package/src/generated/PinescriptLexerBase.ts +395 -0
  20. package/src/generated/PinescriptParser.interp +267 -0
  21. package/src/generated/PinescriptParser.tokens +131 -0
  22. package/src/generated/PinescriptParser.ts +8863 -0
  23. package/src/generated/PinescriptParserBase.ts +21 -0
  24. package/src/generated/PinescriptParserVisitor.ts +840 -0
  25. package/src/index.ts +55 -0
  26. package/src/parser/LexerBase.ts +395 -0
  27. package/src/parser/ParserBase.ts +21 -0
  28. package/src/parser/error.ts +33 -0
  29. package/src/runtime/array.ts +486 -0
  30. package/src/runtime/color.ts +164 -0
  31. package/src/runtime/drawings.ts +510 -0
  32. package/src/runtime/input.ts +102 -0
  33. package/src/runtime/interpret.ts +3897 -0
  34. package/src/runtime/library.ts +282 -0
  35. package/src/runtime/log.ts +69 -0
  36. package/src/runtime/map.ts +85 -0
  37. package/src/runtime/math.ts +231 -0
  38. package/src/runtime/matrix.ts +1154 -0
  39. package/src/runtime/provider.ts +232 -0
  40. package/src/runtime/request.ts +372 -0
  41. package/src/runtime/series.ts +44 -0
  42. package/src/runtime/str.ts +433 -0
  43. package/src/runtime/strategy.ts +1105 -0
  44. package/src/runtime/ta.ts +2093 -0
  45. package/src/runtime/ticker.ts +81 -0
  46. package/src/runtime/time.ts +439 -0
  47. package/src/runtime/timeframe.ts +85 -0
  48. package/src/runtime/udt.ts +119 -0
@@ -0,0 +1,1154 @@
1
+ /**
2
+ * Copyright (C) 2024-2026 jango_blockchained
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ *
5
+ * Tiny Pine `matrix.*` value. `null` is `na`. Get is na-safe (OOB / non-finite → na).
6
+ * Set OOB is a no-op. Python `Matrix` remains the source of truth.
7
+ */
8
+
9
+ export type Cell = number | null;
10
+
11
+ /** Python has no matrix-size cap; bound elements like `array.set` (1e6). */
12
+ export const MAX_MATRIX_ELEMENTS = 1_000_000;
13
+
14
+ function resolveIndex(index: number, length: number): number | null {
15
+ if (!Number.isFinite(index)) return null;
16
+ const i = Math.trunc(index);
17
+ if (i < 0 || i >= length) return null;
18
+ return i;
19
+ }
20
+
21
+ function dim(n: number): number {
22
+ if (!Number.isFinite(n) || n < 0) return 0;
23
+ return Math.min(Math.trunc(n), MAX_MATRIX_ELEMENTS);
24
+ }
25
+
26
+ function fitsElements(rows: number, cols: number): boolean {
27
+ if (rows <= 0 || cols <= 0) return true;
28
+ return cols <= Math.floor(MAX_MATRIX_ELEMENTS / rows);
29
+ }
30
+
31
+ /** Pad with `na` or truncate so the vector has length *n*. */
32
+ function padOrTrunc(values: Cell[] | undefined, n: number): Cell[] {
33
+ const out: Cell[] = [];
34
+ const src = values ?? [];
35
+ for (let i = 0; i < n; i++) out.push(i < src.length ? src[i]! : null);
36
+ return out;
37
+ }
38
+
39
+ function cloneGrid(src: number[][]): number[][] {
40
+ const out: number[][] = [];
41
+ for (let i = 0; i < src.length; i++) out.push(src[i]!.slice());
42
+ return out;
43
+ }
44
+
45
+ function eigen2x2(a: number, b: number, c: number, d: number): [number, number] {
46
+ const tr = a + d;
47
+ const det = a * d - b * c;
48
+ const disc = tr * tr - 4 * det;
49
+ if (disc >= 0) {
50
+ const s = Math.sqrt(disc);
51
+ return [(tr + s) / 2, (tr - s) / 2];
52
+ }
53
+ const re = tr / 2;
54
+ return [re, re];
55
+ }
56
+
57
+ function wilkinsonShift(a: number, b: number, c: number, d: number): number {
58
+ if ((a - d) * (a - d) + 4 * b * c < 0) return d;
59
+ const [l1, l2] = eigen2x2(a, b, c, d);
60
+ return Math.abs(l1 - d) < Math.abs(l2 - d) ? l1 : l2;
61
+ }
62
+
63
+ /** One-sided Jacobi SVD: A (m×n) = U (m×n) Σ Vᵀ (n×n). */
64
+ function jacobiSvd(A: number[][]): { u: number[][]; s: number[]; v: number[][] } | null {
65
+ const m = A.length;
66
+ const n = A[0]!.length;
67
+ const w = cloneGrid(A);
68
+ const v: number[][] = [];
69
+ for (let i = 0; i < n; i++) {
70
+ const row: number[] = [];
71
+ for (let j = 0; j < n; j++) row.push(i === j ? 1 : 0);
72
+ v.push(row);
73
+ }
74
+ for (let sweep = 0; sweep < 50; sweep++) {
75
+ let rotated = false;
76
+ for (let p = 0; p < n - 1; p++) {
77
+ for (let q = p + 1; q < n; q++) {
78
+ let alpha = 0;
79
+ let beta = 0;
80
+ let gamma = 0;
81
+ for (let i = 0; i < m; i++) {
82
+ const ap = w[i]![p]!;
83
+ const aq = w[i]![q]!;
84
+ alpha += ap * ap;
85
+ beta += aq * aq;
86
+ gamma += ap * aq;
87
+ }
88
+ if (!Number.isFinite(alpha) || !Number.isFinite(beta) || !Number.isFinite(gamma)) {
89
+ return null;
90
+ }
91
+ const thresh = 1e-15 * Math.sqrt(Math.max(alpha * beta, 0));
92
+ if (Math.abs(gamma) <= thresh) continue;
93
+ rotated = true;
94
+ const zeta = (beta - alpha) / (2 * gamma);
95
+ const t = (zeta >= 0 ? 1 : -1) / (Math.abs(zeta) + Math.hypot(1, zeta));
96
+ const cs = 1 / Math.sqrt(1 + t * t);
97
+ const sn = cs * t;
98
+ for (let i = 0; i < m; i++) {
99
+ const ap = w[i]![p]!;
100
+ const aq = w[i]![q]!;
101
+ w[i]![p] = cs * ap - sn * aq;
102
+ w[i]![q] = sn * ap + cs * aq;
103
+ }
104
+ for (let i = 0; i < n; i++) {
105
+ const vp = v[i]![p]!;
106
+ const vq = v[i]![q]!;
107
+ v[i]![p] = cs * vp - sn * vq;
108
+ v[i]![q] = sn * vp + cs * vq;
109
+ }
110
+ }
111
+ }
112
+ if (!rotated) break;
113
+ }
114
+ const s: number[] = [];
115
+ const u: number[][] = [];
116
+ for (let i = 0; i < m; i++) {
117
+ const row: number[] = [];
118
+ for (let j = 0; j < n; j++) row.push(0);
119
+ u.push(row);
120
+ }
121
+ for (let j = 0; j < n; j++) {
122
+ let nrm = 0;
123
+ for (let i = 0; i < m; i++) nrm += w[i]![j]! * w[i]![j]!;
124
+ nrm = Math.sqrt(nrm);
125
+ if (!Number.isFinite(nrm)) return null;
126
+ s.push(nrm);
127
+ if (nrm > 0) {
128
+ for (let i = 0; i < m; i++) u[i]![j] = w[i]![j]! / nrm;
129
+ }
130
+ }
131
+ return { u, s, v };
132
+ }
133
+
134
+ /** In-place Householder reduction to upper Hessenberg. */
135
+ function toHessenberg(H: number[][]): void {
136
+ const n = H.length;
137
+ for (let k = 0; k < n - 2; k++) {
138
+ let tail = 0;
139
+ for (let i = k + 2; i < n; i++) tail += H[i]![k]! * H[i]![k]!;
140
+ if (tail < 1e-30) continue;
141
+ let norm = tail + H[k + 1]![k]! * H[k + 1]![k]!;
142
+ norm = Math.sqrt(norm);
143
+ if (norm < 1e-30) continue;
144
+ const x0 = H[k + 1]![k]!;
145
+ const sigma = (x0 >= 0 ? 1 : -1) * norm;
146
+ const hv: number[] = [x0 + sigma];
147
+ for (let i = k + 2; i < n; i++) hv.push(H[i]![k]!);
148
+ let vnorm = 0;
149
+ for (const t of hv) vnorm += t * t;
150
+ vnorm = Math.sqrt(vnorm);
151
+ if (vnorm < 1e-30) continue;
152
+ for (let i = 0; i < hv.length; i++) hv[i]! /= vnorm;
153
+ for (let j = k; j < n; j++) {
154
+ let dot = 0;
155
+ for (let i = 0; i < hv.length; i++) dot += hv[i]! * H[k + 1 + i]![j]!;
156
+ dot *= 2;
157
+ for (let i = 0; i < hv.length; i++) H[k + 1 + i]![j]! -= dot * hv[i]!;
158
+ }
159
+ for (let i = 0; i < n; i++) {
160
+ let dot = 0;
161
+ for (let j = 0; j < hv.length; j++) dot += H[i]![k + 1 + j]! * hv[j]!;
162
+ dot *= 2;
163
+ for (let j = 0; j < hv.length; j++) H[i]![k + 1 + j]! -= dot * hv[j]!;
164
+ }
165
+ H[k + 1]![k] = -sigma;
166
+ for (let i = k + 2; i < n; i++) H[i]![k] = 0;
167
+ }
168
+ }
169
+
170
+ /** Explicit shifted QR sweep on the leading m×m block. */
171
+ function qrSweep(H: number[][], m: number, mu: number): void {
172
+ const cs: { c: number; s: number }[] = [];
173
+ for (let i = 0; i < m; i++) H[i]![i]! -= mu;
174
+ for (let k = 0; k < m - 1; k++) {
175
+ const a = H[k]![k]!;
176
+ const b = H[k + 1]![k]!;
177
+ const r = Math.hypot(a, b);
178
+ const c = r > 0 ? a / r : 1;
179
+ const s = r > 0 ? b / r : 0;
180
+ cs.push({ c, s });
181
+ for (let j = k; j < m; j++) {
182
+ const t0 = c * H[k]![j]! + s * H[k + 1]![j]!;
183
+ const t1 = -s * H[k]![j]! + c * H[k + 1]![j]!;
184
+ H[k]![j] = t0;
185
+ H[k + 1]![j] = t1;
186
+ }
187
+ }
188
+ for (let k = 0; k < m - 1; k++) {
189
+ const { c, s } = cs[k]!;
190
+ for (let i = 0; i <= k + 1 && i < m; i++) {
191
+ const t0 = c * H[i]![k]! + s * H[i]![k + 1]!;
192
+ const t1 = -s * H[i]![k]! + c * H[i]![k + 1]!;
193
+ H[i]![k] = t0;
194
+ H[i]![k + 1] = t1;
195
+ }
196
+ }
197
+ for (let i = 0; i < m; i++) H[i]![i]! += mu;
198
+ }
199
+
200
+ /** Real parts of eigenvalues via Hessenberg + Wilkinson QR. */
201
+ function realEigenvalues(A: number[][]): number[] | null {
202
+ const n = A.length;
203
+ if (n === 0) return [];
204
+ if (n === 1) {
205
+ const v = A[0]![0]!;
206
+ return Number.isFinite(v) ? [v] : null;
207
+ }
208
+ if (n === 2) {
209
+ const vals = eigen2x2(A[0]![0]!, A[0]![1]!, A[1]![0]!, A[1]![1]!);
210
+ if (!Number.isFinite(vals[0]) || !Number.isFinite(vals[1])) return null;
211
+ return [vals[0], vals[1]];
212
+ }
213
+ const H = cloneGrid(A);
214
+ toHessenberg(H);
215
+ const evals: number[] = [];
216
+ let m = n;
217
+ const maxIter = 40 * n * n;
218
+ let iter = 0;
219
+ while (m > 0) {
220
+ if (++iter > maxIter) return null;
221
+ if (m === 1) {
222
+ const v = H[0]![0]!;
223
+ if (!Number.isFinite(v)) return null;
224
+ evals.push(v);
225
+ break;
226
+ }
227
+ if (m === 2) {
228
+ const vals = eigen2x2(H[0]![0]!, H[0]![1]!, H[1]![0]!, H[1]![1]!);
229
+ if (!Number.isFinite(vals[0]) || !Number.isFinite(vals[1])) return null;
230
+ evals.push(vals[0], vals[1]);
231
+ break;
232
+ }
233
+ const scale = Math.abs(H[m - 1]![m - 1]!) + Math.abs(H[m - 2]![m - 2]!);
234
+ if (Math.abs(H[m - 1]![m - 2]!) <= 1e-14 * Math.max(1, scale)) {
235
+ const v = H[m - 1]![m - 1]!;
236
+ if (!Number.isFinite(v)) return null;
237
+ evals.push(v);
238
+ m--;
239
+ continue;
240
+ }
241
+ const scale2 = Math.abs(H[m - 2]![m - 2]!) + Math.abs(H[m - 3]![m - 3]!);
242
+ if (Math.abs(H[m - 2]![m - 3]!) <= 1e-14 * Math.max(1, scale2)) {
243
+ const vals = eigen2x2(
244
+ H[m - 2]![m - 2]!,
245
+ H[m - 2]![m - 1]!,
246
+ H[m - 1]![m - 2]!,
247
+ H[m - 1]![m - 1]!,
248
+ );
249
+ if (!Number.isFinite(vals[0]) || !Number.isFinite(vals[1])) return null;
250
+ evals.push(vals[0], vals[1]);
251
+ m -= 2;
252
+ continue;
253
+ }
254
+ let mu = wilkinsonShift(
255
+ H[m - 2]![m - 2]!,
256
+ H[m - 2]![m - 1]!,
257
+ H[m - 1]![m - 2]!,
258
+ H[m - 1]![m - 1]!,
259
+ );
260
+ if (iter % 10 === 0) {
261
+ mu += Math.abs(H[m - 1]![m - 2]!) + Math.abs(H[m - 2]![m - 3]!);
262
+ }
263
+ if (!Number.isFinite(mu)) return null;
264
+ qrSweep(H, m, mu);
265
+ for (let i = 0; i < m; i++) {
266
+ for (let j = 0; j < i - 1; j++) H[i]![j] = 0;
267
+ }
268
+ }
269
+ return evals;
270
+ }
271
+
272
+ /** Unit nullspace vector of a square matrix, or `null` if none / non-finite. */
273
+ function nullspaceUnit(M: number[][]): number[] | null {
274
+ const n = M.length;
275
+ if (n === 0) return [];
276
+ const a = cloneGrid(M);
277
+ let scale = 0;
278
+ for (let i = 0; i < n; i++) {
279
+ for (let j = 0; j < n; j++) {
280
+ const v = a[i]![j]!;
281
+ if (!Number.isFinite(v)) return null;
282
+ if (Math.abs(v) > scale) scale = Math.abs(v);
283
+ }
284
+ }
285
+ const eps = Math.max(1e-14, 1e-12 * scale);
286
+ const pivotColOfRow: number[] = new Array(n).fill(-1);
287
+ const colIsPivot: boolean[] = new Array(n).fill(false);
288
+ let rank = 0;
289
+ for (let c = 0; c < n && rank < n; c++) {
290
+ let piv = rank;
291
+ let best = Math.abs(a[rank]![c]!);
292
+ for (let i = rank + 1; i < n; i++) {
293
+ const mag = Math.abs(a[i]![c]!);
294
+ if (mag > best) {
295
+ best = mag;
296
+ piv = i;
297
+ }
298
+ }
299
+ if (best < eps) continue;
300
+ if (piv !== rank) {
301
+ const tmp = a[rank]!;
302
+ a[rank] = a[piv]!;
303
+ a[piv] = tmp;
304
+ }
305
+ const diag = a[rank]![c]!;
306
+ for (let j = c; j < n; j++) a[rank]![j]! /= diag;
307
+ for (let i = 0; i < n; i++) {
308
+ if (i === rank) continue;
309
+ const f = a[i]![c]!;
310
+ if (f === 0) continue;
311
+ for (let j = c; j < n; j++) a[i]![j]! -= f * a[rank]![j]!;
312
+ }
313
+ pivotColOfRow[rank] = c;
314
+ colIsPivot[c] = true;
315
+ rank++;
316
+ }
317
+ let free = -1;
318
+ for (let c = 0; c < n; c++) {
319
+ if (!colIsPivot[c]) {
320
+ free = c;
321
+ break;
322
+ }
323
+ }
324
+ if (free < 0) return null;
325
+ const vec: number[] = new Array(n).fill(0);
326
+ vec[free] = 1;
327
+ for (let r = n - 1; r >= 0; r--) {
328
+ const c = pivotColOfRow[r]!;
329
+ if (c < 0) continue;
330
+ let s = 0;
331
+ for (let j = 0; j < n; j++) {
332
+ if (j === c) continue;
333
+ s += a[r]![j]! * vec[j]!;
334
+ }
335
+ vec[c] = -s;
336
+ }
337
+ let nrm = 0;
338
+ for (const x of vec) {
339
+ if (!Number.isFinite(x)) return null;
340
+ nrm += x * x;
341
+ }
342
+ nrm = Math.sqrt(nrm);
343
+ if (!(nrm > eps)) return null;
344
+ for (let i = 0; i < n; i++) vec[i]! /= nrm;
345
+ return vec;
346
+ }
347
+
348
+ export class PineMatrix {
349
+ private readonly cells: Cell[][] = [];
350
+ private nRows: number;
351
+ private nCols: number;
352
+
353
+ constructor(rows: number, cols: number, initial?: Cell) {
354
+ let r = dim(rows);
355
+ let c = dim(cols);
356
+ if (r > 0 && c > Math.floor(MAX_MATRIX_ELEMENTS / r)) {
357
+ r = 0;
358
+ c = 0;
359
+ }
360
+ this.nRows = r;
361
+ this.nCols = c;
362
+ const fill: Cell = initial === undefined ? null : initial;
363
+ for (let i = 0; i < this.nRows; i++) {
364
+ const row: Cell[] = [];
365
+ for (let j = 0; j < this.nCols; j++) row.push(fill);
366
+ this.cells.push(row);
367
+ }
368
+ }
369
+
370
+ rows(): number {
371
+ return this.nRows;
372
+ }
373
+
374
+ columns(): number {
375
+ return this.nCols;
376
+ }
377
+
378
+ /** `matrix.get` — OOB / non-finite index → `na`. */
379
+ get(row: number, col: number): Cell {
380
+ const i = resolveIndex(row, this.nRows);
381
+ const j = resolveIndex(col, this.nCols);
382
+ if (i === null || j === null) return null;
383
+ return this.cells[i]![j]!;
384
+ }
385
+
386
+ /** `matrix.set` — OOB / non-finite index is a no-op. */
387
+ set(row: number, col: number, value: Cell): void {
388
+ const i = resolveIndex(row, this.nRows);
389
+ const j = resolveIndex(col, this.nCols);
390
+ if (i === null || j === null) return;
391
+ this.cells[i]![j] = value;
392
+ }
393
+
394
+ fill(value: Cell): void {
395
+ for (let i = 0; i < this.nRows; i++) {
396
+ const row = this.cells[i]!;
397
+ for (let j = 0; j < this.nCols; j++) row[j] = value;
398
+ }
399
+ }
400
+
401
+ transpose(): PineMatrix {
402
+ const t = new PineMatrix(this.nCols, this.nRows);
403
+ for (let i = 0; i < this.nRows; i++) {
404
+ for (let j = 0; j < this.nCols; j++) t.set(j, i, this.cells[i]![j]!);
405
+ }
406
+ return t;
407
+ }
408
+
409
+ row(i: number): Cell[] {
410
+ const r = resolveIndex(i, this.nRows);
411
+ return r === null ? [] : this.cells[r]!.slice();
412
+ }
413
+
414
+ col(j: number): Cell[] {
415
+ const c = resolveIndex(j, this.nCols);
416
+ if (c === null) return [];
417
+ const out: Cell[] = [];
418
+ for (let i = 0; i < this.nRows; i++) out.push(this.cells[i]![c]!);
419
+ return out;
420
+ }
421
+
422
+ copy(): PineMatrix {
423
+ const out = new PineMatrix(this.nRows, this.nCols);
424
+ for (let i = 0; i < this.nRows; i++) {
425
+ for (let j = 0; j < this.nCols; j++) out.set(i, j, this.cells[i]![j]!);
426
+ }
427
+ return out;
428
+ }
429
+
430
+ elementsCount(): number {
431
+ return this.nRows * this.nCols;
432
+ }
433
+
434
+ isSquare(): boolean {
435
+ return this.nRows === this.nCols && this.nRows > 0;
436
+ }
437
+
438
+ /** `na` if empty or any element is `na`. */
439
+ sum(): Cell {
440
+ const vals = this.finiteAll();
441
+ if (vals === null) return null;
442
+ let acc = 0;
443
+ for (const v of vals) acc += v;
444
+ return acc;
445
+ }
446
+
447
+ avg(): Cell {
448
+ const vals = this.finiteAll();
449
+ if (vals === null || vals.length === 0) return null;
450
+ let acc = 0;
451
+ for (const v of vals) acc += v;
452
+ return acc / vals.length;
453
+ }
454
+
455
+ min(): Cell {
456
+ const vals = this.finiteAll();
457
+ if (vals === null || vals.length === 0) return null;
458
+ let best = vals[0]!;
459
+ for (const v of vals) if (v < best) best = v;
460
+ return best;
461
+ }
462
+
463
+ max(): Cell {
464
+ const vals = this.finiteAll();
465
+ if (vals === null || vals.length === 0) return null;
466
+ let best = vals[0]!;
467
+ for (const v of vals) if (v > best) best = v;
468
+ return best;
469
+ }
470
+
471
+ /** Diagonal sum. Non-square or any na on diagonal → `na`. */
472
+ trace(): Cell {
473
+ if (!this.isSquare()) return null;
474
+ let acc = 0;
475
+ for (let i = 0; i < this.nRows; i++) {
476
+ const v = this.cells[i]![i]!;
477
+ if (v === null || !Number.isFinite(v)) return null;
478
+ acc += v;
479
+ }
480
+ return acc;
481
+ }
482
+
483
+ /** Gaussian elimination. Non-square or any na → `na`. Singular → `0`. */
484
+ det(): Cell {
485
+ if (!this.isSquare()) return null;
486
+ const n = this.nRows;
487
+ if (n === 0) return null;
488
+ const a: number[][] = [];
489
+ for (let i = 0; i < n; i++) {
490
+ const row: number[] = [];
491
+ for (let j = 0; j < n; j++) {
492
+ const v = this.cells[i]![j]!;
493
+ if (v === null || !Number.isFinite(v)) return null;
494
+ row.push(v);
495
+ }
496
+ a.push(row);
497
+ }
498
+ if (n === 1) return a[0]![0]!;
499
+ if (n === 2) {
500
+ const d = a[0]![0]! * a[1]![1]! - a[0]![1]! * a[1]![0]!;
501
+ return Number.isFinite(d) ? d : null;
502
+ }
503
+ let sign = 1;
504
+ for (let k = 0; k < n; k++) {
505
+ let pivot = k;
506
+ let best = Math.abs(a[k]![k]!);
507
+ for (let i = k + 1; i < n; i++) {
508
+ const mag = Math.abs(a[i]![k]!);
509
+ if (mag > best) {
510
+ best = mag;
511
+ pivot = i;
512
+ }
513
+ }
514
+ if (best === 0) return 0;
515
+ if (pivot !== k) {
516
+ const tmp = a[k]!;
517
+ a[k] = a[pivot]!;
518
+ a[pivot] = tmp;
519
+ sign = -sign;
520
+ }
521
+ const diag = a[k]![k]!;
522
+ for (let i = k + 1; i < n; i++) {
523
+ const f = a[i]![k]! / diag;
524
+ for (let j = k; j < n; j++) a[i]![j]! -= f * a[k]![j]!;
525
+ }
526
+ }
527
+ let det = sign;
528
+ for (let i = 0; i < n; i++) det *= a[i]![i]!;
529
+ return Number.isFinite(det) ? det : null;
530
+ }
531
+
532
+ /** Matrix×matrix or scalar×matrix. Incompatible shapes or non-finite grid → `na`. */
533
+ mult(other: PineMatrix | number): PineMatrix | null {
534
+ if (typeof other === "number") {
535
+ if (!Number.isFinite(other)) return null;
536
+ const out = new PineMatrix(this.nRows, this.nCols);
537
+ for (let i = 0; i < this.nRows; i++) {
538
+ for (let j = 0; j < this.nCols; j++) {
539
+ const v = this.cells[i]![j]!;
540
+ if (v === null || !Number.isFinite(v)) {
541
+ out.set(i, j, null);
542
+ continue;
543
+ }
544
+ const p = v * other;
545
+ out.set(i, j, Number.isFinite(p) ? p : null);
546
+ }
547
+ }
548
+ return out;
549
+ }
550
+ if (this.nCols !== other.nRows) return null;
551
+ const a = this.asFiniteGrid();
552
+ const b = other.asFiniteGrid();
553
+ if (a === null || b === null) return null;
554
+ const out = new PineMatrix(this.nRows, other.nCols, 0);
555
+ for (let i = 0; i < this.nRows; i++) {
556
+ for (let j = 0; j < other.nCols; j++) {
557
+ let s = 0;
558
+ for (let k = 0; k < this.nCols; k++) s += a[i]![k]! * b[k]![j]!;
559
+ if (!Number.isFinite(s)) return null;
560
+ out.set(i, j, s);
561
+ }
562
+ }
563
+ return out;
564
+ }
565
+
566
+ /** Gauss–Jordan inverse. Non-square, singular, or non-finite → `na`. */
567
+ inv(): PineMatrix | null {
568
+ if (this.nRows !== this.nCols) return null;
569
+ const n = this.nRows;
570
+ if (n === 0) return new PineMatrix(0, 0);
571
+ const src = this.asFiniteGrid();
572
+ if (src === null) return null;
573
+ const aug: number[][] = [];
574
+ for (let i = 0; i < n; i++) {
575
+ const row = src[i]!.slice();
576
+ for (let j = 0; j < n; j++) row.push(i === j ? 1 : 0);
577
+ aug.push(row);
578
+ }
579
+ const eps = 1e-12;
580
+ for (let k = 0; k < n; k++) {
581
+ let pivot = k;
582
+ let best = Math.abs(aug[k]![k]!);
583
+ for (let i = k + 1; i < n; i++) {
584
+ const mag = Math.abs(aug[i]![k]!);
585
+ if (mag > best) {
586
+ best = mag;
587
+ pivot = i;
588
+ }
589
+ }
590
+ if (best < eps) return null;
591
+ if (pivot !== k) {
592
+ const tmp = aug[k]!;
593
+ aug[k] = aug[pivot]!;
594
+ aug[pivot] = tmp;
595
+ }
596
+ const diag = aug[k]![k]!;
597
+ for (let j = 0; j < 2 * n; j++) aug[k]![j]! /= diag;
598
+ for (let i = 0; i < n; i++) {
599
+ if (i === k) continue;
600
+ const f = aug[i]![k]!;
601
+ if (f === 0) continue;
602
+ for (let j = 0; j < 2 * n; j++) aug[i]![j]! -= f * aug[k]![j]!;
603
+ }
604
+ }
605
+ const out = new PineMatrix(n, n, 0);
606
+ for (let i = 0; i < n; i++) {
607
+ for (let j = 0; j < n; j++) {
608
+ const v = aug[i]![n + j]!;
609
+ if (!Number.isFinite(v)) return null;
610
+ out.set(i, j, v);
611
+ }
612
+ }
613
+ return out;
614
+ }
615
+
616
+ /** Integer power of a square matrix. `n=0` → identity; negative → `inv` then `pow`. */
617
+ pow(n: number): PineMatrix | null {
618
+ if (this.nRows !== this.nCols) return null;
619
+ if (!Number.isFinite(n)) return null;
620
+ const e = Math.trunc(n);
621
+ if (e === 0) return PineMatrix.eye(this.nRows);
622
+ if (e < 0) {
623
+ const inverse = this.inv();
624
+ return inverse === null ? null : inverse.pow(-e);
625
+ }
626
+ if (this.asFiniteGrid() === null) return null;
627
+ let result: PineMatrix | null = PineMatrix.eye(this.nRows);
628
+ let base: PineMatrix | null = this.copy();
629
+ let exp = e;
630
+ while (exp > 0) {
631
+ if (exp % 2 === 1) {
632
+ result = result.mult(base);
633
+ if (result === null) return null;
634
+ }
635
+ exp = Math.trunc(exp / 2);
636
+ if (exp > 0) {
637
+ base = base.mult(base);
638
+ if (base === null) return null;
639
+ }
640
+ }
641
+ return result;
642
+ }
643
+
644
+ /** Kronecker product. Non-finite element → `na`. */
645
+ kron(other: PineMatrix): PineMatrix | null {
646
+ const a = this.asFiniteGrid();
647
+ const b = other.asFiniteGrid();
648
+ if (a === null || b === null) return null;
649
+ const p = other.nRows;
650
+ const q = other.nCols;
651
+ const out = new PineMatrix(this.nRows * p, this.nCols * q, 0);
652
+ for (let i = 0; i < this.nRows; i++) {
653
+ for (let j = 0; j < this.nCols; j++) {
654
+ const av = a[i]![j]!;
655
+ for (let k = 0; k < p; k++) {
656
+ for (let l = 0; l < q; l++) {
657
+ const v = av * b[k]![l]!;
658
+ out.set(i * p + k, j * q + l, Number.isFinite(v) ? v : null);
659
+ }
660
+ }
661
+ }
662
+ }
663
+ return out;
664
+ }
665
+
666
+ /** Python `is_zero`: `na` cells count as zero; any non-zero / non-finite → false. */
667
+ isZero(): boolean {
668
+ for (let i = 0; i < this.nRows; i++) {
669
+ for (let j = 0; j < this.nCols; j++) {
670
+ const v = this.cells[i]![j]!;
671
+ if (v === null) continue;
672
+ if (v !== 0) return false;
673
+ }
674
+ }
675
+ return true;
676
+ }
677
+
678
+ /** Python `is_identity`: 0×0 is identity; non-square / non-finite → false. */
679
+ isIdentity(): boolean {
680
+ if (this.nRows !== this.nCols) return false;
681
+ for (let i = 0; i < this.nRows; i++) {
682
+ for (let j = 0; j < this.nCols; j++) {
683
+ const expected = i === j ? 1 : 0;
684
+ const v = this.cells[i]![j]!;
685
+ if (v === null || v !== expected) return false;
686
+ }
687
+ }
688
+ return true;
689
+ }
690
+
691
+ isDiagonal(): boolean {
692
+ if (this.nRows !== this.nCols) return false;
693
+ for (let i = 0; i < this.nRows; i++) {
694
+ for (let j = 0; j < this.nCols; j++) {
695
+ if (i === j) continue;
696
+ const v = this.cells[i]![j]!;
697
+ if (v !== null && v !== 0) return false;
698
+ }
699
+ }
700
+ return true;
701
+ }
702
+
703
+ isSymmetric(): boolean {
704
+ if (this.nRows !== this.nCols) return false;
705
+ for (let i = 0; i < this.nRows; i++) {
706
+ for (let j = i + 1; j < this.nCols; j++) {
707
+ if (this.cells[i]![j]! !== this.cells[j]![i]!) return false;
708
+ }
709
+ }
710
+ return true;
711
+ }
712
+
713
+ isAntisymmetric(): boolean {
714
+ if (this.nRows !== this.nCols) return false;
715
+ for (let i = 0; i < this.nRows; i++) {
716
+ for (let j = 0; j < this.nCols; j++) {
717
+ const a = this.cells[i]![j]!;
718
+ const b = this.cells[j]![i]!;
719
+ if (i === j) {
720
+ if (a !== null && a !== 0) return false;
721
+ continue;
722
+ }
723
+ if (a !== null && b !== null) {
724
+ if (a !== -b) return false;
725
+ } else if (a !== b) {
726
+ return false;
727
+ }
728
+ }
729
+ }
730
+ return true;
731
+ }
732
+
733
+ /** True if upper- or lower-triangular. 0×0 is triangular; non-square → false. */
734
+ isTriangular(): boolean {
735
+ if (this.nRows !== this.nCols) return false;
736
+ let upper = true;
737
+ let lower = true;
738
+ for (let i = 0; i < this.nRows; i++) {
739
+ for (let j = 0; j < this.nCols; j++) {
740
+ const v = this.cells[i]![j]!;
741
+ const nonzero = v !== null && v !== 0;
742
+ if (i > j && nonzero) upper = false;
743
+ if (i < j && nonzero) lower = false;
744
+ }
745
+ }
746
+ return upper || lower;
747
+ }
748
+
749
+ /** OOB / non-finite index is a no-op. */
750
+ swapRows(i: number, j: number): void {
751
+ const a = resolveIndex(i, this.nRows);
752
+ const b = resolveIndex(j, this.nRows);
753
+ if (a === null || b === null || a === b) return;
754
+ const tmp = this.cells[a]!;
755
+ this.cells[a] = this.cells[b]!;
756
+ this.cells[b] = tmp;
757
+ }
758
+
759
+ /** OOB / non-finite index is a no-op. */
760
+ swapColumns(i: number, j: number): void {
761
+ const a = resolveIndex(i, this.nCols);
762
+ const b = resolveIndex(j, this.nCols);
763
+ if (a === null || b === null || a === b) return;
764
+ for (const row of this.cells) {
765
+ const tmp = row[a]!;
766
+ row[a] = row[b]!;
767
+ row[b] = tmp;
768
+ }
769
+ }
770
+
771
+ /**
772
+ * Insert a row at *index* (append if omitted / past end).
773
+ * Short rows pad `na`; long rows truncate. 0×0 adopts column count from *values*.
774
+ * Negative / non-finite index or size cap → no-op.
775
+ */
776
+ addRow(index?: number, values?: Cell[]): void {
777
+ const adopt = this.nRows === 0 && this.nCols === 0;
778
+ const cols = adopt ? (values?.length ?? 0) : this.nCols;
779
+ if (!fitsElements(this.nRows + 1, cols)) return;
780
+ if (index !== undefined) {
781
+ if (!Number.isFinite(index)) return;
782
+ if (Math.trunc(index) < 0) return;
783
+ }
784
+ const row = padOrTrunc(values, cols);
785
+ if (adopt) this.nCols = cols;
786
+ const at = index === undefined ? this.nRows : Math.trunc(index);
787
+ if (at >= this.nRows) this.cells.push(row);
788
+ else this.cells.splice(at, 0, row);
789
+ this.nRows += 1;
790
+ }
791
+
792
+ /**
793
+ * Insert a column at *index* (append if omitted / past end).
794
+ * Short cols pad `na`; long cols truncate. 0×0 becomes N×1 from *values*.
795
+ */
796
+ addCol(index?: number, values?: Cell[]): void {
797
+ if (this.nRows === 0 && this.nCols === 0) {
798
+ const n = values?.length ?? 0;
799
+ if (!fitsElements(n, n === 0 ? 0 : 1)) return;
800
+ for (const v of values ?? []) this.cells.push([v]);
801
+ this.nRows = n;
802
+ this.nCols = n === 0 ? 0 : 1;
803
+ return;
804
+ }
805
+ if (!fitsElements(this.nRows, this.nCols + 1)) return;
806
+ if (index !== undefined) {
807
+ if (!Number.isFinite(index)) return;
808
+ if (Math.trunc(index) < 0) return;
809
+ }
810
+ const col = padOrTrunc(values, this.nRows);
811
+ const at =
812
+ index === undefined ? this.nCols : Math.min(Math.trunc(index), this.nCols);
813
+ for (let i = 0; i < this.nRows; i++) this.cells[i]!.splice(at, 0, col[i]!);
814
+ this.nCols += 1;
815
+ }
816
+
817
+ /** OOB / non-finite index is a no-op. */
818
+ removeRow(index: number): void {
819
+ const i = resolveIndex(index, this.nRows);
820
+ if (i === null) return;
821
+ this.cells.splice(i, 1);
822
+ this.nRows -= 1;
823
+ }
824
+
825
+ /** OOB / non-finite index is a no-op. */
826
+ removeCol(index: number): void {
827
+ const i = resolveIndex(index, this.nCols);
828
+ if (i === null) return;
829
+ for (const row of this.cells) row.splice(i, 1);
830
+ this.nCols -= 1;
831
+ }
832
+
833
+ /** New matrix, same element count, row-major. Bad size → `na`. */
834
+ reshape(rows: number, cols: number): PineMatrix | null {
835
+ if (!Number.isFinite(rows) || !Number.isFinite(cols)) return null;
836
+ const r = Math.trunc(rows);
837
+ const c = Math.trunc(cols);
838
+ if (r < 0 || c < 0) return null;
839
+ if (r * c !== this.nRows * this.nCols) return null;
840
+ if (!fitsElements(r, c)) return null;
841
+ const flat = this.flatten();
842
+ const out = new PineMatrix(r, c);
843
+ for (let i = 0; i < r; i++) {
844
+ for (let j = 0; j < c; j++) out.set(i, j, flat[i * c + j]!);
845
+ }
846
+ return out;
847
+ }
848
+
849
+ /** Vertical stack when column counts match; else `na`. */
850
+ concat(other: PineMatrix): PineMatrix | null {
851
+ if (this.nCols !== other.nCols) return null;
852
+ const r = this.nRows + other.nRows;
853
+ const c = this.nCols;
854
+ if (!fitsElements(r, c)) return null;
855
+ const out = new PineMatrix(r, c);
856
+ for (let i = 0; i < this.nRows; i++) {
857
+ for (let j = 0; j < c; j++) out.set(i, j, this.cells[i]![j]!);
858
+ }
859
+ for (let i = 0; i < other.nRows; i++) {
860
+ for (let j = 0; j < c; j++) out.set(this.nRows + i, j, other.cells[i]![j]!);
861
+ }
862
+ return out;
863
+ }
864
+
865
+ /** Half-open `[fromRow, toRow) × [fromCol, toCol)`. Invalid range → `na`. */
866
+ submatrix(fromRow: number, toRow: number, fromCol: number, toCol: number): PineMatrix | null {
867
+ if (
868
+ !Number.isFinite(fromRow) ||
869
+ !Number.isFinite(toRow) ||
870
+ !Number.isFinite(fromCol) ||
871
+ !Number.isFinite(toCol)
872
+ ) {
873
+ return null;
874
+ }
875
+ const r0 = Math.trunc(fromRow);
876
+ const r1 = Math.trunc(toRow);
877
+ const c0 = Math.trunc(fromCol);
878
+ const c1 = Math.trunc(toCol);
879
+ if (!(0 <= r0 && r0 <= r1 && r1 <= this.nRows)) return null;
880
+ if (!(0 <= c0 && c0 <= c1 && c1 <= this.nCols)) return null;
881
+ const out = new PineMatrix(r1 - r0, c1 - c0);
882
+ for (let i = r0; i < r1; i++) {
883
+ for (let j = c0; j < c1; j++) out.set(i - r0, j - c0, this.cells[i]![j]!);
884
+ }
885
+ return out;
886
+ }
887
+
888
+ /** Reverse element order: reverse rows, then each row (Python `matrix.reverse`). */
889
+ reverse(): void {
890
+ this.cells.reverse();
891
+ for (const row of this.cells) row.reverse();
892
+ }
893
+
894
+ /** Sort rows by *column*. `na` / non-finite keys always last. */
895
+ sort(column = 0, order: "asc" | "desc" = "asc"): void {
896
+ if (this.nRows === 0) return;
897
+ const c = resolveIndex(column, this.nCols);
898
+ if (c === null) return;
899
+ const desc = order === "desc";
900
+ const nonNa: Cell[][] = [];
901
+ const naRows: Cell[][] = [];
902
+ for (const row of this.cells) {
903
+ const v = row[c]!;
904
+ if (v === null || !Number.isFinite(v)) naRows.push(row);
905
+ else nonNa.push(row);
906
+ }
907
+ nonNa.sort((a, b) => {
908
+ const d = (a[c] as number) - (b[c] as number);
909
+ return desc ? -d : d;
910
+ });
911
+ this.cells.length = 0;
912
+ for (const row of nonNa) this.cells.push(row);
913
+ for (const row of naRows) this.cells.push(row);
914
+ }
915
+
916
+ /** Median of finite cells (Python skips `na`). Empty → `na`. */
917
+ median(): Cell {
918
+ const vals: number[] = [];
919
+ for (let i = 0; i < this.nRows; i++) {
920
+ for (let j = 0; j < this.nCols; j++) {
921
+ const v = this.cells[i]![j]!;
922
+ if (v !== null && Number.isFinite(v)) vals.push(v);
923
+ }
924
+ }
925
+ if (vals.length === 0) return null;
926
+ vals.sort((a, b) => a - b);
927
+ const mid = Math.floor(vals.length / 2);
928
+ if (vals.length % 2 === 1) return vals[mid]!;
929
+ return (vals[mid - 1]! + vals[mid]!) / 2;
930
+ }
931
+
932
+ /** Most common finite cell; first-seen wins ties. Empty / all `na` → `na`. */
933
+ mode(): Cell {
934
+ const counts = new Map<number, number>();
935
+ let best: number | null = null;
936
+ let bestCount = 0;
937
+ for (let i = 0; i < this.nRows; i++) {
938
+ for (let j = 0; j < this.nCols; j++) {
939
+ const v = this.cells[i]![j]!;
940
+ if (v === null || !Number.isFinite(v)) continue;
941
+ const n = (counts.get(v) ?? 0) + 1;
942
+ counts.set(v, n);
943
+ if (n > bestCount) {
944
+ best = v;
945
+ bestCount = n;
946
+ }
947
+ }
948
+ }
949
+ return best;
950
+ }
951
+
952
+ /** Every cell is 0 or 1. `na` / other values → false. Empty is true. */
953
+ isBinary(): boolean {
954
+ for (let i = 0; i < this.nRows; i++) {
955
+ for (let j = 0; j < this.nCols; j++) {
956
+ const v = this.cells[i]![j]!;
957
+ if (v !== 0 && v !== 1) return false;
958
+ }
959
+ }
960
+ return true;
961
+ }
962
+
963
+ /** Each row is non-negative and sums to 1. Empty is true; `na` → false. */
964
+ isStochastic(): boolean {
965
+ for (let i = 0; i < this.nRows; i++) {
966
+ let total = 0;
967
+ for (let j = 0; j < this.nCols; j++) {
968
+ const v = this.cells[i]![j]!;
969
+ if (v === null || !Number.isFinite(v) || v < 0) return false;
970
+ total += v;
971
+ }
972
+ if (Math.abs(total - 1) > 1e-9) return false;
973
+ }
974
+ return true;
975
+ }
976
+
977
+ /** Square; off-antidiagonal cells are 0 or `na`. 0×0 is true. */
978
+ isAntidiagonal(): boolean {
979
+ if (this.nRows !== this.nCols) return false;
980
+ const n = this.nRows;
981
+ for (let i = 0; i < n; i++) {
982
+ for (let j = 0; j < n; j++) {
983
+ if (i + j === n - 1) continue;
984
+ const v = this.cells[i]![j]!;
985
+ if (v !== null && v !== 0) return false;
986
+ }
987
+ }
988
+ return true;
989
+ }
990
+
991
+ /** Element-wise `self - other` (Python `Matrix.diff`). Shape mismatch → `na`. */
992
+ diff(other: PineMatrix): PineMatrix | null {
993
+ if (this.nRows !== other.nRows || this.nCols !== other.nCols) return null;
994
+ const out = new PineMatrix(this.nRows, this.nCols);
995
+ for (let i = 0; i < this.nRows; i++) {
996
+ for (let j = 0; j < this.nCols; j++) {
997
+ const a = this.cells[i]![j]!;
998
+ const b = other.cells[i]![j]!;
999
+ if (a === null || b === null || !Number.isFinite(a) || !Number.isFinite(b)) {
1000
+ out.set(i, j, null);
1001
+ continue;
1002
+ }
1003
+ const d = a - b;
1004
+ out.set(i, j, Number.isFinite(d) ? d : null);
1005
+ }
1006
+ }
1007
+ return out;
1008
+ }
1009
+
1010
+ /** Gaussian rank. Empty → 0; any `na` / non-finite → `na`. */
1011
+ rank(): number | null {
1012
+ if (this.nRows === 0 || this.nCols === 0) return 0;
1013
+ const src = this.asFiniteGrid();
1014
+ if (src === null) return null;
1015
+ const m = this.nRows;
1016
+ const n = this.nCols;
1017
+ const g: number[][] = [];
1018
+ for (let i = 0; i < m; i++) g.push(src[i]!.slice());
1019
+ const eps = 1e-12;
1020
+ let r = 0;
1021
+ for (let c = 0; c < n && r < m; c++) {
1022
+ let pivot = r;
1023
+ let best = Math.abs(g[r]![c]!);
1024
+ for (let i = r + 1; i < m; i++) {
1025
+ const mag = Math.abs(g[i]![c]!);
1026
+ if (mag > best) {
1027
+ best = mag;
1028
+ pivot = i;
1029
+ }
1030
+ }
1031
+ if (best < eps) continue;
1032
+ if (pivot !== r) {
1033
+ const tmp = g[r]!;
1034
+ g[r] = g[pivot]!;
1035
+ g[pivot] = tmp;
1036
+ }
1037
+ const diag = g[r]![c]!;
1038
+ for (let i = r + 1; i < m; i++) {
1039
+ const f = g[i]![c]! / diag;
1040
+ for (let j = c; j < n; j++) g[i]![j]! -= f * g[r]![j]!;
1041
+ }
1042
+ r++;
1043
+ }
1044
+ return r;
1045
+ }
1046
+
1047
+ /** Moore–Penrose pseudoinverse via one-sided Jacobi SVD. Empty-shape → transpose-empty; non-finite → `na`. */
1048
+ pinv(): PineMatrix | null {
1049
+ if (this.nRows === 0 || this.nCols === 0) return new PineMatrix(this.nCols, this.nRows);
1050
+ const src = this.asFiniteGrid();
1051
+ if (src === null) return null;
1052
+ const svd = jacobiSvd(src);
1053
+ if (svd === null) return null;
1054
+ const { u, s, v } = svd;
1055
+ const m = this.nRows;
1056
+ const n = this.nCols;
1057
+ let maxS = 0;
1058
+ for (const si of s) {
1059
+ if (!Number.isFinite(si)) return null;
1060
+ if (si > maxS) maxS = si;
1061
+ }
1062
+ const cutoff = 1e-12 * maxS * Math.max(m, n);
1063
+ const sinv: number[] = [];
1064
+ for (const si of s) sinv.push(si > cutoff ? 1 / si : 0);
1065
+ const out = new PineMatrix(n, m, 0);
1066
+ for (let i = 0; i < n; i++) {
1067
+ for (let j = 0; j < m; j++) {
1068
+ let acc = 0;
1069
+ for (let k = 0; k < n; k++) acc += v[i]![k]! * sinv[k]! * u[j]![k]!;
1070
+ if (!Number.isFinite(acc)) return null;
1071
+ out.set(i, j, acc);
1072
+ }
1073
+ }
1074
+ return out;
1075
+ }
1076
+
1077
+ /** Real parts of eigenvalues. Non-square / non-finite → `na`; 0×0 → `[]`. */
1078
+ eigenvalues(): number[] | null {
1079
+ if (this.nRows !== this.nCols) return null;
1080
+ if (this.nRows === 0) return [];
1081
+ const src = this.asFiniteGrid();
1082
+ if (src === null) return null;
1083
+ return realEigenvalues(src);
1084
+ }
1085
+
1086
+ /** Eigenvectors as unit columns. Non-square / non-finite / zero column → `na`. */
1087
+ eigenvectors(): PineMatrix | null {
1088
+ if (this.nRows !== this.nCols) return null;
1089
+ if (this.nRows === 0) return new PineMatrix(0, 0);
1090
+ const src = this.asFiniteGrid();
1091
+ if (src === null) return null;
1092
+ const vals = realEigenvalues(src);
1093
+ if (vals === null) return null;
1094
+ const n = this.nRows;
1095
+ const out = new PineMatrix(n, n, 0);
1096
+ for (let j = 0; j < n; j++) {
1097
+ const lam = vals[j]!;
1098
+ const shifted: number[][] = [];
1099
+ for (let i = 0; i < n; i++) {
1100
+ const row = src[i]!.slice();
1101
+ row[i]! -= lam;
1102
+ shifted.push(row);
1103
+ }
1104
+ const vec = nullspaceUnit(shifted);
1105
+ if (vec === null) return null;
1106
+ for (let i = 0; i < n; i++) {
1107
+ const v = vec[i]!;
1108
+ if (!Number.isFinite(v)) return null;
1109
+ out.set(i, j, v);
1110
+ }
1111
+ }
1112
+ return out;
1113
+ }
1114
+
1115
+ private static eye(n: number): PineMatrix {
1116
+ const out = new PineMatrix(n, n, 0);
1117
+ for (let i = 0; i < n; i++) out.set(i, i, 1);
1118
+ return out;
1119
+ }
1120
+
1121
+ private asFiniteGrid(): number[][] | null {
1122
+ const out: number[][] = [];
1123
+ for (let i = 0; i < this.nRows; i++) {
1124
+ const row: number[] = [];
1125
+ for (let j = 0; j < this.nCols; j++) {
1126
+ const v = this.cells[i]![j]!;
1127
+ if (v === null || !Number.isFinite(v)) return null;
1128
+ row.push(v);
1129
+ }
1130
+ out.push(row);
1131
+ }
1132
+ return out;
1133
+ }
1134
+
1135
+ private finiteAll(): number[] | null {
1136
+ const out: number[] = [];
1137
+ for (let i = 0; i < this.nRows; i++) {
1138
+ for (let j = 0; j < this.nCols; j++) {
1139
+ const v = this.cells[i]![j]!;
1140
+ if (v === null || !Number.isFinite(v)) return null;
1141
+ out.push(v);
1142
+ }
1143
+ }
1144
+ return out;
1145
+ }
1146
+
1147
+ private flatten(): Cell[] {
1148
+ const out: Cell[] = [];
1149
+ for (let i = 0; i < this.nRows; i++) {
1150
+ for (let j = 0; j < this.nCols; j++) out.push(this.cells[i]![j]!);
1151
+ }
1152
+ return out;
1153
+ }
1154
+ }