Skip to content

Commit c5252db

Browse files
Add docs
1 parent dcba961 commit c5252db

2 files changed

Lines changed: 230 additions & 8 deletions

File tree

src/SpiceSharpParser.CustomComponents/IdealDiodes/IdealDiodeEquation.cs

Lines changed: 114 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,35 @@ namespace SpiceSharpParser.CustomComponents.IdealDiodes
77
/// <summary>
88
/// LTspice-style idealized diode current law.
99
/// </summary>
10+
/// <remarks>
11+
/// This helper owns only the local current law for one ideal diode cell. The
12+
/// biasing behavior that calls it handles the external branch details such as
13+
/// series resistance and the parallel/series diode multipliers.
14+
///
15+
/// The current law is built from straight-line regions in slope/intercept
16+
/// form: <c>I = g * V + b</c>. The off region uses <c>Roff</c> or simulator
17+
/// <c>Gmin</c>, the forward region uses <c>Ron</c> after <c>Vfwd</c>, and the
18+
/// optional reverse-breakdown region uses <c>Rrev</c> after <c>-Vrev</c>.
19+
/// Optional epsilon parameters replace abrupt knees with a finite-width
20+
/// conductance ramp so both current and conductance stay continuous for
21+
/// Newton iteration.
22+
/// </remarks>
1023
internal static class IdealDiodeEquation
1124
{
25+
/// <summary>
26+
/// Lowest fallback off-state conductance when neither <c>Roff</c> nor simulator <c>Gmin</c> supplies one.
27+
/// </summary>
1228
private const double MinimumConductance = 0.0;
1329

1430
/// <summary>
15-
/// Evaluates the current and small-signal conductance for one ideal diode.
31+
/// Evaluates the current and small-signal conductance for one ideal diode cell.
1632
/// </summary>
17-
/// <param name="parameters">The ideal diode parameters.</param>
18-
/// <param name="biasingParameters">The simulation biasing parameters.</param>
19-
/// <param name="voltage">The voltage across one diode.</param>
20-
/// <param name="area">The diode area multiplier.</param>
21-
/// <param name="current">The output current.</param>
22-
/// <param name="conductance">The output conductance.</param>
33+
/// <param name="parameters">The effective model and instance parameters.</param>
34+
/// <param name="biasingParameters">The simulation biasing parameters, used for the default off conductance.</param>
35+
/// <param name="voltage">The local voltage across one diode cell.</param>
36+
/// <param name="area">The diode area multiplier applied after the local equation is evaluated.</param>
37+
/// <param name="current">The resulting local diode current, scaled by <paramref name="area" />.</param>
38+
/// <param name="conductance">The resulting small-signal conductance, scaled by <paramref name="area" />.</param>
2339
public static void Evaluate(
2440
IdealDiodeParameters parameters,
2541
BiasingParameters biasingParameters,
@@ -28,32 +44,51 @@ public static void Evaluate(
2844
out double current,
2945
out double conductance)
3046
{
47+
// Work in conductance because the solver needs dI/dV. Each operating
48+
// region is represented as a line: current = slope * voltage + intercept.
3149
double onConductance = 1.0 / parameters.OnResistance;
3250

51+
// LTspice's ideal diode can omit Roff. In that case the off-state
52+
// leakage follows the simulator's Gmin so the device still contributes
53+
// the same numerical shunt used elsewhere during biasing.
3354
double offConductance = parameters.OffResistance.Given
3455
? 1.0 / parameters.OffResistance.Value
3556
: Math.Max(biasingParameters.Gmin, MinimumConductance);
3657

58+
// Vfwd defaults to zero. With a zero threshold, the forward on-line is
59+
// simply Ron through the origin.
3760
double forwardVoltage = parameters.ForwardVoltage.Given ? parameters.ForwardVoltage.Value : 0.0;
3861

62+
// Start from the off branch. Reverse breakdown and forward conduction
63+
// may overwrite this below when the voltage lies in their region.
3964
current = offConductance * voltage;
4065
conductance = offConductance;
4166

4267
if (parameters.ReverseVoltage.Given)
4368
{
69+
// Reverse breakdown is a line through (-Vrev, 0):
70+
// current = Grev * voltage + Grev * Vrev.
71+
// If Rrev is omitted, LTspice-style behavior falls back to Ron.
4472
double reverseVoltage = Math.Abs(parameters.ReverseVoltage.Value);
4573
double reverseResistance = parameters.ReverseResistance.Given
4674
? parameters.ReverseResistance.Value
4775
: parameters.OnResistance;
4876
double reverseConductance = 1.0 / reverseResistance;
4977
double reverseIntercept = reverseConductance * reverseVoltage;
78+
79+
// Roff/Gmin can tilt the off-line, so the true intersection is not
80+
// always exactly -Vrev. Use the nominal knee only as a degenerate
81+
// fallback when the two lines are effectively parallel.
5082
double boundary = FindIntersection(
5183
reverseConductance,
5284
reverseIntercept,
5385
offConductance,
5486
0.0,
5587
-reverseVoltage);
5688

89+
// Evaluate the reverse-to-off knee. With revepsilon omitted or
90+
// zero this is a hard switch at the intersection; otherwise it is
91+
// smoothed symmetrically around the boundary.
5792
EvaluateTransition(
5893
voltage,
5994
boundary,
@@ -66,18 +101,29 @@ public static void Evaluate(
66101
out conductance);
67102
}
68103

104+
// The forward on-line crosses zero current at Vfwd:
105+
// current = Gon * voltage - Gon * Vfwd.
69106
double onIntercept = -onConductance * forwardVoltage;
107+
108+
// Find the voltage where the off-line and the forward on-line meet.
109+
// This is close to Vfwd when Roff is large, but computing it keeps the
110+
// model continuous for any legal Roff/Gmin.
70111
double forwardBoundary = FindIntersection(
71112
offConductance,
72113
0.0,
73114
onConductance,
74115
onIntercept,
75116
forwardVoltage);
76117

118+
// Avoid a second transition evaluation in the normal off region. For
119+
// smoothed knees, begin applying the blend at the start of the epsilon
120+
// band so the partial-conduction region is not missed.
77121
double forwardWidth = parameters.ForwardEpsilon.Given ? parameters.ForwardEpsilon.Value : 0.0;
78122
double forwardStart = forwardBoundary - (Math.Max(forwardWidth, 0.0) / 2.0);
79123
if (voltage > forwardBoundary || (forwardWidth > 0.0 && voltage >= forwardStart))
80124
{
125+
// Evaluate the off-to-forward knee using the same generic transition
126+
// helper used for reverse breakdown.
81127
EvaluateTransition(
82128
voltage,
83129
forwardBoundary,
@@ -90,12 +136,26 @@ public static void Evaluate(
90136
out conductance);
91137
}
92138

139+
// Current limits are applied to the already-selected region. This keeps
140+
// the normal piecewise law simple and lets the limiter scale the local
141+
// derivative by the tanh derivative.
93142
ApplyCurrentLimits(parameters, ref current, ref conductance);
94143

144+
// Area behaves like parallel identical cells: both DC current and
145+
// small-signal conductance scale linearly.
95146
current *= area;
96147
conductance *= area;
97148
}
98149

150+
/// <summary>
151+
/// Finds where two line segments in <c>current = slope * voltage + intercept</c> form intersect.
152+
/// </summary>
153+
/// <param name="leftSlope">The slope of the first line.</param>
154+
/// <param name="leftIntercept">The intercept of the first line.</param>
155+
/// <param name="rightSlope">The slope of the second line.</param>
156+
/// <param name="rightIntercept">The intercept of the second line.</param>
157+
/// <param name="fallback">The voltage to use if the lines are nearly parallel.</param>
158+
/// <returns>The intersection voltage.</returns>
99159
private static double FindIntersection(
100160
double leftSlope,
101161
double leftIntercept,
@@ -104,11 +164,27 @@ private static double FindIntersection(
104164
double fallback)
105165
{
106166
double denominator = leftSlope - rightSlope;
167+
168+
// Parallel or nearly parallel regions do not give a useful numerical
169+
// knee. The caller provides the physically meaningful nominal boundary.
107170
if (Math.Abs(denominator) <= 1e-30)
108171
return fallback;
172+
109173
return (rightIntercept - leftIntercept) / denominator;
110174
}
111175

176+
/// <summary>
177+
/// Evaluates either a hard or smoothed transition between two linear current regions.
178+
/// </summary>
179+
/// <param name="voltage">The voltage at which to evaluate the transition.</param>
180+
/// <param name="boundary">The intersection voltage of the two unsmoothed lines.</param>
181+
/// <param name="epsilon">The optional smoothing width around <paramref name="boundary" />.</param>
182+
/// <param name="leftSlope">The slope used below the transition.</param>
183+
/// <param name="leftIntercept">The intercept used below the transition.</param>
184+
/// <param name="rightSlope">The slope used above the transition.</param>
185+
/// <param name="rightIntercept">The intercept used above the transition.</param>
186+
/// <param name="current">The evaluated current.</param>
187+
/// <param name="conductance">The evaluated conductance.</param>
112188
private static void EvaluateTransition(
113189
double voltage,
114190
double boundary,
@@ -123,6 +199,8 @@ private static void EvaluateTransition(
123199
double width = epsilon.Given ? epsilon.Value : 0.0;
124200
if (width <= 0.0)
125201
{
202+
// No smoothing requested: choose the line on the active side of
203+
// the intersection and expose that line's slope as conductance.
126204
if (voltage < boundary)
127205
{
128206
current = (leftSlope * voltage) + leftIntercept;
@@ -137,8 +215,11 @@ private static void EvaluateTransition(
137215
return;
138216
}
139217

218+
// The epsilon value is the full width of the blend, centered on the
219+
// natural intersection of the two lines.
140220
double start = boundary - (width / 2.0);
141221
double end = boundary + (width / 2.0);
222+
142223
if (voltage <= start)
143224
{
144225
current = (leftSlope * voltage) + leftIntercept;
@@ -153,6 +234,14 @@ private static void EvaluateTransition(
153234
return;
154235
}
155236

237+
// Inside the smoothing band the conductance moves linearly from the
238+
// left slope to the right slope. Current is the integral of that ramp,
239+
// anchored to the left line at the start of the band:
240+
//
241+
// g(x) = leftSlope + slopeDelta * x / width
242+
// I(x) = I(start) + leftSlope * x + slopeDelta * x^2 / (2 * width)
243+
//
244+
// where x is the distance from the start of the smoothing band.
156245
double distance = voltage - start;
157246
double slopeDelta = rightSlope - leftSlope;
158247
current = (leftSlope * start) + leftIntercept
@@ -161,8 +250,17 @@ private static void EvaluateTransition(
161250
conductance = leftSlope + (slopeDelta * distance / width);
162251
}
163252

253+
/// <summary>
254+
/// Applies the optional forward or reverse current limiter to the selected current branch.
255+
/// </summary>
256+
/// <param name="parameters">The effective ideal diode parameters.</param>
257+
/// <param name="current">The current to limit in place.</param>
258+
/// <param name="conductance">The conductance to update in place with the limiter derivative.</param>
164259
private static void ApplyCurrentLimits(IdealDiodeParameters parameters, ref double current, ref double conductance)
165260
{
261+
// Forward and reverse limits are independent. Select by the sign of the
262+
// already-computed current so leakage, breakdown, and smoothed knees all
263+
// feed into the same limiting law.
166264
if (current > 0.0 && parameters.ForwardCurrentLimit.Given)
167265
{
168266
ApplyCurrentLimit(parameters.ForwardCurrentLimit.Value, ref current, ref conductance);
@@ -173,12 +271,21 @@ private static void ApplyCurrentLimits(IdealDiodeParameters parameters, ref doub
173271
}
174272
}
175273

274+
/// <summary>
275+
/// Smoothly compresses current toward a symmetric magnitude limit.
276+
/// </summary>
277+
/// <param name="limit">The positive or negative limit magnitude.</param>
278+
/// <param name="current">The current to limit in place.</param>
279+
/// <param name="conductance">The conductance to update in place with the limiter derivative.</param>
176280
private static void ApplyCurrentLimit(double limit, ref double current, ref double conductance)
177281
{
178282
limit = Math.Abs(limit);
179283
if (limit <= 0.0)
180284
return;
181285

286+
// I_limited = limit * tanh(I_raw / limit)
287+
// dI_limited/dV = dI_raw/dV * (1 - tanh(I_raw / limit)^2)
288+
// This gives a soft saturation without a derivative discontinuity.
182289
double normalized = current / limit;
183290
double limited = Math.Tanh(normalized);
184291
current = limit * limited;

src/docs/articles/ideal-diode.md

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,122 @@ When the diode is reverse biased, `@D1[i]` is usually negative.
254254
## Current Law
255255

256256
The ideal diode current law is evaluated for one diode cell first. Instance
257-
scaling is applied after that. Define:
257+
scaling is applied after that.
258+
259+
### Local Evaluation Algorithm
260+
261+
The implementation for this section lives in `IdealDiodeEquation`. The method
262+
`Evaluate(...)` does not stamp the circuit matrix. It only evaluates the local
263+
current law for one ideal-diode cell and returns two values:
264+
265+
| Output | Meaning |
266+
|--------|---------|
267+
| `current` | Local diode current at the present voltage |
268+
| `conductance` | Local small-signal derivative `di/dv` used by Newton iteration |
269+
270+
The voltage `v` is positive from anode to cathode, and positive current flows in
271+
that same direction. The algorithm uses a line representation for each operating
272+
region:
273+
274+
```text
275+
i(v) = slope * v + intercept
276+
```
277+
278+
This representation matters because the solver needs both the current and its
279+
derivative. The line slope is already the small-signal conductance for that
280+
region.
281+
282+
The local evaluation is:
283+
284+
```text
285+
gon = 1 / Ron
286+
goff = Roff was given ? 1 / Roff : max(simulation Gmin, 0)
287+
vf = Vfwd was given ? Vfwd : 0
288+
289+
current = goff * v
290+
conductance = goff
291+
292+
if Vrev was given:
293+
vrev = abs(Vrev)
294+
rrev = Rrev was given ? Rrev : Ron
295+
grev = 1 / rrev
296+
297+
reverse line = grev * v + grev * vrev
298+
off line = goff * v
299+
boundary = intersection(reverse line, off line)
300+
301+
current, conductance =
302+
transition(v, boundary, RevEpsilon, reverse line, off line)
303+
304+
forward line = gon * v - gon * vf
305+
off line = goff * v
306+
boundary = intersection(off line, forward line)
307+
308+
if v is past the forward boundary, or inside the forward smoothing window:
309+
current, conductance =
310+
transition(v, boundary, Epsilon, off line, forward line)
311+
312+
if current > 0 and Ilimit was given:
313+
apply tanh current limiter
314+
else if current < 0 and RevIlimit was given:
315+
apply tanh current limiter
316+
317+
current = current * area
318+
conductance = conductance * area
319+
```
320+
321+
The order is intentional. The off line is the default because most voltages are
322+
between reverse breakdown and forward conduction. Reverse breakdown is evaluated
323+
before forward conduction because it only applies on the negative-voltage side.
324+
The forward transition is evaluated last so positive forward conduction replaces
325+
the off result when the voltage reaches the forward knee.
326+
327+
The transition helper is shared by both knees. It receives the line on the
328+
low-voltage side, the line on the high-voltage side, the boundary where the two
329+
unsmoothed lines intersect, and the optional smoothing width. For reverse
330+
breakdown, the low-voltage side is the reverse line and the high-voltage side is
331+
the off line. For forward conduction, the low-voltage side is the off line and
332+
the high-voltage side is the forward line.
333+
334+
The boundary is computed by solving the two-line equation:
335+
336+
```text
337+
leftSlope * v + leftIntercept = rightSlope * v + rightIntercept
338+
```
339+
340+
or:
341+
342+
```text
343+
v = (rightIntercept - leftIntercept) / (leftSlope - rightSlope)
344+
```
345+
346+
If the two slopes are nearly equal, the code uses the nominal knee as a fallback:
347+
`-Vrev` for reverse breakdown or `Vfwd` for forward conduction. That avoids an
348+
unstable division when two regions are almost parallel.
349+
350+
With no smoothing, the transition helper simply chooses one line. With smoothing,
351+
the epsilon value is treated as the full voltage width of the blend, centered on
352+
the boundary. The conductance ramps linearly from the left slope to the right
353+
slope, and the current is the integral of that ramp. This keeps both current and
354+
conductance continuous at the start and end of the smoothing window.
355+
356+
Current limiting happens after the piecewise line and optional smoothing have
357+
selected a raw current. The limiter is based on `tanh`, so it approaches the
358+
limit smoothly and also scales the conductance by the derivative of the limiter.
359+
The sign of the raw current selects the limiter: positive current uses `Ilimit`,
360+
negative current uses `RevIlimit`, and zero current is left unchanged.
361+
362+
Finally, `area` is applied to both current and conductance. Because this happens
363+
after current limiting, increasing `area` scales the limited current and the
364+
limited small-signal conductance as if multiple identical local cells were placed
365+
in parallel.
366+
367+
The caller, `Biasing`, handles everything outside the local cell. It divides the
368+
internal diode voltage by `N` before calling `Evaluate(...)`, multiplies the
369+
equivalent branch current by `M`, scales the conductance by `M / N`, and stamps
370+
`Rs` as the series branch between the external and internal anode.
371+
372+
The formulas below describe the same algorithm. Define:
258373

259374
$$
260375
\begin{aligned}

0 commit comments

Comments
 (0)