Source code for g2elin_core.components.load

"""Constant-impedance (RL) load branch, ported from ``Functions/symLoad.m``.

With ``mode=ALGEBRAIC`` the branch's ``L di/dt`` is dropped and the load
draws the current its own impedance demands at the bus voltage, instantly --
the constant-impedance load of a phasor tool.

See :func:`g2elin_core.components.base.apply_reduction`.
"""

from __future__ import annotations

from functools import lru_cache

import numpy as np
import sympy as sp

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

wb, Rc, Lc = sp.symbols("wb Rc Lc")
icd_g, icq_g = sp.symbols("icd_g icq_g")
wg, vgd_g, vgq_g = sp.symbols("wg vgd_g vgq_g")

_STATE_NAMES = ["i_{c_d}", "i_{c_q}"]


[docs] @lru_cache(maxsize=4) def load_dae(mode: str = DYNAMIC, fixed_frequency: bool = False) -> ComponentDAE: """``mode`` is this load's single state group -- see :mod:`g2elin_core.reduction`. ``fixed_frequency`` pins the ``w*L`` term to nominal instead of following the frame. """ w = sp.Integer(1) if fixed_frequency else wg input_vec = [wg, vgd_g, vgq_g] dicd_g = (wb / Lc) * (vgd_g - Rc * icd_g + w * Lc * icq_g) dicq_g = (wb / Lc) * (vgq_g - Rc * icq_g - w * Lc * icd_g) output_vec = [icd_g, icq_g] state_vec, diffeq_vec, state_names, alg_vec, algeq_vec, output_vec = apply_reduction( states=list(zip([icd_g, icq_g], [dicd_g, dicq_g], _STATE_NAMES)), modes={"icd_g": mode, "icq_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=3, n_out_s=0, n_out_g=2, state_names=state_names, input_names=[], output_names=[], )
[docs] @lru_cache(maxsize=4) def load_nonlinear_funcs(mode: str = DYNAMIC, fixed_frequency: bool = False) -> NonlinearFuncs: return load_dae(mode, fixed_frequency).nonlinear_funcs()
[docs] @lru_cache(maxsize=4) def load_nonlinear_jacobians(mode: str = DYNAMIC, fixed_frequency: bool = False) -> NonlinearJacobians: return load_dae(mode, fixed_frequency).nonlinear_jacobians()
def _load_operating_subs( *, wb_val: float, r_pu: float, x_pu: float, wg0: float, vgd_g0: float, vgq_g0: float ) -> dict: # Steady state: 0 = vgd_g0 - Rc*icd_g0 + wg0*Lc*icq_g0 and # 0 = vgq_g0 - Rc*icq_g0 - wg0*Lc*icd_g0 -- solve the 2x2 linear system. coeff = np.array([[-r_pu, wg0 * x_pu], [-wg0 * x_pu, -r_pu]]) rhs = np.array([-vgd_g0, -vgq_g0]) icd_g0, icq_g0 = np.linalg.solve(coeff, rhs) return equilibrium_subs({ wb: wb_val, Rc: r_pu, Lc: x_pu, wg: wg0, vgd_g: vgd_g0, vgq_g: vgq_g0, icd_g: icd_g0, icq_g: icq_g0, })
[docs] def linearize_load( *, wb_val: float, r_pu: float, x_pu: float, wg0: float, vgd_g0: float, vgq_g0: float, mode: str = DYNAMIC, fixed_frequency: bool = False, ) -> LinearComponent: """Linearize a constant-impedance load at an operating point. ``x_pu`` (the load's equivalent reactance, ``V_pu^2/S_pu * sin(acos(PF))``) plays the role of ``Lc``, matching the toolbox's per-unit convention. """ subs = _load_operating_subs(wb_val=wb_val, r_pu=r_pu, x_pu=x_pu, wg0=wg0, vgd_g0=vgd_g0, vgq_g0=vgq_g0) return load_dae(mode, fixed_frequency).linearize(subs)
[docs] def load_nonlinear_point( *, wb_val: float, r_pu: float, x_pu: float, wg0: float, vgd_g0: float, vgq_g0: float, mode: str = DYNAMIC, fixed_frequency: bool = False, ) -> tuple: """``(x0, z0, u0, p0)`` for :func:`load_nonlinear_funcs`.""" subs = _load_operating_subs(wb_val=wb_val, r_pu=r_pu, x_pu=x_pu, wg0=wg0, vgd_g0=vgd_g0, vgq_g0=vgq_g0) return load_dae(mode, fixed_frequency).point_from_subs(subs)