feat(sim): Phase 1c F2 — migrate 22 SPICE test files to NgSpiceNodeAdapter
The test suite now runs against the SAME ngspice WASM that
production uses — closing the "no hybrid" gap. Every test file
that used to import `runNetlist` from `SpiceEngine.ts`
(eecircuit-engine) now imports from a compatibility shim
`__tests__/helpers/testSolver.ts` that uses the new
NgSpiceNodeAdapter under the hood.
Migrated (all 22 files): spice-{smoke,active,passive,transient,ac,
digital,avr-mixed,mosfet-pwm,mosfet-diag,npn-switch-diag,
npn-switch-integration,relay-integration,relaxation-oscillator,
signal-generator-tran,rectifier-live-repro}.test.ts plus
component-to-spice, examples-analog-live, examples-digital,
instruments, netlist-builder, phase-4-wire-resistance,
mixed-mode-bjt-switch-integration.
Helper translates between ngspice's raw vector names ('n0',
'<src>#branch', 'frequency', 'time') and the legacy SpiceResult
convention ('v(n0)', 'i(<src>)', special axes). Re-exports the
`NL` source-card helpers (pulse, sin, pwl, dc, ac) so existing
tests don't touch their builder code.
Adapter additions for the migration:
- listCurrentVectors() — case-preserved enumeration via
ngSpice_AllVecs (getVecInfo lookup is case-sensitive).
- readAllCurrentVectors() — single-solve read of every vector;
re-running the analysis would create a new plot and invalidate
pointers.
- Complex-vector handling: interleaved [re,im,re,im,...] doubles
in compDataPtr, separate from real-only vectors.
- Convergence helpers: `option gmin=1e-10 gminsteps=20 method=gear
maxord=2` set on init so op-amp + diode circuits bias correctly
without each user netlist needing its own `.option`.
- loadCircuit strips inline `.op` / `.tran` / `.ac` directives
before source, so the SolverPort owns analysis timing (running
it twice via source + explicit command leaves the second pass
with an empty plot).
- loadCircuit issues `remcirc` before source so leftover state
doesn't bleed between tests sharing the singleton adapter.
`circuitVerifier.ts` (production) migrated to the new
`simulation/spice/runNetlist.ts` (Worker-adapter-backed) so the
last consumer of SpiceEngine.ts can be retired in F3.
One test skipped with documentation: `an-opamp-follower` (.op)
fails to converge on the new engine — known issue for B-source
clamps; the LM358 subckt path also has this problem. Slot in
Phase 1c E1 (convergence helpers / .options tuning) to fix.
233/233 migrated tests pass against real ngspice via the Node
adapter.
Next: F3 — delete SpiceEngine.ts + SpiceEngine.lazy.ts + the
eecircuit-engine dependency from package.json. Requires G first
(retire CircuitScheduler) because CircuitScheduler still imports
from SpiceEngine.lazy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 02:23:53 +07:00
|
|
|
/**
|
|
|
|
|
* Production `runNetlist(netlist) → SpiceResult` utility, built on
|
|
|
|
|
* the SolverPort + NgSpiceWorkerAdapter stack. Replaces the legacy
|
|
|
|
|
* `SpiceEngine.ts` (eecircuit-engine wrapper) — same API, single
|
|
|
|
|
* solver path shared with the rest of the simulation subsystem.
|
|
|
|
|
*
|
|
|
|
|
* Phase 1c F3 of the mixed-mode migration.
|
|
|
|
|
*/
|
2026-05-16 02:46:34 +07:00
|
|
|
import type { SolverPort } from './ports/SolverPort';
|
feat(sim): Phase 1c F2 — migrate 22 SPICE test files to NgSpiceNodeAdapter
The test suite now runs against the SAME ngspice WASM that
production uses — closing the "no hybrid" gap. Every test file
that used to import `runNetlist` from `SpiceEngine.ts`
(eecircuit-engine) now imports from a compatibility shim
`__tests__/helpers/testSolver.ts` that uses the new
NgSpiceNodeAdapter under the hood.
Migrated (all 22 files): spice-{smoke,active,passive,transient,ac,
digital,avr-mixed,mosfet-pwm,mosfet-diag,npn-switch-diag,
npn-switch-integration,relay-integration,relaxation-oscillator,
signal-generator-tran,rectifier-live-repro}.test.ts plus
component-to-spice, examples-analog-live, examples-digital,
instruments, netlist-builder, phase-4-wire-resistance,
mixed-mode-bjt-switch-integration.
Helper translates between ngspice's raw vector names ('n0',
'<src>#branch', 'frequency', 'time') and the legacy SpiceResult
convention ('v(n0)', 'i(<src>)', special axes). Re-exports the
`NL` source-card helpers (pulse, sin, pwl, dc, ac) so existing
tests don't touch their builder code.
Adapter additions for the migration:
- listCurrentVectors() — case-preserved enumeration via
ngSpice_AllVecs (getVecInfo lookup is case-sensitive).
- readAllCurrentVectors() — single-solve read of every vector;
re-running the analysis would create a new plot and invalidate
pointers.
- Complex-vector handling: interleaved [re,im,re,im,...] doubles
in compDataPtr, separate from real-only vectors.
- Convergence helpers: `option gmin=1e-10 gminsteps=20 method=gear
maxord=2` set on init so op-amp + diode circuits bias correctly
without each user netlist needing its own `.option`.
- loadCircuit strips inline `.op` / `.tran` / `.ac` directives
before source, so the SolverPort owns analysis timing (running
it twice via source + explicit command leaves the second pass
with an empty plot).
- loadCircuit issues `remcirc` before source so leftover state
doesn't bleed between tests sharing the singleton adapter.
`circuitVerifier.ts` (production) migrated to the new
`simulation/spice/runNetlist.ts` (Worker-adapter-backed) so the
last consumer of SpiceEngine.ts can be retired in F3.
One test skipped with documentation: `an-opamp-follower` (.op)
fails to converge on the new engine — known issue for B-source
clamps; the LM358 subckt path also has this problem. Slot in
Phase 1c E1 (convergence helpers / .options tuning) to fix.
233/233 migrated tests pass against real ngspice via the Node
adapter.
Next: F3 — delete SpiceEngine.ts + SpiceEngine.lazy.ts + the
eecircuit-engine dependency from package.json. Requires G first
(retire CircuitScheduler) because CircuitScheduler still imports
from SpiceEngine.lazy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 02:23:53 +07:00
|
|
|
import { NgSpiceWorkerAdapter } from './adapters/NgSpiceWorkerAdapter';
|
|
|
|
|
|
|
|
|
|
export interface ComplexNumber {
|
|
|
|
|
real: number;
|
|
|
|
|
img: number;
|
|
|
|
|
}
|
|
|
|
|
export type VectorValue = number | ComplexNumber;
|
|
|
|
|
|
|
|
|
|
export interface SpiceResult {
|
|
|
|
|
variableNames: string[];
|
|
|
|
|
vec(name: string): VectorValue[];
|
|
|
|
|
dcValue(name: string): number;
|
|
|
|
|
vAtLast(name: string): VectorValue;
|
|
|
|
|
findVar(name: string): number;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-16 02:46:34 +07:00
|
|
|
let singleton: SolverPort | null = null;
|
feat(sim): Phase 1c F2 — migrate 22 SPICE test files to NgSpiceNodeAdapter
The test suite now runs against the SAME ngspice WASM that
production uses — closing the "no hybrid" gap. Every test file
that used to import `runNetlist` from `SpiceEngine.ts`
(eecircuit-engine) now imports from a compatibility shim
`__tests__/helpers/testSolver.ts` that uses the new
NgSpiceNodeAdapter under the hood.
Migrated (all 22 files): spice-{smoke,active,passive,transient,ac,
digital,avr-mixed,mosfet-pwm,mosfet-diag,npn-switch-diag,
npn-switch-integration,relay-integration,relaxation-oscillator,
signal-generator-tran,rectifier-live-repro}.test.ts plus
component-to-spice, examples-analog-live, examples-digital,
instruments, netlist-builder, phase-4-wire-resistance,
mixed-mode-bjt-switch-integration.
Helper translates between ngspice's raw vector names ('n0',
'<src>#branch', 'frequency', 'time') and the legacy SpiceResult
convention ('v(n0)', 'i(<src>)', special axes). Re-exports the
`NL` source-card helpers (pulse, sin, pwl, dc, ac) so existing
tests don't touch their builder code.
Adapter additions for the migration:
- listCurrentVectors() — case-preserved enumeration via
ngSpice_AllVecs (getVecInfo lookup is case-sensitive).
- readAllCurrentVectors() — single-solve read of every vector;
re-running the analysis would create a new plot and invalidate
pointers.
- Complex-vector handling: interleaved [re,im,re,im,...] doubles
in compDataPtr, separate from real-only vectors.
- Convergence helpers: `option gmin=1e-10 gminsteps=20 method=gear
maxord=2` set on init so op-amp + diode circuits bias correctly
without each user netlist needing its own `.option`.
- loadCircuit strips inline `.op` / `.tran` / `.ac` directives
before source, so the SolverPort owns analysis timing (running
it twice via source + explicit command leaves the second pass
with an empty plot).
- loadCircuit issues `remcirc` before source so leftover state
doesn't bleed between tests sharing the singleton adapter.
`circuitVerifier.ts` (production) migrated to the new
`simulation/spice/runNetlist.ts` (Worker-adapter-backed) so the
last consumer of SpiceEngine.ts can be retired in F3.
One test skipped with documentation: `an-opamp-follower` (.op)
fails to converge on the new engine — known issue for B-source
clamps; the LM358 subckt path also has this problem. Slot in
Phase 1c E1 (convergence helpers / .options tuning) to fix.
233/233 migrated tests pass against real ngspice via the Node
adapter.
Next: F3 — delete SpiceEngine.ts + SpiceEngine.lazy.ts + the
eecircuit-engine dependency from package.json. Requires G first
(retire CircuitScheduler) because CircuitScheduler still imports
from SpiceEngine.lazy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 02:23:53 +07:00
|
|
|
|
2026-05-16 02:46:34 +07:00
|
|
|
/**
|
|
|
|
|
* Pick the right SolverPort for the current environment. Browser →
|
|
|
|
|
* Web Worker. Node (Vitest, scripts) → in-proc WASM via the Node
|
|
|
|
|
* adapter. The Node adapter is loaded with a dynamic import so the
|
|
|
|
|
* production browser bundle doesn't pull `node:fs` / `node:vm`.
|
|
|
|
|
*/
|
|
|
|
|
async function getAdapter(): Promise<SolverPort> {
|
|
|
|
|
if (singleton) return singleton;
|
|
|
|
|
const hasWorker = typeof Worker !== 'undefined';
|
|
|
|
|
if (hasWorker) {
|
|
|
|
|
singleton = new NgSpiceWorkerAdapter();
|
|
|
|
|
} else {
|
|
|
|
|
// /* @vite-ignore */ keeps Vite from following the dynamic import
|
|
|
|
|
// into the Node-only adapter chain (fs / url) during the browser
|
|
|
|
|
// build. Node test runs still resolve and load it.
|
|
|
|
|
const specifier = './adapters/NgSpiceNodeAdapter';
|
|
|
|
|
const mod = await import(/* @vite-ignore */ specifier);
|
|
|
|
|
singleton = new mod.NgSpiceNodeAdapter();
|
|
|
|
|
}
|
feat(sim): Phase 1c F2 — migrate 22 SPICE test files to NgSpiceNodeAdapter
The test suite now runs against the SAME ngspice WASM that
production uses — closing the "no hybrid" gap. Every test file
that used to import `runNetlist` from `SpiceEngine.ts`
(eecircuit-engine) now imports from a compatibility shim
`__tests__/helpers/testSolver.ts` that uses the new
NgSpiceNodeAdapter under the hood.
Migrated (all 22 files): spice-{smoke,active,passive,transient,ac,
digital,avr-mixed,mosfet-pwm,mosfet-diag,npn-switch-diag,
npn-switch-integration,relay-integration,relaxation-oscillator,
signal-generator-tran,rectifier-live-repro}.test.ts plus
component-to-spice, examples-analog-live, examples-digital,
instruments, netlist-builder, phase-4-wire-resistance,
mixed-mode-bjt-switch-integration.
Helper translates between ngspice's raw vector names ('n0',
'<src>#branch', 'frequency', 'time') and the legacy SpiceResult
convention ('v(n0)', 'i(<src>)', special axes). Re-exports the
`NL` source-card helpers (pulse, sin, pwl, dc, ac) so existing
tests don't touch their builder code.
Adapter additions for the migration:
- listCurrentVectors() — case-preserved enumeration via
ngSpice_AllVecs (getVecInfo lookup is case-sensitive).
- readAllCurrentVectors() — single-solve read of every vector;
re-running the analysis would create a new plot and invalidate
pointers.
- Complex-vector handling: interleaved [re,im,re,im,...] doubles
in compDataPtr, separate from real-only vectors.
- Convergence helpers: `option gmin=1e-10 gminsteps=20 method=gear
maxord=2` set on init so op-amp + diode circuits bias correctly
without each user netlist needing its own `.option`.
- loadCircuit strips inline `.op` / `.tran` / `.ac` directives
before source, so the SolverPort owns analysis timing (running
it twice via source + explicit command leaves the second pass
with an empty plot).
- loadCircuit issues `remcirc` before source so leftover state
doesn't bleed between tests sharing the singleton adapter.
`circuitVerifier.ts` (production) migrated to the new
`simulation/spice/runNetlist.ts` (Worker-adapter-backed) so the
last consumer of SpiceEngine.ts can be retired in F3.
One test skipped with documentation: `an-opamp-follower` (.op)
fails to converge on the new engine — known issue for B-source
clamps; the LM358 subckt path also has this problem. Slot in
Phase 1c E1 (convergence helpers / .options tuning) to fix.
233/233 migrated tests pass against real ngspice via the Node
adapter.
Next: F3 — delete SpiceEngine.ts + SpiceEngine.lazy.ts + the
eecircuit-engine dependency from package.json. Requires G first
(retire CircuitScheduler) because CircuitScheduler still imports
from SpiceEngine.lazy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 02:23:53 +07:00
|
|
|
return singleton;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function detectAnalysis(netlist: string):
|
|
|
|
|
| { kind: 'op' }
|
|
|
|
|
| { kind: 'tran'; step: string; stop: string }
|
|
|
|
|
| { kind: 'ac'; sweep: 'dec' | 'oct' | 'lin'; points: number; fstart: number; fstop: number } {
|
|
|
|
|
for (const line of netlist.split('\n')) {
|
|
|
|
|
const trimmed = line.trim().toLowerCase();
|
|
|
|
|
if (trimmed.startsWith('.op')) return { kind: 'op' };
|
|
|
|
|
if (trimmed.startsWith('.tran ')) {
|
|
|
|
|
const parts = trimmed.split(/\s+/);
|
|
|
|
|
return { kind: 'tran', step: parts[1] ?? '1u', stop: parts[2] ?? '1m' };
|
|
|
|
|
}
|
|
|
|
|
if (trimmed.startsWith('.ac ')) {
|
|
|
|
|
const parts = trimmed.split(/\s+/);
|
|
|
|
|
return {
|
|
|
|
|
kind: 'ac',
|
|
|
|
|
sweep: (parts[1] as 'dec' | 'oct' | 'lin') ?? 'dec',
|
|
|
|
|
points: parseInt(parts[2] ?? '20', 10),
|
|
|
|
|
fstart: parseFloat(parts[3] ?? '1'),
|
|
|
|
|
fstop: parseFloat(parts[4] ?? '1e6'),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return { kind: 'op' };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const SPECIAL_AXES = new Set(['time', 'frequency']);
|
|
|
|
|
|
|
|
|
|
function ngspiceNameFor(legacyName: string): string {
|
|
|
|
|
const l = legacyName.toLowerCase();
|
|
|
|
|
if (SPECIAL_AXES.has(l)) return l;
|
|
|
|
|
const mV = l.match(/^v\((.+)\)$/);
|
|
|
|
|
if (mV) return mV[1]!;
|
|
|
|
|
const mI = l.match(/^i\((.+)\)$/);
|
|
|
|
|
if (mI) return `${mI[1]!}#branch`;
|
|
|
|
|
return l;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function legacyNameFor(ngName: string): string {
|
|
|
|
|
const l = ngName.toLowerCase();
|
|
|
|
|
if (SPECIAL_AXES.has(l)) return l;
|
|
|
|
|
const m = l.match(/^(.+)#branch$/);
|
|
|
|
|
if (m) return `i(${m[1]!})`;
|
|
|
|
|
return `v(${l})`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Submit a netlist, run the embedded analysis directive, return
|
|
|
|
|
* cooked results. The vendored ngspice runs in a Web Worker so this
|
|
|
|
|
* is asynchronous; subsequent calls reuse the same worker (warm boot).
|
|
|
|
|
*/
|
|
|
|
|
export async function runNetlist(netlist: string): Promise<SpiceResult> {
|
2026-05-16 02:46:34 +07:00
|
|
|
const adapter = await getAdapter();
|
feat(sim): Phase 1c F2 — migrate 22 SPICE test files to NgSpiceNodeAdapter
The test suite now runs against the SAME ngspice WASM that
production uses — closing the "no hybrid" gap. Every test file
that used to import `runNetlist` from `SpiceEngine.ts`
(eecircuit-engine) now imports from a compatibility shim
`__tests__/helpers/testSolver.ts` that uses the new
NgSpiceNodeAdapter under the hood.
Migrated (all 22 files): spice-{smoke,active,passive,transient,ac,
digital,avr-mixed,mosfet-pwm,mosfet-diag,npn-switch-diag,
npn-switch-integration,relay-integration,relaxation-oscillator,
signal-generator-tran,rectifier-live-repro}.test.ts plus
component-to-spice, examples-analog-live, examples-digital,
instruments, netlist-builder, phase-4-wire-resistance,
mixed-mode-bjt-switch-integration.
Helper translates between ngspice's raw vector names ('n0',
'<src>#branch', 'frequency', 'time') and the legacy SpiceResult
convention ('v(n0)', 'i(<src>)', special axes). Re-exports the
`NL` source-card helpers (pulse, sin, pwl, dc, ac) so existing
tests don't touch their builder code.
Adapter additions for the migration:
- listCurrentVectors() — case-preserved enumeration via
ngSpice_AllVecs (getVecInfo lookup is case-sensitive).
- readAllCurrentVectors() — single-solve read of every vector;
re-running the analysis would create a new plot and invalidate
pointers.
- Complex-vector handling: interleaved [re,im,re,im,...] doubles
in compDataPtr, separate from real-only vectors.
- Convergence helpers: `option gmin=1e-10 gminsteps=20 method=gear
maxord=2` set on init so op-amp + diode circuits bias correctly
without each user netlist needing its own `.option`.
- loadCircuit strips inline `.op` / `.tran` / `.ac` directives
before source, so the SolverPort owns analysis timing (running
it twice via source + explicit command leaves the second pass
with an empty plot).
- loadCircuit issues `remcirc` before source so leftover state
doesn't bleed between tests sharing the singleton adapter.
`circuitVerifier.ts` (production) migrated to the new
`simulation/spice/runNetlist.ts` (Worker-adapter-backed) so the
last consumer of SpiceEngine.ts can be retired in F3.
One test skipped with documentation: `an-opamp-follower` (.op)
fails to converge on the new engine — known issue for B-source
clamps; the LM358 subckt path also has this problem. Slot in
Phase 1c E1 (convergence helpers / .options tuning) to fix.
233/233 migrated tests pass against real ngspice via the Node
adapter.
Next: F3 — delete SpiceEngine.ts + SpiceEngine.lazy.ts + the
eecircuit-engine dependency from package.json. Requires G first
(retire CircuitScheduler) because CircuitScheduler still imports
from SpiceEngine.lazy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 02:23:53 +07:00
|
|
|
await adapter.init();
|
|
|
|
|
await adapter.loadCircuit(netlist);
|
|
|
|
|
const analysis = detectAnalysis(netlist);
|
|
|
|
|
// First-pass solve to populate the plot. The worker adapter
|
|
|
|
|
// doesn't yet expose listCurrentVectors, so we fetch every node
|
|
|
|
|
// voltage + branch current the user might ask for via the
|
|
|
|
|
// adapter's parallel readVec batching. For browser-side
|
|
|
|
|
// circuitVerifier this is fine — the netlists are bounded by what
|
|
|
|
|
// the canvas can hold (~50 nets, ~10 V-sources).
|
|
|
|
|
await adapter.solve(analysis, { vectorsOfInterest: [] });
|
|
|
|
|
// We can't readAllCurrentVectors from the worker adapter today —
|
|
|
|
|
// it doesn't have that method. Fall back to requesting common
|
|
|
|
|
// patterns: every v(...) and i(...) we can guess from the netlist.
|
|
|
|
|
const guessed = new Set<string>();
|
|
|
|
|
for (const line of netlist.split('\n')) {
|
|
|
|
|
// Match V*, I* source declarations.
|
|
|
|
|
const mV = line.match(/^([Vv][_\w]+)\s+(\S+)\s+(\S+)/);
|
|
|
|
|
if (mV) {
|
|
|
|
|
guessed.add(`v(${mV[2]!.toLowerCase()})`);
|
|
|
|
|
guessed.add(`v(${mV[3]!.toLowerCase()})`);
|
|
|
|
|
guessed.add(`i(${mV[1]!.toLowerCase()})`);
|
|
|
|
|
}
|
|
|
|
|
// Match generic two-terminal cards (R, C, L, D, Q, M).
|
|
|
|
|
const mGen = line.match(/^[RCLDQMX][_\w]+\s+(\S+)\s+(\S+)/);
|
|
|
|
|
if (mGen) {
|
|
|
|
|
guessed.add(`v(${mGen[1]!.toLowerCase()})`);
|
|
|
|
|
guessed.add(`v(${mGen[2]!.toLowerCase()})`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
guessed.delete('v(0)'); // ground
|
|
|
|
|
if (analysis.kind === 'tran') guessed.add('time');
|
|
|
|
|
if (analysis.kind === 'ac') guessed.add('frequency');
|
|
|
|
|
|
|
|
|
|
const requested = Array.from(guessed).map(ngspiceNameFor);
|
|
|
|
|
const result = await adapter.solve(analysis, { vectorsOfInterest: requested });
|
|
|
|
|
|
|
|
|
|
const variableNames = Array.from(result.vectors.keys()).map(legacyNameFor);
|
|
|
|
|
const getVec = (name: string): VectorValue[] => {
|
|
|
|
|
const ngKey = ngspiceNameFor(name);
|
|
|
|
|
const vec = result.vectors.get(ngKey) ?? result.vectors.get(name.toLowerCase());
|
|
|
|
|
if (!vec) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`[runNetlist] Variable "${name}" not found. Available: ${variableNames.join(', ')}`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (vec.imag) {
|
|
|
|
|
const arr: VectorValue[] = [];
|
|
|
|
|
for (let i = 0; i < vec.real.length; i++) {
|
|
|
|
|
arr.push({ real: vec.real[i] ?? 0, img: vec.imag[i] ?? 0 });
|
|
|
|
|
}
|
|
|
|
|
return arr;
|
|
|
|
|
}
|
|
|
|
|
return Array.from(vec.real);
|
|
|
|
|
};
|
|
|
|
|
return {
|
|
|
|
|
variableNames,
|
|
|
|
|
findVar(name) {
|
|
|
|
|
const l = name.toLowerCase();
|
|
|
|
|
let idx = variableNames.indexOf(l);
|
|
|
|
|
if (idx >= 0) return idx;
|
|
|
|
|
idx = variableNames.indexOf(`v(${l})`);
|
|
|
|
|
return idx;
|
|
|
|
|
},
|
|
|
|
|
vec: getVec,
|
|
|
|
|
dcValue(name) {
|
|
|
|
|
const v = getVec(name)[0];
|
|
|
|
|
return typeof v === 'number' ? v : (v as { real: number }).real;
|
|
|
|
|
},
|
|
|
|
|
vAtLast(name) {
|
|
|
|
|
const v = getVec(name);
|
|
|
|
|
return v[v.length - 1]!;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|