@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 +32 -8
- package/dist/index.js +342 -122
- package/package.json +4 -4
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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/
|
|
112
|
-
var
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
|
147
|
+
return result + spiceString.slice(cursor);
|
|
127
148
|
};
|
|
128
|
-
var
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
|
134
|
-
if (!
|
|
135
|
-
return
|
|
157
|
+
const match = rawName.match(/^v\((.*)\)$/i);
|
|
158
|
+
if (!match) {
|
|
159
|
+
return rawName;
|
|
136
160
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
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
|
-
|
|
182
|
-
|
|
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
|
|
347
|
+
const voltageProbeMetadata = extractVoltageProbeMetadata(spiceString);
|
|
348
|
+
const currentProbeMetadata = extractCurrentProbeMetadata(spiceString);
|
|
199
349
|
if (!requestedPlots) {
|
|
200
|
-
return
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
|
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
|
-
|
|
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: "
|
|
460
|
+
type: "simulation_transient_current_graph",
|
|
266
461
|
simulation_experiment_id: "placeholder_simulation_experiment_id",
|
|
267
|
-
|
|
268
|
-
name: graph.
|
|
269
|
-
|
|
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?.
|
|
469
|
+
source_probe_id: graph.probeMetadata?.simulation_current_probe_id,
|
|
275
470
|
source_probe_name: graph.probeMetadata?.name,
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
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 =
|
|
509
|
+
const graphs = eecircuitResultToSimulationGraphs(
|
|
510
|
+
result,
|
|
511
|
+
simulationSpiceString
|
|
512
|
+
);
|
|
296
513
|
return {
|
|
297
|
-
simulationResultCircuitJson:
|
|
514
|
+
simulationResultCircuitJson: simulationGraphsToCircuitJson(
|
|
298
515
|
graphs,
|
|
299
|
-
|
|
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.
|
|
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.
|
|
21
|
+
"@tscircuit/props": "^0.0.551",
|
|
22
22
|
"bun-match-svg": "^0.0.14",
|
|
23
|
-
"circuit-json": "^0.0.
|
|
24
|
-
"circuit-to-svg": "^0.0.
|
|
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",
|