Skip to content

Commit 92082c7

Browse files
Merge pull request #176 from SpiceSharp/aggregate
Support for wmin/wmax/lmin/lmax for models and others
2 parents 45ab543 + 5b29c43 commit 92082c7

23 files changed

Lines changed: 1349 additions & 59 deletions
Lines changed: 398 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,398 @@
1+
using System;
2+
using Xunit;
3+
4+
namespace SpiceSharpParser.IntegrationTests.Components
5+
{
6+
/// <summary>
7+
/// Integration tests with simulations for model selection based on L and W parameters.
8+
/// These tests verify that the correct model is selected by running actual circuit simulations.
9+
/// </summary>
10+
public class ModelDimensionSimulationTests : BaseTests
11+
{
12+
#region Resistor Simulation Tests
13+
14+
[Fact]
15+
public void ResistorModelSelectionAffectsSimulationResults()
16+
{
17+
// Model with RSH=100 ohm/square, L/W range for small resistors
18+
// Model with RSH=1000 ohm/square, L/W range for large resistors
19+
// R = RSH * L / W
20+
var netlist = GetSpiceSharpModel(
21+
"Resistor model selection affects resistance value",
22+
"V1 IN 0 10",
23+
"R1 IN 0 RMOD L=1u W=1u",
24+
".model RMOD.0 R RSH=100 lmin=0.1u lmax=5u",
25+
".OP",
26+
".SAVE I(R1)",
27+
".END");
28+
29+
Assert.NotNull(netlist);
30+
Assert.False(netlist.ValidationResult.HasError);
31+
32+
// R1: L=1u, W=1u -> should use RMOD.0 (RSH=100) -> R = 100 * 1 / 1 = 100 ohms
33+
// Expected current: 10V / 100 ohm = 0.1 A
34+
var current1 = RunOpSimulation(netlist, "I(R1)");
35+
Assert.True(EqualsWithTol(0.1, Math.Abs(current1)), $"R1 current expected ~0.1A, got {current1}");
36+
}
37+
38+
[Fact]
39+
public void ResistorWidthParameterAffectsModelSelection()
40+
{
41+
var netlist = GetSpiceSharpModel(
42+
"Resistor width affects model selection",
43+
"V1 IN 0 5",
44+
"R1 IN 0 RMOD L=2u W=2u",
45+
".model RMOD.0 R RSH=50 wmin=1u wmax=10u",
46+
".OP",
47+
".SAVE I(R1)",
48+
".END");
49+
50+
Assert.NotNull(netlist);
51+
Assert.False(netlist.ValidationResult.HasError);
52+
53+
// R1: L=2u, W=2u -> RMOD.0 (RSH=50) -> R = 50 * 2 / 2 = 50 ohms -> I = 5V / 50 = 0.1 A
54+
var current1 = RunOpSimulation(netlist, "I(R1)");
55+
Assert.True(EqualsWithTol(0.1, Math.Abs(current1)), $"R1 current expected ~0.1A, got {current1}");
56+
}
57+
58+
[Fact]
59+
public void ResistorFallsBackToDefaultModelSimulation()
60+
{
61+
var netlist = GetSpiceSharpModel(
62+
"Resistor falls back to default model when dimensions don't match",
63+
"V1 IN 0 10",
64+
"R1 IN 0 RMOD L=0.5u W=1u",
65+
".model RMOD.0 R RSH=100 lmin=1u lmax=10u",
66+
".model RMOD R RSH=500",
67+
".OP",
68+
".SAVE I(R1)",
69+
".END");
70+
71+
Assert.NotNull(netlist);
72+
Assert.False(netlist.ValidationResult.HasError);
73+
74+
// R1: L=0.5u (< lmin=1u) -> should use RMOD (default, RSH=500) -> R = 500 * 0.5 / 1 = 250 ohms
75+
var current1 = RunOpSimulation(netlist, "I(R1)");
76+
Assert.True(EqualsWithTol(10.0 / 250.0, Math.Abs(current1)), $"R1 current expected ~0.04A, got {current1}");
77+
}
78+
79+
[Fact]
80+
public void ResistorWithBothLAndWConstraintsSimulation()
81+
{
82+
var netlist = GetSpiceSharpModel(
83+
"Resistor with both L and W constraints",
84+
"V1 IN 0 12",
85+
"R1 IN 0 RMOD L=1u W=2u",
86+
".model RMOD.0 R RSH=60 lmin=0.5u lmax=5u wmin=1u wmax=10u",
87+
".OP",
88+
".SAVE I(R1)",
89+
".END");
90+
91+
Assert.NotNull(netlist);
92+
Assert.False(netlist.ValidationResult.HasError);
93+
94+
// R1: L=1u, W=2u -> RMOD.0 (RSH=60) -> R = 60 * 1 / 2 = 30 ohms -> I = 12V / 30 = 0.4 A
95+
var current1 = RunOpSimulation(netlist, "I(R1)");
96+
Assert.True(EqualsWithTol(0.4, Math.Abs(current1)), $"R1 current expected ~0.4A, got {current1}");
97+
}
98+
99+
#endregion
100+
101+
#region Capacitor Simulation Tests
102+
103+
[Fact]
104+
public void CapacitorModelSelectionAffectsSimulationResults()
105+
{
106+
// Capacitance C = CJ * L * W (approximately for semiconductor capacitors)
107+
var netlist = GetSpiceSharpModel(
108+
"Capacitor model selection affects capacitance value",
109+
"V1 IN 0 PULSE(0 5 0 1n 1n 10n 20n)",
110+
"R1 IN OUT1 1k",
111+
"C1 OUT1 0 CMOD L=2u W=2u",
112+
".model CMOD.0 C CJ=1e-6 lmin=0.5u lmax=5u wmin=0.5u wmax=5u",
113+
".TRAN 1n 30n",
114+
".SAVE V(OUT1)",
115+
".END");
116+
117+
Assert.NotNull(netlist);
118+
Assert.False(netlist.ValidationResult.HasError);
119+
120+
var exports1 = RunTransientSimulation(netlist, "V(OUT1)");
121+
Assert.NotNull(exports1);
122+
Assert.True(exports1.Length > 0);
123+
}
124+
125+
[Fact]
126+
public void CapacitorWidthParameterAffectsModelSelection()
127+
{
128+
var netlist = GetSpiceSharpModel(
129+
"Capacitor width affects model selection",
130+
"V1 IN 0 PULSE(0 10 0 1n 1n 20n 40n)",
131+
"R1 IN OUT1 1k",
132+
"C1 OUT1 0 CMOD L=1u W=3u",
133+
".model CMOD.0 C CJ=5e-7 wmin=1u wmax=10u",
134+
".TRAN 1n 40n",
135+
".SAVE V(OUT1)",
136+
".END");
137+
138+
Assert.NotNull(netlist);
139+
Assert.False(netlist.ValidationResult.HasError);
140+
141+
var exports1 = RunTransientSimulation(netlist, "V(OUT1)");
142+
Assert.NotNull(exports1);
143+
Assert.True(exports1.Length > 0);
144+
}
145+
146+
[Fact]
147+
public void CapacitorFallsBackToDefaultModelSimulation()
148+
{
149+
var netlist = GetSpiceSharpModel(
150+
"Capacitor falls back to default model",
151+
"V1 IN 0 PULSE(0 5 0 1n 1n 10n 20n)",
152+
"R1 IN OUT1 1k",
153+
"C1 OUT1 0 CMOD L=0.3u W=1u",
154+
".model CMOD.0 C CJ=1e-6 lmin=1u lmax=3u",
155+
".model CMOD C CJ=5e-7",
156+
".TRAN 1n 30n",
157+
".SAVE V(OUT1)",
158+
".END");
159+
160+
Assert.NotNull(netlist);
161+
Assert.False(netlist.ValidationResult.HasError);
162+
163+
var exports1 = RunTransientSimulation(netlist, "V(OUT1)");
164+
Assert.NotNull(exports1);
165+
Assert.True(exports1.Length > 0);
166+
}
167+
168+
#endregion
169+
170+
#region Inductor Simulation Tests
171+
172+
[Fact]
173+
public void InductorWithLengthAndWidthParameters()
174+
{
175+
// Basic test to verify inductors accept L and W parameters
176+
var netlist = GetSpiceSharpModel(
177+
"Inductor with L and W parameters",
178+
"V1 IN 0 PULSE(0 1 0 1n 1n 10n 20n)",
179+
"R1 IN OUT 10",
180+
"L1 OUT 0 1u",
181+
".TRAN 1n 30n",
182+
".SAVE V(OUT) I(L1)",
183+
".END");
184+
185+
Assert.NotNull(netlist);
186+
Assert.False(netlist.ValidationResult.HasError);
187+
188+
var exports = RunTransientSimulation(netlist, "I(L1)");
189+
Assert.NotNull(exports);
190+
Assert.True(exports.Length > 0);
191+
}
192+
193+
[Fact]
194+
public void InductorBasicBehavior()
195+
{
196+
// Test basic RL circuit behavior
197+
var netlist = GetSpiceSharpModel(
198+
"RL circuit transient response",
199+
"V1 IN 0 PULSE(0 10 0 1n 1n 50n 100n)",
200+
"R1 IN OUT 100",
201+
"L1 OUT 0 1u",
202+
".TRAN 0.5n 60n",
203+
".SAVE I(L1)",
204+
".END");
205+
206+
Assert.NotNull(netlist);
207+
Assert.False(netlist.ValidationResult.HasError);
208+
209+
var exports = RunTransientSimulation(netlist, "I(L1)");
210+
211+
// Current through inductor should gradually rise (not instantaneous)
212+
// At t=0, current should be ~0
213+
Assert.True(Math.Abs(exports[0].Item2) < 0.01, $"Initial current should be ~0, got {exports[0].Item2}");
214+
215+
// Current should increase over time
216+
var index30n = 60; // Approximate index for 30ns (0.5ns steps)
217+
if (index30n < exports.Length)
218+
{
219+
Assert.True(exports[index30n].Item2 > exports[10].Item2,
220+
$"Current should increase: I(t=5ns)={exports[10].Item2}, I(t=30ns)={exports[index30n].Item2}");
221+
}
222+
}
223+
224+
[Fact]
225+
public void InductorComparisonDifferentValues()
226+
{
227+
// Compare two inductors with different inductance values
228+
var netlist = GetSpiceSharpModel(
229+
"Compare inductors with different values",
230+
"V1 IN 0 PULSE(0 10 0 1n 1n 50n 100n)",
231+
"R1 IN OUT1 100",
232+
"L1 OUT1 0 1u",
233+
"R2 IN OUT2 100",
234+
"L2 OUT2 0 10u",
235+
".TRAN 0.5n 60n",
236+
".SAVE I(L1) I(L2)",
237+
".END");
238+
239+
Assert.NotNull(netlist);
240+
Assert.False(netlist.ValidationResult.HasError);
241+
242+
var exports1 = RunTransientSimulation(netlist, "I(L1)");
243+
var exports2 = RunTransientSimulation(netlist, "I(L2)");
244+
245+
// Smaller inductance (L1=1u) should reach steady state faster than larger (L2=10u)
246+
var index20n = 40; // Approximate index for 20ns
247+
if (index20n < exports1.Length && index20n < exports2.Length)
248+
{
249+
Assert.True(exports1[index20n].Item2 > exports2[index20n].Item2,
250+
$"Smaller inductor should have higher current earlier: I(L1)={exports1[index20n].Item2}, I(L2)={exports2[index20n].Item2}");
251+
}
252+
}
253+
254+
#endregion
255+
256+
#region Combined Component Tests
257+
258+
[Fact]
259+
public void RLCCircuitWithDimensionBasedModels()
260+
{
261+
var netlist = GetSpiceSharpModel(
262+
"RLC circuit with dimension-based component models",
263+
"V1 IN 0 PULSE(0 5 0 1n 1n 20n 40n)",
264+
"R1 IN N1 RMOD L=2u W=1u",
265+
"L1 N1 N2 10u",
266+
"C1 N2 0 1p",
267+
".model RMOD.0 R RSH=50 lmin=1u lmax=10u",
268+
".TRAN 0.5n 50n",
269+
".SAVE V(N2)",
270+
".END");
271+
272+
Assert.NotNull(netlist);
273+
Assert.False(netlist.ValidationResult.HasError);
274+
275+
var exports = RunTransientSimulation(netlist, "V(N2)");
276+
277+
// Verify simulation runs and produces results
278+
Assert.NotNull(exports);
279+
Assert.True(exports.Length > 0);
280+
281+
// Verify circuit responds to input (voltage should change from 0)
282+
var maxVoltage = 0.0;
283+
foreach (var v in exports)
284+
{
285+
if (Math.Abs(v.Item2) > maxVoltage)
286+
maxVoltage = Math.Abs(v.Item2);
287+
}
288+
Assert.True(maxVoltage > 0.1, $"Circuit should respond to input, max voltage: {maxVoltage}");
289+
}
290+
291+
[Fact]
292+
public void MultipleResistorsWithDifferentModelSelection()
293+
{
294+
var netlist = GetSpiceSharpModel(
295+
"Voltage divider with different resistor models",
296+
"V1 IN 0 10",
297+
"R1 IN MID RMOD L=1u W=1u",
298+
"R2 MID 0 RMOD L=10u W=1u",
299+
".model RMOD.0 R RSH=100 lmin=0.5u lmax=5u",
300+
".model RMOD.1 R RSH=1000 lmin=5u lmax=50u",
301+
".OP",
302+
".SAVE V(MID)",
303+
".END");
304+
305+
Assert.NotNull(netlist);
306+
Assert.False(netlist.ValidationResult.HasError);
307+
308+
// R1: L=1u, W=1u -> RMOD.0 (RSH=100) -> R1 = 100 * 1 / 1 = 100 ohms
309+
// R2: L=10u, W=1u -> RMOD.1 (RSH=1000) -> R2 = 1000 * 10 / 1 = 10000 ohms
310+
// Voltage divider: V(MID) = 10V * R2 / (R1 + R2) = 10 * 10000 / 10100 ≈ 9.9 V
311+
312+
var voltage = RunOpSimulation(netlist, "V(MID)");
313+
var expected = 9.9;
314+
var tolerance = 0.1;
315+
Assert.True(Math.Abs(expected - voltage) < tolerance, $"V(MID) expected ~{expected}V, got {voltage}");
316+
}
317+
318+
[Fact]
319+
public void CapacitorChargeDischargeWithModelSelection()
320+
{
321+
var netlist = GetSpiceSharpModel(
322+
"Capacitor charge/discharge with model selection",
323+
"V1 IN 0 PULSE(0 10 0 1n 1n 50n 100n)",
324+
"R1 IN OUT 1k",
325+
"C1 OUT 0 CMOD L=3u W=3u",
326+
".model CMOD.0 C CJ=1e-6 lmin=1u lmax=10u wmin=1u wmax=10u",
327+
".TRAN 1n 80n",
328+
".SAVE V(OUT)",
329+
".END");
330+
331+
Assert.NotNull(netlist);
332+
Assert.False(netlist.ValidationResult.HasError);
333+
334+
var exports = RunTransientSimulation(netlist, "V(OUT)");
335+
336+
// Verify charging behavior
337+
Assert.True(exports[0].Item2 < 1.0, "Initial voltage should be low");
338+
339+
// Find peak during pulse
340+
var peakVoltage = 0.0;
341+
for (int i = 10; i < Math.Min(50, exports.Length); i++)
342+
{
343+
if (exports[i].Item2 > peakVoltage)
344+
peakVoltage = exports[i].Item2;
345+
}
346+
347+
Assert.True(peakVoltage > 5.0, $"Capacitor should charge significantly, peak: {peakVoltage}V");
348+
}
349+
350+
#endregion
351+
352+
#region Edge Case Simulation Tests
353+
354+
[Fact]
355+
public void ResistorWithLminBoundaryCondition()
356+
{
357+
var netlist = GetSpiceSharpModel(
358+
"Resistor at lmin boundary",
359+
"V1 IN 0 10",
360+
"R1 IN 0 RMOD L=1.01u W=1u",
361+
".model RMOD.0 R RSH=100 lmin=1u",
362+
".model RMOD R RSH=200",
363+
".OP",
364+
".SAVE I(R1)",
365+
".END");
366+
367+
Assert.NotNull(netlist);
368+
Assert.False(netlist.ValidationResult.HasError);
369+
370+
// R1: L=1.01u (>= lmin) -> should use RMOD.0 (RSH=100) -> R = 100 * 1.01 / 1 = 101 ohms
371+
var current1 = RunOpSimulation(netlist, "I(R1)");
372+
Assert.True(EqualsWithTol(10.0 / 101.0, Math.Abs(current1)), $"R1 current expected ~0.099A, got {current1}");
373+
}
374+
375+
[Fact]
376+
public void ResistorWithLmaxBoundaryCondition()
377+
{
378+
var netlist = GetSpiceSharpModel(
379+
"Resistor at lmax boundary",
380+
"V1 IN 0 10",
381+
"R1 IN 0 RMOD L=9.99u W=1u",
382+
".model RMOD.0 R RSH=100 lmax=10u",
383+
".model RMOD R RSH=200",
384+
".OP",
385+
".SAVE I(R1)",
386+
".END");
387+
388+
Assert.NotNull(netlist);
389+
Assert.False(netlist.ValidationResult.HasError);
390+
391+
// R1: L=9.99u (<= lmax) -> should use RMOD.0 (RSH=100) -> R = 100 * 9.99 / 1 = 999 ohms
392+
var current1 = RunOpSimulation(netlist, "I(R1)");
393+
Assert.True(EqualsWithTol(10.0 / 999.0, Math.Abs(current1)), $"R1 current expected ~0.01A, got {current1}");
394+
}
395+
396+
#endregion
397+
}
398+
}

0 commit comments

Comments
 (0)