Skip to content

Derivations

Each section below is generated from a script in python/pe_core/derive/. The same script runs under pytest, which fails unless the symbolic difference between the derived result and the expression stored in equations.yaml simplifies to zero. The prose and headings are written by hand; every formula on this page is printed by sympy from those scripts.

Refreshers: element laws, impedance, Fourier series, magnetics, the ideal transformer

Section titled “Refreshers: element laws, impedance, Fourier series, magnetics, the ideal transformer”

The element laws are integrated for constant excitation, impedances follow from the phasor form of the same laws, the Fourier coefficients and the rms value of a pulse train are computed over one period, and the ideal transformer follows from Faraday's and Ampère's laws with a lossless core of zero reluctance.

  1. Inductor law: v=L di/dtv = L\,\mathrm{d}i/\mathrm{d}t. With a constant voltage VLV_L the slope of the current is constant; integrate it over the interval tt.

    ∫0tVLL dτ\int\limits_{0}^{t} \frac{V_L}{L}\, d\tau
  2. Evaluate the integral.

    Δi=VLtL\Delta i = \frac{V_L t}{L}

    ✓ reproduces ind.di (equality checked symbolically by pytest)

  3. Capacitor law, the dual: i=C dv/dti = C\,\mathrm{d}v/\mathrm{d}t, integrated for a constant current ICI_C.

    ∫0tICC dτ\int\limits_{0}^{t} \frac{I_C}{C}\, d\tau
  4. Evaluate the integral.

    ΔvC=ICtC\Delta v_C = \frac{I_C t}{C}

    ✓ reproduces cap.dv (equality checked symbolically by pytest)

  5. Energy delivered to an inductor: ∫v i dt=∫L i di\int v\,i\,\mathrm{d}t = \int L\,i\,\mathrm{d}i, from zero current to ILI_L.

    ∫0ILLi di\int\limits_{0}^{I_L} L i\, di
  6. Evaluate the integral.

    EL=LIL22E_L = \frac{L I_L^{2}}{2}

    ✓ reproduces ind.E (equality checked symbolically by pytest)

  7. Energy delivered to a capacitor: ∫v i dt=∫C v dv\int v\,i\,\mathrm{d}t = \int C\,v\,\mathrm{d}v, from zero voltage to VCV_C.

    ∫0VCCv dv\int\limits_{0}^{V_C} C v\, dv
  8. Evaluate the integral.

    EC=CVC22E_C = \frac{C V_C^{2}}{2}

    ✓ reproduces cap.E (equality checked symbolically by pytest)

  9. In sinusoidal steady state d/dt\mathrm{d}/\mathrm{d}t becomes multiplication by jωj\omega, with ω=2πf\omega = 2\pi f. The inductor law gives the inductor's impedance.

    ZL=2πiLfZ_L = 2 \pi i L f
  10. Its magnitude.

    ∣ZL∣=2πfL|Z_L| = 2 \pi f L

    ✓ reproduces imp.ZL (equality checked symbolically by pytest)

  11. The capacitor law gives the capacitor's impedance.

    ZC=−i2πCfZ_C = - \frac{i}{2 \pi C f}
  12. Its magnitude.

    ∣ZC∣=12πfC|Z_C| = \frac{1}{2 \pi f C}

    ✓ reproduces imp.ZC (equality checked symbolically by pytest)

  13. Unloaded R-C low-pass filter: the output is the capacitor's share of the divider formed by RfR_f and the capacitor's impedance.

    G(jω)=−i2πCfRff−iG(j\omega) = - \frac{i}{2 \pi C_f R_f f - i}
  14. Squared magnitude.

    ∣G(jω)∣2=11+4π2Cf2Rf2f2\left|{G(j\omega)}\right|^{2} = \frac{1}{1 + 4 \pi^{2} C_f^{2} R_f^{2} f^{2}}
  15. The corner (-3 dB) frequency is where the squared magnitude falls to one half.

    fc=12πRfCff_c = \frac{1}{2 \pi R_f C_f}

    ✓ reproduces rc.fc (equality checked symbolically by pytest)

  16. Write the magnitude with fcf_c in place of RfCfR_f C_f.

    ∣G∣=fcf2+fc2=11+(ffc)2|G| = \frac{f_c}{\sqrt{f^{2} + f_c^{2}}} = \frac{1}{\sqrt{1 + \left(\frac{f}{f_c}\right)^{2}}}

    ✓ reproduces rc.gain (equality checked symbolically by pytest)

  17. An inductance with a parasitic capacitance in parallel: the admittances add.

    Y=2πiCpf−i2πLfY = 2 \pi i C_p f - \frac{i}{2 \pi L f}
  18. At resonance the admittance is zero (the impedance is infinite for the lossless model).

    fsrf=12πLCpf_\mathrm{srf} = \frac{1}{2 \pi \sqrt{L C_p}}

    ✓ reproduces passive.f_srf (equality checked symbolically by pytest)

  19. A pulse train of height VpkV_\mathrm{pk} and width DTD T in every period TT. Its cosine coefficient for the harmonic hh:

    ah,cos⁡=Vpksin⁡(2πDh)πha_{h,\cos} = \frac{V_\mathrm{pk} \sin{\left(2 \pi D h \right)}}{\pi h}
  20. and its sine coefficient:

    ah,sin⁡=2Vpksin⁡2(πDh)πha_{h,\sin} = \frac{2 V_\mathrm{pk} \sin^{2}{\left(\pi D h \right)}}{\pi h}
  21. The squared amplitude of the harmonic is the sum of their squares; with 1−cos⁡2x=2sin⁡2x1 - \cos 2x = 2\sin^2 x it is a perfect square.

    ah2=4Vpk2sin⁡2(πDh)π2h2a_h^{2} = \frac{4 V_\mathrm{pk}^{2} \sin^{2}{\left(\pi D h \right)}}{\pi^{2} h^{2}}
  22. Take the non-negative root.

    ah=2Vpk∣sin⁡(πhD)∣πha_h = \frac{2 V_\mathrm{pk} \left|{\sin{\left(\pi h D \right)}}\right|}{\pi h}

    ✓ reproduces fourier.pulse.harm (equality checked symbolically by pytest)

  23. The mean square over one period: the pulse contributes Vpk2V_\mathrm{pk}^2 during DTD T.

    Vrms2=DVpk2V_\mathrm{rms}^{2} = D V_\mathrm{pk}^{2}
  24. The rms value is its square root.

    Vrms=VpkDV_\mathrm{rms} = V_\mathrm{pk} \sqrt{D}

    ✓ reproduces fourier.pulse.rms (equality checked symbolically by pytest)

  25. The ripple current flows through the capacitor's series resistance, whose voltage follows the current at every instant. The difference between its extremes:

    ΔvESR=iC,maxRESR−iC,minRESR\Delta v_\mathrm{ESR} = i_{C,\mathrm{max}} R_\mathrm{ESR} - i_{C,\mathrm{min}} R_\mathrm{ESR}
  26. The current's peak-to-peak value is its maximum minus its minimum.

    ΔvESR=RESRΔiL,pp\Delta v_\mathrm{ESR} = R_\mathrm{ESR} \Delta i_{L,\mathrm{pp}}

    ✓ reproduces cap.esr.ripple (equality checked symbolically by pytest)

  27. Ampère's law around the core: the line integral of HH equals the enclosed current, NN turns each carrying IwI_w. In an ungapped core with a uniform field it is HH times the path length.

    Hle=IwNH l_e = I_w N
  28. Solve for HH.

    H=NIwleH = \frac{N I_w}{l_e}

    ✓ reproduces mag.H_ampere (equality checked symbolically by pytest)

  29. Faraday's law for two windings on one core: each winding voltage is its number of turns times the rate of change of the common flux. For the primary:

    V1=NpdΦ/dtV_1 = N_p \mathrm{d}\Phi/\mathrm{d}t
  30. and for the secondary:

    V2=NsdΦ/dtV_2 = N_s \mathrm{d}\Phi/\mathrm{d}t
  31. Divide the two: the flux term cancels, and the ratio of the turns is nn (1:n convention).

    V2=nV1V_2 = n V_1

    ✓ reproduces xfmr.V2 (equality checked symbolically by pytest)

  32. Ampère's law with a core of zero reluctance (ideal): the net magnetomotive force is zero.

    I1Np−I2Ns=0I_1 N_p - I_2 N_s = 0
  33. Solve for the secondary current, with Ns=nNpN_s = n N_p.

    I2=I1nI_2 = \frac{I_1}{n}

    ✓ reproduces xfmr.I2 (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/foundations.py

Averaging, balance, and losses in the averaged model

Section titled “Averaging, balance, and losses in the averaged model”

A switching converter chops a dc input into a pulse train; a low-pass filter keeps its average. Averaging each circuit equation over one period turns the switched circuit into a dc model in which losses such as the inductor winding resistance RLR_L appear as ordinary resistors.

  1. The buck switch node is at VgV_g for 0<t<DTs0 < t < D T_s and at 00 for DTs<t<TsD T_s < t < T_s; average it over one period.

    ⟨vs⟩=∫0Tsvs dtTs\langle v_s \rangle = \frac{\int\limits_{0}^{T_s} v_s\, dt}{T_s}
  2. Evaluate the two pieces.

    ⟨vs⟩=DVg\langle v_s \rangle = D V_g

    ✓ reproduces sw.v_avg (equality checked symbolically by pytest)

  3. The inductor obeys vL=L di/dtv_L = L \, di/dt. Integrated over one period, the current's change is the integral of the voltage over LL.

    Δi=∫0TsvL dtL\Delta i = \frac{\int\limits_{0}^{T_s} v_L\, dt}{L}
  4. For a voltage vL,onv_{L,\mathrm{on}} during DTsD T_s and vL,offv_{L,\mathrm{off}} for the rest, the integral is TsT_s times the average over the period.

    ⟨vL⟩=vL,off+vL,onD−vL,offD\langle v_L \rangle = v_{L,\mathrm{off}} + v_{L,\mathrm{on}} D - v_{L,\mathrm{off}} D
  5. So the change over one period is the average voltage times TsT_s over LL; it is zero only when the average is (volt-second balance).

    Δi=⟨vL⟩TsL\Delta i = \frac{\langle v_L \rangle T_s}{L}

    ✓ reproduces vsb.drift (equality checked symbolically by pytest)

  6. With the small-ripple approximation the inductor voltage (or capacitor current) takes one value xonx_\mathrm{on} for DTsD T_s and another value xoffx_\mathrm{off} for (1−D)Ts(1 - D) T_s; its average must be zero.

    xoff(1−D)+xonD=0x_\mathrm{off} \left(1 - D\right) + x_\mathrm{on} D = 0
  7. For the inductor voltage (x=vLx = v_L): volt-second balance.

    vL,off=DvL,on−1+D=−DvL,on1−Dv_{L,\mathrm{off}} = \frac{D v_{L,\mathrm{on}}}{-1 + D} = - \frac{D v_{L,\mathrm{on}}}{1 - D}

    ✓ reproduces vsb.v_off (equality checked symbolically by pytest)

  8. For the capacitor current (x=iCx = i_C): charge balance.

    iC,off=DiC,on−1+D=−DiC,on1−Di_{C,\mathrm{off}} = \frac{D i_{C,\mathrm{on}}}{-1 + D} = - \frac{D i_{C,\mathrm{on}}}{1 - D}

    ✓ reproduces csb.i_off (equality checked symbolically by pytest)

  9. Boost with inductor winding resistance RLR_L, volt-second balance: the inductor sees Vg−IRLV_g - I R_L while the switch is on and Vg−IRL−VV_g - I R_L - V while the diode conducts (II = dc inductor current).

    D(Vg−IRL)+(1−D)(Vg−V−IRL)=0D \left(V_g - I R_L\right) + \left(1 - D\right) \left(V_g - V - I R_L\right) = 0
  10. Charge balance on the output capacitor: −V/R-V/R while the switch is on, I−V/RI - V/R while the diode conducts.

    (1−D)(I−VR)−DVR=0\left(1 - D\right) \left(I - \frac{V}{R}\right) - \frac{D V}{R} = 0
  11. Solve the two linear equations for II.

    I=VgR+RL−2DR+RD2I = \frac{V_g}{R + R_L - 2 D R + R D^{2}}
  12. and for M=V/VgM = V/V_g.

    M=R(1−D)R+RL−2DR+RD2=1(1−D)(1+RL(1−D)2R)M = \frac{R \left(1 - D\right)}{R + R_L - 2 D R + R D^{2}} = \frac{1}{\left(1 - D\right) \left(1 + \frac{R_L}{\left(1 - D\right)^{2} R}\right)}

    ✓ reproduces boost.ccm.M_RL (equality checked symbolically by pytest)

  13. Efficiency is the output power V2/RV^2/R over the input power VgIV_g I.

    η=R(−1+D)2R+RL−2DR+RD2=11+RL(1−D)2R\eta = \frac{R \left(-1 + D\right)^{2}}{R + R_L - 2 D R + R D^{2}} = \frac{1}{1 + \frac{R_L}{\left(1 - D\right)^{2} R}}

    ✓ reproduces boost.ccm.eta_RL (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/averaging.py

In periodic steady state every inductor current ends a switching period where it started, so the average inductor voltage over the period is zero. With the small-ripple approximation each interval applies a constant voltage, and the balance becomes a linear equation in the output voltage VV.

  1. Volt-second balance: the inductor voltage averaged over one switching period is zero.

    ⟨vL⟩=0\langle v_L \rangle = 0
  2. Buck: the inductor sees Vg−VV_g - V while the switch is on (fraction DD) and −V-V while the diode conducts (1−D1 - D).

    D(Vg−V)−V(1−D)=0D \left(V_g - V\right) - V \left(1 - D\right) = 0
  3. Solve for VV and divide by VgV_g.

    M=DM = D

    ✓ reproduces buck.ccm.M (equality checked symbolically by pytest)

  4. Boost: VgV_g while the switch is on, Vg−VV_g - V while the diode conducts.

    DVg+(1−D)(Vg−V)=0D V_g + \left(1 - D\right) \left(V_g - V\right) = 0
  5. Solve for VV.

    M=−1−1+D=11−DM = - \frac{1}{-1 + D} = \frac{1}{1 - D}

    ✓ reproduces boost.ccm.M (equality checked symbolically by pytest)

  6. Buck-boost: VgV_g while the switch is on, the (negative) output voltage VV while the diode conducts.

    V(1−D)+DVg=0V \left(1 - D\right) + D V_g = 0
  7. Solve for VV; the output is inverted.

    M=D−1+D=−D1−DM = \frac{D}{-1 + D} = - \frac{D}{1 - D}

    ✓ reproduces buckboost.ccm.M (equality checked symbolically by pytest)

  8. Flyback (1:nn, magnetizing inductance on the primary): VgV_g while the switch is on; while the diode conducts the secondary is clamped at VV, which reflects to −V/n-V/n on the primary.

    DVg−V(1−D)n=0D V_g - \frac{V \left(1 - D\right)}{n} = 0
  9. Solve for VV.

    M=−Dn−1+D=nD1−DM = - \frac{D n}{-1 + D} = \frac{n D}{1 - D}

    ✓ reproduces flyback.ccm.M (equality checked symbolically by pytest)

  10. Forward: the secondary applies nVgnV_g to the output filter while the switch is on, so the output inductor sees nVg−VnV_g - V, then −V-V while the freewheeling diode conducts.

    D(−V+Vgn)−V(1−D)=0D \left(- V + V_g n\right) - V \left(1 - D\right) = 0
  11. Solve for VV.

    M=nDM = n D

    ✓ reproduces forward.ccm.M (equality checked symbolically by pytest)

  12. Forward reset: the magnetizing inductance sees VgV_g for DTsD T_s, then the reset winding (NrN_r turns) clamps it to −Vg/nr-V_g/n_r for D2TsD_2 T_s until the magnetizing current is back to zero.

    DVg−D2Vgnr=0D V_g - \frac{D_2 V_g}{n_r} = 0
  13. Solve for the reset interval.

    D2=DnrD_2 = D n_r
  14. The reset must finish within the period, D+D2≤1D + D_2 \le 1; the equality gives the largest duty ratio.

    Dmax=11+nrD_\mathrm{max} = \frac{1}{1 + n_r}

    ✓ reproduces forward.reset.Dmax (equality checked symbolically by pytest)

  15. Buck ripple: during the on-interval the inductor current rises with slope (Vg−V)/L(V_g - V)/L for DTsD T_s, which is the full peak-to-peak ripple, i.e. 2ΔiL2\Delta i_L.

    2ΔiL=DTs(Vg−V)L2 \Delta i_L = \frac{D T_s \left(V_g - V\right)}{L}
  16. Half of it is Erickson's ΔiL\Delta i_L.

    ΔiL=(Vg−V)DTs2L\Delta i_L = \frac{\left(V_g - V\right) D T_s}{2 L}

    ✓ reproduces buck.ripple.iL (equality checked symbolically by pytest)

  17. The peak-to-peak value, with Ts=1/fsT_s = 1/f_s.

    ΔiL,pp=(Vg−V)DLfs\Delta i_{L,\mathrm{pp}} = \frac{\left(V_g - V\right) D}{L f_s}

    ✓ reproduces buck.ripple.iL_pp (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/ccm_ratios.py

CCM/DCM boundary and DCM conversion ratios

Section titled “CCM/DCM boundary and DCM conversion ratios”

The converter leaves CCM when the inductor current ripple is large enough for the current to reach zero: at the boundary the half-ripple ΔiL\Delta i_L equals the dc inductor current. In DCM a third interval D3TsD_3 T_s appears in which both switch and diode are off; its length is unknown, so volt-second balance is combined with capacitor charge balance (the average diode or inductor current must equal what the load draws).

  1. Definition used throughout: K=2L/(RTs)K = 2L/(R T_s), i.e. L=KRTs/2L = K R T_s/2.

    K=2LRTsK = \frac{2 L}{R T_s}
  2. Buck: the dc inductor current equals the load current V/RV/R, with V=DVgV = D V_g in CCM.

    IL=DVgRI_L = \frac{D V_g}{R}
  3. The half-ripple over the on-interval, with the CCM operating point substituted.

    ΔiL=DTs(Vg−DVg)2L\Delta i_L = \frac{D T_s \left(V_g - D V_g\right)}{2 L}
  4. Boundary ΔiL=IL\Delta i_L = I_L, with L=KRTs/2L = K R T_s/2, solved for KK.

    Kcrit=1−DK_\mathrm{crit} = 1 - D

    ✓ reproduces Kcrit.buck (equality checked symbolically by pytest)

  5. Boost: the inductor carries the input current, V/((1−D)R)V/((1 - D)R), with V=Vg/(1−D)V = V_g/(1 - D).

    IL=VgR(1−D)2I_L = \frac{V_g}{R \left(1 - D\right)^{2}}
  6. The half-ripple over the on-interval, with the CCM operating point substituted.

    ΔiL=DTsVg2L\Delta i_L = \frac{D T_s V_g}{2 L}
  7. Boundary ΔiL=IL\Delta i_L = I_L, with L=KRTs/2L = K R T_s/2, solved for KK.

    Kcrit=D(−1+D)2=D(1−D)2K_\mathrm{crit} = D \left(-1 + D\right)^{2} = D \left(1 - D\right)^{2}

    ✓ reproduces Kcrit.boost (equality checked symbolically by pytest)

  8. Buck-boost: the inductor current is ∣V∣/((1−D)R)|V|/((1 - D)R), with ∣V∣=DVg/(1−D)|V| = D V_g/(1 - D).

    IL=DVgR(1−D)2I_L = \frac{D V_g}{R \left(1 - D\right)^{2}}
  9. The half-ripple over the on-interval, with the CCM operating point substituted.

    ΔiL=DTsVg2L\Delta i_L = \frac{D T_s V_g}{2 L}
  10. Boundary ΔiL=IL\Delta i_L = I_L, with L=KRTs/2L = K R T_s/2, solved for KK.

    Kcrit=(−1+D)2=(1−D)2K_\mathrm{crit} = \left(-1 + D\right)^{2} = \left(1 - D\right)^{2}

    ✓ reproduces Kcrit.buckboost (equality checked symbolically by pytest)

  11. At the boundary KK equals KcritK_\mathrm{crit}; solve the definition of KK for LL.

    Lcrit=KcritRTs2L_\mathrm{crit} = \frac{K_\mathrm{crit} R T_s}{2}

    ✓ reproduces L.crit (equality checked symbolically by pytest)

  12. Buck DCM, volt-second balance with V=MVgV = M V_g: Vg−VV_g - V for DTsD T_s, −V-V for D2TsD_2 T_s, 00 for D3TsD_3 T_s.

    D(Vg−MVg)−D2MVg=0D \left(V_g - M V_g\right) - D_2 M V_g = 0
  13. Solve for D2D_2.

    D2=−D+DM=D(1−M)MD_2 = - D + \frac{D}{M} = \frac{D \left(1 - M\right)}{M}

    ✓ reproduces buck.dcm.D2 (equality checked symbolically by pytest)

  14. Peak inductor current at the end of the on-interval.

    ipk=DTs(Vg−MVg)Li_\mathrm{pk} = \frac{D T_s \left(V_g - M V_g\right)}{L}
  15. Charge balance: the average inductor current (a triangle of height ipki_\mathrm{pk} and base (D+D2)Ts(D + D_2)T_s) equals the load current V/RV/R.

    TsD2(Vg−MVg)2LM=MVgR\frac{T_s D^{2} \left(V_g - M V_g\right)}{2 L M} = \frac{M V_g}{R}
  16. Substitute L=KRTs/2L = K R T_s/2 and take the positive root of the quadratic in MM.

    M=D(−D+4K+D2)2K=21+1+4KD2M = \frac{D \left(- D + \sqrt{4 K + D^{2}}\right)}{2 K} = \frac{2}{1 + \sqrt{1 + \frac{4 K}{D^{2}}}}

    ✓ reproduces buck.dcm.M (equality checked symbolically by pytest)

  17. Boost DCM, volt-second balance: VgV_g for DTsD T_s, Vg−VV_g - V for D2TsD_2 T_s.

    DVg+D2(Vg−MVg)=0D V_g + D_2 \left(V_g - M V_g\right) = 0
  18. Solve for D2D_2.

    D2=D−1+MD_2 = \frac{D}{-1 + M}
  19. Charge balance at the output: the average diode current (a triangle of height ipki_\mathrm{pk} over D2TsD_2 T_s) equals the load current.

    TsVgD22L(−1+M)=MVgR\frac{T_s V_g D^{2}}{2 L \left(-1 + M\right)} = \frac{M V_g}{R}
  20. Substitute L=KRTs/2L = K R T_s/2 and keep the root with M>1M > 1.

    M=12+K+4D22K=1+1+4D2K2M = \frac{1}{2} + \frac{\sqrt{K + 4 D^{2}}}{2 \sqrt{K}} = \frac{1 + \sqrt{1 + \frac{4 D^{2}}{K}}}{2}

    ✓ reproduces boost.dcm.M (equality checked symbolically by pytest)

  21. Buck-boost DCM, volt-second balance with the negative output V=MVgV = M V_g: VgV_g for DTsD T_s, VV for D2TsD_2 T_s.

    DVg+D2VgM=0D V_g + D_2 V_g M = 0
  22. Solve for D2D_2.

    D2=−DMD_2 = - \frac{D}{M}
  23. Charge balance: the average diode current equals the load current ∣V∣/R|V|/R.

    −TsVgD22LM=−VgMR- \frac{T_s V_g D^{2}}{2 L M} = - \frac{V_g M}{R}
  24. Substitute L=KRTs/2L = K R T_s/2 and solve (M<0M < 0).

    M=−DKM = - \frac{D}{\sqrt{K}}

    ✓ reproduces buckboost.dcm.M (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/dcm.py

Flyback: DCM ratio, boundaries, stresses, loss-free resistor

Section titled “Flyback: DCM ratio, boundaries, stresses, loss-free resistor”

The flyback stores energy in the magnetizing inductance LML_M while the switch is on and releases it to the output through the diode while the switch is off. With a 1:nn transformer (n=Ns/Npn = N_s/N_p) the secondary voltage reflects to the primary divided by nn, and the secondary current is the primary-referred magnetizing current divided by nn.

  1. On-interval: VgV_g across LML_M, so the magnetizing current rises from zero (DCM) to ipki_\mathrm{pk}.

    ipk=DTsVgLMi_\mathrm{pk} = \frac{D T_s V_g}{L_M}
  2. Volt-second balance on LML_M: VgV_g for DTsD T_s, then the reflected output −V/n-V/n for D2TsD_2 T_s.

    DVg−D2Vn=0D V_g - \frac{D_2 V}{n} = 0
  3. Solve for D2D_2.

    D2=DVgnVD_2 = \frac{D V_g n}{V}
  4. Charge balance: the secondary current starts at ipk/ni_\mathrm{pk}/n and falls to zero over D2TsD_2 T_s; its average equals the load current V/RV/R.

    TsD2Vg22LMV=VR\frac{T_s D^{2} V_g^{2}}{2 L_M V} = \frac{V}{R}
  5. Substitute LM=KRTs/2L_M = K R T_s/2 and solve for V/VgV/V_g; nn cancels.

    M=DKM = \frac{D}{\sqrt{K}}

    ✓ reproduces flyback.dcm.M (equality checked symbolically by pytest)

  6. The DCM demagnetizing interval, with the DCM output voltage substituted.

    D2=nKD_2 = n \sqrt{K}
  7. The boundary is reached when the demagnetizing interval fills the rest of the period, D+D2=1D + D_2 = 1.

    Kcrit=(−1+D)2n2=(1−D)2n2K_\mathrm{crit} = \frac{\left(-1 + D\right)^{2}}{n^{2}} = \frac{\left(1 - D\right)^{2}}{n^{2}}

    ✓ reproduces Kcrit.flyback (equality checked symbolically by pytest)

  8. Output held at VV (battery or regulated bus) and the diode dropping VDV_D: the reflected voltage is −(V+VD)/n-(V + V_D)/n during demagnetization.

    DVg+D2(−V−VD)n=0D V_g + \frac{D_2 \left(- V - V_D\right)}{n} = 0
  9. Setting D+D2=1D + D_2 = 1 gives the input voltage at which the converter enters CCM; above it CCM volt-second balance holds VgV_g at this value.

    Vg,crit=V+VD−D(V+VD)Dn=(V+VD)(1−D)nDV_{g,\mathrm{crit}} = \frac{V + V_D - D \left(V + V_D\right)}{D n} = \frac{\left(V + V_D\right) \left(1 - D\right)}{n D}

    ✓ reproduces flyback.V_crit (equality checked symbolically by pytest)

  10. While the diode conducts, the primary winding carries the reflected (V+VD)/n(V + V_D)/n on top of VgV_g, so the off-state switch voltage is their sum (the leakage spike comes on top of this).

    VDS=Vg+V+VDnV_\mathrm{DS} = V_g + \frac{V + V_D}{n}

    ✓ reproduces flyback.Vds_off (equality checked symbolically by pytest)

  11. With the input pinned at Vg,critV_{g,\mathrm{crit}} the off-state switch voltage simplifies.

    VDS=V+VDnDV_\mathrm{DS} = \frac{V + V_D}{n D}

    ✓ reproduces flyback.Vds_clamped (equality checked symbolically by pytest)

  12. While the switch is on the secondary winding voltage nVgn V_g adds to the output voltage across the reverse-biased diode.

    VR=V+nVgV_R = V + n V_g

    ✓ reproduces flyback.diode.VR (equality checked symbolically by pytest)

  13. CCM charge balance: while the diode conducts, for (1−D)Ts(1 - D) T_s, the secondary carries IM/nI_M/n (small ripple); its average equals the load current V/RV/R.

    IM(1−D)n=VR\frac{I_M \left(1 - D\right)}{n} = \frac{V}{R}
  14. Solve for IMI_M.

    IM=−VnR(−1+D)=nV(1−D)RI_M = - \frac{V n}{R \left(-1 + D\right)} = \frac{n V}{\left(1 - D\right) R}

    ✓ reproduces flyback.IM (equality checked symbolically by pytest)

  15. On-interval: VgV_g across LML_M for DTsD T_s, so the magnetizing current rises by twice the half-ripple ΔiM\Delta i_M.

    2ΔiM=DTsVgLM2 \Delta i_M = \frac{D T_s V_g}{L_M}
  16. Solve for ΔiM\Delta i_M.

    ΔiM=VgDTs2LM\Delta i_M = \frac{V_g D T_s}{2 L_M}

    ✓ reproduces flyback.ripple.iM (equality checked symbolically by pytest)

  17. While the switch is on the diode is off, so the capacitor alone supplies the load current V/RV/R for DTsD T_s and its voltage falls by twice the half-ripple Δv\Delta v.

    2Δv=DTsVCR2 \Delta v = \frac{D T_s V}{C R}
  18. Solve for Δv\Delta v.

    Δv=VDTs2RC\Delta v = \frac{V D T_s}{2 R C}

    ✓ reproduces flyback.ripple.v (equality checked symbolically by pytest)

  19. The DCM peak magnetizing current, with Ts=1/fsT_s = 1/f_s.

    Ipk=VgDLMfsI_\mathrm{pk} = \frac{V_g D}{L_M f_s}

    ✓ reproduces flyback.Ipk.dcm (equality checked symbolically by pytest)

  20. Energy stored in LML_M at the end of each on-interval (all of it is delivered every cycle in DCM).

    E=D2Ts2Vg22LME = \frac{D^{2} T_s^{2} V_g^{2}}{2 L_M}
  21. Power = energy per cycle × switching frequency; it depends on VgV_g, DD, LML_M, fsf_s but not on the load.

    P=Vg2D22LMfsP = \frac{V_g^{2} D^{2}}{2 L_M f_s}

    ✓ reproduces dcm.P_in (equality checked symbolically by pytest)

  22. Since P=Vg2/RinP = V_g^2/R_\mathrm{in} the input behaves as a resistance that dissipates nothing (a loss-free resistor).

    Rin=2LMfsD2R_\mathrm{in} = \frac{2 L_M f_s}{D^{2}}

    ✓ reproduces lfr.R_in (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/flyback.py

Flyback clamp: reflected voltage, clamp dissipation, output ceiling

Section titled “Flyback clamp: reflected voltage, clamp dissipation, output ceiling”

When the flyback's switch turns off, the magnetizing current passes to the secondary, but the current in the leakage inductance LlkL_\mathrm{lk} has nowhere to go except a clamp across the primary. The clamp holds the primary at VclampV_\mathrm{clamp} until that current has fallen to zero.

  1. While the output diode conducts, the secondary winding carries the output voltage plus the diode drop.

    vs=V+VDv_s = V + V_D
  2. An ideal 1:nn transformer divides a secondary voltage by nn on the primary.

    VOR=V+VDnV_\mathrm{OR} = \frac{V + V_D}{n}

    ✓ reproduces flyback.V_OR (equality checked symbolically by pytest)

  3. The switch lies between the input and the primary winding, which the clamp holds at VclampV_\mathrm{clamp} while it conducts.

    VDS=Vg+VclampV_\mathrm{DS} = V_g + V_\mathrm{clamp}

    ✓ reproduces clamp.Vds (equality checked symbolically by pytest)

  4. The magnetizing inductance holds VORV_\mathrm{OR} while the output diode conducts, so the leakage inductance sees Vclamp−VORV_\mathrm{clamp} - V_\mathrm{OR} and its current falls linearly from IpkI_\mathrm{pk}.

    ilk=Ipk−t(Vclamp−VOR)Llki_\mathrm{lk} = I_\mathrm{pk} - \frac{t \left(V_\mathrm{clamp} - V_\mathrm{OR}\right)}{L_\mathrm{lk}}
  5. It reaches zero after this time, which must fit in the off-time.

    tr=LlkIpkVclamp−VORt_r = \frac{L_\mathrm{lk} I_\mathrm{pk}}{V_\mathrm{clamp} - V_\mathrm{OR}}

    ✓ reproduces clamp.t_reset (equality checked symbolically by pytest)

  6. The clamp absorbs its voltage times this falling current: more than the leakage energy 12LlkIpk2\tfrac12 L_\mathrm{lk} I_\mathrm{pk}^2, because the reflected voltage keeps driving the current meanwhile.

    Eclamp=LlkVclampIpk22(Vclamp−VOR)E_\mathrm{clamp} = \frac{L_\mathrm{lk} V_\mathrm{clamp} I_\mathrm{pk}^{2}}{2 \left(V_\mathrm{clamp} - V_\mathrm{OR}\right)}
  7. Once per switching period.

    Pclamp=LlkVclampfsIpk22(Vclamp−VOR)=LlkIpk2fsVclamp2(Vclamp−VOR)P_\mathrm{clamp} = \frac{L_\mathrm{lk} V_\mathrm{clamp} f_s I_\mathrm{pk}^{2}}{2 \left(V_\mathrm{clamp} - V_\mathrm{OR}\right)} = \frac{L_\mathrm{lk} I_\mathrm{pk}^{2} f_s V_\mathrm{clamp}}{2 \left(V_\mathrm{clamp} - V_\mathrm{OR}\right)}

    ✓ reproduces clamp.P (equality checked symbolically by pytest)

  8. In an RCD clamp the capacitor holds VclampV_\mathrm{clamp} and the resistor dissipates the clamp power.

    Vclamp2Rclamp=LlkVclampfsIpk22(Vclamp−VOR)\frac{V_\mathrm{clamp}^{2}}{R_\mathrm{clamp}} = \frac{L_\mathrm{lk} V_\mathrm{clamp} f_s I_\mathrm{pk}^{2}}{2 \left(V_\mathrm{clamp} - V_\mathrm{OR}\right)}
  9. The root above VORV_\mathrm{OR}.

    Vclamp=VOR+VOR2+2RclampLlkIpk2fs2V_\mathrm{clamp} = \frac{V_\mathrm{OR} + \sqrt{V_\mathrm{OR}^{2} + 2 R_\mathrm{clamp} L_\mathrm{lk} I_\mathrm{pk}^{2} f_s}}{2}

    ✓ reproduces clamp.rcd.V (equality checked symbolically by pytest)

  10. With the load removed and no regulation, the output keeps charging and its reflected voltage rises until it reaches the clamp voltage; from then on the clamp takes all of the input power. This holds for a clamp whose voltage does not depend on that power (a TVS); an RCD clamp's voltage rises with it.

    VD+Vo,maxn=Vclamp\frac{V_D + V_\mathrm{o,max}}{n} = V_\mathrm{clamp}
  11. Solve for the output voltage.

    Vo,max=nVclamp−VDV_\mathrm{o,max} = n V_\mathrm{clamp} - V_D

    ✓ reproduces flyback.V_ceiling (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/clamp.py

Snubbers: parasitics from the ringing, damping resistance and loss

Section titled “Snubbers: parasitics from the ringing, damping resistance and loss”

A switching edge makes a node's parasitic inductance and capacitance ring. Their values, which cannot be read from the layout, follow from the ringing frequency measured twice: as built, and with a known capacitor added.

  1. As built, the node rings at the resonance of its inductance and capacitance:

    fr0=12πCparLparf_{r0} = \frac{1}{2 \pi \sqrt{C_\mathrm{par}} \sqrt{L_\mathrm{par}}}
  2. A known capacitor across the node adds to the capacitance but leaves the inductance as it is:

    fr1=12πLparCadd+Cparf_{r1} = \frac{1}{2 \pi \sqrt{L_\mathrm{par}} \sqrt{C_\mathrm{add} + C_\mathrm{par}}}
  3. The inductance cancels from the ratio of the two frequencies, x=fr0/fr1x = f_{r0}/f_{r1}:

    x2=Cadd+CparCparx^{2} = \frac{C_\mathrm{add} + C_\mathrm{par}}{C_\mathrm{par}}
  4. Solved for the node's capacitance:

    Cpar=Caddfr12fr02−fr12=Cadd(fr0fr1)2−1C_\mathrm{par} = \frac{C_\mathrm{add} f_{r1}^{2}}{f_{r0}^{2} - f_{r1}^{2}} = \frac{C_\mathrm{add}}{\left(\frac{f_{r0}}{f_{r1}}\right)^{2} - 1}

    ✓ reproduces snub.C_par (equality checked symbolically by pytest)

  5. and the first resonance then gives the inductance:

    Lpar=14π2fr02CparL_\mathrm{par} = \frac{1}{4 \pi^{2} f_{r0}^{2} C_\mathrm{par}}

    ✓ reproduces snub.L_par (equality checked symbolically by pytest)

  6. AN11160 takes the damping factor of the parasitic inductance, the snubber resistor and the snubber capacitor in series, leaving the node's own capacitance out. That loop's natural frequencies solve

    1+sCsnubRsnub+CsnubLpars2=01 + s C_\mathrm{snub} R_\mathrm{snub} + C_\mathrm{snub} L_\mathrm{par} s^{2} = 0
  7. Divided by LparCsnubL_\mathrm{par} C_\mathrm{snub} and written as s2+2ζω0s+ω02=0s^2 + 2\zeta\omega_0 s + \omega_0^2 = 0, with ω0=1/LparCsnub\omega_0 = 1/\sqrt{L_\mathrm{par} C_\mathrm{snub}}, its damping factor is

    ζ=RsnubCsnub2Lpar\zeta = \frac{R_\mathrm{snub} \sqrt{C_\mathrm{snub}}}{2 \sqrt{L_\mathrm{par}}}
  8. Solved for the resistance:

    Rsnub=2ζLparCsnubR_\mathrm{snub} = 2 \zeta \sqrt{\frac{L_\mathrm{par}}{C_\mathrm{snub}}}

    ✓ reproduces snub.R (equality checked symbolically by pytest)

  9. At an edge the node steps by VsnubV_\mathrm{snub} and the snubber capacitor charges through the resistor, i=(Vsnub/R) e−t/(RCsnub)i = (V_\mathrm{snub}/R)\,e^{-t/(R C_\mathrm{snub})}; the energy the resistor takes does not depend on RR:

    ER=CsnubVsnub22E_R = \frac{C_\mathrm{snub} V_\mathrm{snub}^{2}}{2}
  10. The next edge discharges the capacitor, through the resistor and the switch that turns on, and loses its stored energy, as much again, so each period loses

    2ER=CsnubVsnub22 E_R = C_\mathrm{snub} V_\mathrm{snub}^{2}
  11. At fsf_s periods per second:

    P=CsnubVsnub2fsP = C_\mathrm{snub} V_\mathrm{snub}^{2} f_s

    ✓ reproduces snub.P (equality checked symbolically by pytest)

  12. AN11160 rates the resistor from the edge that charges the capacitor while the parasitic inductance carries a current IringI_\mathrm{ring}. The source delivers the capacitor's charge CsnubVsnubC_\mathrm{snub} V_\mathrm{snub} at VsnubV_\mathrm{snub}:

    Es=CsnubVsnub2E_s = C_\mathrm{snub} V_\mathrm{snub}^{2}
  13. The capacitor keeps half of that energy and the inductance gives up all of its own, LparIring2/2L_\mathrm{par} I_\mathrm{ring}^2/2; the resistor takes the rest, whatever its value:

    WR=CsnubVsnub22+LparIring22W_R = \frac{C_\mathrm{snub} V_\mathrm{snub}^{2}}{2} + \frac{L_\mathrm{par} I_\mathrm{ring}^{2}}{2}
  14. Once per period:

    PR,snub=fs(CsnubVsnub2+LparIring2)2P_{R,\mathrm{snub}} = \frac{f_s \left(C_\mathrm{snub} V_\mathrm{snub}^{2} + L_\mathrm{par} I_\mathrm{ring}^{2}\right)}{2}

    ✓ reproduces snub.P_R (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/snubbers.py

Topology details: dc currents, ripple, switch stress, utilization

Section titled “Topology details: dc currents, ripple, switch stress, utilization”

Charge balance on the output capacitor gives each converter's dc inductor current. During the on-interval the inductor sees a constant voltage, so its current changes linearly by twice the half-ripple ΔiL\Delta i_L; the capacitor's charge per period gives the voltage ripple Δv\Delta v in the same way. The transistor utilization UU compares the load power with the product of the transistor's peak voltage and rms current.

  1. Buck, charge balance: the capacitor current is IL−V/RI_L - V/R in both intervals (small ripple).

    ⟨iC⟩=IL−VR\langle i_C \rangle = I_L - \frac{V}{R}
  2. Set the average to zero.

    IL=VRI_L = \frac{V}{R}

    ✓ reproduces buck.IL (equality checked symbolically by pytest)

  3. Buck output ripple: the capacitor takes the triangular ac part of the inductor current. Its positive half lasts Ts/2T_s/2 and peaks at ΔiL\Delta i_L, so it delivers the charge qq.

    q=ΔiLTs4q = \frac{\Delta i_L T_s}{4}
  4. That charge raises the capacitor voltage from its minimum to its maximum, 2Δv2\Delta v.

    2CΔv=ΔiLTs42 C \Delta v = \frac{\Delta i_L T_s}{4}
  5. Solve for Δv\Delta v.

    Δv=ΔiLTs8C\Delta v = \frac{\Delta i_L T_s}{8 C}

    ✓ reproduces buck.ripple.v (equality checked symbolically by pytest)

  6. Boost, charge balance: −V/R-V/R while the switch is on, IL−V/RI_L - V/R while the diode conducts.

    (1−D)(IL−VR)−DVR=0\left(1 - D\right) \left(I_L - \frac{V}{R}\right) - \frac{D V}{R} = 0
  7. Solve for ILI_L.

    IL=−VR(−1+D)=V(1−D)RI_L = - \frac{V}{R \left(-1 + D\right)} = \frac{V}{\left(1 - D\right) R}

    ✓ reproduces boost.IL (equality checked symbolically by pytest)

  8. Boost current ripple: the inductor sees VgV_g for DTsD T_s, so its current rises by VgDTs/LV_g D T_s/L, which is 2ΔiL2\Delta i_L.

    2ΔiL=DTsVgL2 \Delta i_L = \frac{D T_s V_g}{L}
  9. Solve for ΔiL\Delta i_L.

    ΔiL=VgDTs2L\Delta i_L = \frac{V_g D T_s}{2 L}

    ✓ reproduces boost.ripple.iL (equality checked symbolically by pytest)

  10. Boost voltage ripple: while the switch is on, the capacitor alone feeds the load current V/RV/R for DTsD T_s, so its voltage falls by 2Δv2\Delta v.

    2Δv=DTsVCR2 \Delta v = \frac{D T_s V}{C R}
  11. Solve for Δv\Delta v.

    Δv=VDTs2RC\Delta v = \frac{V D T_s}{2 R C}

    ✓ reproduces boost.ripple.v (equality checked symbolically by pytest)

  12. Buck-boost with the output magnitude VV: the inductor sees VgV_g while the switch is on and −V-V while the diode conducts.

    DVg−V(1−D)=0D V_g - V \left(1 - D\right) = 0
  13. Solve for VV.

    V=−DVg−1+D=DVg1−DV = - \frac{D V_g}{-1 + D} = \frac{D V_g}{1 - D}

    ✓ reproduces buckboost.V (equality checked symbolically by pytest)

  14. Charge balance is the boost's: the capacitor feeds the load while the switch is on and receives ILI_L while the diode conducts.

    IL=−VR(−1+D)=V(1−D)RI_L = - \frac{V}{R \left(-1 + D\right)} = \frac{V}{\left(1 - D\right) R}

    ✓ reproduces buckboost.IL (equality checked symbolically by pytest)

  15. The inductor sees VgV_g during DTsD T_s, as in the boost.

    ΔiL=VgDTs2L\Delta i_L = \frac{V_g D T_s}{2 L}

    ✓ reproduces buckboost.ripple.iL (equality checked symbolically by pytest)

  16. The capacitor alone feeds the load during DTsD T_s, as in the boost.

    Δv=VDTs2RC\Delta v = \frac{V D T_s}{2 R C}

    ✓ reproduces buckboost.ripple.v (equality checked symbolically by pytest)

  17. Buck: while the diode conducts, the switch node sits at ground (ideal diode); the switch between the input and that node blocks VgV_g.

    VDS=VgV_\mathrm{DS} = V_g

    ✓ reproduces buck.Vds (equality checked symbolically by pytest)

  18. Boost: while the diode conducts, the switch node is connected to the output; the switch from that node to ground blocks VV.

    VDS=VV_\mathrm{DS} = V

    ✓ reproduces boost.Vds (equality checked symbolically by pytest)

  19. While the diode conducts, the switch's far end sits at the output, −V-V; the switch blocks Vg−(−V)V_g - (-V).

    VDS=Vg+VV_\mathrm{DS} = V_g + V

    ✓ reproduces buckboost.Vds (equality checked symbolically by pytest)

  20. Forward, reset interval: the reset winding (NrN_r turns) connects to VgV_g with reversed polarity, so the primary (NpN_p turns) sees −VgNp/Nr-V_g N_p/N_r.

    vpri=−Vgnrv_\mathrm{pri} = - \frac{V_g}{n_r}
  21. The switch blocks the input voltage minus the primary voltage.

    VDS=Vg(1+nr)nr=Vg(1+1nr)V_\mathrm{DS} = \frac{V_g \left(1 + n_r\right)}{n_r} = V_g \left(1 + \frac{1}{n_r}\right)

    ✓ reproduces forward.Vds (equality checked symbolically by pytest)

  22. Forward output inductor: while the switch is on, the secondary applies nVgn V_g, so the inductor sees nVg−Vn V_g - V for DTsD T_s.

    2ΔiL=DTs(−V+Vgn)L2 \Delta i_L = \frac{D T_s \left(- V + V_g n\right)}{L}
  23. Solve for ΔiL\Delta i_L.

    ΔiL=(nVg−V)DTs2L\Delta i_L = \frac{\left(n V_g - V\right) D T_s}{2 L}

    ✓ reproduces forward.ripple.iL (equality checked symbolically by pytest)

  24. Transistor utilization: the load power P=VIP = V I over the transistor's peak voltage times its rms current. With small ripple the transistor current is a flat pulse of height IQI_Q for DTsD T_s, whose rms value is IQDI_Q \sqrt{D}.

  25. Buck: V=DVgV = D V_g, the transistor blocks VgV_g and carries the inductor current II.

    U=DU = \sqrt{D}
  26. Simplify.

    U=DU = \sqrt{D}

    ✓ reproduces util.buck (equality checked symbolically by pytest)

  27. Boost: the transistor blocks VV and carries the inductor current I/(1−D)I/(1 - D).

    U=1−DDU = \frac{1 - D}{\sqrt{D}}
  28. Simplify.

    U=1−DDU = \frac{1 - D}{\sqrt{D}}

    ✓ reproduces util.boost (equality checked symbolically by pytest)

  29. Buck-boost: V=DVg/(1−D)V = D V_g/(1 - D), the transistor blocks Vg+VV_g + V and carries I/(1−D)I/(1 - D).

    U=VgDVg+DVg1−DU = \frac{V_g \sqrt{D}}{V_g + \frac{D V_g}{1 - D}}
  30. Simplify.

    U=(1−D)DU = \left(1 - D\right) \sqrt{D}

    ✓ reproduces util.buckboost (equality checked symbolically by pytest)

  31. Flyback: V=nDVg/(1−D)V = n D V_g/(1 - D), the transistor blocks Vg+V/nV_g + V/n and carries the primary-referred magnetizing current nI/(1−D)n I/(1 - D); the turns ratio cancels and the result equals the buck-boost's.

    U=D(1−D)U = \sqrt{D} \left(1 - D\right)
  32. Forward: V=nDVgV = n D V_g, the transistor blocks Vg(1+1/nr)V_g(1 + 1/n_r) and carries the reflected inductor current nIn I (magnetizing current neglected).

    U=D1+1nrU = \frac{\sqrt{D}}{1 + \frac{1}{n_r}}
  33. Simplify.

    U=nrD1+nr=D1+1nrU = \frac{n_r \sqrt{D}}{1 + n_r} = \frac{\sqrt{D}}{1 + \frac{1}{n_r}}

    ✓ reproduces util.forward (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/topologies.py

Averaging removes the switching ripple; perturbing the averaged equations around the operating point (d=D+d^d = D + \hat d, v=V+v^v = V + \hat v, i=I+i^i = I + \hat i) and dropping second-order products gives linear equations whose Laplace transform yields v^/d^\hat v/\hat d. Each converter below ends in the standard second-order form with gain Gd0G_{d0}, poles at ω0\omega_0 with quality factor QQ, and, for the boost and buck-boost, a zero ωz\omega_z in the right half-plane.

  1. Buck, linearised averaged equations (v^g=0\hat v_g = 0): the inductor sees d vg−vd\,v_g - v, the capacitor carries i−v/Ri - v/R.

    (i^sL=−v^+d^Vg, sv^C=i^−v^R)\left( \hat i s L = - \hat v + \hat d V_g, \ s \hat v C = \hat i - \frac{\hat v}{R}\right)
  2. Solve for v^/d^\hat v/\hat d.

    Gvd(s)=RVgR+sL+CLRs2G_{vd}(s) = \frac{R V_g}{R + s L + C L R s^{2}}
  3. DC gain: the value at s=0s = 0.

    Gd0=VgG_{d0} = V_g

    ✓ reproduces buck.ss.Gd0 (equality checked symbolically by pytest)

  4. Poles: ω0=1/a2\omega_0 = 1/\sqrt{a_2} from the s2s^2 coefficient a2a_2.

    ω0=1LC\omega_0 = \frac{1}{\sqrt{L C}}

    ✓ reproduces buck.ss.w0 (equality checked symbolically by pytest)

  5. Q=a2/a1Q = \sqrt{a_2}/a_1 from the ss coefficient a1a_1.

    Q=RCLQ = R \sqrt{\frac{C}{L}}

    ✓ reproduces buck.ss.Q (equality checked symbolically by pytest)

  6. Boost: the averaged inductor voltage is vg−d′vv_g - d' v and the capacitor current d′i−v/Rd' i - v/R (d′=1−dd' = 1 - d). Perturbing d′=D′−d^d' = D' - \hat d and linearising:

    (i^sL=d^V−v^D′, sv^C=i^D′−d^I−v^R)\left( \hat i s L = \hat d V - \hat v D', \ s \hat v C = \hat i D' - \hat d I - \frac{\hat v}{R}\right)
  7. Operating point from the steady state, writing D′=1−DD' = 1 - D: V=Vg/D′V = V_g/D', I=V/(D′R)I = V/(D' R).

    (V=VgD′, I=VgD′2R)\left( V = \frac{V_g}{D'}, \ I = \frac{V_g}{D'^{2} R}\right)
  8. Solve for v^/d^\hat v/\hat d.

    Gvd(s)=Vg(−sL+RD′2)D′2(sL+RD′2+CLRs2)G_{vd}(s) = \frac{V_g \left(- s L + R D'^{2}\right)}{D'^{2} \left(s L + R D'^{2} + C L R s^{2}\right)}
  9. DC gain (results are written back with D′=1−DD' = 1 - D).

    Gd0=Vg(1−D)2G_{d0} = \frac{V_g}{\left(1 - D\right)^{2}}

    ✓ reproduces boost.ss.Gd0 (equality checked symbolically by pytest)

  10. Pole frequency.

    ω0=1−DLC\omega_0 = \frac{1 - D}{\sqrt{L C}}

    ✓ reproduces boost.ss.w0 (equality checked symbolically by pytest)

  11. Quality factor.

    Q=(1−D)RCLQ = \left(1 - D\right) R \sqrt{\frac{C}{L}}

    ✓ reproduces boost.ss.Q (equality checked symbolically by pytest)

  12. The numerator vanishes at a positive real ss: a right-half-plane zero.

    ωz=(1−D)2RL\omega_z = \frac{\left(1 - D\right)^{2} R}{L}

    ✓ reproduces boost.ss.wz (equality checked symbolically by pytest)

  13. Buck-boost: averaged inductor voltage d vg+d′vd\,v_g + d' v and capacitor current −d′i−v/R-d' i - v/R, with the output vv negative. Linearised:

    (i^sL=d^(Vg−V)+v^D′, sv^C=d^I−i^D′−v^R)\left( \hat i s L = \hat d \left(V_g - V\right) + \hat v D', \ s \hat v C = \hat d I - \hat i D' - \frac{\hat v}{R}\right)
  14. Operating point, with D′=1−DD' = 1 - D: V=−DVg/D′V = -D V_g/D', I=DVg/(D′2R)I = D V_g/(D'^2 R).

    (V=Vg(−1+D′)D′, I=Vg(1−D′)D′2R)\left( V = \frac{V_g \left(-1 + D'\right)}{D'}, \ I = \frac{V_g \left(1 - D'\right)}{D'^{2} R}\right)
  15. Solve for v^/d^\hat v/\hat d.

    Gvd(s)=Vg(−sL(−1+D′)−RD′2)D′2(sL+RD′2+CLRs2)G_{vd}(s) = \frac{V_g \left(- s L \left(-1 + D'\right) - R D'^{2}\right)}{D'^{2} \left(s L + R D'^{2} + C L R s^{2}\right)}
  16. The dc gain is negative (inverting output); its magnitude is:

    Gd0=Vg(1−D)2G_{d0} = \frac{V_g}{\left(1 - D\right)^{2}}

    ✓ reproduces buckboost.ss.Gd0 (equality checked symbolically by pytest)

  17. Pole frequency.

    ω0=1−DLC\omega_0 = \frac{1 - D}{\sqrt{L C}}

    ✓ reproduces buckboost.ss.w0 (equality checked symbolically by pytest)

  18. Quality factor.

    Q=(1−D)RCLQ = \left(1 - D\right) R \sqrt{\frac{C}{L}}

    ✓ reproduces buckboost.ss.Q (equality checked symbolically by pytest)

  19. Right-half-plane zero.

    ωz=R(−1+D)2DL=(1−D)2RDL\omega_z = \frac{R \left(-1 + D\right)^{2}}{D L} = \frac{\left(1 - D\right)^{2} R}{D L}

    ✓ reproduces buckboost.ss.wz (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/small_signal.py

A voltage-mode controller compares a control voltage with a periodic ramp to set the duty ratio. Going once around the loop multiplies the gains of the compensator, the modulator, the power stage and the sensor; that product, the loop gain TT, decides how strongly the loop rejects disturbances.

  1. The ramp rises from 00 to VMV_M in TsT_s; the switch turns off when it crosses VctrlV_\mathrm{ctrl}.

    VMtoffTs=Vctrl\frac{V_M t_\mathrm{off}}{T_s} = V_\mathrm{ctrl}
  2. The on-time fraction is D=toff/TsD = t_\mathrm{off}/T_s.

    D=VctrlVMD = \frac{V_\mathrm{ctrl}}{V_M}

    ✓ reproduces pwm.d (equality checked symbolically by pytest)

  3. Around the loop: the compensator output drives the modulator (gain 1/VM1/V_M), the power stage turns d^\hat d into v^\hat v (gain GvdG_{vd}), and the sensor feeds back Hv^H \hat v.

  4. The loop gain is the product around the loop.

    T=GcGvdHVMT = \frac{G_c G_{vd} H}{V_M}

    ✓ reproduces loop.T (equality checked symbolically by pytest)

  5. An output disturbance v^dist\hat v_\mathrm{dist} adds to the controlled output; the controller acts on the error v^ref−Hv^\hat v_\mathrm{ref} - H\hat v.

    v^=v^dist+GcGvd(v^ref−v^H)VM\hat v = \hat v_\mathrm{dist} + \frac{G_c G_{vd} \left(\hat v_\mathrm{ref} - \hat v H\right)}{V_M}
  6. The disturbance reaching the output is divided by 1+T1 + T.

    S=11+TS = \frac{1}{1 + T}

    ✓ reproduces loop.suppression (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/control.py

Energy stored in an inductor or capacitor is the integral of the power viv i delivered to it. A loss that repeats every switching period is the energy lost per cycle times fsf_s; a conduction loss is the period average of the instantaneous power.

  1. Energy delivered to an inductance: p=vip = v i with v=L di/dtv = L\,di/dt, so dE=Li didE = L i\,di. Integrate from zero to the turn-off current.

    Elk=∫0IpkiLlk diE_\mathrm{lk} = \int\limits_{0}^{I_\mathrm{pk}} i L_\mathrm{lk}\, di
  2. Evaluate the integral.

    Elk=LlkIpk22E_\mathrm{lk} = \frac{L_\mathrm{lk} I_\mathrm{pk}^{2}}{2}

    ✓ reproduces flyback.leak.E (equality checked symbolically by pytest)

  3. If this energy is removed (clamped or dissipated) every cycle, the average power is ElkfsE_\mathrm{lk} f_s.

    Plk=ElkfsP_\mathrm{lk} = E_\mathrm{lk} f_s

    ✓ reproduces flyback.leak.P (equality checked symbolically by pytest)

  4. Energy stored in the switch-node capacitance charged to VswV_\mathrm{sw}: dE=v dq=Cv dvdE = v\,dq = C v\,dv.

    EC=∫0VswvCnode dvE_C = \int\limits_{0}^{V_\mathrm{sw}} v C_\mathrm{node}\, dv
  5. A hard turn-on discharges it through the switch channel every cycle: P=ECfsP = E_C f_s.

    P=CnodeVsw2fs2P = \frac{C_\mathrm{node} V_\mathrm{sw}^{2} f_s}{2}

    ✓ reproduces loss.sw.cap (equality checked symbolically by pytest)

  6. Conduction loss is the period average of the instantaneous power i(t)2Rxi(t)^2 R_x.

    P=∫0TsRxi2(t) dtTsP = \frac{\int\limits_{0}^{T_s} R_x i^{2}{\left(t \right)}\, dt}{T_s}
  7. By definition the rms current is the square root of the period average of i(t)2i(t)^2.

    Irms2=∫0Tsi2(t) dtTsI_\mathrm{rms}^{2} = \frac{\int\limits_{0}^{T_s} i^{2}{\left(t \right)}\, dt}{T_s}
  8. RxR_x is constant, so it moves out of the average.

    P=Irms2RxP = I_\mathrm{rms}^{2} R_x

    ✓ reproduces loss.cond (equality checked symbolically by pytest)

  9. A conducting diode is modelled as a forward-voltage intercept plus a dynamic resistance, vD=VF+rdiv_D = V_F + r_d i, so p=VFi+rdi2p = V_F i + r_d i^2.

    P=∫0Ts(VFi(t)+rdi2(t)) dtTsP = \frac{\int\limits_{0}^{T_s} \left(V_F i{\left(t \right)} + r_d i^{2}{\left(t \right)}\right)\, dt}{T_s}
  10. The average of ii is IavgI_\mathrm{avg} and the average of i2i^2 is Irms2I_\mathrm{rms}^2.

    P=VFIavg+rdIrms2P = V_F I_\mathrm{avg} + r_d I_\mathrm{rms}^{2}

    ✓ reproduces loss.diode (equality checked symbolically by pytest)

  11. Each cycle the driver draws the gate charge QgQ_g from its VGSV_\mathrm{GS} supply; the energy taken from the supply is the integral of VGS dqV_\mathrm{GS}\,dq.

    EG=∫0QgVGS dqE_G = \int\limits_{0}^{Q_g} V_\mathrm{GS}\, dq
  12. All of it ends up as heat in the driver and gate resistances; multiply by fsf_s.

    P=QgVGSfsP = Q_g V_\mathrm{GS} f_s

    ✓ reproduces loss.gate (equality checked symbolically by pytest)

  13. An undamped L-C loop obeys LC d2v/dt2+v=0L C\,d^2v/dt^2 + v = 0; its characteristic equation is LCs2+1=0L C s^2 + 1 = 0.

    1+CnodeLMs2=01 + C_\mathrm{node} L_M s^{2} = 0
  14. The roots are ±jω0\pm j\omega_0.

    ω0=1CnodeLM\omega_0 = \frac{1}{\sqrt{C_\mathrm{node}} \sqrt{L_M}}
  15. f=ω0/(2π)f = \omega_0/(2\pi).

    fring=12πLMCnodef_\mathrm{ring} = \frac{1}{2 \pi \sqrt{L_M C_\mathrm{node}}}

    ✓ reproduces dcm.ring.f (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/energy.py

Magnetic circuits: the inductance factor, flux density and the core geometrical constant

Section titled “Magnetic circuits: the inductance factor, flux density and the core geometrical constant”

A magnetic path behaves like a resistive circuit: the magnetomotive force NiN i drives flux Φ\Phi through reluctances R=l/(μA)\mathcal{R} = l/(\mu A). With a uniform flux density over the effective area AeA_e, B=Φ/AeB = \Phi/A_e.

  1. Core reluctance (relative permeability μi\mu_i).

    Rc=leAeμ0μi\mathcal{R}_c = \frac{l_e}{A_e \mu_0 \mu_i}
  2. Gap reluctance (no fringing: the gap area equals AeA_e).

    Rg=lgAeμ0\mathcal{R}_g = \frac{l_g}{A_e \mu_0}
  3. They are in series.

    R=lgAeμ0+leAeμ0μi\mathcal{R} = \frac{l_g}{A_e \mu_0} + \frac{l_e}{A_e \mu_0 \mu_i}
  4. Flux linkage λ=NΦ=N(Ni/R)\lambda = N\Phi = N (N i/\mathcal{R}), so L=λ/i=N2/RL = \lambda/i = N^2/\mathcal{R}.

    L=N2RL = \frac{N^{2}}{\mathcal{R}}
  5. The inductance factor is the inductance per turn squared, AL=L/N2=1/RA_L = L/N^2 = 1/\mathcal{R}.

    AL=1RA_L = \frac{1}{\mathcal{R}}
  6. Substitute the two reluctances.

    AL=Aeμ0μile+lgμi=μ0Aelg+leμiA_L = \frac{A_e \mu_0 \mu_i}{l_e + l_g \mu_i} = \frac{\mu_0 A_e}{l_g + \frac{l_e}{\mu_i}}

    ✓ reproduces mag.AL_gap (equality checked symbolically by pytest)

  7. Hence L=ALN2L = A_L N^2.

    L=ALN2L = A_L N^{2}

    ✓ reproduces mag.L_from_AL (equality checked symbolically by pytest)

  8. Flux linkage and inductance: λ=NΦ=Li\lambda = N\Phi = L i, so Φ=Li/N\Phi = L i/N.

    λ=NΦ\lambda = N \Phi
  9. At the peak current, Bpk=Φpk/Ae=LIpk/(NAe)B_\mathrm{pk} = \Phi_\mathrm{pk}/A_e = L I_\mathrm{pk}/(N A_e).

    Bpk=LIpkNAeB_\mathrm{pk} = \frac{L I_\mathrm{pk}}{N A_e}

    ✓ reproduces mag.B_pk (equality checked symbolically by pytest)

  10. The flux density follows the current, B=Li/(NAe)B = L i/(N A_e). A ripple of ±ΔiL\pm\Delta i_L about the dc current moves it by ±L ΔiL/(NAe)\pm L\,\Delta i_L/(N A_e) about its dc value: that amplitude is BacB_\mathrm{ac}.

    Bac=LΔiLNAeB_\mathrm{ac} = \frac{L \Delta i_L}{N A_e}

    ✓ reproduces mag.B_ac (equality checked symbolically by pytest)

  11. Holding BpkB_\mathrm{pk} at the limit BmaxB_\mathrm{max} and solving for the turns gives the fewest turns that stay within it.

    N=LIpkBmaxAeN = \frac{L I_\mathrm{pk}}{B_\mathrm{max} A_e}

    ✓ reproduces mag.N_Bmax (equality checked symbolically by pytest)

  12. With NN fixed, the inductance L=N2/(Rc+Rg)L = N^2/(\mathcal{R}_c + \mathcal{R}_g) sets the gap reluctance, and with it the gap length.

    lg=μ0AeN2L−leμil_g = \frac{\mu_0 A_e N^{2}}{L} - \frac{l_e}{\mu_i}

    ✓ reproduces mag.gap_length (equality checked symbolically by pytest)

  13. Choosing a core. The winding's NN turns of copper area AwA_w may fill only KuK_u of the window WAW_A:

    KuWA=AwNK_u W_A = A_w N
  14. At the limit, its resistance, NN turns of mean length MLT, equals the copper-loss budget Rdc,maxR_\mathrm{dc,max}:

    Rdc,max=MLTNρwAwR_\mathrm{dc,max} = \frac{\mathrm{MLT} N \rho_w}{A_w}
  15. The window fixes the wire, Aw=KuWA/NA_w = K_u W_A/N, so the resistance grows as the square of the turns:

    Rdc,max=MLTρwN2KuWAR_\mathrm{dc,max} = \frac{\mathrm{MLT} \rho_w N^{2}}{K_u W_A}
  16. The flux limit fixes the turns, N=LIpk/(BmaxAe)N = L I_\mathrm{pk}/(B_\mathrm{max} A_e):

    Rdc,max=MLTρwIpk2L2Ae2Bmax2KuWAR_\mathrm{dc,max} = \frac{\mathrm{MLT} \rho_w I_\mathrm{pk}^{2} L^{2}}{A_e^{2} B_\mathrm{max}^{2} K_u W_A}
  17. Collecting the core's dimensions on one side defines the core geometrical constant; the other side depends on the specification alone.

    Kg=WAAe2MLTK_g = \frac{W_A A_e^{2}}{\mathrm{MLT}}
  18. A core meets the flux limit, the window and the resistance budget together when its KgK_g is at least:

    Kg=ρwL2Ipk2Bmax2Rdc,maxKuK_g = \frac{\rho_w L^{2} I_\mathrm{pk}^{2}}{B_\mathrm{max}^{2} R_\mathrm{dc,max} K_u}

    ✓ reproduces mag.Kg_req (equality checked symbolically by pytest)

  19. Faraday's law: v=NAe dB/dtv = N A_e\,dB/dt, so the change of BB over the on-time is the integral of v/(NAe)v/(N A_e).

    ΔB=∫0tonVwAeN dt\Delta B = \int\limits_{0}^{t_\mathrm{on}} \frac{V_w}{A_e N}\, dt
  20. With a constant winding voltage VwV_w the swing is proportional to the volt-seconds VwtonV_w t_\mathrm{on}.

    ΔB=VwtonNAe\Delta B = \frac{V_w t_\mathrm{on}}{N A_e}

    ✓ reproduces mag.dB_faraday (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/magnetics.py

Windings: resistance, skin depth, thin layers, leakage and the short circuit

Section titled “Windings: resistance, skin depth, thin layers, leakage and the short circuit”

A winding is a long conductor wound in layers. Its resistance follows from its length and cross-section; at high frequency the current crowds into a skin; and the field between the windings stores the energy that appears as leakage inductance.

  1. The winding is one conductor, NN turns of mean length MLT:

    lw=MLTNl_w = \mathrm{MLT} N
  2. Its dc resistance is the resistivity times the length over the conductor's area.

    Rdc=ρwNMLTAwR_\mathrm{dc} = \frac{\rho_w N \mathrm{MLT}}{A_w}

    ✓ reproduces wind.dcr (equality checked symbolically by pytest)

  3. In a good conductor (conductivity 1/ρw1/\rho_w) a sinusoidal current density obeys d2J/dx2=jωμ0J/ρwd^2 J/dx^2 = j\omega\mu_0 J/\rho_w, so it varies as e−kxe^{-k x} with

    k2=2πifμ0ρwk^{2} = \frac{2 \pi i f \mu_0}{\rho_w}
  4. The root with a positive real part:

    k=πfμ0ρw+iπfμ0ρwk = \frac{\sqrt{\pi} \sqrt{f} \sqrt{\mu_0}}{\sqrt{\rho_w}} + \frac{i \sqrt{\pi} \sqrt{f} \sqrt{\mu_0}}{\sqrt{\rho_w}}
  5. The magnitude falls by ee over one skin depth, the inverse of the real part of kk.

    δ=ρwπμ0f\delta = \sqrt{\frac{\rho_w}{\pi \mu_0 f}}

    ✓ reproduces wind.skin_depth (equality checked symbolically by pytest)

  6. Dowell's ac resistance factor of MℓM_\ell layers, each φ\varphi skin depths thick (`wind.dowell`):

    FR=φ(sin⁡(2φ)+sinh⁡(2φ)−cos⁡(2φ)+cosh⁡(2φ)+(−23+2Mℓ23)(−sin⁡(φ)+sinh⁡(φ))cos⁡(φ)+cosh⁡(φ))F_R = \varphi \left(\frac{\sin{\left(2 \varphi \right)} + \sinh{\left(2 \varphi \right)}}{- \cos{\left(2 \varphi \right)} + \cosh{\left(2 \varphi \right)}} + \frac{\left(- \frac{2}{3} + \frac{2 M_\ell^{2}}{3}\right) \left(- \sin{\left(\varphi \right)} + \sinh{\left(\varphi \right)}\right)}{\cos{\left(\varphi \right)} + \cosh{\left(\varphi \right)}}\right)
  7. For thin layers, expand the hyperbolic and circular functions in powers of φ\varphi: the terms up to φ3\varphi^3 cancel and the first excess is of fourth order.

    FR=1−φ445+Mℓ2φ49=1+(5Mℓ2−1)φ445F_R = 1 - \frac{\varphi^{4}}{45} + \frac{M_\ell^{2} \varphi^{4}}{9} = 1 + \frac{\left(5 M_\ell^{2} - 1\right) \varphi^{4}}{45}

    ✓ reproduces wind.dowell_low (equality checked symbolically by pytest)

  8. At a given frequency the skin depth is fixed, so a layer φ\varphi skin depths thick has the dc resistance Rδ/φR_\delta/\varphi, RδR_\delta being that of a layer one skin depth thick. Carrying a current II, it loses

    P=FRRδI2φP = \frac{F_R R_\delta I^{2}}{\varphi}
  9. Relative to the loss of a layer one skin depth thick carrying the same current as dc, RδI2R_\delta I^2:

    P/Pδ=FRφP/P_\delta = \frac{F_R}{\varphi}

    ✓ reproduces wind.loss_rel (equality checked symbolically by pytest)

  10. With the series for thin layers:

    P/Pδ=1−φ445+Mℓ2φ49φP/P_\delta = \frac{1 - \frac{\varphi^{4}}{45} + \frac{M_\ell^{2} \varphi^{4}}{9}}{\varphi}
  11. It is least where its derivative vanishes:

    φopt=(155Mℓ2−1)14\varphi_\mathrm{opt} = \left(\frac{15}{5 M_\ell^{2} - 1}\right)^{\frac{1}{4}}

    ✓ reproduces wind.phi_opt (equality checked symbolically by pytest)

  12. There the ac resistance factor is the same for every number of layers:

    FR=43F_R = \frac{4}{3}
  13. The field in the window runs along the breadth bwb_w, so H=F/bwH = \mathcal{F}/b_w where F\mathcal{F} is the MMF enclosed at that height; the energy it stores over the turn length MLT is

    E=MLTμ0∫F2 dy2bwE = \frac{\mathrm{MLT} \mu_0 \int \mathcal{F}^{2}\, dy}{2 b_w}
  14. Primary, then secondary: the MMF rises linearly through the primary to NIN I, stays there across the spacing, and falls back to zero through the secondary.

    F=IN\mathcal{F} = I N
  15. With Llk=2E/I2L_\mathrm{lk} = 2E/I^2, each winding counts with a third of its height, the spacing in full.

    Llk=MLTμ0N2(hp+hs+3hg)3bw=μ0N2MLT(hp3+hg+hs3)bwL_\mathrm{lk} = \frac{\mathrm{MLT} \mu_0 N^{2} \left(h_p + h_s + 3 h_g\right)}{3 b_w} = \frac{\mu_0 N^{2} \mathrm{MLT} \left(\frac{h_p}{3} + h_g + \frac{h_s}{3}\right)}{b_w}

    ✓ reproduces xfmr.leakage.ps (equality checked symbolically by pytest)

  16. Primary split around the secondary: the MMF rises to NI/2N I/2 through the first half, crosses the secondary from NI/2N I/2 to −NI/2-N I/2, and returns to zero through the second half.

    F=IN2\mathcal{F} = \frac{I N}{2}
  17. The peak MMF is halved, so the energy, and the leakage, fall to about a quarter; the two spacings both count.

    Llk=MLTμ0N2(hp+hs+6hg)12bw=μ0N2MLT(hp3+2hg+hs3)4bwL_\mathrm{lk} = \frac{\mathrm{MLT} \mu_0 N^{2} \left(h_p + h_s + 6 h_g\right)}{12 b_w} = \frac{\mu_0 N^{2} \mathrm{MLT} \left(\frac{h_p}{3} + 2 h_g + \frac{h_s}{3}\right)}{4 b_w}

    ✓ reproduces xfmr.leakage.psp (equality checked symbolically by pytest)

  18. Two coupled windings, each with its own inductance and the mutual inductance L12L_{12} between them:

    v1=di1dtL11+di2dtL12v_1 = \frac{di_1}{dt} L_{11} + \frac{di_2}{dt} L_{12}
  19. and at winding 2:

    v2=di1dtL12+di2dtL22v_2 = \frac{di_1}{dt} L_{12} + \frac{di_2}{dt} L_{22}
  20. Shorting winding 2 sets v2=0v_2 = 0, so its current changes against winding 1's:

    di2dt=−di1dtL12L22\frac{di_2}{dt} = - \frac{\frac{di_1}{dt} L_{12}}{L_{22}}
  21. Winding 1 then sees the inductance v1/(di1/dt)v_1/(di_1/dt):

    Lsc=L11−L122L22L_\mathrm{sc} = L_{11} - \frac{L_{12}^{2}}{L_{22}}
  22. With the coupling coefficient k=L12/L11L22k = L_{12}/\sqrt{L_{11} L_{22}} (`xfmr.k`):

    Lsc=L11(1−k2)L_\mathrm{sc} = L_{11} \left(1 - k^{2}\right)

    ✓ reproduces xfmr.L_sc (equality checked symbolically by pytest)

  23. In the transformer model (1:nn, magnetizing inductance LML_M and the leakage inductances, all referred to the primary) the winding inductances are

    L11=LM+Lℓ1L_{11} = L_M + L_{\ell 1}
  24. the mutual inductance,

    L12=LMnL_{12} = L_M n
  25. and winding 2's inductance (its leakage Lℓ2=n2Lℓ2′L_{\ell 2} = n^2 L_{\ell 2}'):

    L22=n2(LM+Lℓ2′)L_{22} = n^{2} \left(L_M + L_{\ell 2}'\right)
  26. Substituting them, the turns ratio cancels: the primary's leakage in series with the secondary's in parallel with LML_M.

    Lsc=Lℓ1+Lℓ2′LMLℓ2′+LML_\mathrm{sc} = L_{\ell 1} + \frac{L_{\ell 2}' L_M}{L_{\ell 2}' + L_M}

    ✓ reproduces xfmr.L_sc_T (equality checked symbolically by pytest)

  27. With winding 2 open, no current flows in it (di2/dt=0di_2/dt = 0), and the two voltages stand in the ratio

    V2/V1=L12L11V_2/V_1 = \frac{L_{12}}{L_{11}}
  28. In the transformer model: slightly below the turns ratio, the primary's leakage taking its share of the voltage.

    V2/V1=nLMLℓ1+LMV_2/V_1 = \frac{n L_M}{L_{\ell 1} + L_M}

    ✓ reproduces xfmr.V_oc (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/windings.py

Measurement: inductance read near self-resonance

Section titled “Measurement: inductance read near self-resonance”

A winding is an inductance with its turns' capacitance in parallel. An impedance meter divides the reactance it measures by ω\omega and reports that as the inductance.

  1. The inductance and the capacitance in parallel:

    Z=1iCpω−iLωZ = \frac{1}{i C_p \omega - \frac{i}{L \omega}}
  2. which is a pure reactance,

    Z=−iLω−1+CpLω2Z = - \frac{i L \omega}{-1 + C_p L \omega^{2}}
  3. so the meter reads the inductance Z/(jω)Z/(j\omega):

    Lapp=−L−1+CpLω2L_\mathrm{app} = - \frac{L}{-1 + C_p L \omega^{2}}
  4. The self-resonant frequency (`passive.f_srf`) gives ω2LCp=(f/fsrf)2\omega^2 L C_p = (f/f_\mathrm{srf})^2:

    fsrf=12πCpLf_\mathrm{srf} = \frac{1}{2 \pi \sqrt{C_p} \sqrt{L}}
  5. Substituting ω=2πf\omega = 2\pi f:

    Lapp=−Lfsrf2f2−fsrf2=L1−(ffsrf)2L_\mathrm{app} = - \frac{L f_\mathrm{srf}^{2}}{f^{2} - f_\mathrm{srf}^{2}} = \frac{L}{1 - \left(\frac{f}{f_\mathrm{srf}}\right)^{2}}

    ✓ reproduces meas.L_app (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/measurement.py

Linear sources: maximum power, constant-voltage extraction and the loss-free resistor

Section titled “Linear sources: maximum power, constant-voltage extraction and the loss-free resistor”

A linear energy source is modelled by its Thevenin equivalent: open-circuit voltage VocV_\mathrm{oc} behind a source resistance RsR_s. How much of its available power reaches the converter depends on what the converter's input looks like.

  1. Power into a load resistance RLR_L.

    PL=RLVoc2(RL+Rs)2P_L = \frac{R_L V_\mathrm{oc}^{2}}{\left(R_L + R_s\right)^{2}}
  2. dPL/dRL=0dP_L/dR_L = 0 gives the matched load.

    RL=RsR_L = R_s
  3. Substitute RL=RsR_L = R_s.

    Pmax=Voc24RsP_\mathrm{max} = \frac{V_\mathrm{oc}^{2}}{4 R_s}

    ✓ reproduces src.Pmax (equality checked symbolically by pytest)

  4. A constant-voltage sink at VcV_c (e.g. a converter pinned at a fixed input voltage) draws I=(Voc−Vc)/RsI = (V_\mathrm{oc} - V_c)/R_s.

    I=Voc−VcRsI = \frac{V_\mathrm{oc} - V_c}{R_s}
  5. The power it absorbs: negative when Vc>VocV_c > V_\mathrm{oc}, when the sink returns energy to the source.

    P=Vc(Voc−Vc)RsP = \frac{V_c \left(V_\mathrm{oc} - V_c\right)}{R_s}

    ✓ reproduces src.cv_power (equality checked symbolically by pytest)

  6. Normalise by PmaxP_\mathrm{max}.

    ηext=4Vc(Voc−Vc)Voc2\eta_\mathrm{ext} = \frac{4 V_c \left(V_\mathrm{oc} - V_c\right)}{V_\mathrm{oc}^{2}}

    ✓ reproduces src.cv_extraction (equality checked symbolically by pytest)

  7. A converter in DCM draws an average input current proportional to its input voltage: its input is a resistance RinR_\mathrm{in}, the loss-free resistor, which takes the place of RLR_L.

  8. The source resistance and RinR_\mathrm{in} divide the source voltage.

    Vg=VocRinRs+RinV_g = \frac{V_\mathrm{oc} R_\mathrm{in}}{R_s + R_\mathrm{in}}

    ✓ reproduces lfr.Vg (equality checked symbolically by pytest)

  9. PLP_L with RL=RinR_L = R_\mathrm{in}, normalised by PmaxP_\mathrm{max}: the source voltage cancels.

    ηext=4RsRin(Rs+Rin)2\eta_\mathrm{ext} = \frac{4 R_s R_\mathrm{in}}{\left(R_s + R_\mathrm{in}\right)^{2}}

    ✓ reproduces lfr.eta (equality checked symbolically by pytest)

  10. The loss-free resistor draws the power of a resistance at its input voltage.

    P=Vg2RinP = \frac{V_g^{2}}{R_\mathrm{in}}
  11. Solve for VgV_g: a larger power needs a higher input voltage.

    Vg=PRinV_g = \sqrt{P R_\mathrm{in}}

    ✓ reproduces lfr.Vg_power (equality checked symbolically by pytest)

  12. A DCM flyback's input resistance is Rin=2LMfs/D2R_\mathrm{in} = 2 L_M f_s / D^2 (derived with the flyback).

    Rin=2LMfsD2R_\mathrm{in} = \frac{2 L_M f_s}{D^{2}}
  13. Solve for LML_M: the inductance that presents a wanted RinR_\mathrm{in}, such as RsR_s to match the source.

    LM=RinD22fsL_M = \frac{R_\mathrm{in} D^{2}}{2 f_s}

    ✓ reproduces lfr.L_M (equality checked symbolically by pytest)

  14. Under an envelope slow against the bus, the loss-free resistor takes ηextVoc2/(4Rs)\eta_\mathrm{ext} V_\mathrm{oc}^2/(4 R_s) at every instant; take Voc(t)=V^ocsin⁡(πt/Tenv)V_\mathrm{oc}(t) = \hat{V}_\mathrm{oc} \sin(\pi t / T_\mathrm{env}), one half-wave of the envelope (a rectified sine repeats it).

    P=ηextV^oc2sin⁡2(πtTenv)4RsP = \frac{\eta_\mathrm{ext} \hat{V}_\mathrm{oc}^{2} \sin^{2}{\left(\frac{\pi t}{T_\mathrm{env}} \right)}}{4 R_s}
  15. Average over the half-wave: the square of a sine averages to half its peak's.

    Pˉ=ηextV^oc28Rs\bar{P} = \frac{\eta_\mathrm{ext} \hat{V}_\mathrm{oc}^{2}}{8 R_s}

    ✓ reproduces lfr.P_env (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/harvesting.py

Piezoelectric source: loads and interfaces

Section titled “Piezoelectric source: loads and interfaces”

Under a steady sinusoidal vibration of constant amplitude, a piezoelectric element is a current source Ipsin⁡ωtI_p \sin \omega t in parallel with its clamped capacitance C0C_0. What a load or an interface takes follows from where that current's charge goes in each half period.

  1. With nothing connected, the whole current charges C0C_0: v=1C0∫Ipsin⁡ωt dtv = \frac{1}{C_0}\int I_p \sin \omega t \, dt.

    v=−Ipcos⁡(ωt)C0ωv = - \frac{I_p \cos{\left(\omega t \right)}}{C_0 \omega}
  2. Its amplitude.

    Vp=IpωC0V_p = \frac{I_p}{\omega C_0}

    ✓ reproduces piezo.Vp (equality checked symbolically by pytest)

  3. A resistor RR across the element shares the current with C0C_0: its voltage amplitude is Ip∣R∥1/(jωC0)∣I_p |R \parallel 1/(j\omega C_0)|, and it takes half the square of that over RR.

    P=RIp22(1+C02R2ω2)P = \frac{R I_p^{2}}{2 \left(1 + C_0^{2} R^{2} \omega^{2}\right)}
  4. dP/dR=0dP/dR = 0.

    R=1ωC0R = \frac{1}{\omega C_0}

    ✓ reproduces piezo.R_opt (equality checked symbolically by pytest)

  5. Substitute it.

    P=Ip24ωC0P = \frac{I_p^{2}}{4 \omega C_0}

    ✓ reproduces piezo.P_R (equality checked symbolically by pytest)

  6. The source drives 2Ip/ω2 I_p/\omega of charge each half period, between two extrema of the displacement.

    q=2Ipωq = \frac{2 I_p}{\omega}
  7. Through a full-wave bridge onto VDCV_\mathrm{DC}, the element's voltage swings from −VDC-V_\mathrm{DC} to VDCV_\mathrm{DC} before the bridge conducts, which takes 2C0VDC2 C_0 V_\mathrm{DC}; the rest of the charge enters VDCV_\mathrm{DC}.

    E1/2=VDC(−2C0VDC+2Ipω)E_{1/2} = V_\mathrm{DC} \left(- 2 C_0 V_\mathrm{DC} + \frac{2 I_p}{\omega}\right)
  8. Per unit time: one such half period every π/ω\pi/\omega.

    P=2IpVDCπ−2C0ωVDC2π=2VDC(Ip−ωC0VDC)πP = \frac{2 I_p V_\mathrm{DC}}{\pi} - \frac{2 C_0 \omega V_\mathrm{DC}^{2}}{\pi} = \frac{2 V_\mathrm{DC} \left(I_p - \omega C_0 V_\mathrm{DC}\right)}{\pi}

    ✓ reproduces piezo.P_std (equality checked symbolically by pytest)

  9. dP/dVDC=0dP/dV_\mathrm{DC} = 0: half the open-circuit amplitude.

    VDC=Ip2C0ωV_\mathrm{DC} = \frac{I_p}{2 C_0 \omega}
  10. Substitute it.

    Pstd,max=Ip22πωC0P_\mathrm{std,max} = \frac{I_p^{2}}{2 \pi \omega C_0}

    ✓ reproduces piezo.P_std_max (equality checked symbolically by pytest)

  11. SECE leaves the element open between extrema, so its voltage climbs from zero to 2Ip/(ωC0)2 I_p/(\omega C_0); at the extremum all of 12C0v2\tfrac12 C_0 v^2 is taken, whatever the load.

    E1/2=2Ip2C0ω2E_{1/2} = \frac{2 I_p^{2}}{C_0 \omega^{2}}
  12. Per unit time.

    P=2Ip2πωC0P = \frac{2 I_p^{2}}{\pi \omega C_0}

    ✓ reproduces piezo.P_sece (equality checked symbolically by pytest)

  13. Parallel SSHI inverts the element's voltage at each extremum, from ∓VDC\mp V_\mathrm{DC} to ±γVDC\pm\gamma V_\mathrm{DC}, so the swing to the other rail takes only (1−γ)C0VDC(1 - \gamma) C_0 V_\mathrm{DC}.

    E1/2=VDC(−C0VDC(1−γ)+2Ipω)E_{1/2} = V_\mathrm{DC} \left(- C_0 V_\mathrm{DC} \left(1 - \gamma\right) + \frac{2 I_p}{\omega}\right)
  14. Per unit time.

    P=2IpVDCπ−C0ωVDC2π+C0γωVDC2π=2VDC(Ip−ωC0VDC(1−γ)2)πP = \frac{2 I_p V_\mathrm{DC}}{\pi} - \frac{C_0 \omega V_\mathrm{DC}^{2}}{\pi} + \frac{C_0 \gamma \omega V_\mathrm{DC}^{2}}{\pi} = \frac{2 V_\mathrm{DC} \left(I_p - \frac{\omega C_0 V_\mathrm{DC} \left(1 - \gamma\right)}{2}\right)}{\pi}

    ✓ reproduces piezo.P_sshi (equality checked symbolically by pytest)

  15. dP/dVDC=0dP/dV_\mathrm{DC} = 0.

    VDC=IpC0ω(1−γ)V_\mathrm{DC} = \frac{I_p}{C_0 \omega \left(1 - \gamma\right)}
  16. Substitute it.

    PSSHI,max=Ip2πωC0(1−γ)P_\mathrm{SSHI,max} = \frac{I_p^{2}}{\pi \omega C_0 \left(1 - \gamma\right)}

    ✓ reproduces piezo.P_sshi_max (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/piezo.py

A current-output high-side monitor converts the small voltage across a shunt into a current, which an output resistor turns back into a ground-referenced voltage.

  1. Ohm's law across the shunt: the signal, and the voltage the shunt takes from the measured circuit.

    VSENSE=ISENSERSENSEV_\mathrm{SENSE} = I_\mathrm{SENSE} R_\mathrm{SENSE}

    ✓ reproduces sense.burden (equality checked symbolically by pytest)

  2. The ideal amplifier forces the same voltage across RINR_\mathrm{IN}, so the current through RINR_\mathrm{IN} is VSENSE/RINV_\mathrm{SENSE}/R_\mathrm{IN}.

    IOUT=VSENSERINI_\mathrm{OUT} = \frac{V_\mathrm{SENSE}}{R_\mathrm{IN}}
  3. That current flows out through ROUTR_\mathrm{OUT}: VOUT=IOUTROUTV_\mathrm{OUT} = I_\mathrm{OUT} R_\mathrm{OUT}.

    VOUT=ISENSERSENSEROUTRINV_\mathrm{OUT} = \frac{I_\mathrm{SENSE} R_\mathrm{SENSE} R_\mathrm{OUT}}{R_\mathrm{IN}}

    ✓ reproduces sense.current_out_monitor (equality checked symbolically by pytest)

  4. A voltage-output amplifier multiplies the sense voltage by its fixed gain GG and adds the voltage on its REF pin, which sets the output at zero current.

    VOUT=GISENSERSENSE+VREFV_\mathrm{OUT} = G I_\mathrm{SENSE} R_\mathrm{SENSE} + V_\mathrm{REF}

    ✓ reproduces sense.voltage_out_monitor (equality checked symbolically by pytest)

  5. An input offset voltage adds directly to the sense voltage, so the output of either amplifier reads the current

    Imeas=VOS+ISENSERSENSERSENSEI_\mathrm{meas} = \frac{V_\mathrm{OS} + I_\mathrm{SENSE} R_\mathrm{SENSE}}{R_\mathrm{SENSE}}
  6. The reading exceeds the current by the offset over the sense resistor, whatever the current.

    IOS,eq=VOSRSENSEI_\mathrm{OS,eq} = \frac{V_\mathrm{OS}}{R_\mathrm{SENSE}}

    ✓ reproduces sense.offset_current (equality checked symbolically by pytest)

  7. Without a Kelvin connection the sense connections sit beyond the pads and solder joints, whose resistance RpadR_\mathrm{pad} carries the same current; the reading, scaled by RSENSER_\mathrm{SENSE}, is

    Imeas=ISENSE(RSENSE+Rpad)RSENSEI_\mathrm{meas} = \frac{I_\mathrm{SENSE} \left(R_\mathrm{SENSE} + R_\mathrm{pad}\right)}{R_\mathrm{SENSE}}
  8. Its relative error: independent of the current, and larger the smaller the shunt.

    εpad=RpadRSENSE\varepsilon_\mathrm{pad} = \frac{R_\mathrm{pad}}{R_\mathrm{SENSE}}

    ✓ reproduces sense.pad_error (equality checked symbolically by pytest)

  9. With both, the amplifier sees ISENSE(RSENSE+Rpad)+VOSI_\mathrm{SENSE} (R_\mathrm{SENSE} + R_\mathrm{pad}) + V_\mathrm{OS} and reads it as a current through RSENSER_\mathrm{SENSE}. The output is the output equation at this current; the offset's sign that raises it bounds the output from above, the other sign from below.

    Imeas=VOS+ISENSE(RSENSE+Rpad)RSENSE=ISENSE(RSENSE+Rpad)RSENSE+VOSRSENSEI_\mathrm{meas} = \frac{V_\mathrm{OS} + I_\mathrm{SENSE} \left(R_\mathrm{SENSE} + R_\mathrm{pad}\right)}{R_\mathrm{SENSE}} = \frac{I_\mathrm{SENSE} \left(R_\mathrm{SENSE} + R_\mathrm{pad}\right)}{R_\mathrm{SENSE}} + \frac{V_\mathrm{OS}}{R_\mathrm{SENSE}}

    ✓ reproduces sense.reading (equality checked symbolically by pytest)

  10. Its relative error is the sum of the two, with no cross term: the offset's share falls as 1/ISENSE1/I_\mathrm{SENSE}, the pad's share stays.

    εI=VOS+ISENSERpadISENSERSENSE=VOSISENSERSENSE+RpadRSENSE\varepsilon_I = \frac{V_\mathrm{OS} + I_\mathrm{SENSE} R_\mathrm{pad}}{I_\mathrm{SENSE} R_\mathrm{SENSE}} = \frac{V_\mathrm{OS}}{I_\mathrm{SENSE} R_\mathrm{SENSE}} + \frac{R_\mathrm{pad}}{R_\mathrm{SENSE}}

    ✓ reproduces sense.rel_error (equality checked symbolically by pytest)

  11. A current-output amplifier drives its current IOUTI_\mathrm{OUT} into ROUTR_\mathrm{OUT} at node A. An R-C filter follows: RfR_f from node A to node B, and CfC_f from node B to ground, with nothing else loading node B. The current balance at node A (Laplace domain):

    IOUT=VA−VBRf+VAROUTI_\mathrm{OUT} = \frac{V_A - V_B}{R_f} + \frac{V_A}{R_\mathrm{OUT}}
  12. and at node B:

    VA−VBRf=VBsCf\frac{V_A - V_B}{R_f} = V_B s C_f
  13. Solving for the capacitor's voltage gives a single pole; at dc the output is still IOUTROUTI_\mathrm{OUT} R_\mathrm{OUT}:

    VBIOUT=ROUT1+sCfROUT+sCfRf\frac{V_B}{I_\mathrm{OUT}} = \frac{R_\mathrm{OUT}}{1 + s C_f R_\mathrm{OUT} + s C_f R_f}
  14. The pole's time constant is Cf(ROUT+Rf)C_f (R_\mathrm{OUT} + R_f): the capacitor sees ROUTR_\mathrm{OUT} in series with RfR_f. ROUTR_\mathrm{OUT} is the Thevenin resistance of the current source it loads.

    Rfilt=ROUT+RfR_\mathrm{filt} = R_\mathrm{OUT} + R_f

    ✓ reproduces sense.filter_R (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/sensing.py

Bench: edges, gate drive, rise time, inrush, heat and a battery’s terminals

Section titled “Bench: edges, gate drive, rise time, inrush, heat and a battery’s terminals”

Results of the inductor's and the capacitor's laws, the first-order step response, Ohm's law across a gate resistor, a series thermal path and the Rint battery model, in the form they take on the bench.

  1. The voltage across an inductance is proportional to the rate of change of its current:

    v=Lloopddti(t)v = L_\mathrm{loop} \frac{d}{d t} i{\left(t \right)}
  2. A current that changes by ΔI\Delta I linearly over the edge tedget_\mathrm{edge} changes at a constant rate:

    ddti(t)=ΔItedge\frac{d}{d t} i{\left(t \right)} = \frac{\Delta I}{t_\mathrm{edge}}
  3. so throughout the edge the loop's inductance develops

    Vspike=LloopΔItedgeV_\mathrm{spike} = \frac{L_\mathrm{loop} \Delta I}{t_\mathrm{edge}}

    ✓ reproduces layout.v_spike (equality checked symbolically by pytest)

  4. After the turn-off edge the loop's inductance rings with the switch node's capacitance, a series L-C, at

    fring=12πCnodeLloopf_\mathrm{ring} = \frac{1}{2 \pi \sqrt{C_\mathrm{node}} \sqrt{L_\mathrm{loop}}}
  5. so the ring's frequency gives the loop's inductance:

    Lloop=14π2fring2CnodeL_\mathrm{loop} = \frac{1}{4 \pi^{2} f_\mathrm{ring}^{2} C_\mathrm{node}}

    ✓ reproduces layout.L_ring (equality checked symbolically by pytest)

  6. While the drain voltage makes its transition the gate stays at the plateau voltage VplV_\mathrm{pl}. The driver switches its output to VGSV_\mathrm{GS} (turn-on) or to 0 (turn-off) behind the gate loop's resistance RGR_G, so by Ohm's law the gate current is constant meanwhile:

  7. at turn-on

    IG=VGS−VplRGI_G = \frac{V_\mathrm{GS} - V_\mathrm{pl}}{R_G}

    ✓ reproduces gate.I_on (equality checked symbolically by pytest)

  8. and at turn-off

    IG=VplRGI_G = \frac{V_\mathrm{pl}}{R_G}

    ✓ reproduces gate.I_off (equality checked symbolically by pytest)

  9. A constant current delivers charge at a constant rate:

    q=IGtq = I_G t
  10. so the plateau, along which the gate takes the gate-drain charge QGDQ_\mathrm{GD}, lasts

    tpl=QGDIGt_\mathrm{pl} = \frac{Q_\mathrm{GD}}{I_G}

    ✓ reproduces gate.t_pl (equality checked symbolically by pytest)

  11. A capacitor's charge is its capacitance times its voltage, so supplying the charge QbootQ_\mathrm{boot} lowers its voltage by

    ΔVboot=QbootCboot\Delta V_\mathrm{boot} = \frac{Q_\mathrm{boot}}{C_\mathrm{boot}}
  12. For a droop of at most ΔVboot\Delta V_\mathrm{boot} the capacitance must be at least

    Cboot=QbootΔVbootC_\mathrm{boot} = \frac{Q_\mathrm{boot}}{\Delta V_\mathrm{boot}}

    ✓ reproduces boot.C (equality checked symbolically by pytest)

  13. A single-pole system answers a unit step with

    y=1−e−tτy = 1 - e^{- \frac{t}{\tau}}
  14. It reaches 10 % and 90 % of its final value at

    (t10=τln⁡(109), t90=τln⁡(10))\left( t_{10} = \tau \ln{\left(\frac{10}{9} \right)}, \ t_{90} = \tau \ln{\left(10 \right)}\right)
  15. so its 10-90 % rise time is

    trise=2τln⁡(3)t_\mathrm{rise} = 2 \tau \ln{\left(3 \right)}
  16. Its -3 dB bandwidth is the corner frequency of the same pole:

    fBW=12πτf_\mathrm{BW} = \frac{1}{2 \pi \tau}
  17. Eliminating τ\tau:

    trise=ln⁡(3)πfBW=ln⁡(9)2πfBWt_\mathrm{rise} = \frac{\ln{\left(3 \right)}}{\pi f_\mathrm{BW}} = \frac{\ln{\left(9 \right)}}{2 \pi f_\mathrm{BW}}

    ✓ reproduces probe.t_rise (equality checked symbolically by pytest)

  18. The tip-and-ground loop's inductance and the probe's input capacitance form a series L-C; without loss its current ii obeys

    Lgndd2dt2i(t)+i(t)Cprobe=0L_\mathrm{gnd} \frac{d^{2}}{d t^{2}} i{\left(t \right)} + \frac{i{\left(t \right)}}{C_\mathrm{probe}} = 0
  19. whose solutions ring at the angular frequency

    ω0=1CprobeLgnd\omega_0 = \frac{1}{\sqrt{C_\mathrm{probe}} \sqrt{L_\mathrm{gnd}}}
  20. that is, the frequency

    fprobe=12πLgndCprobef_\mathrm{probe} = \frac{1}{2 \pi \sqrt{L_\mathrm{gnd} C_\mathrm{probe}}}

    ✓ reproduces probe.f_ring (equality checked symbolically by pytest)

  21. An empty capacitor switched onto a step VgV_g through a series resistance charges with the current

    i=Vge−tCinRserRseri = \frac{V_g e^{- \frac{t}{C_\mathrm{in} R_\mathrm{ser}}}}{R_\mathrm{ser}}
  22. which is largest at the step, when the capacitor has no voltage across it:

    Iinrush=VgRserI_\mathrm{inrush} = \frac{V_g}{R_\mathrm{ser}}

    ✓ reproduces inrush.I_pk (equality checked symbolically by pytest)

  23. The integral of its square over the whole pulse is

    I2t=Vg2Cin2RserI^2 t = \frac{V_g^{2} C_\mathrm{in}}{2 R_\mathrm{ser}}

    ✓ reproduces inrush.I2t (equality checked symbolically by pytest)

  24. A capacitor's current is its capacitance times the rate of change of its voltage:

    i=Cinddtv(t)i = C_\mathrm{in} \frac{d}{d t} v{\left(t \right)}
  25. so a voltage ramped linearly from zero to VgV_g over trampt_\mathrm{ramp} draws a constant current:

    Iramp=CinVgtrampI_\mathrm{ramp} = \frac{C_\mathrm{in} V_g}{t_\mathrm{ramp}}

    ✓ reproduces inrush.I_ramp (equality checked symbolically by pytest)

  26. A thermal resistance is the temperature difference across a part of the heat's path per watt flowing through it. With all of the device's power flowing from the junction to the case, the case to the heat sink and the heat sink to the ambient in turn,

    (TJ−TC=PDθJC, TC−TS=PDθCS, TS−TA=PDθSA)\left( T_J - T_C = P_D \theta_\mathrm{JC}, \ T_C - T_S = P_D \theta_\mathrm{CS}, \ T_S - T_A = P_D \theta_\mathrm{SA}\right)
  27. and the rises add up from the ambient:

    TJ=TA+PD(θJC+θCS+θSA)T_J = T_A + P_D \left(\theta_\mathrm{JC} + \theta_\mathrm{CS} + \theta_\mathrm{SA}\right)

    ✓ reproduces therm.Tj (equality checked symbolically by pytest)

  28. The Rint model is the open-circuit voltage behind the internal resistance; the charging current flows into the positive terminal through the resistance (`bat.rint`):

    Ib=V−VbRbI_b = \frac{V - V_b}{R_b}
  29. so the terminals sit at

    V=Vb+IbRbV = V_b + I_b R_b

    ✓ reproduces bat.v_term (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/bench.py

Simulation: stepping a circuit in time, and how long it takes to settle

Section titled “Simulation: stepping a circuit in time, and how long it takes to settle”

A simulator advances a circuit's state in time steps. Within a step a switching converter is a linear circuit with constant sources, which a first-order example shows can be stepped exactly. The usual integration rules are compared on one decaying mode, the test equation, where each multiplies the deviation by a fixed factor per step. The envelope of the output filter's ring then says how many switching periods a simulation from rest needs before its last periods show the steady state.

  1. Within a step the circuit is linear and its sources are constant. A capacitor charged through a resistance then approaches the voltage v∞v_\infty at a rate proportional to the distance left, over the time constant τ\tau:

    ddtv(t)=v∞−v(t)τ\frac{d}{d t} v{\left(t \right)} = \frac{v_\infty - v{\left(t \right)}}{\tau}
  2. Solve it from the voltage v0v_0 at the start of the step:

    v(t)=v∞+(v0−v∞)e−tτv{\left(t \right)} = v_\infty + \left(v_0 - v_\infty\right) e^{- \frac{t}{\tau}}
  3. At the end of the step, t=Δtt = \Delta t. Nothing was approximated, so the step may be as long as the interval:

    v1=v∞+(v0−v∞)e−Δtτv_1 = v_\infty + \left(v_0 - v_\infty\right) e^{- \frac{\Delta t}{\tau}}

    ✓ reproduces sim.exact_step (equality checked symbolically by pytest)

  4. Compare the usual rules on one decaying mode on its own, the deviation xx from where the circuit is going, which changes at the rate −x/τ-x/\tau (the test equation). A rule that steps it from x0x_0 to x1x_1 over Δt\Delta t multiplies it by a factor g=x1/x0g = x_1/x_0 in every step:

    ddtx(t)=−x(t)τ\frac{d}{d t} x{\left(t \right)} = - \frac{x{\left(t \right)}}{\tau}
  5. Forward Euler takes the slope at the start of the step:

    x1=x0−x0Δtτx_1 = x_0 - \frac{x_0 \Delta t}{\tau}
  6. Its factor per step:

    gFE=1−Δtτg_\mathrm{FE} = 1 - \frac{\Delta t}{\tau}

    ✓ reproduces num.gain_fe (equality checked symbolically by pytest)

  7. The trapezoidal rule averages the slopes at both ends of the step, so x1x_1 appears on both sides:

    x1=x0+Δt(−x0τ−x1τ)2x_1 = x_0 + \frac{\Delta t \left(- \frac{x_0}{\tau} - \frac{x_1}{\tau}\right)}{2}
  8. Solve for x1x_1 and divide by x0x_0:

    gTR=−Δt−2τΔt+2τ=1−Δt2τ1+Δt2τg_\mathrm{TR} = - \frac{\Delta t - 2 \tau}{\Delta t + 2 \tau} = \frac{1 - \frac{\Delta t}{2 \tau}}{1 + \frac{\Delta t}{2 \tau}}

    ✓ reproduces num.gain_tr (equality checked symbolically by pytest)

  9. Backward Euler takes the slope at the end of the step:

    x1=x0−x1Δtτx_1 = x_0 - \frac{x_1 \Delta t}{\tau}
  10. Its factor per step:

    gBE=τΔt+τ=11+Δtτg_\mathrm{BE} = \frac{\tau}{\Delta t + \tau} = \frac{1}{1 + \frac{\Delta t}{\tau}}

    ✓ reproduces num.gain_be (equality checked symbolically by pytest)

  11. For a step far longer than τ\tau, a stiff mode, the exact factor e−Δt/τe^{-\Delta t/\tau} is nearly zero. Forward Euler's factor grows without bound, the trapezoidal rule's tends to −1-1, so the mode keeps its size and flips its sign every step, and backward Euler's tends to zero, as the exact one does:

    (lim⁡Δt→∞(−Δt−2τΔt+2τ)=−1, lim⁡Δt→∞(τΔt+τ)=0)\left( \lim_{\Delta t \to \infty}\left(- \frac{\Delta t - 2 \tau}{\Delta t + 2 \tau}\right) = -1, \ \lim_{\Delta t \to \infty}\left(\frac{\tau}{\Delta t + \tau}\right) = 0\right)
  12. Backward Euler steps an inductor's current with the slope at the end of the step, the voltage vL,1v_{L,1} across it over its inductance:

    i1=i0+vL,1ΔtLi_1 = i_0 + \frac{v_{L,1} \Delta t}{L}
  13. A diode's current has gone past zero, to −Ios-I_\mathrm{os}, before the diode turns off. If nothing else takes the current, the next step must end with it at zero:

    0=−Ios+vL,1ΔtL0 = - I_\mathrm{os} + \frac{v_{L,1} \Delta t}{L}
  14. Solve for the voltage across the inductor in that step:

    vjump=LIosΔtv_\mathrm{jump} = \frac{L I_\mathrm{os}}{\Delta t}

    ✓ reproduces sim.step_jump (equality checked symbolically by pytest)

  15. Take a buck with ideal parts. With its source at rest (a voltage source is a short), the filter's inductance, its capacitance and the load resistance are in parallel. The natural response of this parallel RLC circuit goes as este^{st}, with ss a root of its characteristic equation:

    1CL+sCR+s2=0\frac{1}{C L} + \frac{s}{C R} + s^{2} = 0
  16. When the filter rings, the roots are a complex-conjugate pair. Their common real part, half their sum, is −α-\alpha, and α\alpha is the decay rate of the ring's envelope (the neper frequency):

    α=12CR\alpha = \frac{1}{2 C R}
  17. The envelope decays as e−αte^{-\alpha t}, with the time constant 1/α1/\alpha, whatever the inductance (with ideal parts):

    τenv=2RC\tau_\mathrm{env} = 2 R C

    ✓ reproduces filter.tau_env (equality checked symbolically by pytest)

  18. A simulation from rest starts with an error as large as the steady state itself. After NN switching periods it has fallen to the fraction εr\varepsilon_r:

    εr=e−NTsτenv\varepsilon_r = e^{- \frac{N T_s}{\tau_\mathrm{env}}}
  19. Solve for NN:

    Nset=−τenvln⁡(εr)Ts=τenvln⁡(1εr)TsN_\mathrm{set} = - \frac{\tau_\mathrm{env} \ln{\left(\varepsilon_r \right)}}{T_s} = \frac{\tau_\mathrm{env} \ln{\left(\frac{1}{\varepsilon_r} \right)}}{T_s}

    ✓ reproduces sim.periods_settle (equality checked symbolically by pytest)

Derivation script: python/pe_core/derive/numerics.py

The numbers in the examples, presets and exercises on this site are synthetic: round teaching values, not data from any real design.