Skip to content

Commit 9047d44

Browse files
Add nested function syntax support for .MEAS statements and update documentation
1 parent 0a7e22f commit 9047d44

3 files changed

Lines changed: 135 additions & 4 deletions

File tree

src/SpiceSharpParser.IntegrationTests/DotStatements/MeasTests.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,5 +1310,86 @@ public void MeasSimulationNamePopulated()
13101310
Assert.Single(results);
13111311
Assert.False(string.IsNullOrEmpty(results[0].SimulationName));
13121312
}
1313+
1314+
// =====================================================================
1315+
// Group I: Nested function syntax — mag(V()), db(V()), etc.
1316+
// =====================================================================
1317+
1318+
[Fact]
1319+
public void NestedMagVoltageEqualsVM()
1320+
{
1321+
// mag(V(OUT)) should produce the same result as VM(OUT)
1322+
var model = GetSpiceSharpModel(
1323+
"MEAS Nested mag(V()) vs VM()",
1324+
"V1 IN 0 AC 1",
1325+
"R1 IN OUT 1e3",
1326+
"C1 OUT 0 1e-9",
1327+
".AC DEC 10 1k 100MEG",
1328+
".MEAS AC gain_vm MAX VM(OUT)",
1329+
".MEAS AC gain_mag MAX mag(V(OUT))",
1330+
".END");
1331+
1332+
RunSimulations(model);
1333+
1334+
Assert.True(model.Measurements.ContainsKey("gain_vm"));
1335+
Assert.True(model.Measurements.ContainsKey("gain_mag"));
1336+
1337+
double vmResult = model.Measurements["gain_vm"][0].Value;
1338+
double magResult = model.Measurements["gain_mag"][0].Value;
1339+
1340+
Assert.True(model.Measurements["gain_vm"][0].Success);
1341+
Assert.True(model.Measurements["gain_mag"][0].Success);
1342+
Assert.True(EqualsWithTol(vmResult, magResult));
1343+
}
1344+
1345+
[Fact]
1346+
public void NestedDbVoltageEqualsVDB()
1347+
{
1348+
// db(V(OUT)) should produce the same result as VDB(OUT)
1349+
var model = GetSpiceSharpModel(
1350+
"MEAS Nested db(V()) vs VDB()",
1351+
"V1 IN 0 AC 1",
1352+
"R1 IN OUT 1e3",
1353+
"C1 OUT 0 1e-9",
1354+
".AC DEC 10 1k 100MEG",
1355+
".MEAS AC max_db_vdb MAX VDB(OUT)",
1356+
".MEAS AC max_db_nested MAX db(V(OUT))",
1357+
".END");
1358+
1359+
RunSimulations(model);
1360+
1361+
Assert.True(model.Measurements.ContainsKey("max_db_vdb"));
1362+
Assert.True(model.Measurements.ContainsKey("max_db_nested"));
1363+
1364+
double vdbResult = model.Measurements["max_db_vdb"][0].Value;
1365+
double nestedResult = model.Measurements["max_db_nested"][0].Value;
1366+
1367+
Assert.True(model.Measurements["max_db_vdb"][0].Success);
1368+
Assert.True(model.Measurements["max_db_nested"][0].Success);
1369+
Assert.True(EqualsWithTol(vdbResult, nestedResult));
1370+
}
1371+
1372+
[Fact]
1373+
public void NestedRealImagPhaseVoltage()
1374+
{
1375+
// real(V(OUT)), imag(V(OUT)), phase(V(OUT)) should match VR, VI, VP
1376+
var model = GetSpiceSharpModel(
1377+
"MEAS Nested real/imag/phase",
1378+
"V1 IN 0 AC 1",
1379+
"R1 IN OUT 1e3",
1380+
"C1 OUT 0 1e-9",
1381+
".AC DEC 10 1k 100MEG",
1382+
".MEAS AC max_vr MAX VR(OUT)",
1383+
".MEAS AC max_real MAX real(V(OUT))",
1384+
".END");
1385+
1386+
RunSimulations(model);
1387+
1388+
Assert.True(model.Measurements["max_vr"][0].Success);
1389+
Assert.True(model.Measurements["max_real"][0].Success);
1390+
Assert.True(EqualsWithTol(
1391+
model.Measurements["max_vr"][0].Value,
1392+
model.Measurements["max_real"][0].Value));
1393+
}
13131394
}
13141395
}

src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/Controls/Common/ExportFactory.cs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
34
using SpiceSharp.Simulations;
45
using SpiceSharpParser.Common;
@@ -13,6 +14,23 @@ namespace SpiceSharpParser.ModelReaders.Netlist.Spice.Readers.Controls.Common
1314
{
1415
public class ExportFactory : IExportFactory
1516
{
17+
private static readonly Dictionary<string, string> WrapperSuffixes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
18+
{
19+
{ "mag", "M" },
20+
{ "db", "DB" },
21+
{ "phase", "P" },
22+
{ "ph", "P" },
23+
{ "real", "R" },
24+
{ "re", "R" },
25+
{ "imag", "I" },
26+
{ "im", "I" },
27+
};
28+
29+
private static readonly HashSet<string> BaseExportNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
30+
{
31+
"V", "I",
32+
};
33+
1634
public Export Create(
1735
Parameter exportParameter,
1836
IReadingContext context,
@@ -22,8 +40,9 @@ public Export Create(
2240
if (exportParameter is BracketParameter bp)
2341
{
2442
string type = bp.Name;
43+
bool caseSensitive = context.ReaderSettings.CaseSensitivity.IsFunctionNameCaseSensitive;
2544

26-
if (mapper.TryGetValue(type, context.ReaderSettings.CaseSensitivity.IsFunctionNameCaseSensitive, out var exporter))
45+
if (mapper.TryGetValue(type, caseSensitive, out var exporter))
2746
{
2847
return exporter.CreateExport(
2948
exportParameter.Value,
@@ -32,6 +51,25 @@ public Export Create(
3251
context.EvaluationContext.GetSimulationContext(simulation),
3352
context.ReaderSettings.CaseSensitivity);
3453
}
54+
55+
// Support nested function syntax: mag(V(out)) → VM(out), db(I(R1)) → IDB(R1), etc.
56+
if (WrapperSuffixes.TryGetValue(type, out var suffix)
57+
&& bp.Parameters.Count == 1
58+
&& bp.Parameters[0] is BracketParameter inner
59+
&& BaseExportNames.Contains(inner.Name))
60+
{
61+
string flatType = inner.Name.ToUpper() + suffix;
62+
63+
if (mapper.TryGetValue(flatType, caseSensitive, out var flatExporter))
64+
{
65+
return flatExporter.CreateExport(
66+
exportParameter.Value,
67+
flatType,
68+
inner.Parameters,
69+
context.EvaluationContext.GetSimulationContext(simulation),
70+
context.ReaderSettings.CaseSensitivity);
71+
}
72+
}
3573
}
3674

3775
if (exportParameter is ReferenceParameter rp)

src/docs/articles/meas.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,20 @@ For AC analysis, standard `V(node)` is a complex number, so you need specific ex
562562
.MEAS AC bw_db WHEN VDB(out)=-3
563563
```
564564

565-
> **Note:** The nested function syntax `mag(V(out))` is **not** supported. Use `VM(out)`, `VDB(out)`, etc. instead.
565+
Both the prefix syntax and nested function syntax are supported:
566+
567+
| Prefix Syntax | Nested Syntax | Description |
568+
|---------------|---------------|-------------|
569+
| `VM(out)` | `mag(V(out))` | Voltage magnitude |
570+
| `VDB(out)` | `db(V(out))` | Voltage in decibels |
571+
| `VP(out)` | `phase(V(out))` or `ph(V(out))` | Voltage phase |
572+
| `VR(out)` | `real(V(out))` or `re(V(out))` | Voltage real part |
573+
| `VI(out)` | `imag(V(out))` or `im(V(out))` | Voltage imaginary part |
574+
| `IM(R1)` | `mag(I(R1))` | Current magnitude |
575+
| `IDB(R1)` | `db(I(R1))` | Current in decibels |
576+
| `IP(R1)` | `phase(I(R1))` | Current phase |
577+
578+
The nested syntax also works in `.PRINT`, `.PLOT`, `.SAVE`, and `.WAVE` statements.
566579

567580
---
568581

@@ -649,6 +662,5 @@ C1 OUT 0 1u
649662

650663
## Known Limitations
651664

652-
- `mag(V(out))` nested function syntax is not supported — use `VM(out)` instead
653665
- PARAM measurements must be declared **after** the measurements they reference (results are computed in declaration order, not via dependency resolution)
654666
- When a threshold crossing is not found in the data, the measurement returns `Success = false` and `Value = NaN` rather than raising an error

0 commit comments

Comments
 (0)