Layer 4 V2 — DC operating-point initialization¶
Layers 4 V1 and 5 V1 simulated dynamic circuits with the trap-
rule companion, but every simulation started from
v_C = 0, i_L = 0. For circuits with a meaningful DC steady
state (most real ones), this caused a "startup transient" that
took several time constants to decay before the simulation
matched physics.
Layer 4 V2 adds the DC operating-point capability: compute
the steady-state solution at t = 0 with caps as OPEN circuits
and inductors as SHORT circuits, then seed HistoryState /
DiodeEventState from it. The trap-rule simulation can then
either:
- Skip the startup: simulation starts AT steady state, only
the ripple is visible.
- Use as a more-accurate IC: simulation starts at the
steady state of the t=t_start switch configuration, which is
the right IC for most engineering analyses.
API surface¶
namespace pulsim::pwl {
/// DC MNA assembly: caps SKIPPED, inductors as v=0 shorts.
inline void dc_assemble(const Graph&, const DevicePool&,
const SwitchStateMask&,
sparse::Matrix& J, Vector& b);
/// Solves the DC system, returns the state vector.
inline Vector compute_dc_op(const Graph&, const DevicePool&,
const SwitchStateMask&);
/// Build a HistoryState seeded from a DC OP vector.
inline HistoryState make_seeded_history(
const Graph&, const DevicePool&, const Vector& dc_x);
/// Build a DiodeEventState with each diode set per the DC bias.
inline DiodeEventState make_seeded_diodes(
const Graph&, const DevicePool&, const Vector& dc_x);
} // namespace pulsim::pwl
namespace pulsim::solver {
SimulationResult run_transient(
const PwlStateSpaceCache& cache,
const Graph& graph,
const DevicePool& pool,
const SimulationOptions& opts,
const SwitchScheduleFn& switch_fn,
const BExtraFn& b_extra_fn = {},
bool start_from_dc_op = false); // NEW V3 flag
} // namespace pulsim::solver
start_from_dc_op = false (default) preserves Layer 5 V2.1
behaviour exactly. true makes the loop:
- Get
initial_mask = switch_fn(t_start). - Iterate diode consistency on the DC system.
- Compute
dc_x = compute_dc_op(graph, pool, mask). - Seed HistoryState from
dc_x(cap: v_prev=v_C, i_prev=0; L: v_prev=0, i_prev=i_L). - Record sample 0 as
(t_start, dc_x). - Continue with the trap-rule loop from sample 1.
DC stamping rules¶
| Device | DC behaviour |
|---|---|
| Resistor | Same stamp as transient (G between terminals). |
| Voltage src | Same constraint (v_from − v_to = V). |
| Switch | Same conductance (g_on or g_off per mask bit). |
| Capacitor | SKIPPED (open circuit, G = 0). |
| Inductor | Constraint row v_from − v_to = 0, branch unknown i_L. |
| Diode | Like Switch (per the mask bit). |
A tiny ε on the inductor's branch-row diagonal prevents structural singularity in degenerate topologies.
Examples¶
RC with DC OP¶
PwlStateSpaceCache cache(g, pool);
cache.build(/*dt=*/10e-9);
SimulationOptions opts{ .t_start=0, .t_end=5e-6, .dt=10e-9 };
auto fn = [](Real){ return SwitchStateMask(0); };
// V0: starts at v_C=0, traces exponential charge to V_dc.
auto result_v0 = run_transient(cache, g, pool, opts, fn);
// V3: starts at v_C=V_dc (DC OP), no transient.
auto result_v3 = run_transient(cache, g, pool, opts, fn,
/*b_extra_fn=*/{},
/*start_from_dc_op=*/true);
LC tank — undamped¶
With DC OP, an LC tank with V_dc applied starts at the DC steady state (v_C = V_dc, i_L = 0). The trap-rule simulation then produces a TINY oscillation (numerical noise only) — the system is already at equilibrium.
Buck converter¶
With DC OP at the t=0 switch state, the buck starts at the steady-state v_out for THAT switch config. PWM then drives the small-signal ripple around it. Eliminates the 4-period startup transient that "from-rest" simulations show.
What V0 does NOT do¶
- No DC OP for nonlinear devices. The smooth-blend
IdealDiodein Layer 2 would need Newton iteration to find its DC point. Lands inpulsim-v2-nonlinear-segment-newton. - No "cold-start with charge transient". The DC OP gives
the steady state; the user who wants the from-rest transient
uses the default (
start_from_dc_op = false). - No multi-segment DC sweep. Pick one switch state at
t = t_start; the DC OP is for that one. If the user needs the
DC for a different state (e.g., the buck's "off" state during
startup analysis), they call
compute_dc_opdirectly.
Test coverage¶
pulsim_v2_layer4_v2_tests: 520 assertions / 9 cases.- DC OP for V-R, V-R-C, V-R-L, chopper.
- HistoryState seeding (caps + inductors).
- DiodeEventState seeding.
- RC integration with DC OP (stays at V_dc throughout).
- Regression: from-zero IC still works.
Status¶
| Layer | Cases | Assertions |
|---|---|---|
| 0 | 19 | 80 |
| 1 | 36 | 126 |
| 2 | 36 | 93 |
| 3 | 16 | 61 |
| 4 V0 | 24 | 58 |
| 5 V0 | 21 | 2069 |
| 4 V1 | 32 | 76 |
| 5 V1 | 17 | 59 |
| 5 V2.1 | 18 | 42 |
| 4 V2 | 9 | 520 ← NEW |
| Total | 228 | 3184 |
v1 regression: 304 / 4214, all green.