From cde49b6b4ed1b3da04353a8441430237587c1ca2 Mon Sep 17 00:00:00 2001 From: esaruoho Date: Mon, 2 Mar 2026 15:43:34 +0200 Subject: [PATCH] improve op amp convergence for differentiator circuits (#82) Two changes to OpAmpElm.doStep(): 1. Add voltage limiting: clamp the differential input voltage change between iterations to prevent Newton-Raphson overshoot. With gain=100000, the linear region is only ~0.3mV wide, so large voltage steps cause the iteration to bounce between saturation states indefinitely. 2. Replace random getrand(4) escape mechanism with a deterministic subIterations-based check. The random 25% chance of switching saturation direction introduced nondeterministic behavior that destabilized otherwise-converging iterations in high-gain circuits like differentiators with fast transient inputs. Co-Authored-By: Claude Opus 4.6 --- .../circuitjs1/client/OpAmpElm.java | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/com/lushprojects/circuitjs1/client/OpAmpElm.java b/src/com/lushprojects/circuitjs1/client/OpAmpElm.java index f1c397c83..c66c13902 100644 --- a/src/com/lushprojects/circuitjs1/client/OpAmpElm.java +++ b/src/com/lushprojects/circuitjs1/client/OpAmpElm.java @@ -165,18 +165,29 @@ void stamp() { void doStep() { double vd = volts[1] - volts[0]; double midpoint = (maxOut+minOut)*.5; - if (Math.abs(lastvd-vd) > .1) + double maxAdj = maxOut-midpoint; + double minAdj = minOut-midpoint; + + // limit voltage change to prevent Newton-Raphson overshoot + // in high-gain circuits (e.g., differentiators with fast edges) + double maxVdStep = Math.abs(maxAdj/gain) * 100; + if (maxVdStep < .1) maxVdStep = .1; + if (Math.abs(vd - lastvd) > maxVdStep) { + vd = lastvd + Math.signum(vd - lastvd) * maxVdStep; + sim.converged = false; + } else if (Math.abs(lastvd-vd) > .1) sim.converged = false; else if (volts[2] > maxOut+.1 || volts[2] < minOut-.1) sim.converged = false; double x = 0; double dx = 0; - double maxAdj = maxOut-midpoint; - double minAdj = minOut-midpoint; - if (vd >= maxAdj/gain && (lastvd >= 0 || app.getrand(4) == 1)) { + // use deterministic check instead of random getrand(4) to avoid + // nondeterministic oscillation between saturation states + boolean allowSwitch = (sim.subIterations > 20); + if (vd >= maxAdj/gain && (lastvd >= 0 || allowSwitch)) { dx = 1e-4; x = maxOut - dx*maxAdj/gain; - } else if (vd <= minAdj/gain && (lastvd <= 0 || app.getrand(4) == 1)) { + } else if (vd <= minAdj/gain && (lastvd <= 0 || allowSwitch)) { dx = 1e-4; x = minOut - dx*minAdj/gain; } else {