@tscircuit/ngspice-spice-engine 0.0.8 → 0.0.10
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 +5 -2
- package/dist/index.js +103 -7
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,10 @@ interface VoltageGraph {
|
|
|
15
15
|
time: number[];
|
|
16
16
|
voltage: number[];
|
|
17
17
|
}
|
|
18
|
+
interface NgspiceSpiceEngineOptions {
|
|
19
|
+
pspiceCompatibility?: boolean;
|
|
20
|
+
}
|
|
18
21
|
declare const eecircuitResultToVGraphs: (result: ResultType, spiceString: string) => VoltageGraph[];
|
|
19
|
-
declare const createNgspiceSpiceEngine: () => Promise<SpiceEngine>;
|
|
22
|
+
declare const createNgspiceSpiceEngine: (options?: NgspiceSpiceEngineOptions) => Promise<SpiceEngine>;
|
|
20
23
|
|
|
21
|
-
export { type TranParams, createNgspiceSpiceEngine, createNgspiceSpiceEngine as default, eecircuitResultToVGraphs, parseTranParams };
|
|
24
|
+
export { type NgspiceSpiceEngineOptions, type TranParams, createNgspiceSpiceEngine, createNgspiceSpiceEngine as default, eecircuitResultToVGraphs, parseTranParams };
|
package/dist/index.js
CHANGED
|
@@ -127,7 +127,7 @@ var getSimulation = async () => {
|
|
|
127
127
|
};
|
|
128
128
|
var extractRequestedPlots = (spiceString) => {
|
|
129
129
|
const match = spiceString.match(/\.print\s+tran\s+(.*)/i);
|
|
130
|
-
if (!match
|
|
130
|
+
if (!match?.[1]) {
|
|
131
131
|
return null;
|
|
132
132
|
}
|
|
133
133
|
const tokens = match[1].match(/[VI]\s*\([^)]+\)/gi);
|
|
@@ -155,7 +155,7 @@ var getNetName = (rawName) => {
|
|
|
155
155
|
return match[1] ?? rawName;
|
|
156
156
|
};
|
|
157
157
|
var eecircuitResultToVGraphs = (result, spiceString) => {
|
|
158
|
-
if (!result
|
|
158
|
+
if (!result?.data || result.dataType !== "real") {
|
|
159
159
|
return [];
|
|
160
160
|
}
|
|
161
161
|
const timeData = result.data.find((item) => item.type === "time");
|
|
@@ -240,9 +240,100 @@ var voltageGraphsToCircuitJson = (graphs, spiceString) => {
|
|
|
240
240
|
end_time_ms: (tranParams?.tstop ?? 0) * 1e3
|
|
241
241
|
}));
|
|
242
242
|
};
|
|
243
|
-
var
|
|
243
|
+
var splitFunctionArguments = (input) => {
|
|
244
|
+
const args = [];
|
|
245
|
+
let startIndex = 0;
|
|
246
|
+
let parenDepth = 0;
|
|
247
|
+
let braceDepth = 0;
|
|
248
|
+
for (let index = 0; index < input.length; index++) {
|
|
249
|
+
const char = input[index];
|
|
250
|
+
if (char === "(") parenDepth++;
|
|
251
|
+
else if (char === ")") parenDepth--;
|
|
252
|
+
else if (char === "{") braceDepth++;
|
|
253
|
+
else if (char === "}") braceDepth--;
|
|
254
|
+
else if (char === "," && parenDepth === 0 && braceDepth === 0) {
|
|
255
|
+
args.push(input.slice(startIndex, index));
|
|
256
|
+
startIndex = index + 1;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
args.push(input.slice(startIndex));
|
|
260
|
+
return args;
|
|
261
|
+
};
|
|
262
|
+
var convertPspiceIfFunctions = (input) => {
|
|
263
|
+
let output = "";
|
|
264
|
+
let index = 0;
|
|
265
|
+
while (index < input.length) {
|
|
266
|
+
const ifMatch = input.slice(index).match(/^if\s*\(/i);
|
|
267
|
+
if (!ifMatch) {
|
|
268
|
+
output += input[index];
|
|
269
|
+
index++;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const openParenIndex = index + ifMatch[0].length - 1;
|
|
273
|
+
let depth = 0;
|
|
274
|
+
let closeParenIndex = -1;
|
|
275
|
+
for (let scanIndex = openParenIndex; scanIndex < input.length; scanIndex++) {
|
|
276
|
+
const char = input[scanIndex];
|
|
277
|
+
if (char === "(") depth++;
|
|
278
|
+
else if (char === ")") {
|
|
279
|
+
depth--;
|
|
280
|
+
if (depth === 0) {
|
|
281
|
+
closeParenIndex = scanIndex;
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (closeParenIndex === -1) {
|
|
287
|
+
output += input.slice(index);
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
const innerExpression = convertPspiceIfFunctions(
|
|
291
|
+
input.slice(openParenIndex + 1, closeParenIndex)
|
|
292
|
+
);
|
|
293
|
+
const args = splitFunctionArguments(innerExpression);
|
|
294
|
+
if (args.length !== 3) {
|
|
295
|
+
output += input.slice(index, closeParenIndex + 1);
|
|
296
|
+
} else {
|
|
297
|
+
output += `((${args[0].trim()}) ? (${args[1].trim()}) : (${args[2].trim()}))`;
|
|
298
|
+
}
|
|
299
|
+
index = closeParenIndex + 1;
|
|
300
|
+
}
|
|
301
|
+
return output;
|
|
302
|
+
};
|
|
303
|
+
var preprocessPspiceForEmbeddedNgspice = (spiceString) => {
|
|
304
|
+
let processed = spiceString.replace(/\bif\s*\r?\n\+\s*\(/gi, "if(").replace(/\.MODEL(\s+\S+\s+)VSWITCH\b/gi, ".MODEL$1SW").replace(/\b(VON|VOFF)\s*=\s*[^\s)]+/gi, "").replace(
|
|
305
|
+
/\b(RON|ROFF)\s*=\s*([^\s)]+)/gi,
|
|
306
|
+
(_match, key, value) => `${key}=${value.replace(/V$/i, "")}`
|
|
307
|
+
).replace(/\bTC\s*=\s*([^\s,]+)\s*,\s*([^\s)]+)/gi, "TC1=$1 TC2=$2");
|
|
308
|
+
processed = convertPspiceIfFunctions(processed);
|
|
309
|
+
return processed.replace(/\s&\s/g, " && ").replace(/\s\|\s/g, " || ").replace(/\s\^\s/g, " != ");
|
|
310
|
+
};
|
|
311
|
+
var configureNgBehavior = (simulation, pspiceCompatibility) => {
|
|
312
|
+
const simulationWithCommandList = simulation;
|
|
313
|
+
const commandList = simulationWithCommandList.commandList;
|
|
314
|
+
if (!Array.isArray(commandList)) {
|
|
315
|
+
if (pspiceCompatibility) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
"Unable to enable PSPICE compatibility: eecircuit-engine commandList is not accessible"
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const ngBehaviorCommand = pspiceCompatibility ? "set ngbehavior=psa" : "unset ngbehavior";
|
|
323
|
+
const sourceCommandIndex = commandList.findIndex(
|
|
324
|
+
(command) => /^\s*source\s+test\.cir\s*$/i.test(command)
|
|
325
|
+
);
|
|
326
|
+
if (sourceCommandIndex === -1) {
|
|
327
|
+
commandList.unshift(ngBehaviorCommand);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
commandList.splice(sourceCommandIndex, 0, ngBehaviorCommand);
|
|
331
|
+
};
|
|
332
|
+
var simulate = async (spiceString, options) => {
|
|
333
|
+
const simulationSpiceString = options.pspiceCompatibility ? preprocessPspiceForEmbeddedNgspice(spiceString) : spiceString;
|
|
244
334
|
const simulation = await getSimulation();
|
|
245
|
-
simulation.setNetList(
|
|
335
|
+
simulation.setNetList(simulationSpiceString);
|
|
336
|
+
configureNgBehavior(simulation, false);
|
|
246
337
|
let result;
|
|
247
338
|
try {
|
|
248
339
|
result = await simulation.runSim();
|
|
@@ -261,9 +352,14 @@ var simulate = async (spiceString) => {
|
|
|
261
352
|
)
|
|
262
353
|
};
|
|
263
354
|
};
|
|
264
|
-
var createNgspiceSpiceEngine = async () =>
|
|
265
|
-
|
|
266
|
-
|
|
355
|
+
var createNgspiceSpiceEngine = async (options = {}) => {
|
|
356
|
+
const resolvedOptions = {
|
|
357
|
+
pspiceCompatibility: options.pspiceCompatibility ?? false
|
|
358
|
+
};
|
|
359
|
+
return {
|
|
360
|
+
simulate: (spiceString) => simulate(spiceString, resolvedOptions)
|
|
361
|
+
};
|
|
362
|
+
};
|
|
267
363
|
var index_default = createNgspiceSpiceEngine;
|
|
268
364
|
export {
|
|
269
365
|
createNgspiceSpiceEngine,
|
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.10",
|
|
4
4
|
"description": "A tscircuit-compatible SPICE engine using ngspice.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"typecheck": "tsc --noEmit"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"eecircuit-engine": "^1.
|
|
20
|
+
"eecircuit-engine": "^1.7.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@tscircuit/props": "^0.0.374",
|