Skip to content

Commit e0038af

Browse files
Add support for Laplace sources in behavioral modeling
- Introduced `LaplaceOutputKind` enum to differentiate between voltage and current outputs. - Implemented `TryParseInput` methods in `LaplaceSourceParser` to handle input parsing for Laplace sources. - Enhanced `SourceGenerator` to create Laplace function sources, including voltage and current controlled sources. - Updated `VoltageSourceGenerator` and `CurrentSourceWriter` to support Laplace expressions in behavioral definitions. - Modified `SourceWriterHelper` to handle Laplace function sources and integrate them into the C# output. - Expanded documentation to include examples and explanations for using Laplace sources in SPICE syntax. - Clarified limitations and supported forms for Laplace expressions in various articles.
1 parent 77c0820 commit e0038af

25 files changed

Lines changed: 1788 additions & 294 deletions

File tree

src/SpiceSharpParser.IntegrationTests/AnalogBehavioralModeling/LaplaceTests.cs

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -565,33 +565,89 @@ public void When_ELaplaceInvalidOptionIsUsed_Expect_ReaderValidationError(
565565
}
566566

567567
[Fact]
568-
public void When_ValueLaplaceFunctionIsUsed_Expect_ReaderValidationError()
568+
public void When_ValueLaplaceFunctionIsUsed_Expect_DcGainNearOne()
569569
{
570570
var model = GetSpiceSharpModel(
571-
"E VALUE LAPLACE unsupported",
571+
"E VALUE LAPLACE OP",
572572
"VIN in 0 1",
573-
"EBAD out 0 VALUE = {LAPLACE(V(in), 1/(1+s))}",
573+
"EVALUE out 0 VALUE = {LAPLACE(V(in), 1/(1+s))}",
574574
"RLOAD out 0 1k",
575575
".OP",
576576
".SAVE V(out)",
577577
".END");
578578

579-
AssertReaderErrorContains(model, "function syntax");
579+
AssertNoValidationErrors(model);
580+
Assert.IsType<LaplaceVoltageControlledVoltageSource>(model.Circuit["EVALUE"]);
581+
Assert.True(EqualsWithTol(1.0, RunOpSimulation(model, "V(out)")));
580582
}
581583

582584
[Fact]
583-
public void When_BVoltageLaplaceFunctionIsUsed_Expect_ReaderValidationError()
585+
public void When_BVoltageLaplaceFunctionIsUsed_Expect_DcGainNearOne()
584586
{
585587
var model = GetSpiceSharpModel(
586-
"B LAPLACE unsupported",
588+
"B V LAPLACE OP",
587589
"VIN in 0 1",
588-
"BBAD out 0 V={LAPLACE(V(in), 1/(1+s))}",
590+
"BLOW out 0 V={LAPLACE(V(in), 1/(1+s))}",
591+
"RLOAD out 0 1k",
592+
".OP",
593+
".SAVE V(out)",
594+
".END");
595+
596+
AssertNoValidationErrors(model);
597+
Assert.IsType<LaplaceVoltageControlledVoltageSource>(model.Circuit["BLOW"]);
598+
Assert.True(EqualsWithTol(1.0, RunOpSimulation(model, "V(out)")));
599+
}
600+
601+
[Fact]
602+
public void When_BCurrentLaplaceFunctionIsUsed_Expect_LoadVoltageWithCurrentSourceSign()
603+
{
604+
var model = GetSpiceSharpModel(
605+
"B I LAPLACE OP",
606+
".PARAM gm=1m",
607+
"VIN in 0 1",
608+
"BLOW out 0 I={LAPLACE(V(in), gm/(1+s))}",
609+
"RLOAD out 0 1k",
610+
".OP",
611+
".SAVE V(out)",
612+
".END");
613+
614+
AssertNoValidationErrors(model);
615+
Assert.IsType<LaplaceVoltageControlledCurrentSource>(model.Circuit["BLOW"]);
616+
Assert.True(EqualsWithTol(-1.0, RunOpSimulation(model, "V(out)")));
617+
}
618+
619+
[Fact]
620+
public void When_MixedBVoltageLaplaceFunctionIsUsed_Expect_HelperAndOffset()
621+
{
622+
var model = GetSpiceSharpModel(
623+
"B mixed LAPLACE OP",
624+
"VIN in 0 1",
625+
"BMIX out 0 V={1 + 2*LAPLACE(V(in), 1/(1+s))}",
626+
"RLOAD out 0 1k",
627+
".OP",
628+
".SAVE V(out)",
629+
".END");
630+
631+
AssertNoValidationErrors(model);
632+
Assert.IsType<BehavioralVoltageSource>(model.Circuit["BMIX"]);
633+
Assert.Contains(model.Circuit, entity => entity.Name == "__ssp_laplace_BMIX_0_src");
634+
Assert.True(EqualsWithTol(3.0, RunOpSimulation(model, "V(out)")));
635+
}
636+
637+
[Fact]
638+
public void When_MultipleLaplaceFunctionsUseDelay_Expect_ReaderValidationError()
639+
{
640+
var model = GetSpiceSharpModel(
641+
"B mixed LAPLACE invalid delay",
642+
"VA a 0 1",
643+
"VB b 0 1",
644+
"BMIX out 0 V={LAPLACE(V(a), 1/(1+s)) + LAPLACE(V(b), 1/(1+s))} TD=1n",
589645
"RLOAD out 0 1k",
590646
".OP",
591647
".SAVE V(out)",
592648
".END");
593649

594-
AssertReaderErrorContains(model, "function syntax");
650+
AssertReaderErrorContains(model, "only when one LAPLACE call");
595651
}
596652

597653
[Fact]

src/SpiceSharpParser.Tests/ModelReaders/Spice/Evaluation/Laplace/LaplaceExpressionParserTests.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
using SpiceSharpParser.ModelReaders.Netlist.Spice.Context.Names;
99
using SpiceSharpParser.ModelReaders.Netlist.Spice.Evaluation;
1010
using SpiceSharpParser.ModelReaders.Netlist.Spice.Evaluation.Laplace;
11+
using SpiceSharpParser.Lexers.Expressions;
1112
using Xunit;
13+
using Parser = SpiceSharpParser.Parsers.Expression.Parser;
1214

1315
namespace SpiceSharpParser.Tests.ModelReaders.Spice.Evaluation.Laplace
1416
{
@@ -83,6 +85,18 @@ public void When_LowPassIsParsed_Expect_AscendingCoefficients()
8385
AssertTransfer(new[] { 1.0 }, new[] { 1.0, 1e-6 }, transfer);
8486
}
8587

88+
[Fact]
89+
public void When_LowPassNodeIsParsed_Expect_AscendingCoefficients()
90+
{
91+
var context = CreateContext();
92+
context.SetParameter("tau", 1e-6);
93+
var node = Parser.Parse(Lexer.FromString("1/(1+s*tau)"), true);
94+
95+
var transfer = new LaplaceExpressionParser(context).Parse(node);
96+
97+
AssertTransfer(new[] { 1.0 }, new[] { 1.0, 1e-6 }, transfer);
98+
}
99+
86100
[Fact]
87101
public void When_LowPassUsesUppercaseS_Expect_SymbolicS()
88102
{

src/SpiceSharpParser.Tests/ModelReaders/Spice/Readers/EntityGenerators/Components/Sources/LaplaceSourceParserTests.cs

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ public void When_FSourceUsesVoltageLaplaceInput_Expect_CurrentInputValidationErr
511511
}
512512

513513
[Fact]
514-
public void When_ValueLaplaceFunctionIsUsed_Expect_UnsupportedReaderValidationError()
514+
public void When_ValueLaplaceFunctionIsUsed_Expect_LaplaceEntity()
515515
{
516516
var context = CreateReadingContext();
517517
var generator = new VoltageSourceGenerator();
@@ -528,12 +528,14 @@ public void When_ValueLaplaceFunctionIsUsed_Expect_UnsupportedReaderValidationEr
528528
},
529529
context);
530530

531-
Assert.Null(entity);
532-
AssertSingleReaderError(context, "function syntax");
531+
var laplace = Assert.IsType<LaplaceVoltageControlledVoltageSource>(entity);
532+
Assert.False(context.Result.ValidationResult.HasError);
533+
AssertCoefficients(new[] { 1.0 }, laplace.Parameters.Numerator);
534+
AssertCoefficients(new[] { 1.0, 1.0 }, laplace.Parameters.Denominator);
533535
}
534536

535537
[Fact]
536-
public void When_ValueWordLaplaceFunctionIsUsed_Expect_UnsupportedReaderValidationError()
538+
public void When_ValueWordLaplaceFunctionIsUsed_Expect_CurrentLaplaceEntity()
537539
{
538540
var context = CreateReadingContext();
539541
var generator = new CurrentSourceGenerator();
@@ -551,12 +553,14 @@ public void When_ValueWordLaplaceFunctionIsUsed_Expect_UnsupportedReaderValidati
551553
},
552554
context);
553555

554-
Assert.Null(entity);
555-
AssertSingleReaderError(context, "function syntax");
556+
var laplace = Assert.IsType<LaplaceVoltageControlledCurrentSource>(entity);
557+
Assert.False(context.Result.ValidationResult.HasError);
558+
AssertCoefficients(new[] { 1.0 }, laplace.Parameters.Numerator);
559+
AssertCoefficients(new[] { 1.0, 1.0 }, laplace.Parameters.Denominator);
556560
}
557561

558562
[Fact]
559-
public void When_BSourceLaplaceFunctionIsUsed_Expect_UnsupportedReaderValidationError()
563+
public void When_BSourceLaplaceFunctionIsUsed_Expect_LaplaceEntity()
560564
{
561565
var context = CreateReadingContext();
562566
var generator = new ArbitraryBehavioralGenerator();
@@ -573,8 +577,34 @@ public void When_BSourceLaplaceFunctionIsUsed_Expect_UnsupportedReaderValidation
573577
},
574578
context);
575579

576-
Assert.Null(entity);
577-
AssertSingleReaderError(context, "function syntax");
580+
var laplace = Assert.IsType<LaplaceVoltageControlledVoltageSource>(entity);
581+
Assert.False(context.Result.ValidationResult.HasError);
582+
AssertCoefficients(new[] { 1.0 }, laplace.Parameters.Numerator);
583+
AssertCoefficients(new[] { 1.0, 1.0 }, laplace.Parameters.Denominator);
584+
}
585+
586+
[Fact]
587+
public void When_MixedBSourceLaplaceFunctionIsUsed_Expect_HelperAndBehavioralEntity()
588+
{
589+
var context = CreateReadingContext();
590+
var generator = new ArbitraryBehavioralGenerator();
591+
592+
var entity = generator.Generate(
593+
"B1",
594+
"B1",
595+
"b",
596+
new ParameterCollection
597+
{
598+
new IdentifierParameter("out"),
599+
new IdentifierParameter("0"),
600+
Assignment("V", "1 + 2*LAPLACE(V(in), 1/(1+s))"),
601+
},
602+
context);
603+
604+
var behavioral = Assert.IsType<BehavioralVoltageSource>(entity);
605+
Assert.False(context.Result.ValidationResult.HasError);
606+
Assert.Contains("__ssp_laplace_B1_0", behavioral.Parameters.Expression);
607+
Assert.Contains(context.ContextEntities, item => item is LaplaceVoltageControlledVoltageSource && item.Name == "__ssp_laplace_B1_0_src");
578608
}
579609

580610
private static LaplaceSourceDefinition ParseDefinition(ParameterCollection parameters)
@@ -666,7 +696,9 @@ private static IReadingContext CreateReadingContext()
666696
{
667697
var context = Substitute.For<IReadingContext>();
668698
var evaluationContext = CreateEvaluationContext();
669-
context.Result.Returns(new SpiceSharpModel(new Circuit(), "test"));
699+
var circuit = new Circuit();
700+
context.Result.Returns(new SpiceSharpModel(circuit, "test"));
701+
context.ContextEntities.Returns(circuit);
670702
context.ReaderSettings.Returns(
671703
new SpiceNetlistReaderSettings(
672704
new SpiceNetlistCaseSensitivitySettings(),
@@ -675,6 +707,7 @@ private static IReadingContext CreateReadingContext()
675707
context.EvaluationContext.Returns(evaluationContext);
676708
context.Evaluator.Returns(evaluationContext.Evaluator);
677709
context.NameGenerator.Returns(evaluationContext.NameGenerator);
710+
context.SimulationPreparations.Returns(Substitute.For<ISimulationPreparations>());
678711
return context;
679712
}
680713

src/SpiceSharpParser.Tests/ModelWriters/LaplaceSourceWriterTests.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,58 @@ public void When_FLaplaceNoEqualsIsWritten_Expect_LaplaceCurrentControlledCurren
8888
Assert.Contains(lines.OfType<CSharpAssignmentStatement>(), line => line.Left.EndsWith(".Parameters.Numerator") && line.ValueExpression == "new[] { 2d }");
8989
}
9090

91+
[Fact]
92+
public void When_ValueLaplaceFunctionIsWritten_Expect_LaplaceVoltageControlledVoltageSource()
93+
{
94+
var component = new Component(
95+
"VLOW",
96+
new ParameterCollection(
97+
new List<Parameter>
98+
{
99+
new IdentifierParameter("out"),
100+
new IdentifierParameter("0"),
101+
Assignment("VALUE", "LAPLACE(V(in), 1/(1+s*tau))"),
102+
Assignment("M", "2"),
103+
}),
104+
lineInfo: null);
105+
106+
var writer = new VoltageSourceWriter(new WaveformWriter());
107+
var lines = writer.Write(component, CreateContext(("tau", "1u")));
108+
109+
Assert.Contains(lines.OfType<CSharpNewStatement>(), line => line.NewExpression == @"new LaplaceVoltageControlledVoltageSource(""VLOW"")");
110+
Assert.Contains(lines.OfType<CSharpCallStatement>(), line => line.CallExpression == @"Connect(""out"", ""0"", ""in"", ""0"")");
111+
Assert.Contains(lines.OfType<CSharpAssignmentStatement>(), line => line.Left.EndsWith(".Parameters.Numerator") && line.ValueExpression == "new[] { 2d }");
112+
}
113+
114+
[Fact]
115+
public void When_MixedBSourceLaplaceFunctionIsWritten_Expect_HelperBeforeBehavioralSource()
116+
{
117+
var component = new Component(
118+
"BMIX",
119+
new ParameterCollection(
120+
new List<Parameter>
121+
{
122+
new IdentifierParameter("out"),
123+
new IdentifierParameter("0"),
124+
Assignment("V", "1 + 2*LAPLACE(V(in), 1/(1+s))"),
125+
}),
126+
lineInfo: null);
127+
128+
var writer = new ArbitraryBehavioralWriter();
129+
var lines = writer.Write(component, CreateContext());
130+
131+
var helperIndex = lines.FindIndex(line => line is CSharpNewStatement statement
132+
&& statement.NewExpression == @"new LaplaceVoltageControlledVoltageSource(""__ssp_laplace_BMIX_0_src"")");
133+
var behavioralIndex = lines.FindIndex(line => line is CSharpNewStatement statement
134+
&& statement.NewExpression == @"new BehavioralVoltageSource(""BMIX"")");
135+
136+
Assert.True(helperIndex >= 0);
137+
Assert.True(behavioralIndex > helperIndex);
138+
Assert.Contains(lines.OfType<CSharpAssignmentStatement>(), line =>
139+
line.Left.EndsWith(".Parameters.Expression")
140+
&& line.ValueExpression.Contains("V(__ssp_laplace_BMIX_0)"));
141+
}
142+
91143
private static WriterContext CreateContext(params (string Name, string Expression)[] parameters)
92144
{
93145
var parser = new ExpressionParser(

src/SpiceSharpParser/ModelReaders/Netlist/Spice/Evaluation/Laplace/LaplaceExpressionParser.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,25 @@ public LaplaceTransferFunction Parse(string expression)
3939
try
4040
{
4141
var node = Parser.Parse(Lexer.FromString(expression), true);
42+
return Parse(node);
43+
}
44+
catch (LaplaceExpressionException)
45+
{
46+
throw;
47+
}
48+
catch (Exception ex)
49+
{
50+
throw new LaplaceExpressionException(
51+
"laplace transfer expression must be a rational polynomial in s",
52+
ex,
53+
_lineInfo);
54+
}
55+
}
56+
57+
public LaplaceTransferFunction Parse(Node node)
58+
{
59+
try
60+
{
4261
var rational = Build(node).Normalize(_options.ZeroTolerance, _options.RelativeTolerance);
4362
ValidateTransfer(rational);
4463

0 commit comments

Comments
 (0)