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