diff --git a/src/SpiceSharpParser.IntegrationTests/Components/ModelDimensionSimulationTests.cs b/src/SpiceSharpParser.IntegrationTests/Components/ModelDimensionSimulationTests.cs new file mode 100644 index 00000000..1a360cfe --- /dev/null +++ b/src/SpiceSharpParser.IntegrationTests/Components/ModelDimensionSimulationTests.cs @@ -0,0 +1,398 @@ +using System; +using Xunit; + +namespace SpiceSharpParser.IntegrationTests.Components +{ + /// + /// Integration tests with simulations for model selection based on L and W parameters. + /// These tests verify that the correct model is selected by running actual circuit simulations. + /// + public class ModelDimensionSimulationTests : BaseTests + { + #region Resistor Simulation Tests + + [Fact] + public void ResistorModelSelectionAffectsSimulationResults() + { + // Model with RSH=100 ohm/square, L/W range for small resistors + // Model with RSH=1000 ohm/square, L/W range for large resistors + // R = RSH * L / W + var netlist = GetSpiceSharpModel( + "Resistor model selection affects resistance value", + "V1 IN 0 10", + "R1 IN 0 RMOD L=1u W=1u", + ".model RMOD.0 R RSH=100 lmin=0.1u lmax=5u", + ".OP", + ".SAVE I(R1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + // R1: L=1u, W=1u -> should use RMOD.0 (RSH=100) -> R = 100 * 1 / 1 = 100 ohms + // Expected current: 10V / 100 ohm = 0.1 A + var current1 = RunOpSimulation(netlist, "I(R1)"); + Assert.True(EqualsWithTol(0.1, Math.Abs(current1)), $"R1 current expected ~0.1A, got {current1}"); + } + + [Fact] + public void ResistorWidthParameterAffectsModelSelection() + { + var netlist = GetSpiceSharpModel( + "Resistor width affects model selection", + "V1 IN 0 5", + "R1 IN 0 RMOD L=2u W=2u", + ".model RMOD.0 R RSH=50 wmin=1u wmax=10u", + ".OP", + ".SAVE I(R1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + // R1: L=2u, W=2u -> RMOD.0 (RSH=50) -> R = 50 * 2 / 2 = 50 ohms -> I = 5V / 50 = 0.1 A + var current1 = RunOpSimulation(netlist, "I(R1)"); + Assert.True(EqualsWithTol(0.1, Math.Abs(current1)), $"R1 current expected ~0.1A, got {current1}"); + } + + [Fact] + public void ResistorFallsBackToDefaultModelSimulation() + { + var netlist = GetSpiceSharpModel( + "Resistor falls back to default model when dimensions don't match", + "V1 IN 0 10", + "R1 IN 0 RMOD L=0.5u W=1u", + ".model RMOD.0 R RSH=100 lmin=1u lmax=10u", + ".model RMOD R RSH=500", + ".OP", + ".SAVE I(R1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + // R1: L=0.5u (< lmin=1u) -> should use RMOD (default, RSH=500) -> R = 500 * 0.5 / 1 = 250 ohms + var current1 = RunOpSimulation(netlist, "I(R1)"); + Assert.True(EqualsWithTol(10.0 / 250.0, Math.Abs(current1)), $"R1 current expected ~0.04A, got {current1}"); + } + + [Fact] + public void ResistorWithBothLAndWConstraintsSimulation() + { + var netlist = GetSpiceSharpModel( + "Resistor with both L and W constraints", + "V1 IN 0 12", + "R1 IN 0 RMOD L=1u W=2u", + ".model RMOD.0 R RSH=60 lmin=0.5u lmax=5u wmin=1u wmax=10u", + ".OP", + ".SAVE I(R1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + // R1: L=1u, W=2u -> RMOD.0 (RSH=60) -> R = 60 * 1 / 2 = 30 ohms -> I = 12V / 30 = 0.4 A + var current1 = RunOpSimulation(netlist, "I(R1)"); + Assert.True(EqualsWithTol(0.4, Math.Abs(current1)), $"R1 current expected ~0.4A, got {current1}"); + } + + #endregion + + #region Capacitor Simulation Tests + + [Fact] + public void CapacitorModelSelectionAffectsSimulationResults() + { + // Capacitance C = CJ * L * W (approximately for semiconductor capacitors) + var netlist = GetSpiceSharpModel( + "Capacitor model selection affects capacitance value", + "V1 IN 0 PULSE(0 5 0 1n 1n 10n 20n)", + "R1 IN OUT1 1k", + "C1 OUT1 0 CMOD L=2u W=2u", + ".model CMOD.0 C CJ=1e-6 lmin=0.5u lmax=5u wmin=0.5u wmax=5u", + ".TRAN 1n 30n", + ".SAVE V(OUT1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var exports1 = RunTransientSimulation(netlist, "V(OUT1)"); + Assert.NotNull(exports1); + Assert.True(exports1.Length > 0); + } + + [Fact] + public void CapacitorWidthParameterAffectsModelSelection() + { + var netlist = GetSpiceSharpModel( + "Capacitor width affects model selection", + "V1 IN 0 PULSE(0 10 0 1n 1n 20n 40n)", + "R1 IN OUT1 1k", + "C1 OUT1 0 CMOD L=1u W=3u", + ".model CMOD.0 C CJ=5e-7 wmin=1u wmax=10u", + ".TRAN 1n 40n", + ".SAVE V(OUT1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var exports1 = RunTransientSimulation(netlist, "V(OUT1)"); + Assert.NotNull(exports1); + Assert.True(exports1.Length > 0); + } + + [Fact] + public void CapacitorFallsBackToDefaultModelSimulation() + { + var netlist = GetSpiceSharpModel( + "Capacitor falls back to default model", + "V1 IN 0 PULSE(0 5 0 1n 1n 10n 20n)", + "R1 IN OUT1 1k", + "C1 OUT1 0 CMOD L=0.3u W=1u", + ".model CMOD.0 C CJ=1e-6 lmin=1u lmax=3u", + ".model CMOD C CJ=5e-7", + ".TRAN 1n 30n", + ".SAVE V(OUT1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var exports1 = RunTransientSimulation(netlist, "V(OUT1)"); + Assert.NotNull(exports1); + Assert.True(exports1.Length > 0); + } + + #endregion + + #region Inductor Simulation Tests + + [Fact] + public void InductorWithLengthAndWidthParameters() + { + // Basic test to verify inductors accept L and W parameters + var netlist = GetSpiceSharpModel( + "Inductor with L and W parameters", + "V1 IN 0 PULSE(0 1 0 1n 1n 10n 20n)", + "R1 IN OUT 10", + "L1 OUT 0 1u", + ".TRAN 1n 30n", + ".SAVE V(OUT) I(L1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var exports = RunTransientSimulation(netlist, "I(L1)"); + Assert.NotNull(exports); + Assert.True(exports.Length > 0); + } + + [Fact] + public void InductorBasicBehavior() + { + // Test basic RL circuit behavior + var netlist = GetSpiceSharpModel( + "RL circuit transient response", + "V1 IN 0 PULSE(0 10 0 1n 1n 50n 100n)", + "R1 IN OUT 100", + "L1 OUT 0 1u", + ".TRAN 0.5n 60n", + ".SAVE I(L1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var exports = RunTransientSimulation(netlist, "I(L1)"); + + // Current through inductor should gradually rise (not instantaneous) + // At t=0, current should be ~0 + Assert.True(Math.Abs(exports[0].Item2) < 0.01, $"Initial current should be ~0, got {exports[0].Item2}"); + + // Current should increase over time + var index30n = 60; // Approximate index for 30ns (0.5ns steps) + if (index30n < exports.Length) + { + Assert.True(exports[index30n].Item2 > exports[10].Item2, + $"Current should increase: I(t=5ns)={exports[10].Item2}, I(t=30ns)={exports[index30n].Item2}"); + } + } + + [Fact] + public void InductorComparisonDifferentValues() + { + // Compare two inductors with different inductance values + var netlist = GetSpiceSharpModel( + "Compare inductors with different values", + "V1 IN 0 PULSE(0 10 0 1n 1n 50n 100n)", + "R1 IN OUT1 100", + "L1 OUT1 0 1u", + "R2 IN OUT2 100", + "L2 OUT2 0 10u", + ".TRAN 0.5n 60n", + ".SAVE I(L1) I(L2)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var exports1 = RunTransientSimulation(netlist, "I(L1)"); + var exports2 = RunTransientSimulation(netlist, "I(L2)"); + + // Smaller inductance (L1=1u) should reach steady state faster than larger (L2=10u) + var index20n = 40; // Approximate index for 20ns + if (index20n < exports1.Length && index20n < exports2.Length) + { + Assert.True(exports1[index20n].Item2 > exports2[index20n].Item2, + $"Smaller inductor should have higher current earlier: I(L1)={exports1[index20n].Item2}, I(L2)={exports2[index20n].Item2}"); + } + } + + #endregion + + #region Combined Component Tests + + [Fact] + public void RLCCircuitWithDimensionBasedModels() + { + var netlist = GetSpiceSharpModel( + "RLC circuit with dimension-based component models", + "V1 IN 0 PULSE(0 5 0 1n 1n 20n 40n)", + "R1 IN N1 RMOD L=2u W=1u", + "L1 N1 N2 10u", + "C1 N2 0 1p", + ".model RMOD.0 R RSH=50 lmin=1u lmax=10u", + ".TRAN 0.5n 50n", + ".SAVE V(N2)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var exports = RunTransientSimulation(netlist, "V(N2)"); + + // Verify simulation runs and produces results + Assert.NotNull(exports); + Assert.True(exports.Length > 0); + + // Verify circuit responds to input (voltage should change from 0) + var maxVoltage = 0.0; + foreach (var v in exports) + { + if (Math.Abs(v.Item2) > maxVoltage) + maxVoltage = Math.Abs(v.Item2); + } + Assert.True(maxVoltage > 0.1, $"Circuit should respond to input, max voltage: {maxVoltage}"); + } + + [Fact] + public void MultipleResistorsWithDifferentModelSelection() + { + var netlist = GetSpiceSharpModel( + "Voltage divider with different resistor models", + "V1 IN 0 10", + "R1 IN MID RMOD L=1u W=1u", + "R2 MID 0 RMOD L=10u W=1u", + ".model RMOD.0 R RSH=100 lmin=0.5u lmax=5u", + ".model RMOD.1 R RSH=1000 lmin=5u lmax=50u", + ".OP", + ".SAVE V(MID)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + // R1: L=1u, W=1u -> RMOD.0 (RSH=100) -> R1 = 100 * 1 / 1 = 100 ohms + // R2: L=10u, W=1u -> RMOD.1 (RSH=1000) -> R2 = 1000 * 10 / 1 = 10000 ohms + // Voltage divider: V(MID) = 10V * R2 / (R1 + R2) = 10 * 10000 / 10100 ≈ 9.9 V + + var voltage = RunOpSimulation(netlist, "V(MID)"); + var expected = 9.9; + var tolerance = 0.1; + Assert.True(Math.Abs(expected - voltage) < tolerance, $"V(MID) expected ~{expected}V, got {voltage}"); + } + + [Fact] + public void CapacitorChargeDischargeWithModelSelection() + { + var netlist = GetSpiceSharpModel( + "Capacitor charge/discharge with model selection", + "V1 IN 0 PULSE(0 10 0 1n 1n 50n 100n)", + "R1 IN OUT 1k", + "C1 OUT 0 CMOD L=3u W=3u", + ".model CMOD.0 C CJ=1e-6 lmin=1u lmax=10u wmin=1u wmax=10u", + ".TRAN 1n 80n", + ".SAVE V(OUT)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var exports = RunTransientSimulation(netlist, "V(OUT)"); + + // Verify charging behavior + Assert.True(exports[0].Item2 < 1.0, "Initial voltage should be low"); + + // Find peak during pulse + var peakVoltage = 0.0; + for (int i = 10; i < Math.Min(50, exports.Length); i++) + { + if (exports[i].Item2 > peakVoltage) + peakVoltage = exports[i].Item2; + } + + Assert.True(peakVoltage > 5.0, $"Capacitor should charge significantly, peak: {peakVoltage}V"); + } + + #endregion + + #region Edge Case Simulation Tests + + [Fact] + public void ResistorWithLminBoundaryCondition() + { + var netlist = GetSpiceSharpModel( + "Resistor at lmin boundary", + "V1 IN 0 10", + "R1 IN 0 RMOD L=1.01u W=1u", + ".model RMOD.0 R RSH=100 lmin=1u", + ".model RMOD R RSH=200", + ".OP", + ".SAVE I(R1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + // R1: L=1.01u (>= lmin) -> should use RMOD.0 (RSH=100) -> R = 100 * 1.01 / 1 = 101 ohms + var current1 = RunOpSimulation(netlist, "I(R1)"); + Assert.True(EqualsWithTol(10.0 / 101.0, Math.Abs(current1)), $"R1 current expected ~0.099A, got {current1}"); + } + + [Fact] + public void ResistorWithLmaxBoundaryCondition() + { + var netlist = GetSpiceSharpModel( + "Resistor at lmax boundary", + "V1 IN 0 10", + "R1 IN 0 RMOD L=9.99u W=1u", + ".model RMOD.0 R RSH=100 lmax=10u", + ".model RMOD R RSH=200", + ".OP", + ".SAVE I(R1)", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + // R1: L=9.99u (<= lmax) -> should use RMOD.0 (RSH=100) -> R = 100 * 9.99 / 1 = 999 ohms + var current1 = RunOpSimulation(netlist, "I(R1)"); + Assert.True(EqualsWithTol(10.0 / 999.0, Math.Abs(current1)), $"R1 current expected ~0.01A, got {current1}"); + } + + #endregion + } +} diff --git a/src/SpiceSharpParser.IntegrationTests/Components/ModelDimensionTests.cs b/src/SpiceSharpParser.IntegrationTests/Components/ModelDimensionTests.cs new file mode 100644 index 00000000..5fdf8a94 --- /dev/null +++ b/src/SpiceSharpParser.IntegrationTests/Components/ModelDimensionTests.cs @@ -0,0 +1,695 @@ +using SpiceSharp.Components; +using Xunit; + +namespace SpiceSharpParser.IntegrationTests.Components +{ + /// + /// Integration tests for model selection (binning) based on instance parameters + /// and model selection parameters (e.g. lmin, lmax, wmin, wmax). + /// + public class ModelDimensionTests : BaseTests + { + #region MOSFET Tests + + [Fact] + public void MosfetWithLengthAndWidthParameters() + { + var netlist = GetSpiceSharpModel( + "MOSFET with L and W parameters", + "M1 D G S B NMOS1 L=1u W=10u", + ".model NMOS1 NMOS level=1", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + var mosfet = netlist.Circuit["m1"] as Mosfet1; + Assert.NotNull(mosfet); + } + + [Fact] + public void MosfetSelectsCorrectModelBasedOnLength() + { + var netlist = GetSpiceSharpModel( + "MOSFET model selection based on length", + "M1 D G S B NMOS L=0.5u W=10u", + "M2 D G S B NMOS L=5u W=10u", + ".model NMOS.0 NMOS level=1 lmin=0.1u lmax=1u", + ".model NMOS.1 NMOS level=1 lmin=1u lmax=10u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + // M1 L=0.5u should match NMOS.0 (lmin=0.1u lmax=1u) + var mosfet1 = netlist.Circuit["m1"] as Mosfet1; + Assert.NotNull(mosfet1); + + // M2 L=5u should match NMOS.1 (lmin=1u lmax=10u) + var mosfet2 = netlist.Circuit["m2"] as Mosfet1; + Assert.NotNull(mosfet2); + + Assert.NotNull(netlist.Circuit["NMOS.0"]); + Assert.NotNull(netlist.Circuit["NMOS.1"]); + } + + [Fact] + public void MosfetSelectsCorrectModelBasedOnWidth() + { + var netlist = GetSpiceSharpModel( + "MOSFET model selection based on width", + "M1 D G S B PMOS L=1u W=2u", + "M2 D G S B PMOS L=1u W=20u", + ".model PMOS.0 PMOS level=1 wmin=1u wmax=10u", + ".model PMOS.1 PMOS level=1 wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + // M1 W=2u should match PMOS.0 (wmin=1u wmax=10u) + var mosfet1 = netlist.Circuit["m1"] as Mosfet1; + Assert.NotNull(mosfet1); + + // M2 W=20u should match PMOS.1 (wmin=10u wmax=100u) + var mosfet2 = netlist.Circuit["m2"] as Mosfet1; + Assert.NotNull(mosfet2); + } + + [Fact] + public void MosfetSelectsCorrectModelBasedOnLengthAndWidth() + { + var netlist = GetSpiceSharpModel( + "MOSFET model selection based on L and W", + "M1 D G S B NMOS L=0.5u W=5u", + "M2 D G S B NMOS L=5u W=50u", + ".model NMOS.0 NMOS level=1 lmin=0.1u lmax=1u wmin=1u wmax=10u", + ".model NMOS.1 NMOS level=1 lmin=1u lmax=10u wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + // M1 L=0.5u W=5u should match NMOS.0 + var mosfet1 = netlist.Circuit["m1"] as Mosfet1; + Assert.NotNull(mosfet1); + + // M2 L=5u W=50u should match NMOS.1 + var mosfet2 = netlist.Circuit["m2"] as Mosfet1; + Assert.NotNull(mosfet2); + } + + [Fact] + public void MosfetFallsBackToBaseModelWhenNoMatch() + { + // L=100u exceeds NMOS.0's lmax=1u, should fall back to base NMOS + var netlist = GetSpiceSharpModel( + "MOSFET falls back to base model", + "M1 D G S B NMOS L=100u W=100u", + ".model NMOS.0 NMOS level=1 lmin=0.1u lmax=1u", + ".model NMOS NMOS level=1", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var mosfet = netlist.Circuit["m1"] as Mosfet1; + Assert.NotNull(mosfet); + } + + #endregion + + #region Resistor Tests + + [Fact] + public void ResistorWithLengthAndWidthParameters() + { + var netlist = GetSpiceSharpModel( + "Resistor with L and W parameters", + "R1 1 0 RMOD L=1u W=10u", + ".model RMOD R RSH=1", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + var resistor = netlist.Circuit["r1"] as Resistor; + Assert.NotNull(resistor); + } + + [Fact] + public void ResistorSelectsCorrectModelBasedOnDimensions() + { + var netlist = GetSpiceSharpModel( + "Resistor model selection based on dimensions", + "R1 1 0 RMOD L=0.5u W=5u", + "R2 1 0 RMOD L=5u W=50u", + ".model RMOD.0 R RSH=1 lmin=0.1u lmax=1u wmin=1u wmax=10u", + ".model RMOD.1 R RSH=1 lmin=1u lmax=10u wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var resistor1 = netlist.Circuit["r1"] as Resistor; + Assert.NotNull(resistor1); + + var resistor2 = netlist.Circuit["r2"] as Resistor; + Assert.NotNull(resistor2); + } + + [Fact] + public void ResistorModelWithOnlyLengthConstraints() + { + // Models only constrain L (no wmin/wmax), so W is irrelevant for selection + var netlist = GetSpiceSharpModel( + "Resistor model with only length constraints", + "R1 1 0 RMOD L=0.5u W=1u", + "R2 1 0 RMOD L=5u W=1u", + ".model RMOD.0 R RSH=1 lmin=0.1u lmax=1u", + ".model RMOD.1 R RSH=1 lmin=1u lmax=10u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var resistor1 = netlist.Circuit["r1"] as Resistor; + Assert.NotNull(resistor1); + + var resistor2 = netlist.Circuit["r2"] as Resistor; + Assert.NotNull(resistor2); + } + + [Fact] + public void ResistorModelWithOnlyWidthConstraints() + { + // Models only constrain W (no lmin/lmax), so L is irrelevant for selection + var netlist = GetSpiceSharpModel( + "Resistor model with only width constraints", + "R1 1 0 RMOD L=1u W=5u", + "R2 1 0 RMOD L=1u W=50u", + ".model RMOD.0 R RSH=1 wmin=1u wmax=10u", + ".model RMOD.1 R RSH=1 wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var resistor1 = netlist.Circuit["r1"] as Resistor; + Assert.NotNull(resistor1); + + var resistor2 = netlist.Circuit["r2"] as Resistor; + Assert.NotNull(resistor2); + } + + #endregion + + #region Capacitor Tests + + [Fact] + public void CapacitorWithLengthAndWidthParameters() + { + var netlist = GetSpiceSharpModel( + "Capacitor with L and W parameters", + "C1 1 0 CMOD L=1u W=10u", + ".model CMOD C CJ=1e-6", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + var capacitor = netlist.Circuit["c1"] as Capacitor; + Assert.NotNull(capacitor); + } + + [Fact] + public void CapacitorSelectsCorrectModelBasedOnDimensions() + { + var netlist = GetSpiceSharpModel( + "Capacitor model selection based on dimensions", + "C1 1 0 CMOD L=0.5u W=5u", + "C2 1 0 CMOD L=5u W=50u", + ".model CMOD.0 C CJ=1e-6 lmin=0.1u lmax=1u wmin=1u wmax=10u", + ".model CMOD.1 C CJ=1e-6 lmin=1u lmax=10u wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var capacitor1 = netlist.Circuit["c1"] as Capacitor; + Assert.NotNull(capacitor1); + + var capacitor2 = netlist.Circuit["c2"] as Capacitor; + Assert.NotNull(capacitor2); + } + + #endregion + + #region BJT Tests + + [Fact] + public void BJTWithLengthAndWidthParameters() + { + var netlist = GetSpiceSharpModel( + "BJT with L and W parameters", + "Q1 C B E QMOD L=1u W=10u", + ".model QMOD NPN", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + var bjt = netlist.Circuit["q1"] as BipolarJunctionTransistor; + Assert.NotNull(bjt); + } + + [Fact] + public void BJTSelectsCorrectModelBasedOnDimensions() + { + var netlist = GetSpiceSharpModel( + "BJT model selection based on dimensions", + "Q1 C B E QMOD L=0.5u W=5u", + "Q2 C B E QMOD L=5u W=50u", + ".model QMOD.0 NPN lmin=0.1u lmax=1u wmin=1u wmax=10u", + ".model QMOD.1 NPN lmin=1u lmax=10u wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var bjt1 = netlist.Circuit["q1"] as BipolarJunctionTransistor; + Assert.NotNull(bjt1); + + var bjt2 = netlist.Circuit["q2"] as BipolarJunctionTransistor; + Assert.NotNull(bjt2); + } + + [Fact] + public void BJTModelWithOnlyLengthConstraints() + { + // Models only constrain L, component has only L + var netlist = GetSpiceSharpModel( + "BJT model with only length constraints", + "Q1 C B E QMOD L=0.5u", + "Q2 C B E QMOD L=5u", + ".model QMOD.0 NPN lmin=0.1u lmax=1u", + ".model QMOD.1 NPN lmin=1u lmax=10u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var bjt1 = netlist.Circuit["q1"] as BipolarJunctionTransistor; + Assert.NotNull(bjt1); + + var bjt2 = netlist.Circuit["q2"] as BipolarJunctionTransistor; + Assert.NotNull(bjt2); + } + + #endregion + + #region Diode Tests + + [Fact] + public void DiodeWithLengthAndWidthParameters() + { + var netlist = GetSpiceSharpModel( + "Diode with L and W parameters", + "D1 A K DMOD L=1u W=10u", + ".model DMOD D", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + var diode = netlist.Circuit["d1"] as Diode; + Assert.NotNull(diode); + } + + [Fact] + public void DiodeSelectsCorrectModelBasedOnDimensions() + { + var netlist = GetSpiceSharpModel( + "Diode model selection based on dimensions", + "D1 A K DMOD L=0.5u W=5u", + "D2 A K DMOD L=5u W=50u", + ".model DMOD.0 D lmin=0.1u lmax=1u wmin=1u wmax=10u", + ".model DMOD.1 D lmin=1u lmax=10u wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var diode1 = netlist.Circuit["d1"] as Diode; + Assert.NotNull(diode1); + + var diode2 = netlist.Circuit["d2"] as Diode; + Assert.NotNull(diode2); + } + + [Fact] + public void DiodeModelWithOnlyWidthConstraints() + { + // Models only constrain W, component has only W + var netlist = GetSpiceSharpModel( + "Diode model with only width constraints", + "D1 A K DMOD W=5u", + "D2 A K DMOD W=50u", + ".model DMOD.0 D wmin=1u wmax=10u", + ".model DMOD.1 D wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var diode1 = netlist.Circuit["d1"] as Diode; + Assert.NotNull(diode1); + + var diode2 = netlist.Circuit["d2"] as Diode; + Assert.NotNull(diode2); + } + + #endregion + + #region JFET Tests + + [Fact] + public void JFETWithLengthAndWidthParameters() + { + var netlist = GetSpiceSharpModel( + "JFET with L and W parameters", + "J1 D G S JMOD L=1u W=10u", + ".model JMOD NJF", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + var jfet = netlist.Circuit["j1"] as JFET; + Assert.NotNull(jfet); + } + + [Fact] + public void JFETSelectsCorrectModelBasedOnDimensions() + { + var netlist = GetSpiceSharpModel( + "JFET model selection based on dimensions", + "J1 D G S JMOD L=0.5u W=5u", + "J2 D G S JMOD L=5u W=50u", + ".model JMOD.0 NJF lmin=0.1u lmax=1u wmin=1u wmax=10u", + ".model JMOD.1 NJF lmin=1u lmax=10u wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var jfet1 = netlist.Circuit["j1"] as JFET; + Assert.NotNull(jfet1); + + var jfet2 = netlist.Circuit["j2"] as JFET; + Assert.NotNull(jfet2); + } + + [Fact] + public void JFETModelWithOnlyLengthConstraints() + { + // Models only constrain L, component has only L + var netlist = GetSpiceSharpModel( + "JFET model with only length constraints", + "J1 D G S JMOD L=0.5u", + "J2 D G S JMOD L=5u", + ".model JMOD.0 PJF lmin=0.1u lmax=1u", + ".model JMOD.1 PJF lmin=1u lmax=10u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var jfet1 = netlist.Circuit["j1"] as JFET; + Assert.NotNull(jfet1); + + var jfet2 = netlist.Circuit["j2"] as JFET; + Assert.NotNull(jfet2); + } + + #endregion + + #region Non-Numeric Model Suffix Tests + + [Fact] + public void MosfetSelectsModelWithNonNumericSuffix() + { + var netlist = GetSpiceSharpModel( + "MOSFET model selection with non-numeric suffix", + "M1 D G S B NMOS L=0.5u W=10u", + "M2 D G S B NMOS L=5u W=10u", + ".model NMOS.small NMOS level=1 lmin=0.1u lmax=1u", + ".model NMOS.large NMOS level=1 lmin=1u lmax=10u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var mosfet1 = netlist.Circuit["m1"] as Mosfet1; + Assert.NotNull(mosfet1); + + var mosfet2 = netlist.Circuit["m2"] as Mosfet1; + Assert.NotNull(mosfet2); + + Assert.NotNull(netlist.Circuit["NMOS.small"]); + Assert.NotNull(netlist.Circuit["NMOS.large"]); + } + + [Fact] + public void MosfetSelectsModelWithArbitrarySuffix() + { + var netlist = GetSpiceSharpModel( + "MOSFET model selection with arbitrary suffix", + "M1 D G S B NMOS L=0.5u W=10u", + "M2 D G S B NMOS L=5u W=10u", + ".model NMOS.hp NMOS level=1 lmin=0.1u lmax=1u", + ".model NMOS.lp NMOS level=1 lmin=1u lmax=10u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var mosfet1 = netlist.Circuit["m1"] as Mosfet1; + Assert.NotNull(mosfet1); + + var mosfet2 = netlist.Circuit["m2"] as Mosfet1; + Assert.NotNull(mosfet2); + } + + [Fact] + public void ModelWithNonSequentialNumericSuffixes() + { + var netlist = GetSpiceSharpModel( + "Model with non-sequential numeric suffixes", + "M1 D G S B NMOS L=0.5u W=10u", + "M2 D G S B NMOS L=5u W=10u", + ".model NMOS.3 NMOS level=1 lmin=0.1u lmax=1u", + ".model NMOS.7 NMOS level=1 lmin=1u lmax=10u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var mosfet1 = netlist.Circuit["m1"] as Mosfet1; + Assert.NotNull(mosfet1); + + var mosfet2 = netlist.Circuit["m2"] as Mosfet1; + Assert.NotNull(mosfet2); + } + + [Fact] + public void FallsBackToBaseModelWhenNoSuffixedModelMatches() + { + // L=100u exceeds both suffixed models' lmax, should fall back to base NMOS + var netlist = GetSpiceSharpModel( + "Falls back to base model when no suffixed model matches", + "M1 D G S B NMOS L=100u W=100u", + ".model NMOS.small NMOS level=1 lmin=0.1u lmax=1u", + ".model NMOS.large NMOS level=1 lmin=1u lmax=10u", + ".model NMOS NMOS level=1", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var mosfet = netlist.Circuit["m1"] as Mosfet1; + Assert.NotNull(mosfet); + } + + [Fact] + public void DiodeSelectsModelWithNonNumericSuffix() + { + var netlist = GetSpiceSharpModel( + "Diode model selection with non-numeric suffix", + "D1 A K DMOD L=0.5u W=5u", + "D2 A K DMOD L=5u W=50u", + ".model DMOD.fast D lmin=0.1u lmax=1u wmin=1u wmax=10u", + ".model DMOD.slow D lmin=1u lmax=10u wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var diode1 = netlist.Circuit["d1"] as Diode; + Assert.NotNull(diode1); + + var diode2 = netlist.Circuit["d2"] as Diode; + Assert.NotNull(diode2); + } + + [Fact] + public void ResistorSelectsModelWithNonNumericSuffix() + { + var netlist = GetSpiceSharpModel( + "Resistor model selection with non-numeric suffix", + "R1 1 0 RMOD L=0.5u W=5u", + "R2 1 0 RMOD L=5u W=50u", + ".model RMOD.thin R RSH=1 lmin=0.1u lmax=1u wmin=1u wmax=10u", + ".model RMOD.wide R RSH=1 lmin=1u lmax=10u wmin=10u wmax=100u", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var resistor1 = netlist.Circuit["r1"] as Resistor; + Assert.NotNull(resistor1); + + var resistor2 = netlist.Circuit["r2"] as Resistor; + Assert.NotNull(resistor2); + } + + #endregion + + #region Edge Cases + + [Fact] + public void MosfetWithoutLWParametersMatchesFirstSuffixedModel() + { + // Without L/W, predicate is null — first suffixed model is selected (not base model) + var netlist = GetSpiceSharpModel( + "MOSFET without L/W matches first suffixed model", + "M1 D G S B NMOS", + ".model NMOS.0 NMOS level=1 lmin=0.1u lmax=1u", + ".model NMOS NMOS level=1", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var mosfet = netlist.Circuit["m1"] as Mosfet1; + Assert.NotNull(mosfet); + } + + [Fact] + public void ModelWithOnlyLminConstraint() + { + // RMOD.0 has lmin=1u: R1 L=0.5u fails (below min), falls back to base RMOD + // R2 L=5u passes (above min), matches RMOD.0 + var netlist = GetSpiceSharpModel( + "Model with only lmin constraint", + "R1 1 0 RMOD L=0.5u W=1u", + "R2 1 0 RMOD L=5u W=1u", + ".model RMOD.0 R RSH=1 lmin=1u", + ".model RMOD R RSH=1", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var resistor1 = netlist.Circuit["r1"] as Resistor; + Assert.NotNull(resistor1); + + var resistor2 = netlist.Circuit["r2"] as Resistor; + Assert.NotNull(resistor2); + } + + [Fact] + public void ModelWithOnlyLmaxConstraint() + { + // CMOD.0 has lmax=1u: C1 L=0.5u passes (below max), matches CMOD.0 + // C2 L=5u fails (above max), falls back to base CMOD + var netlist = GetSpiceSharpModel( + "Model with only lmax constraint", + "C1 1 0 CMOD L=0.5u W=1u", + "C2 1 0 CMOD L=5u W=1u", + ".model CMOD.0 C CJ=1e-6 lmax=1u", + ".model CMOD C CJ=1e-6", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var capacitor1 = netlist.Circuit["c1"] as Capacitor; + Assert.NotNull(capacitor1); + + var capacitor2 = netlist.Circuit["c2"] as Capacitor; + Assert.NotNull(capacitor2); + } + + [Fact] + public void ModelWithOnlyWminConstraint() + { + // NMOS.0 has wmin=1u: M1 W=0.5u fails (below min), falls back to base NMOS + // M2 W=5u passes (above min), matches NMOS.0 + var netlist = GetSpiceSharpModel( + "Model with only wmin constraint", + "M1 D G S B NMOS L=1u W=0.5u", + "M2 D G S B NMOS L=1u W=5u", + ".model NMOS.0 NMOS level=1 wmin=1u", + ".model NMOS NMOS level=1", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var mosfet1 = netlist.Circuit["m1"] as Mosfet1; + Assert.NotNull(mosfet1); + + var mosfet2 = netlist.Circuit["m2"] as Mosfet1; + Assert.NotNull(mosfet2); + } + + [Fact] + public void ModelWithOnlyWmaxConstraint() + { + // QMOD.0 has wmax=10u: Q1 W=5u passes (below max), matches QMOD.0 + // Q2 W=50u fails (above max), falls back to base QMOD + var netlist = GetSpiceSharpModel( + "Model with only wmax constraint", + "Q1 C B E QMOD W=5u", + "Q2 C B E QMOD W=50u", + ".model QMOD.0 NPN wmax=10u", + ".model QMOD NPN", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var bjt1 = netlist.Circuit["q1"] as BipolarJunctionTransistor; + Assert.NotNull(bjt1); + + var bjt2 = netlist.Circuit["q2"] as BipolarJunctionTransistor; + Assert.NotNull(bjt2); + } + + [Fact] + public void MultipleModelsWithOverlappingRanges() + { + // D1 matches both DMOD.0 and DMOD.1; first match wins + var netlist = GetSpiceSharpModel( + "Multiple models with overlapping ranges", + "D1 A K DMOD L=0.8u W=8u", + ".model DMOD.0 D lmin=0.1u lmax=1u wmin=1u wmax=10u", + ".model DMOD.1 D lmin=0.5u lmax=2u wmin=5u wmax=20u", + ".model DMOD D", + ".END"); + + Assert.NotNull(netlist); + Assert.False(netlist.ValidationResult.HasError); + + var diode1 = netlist.Circuit["d1"] as Diode; + Assert.NotNull(diode1); + } + + #endregion + } +} diff --git a/src/SpiceSharpParser.IntegrationTests/Examples/Circuits/MosfetExample3.cir b/src/SpiceSharpParser.IntegrationTests/Examples/Circuits/MosfetExample3.cir new file mode 100644 index 00000000..cae39dda --- /dev/null +++ b/src/SpiceSharpParser.IntegrationTests/Examples/Circuits/MosfetExample3.cir @@ -0,0 +1,5 @@ +Mosfet circuit +Md 0 1 2 3 my-pmos +.model my-pmos.1 pmos(level = 49 lmin=0.18u) +.model my-pmos.2 pmos(level = 49 lmin=1.18u) +.END diff --git a/src/SpiceSharpParser.IntegrationTests/SpiceSharpParser.IntegrationTests.csproj b/src/SpiceSharpParser.IntegrationTests/SpiceSharpParser.IntegrationTests.csproj index 95097b93..2b1b40db 100644 --- a/src/SpiceSharpParser.IntegrationTests/SpiceSharpParser.IntegrationTests.csproj +++ b/src/SpiceSharpParser.IntegrationTests/SpiceSharpParser.IntegrationTests.csproj @@ -97,6 +97,9 @@ Always + + Always + Always diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/IModelsRegistry.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/IModelsRegistry.cs index c66a878b..fd5fbd8a 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/IModelsRegistry.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/IModelsRegistry.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using SpiceSharp.Entities; -using SpiceSharp.Simulations; using SpiceSharpParser.Common; using SpiceSharpParser.Models.Netlist.Spice.Objects; @@ -9,11 +8,11 @@ namespace SpiceSharpParser.ModelReaders.Netlist.Spice.Context.Models { public interface IModelsRegistry { - void SetModel(Entity entity, ISimulationWithEvents simulation, Parameter modelNameParameter, string exceptionMessage, Action setModelAction, IReadingContext context); + void SetModel(Entity entity, Func predicate, ISimulationWithEvents simulation, Parameter modelNameParameter, string exceptionMessage, Action setModelAction, IReadingContext context); Model FindModel(string modelName); - IEntity FindModelEntity(string modelName); + IEntity FindModelEntity(string modelName, Func predicate); void RegisterModelInstance(Model model); diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/Model.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/Model.cs index b78ad212..3841e6df 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/Model.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/Model.cs @@ -1,10 +1,13 @@ -using SpiceSharp.Entities; +using SpiceSharp.Entities; using SpiceSharp.ParameterSets; +using System.Collections.Generic; namespace SpiceSharpParser.ModelReaders.Netlist.Spice.Context.Models { public class Model { + private readonly Dictionary _selectionParameters = new Dictionary(System.StringComparer.OrdinalIgnoreCase); + public Model(string name, IEntity entity, IParameterSet parameters) { Name = name; @@ -17,5 +20,31 @@ public Model(string name, IEntity entity, IParameterSet parameters) public IEntity Entity { get; } public IParameterSet Parameters { get; } + + /// + /// Gets all selection parameters as a read-only dictionary. + /// + public IReadOnlyDictionary SelectionParameters => _selectionParameters; + + /// + /// Sets a selection parameter for model matching. + /// + /// The parameter name. + /// The parameter value. + public void SetSelectionParameter(string parameterName, double value) + { + _selectionParameters[parameterName] = value; + } + + /// + /// Gets a selection parameter for model matching. + /// + /// The parameter name. + /// The parameter value. + /// True if the parameter exists. + public bool TryGetSelectionParameter(string parameterName, out double value) + { + return _selectionParameters.TryGetValue(parameterName, out value); + } } } diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/StochasticModelsRegistry.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/StochasticModelsRegistry.cs index 06c2543b..b8b5cc91 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/StochasticModelsRegistry.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/StochasticModelsRegistry.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.ExceptionServices; using SpiceSharp.Entities; using SpiceSharpParser.Common; using SpiceSharpParser.Common.Validation; @@ -231,9 +232,9 @@ public Dictionary GetStochasticModelLotParameter return null; } - public void SetModel(Entity entity, ISimulationWithEvents simulation, Parameter modelNameParameter, string exceptionMessage, Action setModelAction, IReadingContext context) + public void SetModel(Entity entity, Func predicate, ISimulationWithEvents simulation, Parameter modelNameParameter, string exceptionMessage, Action setModelAction, IReadingContext context) { - var model = FindModelEntity(modelNameParameter.Value); + var model = FindModelWithPredicate(modelNameParameter.Value, predicate); if (model == null) { @@ -241,7 +242,7 @@ public void SetModel(Entity entity, ISimulationWithEvents simulation, Parameter return; } - var stochasticModel = ProvideStochasticModel(entity.Name, simulation, new Model(model.Name, model, model.ParameterSets.First())); + var stochasticModel = ProvideStochasticModel(entity.Name, simulation, model); setModelAction(stochasticModel); if (stochasticModel != null) @@ -255,28 +256,39 @@ public void SetModel(Entity entity, ISimulationWithEvents simulation, Parameter public Model FindModel(string modelName) { - foreach (var generator in NamesGenerators) - { - var modelNameToSearch = generator.GenerateObjectName(modelName); - - if (AllModels.TryGetValue(modelNameToSearch, out var model)) - { - return model; - } - } + return FindModelWithPredicate(modelName, null); + } - return null; + public IEntity FindModelEntity(string modelName, Func predicate) + { + return FindModelWithPredicate(modelName, predicate)?.Entity; } - public IEntity FindModelEntity(string modelName) + private Model FindModelWithPredicate(string modelName, Func predicate) { + var comparison = IsModelNameCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + foreach (var generator in NamesGenerators) { var modelNameToSearch = generator.GenerateObjectName(modelName); + var prefix = modelNameToSearch + "."; + // Check suffixed models (e.g., MODELNAME.small, MODELNAME.0) + foreach (var kvp in AllModels) + { + if (kvp.Key.StartsWith(prefix, comparison) && kvp.Key.Length > prefix.Length) + { + if (predicate == null || predicate(kvp.Value)) + { + return kvp.Value; + } + } + } + + // Fall back to exact match (base model without suffix) if (AllModels.TryGetValue(modelNameToSearch, out var model)) { - return model.Entity; + return model; } } @@ -296,4 +308,4 @@ public IModelsRegistry CreateChildRegistry(List generators) return result; } } -} \ No newline at end of file +} diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/ComponentGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/ComponentGenerator.cs index 815f2128..5bb5f925 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/ComponentGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/ComponentGenerator.cs @@ -1,7 +1,9 @@ using System; +using System.Linq; using SpiceSharp.Entities; using SpiceSharpParser.Common.Validation; using SpiceSharpParser.ModelReaders.Netlist.Spice.Context; +using SpiceSharpParser.ModelReaders.Netlist.Spice.Context.Models; using SpiceSharpParser.Models.Netlist.Spice.Objects; using SpiceSharpParser.Models.Netlist.Spice.Objects.Parameters; @@ -11,6 +13,55 @@ public abstract class ComponentGenerator : IComponentGenerator { public abstract IEntity Generate(string componentIdentifier, string originalName, string type, ParameterCollection parameters, IReadingContext context); + /// + /// Creates a predicate that checks whether a model's selection parameters + /// satisfy min/max range constraints for the given instance parameter values. + /// For each (name, value) pair, checks model's {name}min and {name}max. + /// + public static Func CreateRangePredicate(params (string name, double? value)[] parameters) + { + if (parameters.All(p => p.value == null)) + { + return null; + } + + return model => + { + foreach (var (name, value) in parameters) + { + if (!value.HasValue) + { + continue; + } + + if (model.TryGetSelectionParameter(name + "min", out double min) && min > 0 && value.Value < min) + { + return false; + } + + if (model.TryGetSelectionParameter(name + "max", out double max) && max > 0 && value.Value > max) + { + return false; + } + } + + return true; + }; + } + + /// + /// Gets the value of a named assignment parameter from the collection, or null if not found. + /// + public static double? GetAssignmentParameterValue(string name, ParameterCollection parameters, IReadingContext context) + { + var parameter = parameters.FirstOrDefault(p => p is AssignmentParameter ap && ap.Name.ToLower() == name); + if (parameter is AssignmentParameter ap) + { + return context.Evaluator.EvaluateDouble(ap.Value); + } + return null; + } + protected void SetParameters(IReadingContext context, IEntity entity, ParameterCollection parameters) { foreach (Parameter parameter in parameters) diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/RLCKGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/RLCKGenerator.cs index 6d20abdd..705aa089 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/RLCKGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/RLCKGenerator.cs @@ -205,8 +205,12 @@ protected IComponent GenerateCap(string name, ParameterCollection parameters, IR { context.SimulationPreparations.ExecuteActionBeforeSetup((simulation) => { + double? l = GetAssignmentParameterValue("l", parameters, context); + double? w = GetAssignmentParameterValue("w", parameters, context); + context.ModelsRegistry.SetModel( capacitor, + CreateRangePredicate(("l", l), ("w", w)), simulation, parameters.Get(2), $"Could not find model {parameters.Get(2)} for capacitor {name}", @@ -249,7 +253,9 @@ protected IComponent GenerateCap(string name, ParameterCollection parameters, IR if (modelBased) { - var model = context.ModelsRegistry.FindModelEntity(parameters.Get(2).Value); + double? l = GetAssignmentParameterValue("l", parameters, context); + double? w = GetAssignmentParameterValue("w", parameters, context); + var model = context.ModelsRegistry.FindModelEntity(parameters.Get(2).Value, CreateRangePredicate(("l", l), ("w", w))); if (tcParameterAssignment.Values.Count == 2) { @@ -417,8 +423,12 @@ protected IEntity GenerateRes(string name, ParameterCollection parameters, IRead { context.SimulationPreparations.ExecuteActionBeforeSetup((simulation) => { + double? l = GetAssignmentParameterValue("l", parameters, context); + double? w = GetAssignmentParameterValue("w", parameters, context); + context.ModelsRegistry.SetModel( res, + CreateRangePredicate(("l", l), ("w", w)), simulation, something, $"Could not find model {something} for resistor {name}", @@ -477,8 +487,12 @@ protected IEntity GenerateRes(string name, ParameterCollection parameters, IRead // Ignore tc parameter on resistor ... context.SimulationPreparations.ExecuteActionBeforeSetup((simulation) => { + double? l = GetAssignmentParameterValue("l", parameters, context); + double? w = GetAssignmentParameterValue("w", parameters, context); + context.ModelsRegistry.SetModel( res, + CreateRangePredicate(("l", l), ("w", w)), simulation, modelNameParameter, $"Could not find model {modelNameParameter} for resistor {name}", @@ -598,5 +612,6 @@ private string MultiplyIfNeeded(string expression, string mExpression, string nE return expression; } + } } \ No newline at end of file diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/BipolarJunctionTransistorGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/BipolarJunctionTransistorGenerator.cs index bf5d8bf9..c437fed5 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/BipolarJunctionTransistorGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/BipolarJunctionTransistorGenerator.cs @@ -2,6 +2,7 @@ using SpiceSharp.Entities; using SpiceSharpParser.Common.Validation; using SpiceSharpParser.ModelReaders.Netlist.Spice.Context; +using SpiceSharpParser.ModelReaders.Netlist.Spice.Readers.EntityGenerators.Components; using SpiceSharpParser.Models.Netlist.Spice.Objects; using SpiceSharpParser.Models.Netlist.Spice.Objects.Parameters; using System.Linq; @@ -38,8 +39,12 @@ public IEntity Generate(string componentIdentifier, string originalName, string context.CreateNodes(bjt, parameters.Take(BipolarJunctionTransistor.PinCount)); context.SimulationPreparations.ExecuteActionBeforeSetup((simulation) => { + double? l = ComponentGenerator.GetAssignmentParameterValue("l", parameters, context); + double? w = ComponentGenerator.GetAssignmentParameterValue("w", parameters, context); + context.ModelsRegistry.SetModel( bjt, + ComponentGenerator.CreateRangePredicate(("l", l), ("w", w)), simulation, parameters.Get(4), $"Could not find model {parameters.Get(4)} for BJT {originalName}", @@ -93,6 +98,10 @@ public IEntity Generate(string componentIdentifier, string originalName, string context.SetParameter(bjt, "icvbe", asg.Values[0]); } } + else if (asg.Name.ToLower() == "l" || asg.Name.ToLower() == "w") + { + // Skip L and W parameters - they are used for model selection only + } else { context.SetParameter(bjt, asg.Name, asg); @@ -102,5 +111,6 @@ public IEntity Generate(string componentIdentifier, string originalName, string return bjt; } + } } \ No newline at end of file diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/DiodeGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/DiodeGenerator.cs index d90f53fb..f6e75b85 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/DiodeGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/DiodeGenerator.cs @@ -1,8 +1,10 @@ using SpiceSharp.Components; using SpiceSharp.Entities; using SpiceSharpParser.ModelReaders.Netlist.Spice.Context; +using SpiceSharpParser.ModelReaders.Netlist.Spice.Readers.EntityGenerators.Components; using SpiceSharpParser.Models.Netlist.Spice.Objects; using SpiceSharpParser.Models.Netlist.Spice.Objects.Parameters; +using System.Linq; namespace SpiceSharpParser.ModelReaders.Netlist.Spice.Readers.EntityGenerators.Components.Semiconductors { @@ -20,8 +22,12 @@ public IEntity Generate(string componentIdentifier, string originalName, string context.SimulationPreparations.ExecuteActionBeforeSetup((simulation) => { + double? l = ComponentGenerator.GetAssignmentParameterValue("l", parameters, context); + double? w = ComponentGenerator.GetAssignmentParameterValue("w", parameters, context); + context.ModelsRegistry.SetModel( diode, + ComponentGenerator.CreateRangePredicate(("l", l), ("w", w)), simulation, parameters.Get(2), $"Could not find model {parameters.Get(2)} for diode {originalName}", @@ -51,6 +57,11 @@ public IEntity Generate(string componentIdentifier, string originalName, string if (parameters[i] is AssignmentParameter asg) { + // Skip L and W parameters - they are used for model selection only + if (asg.Name.ToLower() == "l" || asg.Name.ToLower() == "w") + { + continue; + } context.SetParameter(diode, asg.Name, asg); } @@ -70,5 +81,6 @@ public IEntity Generate(string componentIdentifier, string originalName, string return diode; } + } } \ No newline at end of file diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/JFETGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/JFETGenerator.cs index 39ba9dd3..9b3b093e 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/JFETGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/JFETGenerator.cs @@ -1,8 +1,10 @@ using SpiceSharp.Components; using SpiceSharp.Entities; using SpiceSharpParser.ModelReaders.Netlist.Spice.Context; +using SpiceSharpParser.ModelReaders.Netlist.Spice.Readers.EntityGenerators.Components; using SpiceSharpParser.Models.Netlist.Spice.Objects; using SpiceSharpParser.Models.Netlist.Spice.Objects.Parameters; +using System.Linq; namespace SpiceSharpParser.ModelReaders.Netlist.Spice.Readers.EntityGenerators.Components.Semiconductors { @@ -20,8 +22,12 @@ public IEntity Generate(string componentIdentifier, string originalName, string context.SimulationPreparations.ExecuteActionBeforeSetup((simulation) => { + double? l = ComponentGenerator.GetAssignmentParameterValue("l", parameters, context); + double? w = ComponentGenerator.GetAssignmentParameterValue("w", parameters, context); + context.ModelsRegistry.SetModel( jfet, + ComponentGenerator.CreateRangePredicate(("l", l), ("w", w)), simulation, parameters.Get(3), $"Could not find model {parameters.Get(3)} for JFET {originalName}", @@ -63,6 +69,10 @@ public IEntity Generate(string componentIdentifier, string originalName, string { context.SetParameter(jfet, "area", asg.Value); } + else if (asg.Name.ToLower() == "l" || asg.Name.ToLower() == "w") + { + // Skip L and W parameters - they are used for model selection only + } else { context.SetParameter(jfet, asg.Name, asg.Value); @@ -77,5 +87,6 @@ public IEntity Generate(string componentIdentifier, string originalName, string return jfet; } + } } \ No newline at end of file diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/MosfetGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/MosfetGenerator.cs index 3be4c2fc..075e8252 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/MosfetGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/Semiconductors/MosfetGenerator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using SpiceSharp.Components; using SpiceSharp.Entities; using SpiceSharpParser.Common.Validation; @@ -95,8 +96,12 @@ public override IEntity Generate(string componentIdentifier, string originalName context.SimulationPreparations.ExecuteActionBeforeSetup((simulation) => { + double? l = GetAssignmentParameterValue("l", parameters, context); + double? w = GetAssignmentParameterValue("w", parameters, context); + context.ModelsRegistry.SetModel( mosfetDetails.Mosfet, + CreateRangePredicate(("l", l), ("w", w)), simulation, modelNameParameter, $"Could not find model {modelNameParameter} for mosfet {componentIdentifier}", diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/SwitchGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/SwitchGenerator.cs index e68ced45..da0cd6a0 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/SwitchGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Components/SwitchGenerator.cs @@ -112,6 +112,7 @@ protected IEntity GenerateVoltageSwitch(string name, ParameterCollection paramet { context.ModelsRegistry.SetModel( vsw, + null, simulation, parameters.Get(4), $"Could not find model {parameters.Get(4)} for voltage switch {name}", @@ -270,6 +271,7 @@ private IEntity GenerateCurrentSwitch(string name, ParameterCollection parameter { context.ModelsRegistry.SetModel( csw, + null, simulation, parameters.Get(3), $"Could not find model {parameters.Get(3)} for current switch {name}", diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/IModelGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/IModelGenerator.cs index 37544c3f..4c4dcff9 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/IModelGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/IModelGenerator.cs @@ -7,4 +7,9 @@ public interface IModelGenerator { Model Generate(string id, string type, SpiceSharpParser.Models.Netlist.Spice.Objects.ParameterCollection parameters, IReadingContext context); } + + public interface ICustomModelGenerator : IModelGenerator + { + Context.Models.Model Process(Context.Models.Model model, IModelsRegistry models); + } } \ No newline at end of file diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/BipolarModelGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/BipolarModelGenerator.cs index 6e6e8c57..2cdde50a 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/BipolarModelGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/BipolarModelGenerator.cs @@ -8,20 +8,21 @@ public class BipolarModelGenerator : ModelGenerator { public override Model Generate(string id, string type, SpiceSharpParser.Models.Netlist.Spice.Objects.ParameterCollection parameters, IReadingContext context) { - BipolarJunctionTransistorModel model = new BipolarJunctionTransistorModel(id); + BipolarJunctionTransistorModel bjtModel = new BipolarJunctionTransistorModel(id); if (type.ToLower() == "npn") { - model.SetParameter("npn", true); + bjtModel.SetParameter("npn", true); } else if (type.ToLower() == "pnp") { - model.SetParameter("pnp", true); + bjtModel.SetParameter("pnp", true); } - SetParameters(context, model, parameters); + var contextModel = new Model(id, bjtModel, bjtModel.Parameters); + SetParameters(context, bjtModel, contextModel, parameters); - return new Model(id, model, model.Parameters); + return contextModel; } } } \ No newline at end of file diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/DiodeModelGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/DiodeModelGenerator.cs index 3e4b0d5b..3052b06d 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/DiodeModelGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/DiodeModelGenerator.cs @@ -9,10 +9,11 @@ public class DiodeModelGenerator : ModelGenerator { public override Model Generate(string id, string type, ParameterCollection parameters, IReadingContext context) { - var model = new DiodeModel(id); - SetParameters(context, model, parameters); + var diodeModel = new DiodeModel(id); + var contextModel = new Model(id, diodeModel, diodeModel.Parameters); + SetParameters(context, diodeModel, contextModel, parameters); - return new Model(id, model, model.Parameters); + return contextModel; } } } \ No newline at end of file diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/JFETModelGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/JFETModelGenerator.cs index 9ba3f6f0..c869cafb 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/JFETModelGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/JFETModelGenerator.cs @@ -8,15 +8,16 @@ public class JFETModelGenerator : ModelGenerator { public override Context.Models.Model Generate(string id, string type, ParameterCollection parameters, IReadingContext context) { - var model = new JFETModel(id); + var jfetModel = new JFETModel(id); switch (type.ToLower()) { - case "pjf": model.SetParameter("pjf", true); break; - case "njf": model.SetParameter("njf", true); break; + case "pjf": jfetModel.SetParameter("pjf", true); break; + case "njf": jfetModel.SetParameter("njf", true); break; } - SetParameters(context, model, parameters); - return new Context.Models.Model(id, model, model.Parameters); + var contextModel = new Context.Models.Model(id, jfetModel, jfetModel.Parameters); + SetParameters(context, jfetModel, contextModel, parameters); + return contextModel; } } } \ No newline at end of file diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/ModelGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/ModelGenerator.cs index ff456d93..7b66b09f 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/ModelGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/ModelGenerator.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Collections.Generic; using SpiceSharp.Entities; using SpiceSharpParser.Common.Validation; using SpiceSharpParser.ModelReaders.Netlist.Spice.Context; @@ -9,14 +10,41 @@ namespace SpiceSharpParser.ModelReaders.Netlist.Spice.Readers.EntityGenerators.M { public abstract class ModelGenerator : IModelGenerator { + /// + /// Parameters to skip when setting on the SpiceSharp entity (because they are selection-only). + /// Subclasses can override to add custom selection parameter names. + /// + protected virtual ISet EntitySkipParameters { get; } = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "lmin", "lmax", "wmin", "wmax" + }; + public abstract Context.Models.Model Generate(string id, string type, ParameterCollection parameters, IReadingContext context); - protected void SetParameters(IReadingContext context, IEntity entity, ParameterCollection parameters, bool onload = true) + protected void SetParameters(IReadingContext context, IEntity entity, Context.Models.Model model, ParameterCollection parameters, bool onload = true) { foreach (Parameter parameter in parameters) { if (parameter is AssignmentParameter ap) { + // Store ALL parameters on Model for predicate-based selection + try + { + var value = context.Evaluator.EvaluateDouble(ap.Value); + model.SetSelectionParameter(ap.Name, value); + } + catch + { + // Evaluation error — will be reported below when setting on entity + } + + // Skip entity-setting for params in the skip set + if (EntitySkipParameters.Contains(ap.Name)) + { + continue; + } + + // Set on SpiceSharp entity try { context.SetParameter(entity, ap.Name, ap.Value, onload); @@ -28,9 +56,9 @@ protected void SetParameters(IReadingContext context, IEntity entity, ParameterC } else { - context.Result.ValidationResult.AddError(ValidationEntrySource.Reader, $"Unsupported parameter: {parameter}", parameter.LineInfo); + context.Result.ValidationResult.AddError(ValidationEntrySource.Reader, $"Unsupported parameter: {parameter}", parameter.LineInfo); } } } } -} \ No newline at end of file +} diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/MosfetModelGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/MosfetModelGenerator.cs index 4bec0787..aaee2ea0 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/MosfetModelGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/MosfetModelGenerator.cs @@ -146,8 +146,8 @@ public override Context.Models.Model Generate(string id, string type, ParameterC return null; } - // Read all the parameters - SetParameters(context, model.Entity, clonedParameters); + // Read all the parameters and store on both entity and model + SetParameters(context, model.Entity, model, clonedParameters); return model; } diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/RLCModelGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/RLCModelGenerator.cs index 2294b63d..6b1fa6fe 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/RLCModelGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/RLCModelGenerator.cs @@ -12,14 +12,16 @@ public override Context.Models.Model Generate(string id, string type, ParameterC { case "res": case "r": - var model = new ResistorModel(id); - SetParameters(context, model, parameters); - return new Context.Models.Model(id, model, model.Parameters); + var resistorModel = new ResistorModel(id); + var resistorContextModel = new Context.Models.Model(id, resistorModel, resistorModel.Parameters); + SetParameters(context, resistorModel, resistorContextModel, parameters); + return resistorContextModel; case "c": - var model2 = new CapacitorModel(id); - SetParameters(context, model2, parameters); - return new Context.Models.Model(id, model2, model2.Parameters); + var capacitorModel = new CapacitorModel(id); + var capacitorContextModel = new Context.Models.Model(id, capacitorModel, capacitorModel.Parameters); + SetParameters(context, capacitorModel, capacitorContextModel, parameters); + return capacitorContextModel; } return null; diff --git a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/SwitchModelGenerator.cs b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/SwitchModelGenerator.cs index 1ce63cf1..3e2b78f7 100644 --- a/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/SwitchModelGenerator.cs +++ b/src/SpiceSharpParser/ModelReaders/Netlist/Spice/Readers/EntityGenerators/Models/SwitchModelGenerator.cs @@ -11,23 +11,28 @@ public override Context.Models.Model Generate(string id, string type, ParameterC { switch (type.ToLower()) { - case "sw": var model = new VoltageSwitchModel(id); - SetParameters(context, model, parameters); - return new Context.Models.Model(id, model, model.Parameters); + case "sw": + var vsModel = new VoltageSwitchModel(id); + var vsContextModel = new Context.Models.Model(id, vsModel, vsModel.Parameters); + SetParameters(context, vsModel, vsContextModel, parameters); + return vsContextModel; case "csw": - var model2 = new CurrentSwitchModel(id); - SetParameters(context, model2, parameters); - return new Context.Models.Model(id, model2, model2.Parameters); + var csModel = new CurrentSwitchModel(id); + var csContextModel = new Context.Models.Model(id, csModel, csModel.Parameters); + SetParameters(context, csModel, csContextModel, parameters); + return csContextModel; case "vswitch": var vSwitchModel = new VSwitchModel(id); - SetParameters(context, vSwitchModel, parameters); - return new Context.Models.Model(id, vSwitchModel, vSwitchModel.Parameters); + var vSwitchContextModel = new Context.Models.Model(id, vSwitchModel, vSwitchModel.Parameters); + SetParameters(context, vSwitchModel, vSwitchContextModel, parameters); + return vSwitchContextModel; case "iswitch": var iSwitchModel = new ISwitchModel(id); - SetParameters(context, iSwitchModel, parameters); - return new Context.Models.Model(id, iSwitchModel, iSwitchModel.Parameters); + var iSwitchContextModel = new Context.Models.Model(id, iSwitchModel, iSwitchModel.Parameters); + SetParameters(context, iSwitchModel, iSwitchContextModel, parameters); + return iSwitchContextModel; } return null; diff --git a/src/SpiceSharpParser/SpiceSharpParser.csproj b/src/SpiceSharpParser/SpiceSharpParser.csproj index fdbcaffb..9b852cfb 100644 --- a/src/SpiceSharpParser/SpiceSharpParser.csproj +++ b/src/SpiceSharpParser/SpiceSharpParser.csproj @@ -5,7 +5,7 @@ netstandard2.0;net8.0 SpiceSharp https://github.com/SpiceSharp/SpiceSharpParser - Copyright 2025 + Copyright 2026 true https://github.com/SpiceSharp/SpiceSharpParser @@ -22,7 +22,7 @@ MIT latest - 3.2.8 + 3.2.9