When a transient step fails to converge, DCtran divides CKTdelta by 8
and tries again. With PREDICTOR undefined (the default), nothing puts
CKTrhsOld back, so the retry starts from the last iterate of the attempt
that just failed. If that iterate ran away (to NaN, or just far off),
every retry inherits it and fails the same way, down to "timestep too
small". The device named in the message is then meaningless: it is just
the first one checked.
Reproduce, with the ngspice program (no shared library, no callbacks):
ngspice -b -r /dev/null retry-stall.cir
Stock ngspice 47 stops at 37 ms:
Timestep too small; time = 0.0370052, timestep = 1.25e-16:
trouble with node "n5"
With the attached patch it runs the whole 2 s.
The deck is part of a valve guitar amplifier simulation: a 12AX7 driven at 12 V
peak, direct-coupled into a second 12AX7 and a 12AT7 long-tailed pair.
It was cut down automatically from the whole amplifier, keeping only cuts
that left every node a DC path to ground and still stopped stock 47
mid-run. Some of the remaining resistors carry no current, and removing
any one of them moves or removes the stop. I think that is the point:
whether it stops depends on rounding, not on the circuit. At 12 and 16 V
peak it stops; at 2-10 V it runs. The whole amplifier stops at every
drive level I tried (2-12 V), and with the patch runs at all of them.
I can send it if useful.
What happens, traced in the full amplifier at a 100 us step: the first
attempt runs 85 Newton iterations without converging and its iterate
goes NaN at every node. Each retry then runs 101 iterations (NIiter's
floor of 100) and fails, down to a delta of 1.25e-16. Each run named a
different grid diode. Up to the first non-convergence, stock and patched
builds follow the identical path step for step; the patched one then
converges on its first retry. Wall time is the same wherever both run,
so retries are rare and cheap.
Why only some devices are hit: before every attempt, retries included,
DCtran sets MODEINITPRED, and the built-in semiconductors (dioload.c and
the rest) then predict their junction voltages from their accepted
states, so they never see the failed iterate. B sources don't:
asrcload.c reads CKTrhsOld unconditionally, and so starts each retry
from wherever the failed attempt ended. The patch gives every device
what MODEINITPRED already gives the built-in ones.
The patch adds CKTrhsAccepted to CKTcircuit, allocated in NIreinit
beside the other rhs vectors and freed in NIdestroy (with CKTrhsSize, as
KLU's rhs length isn't SMPmatSize). DCtran copies CKTrhs into it after
each CKTaccept (the operating point passes through there too) and copies
it back into CKTrhsOld before a non-convergence retry. Every time, not
only for NaN: a finite runaway iterate poisons a retry just as surely.
For circuits of built-in devices only it should change little, since in
MODEINITPRED they don't read CKTrhsOld on the first iteration.
Tested: make check passes 58 of 58, the same as unpatched 47. The
deck above and the whole amplifier at six drive levels all run to the
end, in the same wall time as before wherever unpatched 47 ran.
Limits, said up front: this changes how every transient recovers from a
failed step, and I don't know how much of make check exercises a retry,
so it needs your wider circuits too. It might reasonably sit behind an
option, like linesearch, rather than be the default. It is not the same
as linesearch: that damps the Newton steps within an attempt, while this
changes where the next attempt starts. Nor is it #695/#824 (the B-source
convergence test): that is about when an attempt counts as converged;
this is about where the next one starts.
Patch made against pre-master-48 as of 9 Oct (after f4f9cbdf); built
and tested on 47 (the patched code is unchanged there), macOS 15,
x86_64.
Sorry, I forgot to include the deck in the orignal post. hmm.
Thanks for the patch. I can reproduce the issue. Indeed will need a lot of testing.
A first quick test (MS Widows 11, pre-master-48 patched), our so called simple paranoia test (about 80 different input files, very short runtime each): no extra errors, runs faster than ever (31s versus 38s). Wow, this is interesting.
NIiter() raises every iteration limit below 100 to 100, so ITL4 (and ITL2) are silently ignored and transient timestep control no longer matches SPICE3
Description
src/maths/ni/niiter.c, start of NIiter() (lines 37-39 in 44.2, unchanged on master):
/ some convergence issues that get resolved by increasing max iter /
if (maxIter < 100)
maxIter = 100;
This was added in commit 6d5a4474 (2007-10-10, "Fixed minimum iterations number to 100 (Phil Barker)"). Since then it has only been reformatted. Berkeley SPICE3F5's NIiter() has no such floor.
Every caller goes through it, so any limit below 100 is replaced without warning:
ITL4 (CKTtranMaxIter, default 10): dctran.c calls NIiter(ckt, ckt->CKTtranMaxIter) for each timepoint, and the call actually runs up to 100 iterations. The documented default of 10 never takes effect, and neither does any user value below 100.
ITL2 (CKTdcTrcvMaxIter, default 50): used by dctrcurv.c and by the gmin/source stepping in cktop.c, which also get 100.
ITL1 (default 100): only affected when the user sets it lower. This was reported in the forum thread "option itl1=<num> doesn't work on ngspice 44" (March 2025, https://sourceforge.net/p/ngspice/discussion/133842/thread/d1c7df6e5c/), where the floor was identified as the cause.</num>
Why it matters for transient
In SPICE3, ITL4 is part of timestep control as well as a convergence limit. When a timepoint fails to converge within ITL4 iterations, DCtran cuts the step by 8 and retries. That is the intended recovery: a smaller step gives a better predictor, so it converges in a few iterations.
With the floor, each failed attempt runs about 100 Newton iterations instead of 10 before the step is cut:
Runs take roughly 10x more iterations per rejected step. If the iterate has diverged, they simply grind to 100.
An iterate is allowed to wander 100 iterations away from the accepted solution. With some devices (for example B sources, see #879), the retry then starts from that state. Ticket #879's trace shows each retry "runs 101 iterations (NIiter's floor of 100)" and fails.
Accepted timepoints, and the timestep sequence, differ from SPICE3F5 and from a user's explicit .options itl4=. This makes ngspice hard to use as a reference for timestep-control work, and it is how I found this.
Suggested fix
Remove the floor. The defaults (ITL1 = 100, ITL2 = 50, ITL4 = 10) are already set in cktinit.c and cktntask.c, and users who need more iterations can set them in .options. If the floor is kept for compatibility, apply it only to the DC operating point, or make it an option. Either way, a user-specified ITLx should be honoured.
Version
ngspice 44.2 source; also present on master.
Back on topic, I think it may make sense to fix this in the controlled-source code and avoid copying a potentially-large vector on every iteration. But there may be instances of the bug in other devices.
Thanks. On cost: the patch copies once per accepted timepoint (after CKTaccept), not per Newton iteration, and copies back only before a retry — one memcpy beside at least one load and factor per timepoint, and Holger's paranoia run got faster. A fix in asrcload.c would need the B source to keep its accepted controlling values in its states and use them in MODEINITPRED, as dioload.c does. I'm happy to try that if you prefer it, but as you say other devices may have the same bug, which is why I put it in DCtran.
I have run a CMOS circuit with 37520 MOS devices (PSP103 model via OSDI), with 270 accepted time steps.
with new retry total analysis time 81.8s, without 82.6s
which is equal within the typical run statistics on MS Windows.
with additionally removing the maxIter floor 100
with new retry total analysis time 86.3s, without 87.2s, accepted time steps are now 299, so a little slower!
Thanks for remove maxIter. It's been a bug bear of mine for a while, and I had thought I posted a PR about years ago. But, it turned out I hadn't hence when I thought about it last week and found this PR I hijacked it.
Whilst my maxIter input was a little off-topic, I think the B src predictor bug is why I have never trusted them in any simulations for many years. They have always seemed to struggle in any circuit with real devices in and this predictor bug is very likely the culprit.
FYI - I have been dabbling on and off in SPICE itself for decades now - the first version I did serious work with was XSPICE adding device models.
A couple of years back I got back into it, and ended up doing a ground up MATLAB version. In that process I traced the ins and outs of the code and the original papers - and dctran.c is basically a port of the same from Fortran with more complex memory manangent. The various values (timestep/8) are all heuristics from the early 1970s test circuits.
From bitter experience whilst doing my MATLAB version, the predictor initialiser is absolutely critical to any circuits with non-linearities in them. They are basically numerical hacks and heuristics to give the Newton Raphson a "close enough" starting point.
The managment of the saved states and roll back of failed transient steps is the hardest part and the SPICE3 code is opaque. In particular managing integrator state is horrible! I parked my MATLAB exploration when I realised it doesn't get the management quite right. But that was before AI and at some point I will return to it.
Anyway, do reach out if you want to pick my brains or are interested in the analysis of the original papers.
AI notes on the importance of the ITL4 = 10 below. Note that the on the average (p.196) refers the handfull of test circuits they used when writing SPICE in 1974. Think how many of the PCBs and IC in the electronics you are using right now to read this message are based on that small set of circuits! Of course, HSPICE and SPECTRE etc. will have moved away from this a long long time ago.
SPICE's transient timestep control was designed as a pair of mechanisms (Nagel, SPICE2: A Computer Program to Simulate Semiconductor Circuits, ERL-M520, 1975):
He tuned those numbers empirically:
SPICE3F5 keeps exactly this structure. In
src/lib/ckt/dctran.c:329-341,NIiter(ckt, ckt->CKTtranMaxIter)is called, and on non-convergence the step is undone, counted as rejected, andCKTdelta = CKTdelta/8.So "10" and "/8" are a tuned pair. The iteration limit is the detector for "this step is too big for Newton to track the nonlinearity", and the /8 cut is the remedy. With a limit of 100, the detector fires ten times later, by which point the iterate has often wandered far from the solution or gone to NaN.
I have found others circuits, e.g. a 256 bit full adder with BSIM4, which up to now did not converge. Steven's patch about CKTrhsAccepted helped to achieve convergence. I did not find any of my test circuits suffering, also no speed degradation. So I will put this patch onto our git development branch pre-master-48.
It is correct that the maxIter floor may reduce flexibility in choosing optimum parameters. However simply removing it leads to failure of some legacy circuit. So I will remove it but set the defaults to 100.