hermess.devices.inverter ======================== .. py:module:: hermess.devices.inverter Classes ------- .. autoapisummary:: hermess.devices.inverter.Inverter hermess.devices.inverter.GridFollowing hermess.devices.inverter.GridForming Module Contents --------------- .. py:class:: Inverter(filter=None, pll=None, angle=None, voltage=None, inner=None) Bases: :py:obj:`hermess.devices.device.DeviceRect` Metaclass for inverters .. py:attribute:: _init_method :value: 'sequential' .. py:attribute:: _filter :type: hermess.devices.inverter_filter.Filter .. py:attribute:: _angle :type: hermess.devices.inverter_angle.AngleSource :value: None .. py:attribute:: _voltage :type: hermess.devices.inverter_voltage.VoltageControl .. py:attribute:: _inner :type: hermess.devices.inverter_inner.InnerControl .. py:attribute:: _pll :type: hermess.devices.inverter_pll.PLL :value: None .. py:attribute:: omega_f .. py:attribute:: ns :value: 0 .. py:attribute:: Vcd :value: None .. py:attribute:: ifd_ref :value: None .. py:attribute:: ifq_ref :value: None .. py:attribute:: Vswd :value: None .. py:attribute:: Vswq :value: None .. py:method:: gcall(dae: hermess.system.Dae) -> None .. py:method:: var_sym(dae: hermess.system.Dae, name: str) -> casadi.SX 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, ...) regardless of whether a given realization made it a state or an algebraic (dynamic ``LCL`` states vs quasi-static ``LCL_static`` algebraics). .. py:method:: to_internal(d_ext, q_ext, delta) :staticmethod: Park-rotate ``(d, q)`` from the network (external) frame into the converter (internal) frame defined by angle ``delta``. .. py:method:: to_external(d_int, q_int, delta) :staticmethod: Park-rotate ``(d, q)`` from the converter (internal) frame back to the network (external) frame defined by angle ``delta``. .. py:method:: _pll_fgcall(dae: hermess.system.Dae, omega_ref_vec, omega_b) -> None 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 :meth:`pll_frequency`. .. py:method:: pll_frequency(dae: hermess.system.Dae) -> casadi.SX Synchronizing frequency from the PLL, host-mediated: the PLL's ``omega_pll`` when a PLL strategy is present, else nominal ``omega_net`` (no PLL => no estimate). Consumers read this accessor and never reference the PLL strategy directly. .. py:method:: voltage_command(dae: hermess.system.Dae) -> casadi.SX Voltage-magnitude reference ``Vcd``, host-mediated: published by the voltage strategy's ``fgcall`` and read by the inner controller. A symbolic intermediate, not a DAE variable. .. py:method:: current_ref(dae: hermess.system.Dae) Inner current references ``(ifd_ref, ifq_ref)``, host-mediated: published by the inner strategy and read back by its own current loop. This accessor is the interception seam for a future current limiter, which would resolve it to a saturated ``var_sym`` algebraic (``i* = sat(iref)``). .. py:method:: switching_voltage(dae: hermess.system.Dae) 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. .. py:method:: fgcall(dae: hermess.system.Dae) -> None 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. .. py:method:: finit_anchor_residuals(dae: hermess.system.Dae) -> list Extra steady-state anchor residuals (each an n-vector that must equal 0) a device declares to keep its initialization Newton system square. The joint init pins each device's state ODEs (``f = 0``) plus two quantities through the bus-current balance (``g[vre]``/``g[vim]``), so a square system requires ``n_setpoints == 2`` (the SG: Pref + Vref). A device with more setpoints returns ``n_setpoints - 2`` anchor residuals to balance them. Empty by default. Example: the inverter has 3 setpoints (Pref/Qref/Vref) but the bus equations pin only 2; it returns one anchor (Qref = Qc) here. .. py:method:: _finit_sequential(dae: hermess.system.Dae) -> None 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. 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). .. py:method:: _load_finit(dae: hermess.system.Dae, vals: dict) -> None 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. .. py:class:: GridFollowing(filter=None, angle=None, voltage=None, inner=None, pll=None) Bases: :py:obj:`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** .. math:: \dot{v}_{fd_{ext}} = \frac{\omega_{b}}{c_{f}}(i_{fd_{ext}} - i_{td_{ext}}) + \omega_{net}\omega_{b}v_{fq_{ext}} .. math:: \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** .. math:: \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}} .. math:: \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** .. math:: \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}} .. math:: \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** .. math:: \dot{\epsilon} = v_{fq_{pll}} .. math:: \delta\dot{\theta}_{pll} = \omega_{b}\delta\omega_{pll} **Power and Frequency Dynamics** .. math:: \dot{\tilde{p}}_{c} = \omega_{f}(p_{c} - \tilde{p}_{c}) .. math:: \delta\dot{\theta}_{c} = \omega_{b}\delta\omega_{c} .. math:: \dot{\tilde{q}}_{c} = \omega_{f}(q_{c} - \tilde{q}_{c}) **Control Dynamics** .. math:: \dot{\xi}_{d} = v_{fd^{*}} - v_{fd_{int}} .. math:: \dot{\xi}_{q} = v_{fq^{*}} - v_{fq_{int}} .. math:: \dot{\gamma}_{d} = i_{fd^{*}} - i_{fd_{int}} .. math:: \dot{\gamma}_{q} = i_{fq^{*}} - i_{fq_{int}} .. py:attribute:: _type :value: 'Inverter' .. py:attribute:: _name :value: 'GridFollowing_inverter_model' .. py:class:: GridForming(filter=None, angle=None, voltage=None, inner=None, pll=None) Bases: :py:obj:`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** .. math:: \dot{v}_{fd_{ext}} = \frac{\omega_{b}}{c_{f}}(i_{fd_{ext}} - i_{td_{ext}}) + \omega_{net}\omega_{b}v_{fq_{ext}} .. math:: \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** .. math:: \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}} .. math:: \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** .. math:: \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}} .. math:: \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** .. math:: \dot{\tilde{p}}_{c} = \omega_{f}(p_{c} - \tilde{p}_{c}) .. math:: \delta\dot{\theta}_{c} = \omega_{b}\delta\omega_{c} .. math:: \dot{\tilde{q}}_{c} = \omega_{f}(q_{c} - \tilde{q}_{c}) **Control Dynamics** .. math:: \dot{\xi}_{d} = v_{fd^{*}} - v_{fd_{int}} .. math:: \dot{\xi}_{q} = v_{fq^{*}} - v_{fq_{int}} .. math:: \dot{\gamma}_{d} = i_{fd^{*}} - i_{fd_{int}} .. math:: \dot{\gamma}_{q} = i_{fq^{*}} - i_{fq_{int}} .. py:attribute:: _type :value: 'Inverter' .. py:attribute:: _name :value: 'GridForming_inverter_model' .. py:attribute:: omega_c :value: None