Skip to content

Commit df6168a

Browse files
feat: Implement function-style LAPLACE support in behavioral source expressions
1 parent ff55d8b commit df6168a

1 file changed

Lines changed: 350 additions & 0 deletions

File tree

Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
# Function-Style LAPLACE And B-Source LAPLACE
2+
3+
## Summary
4+
5+
Add `LAPLACE(input, transfer)` support inside behavioral source expressions, including `VALUE={...}`, `VALUE {...}`, and `B ... V=/I=` forms. The implementation should be AST-based: parse the behavioral expression, detect `LAPLACE(...)` calls, validate their arguments, and lower them into existing SpiceSharp Laplace source entities.
6+
7+
There are two target shapes:
8+
9+
- A whole-expression `LAPLACE(...)` maps directly to an existing Laplace source entity.
10+
- A mixed expression, such as `1 + 2*LAPLACE(...)`, is lowered by generating internal voltage-output Laplace helper sources and replacing each call with `V(<helperNode>)` in the final behavioral expression.
11+
12+
This keeps runtime behavior on the existing SpiceSharp Laplace components instead of inventing a new behavioral runtime function.
13+
14+
## Goals
15+
16+
- Support function-style Laplace expressions in all expression-based voltage/current source paths.
17+
- Support `B` sources:
18+
- `B<name> <out+> <out-> V={LAPLACE(...)}`
19+
- `B<name> <out+> <out-> I={LAPLACE(...)}`
20+
- mixed `B` expressions containing one or more `LAPLACE(...)` calls.
21+
- Support `VALUE` forms on existing source generators:
22+
- `E`, `V`, and `H` expression outputs as voltage sources.
23+
- `G`, `I`, and `F` expression outputs as current sources.
24+
- Keep existing source-level `E/G/F/H ... LAPLACE ...` behavior unchanged.
25+
- Reuse the existing Laplace transfer parser, coefficient normalization, validation messages, and entity types wherever practical.
26+
- Preserve current non-Laplace behavioral source behavior.
27+
28+
## Out Of Scope
29+
30+
- Inline option arguments inside the function call, for example `LAPLACE(V(in), H(s), TD=1n)`.
31+
- Arbitrary Laplace input expressions, for example `LAPLACE(V(a)-V(b), H(s))`.
32+
- Function-style delay expressions such as `exp(-s*td)` or broader PSpice/LTspice dialect forms.
33+
- Supporting `LAPLACE(...)` inside `.FUNC` definitions as a dynamic function.
34+
- Hiding generated helper entities from every low-level circuit inspection API.
35+
36+
## Supported Syntax
37+
38+
The supported function signature is:
39+
40+
```spice
41+
LAPLACE(<input>, <transfer>)
42+
```
43+
44+
Accepted input shapes:
45+
46+
```spice
47+
V(node)
48+
V(node1,node2)
49+
I(source)
50+
```
51+
52+
Accepted output contexts:
53+
54+
```spice
55+
E1 out 0 VALUE={LAPLACE(V(in), 1/(1+s*tau))}
56+
G1 out 0 VALUE={LAPLACE(V(in), gm/(1+s*tau))}
57+
V1 out 0 VALUE={LAPLACE(V(in), wc/(s+wc))}
58+
I1 out 0 VALUE={LAPLACE(V(in), gm*wc/(s+wc))}
59+
B1 out 0 V={LAPLACE(V(in), 1/(1+s*tau))}
60+
B2 out 0 I={LAPLACE(V(in), gm/(1+s*tau))}
61+
B3 out 0 V={1 + 2*LAPLACE(V(in), 1/(1+s))}
62+
B4 out 0 I={LAPLACE(V(a), 1/(1+s*t1)) - LAPLACE(V(b), 1/(1+s*t2))}
63+
```
64+
65+
The function name is case-insensitive. Whitespace inside the expression should follow the existing behavioral expression parser rules.
66+
67+
## Entity Mapping
68+
69+
When the entire expression is exactly one `LAPLACE(...)` call, create the final Laplace entity directly:
70+
71+
| Output kind | Input kind | Entity |
72+
| --- | --- | --- |
73+
| Voltage | Voltage | `LaplaceVoltageControlledVoltageSource` |
74+
| Voltage | Current | `LaplaceCurrentControlledVoltageSource` |
75+
| Current | Voltage | `LaplaceVoltageControlledCurrentSource` |
76+
| Current | Current | `LaplaceCurrentControlledCurrentSource` |
77+
78+
Output kind is determined by the source path:
79+
80+
| Source form | Output kind |
81+
| --- | --- |
82+
| `E ... VALUE=...` | Voltage |
83+
| `H ... VALUE=...` | Voltage |
84+
| `V ... VALUE=...` | Voltage |
85+
| `B ... V=...` | Voltage |
86+
| `G ... VALUE=...` | Current |
87+
| `F ... VALUE=...` | Current |
88+
| `I ... VALUE=...` | Current |
89+
| `B ... I=...` | Current |
90+
91+
For mixed expressions, always lower each `LAPLACE(...)` call to a voltage-output helper:
92+
93+
| Laplace input kind | Helper entity |
94+
| --- | --- |
95+
| Voltage input | `LaplaceVoltageControlledVoltageSource` connected from helper node to `0` |
96+
| Current input | `LaplaceCurrentControlledVoltageSource` connected from helper node to `0` |
97+
98+
Then replace the original `LAPLACE(...)` call in the final behavioral expression with:
99+
100+
```spice
101+
V(<helperNode>)
102+
```
103+
104+
The final behavioral source remains a `BehavioralVoltageSource` or `BehavioralCurrentSource` according to the output kind.
105+
106+
## Lowering Algorithm
107+
108+
1. Identify the expression-bearing parameter:
109+
- `AssignmentParameter` named `VALUE`, `V`, or `I`.
110+
- `WordParameter` `VALUE` followed by an `ExpressionParameter`.
111+
- bare `ExpressionParameter` paths that are already treated as behavioral expressions.
112+
2. Parse the raw expression with the existing expression parser into an AST.
113+
3. Traverse the AST and find case-insensitive `FunctionNode` calls named `laplace`.
114+
4. Do not traverse inside a `laplace` call's arguments for further lowering. Nested calls inside input or transfer arguments are invalid through normal input/transfer validation.
115+
5. Validate each `laplace` call:
116+
- exactly two arguments;
117+
- first argument is a supported voltage or current probe shape;
118+
- second argument is a proper rational polynomial in `s`;
119+
- finite coefficients;
120+
- finite, non-singular DC gain;
121+
- denominator is non-zero;
122+
- transfer order stays within existing `LaplaceExpressionOptions.MaxOrder`.
123+
6. If zero calls are found, return "not handled" and let existing behavioral logic run unchanged.
124+
7. If the root AST node is exactly a single `laplace` call:
125+
- create the direct final Laplace entity from the mapping table above;
126+
- no helper node is needed.
127+
8. If the expression is mixed:
128+
- allocate one helper source and helper node per call;
129+
- replace the call node with a voltage probe node for the helper node;
130+
- serialize the modified AST back to a behavioral expression string;
131+
- create helper entities first;
132+
- create and return the final behavioral source using the modified expression.
133+
134+
## AST And Formatting Requirements
135+
136+
- Do not locate or replace `LAPLACE(...)` using string slicing or regular expressions.
137+
- Add internal APIs so existing Laplace parsers can work from parsed `Node` instances:
138+
- `LaplaceExpressionParser.Parse(Node node)`
139+
- input parsing from a `Node` for `V(...)` and `I(...)`.
140+
- Add a small internal behavioral expression formatter for modified ASTs. It should emit syntax accepted by the existing behavioral parser:
141+
- binary operators with parentheses;
142+
- `**` for power if that is what existing expressions expect;
143+
- `V(node)` and `I(source)` probes;
144+
- normal function calls for non-Laplace functions;
145+
- ternary and unary operators where already supported.
146+
- Keep reader and writer formatting code separate from C# interpolation formatting. The reader needs a SPICE expression string; the writer needs generated C# statements.
147+
148+
## Options And Multipliers
149+
150+
Existing source-level `E/G/F/H ... LAPLACE ...` option behavior remains unchanged.
151+
152+
For function-style `LAPLACE(...)` expressions:
153+
154+
- `TD=` and `DELAY=` are accepted as trailing source-level parameters only if exactly one `LAPLACE(...)` call is present.
155+
- The delay option applies to that direct Laplace entity or helper source.
156+
- If more than one `LAPLACE(...)` call is present and a trailing `TD=` or `DELAY=` option is present, emit a reader validation error.
157+
- `M=` must preserve existing current-source behavior:
158+
- for source-level Laplace syntax, keep folding `M` into the Laplace numerator as today;
159+
- for direct whole-expression function-style Laplace, folding `M` into the numerator is acceptable when it is semantically equivalent;
160+
- for mixed current-source behavioral expressions, apply existing `M` behavior to the final current source expression, not to every helper;
161+
- for voltage-output `VALUE`/`B V=` mixed expressions, reject trailing `M=` unless an existing source path already supports it.
162+
163+
This avoids changing existing `B ... I={expr} M=...` behavior while still allowing direct function-style Laplace to use the current Laplace coefficient path.
164+
165+
## Naming And Collision Rules
166+
167+
Generated helpers should use deterministic, reserved names:
168+
169+
```text
170+
__ssp_laplace_<sanitizedSourceName>_<index>
171+
__ssp_laplace_<sanitizedSourceName>_<index>_src
172+
```
173+
174+
Rules:
175+
176+
- Sanitize to ASCII letters, digits, and underscores.
177+
- Prefix with `__ssp_laplace_`.
178+
- Use a stable index based on traversal order.
179+
- If the generated entity name already exists in `context.ContextEntities`, append an incrementing suffix.
180+
- Helper node names should be local names and go through the same name-generation/scoping path as ordinary node names when subcircuits are expanded.
181+
- The final rewritten behavioral expression should reference the local helper node name. Existing expression resolution should scope it during subcircuit expansion.
182+
183+
## Reader Implementation Plan
184+
185+
1. Refactor Laplace transfer parsing.
186+
- Add `LaplaceExpressionParser.Parse(Node node)`.
187+
- Keep `Parse(string expression)` as a wrapper that parses the string then calls `Parse(Node)`.
188+
- Keep current validation messages unless the function-style path needs a more specific "laplace function expects two arguments" message.
189+
190+
2. Refactor Laplace input parsing.
191+
- Move voltage/current input parsing out of private string-only helpers or add parallel `Node`-based helpers.
192+
- Preserve existing string parser behavior for source-level forms.
193+
- Node-based validation should accept the same shapes as existing source-level validation.
194+
195+
3. Add lowerer types in the source generator area.
196+
- `LaplaceFunctionExpressionLowerer`
197+
- `LaplaceFunctionLoweringResult`
198+
- `LaplaceFunctionCallDefinition`
199+
- `LaplaceOutputKind`
200+
- Keep them internal.
201+
202+
4. Add entity factory helpers.
203+
- Share direct entity creation between `VoltageSourceGenerator`, `CurrentSourceGenerator`, `ArbitraryBehavioralGenerator`, and the lowerer.
204+
- Avoid duplicating numerator/denominator/delay assignment logic.
205+
- Do not change the public `IComponentGenerator.Generate(...)` interface.
206+
207+
5. Update generators.
208+
- Remove the current early rejection for `LAPLACE(...)`.
209+
- Before normal behavioral source creation, invoke the lowerer.
210+
- If the lowerer returns a direct final entity, return it.
211+
- If the lowerer returns helpers and a final behavioral expression, add helpers to `context.ContextEntities` and return the final behavioral source.
212+
- If the lowerer reports validation errors, return `null`.
213+
- If the lowerer finds no `LAPLACE(...)`, continue current behavior unchanged.
214+
215+
6. Keep parse actions correct.
216+
- Final behavioral sources created after mixed lowering must still set `Parameters.Expression`.
217+
- `Parameters.ParseAction` must use the existing simulation-aware resolver setup.
218+
- If the rewritten expression has functions or spice properties, retain the current before-setup parse-action refresh behavior.
219+
220+
## Writer Implementation Plan
221+
222+
1. Add writer-side lowering support in `SourceWriterHelper`.
223+
- Reuse the same AST parser and validation logic.
224+
- Use the writer evaluation context for parameters.
225+
- Emit comments when lowering fails, matching existing writer error style.
226+
227+
2. Update `ArbitraryBehavioralWriter`.
228+
- Route `V=` and `I=` expressions through the new writer helper before plain behavioral output.
229+
230+
3. Update `VoltageSourceWriter` and `CurrentSourceWriter`.
231+
- Route `VALUE` and expression-style behavioral paths through the writer helper before plain behavioral output.
232+
233+
4. Emit helper C# statements before final behavioral source statements for mixed expressions.
234+
235+
5. Ensure generated C# uses the same helper names and final expression shape as the reader path.
236+
237+
## Validation And Diagnostics
238+
239+
Use reader validation errors, not runtime exceptions, for expected invalid netlist forms.
240+
241+
Add or preserve clear messages for:
242+
243+
- `laplace function expects exactly two arguments`
244+
- `laplace input expression must be V(node), V(node1,node2), or I(source)`
245+
- `laplace transfer expression must be a rational polynomial in s`
246+
- `laplace transfer function is improper; numerator degree exceeds denominator degree`
247+
- `laplace transfer function has singular DC gain`
248+
- `laplace transfer coefficients must be finite`
249+
- `laplace delay can be specified only once`
250+
- `laplace delay must be non-negative`
251+
- `laplace source-level delay options can be used only when one LAPLACE call is present`
252+
- `laplace option arguments inside LAPLACE(...) are not supported`
253+
254+
For mixed expressions, include the parent source line info when possible. For transfer-specific errors, keep the source expression line info if individual argument line info is not available.
255+
256+
## Test Plan
257+
258+
### Unit Tests
259+
260+
Add tests around the lowerer and updated source generators:
261+
262+
- Direct `E ... VALUE={LAPLACE(V(in), 1/(1+s*tau))}` creates `LaplaceVoltageControlledVoltageSource`.
263+
- Direct `G ... VALUE={LAPLACE(V(in), gm/(1+s*tau))}` creates `LaplaceVoltageControlledCurrentSource`.
264+
- Direct `H ... VALUE={LAPLACE(I(VSENSE), 1000/(s+1000))}` creates `LaplaceCurrentControlledVoltageSource`.
265+
- Direct `F ... VALUE={LAPLACE(I(VSENSE), 1/(1+s))}` creates `LaplaceCurrentControlledCurrentSource`.
266+
- `B ... V={LAPLACE(V(in), 1/(1+s))}` creates a voltage-output Laplace entity.
267+
- `B ... I={LAPLACE(V(in), gm/(1+s))}` creates a current-output Laplace entity.
268+
- Mixed `B ... V={1 + 2*LAPLACE(V(in), 1/(1+s))}` creates one helper plus final behavioral voltage source.
269+
- Mixed `B ... I={LAPLACE(V(a), 1/(1+s*t1)) - LAPLACE(V(b), 1/(1+s*t2))}` creates two helpers plus final behavioral current source.
270+
- Mixed expressions preserve non-Laplace functions such as `if()`, `abs()`, and parameters.
271+
- Source-level `TD=` and `DELAY=` apply with one function call and fail with two calls.
272+
- Current-source `M=` behavior is preserved for mixed expressions.
273+
- Invalid function argument count fails.
274+
- Invalid input shape fails.
275+
- Invalid transfer fails.
276+
- User-defined `.FUNC LAPLACE(...)` does not override built-in detection.
277+
278+
### Integration Tests
279+
280+
Add OP, AC, and focused transient coverage:
281+
282+
- OP parity: `E VALUE={LAPLACE(...)}` equals source-level `E ... LAPLACE ...`.
283+
- OP parity: `B V={LAPLACE(...)}` equals equivalent `E ... LAPLACE ...`.
284+
- OP parity: `B I={LAPLACE(...)}` through a grounded resistor matches expected current-source sign.
285+
- AC low-pass cutoff magnitude and phase for `VALUE={LAPLACE(...)}`.
286+
- AC mixed expression: DC offset plus low-pass term has expected low-frequency and cutoff behavior.
287+
- Multiple-call AC expression combines two Laplace helper outputs correctly.
288+
- Transient smoke test for a mixed first-order low-pass expression.
289+
- Subcircuit expansion smoke test to verify helper node/entity names are scoped and do not collide.
290+
291+
### Writer Tests
292+
293+
- Direct function-style VALUE emits the matching Laplace constructor and parameters.
294+
- Direct B source LAPLACE emits the matching Laplace constructor and parameters.
295+
- Mixed expression emits helper Laplace source statements before the final behavioral source statement.
296+
- Writer emits useful comments for invalid function-style Laplace expressions.
297+
298+
### Regression Tests
299+
300+
- Existing source-level `E/G/F/H ... LAPLACE ...` tests continue passing.
301+
- Existing non-Laplace `VALUE`, `B V=`, and `B I=` tests continue passing.
302+
- Existing unsupported forms still fail if they are still out of scope.
303+
304+
## Documentation Updates
305+
306+
Update:
307+
308+
- `src/docs/articles/laplace.md`
309+
- `src/docs/articles/laplace-basics.md`
310+
- `src/docs/articles/behavioral-source.md`
311+
- `src/docs/articles/vcvs.md`
312+
- `src/docs/articles/vccs.md`
313+
- `src/docs/articles/voltage-source.md`
314+
- `src/docs/articles/current-source.md`
315+
- `src/docs/articles/intro.md`
316+
317+
Docs should cover:
318+
319+
- Function-style syntax.
320+
- B source voltage and current examples.
321+
- Mixed expression examples.
322+
- Input and transfer limitations.
323+
- `TD=` / `DELAY=` limitation for multiple calls.
324+
- The fact that helper entities are an implementation detail and may appear during low-level circuit inspection.
325+
326+
Remove "not supported yet" statements for `VALUE={LAPLACE(...)}` and `B` source LAPLACE forms once implementation and tests are complete.
327+
328+
## Acceptance Criteria
329+
330+
- Supported netlists read without validation errors.
331+
- Unsupported function-style forms fail during reading with clear validation errors.
332+
- OP, AC, and transient tests pass for direct and mixed forms.
333+
- Generated C# output matches reader behavior for direct and mixed forms.
334+
- Existing source-level Laplace and non-Laplace behavioral source behavior is unchanged.
335+
- Documentation accurately reflects the supported subset.
336+
337+
## Risks
338+
339+
- Mixed-expression lowering creates extra entities and nodes. The names must be collision-resistant and stable.
340+
- AST formatting must produce expressions that the existing behavioral parser can parse again.
341+
- Current-source `M=` has existing semantics. The implementation must not accidentally apply it twice or move it from the final behavioral expression to every helper.
342+
- Subcircuit expansion may expose naming bugs if helper nodes are generated with already-scoped names instead of local names.
343+
- Writer support can drift from reader behavior unless both share the parser/lowering model.
344+
345+
## Assumptions
346+
347+
- The existing SpiceSharp Laplace source components are the correct runtime implementation for all supported function-style forms.
348+
- Helper voltage outputs are a valid numeric representation of each `LAPLACE(...)` term inside mixed expressions.
349+
- Internal helper entities may be visible during low-level circuit inspection, but their names are reserved and deterministic.
350+
- Inline function options and arbitrary input expressions remain future work.

0 commit comments

Comments
 (0)