@tscircuit/ngspice-spice-engine 0.0.15 → 0.0.17

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.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { SpiceEngine } from '@tscircuit/props';
2
2
  import { ResultType } from '@tscircuit/eecircuit-engine';
3
+ import { SimulationTransientVoltageGraph, SimulationTransientCurrentGraph } from 'circuit-json';
3
4
 
4
5
  interface TranParams {
5
6
  tstep?: number;
@@ -10,20 +11,43 @@ interface TranParams {
10
11
  }
11
12
  declare const parseTranParams: (spiceString: string) => TranParams | null;
12
13
 
13
- interface VoltageGraph {
14
- netName: string;
15
- time: number[];
16
- voltage: number[];
17
- probeMetadata?: ProbeMetadata;
18
- }
19
- interface ProbeMetadata {
14
+ declare const rewritePspiceCompatibilitySyntax: (spiceString: string) => string;
15
+
16
+ interface VoltageProbeMetadata {
20
17
  simulation_voltage_probe_id: string;
21
18
  name?: string;
22
19
  spice_vector: string;
23
20
  source_node_name: string;
24
21
  reference_node_name?: string;
25
22
  }
23
+ interface CurrentProbeMetadata {
24
+ simulation_current_probe_id: string;
25
+ name?: string;
26
+ spice_vector: string;
27
+ source_component_id?: string;
28
+ source_trace_id?: string;
29
+ }
30
+ interface VoltageGraph {
31
+ graphType: "voltage";
32
+ netName: string;
33
+ time: number[];
34
+ voltage: number[];
35
+ probeMetadata?: VoltageProbeMetadata;
36
+ }
37
+ interface CurrentGraph {
38
+ graphType: "current";
39
+ currentName: string;
40
+ time: number[];
41
+ current: number[];
42
+ probeMetadata?: CurrentProbeMetadata;
43
+ }
44
+ type SimulationGraph = VoltageGraph | CurrentGraph;
45
+
46
+ declare const eecircuitResultToSimulationGraphs: (result: ResultType, spiceString: string) => SimulationGraph[];
26
47
  declare const eecircuitResultToVGraphs: (result: ResultType, spiceString: string) => VoltageGraph[];
48
+
49
+ declare const simulationGraphsToCircuitJson: (graphs: SimulationGraph[], spiceString: string) => Array<SimulationTransientVoltageGraph | SimulationTransientCurrentGraph>;
50
+
27
51
  declare const createNgspiceSpiceEngine: () => Promise<SpiceEngine>;
28
52
 
29
- export { type TranParams, createNgspiceSpiceEngine, createNgspiceSpiceEngine as default, eecircuitResultToVGraphs, parseTranParams };
53
+ export { type TranParams, createNgspiceSpiceEngine, createNgspiceSpiceEngine as default, eecircuitResultToSimulationGraphs, eecircuitResultToVGraphs, parseTranParams, rewritePspiceCompatibilitySyntax, simulationGraphsToCircuitJson };
package/dist/index.js CHANGED
@@ -1,28 +1,3 @@
1
- // lib/linear-interpolate.ts
2
- var linearInterpolate = (targetX, xPoints, yPoints) => {
3
- if (xPoints.length === 0) {
4
- return 0;
5
- }
6
- if (targetX <= xPoints[0]) {
7
- return yPoints[0];
8
- }
9
- if (targetX >= xPoints[xPoints.length - 1]) {
10
- return yPoints[yPoints.length - 1];
11
- }
12
- let i = 1;
13
- while (i < xPoints.length && xPoints[i] < targetX) {
14
- i++;
15
- }
16
- const x1 = xPoints[i - 1];
17
- const y1 = yPoints[i - 1];
18
- const x2 = xPoints[i];
19
- const y2 = yPoints[i];
20
- if (x2 === x1) {
21
- return y1;
22
- }
23
- return y1 + (y2 - y1) * (targetX - x1) / (x2 - x1);
24
- };
25
-
26
1
  // lib/parse-tran-params.ts
27
2
  var SUFFIX_MULTIPLIERS = {
28
3
  t: 1e12,
@@ -108,43 +83,149 @@ var parseTranParams = (spiceString) => {
108
83
  return null;
109
84
  };
110
85
 
111
- // lib/index.ts
112
- var ensureSimulation = async () => {
113
- const { Simulation: SimulationCtor } = await import("@tscircuit/eecircuit-engine");
114
- const instance = new SimulationCtor({ ngBehavior: "psa" });
115
- await instance.start();
116
- return instance;
117
- };
118
- var simulationPromise = null;
119
- var getSimulation = async () => {
120
- if (!simulationPromise) {
121
- simulationPromise = ensureSimulation().catch((error) => {
122
- simulationPromise = null;
123
- throw error;
86
+ // lib/rewrite-pspice-compatibility-syntax.ts
87
+ var pspiceNumberToken = String.raw`([+-]?(?:(?:\d+(?:\.\d*)?)|(?:\.\d+))(?:[eE][+-]?\d+)?)`;
88
+ var rewritePspiceResistorTcPairs = (spiceString) => spiceString.split(/\r?\n/).map((line) => {
89
+ if (!/^\s*r/i.test(line)) return line;
90
+ return line.replace(
91
+ new RegExp(
92
+ String.raw`\bTC\s*=\s*${pspiceNumberToken}\s*,\s*${pspiceNumberToken}\b`,
93
+ "gi"
94
+ ),
95
+ "TC1=$1 TC2=$2"
96
+ );
97
+ }).join("\n");
98
+ var pspiceComparisonOperator = "(?:<=|>=|==|!=|(?<![!<>=])=(?!=)|<|>)";
99
+ var pspiceComparisonOperand = String.raw`(?:V\s*\([^)]*\)|\{[^}\r\n]+\}|${pspiceNumberToken}(?:[a-zA-Z]+)?|[A-Za-z_][\w.$]*)`;
100
+ var pspiceComparisonExpression = String.raw`${pspiceComparisonOperand}\s*${pspiceComparisonOperator}\s*${pspiceComparisonOperand}`;
101
+ var pspiceComparisonBeforeCaretPattern = new RegExp(
102
+ String.raw`${pspiceComparisonExpression}\s*$`,
103
+ "i"
104
+ );
105
+ var pspiceComparisonAfterCaretPattern = new RegExp(
106
+ String.raw`^\s*\+?\s*${pspiceComparisonExpression}`,
107
+ "i"
108
+ );
109
+ var isPspiceBooleanCaret = (block, caretOffset, operatorLength) => pspiceComparisonBeforeCaretPattern.test(block.slice(0, caretOffset)) && pspiceComparisonAfterCaretPattern.test(
110
+ block.slice(caretOffset + operatorLength)
111
+ );
112
+ var rewritePspiceValueBooleanCarets = (spiceString) => {
113
+ let result = "";
114
+ let cursor = 0;
115
+ const valueStartPattern = /\bVALUE\s*\{/gi;
116
+ for (; ; ) {
117
+ valueStartPattern.lastIndex = cursor;
118
+ const match = valueStartPattern.exec(spiceString);
119
+ if (!match) break;
120
+ const blockStart = match.index;
121
+ const firstBraceIndex = spiceString.indexOf("{", blockStart);
122
+ let depth = 0;
123
+ let blockEnd = -1;
124
+ for (let i = firstBraceIndex; i < spiceString.length; i++) {
125
+ const char = spiceString[i];
126
+ if (char === "{") {
127
+ depth += 1;
128
+ } else if (char === "}") {
129
+ depth -= 1;
130
+ if (depth === 0) {
131
+ blockEnd = i + 1;
132
+ break;
133
+ }
134
+ }
135
+ }
136
+ if (blockEnd === -1) break;
137
+ result += spiceString.slice(cursor, blockStart);
138
+ const block = spiceString.slice(blockStart, blockEnd);
139
+ result += block.replace(/\s+\^\s+/g, (operator, offset, fullBlock) => {
140
+ if (isPspiceBooleanCaret(fullBlock, offset, operator.length)) {
141
+ return operator.replace("^", "!=");
142
+ }
143
+ return operator;
124
144
  });
145
+ cursor = blockEnd;
125
146
  }
126
- return simulationPromise;
147
+ return result + spiceString.slice(cursor);
127
148
  };
128
- var extractRequestedPlots = (spiceString) => {
129
- const match = spiceString.match(/\.print\s+tran\s+(.*)/i);
130
- if (!match || !match[1]) {
131
- return null;
149
+ var rewritePspiceCompatibilitySyntax = (spiceString) => rewritePspiceValueBooleanCarets(rewritePspiceResistorTcPairs(spiceString));
150
+
151
+ // lib/create-graph-from-requested-plot.ts
152
+ var getNetName = (rawName) => {
153
+ const diffMatch = rawName.match(/^v\(([^,]+),\s*([^)]+)\)$/i);
154
+ if (diffMatch?.[1] && diffMatch?.[2]) {
155
+ return `${diffMatch[1].trim()}-${diffMatch[2].trim()}`;
132
156
  }
133
- const tokens = match[1].match(/[VI]\s*\([^)]+\)/gi);
134
- if (!tokens) {
135
- return null;
157
+ const match = rawName.match(/^v\((.*)\)$/i);
158
+ if (!match) {
159
+ return rawName;
136
160
  }
137
- const plotMap = /* @__PURE__ */ new Map();
138
- for (const token of tokens) {
139
- const lowerCaseToken = token.toLowerCase().replace(/\s/g, "");
140
- if (!plotMap.has(lowerCaseToken)) {
141
- plotMap.set(lowerCaseToken, token);
161
+ return match[1] ?? rawName;
162
+ };
163
+ var getCurrentName = (rawName) => {
164
+ const match = rawName.match(/^i\((.*)\)$/i);
165
+ if (!match) {
166
+ return rawName;
167
+ }
168
+ return match[1] ?? rawName;
169
+ };
170
+ var createVoltageGraphFromRequestedPlot = ({
171
+ lowerCaseToken,
172
+ originalToken,
173
+ timeValues,
174
+ voltageDataMap,
175
+ voltageProbeMetadata
176
+ }) => {
177
+ if (!lowerCaseToken.startsWith("v(")) return null;
178
+ const diffMatch = originalToken.match(/^v\(([^,]+),\s*([^)]+)\)$/i);
179
+ let voltage;
180
+ if (diffMatch?.[1] && diffMatch?.[2]) {
181
+ voltage = voltageDataMap.get(lowerCaseToken);
182
+ if (!voltage) {
183
+ const node1 = diffMatch[1].trim();
184
+ const node2 = diffMatch[2].trim();
185
+ const node1Data = voltageDataMap.get(`v(${node1.toLowerCase()})`);
186
+ const node2Data = voltageDataMap.get(`v(${node2.toLowerCase()})`);
187
+ if (node1Data && node2Data) {
188
+ voltage = node1Data.map((v, i) => v - (node2Data[i] ?? 0));
189
+ }
142
190
  }
191
+ } else {
192
+ voltage = voltageDataMap.get(lowerCaseToken);
143
193
  }
144
- return plotMap;
194
+ if (!voltage) return null;
195
+ const metadata = voltageProbeMetadata.get(lowerCaseToken);
196
+ const voltageGraph = {
197
+ graphType: "voltage",
198
+ netName: metadata?.name ?? getNetName(originalToken),
199
+ time: timeValues,
200
+ voltage,
201
+ probeMetadata: metadata
202
+ };
203
+ return voltageGraph;
145
204
  };
205
+ var createCurrentGraphFromRequestedPlot = ({
206
+ lowerCaseToken,
207
+ originalToken,
208
+ timeValues,
209
+ currentDataMap,
210
+ currentProbeMetadata
211
+ }) => {
212
+ if (!lowerCaseToken.startsWith("i(")) return null;
213
+ const current = currentDataMap.get(lowerCaseToken);
214
+ if (!current) return null;
215
+ const metadata = currentProbeMetadata.get(lowerCaseToken);
216
+ const currentGraph = {
217
+ graphType: "current",
218
+ currentName: metadata?.name ?? getCurrentName(originalToken),
219
+ time: timeValues,
220
+ current,
221
+ probeMetadata: metadata
222
+ };
223
+ return currentGraph;
224
+ };
225
+
226
+ // lib/extract-probe-metadata.ts
146
227
  var normalizeSpiceVector = (value) => value.toLowerCase().replace(/\s/g, "");
147
- var extractProbeMetadata = (spiceString) => {
228
+ var extractVoltageProbeMetadata = (spiceString) => {
148
229
  const metadata = /* @__PURE__ */ new Map();
149
230
  for (const line of spiceString.split(/\r?\n/)) {
150
231
  const match = line.match(/^\s*\*\s*tscircuit_probe\s+(.+)\s*$/);
@@ -154,32 +235,93 @@ var extractProbeMetadata = (spiceString) => {
154
235
  if (typeof parsed.simulation_voltage_probe_id !== "string" || typeof parsed.spice_vector !== "string" || typeof parsed.source_node_name !== "string") {
155
236
  continue;
156
237
  }
157
- metadata.set(normalizeSpiceVector(parsed.spice_vector), {
238
+ const normalizedSpiceVector = normalizeSpiceVector(parsed.spice_vector);
239
+ const voltageProbeMetadata = {
158
240
  simulation_voltage_probe_id: parsed.simulation_voltage_probe_id,
159
241
  name: typeof parsed.name === "string" ? parsed.name : void 0,
160
242
  spice_vector: parsed.spice_vector,
161
243
  source_node_name: parsed.source_node_name,
162
244
  reference_node_name: typeof parsed.reference_node_name === "string" ? parsed.reference_node_name : void 0
163
- });
245
+ };
246
+ metadata.set(normalizedSpiceVector, voltageProbeMetadata);
164
247
  } catch {
165
- continue;
166
248
  }
167
249
  }
168
250
  return metadata;
169
251
  };
170
- var getNetName = (rawName) => {
171
- const diffMatch = rawName.match(/^v\(([^,]+),\s*([^)]+)\)$/i);
172
- if (diffMatch?.[1] && diffMatch?.[2]) {
173
- return `${diffMatch[1].trim()}-${diffMatch[2].trim()}`;
252
+ var extractCurrentProbeMetadata = (spiceString) => {
253
+ const metadata = /* @__PURE__ */ new Map();
254
+ for (const line of spiceString.split(/\r?\n/)) {
255
+ const match = line.match(/^\s*\*\s*tscircuit_current_probe\s+(.+)\s*$/);
256
+ if (!match?.[1]) continue;
257
+ try {
258
+ const parsed = JSON.parse(match[1]);
259
+ if (typeof parsed.simulation_current_probe_id !== "string" || typeof parsed.spice_vector !== "string") {
260
+ continue;
261
+ }
262
+ const normalizedSpiceVector = normalizeSpiceVector(parsed.spice_vector);
263
+ const currentProbeMetadata = {
264
+ simulation_current_probe_id: parsed.simulation_current_probe_id,
265
+ name: typeof parsed.name === "string" ? parsed.name : void 0,
266
+ spice_vector: parsed.spice_vector,
267
+ source_component_id: typeof parsed.source_component_id === "string" ? parsed.source_component_id : void 0,
268
+ source_trace_id: typeof parsed.source_trace_id === "string" ? parsed.source_trace_id : void 0
269
+ };
270
+ metadata.set(normalizedSpiceVector, currentProbeMetadata);
271
+ } catch {
272
+ }
174
273
  }
175
- const match = rawName.match(/^v\((.*)\)$/i);
176
- if (!match) {
177
- return rawName;
274
+ return metadata;
275
+ };
276
+
277
+ // lib/extract-requested-plots.ts
278
+ var extractRequestedPlots = (spiceString) => {
279
+ const match = spiceString.match(/\.print\s+tran\s+(.*)/i);
280
+ if (!match?.[1]) {
281
+ return null;
178
282
  }
179
- return match[1] ?? rawName;
283
+ const tokens = match[1].match(/[VI]\s*\([^)]+\)/gi);
284
+ if (!tokens) {
285
+ return null;
286
+ }
287
+ const plotMap = /* @__PURE__ */ new Map();
288
+ for (const token of tokens) {
289
+ const lowerCaseToken = token.toLowerCase().replace(/\s/g, "");
290
+ if (!plotMap.has(lowerCaseToken)) {
291
+ plotMap.set(lowerCaseToken, token);
292
+ }
293
+ }
294
+ return plotMap;
180
295
  };
181
- var eecircuitResultToVGraphs = (result, spiceString) => {
182
- if (!result || !result.data || result.dataType !== "real") {
296
+
297
+ // lib/linear-interpolate.ts
298
+ var linearInterpolate = (targetX, xPoints, yPoints) => {
299
+ if (xPoints.length === 0) {
300
+ return 0;
301
+ }
302
+ if (targetX <= xPoints[0]) {
303
+ return yPoints[0];
304
+ }
305
+ if (targetX >= xPoints[xPoints.length - 1]) {
306
+ return yPoints[yPoints.length - 1];
307
+ }
308
+ let i = 1;
309
+ while (i < xPoints.length && xPoints[i] < targetX) {
310
+ i++;
311
+ }
312
+ const x1 = xPoints[i - 1];
313
+ const y1 = yPoints[i - 1];
314
+ const x2 = xPoints[i];
315
+ const y2 = yPoints[i];
316
+ if (x2 === x1) {
317
+ return y1;
318
+ }
319
+ return y1 + (y2 - y1) * (targetX - x1) / (x2 - x1);
320
+ };
321
+
322
+ // lib/eecircuit-result-to-simulation-graphs.ts
323
+ var eecircuitResultToSimulationGraphs = (result, spiceString) => {
324
+ if (!result?.data || result.dataType !== "real") {
183
325
  return [];
184
326
  }
185
327
  const timeData = result.data.find((item) => item.type === "time");
@@ -194,46 +336,62 @@ var eecircuitResultToVGraphs = (result, spiceString) => {
194
336
  for (const item of voltageDataItems) {
195
337
  voltageDataMap.set(item.name.toLowerCase(), item.values);
196
338
  }
339
+ const currentDataItems = result.data.filter(
340
+ (item) => item.type === "current" && Array.isArray(item.values)
341
+ );
342
+ const currentDataMap = /* @__PURE__ */ new Map();
343
+ for (const item of currentDataItems) {
344
+ currentDataMap.set(normalizeSpiceVector(item.name), item.values);
345
+ }
197
346
  const requestedPlots = extractRequestedPlots(spiceString);
198
- const probeMetadata = extractProbeMetadata(spiceString);
347
+ const voltageProbeMetadata = extractVoltageProbeMetadata(spiceString);
348
+ const currentProbeMetadata = extractCurrentProbeMetadata(spiceString);
199
349
  if (!requestedPlots) {
200
- return voltageDataItems.map((item) => {
201
- const metadata = probeMetadata.get(normalizeSpiceVector(item.name));
202
- return {
203
- netName: metadata?.name ?? getNetName(item.name),
204
- time: timeValues,
205
- voltage: item.values,
206
- probeMetadata: metadata
207
- };
208
- });
350
+ return [
351
+ ...voltageDataItems.map((item) => {
352
+ const metadata = voltageProbeMetadata.get(
353
+ normalizeSpiceVector(item.name)
354
+ );
355
+ const voltageGraph = {
356
+ graphType: "voltage",
357
+ netName: metadata?.name ?? getNetName(item.name),
358
+ time: timeValues,
359
+ voltage: item.values,
360
+ probeMetadata: metadata
361
+ };
362
+ return voltageGraph;
363
+ }),
364
+ ...currentDataItems.map((item) => {
365
+ const metadata = currentProbeMetadata.get(
366
+ normalizeSpiceVector(item.name)
367
+ );
368
+ const currentGraph = {
369
+ graphType: "current",
370
+ currentName: metadata?.name ?? getCurrentName(item.name),
371
+ time: timeValues,
372
+ current: item.values,
373
+ probeMetadata: metadata
374
+ };
375
+ return currentGraph;
376
+ })
377
+ ];
209
378
  }
210
379
  const graphs = [];
211
380
  for (const [lowerCaseToken, originalToken] of requestedPlots.entries()) {
212
- const diffMatch = originalToken.match(/^v\(([^,]+),\s*([^)]+)\)$/i);
213
- let voltage;
214
- if (diffMatch?.[1] && diffMatch?.[2]) {
215
- voltage = voltageDataMap.get(lowerCaseToken);
216
- if (!voltage) {
217
- const node1 = diffMatch[1].trim();
218
- const node2 = diffMatch[2].trim();
219
- const node1Data = voltageDataMap.get(`v(${node1.toLowerCase()})`);
220
- const node2Data = voltageDataMap.get(`v(${node2.toLowerCase()})`);
221
- if (node1Data && node2Data) {
222
- voltage = node1Data.map((v, i) => v - (node2Data[i] ?? 0));
223
- }
224
- }
225
- } else {
226
- voltage = voltageDataMap.get(lowerCaseToken);
227
- }
228
- if (voltage) {
229
- const metadata = probeMetadata.get(lowerCaseToken);
230
- graphs.push({
231
- netName: metadata?.name ?? getNetName(originalToken),
232
- time: timeValues,
233
- voltage,
234
- probeMetadata: metadata
235
- });
236
- }
381
+ const graph = createVoltageGraphFromRequestedPlot({
382
+ lowerCaseToken,
383
+ originalToken,
384
+ timeValues,
385
+ voltageDataMap,
386
+ voltageProbeMetadata
387
+ }) ?? createCurrentGraphFromRequestedPlot({
388
+ lowerCaseToken,
389
+ originalToken,
390
+ timeValues,
391
+ currentDataMap,
392
+ currentProbeMetadata
393
+ });
394
+ if (graph) graphs.push(graph);
237
395
  }
238
396
  const tranParams = parseTranParams(spiceString);
239
397
  if (tranParams?.tstep && tranParams.tstep > 0 && tranParams.tstop && graphs.length > 0) {
@@ -246,42 +404,98 @@ var eecircuitResultToVGraphs = (result, spiceString) => {
246
404
  (_, i) => tstart + i * tstep
247
405
  );
248
406
  const oldTimeValues = graphs[0].time;
249
- return graphs.map((graph) => ({
250
- ...graph,
251
- time: newTimeValues,
252
- voltage: newTimeValues.map(
253
- (t) => linearInterpolate(t, oldTimeValues, graph.voltage)
254
- )
255
- }));
407
+ return graphs.map((graph) => {
408
+ if (graph.graphType === "voltage") {
409
+ return {
410
+ ...graph,
411
+ time: newTimeValues,
412
+ voltage: newTimeValues.map(
413
+ (t) => linearInterpolate(t, oldTimeValues, graph.voltage)
414
+ )
415
+ };
416
+ }
417
+ return {
418
+ ...graph,
419
+ time: newTimeValues,
420
+ current: newTimeValues.map(
421
+ (t) => linearInterpolate(t, oldTimeValues, graph.current)
422
+ )
423
+ };
424
+ });
256
425
  }
257
426
  }
258
427
  return graphs;
259
428
  };
260
- var voltageGraphsToCircuitJson = (graphs, spiceString) => {
429
+ var eecircuitResultToVGraphs = (result, spiceString) => {
430
+ return eecircuitResultToSimulationGraphs(result, spiceString).filter(
431
+ (graph) => graph.graphType === "voltage"
432
+ );
433
+ };
434
+
435
+ // lib/simulation-graphs-to-circuit-json.ts
436
+ var simulationGraphsToCircuitJson = (graphs, spiceString) => {
261
437
  const tranParams = parseTranParams(spiceString);
262
438
  return graphs.map((graph, index) => {
263
- const graphIdSource = graph.probeMetadata?.simulation_voltage_probe_id ?? `${index}_${graph.netName}`;
439
+ if (graph.graphType === "voltage") {
440
+ const graphIdSource2 = graph.probeMetadata?.simulation_voltage_probe_id ?? `${index}_${graph.netName}`;
441
+ const graphElement2 = {
442
+ type: "simulation_transient_voltage_graph",
443
+ simulation_experiment_id: "placeholder_simulation_experiment_id",
444
+ simulation_transient_voltage_graph_id: `simulation_graph_${graphIdSource2}`,
445
+ name: graph.netName,
446
+ voltage_levels: graph.voltage,
447
+ timestamps_ms: graph.time.map((timePoint) => timePoint * 1e3),
448
+ start_time_ms: (tranParams?.tstart ?? 0) * 1e3,
449
+ time_per_step: (tranParams?.tstep ?? 0) * 1e3,
450
+ end_time_ms: (tranParams?.tstop ?? 0) * 1e3,
451
+ source_probe_id: graph.probeMetadata?.simulation_voltage_probe_id,
452
+ source_probe_name: graph.probeMetadata?.name,
453
+ source_node_name: graph.probeMetadata?.source_node_name,
454
+ reference_node_name: graph.probeMetadata?.reference_node_name
455
+ };
456
+ return graphElement2;
457
+ }
458
+ const graphIdSource = graph.probeMetadata?.simulation_current_probe_id ?? `${index}_${graph.currentName}`;
264
459
  const graphElement = {
265
- type: "simulation_transient_voltage_graph",
460
+ type: "simulation_transient_current_graph",
266
461
  simulation_experiment_id: "placeholder_simulation_experiment_id",
267
- simulation_transient_voltage_graph_id: `simulation_graph_${graphIdSource}`,
268
- name: graph.netName,
269
- voltage_levels: graph.voltage,
462
+ simulation_transient_current_graph_id: `simulation_graph_${graphIdSource}`,
463
+ name: graph.currentName,
464
+ current_levels: graph.current,
270
465
  timestamps_ms: graph.time.map((timePoint) => timePoint * 1e3),
271
466
  start_time_ms: (tranParams?.tstart ?? 0) * 1e3,
272
467
  time_per_step: (tranParams?.tstep ?? 0) * 1e3,
273
468
  end_time_ms: (tranParams?.tstop ?? 0) * 1e3,
274
- source_probe_id: graph.probeMetadata?.simulation_voltage_probe_id,
469
+ source_probe_id: graph.probeMetadata?.simulation_current_probe_id,
275
470
  source_probe_name: graph.probeMetadata?.name,
276
- source_node_name: graph.probeMetadata?.source_node_name,
277
- reference_node_name: graph.probeMetadata?.reference_node_name
471
+ source_component_id: graph.probeMetadata?.source_component_id,
472
+ source_trace_id: graph.probeMetadata?.source_trace_id
278
473
  };
279
474
  return graphElement;
280
475
  });
281
476
  };
477
+
478
+ // lib/index.ts
479
+ var ensureSimulation = async () => {
480
+ const { Simulation: SimulationCtor } = await import("@tscircuit/eecircuit-engine");
481
+ const instance = new SimulationCtor({ ngBehavior: "psa" });
482
+ await instance.start();
483
+ return instance;
484
+ };
485
+ var simulationPromise = null;
486
+ var getSimulation = async () => {
487
+ if (!simulationPromise) {
488
+ simulationPromise = ensureSimulation().catch((error) => {
489
+ simulationPromise = null;
490
+ throw error;
491
+ });
492
+ }
493
+ return simulationPromise;
494
+ };
282
495
  var simulate = async (spiceString) => {
283
496
  const simulation = await getSimulation();
284
- simulation.setNetList(spiceString);
497
+ const simulationSpiceString = rewritePspiceCompatibilitySyntax(spiceString);
498
+ simulation.setNetList(simulationSpiceString);
285
499
  let result;
286
500
  try {
287
501
  result = await simulation.runSim();
@@ -292,11 +506,14 @@ var simulate = async (spiceString) => {
292
506
  if (!result) {
293
507
  return { simulationResultCircuitJson: [] };
294
508
  }
295
- const graphs = eecircuitResultToVGraphs(result, spiceString);
509
+ const graphs = eecircuitResultToSimulationGraphs(
510
+ result,
511
+ simulationSpiceString
512
+ );
296
513
  return {
297
- simulationResultCircuitJson: voltageGraphsToCircuitJson(
514
+ simulationResultCircuitJson: simulationGraphsToCircuitJson(
298
515
  graphs,
299
- spiceString
516
+ simulationSpiceString
300
517
  )
301
518
  };
302
519
  };
@@ -309,6 +526,9 @@ var index_default = createNgspiceSpiceEngine;
309
526
  export {
310
527
  createNgspiceSpiceEngine,
311
528
  index_default as default,
529
+ eecircuitResultToSimulationGraphs,
312
530
  eecircuitResultToVGraphs,
313
- parseTranParams
531
+ parseTranParams,
532
+ rewritePspiceCompatibilitySyntax,
533
+ simulationGraphsToCircuitJson
314
534
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/ngspice-spice-engine",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "description": "A tscircuit-compatible SPICE engine using ngspice.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -18,10 +18,10 @@
18
18
  },
19
19
  "devDependencies": {
20
20
  "@tscircuit/eecircuit-engine": "https://jscdn.tscircuit.com/@tscircuit/eecircuit-engine/1.7.4.tgz",
21
- "@tscircuit/props": "^0.0.374",
21
+ "@tscircuit/props": "^0.0.551",
22
22
  "bun-match-svg": "^0.0.14",
23
- "circuit-json": "^0.0.435",
24
- "circuit-to-svg": "^0.0.217",
23
+ "circuit-json": "^0.0.437",
24
+ "circuit-to-svg": "^0.0.357",
25
25
  "format-si-unit": "^0.0.7",
26
26
  "@biomejs/biome": "^2.2.7",
27
27
  "@types/bun": "latest",