Source code for g2elin_core.components.line

"""RL-series transmission line, ported from ``Functions/symLine.m``.

Also serves shunt reactors and branch transformers -- both are the same RL
branch, differing only in how ``interconnect/network_assembly.py`` wires
their two ends (see that module).

With ``mode=ALGEBRAIC`` the branch's ``L di/dt`` is dropped and the current
becomes an algebraic variable of the series impedance: the quasi-stationary
branch a phasor tool uses. Nothing else about the model changes -- see
:func:`g2elin_core.components.base.apply_reduction`.
"""

from __future__ import annotations

from functools import lru_cache

import sympy as sp

from .base import (
    DYNAMIC, ComponentDAE, LinearComponent, NonlinearFuncs, NonlinearJacobians,
    apply_reduction, build_dae, equilibrium_subs,
)

wb, Rl, Ll = sp.symbols("wb Rl Ll")
ild_g, ilq_g = sp.symbols("ild_g ilq_g")
wg, vgdj_g, vgqj_g, vgdk_g, vgqk_g = sp.symbols("wg vgdj_g vgqj_g vgdk_g vgqk_g")

_STATE_NAMES = ["i_{l_d}", "i_{l_q}"]


[docs] @lru_cache(maxsize=4) def line_dae(mode: str = DYNAMIC, fixed_frequency: bool = False) -> ComponentDAE: """``mode`` is this branch's single state group -- see :mod:`g2elin_core.reduction`. ``fixed_frequency`` pins the ``w*L`` speed-voltage term to nominal instead of following the frame, which is the classic phasor-network convention. """ w = sp.Integer(1) if fixed_frequency else wg input_vec = [wg, vgdj_g, vgqj_g, vgdk_g, vgqk_g] dild_g = (wb / Ll) * (vgdj_g - vgdk_g - Rl * ild_g + w * Ll * ilq_g) dilq_g = (wb / Ll) * (vgqj_g - vgqk_g - Rl * ilq_g - w * Ll * ild_g) output_vec = [ild_g, ilq_g] # outputeqVec_g only; outputeqVec_s is empty state_vec, diffeq_vec, state_names, alg_vec, algeq_vec, output_vec = apply_reduction( states=list(zip([ild_g, ilq_g], [dild_g, dilq_g], _STATE_NAMES)), modes={"ild_g": mode, "ilq_g": mode}, alg_vec=[], algeq_vec=[], output_vec=output_vec, ) return build_dae( state_vec=state_vec, alg_vec=alg_vec, input_vec=input_vec, diffeq_vec=diffeq_vec, algeq_vec=algeq_vec, output_vec=output_vec, n_us=0, n_ug=5, n_out_s=0, n_out_g=2, state_names=state_names, input_names=[], output_names=[], )
[docs] @lru_cache(maxsize=4) def line_nonlinear_funcs(mode: str = DYNAMIC, fixed_frequency: bool = False) -> NonlinearFuncs: return line_dae(mode, fixed_frequency).nonlinear_funcs()
[docs] @lru_cache(maxsize=4) def line_nonlinear_jacobians(mode: str = DYNAMIC, fixed_frequency: bool = False) -> NonlinearJacobians: return line_dae(mode, fixed_frequency).nonlinear_jacobians()
def _line_operating_subs( *, wb_val: float, r_pu: float, x_pu: float, wg0: float, ild_g0: float, ilq_g0: float, vgdj_g0: float = 0.0, vgqj_g0: float = 0.0, vgdk_g0: float = 0.0, vgqk_g0: float = 0.0, ) -> dict: return equilibrium_subs({ wb: wb_val, Rl: r_pu, Ll: x_pu, wg: wg0, ild_g: ild_g0, ilq_g: ilq_g0, vgdj_g: vgdj_g0, vgqj_g: vgqj_g0, vgdk_g: vgdk_g0, vgqk_g: vgqk_g0, })
[docs] def linearize_line( *, wb_val: float, r_pu: float, x_pu: float, wg0: float, ild_g0: float, ilq_g0: float, mode: str = DYNAMIC, fixed_frequency: bool = False, ) -> LinearComponent: """Linearize a line at an operating point. ``x_pu`` plays the role of ``Ll`` in ``(wb/Ll)*(...)``, matching the toolbox's convention of using per-unit reactance directly in place of an inductance. ``vgdj_g``/``vgqj_g``/``vgdk_g``/``vgqk_g`` (the two end voltages) don't appear in the linear system's coefficients (only ``wg`` does, since it multiplies the states), so they're left at the :func:`_line_operating_subs` default of 0 here — harmless for linearization, but see :func:`line_nonlinear_point` for where the real values are needed (a nonlinear equilibrium check needs the true voltages). """ subs = _line_operating_subs(wb_val=wb_val, r_pu=r_pu, x_pu=x_pu, wg0=wg0, ild_g0=ild_g0, ilq_g0=ilq_g0) return line_dae(mode, fixed_frequency).linearize(subs)
[docs] def line_nonlinear_point( *, wb_val: float, r_pu: float, x_pu: float, wg0: float, ild_g0: float, ilq_g0: float, vgdj_g0: float, vgqj_g0: float, vgdk_g0: float, vgqk_g0: float, mode: str = DYNAMIC, fixed_frequency: bool = False, ) -> tuple: """``(x0, z0, u0, p0)`` for :func:`line_nonlinear_funcs`, at a line's true operating point (unlike :func:`linearize_line`, the end voltages matter here). """ subs = _line_operating_subs( wb_val=wb_val, r_pu=r_pu, x_pu=x_pu, wg0=wg0, ild_g0=ild_g0, ilq_g0=ilq_g0, vgdj_g0=vgdj_g0, vgqj_g0=vgqj_g0, vgdk_g0=vgdk_g0, vgqk_g0=vgqk_g0, ) return line_dae(mode, fixed_frequency).point_from_subs(subs)