Layer 4 V9 — V_F0 + combined κ/V_F0 override helpers¶
V8 shipped make_kappa_override_refresh (a factory for
NonlinearRefreshFn that overrides the smooth-blend
IdealDiode's κ). V9 completes the override set:
| Factory | Overrides | Pool-sourced |
|---|---|---|
make_kappa_override_refresh (V8) |
κ | V_F0, R_d, G_off |
make_vf0_override_refresh (V9) |
V_F0 | R_d, G_off, κ |
make_kappa_vf0_override_refresh (V9) |
κ, V_F0 | R_d, G_off |
All three return a NonlinearRefreshFn usable directly by
solve_with_newton_b_extra or continuation_solve.
API¶
#include "pulsim/pwl/nonlinear_refresh_diode.hpp"
namespace pulsim::pwl {
[[nodiscard]] inline NonlinearRefreshFn
make_vf0_override_refresh(Real V_F0_override);
[[nodiscard]] inline NonlinearRefreshFn
make_kappa_vf0_override_refresh(Real kappa_override,
Real V_F0_override);
}
Both are [[nodiscard]] inline — pure factory functions that
return a lambda capturing the override parameter(s) by value.
They walk BranchKind::Nonlinear branches with
StoredKind::NonlinearDiode, construct a temporary
IdealDiode::Params with the override(s) applied (other
fields taken from pool.nonlinear_diode_params(branch.id)),
and stamp via the standard 2-terminal pattern from V3
(evaluate_current_and_jacobian<IdealDiode>).
Use cases¶
Parameter sweep¶
for (Real vf0 : {0.3, 0.5, 0.7, 1.0}) {
auto refresh = make_vf0_override_refresh(vf0);
Vector x = solve_with_newton_b_extra(
seg, refresh, g, pool,
Vector::Zero(seg.state_size),
Vector::Zero(seg.state_size));
log("V_F0=" << vf0 << ": v_out=" << x[output_node]);
}
The pool's diode is constructed once; each iteration overrides just V_F0 without touching the pool. The user can parametrically sweep V_F0 (or κ, or both) and study the converged operating point.
Combined homotopy¶
const std::vector<std::pair<Real, Real>> chain{
{2.0, -5.0},
{3.0, -1.0},
{5.0, 0.0},
{10.0, 0.7} // target
};
std::vector<NonlinearRefreshFn> refreshes;
for (auto [k, vf0] : chain) {
refreshes.push_back(
make_kappa_vf0_override_refresh(k, vf0));
}
Vector x = continuation_solve(seg, refreshes, g, pool,
x_init, b_extra);
Combined chains ramp κ and V_F0 simultaneously, which is
more robust than ramping either alone for problems where the
two parameters jointly modulate stiffness.
Honest scope: V9 does NOT solve the κ=20 stiff rectifier from x=0¶
V8 motivated the κ-override helper with the κ=20 sinusoidal
rectifier. V8 ended up requiring a per-step load-line
warm-start to converge (documented in
layer4-v8-continuation.md).
V9 explored whether V_F0 (or combined κ+V_F0) homotopies could
solve the κ=20 case from x = 0. Empirical findings:
- Pure V_F0 chain at κ=20 (
{−10, −5, −2, −0.5, 0, 0.3, 0.5, 0.7}and finer variants): fails. Newton in the inner solve overshoots through the sigmoid's narrow knee, and the resulting Jacobian becomes singular at one of the intermediate steps. - Combined κ+V_F0 chain with κ_target=20: same failure mode. Once κ exceeds ~10, the sigmoid is too sharp for any reasonable warm-start chain.
- LM (Levenberg-Marquardt) damping: stalls at local
minima of
‖f‖²where the residual gradient is non-zero but no descent direction satisfies the LM step rule.
Conclusion: for κ ≥ ~8 sinusoidal rectifiers from x = 0,
no continuation chain (with the currently available overrides
plus plain Newton + line search / LM) converges at every time
step. The V8 load-line warm-start remains the recommended
path for those problems.
V9 ships the override factories as PARAMETER-SWEEP and GENERIC-HOMOTOPY building blocks, validated on the DC load- line from V3 (V_dc=2V, R_load=1kΩ, κ=20). The factories themselves are correct; the limitation is upstream in Newton's behaviour on steep sigmoid problems.
Test coverage¶
Four tests in
tests/v2/layer5_v4/test_vf0_continuation_rectifier.cpp:
- V_F0 override actually overrides. Residual at a fixed x differs from the pool-default refresh, confirming the sigmoid centre has shifted.
- Combined override differs from each single override.
Residuals at a fixed x for
make_kappa_override_refresh(5),make_vf0_override_refresh(0), and the combinedmake_kappa_vf0_override_refresh(5, 0)are all distinct. - Single-element V_F0 continuation == direct Newton.
continuation_solvewith a 1-element sequence containing the override refresh produces bit-identical output tosolve_with_newton_b_extradirectly. - V_F0 sweep on DC load-line. For V_F0 ∈ {0.3, 0.5,
0.7}, the converged v_n1 matches the analytical
V_dc − V_F0within 50 mV.
What V9 deliberately does NOT do¶
- Solve the κ=20 stiff sinusoidal rectifier from x=0 (empirically not solvable with current overrides).
- R_d / G_off overrides. Could be added trivially; not needed for any motivating use case identified to date.
- Auto-tune V_F0 sequences.
- Adaptive continuation step refinement on inner-solver failure.
Files added / modified¶
- MODIFIED
core/include/pulsim/pwl/nonlinear_refresh_diode.hpp(+make_vf0_override_refresh, +make_kappa_vf0_override_refresh) - NEW
core/tests/v2/layer5_v4/test_vf0_continuation_rectifier.cpp(4 cases) - NEW
openspec/changes/pulsim-v2-vf0-continuation/ - MODIFIED
core/CMakeLists.txt(test wired intopulsim_v2_layer5_v4_tests)