Skip to content

Commit 37b79e9

Browse files
Fixes
1 parent 47fbc7b commit 37b79e9

4 files changed

Lines changed: 172 additions & 96 deletions

File tree

src/SpiceSharpParser.CustomComponents/IdealDiodes/IdealDiodeEquation.cs

Lines changed: 14 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -64,32 +64,25 @@ public static void Evaluate(
6464

6565
if (parameters.ReverseVoltage.Given)
6666
{
67-
// Reverse breakdown is a line through (-Vrev, 0):
68-
// current = Grev * voltage + Grev * Vrev.
67+
// Reverse breakdown is anchored to the off-line current at
68+
// -Vrev. With finite Roff, LTspice keeps the nominal knee at
69+
// -Vrev rather than moving it to the mathematical line
70+
// intersection.
6971
// If Rrev is omitted, LTspice-style behavior falls back to Ron.
7072
double reverseVoltage = Math.Abs(parameters.ReverseVoltage.Value);
7173
double reverseResistance = parameters.ReverseResistance.Given
7274
? parameters.ReverseResistance.Value
7375
: parameters.OnResistance;
7476
double reverseConductance = 1.0 / reverseResistance;
75-
double reverseIntercept = reverseConductance * reverseVoltage;
76-
77-
// Roff/Gmin can tilt the off-line, so the true intersection is not
78-
// always exactly -Vrev. Use the nominal knee only as a degenerate
79-
// fallback when the two lines are effectively parallel.
80-
double boundary = FindIntersection(
81-
reverseConductance,
82-
reverseIntercept,
83-
offConductance,
84-
0.0,
85-
-reverseVoltage);
77+
double reverseBoundary = -reverseVoltage;
78+
double reverseIntercept = (reverseConductance - offConductance) * reverseVoltage;
8679

8780
// Evaluate the reverse-to-off knee. With revepsilon omitted or
88-
// zero this is a hard switch at the intersection; otherwise the
89-
// smoothing window extends from the boundary into reverse breakdown.
81+
// zero this is a hard switch at -Vrev; otherwise the smoothing
82+
// window extends from the nominal boundary into reverse breakdown.
9083
EvaluateTransition(
9184
voltage,
92-
boundary,
85+
reverseBoundary,
9386
parameters.ReverseEpsilon,
9487
reverseConductance,
9588
reverseIntercept,
@@ -100,19 +93,11 @@ public static void Evaluate(
10093
out conductance);
10194
}
10295

103-
// The forward on-line crosses zero current at Vfwd:
104-
// current = Gon * voltage - Gon * Vfwd.
105-
double onIntercept = -onConductance * forwardVoltage;
106-
107-
// Find the voltage where the off-line and the forward on-line meet.
108-
// This is close to Vfwd when Roff is large, but computing it keeps the
109-
// model continuous for any legal Roff/Gmin.
110-
double forwardBoundary = FindIntersection(
111-
offConductance,
112-
0.0,
113-
onConductance,
114-
onIntercept,
115-
forwardVoltage);
96+
// The forward on-line is anchored to the off-line current at Vfwd.
97+
// With finite Roff, LTspice keeps the nominal knee at Vfwd rather
98+
// than moving it to the mathematical line intersection.
99+
double onIntercept = (offConductance - onConductance) * forwardVoltage;
100+
double forwardBoundary = forwardVoltage;
116101

117102
// Avoid a second transition evaluation in the off or reverse regions.
118103
// LTspice-style forward epsilon starts at the boundary and extends
@@ -141,32 +126,6 @@ public static void Evaluate(
141126

142127
}
143128

144-
/// <summary>
145-
/// Finds where two line segments in <c>current = slope * voltage + intercept</c> form intersect.
146-
/// </summary>
147-
/// <param name="leftSlope">The slope of the first line.</param>
148-
/// <param name="leftIntercept">The intercept of the first line.</param>
149-
/// <param name="rightSlope">The slope of the second line.</param>
150-
/// <param name="rightIntercept">The intercept of the second line.</param>
151-
/// <param name="fallback">The voltage to use if the lines are nearly parallel.</param>
152-
/// <returns>The intersection voltage.</returns>
153-
private static double FindIntersection(
154-
double leftSlope,
155-
double leftIntercept,
156-
double rightSlope,
157-
double rightIntercept,
158-
double fallback)
159-
{
160-
double denominator = leftSlope - rightSlope;
161-
162-
// Parallel or nearly parallel regions do not give a useful numerical
163-
// knee. The caller provides the physically meaningful nominal boundary.
164-
if (Math.Abs(denominator) <= 1e-30)
165-
return fallback;
166-
167-
return (rightIntercept - leftIntercept) / denominator;
168-
}
169-
170129
/// <summary>
171130
/// Evaluates either a hard or smoothed transition between two linear current regions.
172131
/// </summary>

src/SpiceSharpParser.Tests/CustomComponents/IdealDiodeLtspiceGoldenTests.cs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,103 @@ private static IReadOnlyList<LtspiceIdealDiodeCase> CreateCases()
131131
},
132132
1e-6,
133133
1e-3),
134+
135+
new LtspiceIdealDiodeCase(
136+
"rrev_omitted_with_reverse_limit_and_m_n",
137+
"D1 in 0 did m=2 n=3",
138+
".model did D(Ron=1.5 Roff=1e10 Vfwd=0.6 Vrev=1.2 RevIlimit=2.5)",
139+
-12.0,
140+
6.0,
141+
0.2,
142+
parameters =>
143+
{
144+
parameters.ParallelMultiplier = 2.0;
145+
parameters.SeriesMultiplier = 3.0;
146+
parameters.OnResistance = 1.5;
147+
parameters.OffResistance = 1e10;
148+
parameters.ForwardVoltage = 0.6;
149+
parameters.ReverseVoltage = 1.2;
150+
parameters.ReverseCurrentLimit = 2.5;
151+
},
152+
1e-6,
153+
1e-3),
154+
155+
new LtspiceIdealDiodeCase(
156+
"forward_current_limit_and_parallel_cells",
157+
"D1 in 0 did m=4",
158+
".model did D(Ron=0.25 Roff=1e12 Vfwd=0.8 Ilimit=1.5)",
159+
-0.5,
160+
4.0,
161+
0.05,
162+
parameters =>
163+
{
164+
parameters.ParallelMultiplier = 4.0;
165+
parameters.OnResistance = 0.25;
166+
parameters.OffResistance = 1e12;
167+
parameters.ForwardVoltage = 0.8;
168+
parameters.ForwardCurrentLimit = 1.5;
169+
},
170+
1e-6,
171+
1e-3),
172+
173+
new LtspiceIdealDiodeCase(
174+
"reverse_limit_and_asymmetric_rrev",
175+
"D1 in 0 did",
176+
".model did D(Ron=3 Roff=1e11 Vfwd=0.5 Vrev=1.25 Rrev=0.75 RevIlimit=2.2)",
177+
-4.0,
178+
2.0,
179+
0.05,
180+
parameters =>
181+
{
182+
parameters.OnResistance = 3.0;
183+
parameters.OffResistance = 1e11;
184+
parameters.ForwardVoltage = 0.5;
185+
parameters.ReverseVoltage = 1.25;
186+
parameters.ReverseResistance = 0.75;
187+
parameters.ReverseCurrentLimit = 2.2;
188+
},
189+
1e-6,
190+
1e-3),
191+
192+
new LtspiceIdealDiodeCase(
193+
"moderate_roff_intersections",
194+
"D1 in 0 did",
195+
".model did D(Ron=2 Roff=20 Vfwd=1 Vrev=2 Rrev=4)",
196+
-6.0,
197+
4.0,
198+
0.1,
199+
parameters =>
200+
{
201+
parameters.OnResistance = 2.0;
202+
parameters.OffResistance = 20.0;
203+
parameters.ForwardVoltage = 1.0;
204+
parameters.ReverseVoltage = 2.0;
205+
parameters.ReverseResistance = 4.0;
206+
},
207+
1e-6,
208+
1e-3),
209+
210+
new LtspiceIdealDiodeCase(
211+
"combined_limits_asymmetric_reverse_and_m_n",
212+
"D1 in 0 did m=2 n=2",
213+
".model did D(Ron=1.25 Roff=1e10 Vfwd=0.4 Vrev=1.6 Rrev=2.2 Ilimit=3 RevIlimit=2)",
214+
-7.0,
215+
7.0,
216+
0.1,
217+
parameters =>
218+
{
219+
parameters.ParallelMultiplier = 2.0;
220+
parameters.SeriesMultiplier = 2.0;
221+
parameters.OnResistance = 1.25;
222+
parameters.OffResistance = 1e10;
223+
parameters.ForwardVoltage = 0.4;
224+
parameters.ReverseVoltage = 1.6;
225+
parameters.ReverseResistance = 2.2;
226+
parameters.ForwardCurrentLimit = 3.0;
227+
parameters.ReverseCurrentLimit = 2.0;
228+
},
229+
1e-6,
230+
1e-3),
134231
};
135232
}
136233

src/SpiceSharpParser.Tests/CustomComponents/IdealDiodeTests.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,31 @@ public void Op_WhenReverseBreakdown_UsesReverseResistance()
5858
AssertClose(-1.0, current, 1e-9);
5959
}
6060

61+
[Fact]
62+
public void Op_WhenOffResistanceIsModerate_AnchorsConductingLinesAtNominalThresholds()
63+
{
64+
double reverseCurrent = RunOpCurrent(-6.0, diode =>
65+
{
66+
diode.Parameters.OnResistance = 2.0;
67+
diode.Parameters.OffResistance = 20.0;
68+
diode.Parameters.ForwardVoltage = 1.0;
69+
diode.Parameters.ReverseVoltage = 2.0;
70+
diode.Parameters.ReverseResistance = 4.0;
71+
});
72+
73+
double forwardCurrent = RunOpCurrent(4.0, diode =>
74+
{
75+
diode.Parameters.OnResistance = 2.0;
76+
diode.Parameters.OffResistance = 20.0;
77+
diode.Parameters.ForwardVoltage = 1.0;
78+
diode.Parameters.ReverseVoltage = 2.0;
79+
diode.Parameters.ReverseResistance = 4.0;
80+
});
81+
82+
AssertClose(-1.1, reverseCurrent, 1e-9);
83+
AssertClose(1.55, forwardCurrent, 1e-9);
84+
}
85+
6186
[Fact]
6287
public void Op_WhenMultipliersAreSet_ScalesParallelAndSeries()
6388
{
@@ -926,8 +951,12 @@ private static string GetValidationMessages(SpiceSharpModel model)
926951

927952
private static void AssertClose(double expected, double actual, double tolerance)
928953
{
954+
double effectiveTolerance = Math.Abs(expected) > 1e-6
955+
? Math.Max(tolerance, (Math.Abs(expected) * 2e-9) + 5e-9)
956+
: tolerance;
957+
929958
Assert.True(
930-
Math.Abs(expected - actual) <= tolerance,
959+
Math.Abs(expected - actual) <= effectiveTolerance,
931960
$"Expected {expected:R}, got {actual:R}.");
932961
}
933962

src/docs/articles/ideal-diode.md

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -293,16 +293,16 @@ if Vrev was given:
293293
rrev = Rrev was given ? Rrev : Ron
294294
grev = 1 / rrev
295295
296-
reverse line = grev * v + grev * vrev
296+
reverse line = grev * v + (grev - goff) * vrev
297297
off line = goff * v
298-
boundary = intersection(reverse line, off line)
298+
boundary = -vrev
299299
300300
current, conductance =
301301
transition(v, boundary, RevEpsilon, reverse line, off line)
302302
303-
forward line = gon * v - gon * vf
303+
forward line = gon * v + (goff - gon) * vf
304304
off line = goff * v
305-
boundary = intersection(off line, forward line)
305+
boundary = vf
306306
307307
if v is past the forward boundary, or inside the forward smoothing window:
308308
current, conductance =
@@ -321,27 +321,17 @@ The forward transition is evaluated last so positive forward conduction replaces
321321
the off result when the voltage reaches the forward knee.
322322

323323
The transition helper is shared by both knees. It receives the line on the
324-
low-voltage side, the line on the high-voltage side, the boundary where the two
325-
unsmoothed lines intersect, and the optional smoothing width. For reverse
326-
breakdown, the low-voltage side is the reverse line and the high-voltage side is
327-
the off line. For forward conduction, the low-voltage side is the off line and
328-
the high-voltage side is the forward line.
329-
330-
The boundary is computed by solving the two-line equation:
331-
332-
```text
333-
leftSlope * v + leftIntercept = rightSlope * v + rightIntercept
334-
```
335-
336-
or:
337-
338-
```text
339-
v = (rightIntercept - leftIntercept) / (leftSlope - rightSlope)
340-
```
341-
342-
If the two slopes are nearly equal, the code uses the nominal knee as a fallback:
343-
`-Vrev` for reverse breakdown or `Vfwd` for forward conduction. That avoids an
344-
unstable division when two regions are almost parallel.
324+
low-voltage side, the line on the high-voltage side, the nominal LTspice knee,
325+
and the optional smoothing width. For reverse breakdown, the low-voltage side is
326+
the reverse line and the high-voltage side is the off line. For forward
327+
conduction, the low-voltage side is the off line and the high-voltage side is
328+
the forward line.
329+
330+
With finite `Roff`, the conducting lines are anchored to the off-line current at
331+
the nominal threshold. Forward conduction is continuous with the off line at
332+
`Vfwd`, and reverse breakdown is continuous with the off line at `-Vrev`.
333+
LTspice keeps those nominal knees rather than moving the transition to the
334+
mathematical intersection of the two unsmoothed lines.
345335

346336
With no smoothing, the transition helper simply chooses one line. With smoothing,
347337
the epsilon value is treated as a one-sided LTspice-style width. Forward
@@ -391,9 +381,9 @@ The forward, off, and reverse-breakdown lines are:
391381

392382
$$
393383
\begin{aligned}
394-
i_{\text{on}}(v) &= g_{\text{on}} \cdot (v - V_f) \\
384+
i_{\text{on}}(v) &= g_{\text{on}} \cdot (v - V_f) + g_{\text{off}} \cdot V_f \\
395385
i_{\text{off}}(v) &= g_{\text{off}} \cdot v \\
396-
i_{\text{rev}}(v) &= g_{\text{rev}} \cdot (v + V_{\text{rev}})
386+
i_{\text{rev}}(v) &= g_{\text{rev}} \cdot (v + V_{\text{rev}}) - g_{\text{off}} \cdot V_{\text{rev}}
397387
\end{aligned}
398388
$$
399389

@@ -411,8 +401,8 @@ the present voltage:
411401
$$
412402
i_{\text{raw}}(v) =
413403
\begin{cases}
414-
i_{\text{rev}}(v), & \text{if } V_{\text{rev}} \text{ is enabled and } v < v_{\text{rev,boundary}} \\
415-
i_{\text{off}}(v), & \text{if } v < v_{\text{fwd,boundary}} \\
404+
i_{\text{rev}}(v), & \text{if } V_{\text{rev}} \text{ is enabled and } v < -V_{\text{rev}} \\
405+
i_{\text{off}}(v), & \text{if } v < V_f \\
416406
i_{\text{on}}(v), & \text{otherwise}
417407
\end{cases}
418408
$$
@@ -429,18 +419,18 @@ $$
429419
g_{d,\text{raw}} \in \{g_{\text{rev}}, g_{\text{off}}, g_{\text{on}}\}
430420
$$
431421

432-
The forward transition point is the intersection of the off line and the on
433-
line:
422+
The forward transition point is the nominal LTspice threshold:
434423

435424
$$
436-
v_{\text{fwd,boundary}} = \frac{g_{\text{on}} \cdot V_f}{g_{\text{on}} - g_{\text{off}}}
425+
v_{\text{fwd,boundary}} = V_{\text{fwd}}
437426
$$
438427

439-
When `Roff` is much larger than `Ron`, `g_off` is tiny compared with `g_on`,
440-
so this is very close to:
428+
The forward line is shifted by the off-line current at `Vfwd`, so finite `Roff`
429+
adds a small term:
441430

442431
$$
443-
v_{\text{fwd,boundary}} \approx V_{\text{fwd}}
432+
i_{\text{on}}(v) = \frac{v - V_{\text{fwd}}}{R_{\text{on}}}
433+
+ \frac{V_{\text{fwd}}}{R_{\text{off}}}
444434
$$
445435

446436
### Reverse Breakdown
@@ -457,17 +447,18 @@ $$
457447
i \approx \frac{v + V_{\text{rev}}}{R_{\text{rev}}}
458448
$$
459449

460-
The exact reverse transition point is the intersection of the reverse line and
461-
the off line:
450+
The reverse transition point is the nominal LTspice threshold:
462451

463452
$$
464-
v_{\text{rev,boundary}} = -\frac{g_{\text{rev}} \cdot V_{\text{rev}}}{g_{\text{rev}} - g_{\text{off}}}
453+
v_{\text{rev,boundary}} = -V_{\text{rev}}
465454
$$
466455

467-
When `Roff` is much larger than `Rrev`, this is very close to:
456+
The reverse line is shifted by the off-line current at `-Vrev`, so finite
457+
`Roff` adds a small term:
468458

469459
$$
470-
v_{\text{rev,boundary}} \approx -V_{\text{rev}}
460+
i_{\text{rev}}(v) = \frac{v + V_{\text{rev}}}{R_{\text{rev}}}
461+
- \frac{V_{\text{rev}}}{R_{\text{off}}}
471462
$$
472463

473464
Example:

0 commit comments

Comments
 (0)