@hoox-sh/pynets 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hoox-sh/pynets",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "author": "jango_blockchained",
package/src/cli/rich.ts CHANGED
@@ -12,7 +12,7 @@ const MUTED = [203, 204, 189] as const;
12
12
  const FAIL = [231, 0, 11] as const;
13
13
  const WARN = [235, 169, 65] as const;
14
14
 
15
- export const VERSION = "0.1.0";
15
+ export const VERSION = "0.2.0";
16
16
 
17
17
  type Rgb = readonly [number, number, number];
18
18
 
@@ -122,7 +122,7 @@ export class Rich {
122
122
  this.write(this.paint("accent", "COMMANDS"));
123
123
  } else {
124
124
  this.write(
125
- "usage: pynets <check|format|run|dump|info> [file] [--bars N] [--commission N] [--slippage N] [--pyramiding N] [--json] [--plain] [--rich] [--full]",
125
+ "usage: pynets <check|format|run|dump|info> [file] [--bars N] [--mode interpret|compile|auto] [--commission N] [--slippage N] [--pyramiding N] [--json] [--plain] [--rich] [--full]",
126
126
  );
127
127
  this.write("");
128
128
  this.write("commands:");
@@ -130,7 +130,7 @@ export class Rich {
130
130
  const cmds: Array<[string, string]> = [
131
131
  ["check", "Parse-only validation (CI-friendly exit codes)"],
132
132
  ["format", "Parse → unparse (pretty-print on TTY)"],
133
- ["run", "Interpret on synthetic OHLCV"],
133
+ ["run", "Run on synthetic OHLCV (interpret or compile JS emit)"],
134
134
  ["dump", "Dump the ASDL-shaped AST"],
135
135
  ["info", "Version and runtime extras"],
136
136
  ];
@@ -146,6 +146,7 @@ export class Rich {
146
146
  else this.write("options:");
147
147
  const opts: Array<[string, string]> = [
148
148
  ["--bars N", "Synthetic bar count for run (default 20, max 100000)"],
149
+ ["--mode MODE", "run engine: interpret | compile | auto (JS emit; default interpret)"],
149
150
  ["--commission N", "Broker commission fraction for run (e.g. 0.001)"],
150
151
  ["--slippage N", "Broker slippage in price units for run"],
151
152
  ["--pyramiding N", "Broker max same-direction adds for run"],
package/src/cli.ts CHANGED
@@ -20,6 +20,8 @@ import {
20
20
  useRich,
21
21
  } from "./cli/rich.ts";
22
22
 
23
+ export type RunMode = "interpret" | "compile" | "auto";
24
+
23
25
  export const MAX_BARS = 100_000;
24
26
 
25
27
  export class UsageError extends Error {
@@ -155,6 +157,11 @@ function takeValue(args: string[], i: number, flag: string): { raw: string | und
155
157
  return { raw: a.slice(flag.length + 1), next: i };
156
158
  }
157
159
 
160
+ function requireMode(raw: string | undefined): RunMode {
161
+ if (raw === "interpret" || raw === "compile" || raw === "auto") return raw;
162
+ throw new UsageError("error: --mode must be interpret|compile|auto");
163
+ }
164
+
158
165
  export function parseArgs(argv: string[]): {
159
166
  cmd: string;
160
167
  file: string | undefined;
@@ -162,6 +169,7 @@ export function parseArgs(argv: string[]): {
162
169
  json: boolean;
163
170
  indent: number;
164
171
  full: boolean;
172
+ mode: RunMode;
165
173
  commission: number | undefined;
166
174
  slippage: number | undefined;
167
175
  pyramiding: number | undefined;
@@ -174,13 +182,14 @@ export function parseArgs(argv: string[]): {
174
182
  let json = false;
175
183
  let indent = 2;
176
184
  let full = false;
185
+ let mode: RunMode = "interpret";
177
186
  let commission: number | undefined;
178
187
  let slippage: number | undefined;
179
188
  let pyramiding: number | undefined;
180
189
  for (let i = 1; i < args.length; i++) {
181
190
  const a = args[i]!;
182
191
  if (a === "--help" || a === "-h") {
183
- return { cmd: "help", file: undefined, bars, json, indent, full, commission, slippage, pyramiding };
192
+ return { cmd: "help", file: undefined, bars, json, indent, full, mode, commission, slippage, pyramiding };
184
193
  }
185
194
  if (a === "--json") {
186
195
  json = true;
@@ -190,6 +199,12 @@ export function parseArgs(argv: string[]): {
190
199
  full = true;
191
200
  continue;
192
201
  }
202
+ if (a === "--mode" || a.startsWith("--mode=")) {
203
+ const got = takeValue(args, i, "--mode");
204
+ mode = requireMode(got.raw);
205
+ i = got.next;
206
+ continue;
207
+ }
193
208
  if (a === "--bars" || a.startsWith("--bars=")) {
194
209
  const got = takeValue(args, i, "--bars");
195
210
  bars = clampBars(requireUint(got.raw, "--bars"));
@@ -230,7 +245,7 @@ export function parseArgs(argv: string[]): {
230
245
  if (file != null) throw new UsageError(`error: unexpected argument: ${a}`);
231
246
  file = a;
232
247
  }
233
- return { cmd, file, bars, json, indent, full, commission, slippage, pyramiding };
248
+ return { cmd, file, bars, json, indent, full, mode, commission, slippage, pyramiding };
234
249
  }
235
250
 
236
251
  function fmtNum(v: number | null | undefined): string {
@@ -346,21 +361,27 @@ function runJson(out: RuntimeOut): void {
346
361
  series: typeof out.series;
347
362
  count: number;
348
363
  script_name: string | null;
364
+ mode: RuntimeOut["mode"];
349
365
  events?: RuntimeEvent[];
350
366
  drawings?: DrawingLike[];
351
367
  fills?: RuntimeFill[];
352
368
  alerts?: AlertLike[];
369
+ compile_fallback_reason?: string;
370
+ auto_backend?: RuntimeOut["auto_backend"];
353
371
  error?: string;
354
372
  } = {
355
373
  plots: out.plots,
356
374
  series: out.series,
357
375
  count: out.count,
358
376
  script_name: out.script_name,
377
+ mode: out.mode,
359
378
  };
360
379
  if (out.events != null) payload.events = out.events;
361
380
  if (out.drawings != null) payload.drawings = out.drawings;
362
381
  if (out.fills != null) payload.fills = out.fills;
363
382
  if (out.alerts != null) payload.alerts = out.alerts;
383
+ if (out.compile_fallback_reason != null) payload.compile_fallback_reason = out.compile_fallback_reason;
384
+ if (out.auto_backend != null) payload.auto_backend = out.auto_backend;
364
385
  if (out.error != null) payload.error = out.error;
365
386
  writeJson(payload);
366
387
  if (out.error != null) process.exit(1);
@@ -506,11 +527,12 @@ function run(
506
527
  bars: number,
507
528
  asJson: boolean,
508
529
  broker: BrokerSettings,
530
+ mode: RunMode,
509
531
  ): void {
510
532
  const t0 = performance.now();
511
533
  let out: RuntimeOut;
512
534
  try {
513
- out = new Runtime("AAPL", { broker }).run(source, syntheticBars(bars));
535
+ out = new Runtime("AAPL", { broker, mode }).run(source, syntheticBars(bars));
514
536
  } catch (err) {
515
537
  fail(ui, err instanceof Error ? err.message : String(err), 1);
516
538
  }
@@ -538,7 +560,7 @@ function infoPayload(ui: Rich): {
538
560
  version: VERSION,
539
561
  runtime: "bun",
540
562
  bun: typeof Bun !== "undefined" ? Bun.version : null,
541
- mode: "interpret",
563
+ mode: "interpret+compile",
542
564
  rich: ui.enabled,
543
565
  docs: "https://hoox.sh/pyne",
544
566
  };
@@ -558,7 +580,7 @@ function info(ui: Rich, asJson: boolean): void {
558
580
  ["package", "@hoox-sh/pynets"],
559
581
  ["version", VERSION],
560
582
  ["runtime", `bun ${payload.bun ?? "?"}`],
561
- ["engine", "interpret"],
583
+ ["engine", "interpret + compile (JS emit)"],
562
584
  ["rich", "yes (TTY)"],
563
585
  ["docs", payload.docs],
564
586
  ],
@@ -577,7 +599,7 @@ function main(): void {
577
599
  failUsage(ui, msg);
578
600
  }
579
601
 
580
- const { cmd, file, bars, json, indent, full } = parsed;
602
+ const { cmd, file, bars, json, indent, full, mode } = parsed;
581
603
  const broker = brokerFromFlags(parsed);
582
604
  if (cmd === "help" || cmd === "-h" || cmd === "--help") {
583
605
  ui.help();
@@ -610,7 +632,7 @@ function main(): void {
610
632
  return;
611
633
  }
612
634
  if (cmd === "run") {
613
- run(ui, readSource(ui, file), file, bars, json, broker);
635
+ run(ui, readSource(ui, file), file, bars, json, broker, mode);
614
636
  return;
615
637
  }
616
638
  failUsage(ui, `error: unknown command: ${cmd}`);
package/src/index.ts CHANGED
@@ -20,6 +20,27 @@ export type {
20
20
  StrategyEvent,
21
21
  StreamEvent,
22
22
  } from "./runtime/interpret.ts";
23
+ export {
24
+ compileScript,
25
+ transpile,
26
+ compileEligible,
27
+ clearCompileCache,
28
+ compileCacheStats,
29
+ runScript,
30
+ compileToResult,
31
+ CompileError,
32
+ CompileEmitError,
33
+ CompileIneligibleError,
34
+ CompileLoadError,
35
+ } from "./runtime/compile/index.ts";
36
+ export type {
37
+ CompileBackend,
38
+ CompileCacheStats,
39
+ CompileEligibility,
40
+ CompilePlotMeta,
41
+ CompiledScript,
42
+ RuntimeMode,
43
+ } from "./runtime/compile/index.ts";
23
44
  export { NA, PineSeries } from "./runtime/series.ts";
24
45
  export { TaEngine } from "./runtime/ta.ts";
25
46
  export type { BrokerSettings, StrategySummary } from "./runtime/strategy.ts";
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Copyright (C) 2024-2026 jango_blockchained
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ *
5
+ * Compile-path array / map / matrix facades. Wraps PineArray / PineMap / PineMatrix.
6
+ */
7
+ import { PineArray } from "../array.ts";
8
+ import { PineMap } from "../map.ts";
9
+ import { PineMatrix } from "../matrix.ts";
10
+ import { naNum } from "./helpers.ts";
11
+
12
+ function n(v: unknown, fallback = 0): number {
13
+ const x = naNum(v);
14
+ return x == null ? fallback : x;
15
+ }
16
+
17
+ function cell(v: unknown): number | null {
18
+ if (v != null && typeof v === "object") return null;
19
+ return naNum(v);
20
+ }
21
+
22
+ function asArray(id: unknown): PineArray | null {
23
+ return id instanceof PineArray ? id : null;
24
+ }
25
+
26
+ function asMap(id: unknown): PineMap | null {
27
+ return id instanceof PineMap ? id : null;
28
+ }
29
+
30
+ function asMatrix(id: unknown): PineMatrix | null {
31
+ return id instanceof PineMatrix ? id : null;
32
+ }
33
+
34
+ function sortOrder(order: unknown): "asc" | "desc" {
35
+ return String(order ?? "asc").toLowerCase().includes("desc") ? "desc" : "asc";
36
+ }
37
+
38
+ function wrapCells(cells: ReadonlyArray<number | null> | null | undefined): PineArray | null {
39
+ if (cells == null) return null;
40
+ const out = new PineArray();
41
+ for (const v of cells) out.push(v);
42
+ return out;
43
+ }
44
+
45
+ export const compileArray = {
46
+ new(size?: unknown, initial?: unknown): PineArray {
47
+ return new PineArray(n(size, 0), cell(initial));
48
+ },
49
+ get(id: unknown, index?: unknown): number | null {
50
+ return asArray(id)?.get(n(index, 0)) ?? null;
51
+ },
52
+ set(id: unknown, index?: unknown, value?: unknown): unknown {
53
+ asArray(id)?.set(n(index, 0), cell(value));
54
+ return id ?? null;
55
+ },
56
+ push(id: unknown, value?: unknown): unknown {
57
+ asArray(id)?.push(cell(value));
58
+ return id ?? null;
59
+ },
60
+ pop(id: unknown): number | null {
61
+ return asArray(id)?.pop() ?? null;
62
+ },
63
+ unshift(id: unknown, value?: unknown): unknown {
64
+ asArray(id)?.unshift(cell(value));
65
+ return id ?? null;
66
+ },
67
+ shift(id: unknown): number | null {
68
+ return asArray(id)?.shift() ?? null;
69
+ },
70
+ size(id: unknown): number {
71
+ return asArray(id)?.size() ?? 0;
72
+ },
73
+ includes(id: unknown, value?: unknown): boolean {
74
+ return asArray(id)?.includes(cell(value)) ?? false;
75
+ },
76
+ first(id: unknown): number | null {
77
+ return asArray(id)?.first() ?? null;
78
+ },
79
+ last(id: unknown): number | null {
80
+ return asArray(id)?.last() ?? null;
81
+ },
82
+ insert(id: unknown, index?: unknown, value?: unknown): unknown {
83
+ asArray(id)?.insert(n(index, 0), cell(value));
84
+ return id ?? null;
85
+ },
86
+ remove(id: unknown, index?: unknown): number | null {
87
+ return asArray(id)?.remove(n(index, 0)) ?? null;
88
+ },
89
+ clear(id: unknown): unknown {
90
+ asArray(id)?.clear();
91
+ return id ?? null;
92
+ },
93
+ fill(id: unknown, value?: unknown): unknown {
94
+ asArray(id)?.fill(cell(value));
95
+ return id ?? null;
96
+ },
97
+ copy(id: unknown): PineArray | null {
98
+ return asArray(id)?.copy() ?? null;
99
+ },
100
+ reverse(id: unknown): unknown {
101
+ asArray(id)?.reverse();
102
+ return id ?? null;
103
+ },
104
+ sort(id: unknown, order?: unknown): unknown {
105
+ const o = String(order ?? "asc").toLowerCase();
106
+ asArray(id)?.sort(o.startsWith("desc") ? "desc" : "asc");
107
+ return id ?? null;
108
+ },
109
+ concat(id: unknown, other?: unknown): PineArray | null {
110
+ const a = asArray(id);
111
+ const b = asArray(other);
112
+ if (a == null) return b?.copy() ?? null;
113
+ if (b == null) return a;
114
+ return a.concat(b);
115
+ },
116
+ slice(id: unknown, from?: unknown, to?: unknown): PineArray | null {
117
+ return asArray(id)?.slice(n(from, 0), to == null ? undefined : n(to, 0)) ?? null;
118
+ },
119
+ indexof(id: unknown, value?: unknown): number | null {
120
+ return asArray(id)?.indexof(cell(value)) ?? null;
121
+ },
122
+ lastindexof(id: unknown, value?: unknown): number | null {
123
+ return asArray(id)?.lastIndexOf(cell(value)) ?? null;
124
+ },
125
+ avg(id: unknown): number | null {
126
+ return asArray(id)?.avg() ?? null;
127
+ },
128
+ min(id: unknown): number | null {
129
+ return asArray(id)?.min() ?? null;
130
+ },
131
+ max(id: unknown): number | null {
132
+ return asArray(id)?.max() ?? null;
133
+ },
134
+ sum(id: unknown): number | null {
135
+ return asArray(id)?.sum() ?? null;
136
+ },
137
+ stdev(id: unknown, biased?: unknown): number | null {
138
+ return asArray(id)?.stdev(biased !== false && biased !== 0) ?? null;
139
+ },
140
+ variance(id: unknown, biased?: unknown): number | null {
141
+ return asArray(id)?.variance(biased !== false && biased !== 0) ?? null;
142
+ },
143
+ join(id: unknown, sep?: unknown): string {
144
+ return asArray(id)?.join(sep == null ? "," : String(sep)) ?? "";
145
+ },
146
+ every(id: unknown): boolean {
147
+ return asArray(id)?.every() ?? false;
148
+ },
149
+ some(id: unknown): boolean {
150
+ return asArray(id)?.some() ?? false;
151
+ },
152
+ abs(id: unknown): PineArray | null {
153
+ return asArray(id)?.abs() ?? null;
154
+ },
155
+ range(id: unknown): number | null {
156
+ return asArray(id)?.range() ?? null;
157
+ },
158
+ percentile_nearest(id: unknown, percentage?: unknown): number | null {
159
+ const a = asArray(id);
160
+ const p = naNum(percentage);
161
+ return a != null && p != null ? a.percentileNearestRank(p) : null;
162
+ },
163
+ percentileNearestRank(id: unknown, percentage?: unknown): number | null {
164
+ return compileArray.percentile_nearest(id, percentage);
165
+ },
166
+ percentile_linear(id: unknown, percentage?: unknown): number | null {
167
+ const a = asArray(id);
168
+ const p = naNum(percentage);
169
+ return a != null && p != null ? a.percentileLinearInterpolation(p) : null;
170
+ },
171
+ percentileLinearInterpolation(id: unknown, percentage?: unknown): number | null {
172
+ return compileArray.percentile_linear(id, percentage);
173
+ },
174
+ percentrank(id: unknown, value?: unknown): number | null {
175
+ return asArray(id)?.percentrank(cell(value)) ?? null;
176
+ },
177
+ binary_search(id: unknown, value?: unknown): number | null {
178
+ return asArray(id)?.binarySearch(cell(value)) ?? null;
179
+ },
180
+ binary_search_leftmost(id: unknown, value?: unknown): number | null {
181
+ return asArray(id)?.binarySearchLeftmost(cell(value)) ?? null;
182
+ },
183
+ binary_search_rightmost(id: unknown, value?: unknown): number | null {
184
+ return asArray(id)?.binarySearchRightmost(cell(value)) ?? null;
185
+ },
186
+ sort_indices(id: unknown, order?: unknown): PineArray | null {
187
+ const idx = asArray(id)?.sortIndices(sortOrder(order));
188
+ return wrapCells(idx);
189
+ },
190
+ };
191
+
192
+ export const compileMap = {
193
+ new(): PineMap {
194
+ return new PineMap();
195
+ },
196
+ put(id: unknown, key?: unknown, value?: unknown): unknown {
197
+ asMap(id)?.put(key, cell(value));
198
+ return id ?? null;
199
+ },
200
+ get(id: unknown, key?: unknown): number | null {
201
+ return asMap(id)?.get(key) ?? null;
202
+ },
203
+ contains(id: unknown, key?: unknown): boolean {
204
+ return asMap(id)?.contains(key) ?? false;
205
+ },
206
+ remove(id: unknown, key?: unknown): number | null {
207
+ return asMap(id)?.remove(key) ?? null;
208
+ },
209
+ clear(id: unknown): unknown {
210
+ asMap(id)?.clear();
211
+ return id ?? null;
212
+ },
213
+ size(id: unknown): number {
214
+ return asMap(id)?.size() ?? 0;
215
+ },
216
+ keys(id: unknown): unknown[] {
217
+ return asMap(id)?.keys() ?? [];
218
+ },
219
+ values(id: unknown): Array<number | null> {
220
+ return asMap(id)?.values() ?? [];
221
+ },
222
+ copy(id: unknown): PineMap | null {
223
+ return asMap(id)?.copy() ?? null;
224
+ },
225
+ put_all(id: unknown, other?: unknown): unknown {
226
+ const m = asMap(id);
227
+ const o = asMap(other);
228
+ if (m != null && o != null) m.putAll(o);
229
+ return id ?? null;
230
+ },
231
+ };
232
+
233
+ export const compileMatrix = {
234
+ new(rows?: unknown, cols?: unknown, initial?: unknown): PineMatrix {
235
+ return new PineMatrix(n(rows, 0), n(cols, 0), cell(initial));
236
+ },
237
+ get(id: unknown, row?: unknown, col?: unknown): number | null {
238
+ return asMatrix(id)?.get(n(row, 0), n(col, 0)) ?? null;
239
+ },
240
+ set(id: unknown, row?: unknown, col?: unknown, value?: unknown): unknown {
241
+ asMatrix(id)?.set(n(row, 0), n(col, 0), cell(value));
242
+ return id ?? null;
243
+ },
244
+ rows(id: unknown): number {
245
+ return asMatrix(id)?.rows() ?? 0;
246
+ },
247
+ columns(id: unknown): number {
248
+ return asMatrix(id)?.columns() ?? 0;
249
+ },
250
+ fill(id: unknown, value?: unknown): unknown {
251
+ asMatrix(id)?.fill(cell(value));
252
+ return id ?? null;
253
+ },
254
+ transpose(id: unknown): PineMatrix | null {
255
+ return asMatrix(id)?.transpose() ?? null;
256
+ },
257
+ copy(id: unknown): PineMatrix | null {
258
+ return asMatrix(id)?.copy() ?? null;
259
+ },
260
+ row(id: unknown, index?: unknown): PineArray | null {
261
+ const m = asMatrix(id);
262
+ return m == null ? null : wrapCells(m.row(n(index, 0)));
263
+ },
264
+ col(id: unknown, index?: unknown): PineArray | null {
265
+ const m = asMatrix(id);
266
+ return m == null ? null : wrapCells(m.col(n(index, 0)));
267
+ },
268
+ sum(id: unknown): number | null {
269
+ return asMatrix(id)?.sum() ?? null;
270
+ },
271
+ avg(id: unknown): number | null {
272
+ return asMatrix(id)?.avg() ?? null;
273
+ },
274
+ min(id: unknown): number | null {
275
+ return asMatrix(id)?.min() ?? null;
276
+ },
277
+ max(id: unknown): number | null {
278
+ return asMatrix(id)?.max() ?? null;
279
+ },
280
+ det(id: unknown): number | null {
281
+ return asMatrix(id)?.det() ?? null;
282
+ },
283
+ mult(id: unknown, other?: unknown): PineMatrix | null {
284
+ const m = asMatrix(id);
285
+ if (m == null) return null;
286
+ const o = asMatrix(other);
287
+ if (o != null) return m.mult(o);
288
+ const s = naNum(other);
289
+ return s == null ? null : m.mult(s);
290
+ },
291
+ inv(id: unknown): PineMatrix | null {
292
+ return asMatrix(id)?.inv() ?? null;
293
+ },
294
+ pinv(id: unknown): PineMatrix | null {
295
+ return asMatrix(id)?.pinv() ?? null;
296
+ },
297
+ kron(id: unknown, other?: unknown): PineMatrix | null {
298
+ const m = asMatrix(id);
299
+ const o = asMatrix(other);
300
+ return m != null && o != null ? m.kron(o) : null;
301
+ },
302
+ rank(id: unknown): number | null {
303
+ return asMatrix(id)?.rank() ?? null;
304
+ },
305
+ add(id: unknown, other?: unknown): PineMatrix | null {
306
+ const m = asMatrix(id);
307
+ if (m == null) return null;
308
+ const o = asMatrix(other);
309
+ if (o != null) {
310
+ const neg = o.mult(-1);
311
+ return neg == null ? null : m.diff(neg);
312
+ }
313
+ const s = naNum(other);
314
+ if (s == null) return null;
315
+ return m.diff(new PineMatrix(m.rows(), m.columns(), -s));
316
+ },
317
+ diff(id: unknown, other?: unknown): PineMatrix | null {
318
+ const m = asMatrix(id);
319
+ const o = asMatrix(other);
320
+ return m != null && o != null ? m.diff(o) : null;
321
+ },
322
+ reshape(id: unknown, rows?: unknown, cols?: unknown): PineMatrix | null {
323
+ return asMatrix(id)?.reshape(n(rows, 0), n(cols, 0)) ?? null;
324
+ },
325
+ reverse(id: unknown): unknown {
326
+ asMatrix(id)?.reverse();
327
+ return id ?? null;
328
+ },
329
+ sort(id: unknown, column?: unknown, order?: unknown): unknown {
330
+ asMatrix(id)?.sort(n(column, 0), sortOrder(order));
331
+ return id ?? null;
332
+ },
333
+ eigenvalues(id: unknown): PineArray | null {
334
+ return wrapCells(asMatrix(id)?.eigenvalues());
335
+ },
336
+ eigenvectors(id: unknown): PineMatrix | null {
337
+ return asMatrix(id)?.eigenvectors() ?? null;
338
+ },
339
+ };