Skip to content

Commit 5437ace

Browse files
Make it more generic
1 parent 500f5aa commit 5437ace

18 files changed

Lines changed: 275 additions & 273 deletions

src/SpiceSharpParser.IntegrationTests/Components/ModelDimensionTests.cs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,136 @@ public void JFETWithOnlyLengthParameter()
417417

418418
#endregion
419419

420+
#region Non-Numeric Model Suffix Tests
421+
422+
[Fact]
423+
public void MosfetSelectsModelWithNonNumericSuffix()
424+
{
425+
var netlist = GetSpiceSharpModel(
426+
"MOSFET model selection with non-numeric suffix",
427+
"M1 D G S B NMOS L=0.5u W=10u",
428+
"M2 D G S B NMOS L=5u W=10u",
429+
".model NMOS.small NMOS level=1 lmin=0.1u lmax=1u",
430+
".model NMOS.large NMOS level=1 lmin=1u lmax=10u",
431+
".END");
432+
433+
Assert.NotNull(netlist);
434+
Assert.False(netlist.ValidationResult.HasError);
435+
436+
var mosfet1 = netlist.Circuit["m1"] as Mosfet1;
437+
Assert.NotNull(mosfet1);
438+
439+
var mosfet2 = netlist.Circuit["m2"] as Mosfet1;
440+
Assert.NotNull(mosfet2);
441+
442+
Assert.NotNull(netlist.Circuit["NMOS.small"]);
443+
Assert.NotNull(netlist.Circuit["NMOS.large"]);
444+
}
445+
446+
[Fact]
447+
public void MosfetSelectsModelWithArbitrarySuffix()
448+
{
449+
var netlist = GetSpiceSharpModel(
450+
"MOSFET model selection with arbitrary suffix",
451+
"M1 D G S B NMOS L=0.5u W=10u",
452+
"M2 D G S B NMOS L=5u W=10u",
453+
".model NMOS.hp NMOS level=1 lmin=0.1u lmax=1u",
454+
".model NMOS.lp NMOS level=1 lmin=1u lmax=10u",
455+
".END");
456+
457+
Assert.NotNull(netlist);
458+
Assert.False(netlist.ValidationResult.HasError);
459+
460+
var mosfet1 = netlist.Circuit["m1"] as Mosfet1;
461+
Assert.NotNull(mosfet1);
462+
463+
var mosfet2 = netlist.Circuit["m2"] as Mosfet1;
464+
Assert.NotNull(mosfet2);
465+
}
466+
467+
[Fact]
468+
public void ModelWithNonSequentialNumericSuffixes()
469+
{
470+
var netlist = GetSpiceSharpModel(
471+
"Model with non-sequential numeric suffixes",
472+
"M1 D G S B NMOS L=0.5u W=10u",
473+
"M2 D G S B NMOS L=5u W=10u",
474+
".model NMOS.3 NMOS level=1 lmin=0.1u lmax=1u",
475+
".model NMOS.7 NMOS level=1 lmin=1u lmax=10u",
476+
".END");
477+
478+
Assert.NotNull(netlist);
479+
Assert.False(netlist.ValidationResult.HasError);
480+
481+
var mosfet1 = netlist.Circuit["m1"] as Mosfet1;
482+
Assert.NotNull(mosfet1);
483+
484+
var mosfet2 = netlist.Circuit["m2"] as Mosfet1;
485+
Assert.NotNull(mosfet2);
486+
}
487+
488+
[Fact]
489+
public void FallsBackToBaseModelWhenNoSuffixedModelMatches()
490+
{
491+
var netlist = GetSpiceSharpModel(
492+
"Falls back to base model when no suffixed model matches",
493+
"M1 D G S B NMOS L=100u W=100u",
494+
".model NMOS.small NMOS level=1 lmin=0.1u lmax=1u",
495+
".model NMOS.large NMOS level=1 lmin=1u lmax=10u",
496+
".model NMOS NMOS level=1",
497+
".END");
498+
499+
Assert.NotNull(netlist);
500+
Assert.False(netlist.ValidationResult.HasError);
501+
502+
var mosfet = netlist.Circuit["m1"] as Mosfet1;
503+
Assert.NotNull(mosfet);
504+
}
505+
506+
[Fact]
507+
public void DiodeSelectsModelWithNonNumericSuffix()
508+
{
509+
var netlist = GetSpiceSharpModel(
510+
"Diode model selection with non-numeric suffix",
511+
"D1 A K DMOD L=0.5u W=5u",
512+
"D2 A K DMOD L=5u W=50u",
513+
".model DMOD.fast D lmin=0.1u lmax=1u wmin=1u wmax=10u",
514+
".model DMOD.slow D lmin=1u lmax=10u wmin=10u wmax=100u",
515+
".END");
516+
517+
Assert.NotNull(netlist);
518+
Assert.False(netlist.ValidationResult.HasError);
519+
520+
var diode1 = netlist.Circuit["d1"] as Diode;
521+
Assert.NotNull(diode1);
522+
523+
var diode2 = netlist.Circuit["d2"] as Diode;
524+
Assert.NotNull(diode2);
525+
}
526+
527+
[Fact]
528+
public void ResistorSelectsModelWithNonNumericSuffix()
529+
{
530+
var netlist = GetSpiceSharpModel(
531+
"Resistor model selection with non-numeric suffix",
532+
"R1 1 0 RMOD L=0.5u W=5u",
533+
"R2 1 0 RMOD L=5u W=50u",
534+
".model RMOD.thin R RSH=1 lmin=0.1u lmax=1u wmin=1u wmax=10u",
535+
".model RMOD.wide R RSH=1 lmin=1u lmax=10u wmin=10u wmax=100u",
536+
".END");
537+
538+
Assert.NotNull(netlist);
539+
Assert.False(netlist.ValidationResult.HasError);
540+
541+
var resistor1 = netlist.Circuit["r1"] as Resistor;
542+
Assert.NotNull(resistor1);
543+
544+
var resistor2 = netlist.Circuit["r2"] as Resistor;
545+
Assert.NotNull(resistor2);
546+
}
547+
548+
#endregion
549+
420550
#region Edge Cases
421551

422552
[Fact]

src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/IModelsRegistry.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ namespace SpiceSharpParser.ModelReaders.Netlist.Spice.Context.Models
88
{
99
public interface IModelsRegistry
1010
{
11-
void SetModel(Entity entity, double? l, double? w, ISimulationWithEvents simulation, Parameter modelNameParameter, string exceptionMessage, Action<Model> setModelAction, IReadingContext context);
11+
void SetModel(Entity entity, Func<Model, bool> predicate, ISimulationWithEvents simulation, Parameter modelNameParameter, string exceptionMessage, Action<Model> setModelAction, IReadingContext context);
1212

1313
Model FindModel(string modelName);
1414

15-
IEntity FindModelEntity(string modelName, double? l, double? w);
15+
IEntity FindModelEntity(string modelName, Func<Model, bool> predicate);
1616

1717
void RegisterModelInstance(Model model);
1818

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
using SpiceSharp.Entities;
1+
using SpiceSharp.Entities;
22
using SpiceSharp.ParameterSets;
33
using System.Collections.Generic;
44

55
namespace SpiceSharpParser.ModelReaders.Netlist.Spice.Context.Models
66
{
77
public class Model
88
{
9-
private readonly Dictionary<string, double> _dimensionParameters = new Dictionary<string, double>(System.StringComparer.OrdinalIgnoreCase);
9+
private readonly Dictionary<string, double> _selectionParameters = new Dictionary<string, double>(System.StringComparer.OrdinalIgnoreCase);
1010

1111
public Model(string name, IEntity entity, IParameterSet parameters)
1212
{
@@ -22,24 +22,29 @@ public Model(string name, IEntity entity, IParameterSet parameters)
2222
public IParameterSet Parameters { get; }
2323

2424
/// <summary>
25-
/// Sets a dimension parameter (lmin, lmax, wmin, wmax) for model selection.
25+
/// Gets all selection parameters as a read-only dictionary.
26+
/// </summary>
27+
public IReadOnlyDictionary<string, double> SelectionParameters => _selectionParameters;
28+
29+
/// <summary>
30+
/// Sets a selection parameter for model matching.
2631
/// </summary>
2732
/// <param name="parameterName">The parameter name.</param>
2833
/// <param name="value">The parameter value.</param>
29-
public void SetDimensionParameter(string parameterName, double value)
34+
public void SetSelectionParameter(string parameterName, double value)
3035
{
31-
_dimensionParameters[parameterName] = value;
36+
_selectionParameters[parameterName] = value;
3237
}
3338

3439
/// <summary>
35-
/// Gets a dimension parameter (lmin, lmax, wmin, wmax) for model selection.
40+
/// Gets a selection parameter for model matching.
3641
/// </summary>
3742
/// <param name="parameterName">The parameter name.</param>
3843
/// <param name="value">The parameter value.</param>
3944
/// <returns>True if the parameter exists.</returns>
40-
public bool TryGetDimensionParameter(string parameterName, out double value)
45+
public bool TryGetSelectionParameter(string parameterName, out double value)
4146
{
42-
return _dimensionParameters.TryGetValue(parameterName, out value);
47+
return _selectionParameters.TryGetValue(parameterName, out value);
4348
}
4449
}
4550
}

src/SpiceSharpParser/ModelReaders/Netlist/Spice/Context/Models/StochasticModelsRegistry.cs

Lines changed: 20 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -232,17 +232,17 @@ public Dictionary<Parameter, ParameterRandomness> GetStochasticModelLotParameter
232232
return null;
233233
}
234234

235-
public void SetModel(Entity entity, double? l, double? w, ISimulationWithEvents simulation, Parameter modelNameParameter, string exceptionMessage, Action<Model> setModelAction, IReadingContext context)
235+
public void SetModel(Entity entity, Func<Model, bool> predicate, ISimulationWithEvents simulation, Parameter modelNameParameter, string exceptionMessage, Action<Model> setModelAction, IReadingContext context)
236236
{
237-
var model = FindModelEntity(modelNameParameter.Value, l, w);
237+
var model = FindModelWithPredicate(modelNameParameter.Value, predicate);
238238

239239
if (model == null)
240240
{
241241
context.Result.ValidationResult.AddError(ValidationEntrySource.Reader, exceptionMessage, modelNameParameter.LineInfo);
242242
return;
243243
}
244244

245-
var stochasticModel = ProvideStochasticModel(entity.Name, simulation, new Model(model.Name, model, model.ParameterSets.First()));
245+
var stochasticModel = ProvideStochasticModel(entity.Name, simulation, model);
246246
setModelAction(stochasticModel);
247247

248248
if (stochasticModel != null)
@@ -256,103 +256,45 @@ public void SetModel(Entity entity, double? l, double? w, ISimulationWithEvents
256256

257257
public Model FindModel(string modelName)
258258
{
259-
foreach (var generator in NamesGenerators)
260-
{
261-
var modelNameToSearch = generator.GenerateObjectName(modelName);
262-
263-
if (AllModels.TryGetValue(modelNameToSearch, out var model))
264-
{
265-
return model;
266-
}
267-
268-
// Check for dimension-based model naming convention (e.g., MODELNAME.0, MODELNAME.1)
269-
for (var i = 0; i < AllModels.Count; i++)
270-
{
271-
var key = AllModels.Keys.ElementAt(i);
272-
273-
if (key == modelNameToSearch + "." + i)
274-
{
275-
if (AllModels.TryGetValue(key, out var modelInstance))
276-
{
277-
return modelInstance;
278-
}
279-
}
280-
}
281-
}
259+
return FindModelWithPredicate(modelName, null);
260+
}
282261

283-
return null;
262+
public IEntity FindModelEntity(string modelName, Func<Model, bool> predicate)
263+
{
264+
return FindModelWithPredicate(modelName, predicate)?.Entity;
284265
}
285266

286-
public IEntity FindModelEntity(string modelName, double? l, double? w)
267+
private Model FindModelWithPredicate(string modelName, Func<Model, bool> predicate)
287268
{
269+
var comparison = IsModelNameCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
270+
288271
foreach (var generator in NamesGenerators)
289272
{
290273
var modelNameToSearch = generator.GenerateObjectName(modelName);
274+
var prefix = modelNameToSearch + ".";
291275

292-
for (var i = 0; i < AllModels.Count; i++)
276+
// Check suffixed models (e.g., MODELNAME.small, MODELNAME.0)
277+
foreach (var kvp in AllModels)
293278
{
294-
var key = AllModels.Keys.ElementAt(i);
295-
296-
if (key == modelNameToSearch + "." + i)
279+
if (kvp.Key.StartsWith(prefix, comparison) && kvp.Key.Length > prefix.Length)
297280
{
298-
if (AllModels.TryGetValue(key, out var modelInstance))
281+
if (predicate == null || predicate(kvp.Value))
299282
{
300-
if (HasGoodSize(modelInstance, l, w))
301-
{
302-
return modelInstance.Entity;
303-
}
283+
return kvp.Value;
304284
}
305285
}
306286
}
307287

308-
309-
288+
// Fall back to exact match (base model without suffix)
310289
if (AllModels.TryGetValue(modelNameToSearch, out var model))
311290
{
312-
return model.Entity;
291+
return model;
313292
}
314293
}
315294

316295
return null;
317296
}
318297

319-
private bool HasGoodSize(Model model, double? l, double? w)
320-
{
321-
model.TryGetDimensionParameter("lmin", out double lminValue);
322-
model.TryGetDimensionParameter("lmax", out double lmaxValue);
323-
model.TryGetDimensionParameter("wmin", out double wminValue);
324-
model.TryGetDimensionParameter("wmax", out double wmaxValue);
325-
326-
// Check L dimension
327-
if (l.HasValue)
328-
{
329-
if (lminValue > 0 && l.Value < lminValue)
330-
{
331-
return false;
332-
}
333-
334-
if (lmaxValue > 0 && l.Value > lmaxValue)
335-
{
336-
return false;
337-
}
338-
}
339-
340-
// Check W dimension
341-
if (w.HasValue)
342-
{
343-
if (wminValue > 0 && w.Value < wminValue)
344-
{
345-
return false;
346-
}
347-
if (wmaxValue > 0 && w.Value > wmaxValue)
348-
{
349-
return false;
350-
}
351-
}
352-
353-
return true;
354-
}
355-
356298
public IModelsRegistry CreateChildRegistry(List<INameGenerator> generators)
357299
{
358300
var result = new StochasticModelsRegistry(generators, IsModelNameCaseSensitive)
@@ -366,4 +308,4 @@ public IModelsRegistry CreateChildRegistry(List<INameGenerator> generators)
366308
return result;
367309
}
368310
}
369-
}
311+
}

0 commit comments

Comments
 (0)