Skip to content

Releases: javaNoviceProgrammer/Ngspice_OpenVAF_Enhancements

Release list

Enhancement-888: ordfix says it runs without truncation control, and a diverging run under it stops — Adams at ordfix=3 ended a stiff network at 3e201 V with exit 0

Choose a tag to compare

Enhancement-888: ordfix says it runs without truncation control, and a diverging run under it stops — Adams at ordfix=3 ended a stiff network at 3e201 V with exit 0

Scope: N6 of the
transient stress campaign of 2026-10-12.

ngspice: spicelib/analysis/dctran.c: a note at the start of an ordfix run, and at each
accepted point of one a node past 1e15 V ends the run.

examples/transnotes_examples/ (new, shared with E-885,
E-886 and E-887); examples/transopts_examples/
check [5] reads E-882's note by its own text. ngspice only.

Suites:

  • transnotes_examples: [9] to [11] are this enhancement's,
    11 of 11 per solver. On the E-883 binaries [9] and [10] fail; [11] is a control.
  • transopts (7 of 7), corenum, integmethod, maxordnote and dynorder pass.
  • The full sweep: 587 of 587.

What was wrong

ordfix (Enhancement-181) is a verification pin. It fixes the
integration order and accepts every converged step: the step grows to tmax, and no truncation
estimate checks it. A run said nothing of this. At an order that is not A-stable, Gear or Adams
at 3 and above, a stiff network grew without bound. One of Family A's networks (seed 1017, nine
nodes, four capacitors, nine resistors) under method=adams maxord=3 ordfix=3:

  • ended at −3e201 V at its stop time, with exit 0 and no message;
  • run ten times longer, reached 5e305 V after 730 529 iterations, still with exit 0.

Over Family A the error reached 5e206 times trap's.

The change

An ordfix run says what it is:

Note: ordfix=3 accepts every converged step: the step grows to tmax, and no truncation-error estimate checks it.
Note: order 3 of adams is not A-stable: on a stiff circuit the result can grow without bound at a fixed order. A run whose node passes 1e+15 V stops.

The second note is printed only for Gear and Adams pinned at 3 and above.

At each accepted point of an ordfix run, a node voltage past 1e15 V, or not a number, ends the
run. This is E-679's bound for a node held only by gmin:

TRAN:  time = 1.26652e-05: the solution is diverging -- node '5' has reached -1.21601e+15 V.
    ordfix=3 accepts every converged step, with no truncation-error control. Run without ordfix, or pin an order that is A-stable (gear or adams at 2 at most).

The same network at gear ordfix=2 runs to the end, at −0.917 V, with the first note only.

transopts [5] (E-882) checked that ordfix=4 at maxord=4 printed no
ordfix= note; it now reads E-882's note by its own text, since this note is printed either way.

The checks

transnotes_examples:

  • [9] An ordfix run says every converged step is accepted with no truncation-error estimate;
    adams ordfix=3 also says the order is not A-stable, and gear ordfix=2 does not.
  • [10] The stiff network at adams ordfix=3 stops when a node passes 1e15 V.
  • [11] (control) At gear ordfix=2 the same network runs to the end.

Limits

The bound catches a divergence that reaches 1e15 V. A run whose error grows but stays below it,
such as trbdf2 ordfix=2 73 % off on one of Family A's networks at a tmax too coarse for it,
ends normally: that is what ordfix without truncation control does, and the first note says so.

Write-up: enhancements_doc/Enhancement-888.md

Enhancement-887: an option given while a run is paused is applied or held, and said — option maxord=3 or option klu before resume crashed a run started by run, and a run started by tran ignored every option without a word

Enhancement-887: an option given while a run is paused is applied or held, and said — option maxord=3 or option klu before resume crashed a run started by run, and a run started by tran ignored every option without a word

Scope: N5 of the
transient stress campaign of 2026-10-12.

ngspice:

  • frontend/spiceif.c, if_run: the options a run starts with are kept; on resume the ones
    changed since are applied or held, with a note.
  • include/ngspice/ftedefs.h: ci_runopts.
  • frontend/runcoms2.c: freed with the circuit.

examples/transnotes_examples/ (new, shared with E-885,
E-886 and E-888). ngspice only.

Suites:

  • transnotes_examples: [5] to [8] are this enhancement's,
    11 of 11 per solver. On the E-883 binaries [5] to [8] fail.
  • checkpoint, ckorder, resumebreak, stagedslots, autosave, idtassert and writemc
    pass.
  • The full sweep: 587 of 587.

What was wrong

option writes the default task's options. What a paused run did with them depended on how the
run started.

  • Started by run (the deck's own .tran): the run's task IS the default task, and
    CKTdoJob copies every option from it again at a resume. A new method or maxord met history
    slots the run had not allocated, and option klu met a matrix built for Sparse:

    • option maxord=3, option method=gear maxord=4 dynorder: a segmentation fault in CAPload;
    • option klu: one in NIiter.

    option method=gear switched the method mid-run, and option reltol=1e-6 was taken. None of
    it was said.

  • Started by tran (from .control): the run has its own copy of the task, made at the
    command, and nothing given during the pause reached it. Method, maxord, dynorder, the
    solver and reltol all gave the uninterrupted run's 1 346 iterations, without a word. This is
    what the campaign saw: its sessions started their runs with tran.

The change

Both routes now resume alike. When a run starts, if_run keeps a copy of its options; at
resume it compares the default task, where option writes, with that copy. So an option set
before the run is not reported, and only what changed during the pause is.

  • Taken by the resumed run: the options read afresh at every step. These are reltol,
    abstol, vntol, chgtol, trtol, xmu, trgamma, itl4, gmin, gshunt, pivtol,
    pivrel, absdv, reldv, bypass, and the Newton aids nodedamping, linesearch and
    trustregion.
    Note: the resumed run takes reltol = 1e-06 and trtol = 0.1, given during the pause.
    
  • Held at the run's values: the options the run was built on. These are the method,
    maxord, ordfix, dynorder, temp, tnom, cshunt and the solver with its klu_*
    settings. They take effect from the next analysis.
    Note: method = gear, maxord = 4 and dynorder on, given during the pause, take effect from the next analysis; the resumed run keeps method = trap, maxord = 2 and dynorder off, which it was built on.
    
    On the run route the default task gets the values given back after the resumed run, for the
    next analysis. A held option is named once, however many pauses follow.

A checkpoint's continuation (loadstate) starts from the options set at that moment, which
E-879 built its circuit on. It compares nothing, and is the start a later
pause compares against.

Measured

On an RC with an inductor, stopped at 1 µs of 4 µs, with delete all before the resume:

given during the pause E-883, run E-883, tran E-887, both
maxord=3 segmentation fault kept, silently kept, named; 4.44486e-7, the uninterrupted value
klu segmentation fault kept, silently kept, named
method=gear maxord=4 dynorder segmentation fault kept, silently kept, named
method=gear switched mid-run, silently kept, silently kept, named
reltol=1e-6 trtol=0.1 taken (3 385 iterations), silently ignored (887) taken on both, named; 4.51376e-7

The next analysis after the resumed run, with method=gear maxord=3 klu given during the pause,
ends at 4.212264e-7 on both routes. That is the value of a fresh run with those options.

The checks

transnotes_examples:

  • [5] A run started by run, given maxord=3, the other solver, and
    method=gear maxord=4 dynorder during a pause, resumes without a crash. It names each with the
    value it keeps, and ends where the uninterrupted run ends.
  • [6] A run started by tran names and keeps them the same way.
  • [7] reltol and trtol given during the pause are taken on both routes, which say so and
    end at the same value.
  • [8] The options held for the resumed run take effect at the next analysis, on both routes.

Write-up: enhancements_doc/Enhancement-887.md

Enhancement-886: newtrunc and its three tolerances say they have no effect, and what is used instead — ltereltol, lteabstol and ltetrtol were taken without a word

Choose a tag to compare

Enhancement-886: newtrunc and its three tolerances say they have no effect, and what is used instead — ltereltol, lteabstol and ltetrtol were taken without a word

Scope: N4 of the
transient stress campaign of 2026-10-12.

ngspice: spicelib/analysis/cktsopt.c, CKTsetOpt: OPT_LTERELTOL, OPT_LTEABSTOL and
OPT_LTETRTOL warn in a build without PREDICTOR, and OPT_NEWTRUNC's warning says what runs
instead. Stock options.

examples/transnotes_examples/ (new, shared with E-885,
E-887 and E-888). ngspice only.

Suites:

  • transnotes_examples: [4] is this enhancement's, 11 of 11
    per solver. On the E-883 binaries [4] fails.
  • inputguard passes.
  • The full sweep: 587 of 587.

What was wrong

.option newtrunc selects a truncation estimate from node voltages: the difference between a
predictor and the corrector. The predictor is compiled in only by --enable-predictor
(PREDICTOR), which also changes every device's Newton start, and this build, like stock
ngspice's default, does not have it. newtrunc said so:

Warning: Option 'newtrunc' ignored,
    compilation with preprocessor flag 'PREDICTOR' is required.

Its three tolerances, ltereltol, lteabstol and ltetrtol, were stored and read by nothing,
and said nothing. Family A's newtrunc rows were trap rows.

The change

Warning -- Option newtrunc is ignored: the voltage-based truncation estimate needs the predictor, which this build does not have (ngspice configured with --enable-predictor has it). The estimate from the charges, set by reltol, abstol, chgtol and trtol, is used.

Warning -- Option ltereltol has no effect: it is a tolerance of newtrunc's voltage-based truncation estimate, which this build does not have (see newtrunc). The estimate from the charges, set by reltol, abstol, chgtol and trtol, is used.

lteabstol and ltetrtol say the same. newtrunc=0 and a deck without them say nothing. In a
build with PREDICTOR nothing changes.

The checks

transnotes_examples:

  • [4] newtrunc says it is ignored and that the charge estimate is used; ltereltol,
    lteabstol and ltetrtol each say they have no effect; a deck without them prints neither.

Write-up: enhancements_doc/Enhancement-886.md

Enhancement-885: the truncation estimate sees the first-order error of xmu below 0.5 — xmu=0 ran at trap's points with ten times trap's error

Choose a tag to compare

Enhancement-885: the truncation estimate sees the first-order error of xmu below 0.5 — xmu=0 ran at trap's points with ten times trap's error

Scope: N2 of the
transient stress campaign of 2026-10-12.

ngspice: spicelib/analysis/cktterr.c, terr: at trap's order 2 with xmu other than 0.5, the
first-order term of the local error is estimated as well. Stock behaviour.

examples/transnotes_examples/ (new, shared with E-886,
E-887 and E-888). ngspice only.

Suites:

  • transnotes_examples: [1] to [3] are this enhancement's,
    11 of 11 per solver. On the E-883 binaries [1] and [2] fail; [3] is a control.
  • transopts, corenum and integmethod pass.
  • The full sweep: 587 of 587.

What was wrong

xmu mixes the trapezoidal rule (0.5) with backward Euler (0). The step is the θ-method

(1 − xmu)·i(n) + xmu·i(n−1) = (q(n) − q(n−1))/h,

whose local error starts at (xmu − ½)·h²·q″. Below 0.5 it is a first-order method. The
truncation estimate kept trap's: its constant on the third divided difference, the h³ term, and a
square root. It sized the steps as if the method were second order, and since the damping lowers
the third difference, it took fewer points than trap.

On an RC with an inductor and a second capacitor, at a 100 ns output step, against a tight
reference:

xmu points rms error (of the range)
0.5 (trap) 177 2.76e-3
0.45 165 3.70e-3
0.3 153 1.13e-2
0.2 150 1.71e-2
0 147 2.94e-2
method=gear maxord=1 592 1.04e-2

xmu=0 is backward Euler, and backward Euler's own control took four times its points. Over the
campaign's Family A, xmu=0 was 106 times trap's error at the same points (median), and
xmu=0.2 up to 764 times.

The change

At trap's order 2 with xmu other than 0.5, the step is also limited by the first-order term,
estimated as backward Euler's is: the constant |0.5 − xmu| on the second divided difference, at
the first power. At xmu=0 that IS backward Euler's estimate. The step is the smaller of the two.
xmu=0.5 takes the trap path as before.

xmu points rms error
0.5 177 2.76e-3
0.45 196 3.59e-3
0.3 313 6.86e-3
0.2 426 8.13e-3
0 592 1.04e-2 — backward Euler's steps

The error now follows the method's order at the tolerance asked for. A damped trap is still a
first-order method, and costs what one costs.

The checks

transnotes_examples:

  • [1] xmu=0 takes backward Euler's steps: the points and the value of method=gear maxord=1,
    to rounding. The two are the same method computed two ways, trap's (q(n) − q(n−1))/h and
    Gear's ag0·q(n) + ag1·q(n−1). They agree to the bit on arm64; on x86-64 under Sparse they are
    one point apart in 589, and 4e-12 apart in value.
  • [2] xmu=0.2 takes more points than trap, and its error is below 1 % of the range.
  • [3] (control) xmu=0.5 is the default run.

Limits

TR-BDF2's first stage honours xmu too (E-419). Its estimate keeps
TR-BDF2's own second-order constant.

Write-up: enhancements_doc/Enhancement-885.md

Enhancement-884: built-in switches land on their crossing — a switch without hysteresis ended a charge pump with "Timestep too small" at reltol 1e-6, and a switch at a 100 ns step closed 11 ns late

Choose a tag to compare

Enhancement-884: built-in switches land on their crossing — a switch without hysteresis ended a charge pump with "Timestep too small" at reltol 1e-6, and a switch at a 100 ns step closed 11 ns late

Scope: N1 of the
transient stress campaign of 2026-10-12,
and the switching half of its N3.

ngspice:

  • spicelib/devices/sw/swtrunc.c and csw/cswtrunc.c: a switch that turned over in the point
    under way asks for the point to be redone at its crossing.
  • sw/swload.c and csw/cswload.c: the switch keeps its accepted state on that landing.
    cswload.c also stores the controlling current, which stock ngspice left out.
  • spicelib/analysis/cktterr.c: CKTlandRequest, the landing request any device makes.
  • spicelib/analysis/dctran.c: the retry takes the earliest request; a switch's landing is
    redone from the accepted solution, and the step after it starts at order 1.
  • osdi/osditrunc.c: Enhancement-759's Verilog-A landing goes through the
    same request.
  • include/ngspice/cktdefs.h, sw/swdefs.h, csw/cswdefs.h.

examples/swland_examples/ (new). ngspice only.

Suites:

  • swland_examples: 6 of 6 per solver. On the E-883 binaries [1]
    to [5] fail; [6] is a control.
  • argguard, ctrlnode, dcpath, gatecheck, sensrestore, solvercore and warnphysics
    (the suites with built-in switches) pass.
  • evtedge (24 of 24), simctrl, discontinuity and finalstep (E-759's landing) pass.
  • The full sweep: 587 of 587.

What was wrong

A charge pump, two switches with vh=0 driven by complementary 1 ns ramps, ran at
reltol=1e-3 and 1e-5 and ended "Timestep too small" at 1e-6 and 1e-7, under both
solvers. Family C of the campaign saw it in 34 of 40 charge-pump references (one more ran past
120 s) and 5 of 40 boost references. Any hysteresis seemed to cure it.

Nothing placed a point at a switch's crossing. The step that crossed was judged by the
truncation estimate, which at a crossing measures the jump in current the switching brings. The
point was rejected, and the retry, sized by that estimate, fell short of the crossing and was
accepted. The next point crossed again. The accepted points crept up on the crossing, each
closer than the last: 4.9984, 4.99994, 4.99998 … × 10⁻¹⁰ s. At reltol=1e-6 the steps reached
1e-16 s, where the rounding in Δq/h is larger than Newton's tolerance, and every retry failed to
converge. Hysteresis did not cure it: a vh=0.01 switch crept the same way, in 1e-14 s steps,
and got across.

At looser tolerance the crossing step was accepted on the grid. A built-in sw driven by a 1 µs
ramp, at a 100 ns step, closed 11.2 ns late and opened 19.7 ns early; E-759's table has the same
lag, 31.2 ns, on its own deck ("plain if, built-in sw: unchanged").
Trap integrated the crossing step with the new state at its end, and rang. The seed-9 charge
pump of Family C (N3) put its switch node at 2.66 V on a 2.49 V supply, and its output 1.1 %
off.

The change

A switch whose state changed between the accepted point and the attempt finds where its control
crossed the band edge that turned it over. The control is interpolated linearly between the two
points; the edge is vt + vh or vt − vh, by SWload's own rule, for either sign of vh. The
switch then asks for the attempt to be redone at the crossing, through E-759's landing protocol.
On that landing it keeps its accepted state. The circuit is integrated with the old state up to
the crossing and with the new one from it, and no gap is left to creep across. The landing is
redone from the accepted solution, not the rejected attempt's: a switch with hysteresis keeps
the state its first iterate took while the control stays in the band.

The step after the landing is the one the switch turns over in. It is taken at order 1, and at
a tenth of the landing step at most, as after a source's corner. No switch asks for a landing
from the landing itself, from the step after one, or for a crossing in the first thousandth of
the step.

CKTlandRequest takes the earliest request, and dctran uses it as the retry's step,
whatever a device type later in the table estimated. The switch comes before the OSDI devices
and after the capacitors in that table. E-759's OSDI request goes through it too; with OSDI
devices last, their landings are unchanged. Under ordfix the request replaces the pinned
step, which would have redone the point at twice the step and crossed again.

The current-controlled switch had no landing to make. CSWload did not store its controlling
current ("without this statement truncation is not used, as with SW. But test are needed"), so
CSWtrunc saw no change. It is stored now, for the landing. The approach limit that read it, which
never ran, is left out.

Measured

E-883 E-884
charge pump, vh=0, reltol=1e-6 / 1e-7 "Timestep too small" 4.243345 V / 4.243344 V, 80 landings (one per crossing)
the same, iterations at 1e-3 / 1e-5 42 042 / 50 004 44 616 / 46 474
sw, vh=0, 100 ns step: closes / opens (exact 500.00 / 1500.69 ns) 511.22 / 1480.98 ns 500.07 / 1500.78 ns
vh=0.1 (600.00 / 1600.69 ns) 591.22 / 1600.98 ns 600.22 / 1600.74 ns
vh=−0.1 (400.00 / 1400.69 ns) 391.22 / 1400.98 ns 400.22 / 1400.74 ns
csw (500.00 / 1500.69 ns) 511.22 / 1480.98 ns 500.07 / 1500.78 ns
seed-9 pump, default trap (tight: 1.183447 V) 1.196523 V 1.183490 V
its switch node's peak (supply 2.4932 V) 2.661 V 2.502 V
its reltol=1e-6 run, iterations 57 515 35 503
its method=trbdf2 run 1.198507 V 1.184133 V

Family C, rerun on both binaries (the three templates with switches):

  • Charge pump and boost references (trap, reltol=1e-6): all 80 end. On E-883, 39 ended
    "Timestep too small" and one ran past 120 s.
  • The 160 runs under random settings: 4 fail (were 11). Each of the four pins an order that
    is not A-stable with ordfix, as N6 describes (E-888).
  • The Schmitt trigger: all 120 runs end, on both binaries.

The waveforms were checked against each binary's tight reference, by the rms error over time.
On the charge pump the median error ratio, E-884 over E-883, is 0.33 (21 of 35 runs better by
more than 2×, 2 worse). On the Schmitt trigger it is 1.0 (16 better, 3 worse), and on the boost
0.99 (9 better, 3 worse). The runs that are worse are:

  • ordfix=6;
  • boost converters at loose tolerance whose result is set by trap's phase error on a ringing
    switch node. On seed 343 the two binaries differ by 18 % and 3 % at default tolerance, the
    other way round at reltol=1e-2, and agree to 0.01 V at 1e-4 and 1e-5.

The checks

swland_examples:

  • [1] The charge pump with vh=0 runs to the end at reltol=1e-6 and 1e-7.
  • [2] A vh=0 switch at a 100 ns step closes at 500 ns and opens at 1500 ns, within 0.3 ns.
  • [3] With vh=0.1 and vh=−0.1 it turns over at the band edge that applies, within 0.5 ns.
  • [4] A current-controlled switch lands on its crossing, within 0.3 ns.
  • [5] The seed-9 pump under default trap ends within 0.05 % of reltol=1e-6, and its switch
    node peaks within 1 % of the supply.
  • [6] (control) With vh=0.01 the pump runs at reltol=1e-6 and ends at 4.24334 V.

Limits

  • The crossing is found by linear interpolation of the control. A curved control lands near,
    not on, its crossing; the switch keeps its state there either way, and turns over in the next
    step.
  • The step after a landing takes the new state from its start. Its truncation estimate follows
    a long landing step, and a fast transient inside it (a picosecond time constant behind a 1 Ω
    switch) is crossed in one backward-Euler step rather than resolved; the point after it is
    where the transient ends.
  • trbdf2 and sdirk keep their own order across the landing; on the seed-9 pump trbdf2's
    switch node still peaks at 2.57 V.
  • An XSPICE digital or aswitch code model is not a built-in switch and does not land.

Write-up: enhancements_doc/Enhancement-884.md

Enhancement-883: an unknown integration method is named, and so is the method that runs instead — method=trbdf ran trap after "unsupported integration method"

Choose a tag to compare

Enhancement-883: an unknown integration method is named, and so is the method that runs instead — method=trbdf ran trap after "unsupported integration method"

Scope: D3 of the
transient stress campaign of 2026-10-12.

ngspice: spicelib/analysis/cktsopt.c, CKTsetOpt: OPT_METHOD with a name it does not know
warns and keeps the current method, instead of returning E_METHOD.

examples/transopts_examples/ (new, shared with E-881 and
E-882). ngspice only.

Suites:

  • transopts_examples: [6] is this enhancement's, 7 of 7 per
    solver. On the E-875 binaries [6] fails.
  • The full sweep: 585 of 585.

What was wrong

.option method=trbdf, a typo for trbdf2, printed

setAnalysisParm(options) ci_curOpt: unsupported integration method

and the run went on with trap. The message named neither the value nor the method that ran.
method=bdf and method=gear2 did the same.

The change

An unknown name is reported with the value, the names that exist and the method that is kept:

Warning -- Option method = trbdf is unknown (use trap, gear, trbdf2, sdirk or adams); ignored, keeping trap

After an option method=gear, an unknown name says keeping gear. The generic line is no longer
printed.

The checks

transopts_examples:

  • [6] method=trbdf names the value and the valid ones, and says trap is kept. After
    option method=gear, method=foo says gear is kept. The generic "unsupported integration
    method" line is gone.

Write-up: enhancements_doc/Enhancement-883.md

Enhancement-882: ordfix and reltol say when a value has no reading — ordfix=-1 and ordfix=100 were taken without a word, and so was reltol=10

Choose a tag to compare

Enhancement-882: ordfix and reltol say when a value has no reading — ordfix=-1 and ordfix=100 were taken without a word, and so was reltol=10

Scope: D2 of the
transient stress campaign of 2026-10-12.

ngspice:

  • spicelib/analysis/cktsopt.c, CKTsetOpt: OPT_ORDFIX refuses a negative value and caps one
    above 6, with a warning; OPT_RELTOL refuses 1 and more.
  • spicelib/analysis/dctran.c: a note when ordfix is above the order the method and maxord
    allow.

examples/transopts_examples/ (new, shared with E-881 and
E-883). ngspice only.

Suites:

  • transopts_examples: [3], [4], [5] and [7] are this
    enhancement's, 7 of 7 per solver. On the E-875 binaries [3] to [5] fail; [7] is a control.
  • inputguard (107 of 107) and corenum pass.
  • The full sweep: 585 of 585.

What was wrong

  • ordfix (Enhancement-181) pins the integration order: 0 is off, 1 to
    6 an order. ordfix=-1 was taken as off. ordfix=7, 100 and 1e9 were cut to the order
    ceiling. Neither said anything.
  • The ceiling (Enhancement-853) is maxord, or 2 under trap. So
    method=gear ordfix=4 at the default maxord=2 ran at order 2, also without a word.
  • reltol: reltol=1 and reltol=10 were taken. A relative tolerance of 100 % or more has no
    reading. Enhancement-426 refused only reltol <= 0.

The change

  • ordfix below 0 is refused. Above 6 it is set to 6, as maxord is:

    Warning -- Option ordfix = 7 is above 6, the highest integration order; 6 is used
    
  • At the start of a transient, an ordfix above the ceiling says so:

    Note: ordfix=4 is above the highest order allowed here (2, from maxord); the order is pinned at 2.
    
  • reltol of 1 or more is refused and the run keeps its value:

    Warning -- Option reltol = 10 must be less than 1; ignored, keeping 0.001
    

    reltol <= 0 keeps its "must be greater than zero".

The checks

transopts_examples:

  • [3] ordfix=-1 is refused; ordfix=7 and ordfix=1e9 warn that 6 is used.
  • [4] reltol=1 and reltol=10 are refused, and the run is the default run.
  • [5] Gear with ordfix=4 at the default maxord=2 says the order is pinned at 2; at
    maxord=4 it says nothing.
  • [7] (control) reltol=0 is refused as "must be greater than zero", as before.

Update (E-888). Every ordfix run now prints a note of its own, so
[5]'s "at maxord=4 it says nothing" reads: no note that the order is pinned at a ceiling. The
check looks for E-882's note by its text.

Write-up: enhancements_doc/Enhancement-882.md

Enhancement-881: xmu outside [0, 0.5] is refused — xmu=2 turned an answer of 3.757e-7 into −2.53e-7 without a word

Choose a tag to compare

Enhancement-881: xmu outside [0, 0.5] is refused — xmu=2 turned an answer of 3.757e-7 into −2.53e-7 without a word

Scope: D1 of the
transient stress campaign of 2026-10-12.

ngspice: spicelib/analysis/cktsopt.c, CKTsetOpt: OPT_XMU is range-checked. Stock option.

examples/transopts_examples/ (new, shared with E-882 and
E-883). ngspice only.

Suites:

  • transopts_examples: [1] and [2] are this enhancement's,
    7 of 7 per solver. On the E-875 binaries [1] fails; [2] is a control.
  • The full sweep: 585 of 585.

What was wrong

xmu weights the trapezoidal rule (0.5) against backward Euler (0). On an RC with an inductor
whose answer at 4 µs is 3.757e-7:

  • .option xmu=-1 gave 2.58e-6;
  • .option xmu=2 gave −2.53e-7.

Neither printed anything. Outside [0, 0.5] the formula is neither method. Every sibling
tolerance has been range-checked since Enhancement-426 and
Enhancement-447.

The change

A value outside [0, 0.5] is refused, and the run keeps the value it had:

Warning -- Option xmu = 2 must lie in [0, 0.5]; ignored, keeping 0.5

Both decks now give 3.757e-7. xmu=0 and xmu=0.3 are taken as before.

The checks

transopts_examples:

  • [1] xmu=-1 and xmu=2 are refused with a warning, and the run is the default run.
  • [2] (control) xmu=0 and xmu=0.3 are taken, and move the answer.

Write-up: enhancements_doc/Enhancement-881.md

Enhancement-880: a resumed transient keeps the breakpoint spacing it ran with — a resume, in memory or from a checkpoint, took a minbreak 5e6 times larger and stepped over edges the run had landed on

Choose a tag to compare

Enhancement-880: a resumed transient keeps the breakpoint spacing it ran with — a resume, in memory or from a checkpoint, took a minbreak 5e6 times larger and stepped over edges the run had landed on

Scope: F7 of the
transient stress campaign of 2026-10-12.

ngspice: spicelib/analysis/dctran.c: tran_minbreak, the run's rule for a minbreak that is not
given, used at the start, on a resume and on a checkpoint continuation. Stock ngspice-46
behaviour for the in-memory resume; the checkpoint branch of
Enhancement-131 copied it.

examples/resumebreak_examples/ (new). ngspice only.

Suites:

  • resumebreak_examples: 5 of 5 per solver. On the E-875
    binaries [1] to [3] fail; [4] and [5] are controls.
  • checkpoint, ckorder and stagedslots pass.
  • The campaign's Family D, rerun: no resumed run and no checkpoint continuation is more than 10×
    less accurate than the uninterrupted run, except one under ordfix=6. Before, there were 5
    and 4. Every one of those nine now matches the uninterrupted run's error.
  • The full sweep: 584 of 584.

What was wrong

A checkpoint continuation did not land where the uninterrupted run landed. On a network of the
campaign, driven by a pulse with an 82 fs rise, it was 0.19 off against the exact solution where
the uninterrupted run was 0.011. On an RC driven by a pulse with 10 fs edges, the uninterrupted run
landed on 17 of the 18 corners after the stop, and the continuation on 9. Every rise's end was
stepped over.

minbreak is the smallest spacing kept between breakpoints; a source skips a corner closer than
that. With no .option minbreak, a run starts with ten times delmin in a build with XSPICE, as
ours is, and with tmax/20 000 in a build without it. CKTdoJob puts the option's 0 back for every
job, a resume included. The resume branch of DCtran then re-derived it with tmax/20 000 in
either build, and the checkpoint branch was written the same way. On these decks that is 1e-12 s
against 2e-18 s, 5e6 times larger. The 10 fs edges fell under it, and the continuation skipped
the corners the run had landed on.

The checkpoint continuation is the in-memory resume, point for point, on both the E-875 and the
current binaries. The defect was the resume's, and F7's checkpoint cases were two of its faces.
Enhancement-877 fixed three of the five Family D resumes it explained (a
current source's chain). The other two, under ordfix and trbdf2, were this.

The change

tran_minbreak returns the run's rule: ten times delmin with XSPICE, tmax/20 000 without. A run's
start, a resume and a checkpoint continuation all take it when minbreak is not given. A given
minbreak is used as given, as before.

The RC continuation now lands on the same 17 corners as the run, and the campaign network's
continuation follows the uninterrupted run to 1e-6 of the range; it was 0.16.

A continuation is still not the uninterrupted run point for point. stop when time > t sets a
breakpoint at t, so the stopped run lands there and restarts its step. The continuation equals a
run with the same stop.

The checks

resumebreak_examples, every deck under .option sparse (checkpoints need it):

  • [1] An RC driven by a pulse with 10 fs edges: after the stop, a checkpoint continuation
    lands on every corner the uninterrupted run lands on.
  • [2] The in-memory resume: the same.
  • [3] The campaign network: the continuation follows the uninterrupted run to 1e-4 of the
    range.
  • [4] (control) The checkpoint continuation and the in-memory resume are the same run, point
    for point.
  • [5] (control) With .option minbreak=1p, the 10 fs edges' ends are skipped by the run and by
    the continuation alike.

Write-up: enhancements_doc/Enhancement-880.md

Enhancement-879: loadstate restores a checkpoint taken with any maxord, and a mismatch names the order — every checkpoint above maxord=2 was refused as "does not match this circuit (equations 4 vs 4, states 2 vs 2)"

Enhancement-879: loadstate restores a checkpoint taken with any maxord, and a mismatch names the order — every checkpoint above maxord=2 was refused as "does not match this circuit (equations 4 vs 4, states 2 vs 2)"

Scope: F6 of the
transient stress campaign of 2026-10-12.

ngspice: frontend/com_checkpoint.c, loadstate:

  • the integration method and maxord are copied from the task before the circuit is set up,
    with Enhancement-874's pin for the staged methods;
  • the mismatch message names the maximum order.

examples/ckorder_examples/ (new). ngspice only.

Suites:

  • ckorder_examples: 5 of 5 per solver. On the E-875 binaries
    [1] to [4] fail; [5] is a control.
  • checkpoint (22 of 22) and stagedslots pass.
  • The full sweep: 583 of 583.

What was wrong

* run part of a transient and save it; then, in a fresh process, load it
v1 a 0 pulse(0 1 0 1n 1n 1u 2u)
r1 a b 1k
c1 b 0 1n
.option sparse method=gear maxord=3
.tran 10n 4u
.control
stop when time > 1.5u
run
savestate ck.bin              ; the second deck has `loadstate ck.bin` here
.endc
.end
Error: loadstate: checkpoint does not match this circuit (equations 4 vs 4, states 2 vs 2).

The same happened with maxord=3 alone, method=adams maxord=4, gear maxord=6 dynorder,
method=trbdf2 maxord=3, and method=sdirk, whose order is 3. Only maxord=2 restored. In the
campaign's Family D, 34 of 96 checkpoint trials stopped here.

loadstate sets the circuit up itself when it is not set up yet, as it is in a fresh process.
CKTdoJob copies the task's options into the circuit only when the analysis is dispatched, at the
resume, after that setup. Enhancement-180 copied the KLU flags across that
gap. The integration options were not copied, so the circuit had the default maxord of 2. The
signature check compares the saved maxord with it, and the message printed the two counts that
matched, not the order that did not.

The change

Before the setup, loadstate copies the method and maxord from the task. A staged method's order
is pinned as CKTdoJob pins it: 2 for trbdf2, 3 for sdirk. The checkpoint then meets the
circuit it was taken from. A real mismatch names the order:

Error: loadstate: checkpoint does not match this circuit (equations 6 vs 6, states 4 vs 4, maximum order 3 vs 2).

Every setting above now restores, and the continuation ends where the uninterrupted run does.
It does not follow the uninterrupted run's time grid (F7 of the campaign), so the two agree to
about 3e-8, not to the bit.

The checks

ckorder_examples, every deck under .option sparse (checkpoints need it:
Enhancement-131):

  • [1] gear maxord=3: the checkpoint restores in a fresh process, and v(b) at 2.5 µs is the
    uninterrupted run's to 1e-6.
  • [2] adams maxord=4, and gear maxord=6 with dynorder: the same.
  • [3] sdirk: the same.
  • [4] A checkpoint taken at maxord=3 and loaded into a deck at maxord=2 is refused with
    "maximum order 3 vs 2" in the message.
  • [5] (control) maxord=2 restores, as before.

Write-up: enhancements_doc/Enhancement-879.md