Layer 4 V8 — Continuation / homotopy Newton solve¶
Layer 4 V4 (line search) and V5 (LM damping) both failed on
the stiff κ=20 sinusoidal rectifier — neither single-shot
Newton variant converges across diode zero-crossings when the
warm-start is "in the wrong basin". The classical fix is
continuation (homotopy): solve a SEQUENCE of progressively
harder problems, warm-starting each from the previous step's
converged solution.
Layer 4 V8 ships continuation_solve — a thin orchestrator
that runs solve_with_newton_b_extra once per refresh in a
caller-supplied sequence, threading the converged x from
step k as the warm-start for step k+1.
API¶
#include "pulsim/pwl/continuation.hpp"
namespace pulsim::pwl {
[[nodiscard]] Vector continuation_solve(
const PwlSegment& seg,
const std::vector<NonlinearRefreshFn>& refresh_sequence,
const topology::Graph& graph,
const DevicePool& pool,
const Vector& x_init,
const Vector& b_extra,
Size max_iters_per_step = 100,
Real tol_dx = 1e-7,
Real tol_res = 1e-5,
bool enable_line_search = false,
bool enable_lm = false);
}
Semantics:
refresh_sequencemust be non-empty (else throws).x_initwarm-starts the FIRST refresh. Each subsequent refresh receives the prior step's convergedxas its warm-start.- All Newton tuning knobs (
max_iters_per_step, tolerances, globalizations) are forwarded unchanged to every inner solve. - On any inner-Newton failure, the function rethrows with a message identifying the failing step index.
Cost: N inner Newton solves vs 1 for single-shot Newton. The robustness boost justifies the linear blow-up for stiff problems; for easy problems the caller simply doesn't enable continuation (or uses a single-element sequence).
Companion helper: make_kappa_override_refresh¶
pwl/nonlinear_refresh_diode.hpp ships a factory that returns
a NonlinearRefreshFn stamping the smooth-blend IdealDiode
with an OVERRIDDEN κ. All other params (V_F0, R_d,
G_off) come from the DevicePool. Used to build a κ-
continuation chain in one line:
std::vector<NonlinearRefreshFn> refreshes;
for (Real k : {2.0, 5.0, 10.0, 20.0}) {
refreshes.push_back(make_kappa_override_refresh(k));
}
Vector x = continuation_solve(seg, refreshes, g, pool,
x_init, b_extra);
When continuation actually helps (empirical caveat)¶
The natural homotopy parameter for the smooth-blend
IdealDiode is κ (the sigmoid sharpness). Low κ smoothes the
sigmoid → Newton-friendly. High κ → diode-like behaviour.
But the κ homotopy is NOT universally useful. Specifically, in a half-wave rectifier (V_sine → diode → R_load → GND):
- At κ=2 with V_sine ≈ 0, the smooth-blend "knee" is wide enough that the diode model leaks substantial current even for moderately reverse-biased v_diode (alpha = 0.13 at delta = -0.7).
- The self-consistent operating point at small V_sine with κ=2 has v_n1 ≈ −100 V (a stable but UNPHYSICAL branch of the sigmoid-circuit equations).
- Continuing to higher κ from that unphysical x is a poor warm-start — the chain diverges or finds a wrong branch.
The practical recipe for the rectifier:
- Smart warm-start per time step (load-line guess):
x_init[n0] = v_sine; x_init[n1] = std::max(v_sine − V_F0, 0.0); x_init[i_src] = 0.0; - Single-element κ chain at the target plus line search. With the load-line warm-start, plain Newton + line search converges at every step (verified across 2 cycles at dt=100µs).
This is what tests/v2/layer5_v4/test_continuation_rectifier.cpp
ships: a load-line warm-start with kappa_seq = {20.0}, which
validates the continuation_solve integration path end-to-end
while honestly acknowledging that the κ chain itself doesn't
do the heavy lifting on this problem.
When continuation IS useful¶
Continuation shines when:
- The user has NO physically-motivated warm-start, and
needs to solve from
x = 0(or some neutral guess). - The parameter family
F(x; p)interpolates between an EASY problem (Newton converges fromx=0) and the HARD target without branch-switching along the way. - The user wants robustness over speed: the linear blow-up in cost buys avoidance of catastrophic divergence.
Examples (future OpenSpecs may exercise these):
- V_F0 ramping: start with
V_F0=0(ideal short — diode is a resistor, trivially Newton-solvable), ramp toward the targetV_F0=0.7 V. No branch switching. - Source-amplitude ramping: start with
V_amp=0.5 V(rectifier is trivially in linear regime), ramp towardV_amp=10 V. Operating-point variation is gradual.
The continuation_solve primitive is parameter-agnostic — it
just iterates user-supplied NonlinearRefreshFns. The κ
override is the only built-in helper; other homotopy
parameters require the user to write a closure that captures
the desired interpolation.
Test coverage¶
Three tests in tests/v2/layer5_v4/test_continuation_rectifier.cpp:
- Trivial single-refresh == direct Newton —
continuation_solvewith a 1-element sequence produces bit-identical output tosolve_with_newton_b_extradirectly. - DC κ-chain finds load-line — V_dc = 2V through
smooth diode (κ=20), continuation through
{2, 5, 10, 20}. The κ chain WORKS at DC because the diode's operating point at κ=2 vs κ=20 sits in the same physical branch (the load is small enough that diode-leakage doesn't dominate). - κ=20 sinusoidal rectifier —
V_amp=10 V, 60 Hz,R_load=10 Ω. 2 cycles at dt=100 µs. Load-line warm- start + κ={20} continuation. Verifies > 95% pos-half tracking, > 95% neg-half tracking, mean power within 15% of analytical.
What V0 deliberately does NOT do¶
- Auto κ-sequence selection (adaptive scheduling). V0 takes the sequence as input.
- Predictor-corrector continuation (tangent prediction via implicit-function-theorem df/dp). V0 is pure warm-start (predictor = previous x).
- Per-step failure recovery (sub-step refinement on Newton divergence). V0 throws on any failure.
- Alternative homotopies (V_F0 ramping, source-amp
ramping). V0 ships κ-override only; the primitive is
generic and user-extensible via
NonlinearRefreshFn.
These are V1+ candidates.
Files added/modified¶
core/include/pulsim/pwl/continuation.hpp(NEW)core/include/pulsim/pwl/nonlinear_refresh_diode.hpp(extended withmake_kappa_override_refresh)core/tests/v2/layer5_v4/test_continuation_rectifier.cpp(NEW)core/CMakeLists.txt(test wired intopulsim_v2_layer5_v4_tests)