pydynamicestimator.devices.inverter

Classes

Inverter

Metaclass for inverters

GridFollowing

Grid-Following Inverter (with Droop)

GridForming

Grid-Forming Inverter (Droop-Based)

Module Contents

class pydynamicestimator.devices.inverter.Inverter(filter=None, pll=None, angle=None, voltage=None, inner=None)[source]

Bases: pydynamicestimator.devices.device.DeviceRect

Metaclass for inverters

_init_method = 'sequential'
_filter: pydynamicestimator.devices.inverter_filter.Filter
_angle: pydynamicestimator.devices.inverter_angle.AngleSource = None
_voltage: pydynamicestimator.devices.inverter_voltage.VoltageControl
_inner: pydynamicestimator.devices.inverter_inner.InnerControl
_pll: pydynamicestimator.devices.inverter_pll.PLL = None
omega_f
ns = 0
Vcd = None
ifd_ref = None
ifq_ref = None
Vswd = None
Vswq = None
gcall(dae: pydynamicestimator.system.Dae) None[source]
Parameters:

dae (pydynamicestimator.system.Dae)

Return type:

None

var_sym(dae: pydynamicestimator.system.Dae, name: str) casadi.SX[source]

Resolve a registered variable name to its symbol wherever it lives: dae.y for a device-private algebraic, dae.x for a differential state. Lets the converter equations read a coupling quantity (a filter state, the frame angle, …) without knowing whether a given realization made it a state or an algebraic – e.g. the dynamic LCL filter (states) vs a future quasi-static LCL_static (algebraics). Mirrors Synchronous.var_sym; today every inverter variable is a state, so this resolves to dae.x.

Parameters:
Return type:

casadi.SX

static to_internal(d_ext, q_ext, delta)[source]

Park-rotate (d, q) from the network (external) frame into the converter (internal) frame defined by angle delta.

static to_external(d_int, q_int, delta)[source]

Park-rotate (d, q) from the converter (internal) frame back to the network (external) frame defined by angle delta.

_pll_fgcall(dae: pydynamicestimator.system.Dae, omega_ref_vec, omega_b) None[source]

Delegate to the PLL strategy (no-op if there is no PLL). Runs before the angle source so that self.omega_pll is set when a PLL-consuming angle source reads it via pll_frequency().

Parameters:

dae (pydynamicestimator.system.Dae)

Return type:

None

pll_frequency(dae: pydynamicestimator.system.Dae) casadi.SX[source]

Synchronizing frequency from the PLL, host-mediated (the PSS-style coupling): the PLL’s omega_pll when a PLL strategy is present, else the nominal omega_net (no PLL => no estimate). A consumer reads this accessor and never references the PLL strategy directly.

Parameters:

dae (pydynamicestimator.system.Dae)

Return type:

casadi.SX

voltage_command(dae: pydynamicestimator.system.Dae) casadi.SX[source]

Voltage-magnitude reference Vcd, host-mediated: published on the host by the voltage strategy’s fgcall and read here by the inner controller. A symbolic intermediate, not a DAE variable (byte-identical).

Parameters:

dae (pydynamicestimator.system.Dae)

Return type:

casadi.SX

current_ref(dae: pydynamicestimator.system.Dae)[source]

Inner current references (ifd_ref, ifq_ref), host-mediated: published by the inner strategy and read back by its own current loop. A symbolic intermediate today; when the current limiter (b1) lands it will resolve to a saturated var_sym algebraic (i* = sat(iref)), with no consumer change – this accessor is that interception seam.

Parameters:

dae (pydynamicestimator.system.Dae)

switching_voltage(dae: pydynamicestimator.system.Dae)[source]

Switching voltage (Vswd, Vswq) in the network frame, host-mediated: published by the inner strategy and read by the filter. A symbolic intermediate, not a DAE variable (byte-identical).

Parameters:

dae (pydynamicestimator.system.Dae)

fgcall(dae: pydynamicestimator.system.Dae) None[source]

Orchestrate the pluggable converter strategies into the DAE residual.

The host owns only the cross-cutting glue: the reference-frame frequency, the Park transforms into the converter frame, the power measurement, and the power-measurement filters. Everything else is delegated, in signal-flow order: PLL -> angle source (omega_c, delta_c) -> voltage control (Vcd) -> inner control (Vsw) -> filter (LCL). Each strategy reads host params/states by attribute and writes its own equations; the host threads the symbolic intermediates (omega_c, Vcd, Vsw) between them.

Parameters:

dae (pydynamicestimator.system.Dae)

Return type:

None

finit_anchor_residuals(dae: pydynamicestimator.system.Dae) list[source]

Extra steady-state anchor residuals (each an n-vector that must equal 0) a device declares to keep its initialization Newton system square.

The generic finit() pins each device’s state ODEs (f = 0) plus two quantities through the bus-current balance (g[vre]/g[vim]), so the square-system invariant is n_setpoints == 2 (the SG: Pref + Vref). A device with more setpoints returns n_setpoints - 2 anchor residuals to balance them. Empty by default -> existing devices are unaffected.

Example – the inverter has 3 setpoints (Pref/Qref/Vref) but the bus equations pin only 2; it returns one anchor (Qref = Qc) here. See docs/inverter_modernization_design.md §7.

Parameters:

dae (pydynamicestimator.system.Dae)

Return type:

list

_finit_sequential(dae: pydynamicestimator.system.Dae) None[source]

Strategy-composed staged initialization (the inverter default), shared by GridForming and GridFollowing. Each strategy owns its own stage; the host threads the operating point between them and assembles the result by name. See docs/inverter_init_schematic.html for the data flow.

filter (decoupled) -> pll (decoupled) -> powers from the filter op-point (frame-invariant) -> inner (frame delta_c + command Vcd + the four integrators) -> angle (Pref/Pc_tilde, owns delta_c) -> voltage (Qref/Qc_tilde, unpacks Vcd -> Vref via its gauge).

Parameters:

dae (pydynamicestimator.system.Dae)

Return type:

None

_load_finit(dae: pydynamicestimator.system.Dae, vals: dict) None[source]

Load the computed init values by NAME: differential states -> dae.xinit, device-private algebraics -> dae.yinit, setpoints -> the device’s setpoint arrays. Driven by self.states / self._algebs_int / self._setpoints, so it adapts to any strategy combo and to the state-vs-algebraic realization (replacing the previous hardcoded, layout-specific solution list).

Parameters:
Return type:

None

class pydynamicestimator.devices.inverter.GridFollowing(filter=None, angle=None, voltage=None, inner=None, pll=None)[source]

Bases: Inverter

Grid-Following Inverter (with Droop) Based on the grid-following inverter model in https://doi.org/10.1109/TPWRS.2021.3061434

The dynamic behavior of the grid-following converter is described by the following differential equations:

Converter Voltage Dynamics

\[\dot{v}_{fd_{ext}} = \frac{\omega_{b}}{c_{f}}(i_{fd_{ext}} - i_{td_{ext}}) + \omega_{net}\omega_{b}v_{fq_{ext}}\]
\[\dot{v}_{fq_{ext}} = \frac{\omega_{b}}{c_{f}}(i_{fq_{ext}} - i_{tq_{ext}}) - \omega_{net}\omega_{b}v_{fd_{ext}}\]

Converter Current Dynamics

\[\dot{i}_{fd_{ext}} = \frac{\omega_{b}}{l_{f}}(v_{swd} - v_{fd_{ext}}) - \frac{\omega_{b}r_{f}}{l_{f}}i_{fd_{ext}} + \omega_{net}\omega_{b}i_{fq_{ext}}\]
\[\dot{i}_{fq_{ext}} = \frac{\omega_{b}}{l_{f}}(v_{swq} - v_{fq_{ext}}) - \frac{\omega_{b}r_{f}}{l_{f}}i_{fq_{ext}} - \omega_{net}\omega_{b}i_{fd_{ext}}\]

Grid-Side Current Dynamics

\[\dot{i}_{td_{ext}} = \frac{\omega_b}{l_t}(v_{fd_{ext}} - v_{n_{re}}) - \frac{\omega_b r_t}{l_t}i_{td_{ext}} + \omega_{net} \omega_b i_{tq_{ext}}\]
\[\dot{i}_{tq_{ext}} = \frac{\omega_b}{l_t}(v_{fq_{ext}} - v_{n_{im}}) - \frac{\omega_b r_t}{l_t}i_{tq_{ext}} - \omega_{net} \omega_b i_{td_{ext}}\]

Phase-Locked Loop (PLL) Dynamics

\[\dot{\epsilon} = v_{fq_{pll}}\]
\[\delta\dot{\theta}_{pll} = \omega_{b}\delta\omega_{pll}\]

Power and Frequency Dynamics

\[\dot{\tilde{p}}_{c} = \omega_{f}(p_{c} - \tilde{p}_{c})\]
\[\delta\dot{\theta}_{c} = \omega_{b}\delta\omega_{c}\]
\[\dot{\tilde{q}}_{c} = \omega_{f}(q_{c} - \tilde{q}_{c})\]

Control Dynamics

\[\dot{\xi}_{d} = v_{fd^{*}} - v_{fd_{int}}\]
\[\dot{\xi}_{q} = v_{fq^{*}} - v_{fq_{int}}\]
\[\dot{\gamma}_{d} = i_{fd^{*}} - i_{fd_{int}}\]
\[\dot{\gamma}_{q} = i_{fq^{*}} - i_{fq_{int}}\]
_type = 'Inverter'
_name = 'GridFollowing_inverter_model'
class pydynamicestimator.devices.inverter.GridForming(filter=None, angle=None, voltage=None, inner=None, pll=None)[source]

Bases: Inverter

Grid-Forming Inverter (Droop-Based) Based on the grid-forming inverter model in https://doi.org/10.1109/TPWRS.2021.3061434 The dynamic behavior of the grid-forming converter is described by the following differential equations:

Converter Voltage Dynamics

\[\dot{v}_{fd_{ext}} = \frac{\omega_{b}}{c_{f}}(i_{fd_{ext}} - i_{td_{ext}}) + \omega_{net}\omega_{b}v_{fq_{ext}}\]
\[\dot{v}_{fq_{ext}} = \frac{\omega_{b}}{c_{f}}(i_{fq_{ext}} - i_{tq_{ext}}) - \omega_{net}\omega_{b}v_{fd_{ext}}\]

Converter Current Dynamics

\[\dot{i}_{fd_{ext}} = \frac{\omega_{b}}{l_{f}}(v_{swd} - v_{fd_{ext}}) - \frac{\omega_{b}r_{f}}{l_{f}}i_{fd_{ext}} + \omega_{net}\omega_{b}i_{fq_{ext}}\]
\[\dot{i}_{fq_{ext}} = \frac{\omega_{b}}{l_{f}}(v_{swq} - v_{fq_{ext}}) - \frac{\omega_{b}r_{f}}{l_{f}}i_{fq_{ext}} - \omega_{net}\omega_{b}i_{fd_{ext}}\]

Grid-Side Current Dynamics

\[\dot{i}_{td_{ext}} = \frac{\omega_b}{l_t}(v_{fd_{ext}} - v_{n_{re}}) - \frac{\omega_b r_t}{l_t}i_{td_{ext}} + \omega_{net} \omega_b i_{tq_{ext}}\]
\[\dot{i}_{tq_{ext}} = \frac{\omega_b}{l_t}(v_{fq_{ext}} - v_{n_{im}}) - \frac{\omega_b r_t}{l_t}i_{tq_{ext}} - \omega_{net} \omega_b i_{td_{ext}}\]

Power and Frequency Dynamics

\[\dot{\tilde{p}}_{c} = \omega_{f}(p_{c} - \tilde{p}_{c})\]
\[\delta\dot{\theta}_{c} = \omega_{b}\delta\omega_{c}\]
\[\dot{\tilde{q}}_{c} = \omega_{f}(q_{c} - \tilde{q}_{c})\]

Control Dynamics

\[\dot{\xi}_{d} = v_{fd^{*}} - v_{fd_{int}}\]
\[\dot{\xi}_{q} = v_{fq^{*}} - v_{fq_{int}}\]
\[\dot{\gamma}_{d} = i_{fd^{*}} - i_{fd_{int}}\]
\[\dot{\gamma}_{q} = i_{fq^{*}} - i_{fq_{int}}\]
_type = 'Inverter'
_name = 'GridForming_inverter_model'
omega_c = None