Source code for VeraGridEngine.Templates.Emt.simple_generator_emt_template

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
# SPDX-License-Identifier: MPL-2.0


import numpy as np
from VeraGridEngine.Devices.Dynamic.var_factory import VarFactory
from VeraGridEngine.Utils.Symbolic import symbolic as sym
from VeraGridEngine.Devices.Dynamic.emt_template import EmtModelTemplate
from VeraGridEngine.Utils.Symbolic.block import Block
from VeraGridEngine.Devices.Injections.generator import Generator
from VeraGridEngine.Templates.Emt.generator_emt_type_template import get_pf_positive_sequence_init_refs
from VeraGridEngine.enumerations import VarPowerFlowReferenceType, DeviceType, ParamPowerFlowReferenceType



# def get_simple_generator_emt_template(vf: VarFactory, name: str = "simple_emt_type_generator_template") -> EmtModelTemplate:
#     """
#     EMT type machine model without damping effects.
#     :param vf: grid.var_factory
#     :param name: string to identify the generator and model
#     :return: EmtModelTemplate
#     """
#
#     templ = EmtModelTemplate()
#     templ.tpe = DeviceType.GeneratorDevice
#     templ.name = name
#     templ.block.name = name
#
#     # --------------------------------------------------------------------------------------
#     # Inputs: instantaneous abc terminal voltages in pu (at bus)
#     # --------------------------------------------------------------------------------------
#     v_A = vf.add_var(name=f"v_A_{name}", reference= VarPowerFlowReferenceType.v_A)
#     v_B = vf.add_var(name=f"v_B_{name}", reference= VarPowerFlowReferenceType.v_B)
#     v_C = vf.add_var(name=f"v_C_{name}", reference= VarPowerFlowReferenceType.v_C)
#     Tm = vf.add_var(name=f"Tm_{name}")
#     v_f = vf.add_var(name=f"v_f_{name}")
#
#     # to connect complete block with gen block
#     Ipk = vf.add_var(name="Ipk", reference= VarPowerFlowReferenceType.Ipk)
#     Vpk = vf.add_var(name="Vpk", reference= VarPowerFlowReferenceType.Vpk)
#     phi = vf.add_var(name="phi", reference= VarPowerFlowReferenceType.phi)
#     phi_v = vf.add_var(name="phi_v", reference= VarPowerFlowReferenceType.phi_v)
#     inputs = [v_A, v_B, v_C]
#     # --------------------------------------------------------------------------------------
#     # States (pu, except theta [rad])
#     # --------------------------------------------------------------------------------------
#
#     theta = vf.add_var("theta_" + name)  # electrical angle [rad]
#     omega = vf.add_var(name=f"omega_{name}")  # speed [pu]
#     psi_d = vf.add_var("psi_d_" + name)  # flux linkages [pu] on psi_base = Vbase/omega_base
#     psi_q = vf.add_var("psi_q_" + name)
#     psi_f = vf.add_var("psi_f_" + name)
#     psi_0 = vf.add_var("psi_0_" + name)
#     et    = vf.add_var("et_" + name)     # PI integrator state (units: pu*s or equivalent)
#
#
#     # Diff vars (derivatives)
#     d_omega = vf.add_diff_var(name = f"d_omega_{name}", base_var=omega)
#     d_theta = vf.add_diff_var(name = f"d_theta_{name}", base_var=theta)
#     d_psi_d = vf.add_diff_var(name = f"d_psi_d_{name}", base_var=psi_d)
#     d_psi_q = vf.add_diff_var(name = f"d_psi_q_{name}", base_var=psi_q)
#     d_psi_0 = vf.add_diff_var(name = f"d_psi_0_{name}", base_var=psi_0)
#     d_psi_f = vf.add_diff_var(name = f"d_psi_f_{name}", base_var=psi_f)
#     d_et    = vf.add_diff_var(name = f"d_et_{name}", base_var=et)
#
#     # --------------------------------------------------------------------------------------
#     # Algebraic eqs
#     # --------------------------------------------------------------------------------------
#     i_A = vf.add_var(name=f"i_A_{name}", reference= VarPowerFlowReferenceType.i_A)
#     i_B = vf.add_var(name=f"i_B_{name}", reference= VarPowerFlowReferenceType.i_B)
#     i_C = vf.add_var(name=f"i_C_{name}", reference= VarPowerFlowReferenceType.i_C)
#
#     # dq0 voltages
#     v_d = vf.add_var("v_d_" + name)
#     v_q = vf.add_var("v_q_" + name)
#     v_0 = vf.add_var("v_0_" + name)
#
#     # dq0 currents
#     i_d = vf.add_var("i_d_" + name)
#     i_q = vf.add_var("i_q_" + name)
#     i_0 = vf.add_var("i_0_" + name)
#
#     # field
#     i_f = vf.add_var(name=f"i_f_{name}")
#
#     # powers/torques
#     Te = vf.add_var("Te_" + name)
#     Pe = vf.add_var("Pe_" + name)
#     Qe = vf.add_var("Qe_" + name)
#     Pm = vf.add_var("Pm_" + name)
#
#     # --------------------------------------------------------------------------------------
#     # Parameters
#     # --------------------------------------------------------------------------------------
#     omega_base = vf.add_var("omega_base")
#     H = vf.add_var("H")
#     D = vf.add_var("D")
#
#     Ra  = vf.add_var("Ra")
#     La  = vf.add_var("La")
#     Ld  = vf.add_var("Ld")
#     Lmd = vf.add_var("Lmd")
#     Lmq = vf.add_var("Lmq")
#     Lf  = vf.add_var("Lf")
#     Rf  = vf.add_var("Rf")
#     R0  = vf.add_var("R0")
#     L0  = vf.add_var("L0")
#
#     omega_ref = vf.add_var("omega_ref")  # pu
#     delta = vf.add_var("delta_" + name)  # difference between rotor angle and grid angle
#
#     Kp = vf.add_var("Kp")
#     Ki = vf.add_var("Ki")
#
#     v_f0 = vf.add_var("v_f0")  # temporary fixed exciter output
#
#     templ.block = Block(
#         # --------------------------------------------------------------------------------------
#         # STATE EQUATIONS (seconds + pu)
#         # --------------------------------------------------------------------------------------
#         state_eqs=[
#             -v_d - Ra * i_d + omega * psi_q,
#             -v_q - Ra * i_q - omega * psi_d,
#             -v_0 - R0 * i_0,
#             v_f - Rf * i_f,
#             omega_base * omega,
#             (Tm - Te - D * (omega - omega_ref)) / (2 * H),
#             omega_base * (omega_ref - omega),
#         ],
#         state_vars=[psi_d, psi_q, psi_0, psi_f, theta, omega, et],
#
#         # --------------------------------------------------------------------------------------
#         # ALGEBRAIC EQUATIONS
#         # --------------------------------------------------------------------------------------
#         algebraic_eqs=[
#             psi_d - (Lmd * i_f - (Lmd + La) * i_d),
#             psi_q - (-(Lmq + La) * i_q),
#             psi_0 - (-L0 * i_0),
#             psi_f - ((Lmd + Lf) * i_f - Lmd * i_d),
#
#             v_d - (2 / 3) * (
#                     inputs[0] * sym.sin(theta) +
#                     inputs[1] * sym.sin(theta - 2 * np.pi / 3) +
#                     inputs[2] * sym.sin(theta + 2 * np.pi / 3)),
#             v_q - (2 / 3) * (
#                     inputs[0] * sym.cos(theta) +
#                     inputs[1] * sym.cos(theta - 2 * np.pi / 3) +
#                     inputs[2] * sym.cos(theta + 2 * np.pi / 3)),
#             v_0 - (1 / 3) * (inputs[0] + inputs[1] + inputs[2]),
#
#             i_A - (i_d * sym.sin(theta) + i_q * sym.cos(theta) + i_0),
#             i_B - (i_d * sym.sin(theta - 2 * np.pi / 3) + i_q * sym.cos(theta - 2 * np.pi / 3) + i_0),
#             i_C - (i_d * sym.sin(theta + 2 * np.pi / 3) + i_q * sym.cos(theta + 2 * np.pi / 3) + i_0),
#
#             Te - (3 / 2) * (psi_q * i_d - psi_d * i_q),
#             Pe - (i_A * inputs[0] + i_B * inputs[1] + i_C * inputs[2]),
#             Qe - (1 / np.sqrt(3)) * ((inputs[0] - inputs[1]) * i_C +
#                                      (inputs[1] - inputs[2]) * i_A +
#                                      (inputs[2] - inputs[0]) * i_B),
#             Pe - Pm,
#             Tm - (Te + Kp * (omega_ref - omega) + Ki * et),
#             v_f - v_f0,
#         ],
#         algebraic_vars=[
#             i_d, i_q, i_0, i_f,
#             v_d, v_q, v_0,
#             i_A, i_B, i_C,
#             Te, Pe, Qe, Pm,Tm, v_f,
#         ],
#         in_vars=inputs,
#         out_vars=[i_A, i_B, i_C, omega],
#         # out_vars=[i_A, i_B, i_C],
#     )
#
#     templ.block.diff_vars = [d_psi_d, d_psi_q, d_psi_0, d_psi_f, d_theta, d_omega, d_et]
#
#     # --------------------------------------------------------------------------------------
#     # external mapping
#     # --------------------------------------------------------------------------------------
#
#     templ.block.external_mapping = {
#         VarPowerFlowReferenceType.v_N: None,
#         VarPowerFlowReferenceType.v_A: v_A,
#         VarPowerFlowReferenceType.v_B: v_B,
#         VarPowerFlowReferenceType.v_C: v_C,
#         VarPowerFlowReferenceType.P_N: None,
#         VarPowerFlowReferenceType.Q_N: None,
#         VarPowerFlowReferenceType.P_A: None,
#         VarPowerFlowReferenceType.Q_A: None,
#         VarPowerFlowReferenceType.P_B: None,
#         VarPowerFlowReferenceType.Q_B: None,
#         VarPowerFlowReferenceType.P_C: None,
#         VarPowerFlowReferenceType.Q_C: None,
#         VarPowerFlowReferenceType.i_N: None,
#         VarPowerFlowReferenceType.i_A: i_A,
#         VarPowerFlowReferenceType.i_B: i_B,
#         VarPowerFlowReferenceType.i_C: i_C,
#         VarPowerFlowReferenceType.phi_v: phi_v,
#         VarPowerFlowReferenceType.phi: phi,
#         VarPowerFlowReferenceType.Vpk: Vpk,
#         VarPowerFlowReferenceType.Ipk: Ipk,
#         VarPowerFlowReferenceType.d_v_N: None,
#         VarPowerFlowReferenceType.d_v_A: None,
#         VarPowerFlowReferenceType.d_v_B: None,
#         VarPowerFlowReferenceType.d_v_C: None,
#     }
#
#
#     # --------------------------------------------------------------------------------------
#     # Event dict (constants)
#     # --------------------------------------------------------------------------------------
#
#     templ.block.event_dict = {
#         H:          vf.add_const(5.0),
#         D:          vf.add_const(2.0),
#         La:         vf.add_const(0.15),
#         Lmq:        vf.add_const(1.55),
#         Lf:         vf.add_const(0.10),
#         Rf:         vf.add_const(0.017),
#         R0:         vf.add_const(0.001),
#         omega_ref:  vf.add_const(1.0),
#         Kp:         vf.add_const(2.0),
#         Ki:         vf.add_const(2.0),
#         v_f0:       vf.add_const(-0.000006702),
#         Lmd: Ld - La,
#         # init-only external auxiliary values
#         phi_v: vf.add_const(None),
#         phi: vf.add_const(None),
#         Vpk: vf.add_const(None),
#         Ipk: vf.add_const(None),
#         # delta: vf.add_const(None),
#         # delta: sym.atan(
#         #     (Ra * Ipk * sym.sin(phi) - omega * (Lmq + La) * Ipk * sym.cos(phi)) /
#         #     (Vpk + Ra * Ipk * sym.cos(phi) + omega * (Lmq + La) * Ipk * sym.sin(phi))
#         # ),
#         delta: sym.atan(
#             (Ra * Ipk * sym.sin(phi) - omega_ref * (Lmq + La) * Ipk * sym.cos(phi)) /
#             (Vpk + Ra * Ipk * sym.cos(phi) + omega_ref * (Lmq + La) * Ipk * sym.sin(phi))
#         ),
#     }
#     templ.block.api_obj_mapping = {
#         ParamPowerFlowReferenceType.omega_base : omega_base,
#         ParamPowerFlowReferenceType.R1: Ra,
#         ParamPowerFlowReferenceType.X1: Ld,
#         ParamPowerFlowReferenceType.X0: L0,
#     }
#
#     # --------------------------------------------------------------------------------------
#     # INIT EQUATIONS
#     # --------------------------------------------------------------------------------------
#
#     templ.block.init_eqs = {
#         et: vf.add_const(0.0),
#         omega: omega_ref,
#
#         theta: phi_v + delta,
#
#         v_d: 2 / 3 * (sym.sin(theta) * inputs[0] +
#                       sym.sin(theta - 2 * np.pi / 3) * inputs[1] +
#                       sym.sin(theta + 2 * np.pi / 3) * inputs[2]),
#         v_q: 2 / 3 * (sym.cos(theta) * inputs[0] +
#                       sym.cos(theta - 2 * np.pi / 3) * inputs[1] +
#                       sym.cos(theta + 2 * np.pi / 3) * inputs[2]),
#         v_0: (1 / 3) * (inputs[0] + inputs[1] + inputs[2]),
#
#         i_d: 2 / 3 * (sym.sin(theta) * i_A +
#                       sym.sin(theta - 2 * np.pi / 3) * i_B +
#                       sym.sin(theta + 2 * np.pi / 3) * i_C),
#         i_q: 2 / 3 * (sym.cos(theta) * i_A +
#                       sym.cos(theta - 2 * np.pi / 3) * i_B +
#                       sym.cos(theta + 2 * np.pi / 3) * i_C),
#         i_0: (1 / 3) * (i_A + i_B + i_C),
#
#         psi_q: (v_d + Ra * i_d),
#         psi_d: -(v_q + Ra * i_q),
#         psi_0: -L0 * i_0,
#
#         i_f: (psi_d + (Lmd + La) * i_d) / Lmd,
#         v_f: i_f * Rf,
#         psi_f: (Lmd + Lf) * i_f - Lmd * i_d,
#
#         Pe: (i_A * inputs[0] + i_B * inputs[1] + i_C * inputs[2]),
#         Qe: (1 / np.sqrt(3)) * ((inputs[0] - inputs[1]) * i_C +
#                                      (inputs[1] - inputs[2]) * i_A +
#                                      (inputs[2] - inputs[0]) * i_B),
#
#         Te: (3 / 2) * (psi_q * i_d - psi_d * i_q),
#         Pm: Pe,
#
#
#     }
#
#     # --------------------------------------------------------------------------------------
#     # DIFF INIT EQS
#     # --------------------------------------------------------------------------------------
#     c0 = vf.add_const(0.0)
#     templ.block.diff_init_eqs = {
#         d_theta: omega_base*omega,
#         d_et: (omega_ref - omega),
#         d_omega: c0,
#         d_psi_d: c0,
#         d_psi_q: c0,
#         d_psi_0: c0,
#         d_psi_f: c0,
#     }
#
#     return templ

[docs] def get_simple_generator_emt_template(vf: VarFactory, name: str = "simple_emt_type_generator_template") -> EmtModelTemplate: """ EMT type machine model without damping effects. :param vf: grid.var_factory :param name: string to identify the generator and model :return: EmtModelTemplate """ templ = EmtModelTemplate() templ.tpe = DeviceType.GeneratorDevice templ.name = name templ.block.name = name # -------------------------------------------------------------------------------------- # Inputs: instantaneous abc terminal voltages in pu (at bus) # -------------------------------------------------------------------------------------- v_A = vf.add_var(name=f"v_A_{name}", reference= VarPowerFlowReferenceType.v_A) v_B = vf.add_var(name=f"v_B_{name}", reference= VarPowerFlowReferenceType.v_B) v_C = vf.add_var(name=f"v_C_{name}", reference= VarPowerFlowReferenceType.v_C) Tm = vf.add_var(name=f"Tm_{name}") v_f = vf.add_var(name=f"v_f_{name}") d_v_A = vf.add_var(name=f"d_v_A_{name}", reference=VarPowerFlowReferenceType.d_v_A) d_v_B = vf.add_var(name=f"d_v_B_{name}", reference=VarPowerFlowReferenceType.d_v_B) d_v_C = vf.add_var(name=f"d_v_C_{name}", reference=VarPowerFlowReferenceType.d_v_C) p_A = vf.add_var(name=f"P_A_{name}", reference=VarPowerFlowReferenceType.P_A) q_A = vf.add_var(name=f"Q_A_{name}", reference=VarPowerFlowReferenceType.Q_A) p_B = vf.add_var(name=f"P_B_{name}", reference=VarPowerFlowReferenceType.P_B) q_B = vf.add_var(name=f"Q_B_{name}", reference=VarPowerFlowReferenceType.Q_B) p_C = vf.add_var(name=f"P_C_{name}", reference=VarPowerFlowReferenceType.P_C) q_C = vf.add_var(name=f"Q_C_{name}", reference=VarPowerFlowReferenceType.Q_C) inputs = [v_A, v_B, v_C] # -------------------------------------------------------------------------------------- # States (pu, except theta [rad]) # -------------------------------------------------------------------------------------- theta = vf.add_var("theta_" + name) # electrical angle [rad] omega = vf.add_var(name=f"omega_{name}") # speed [pu] psi_d = vf.add_var("psi_d_" + name) # flux linkages [pu] on psi_base = Vbase/omega_base psi_q = vf.add_var("psi_q_" + name) psi_f = vf.add_var("psi_f_" + name) psi_0 = vf.add_var("psi_0_" + name) et = vf.add_var("et_" + name) # PI integrator state (units: pu*s or equivalent) # Diff vars (derivatives) d_omega = vf.add_diff_var(name = f"d_omega_{name}", base_var=omega) d_theta = vf.add_diff_var(name = f"d_theta_{name}", base_var=theta) d_psi_d = vf.add_diff_var(name = f"d_psi_d_{name}", base_var=psi_d) d_psi_q = vf.add_diff_var(name = f"d_psi_q_{name}", base_var=psi_q) d_psi_0 = vf.add_diff_var(name = f"d_psi_0_{name}", base_var=psi_0) d_psi_f = vf.add_diff_var(name = f"d_psi_f_{name}", base_var=psi_f) d_et = vf.add_diff_var(name = f"d_et_{name}", base_var=et) # -------------------------------------------------------------------------------------- # Algebraic eqs # -------------------------------------------------------------------------------------- i_A = vf.add_var(name=f"i_A_{name}", reference= VarPowerFlowReferenceType.i_A) i_B = vf.add_var(name=f"i_B_{name}", reference= VarPowerFlowReferenceType.i_B) i_C = vf.add_var(name=f"i_C_{name}", reference= VarPowerFlowReferenceType.i_C) # dq0 voltages v_d = vf.add_var("v_d_" + name) v_q = vf.add_var("v_q_" + name) v_0 = vf.add_var("v_0_" + name) # dq0 currents i_d = vf.add_var("i_d_" + name) i_q = vf.add_var("i_q_" + name) i_0 = vf.add_var("i_0_" + name) # field i_f = vf.add_var(name=f"i_f_{name}") # powers/torques Te = vf.add_var("Te_" + name) Pe = vf.add_var("Pe_" + name) Qe = vf.add_var("Qe_" + name) Pm = vf.add_var("Pm_" + name) # -------------------------------------------------------------------------------------- # Parameters # -------------------------------------------------------------------------------------- omega_base = vf.add_var("omega_base_" + name) H = vf.add_var("H_" + name) D = vf.add_var("D_" + name) Ra = vf.add_var("Ra_" + name) La = vf.add_var("La_" + name) Ld = vf.add_var("Ld_" + name) Lmd = vf.add_var("Lmd_" + name) Lmq = vf.add_var("Lmq_" + name) Lf = vf.add_var("Lf_" + name) Rf = vf.add_var("Rf_" + name) R0 = vf.add_var("R0_" + name) L0 = vf.add_var("L0_" + name) omega_ref = vf.add_var("omega_ref_" + name) # pu delta = vf.add_var("delta_" + name) # difference between rotor angle and grid angle # These symbolic expressions reconstruct the PF-consistent phasor quantities # from the seeded EMT voltage and power references. The expressions are used # later in ``init_eqs`` so runtime-parameter initialization does not depend on # bus algebraic variables that are not available yet. phi_v_init: sym.Expr phi_init: sym.Expr vpk_init: sym.Expr ipk_init: sym.Expr phi_v_init, phi_init, vpk_init, ipk_init = get_pf_positive_sequence_init_refs( v_a=v_A, v_b=v_B, v_c=v_C, d_v_a=d_v_A, d_v_b=d_v_B, d_v_c=d_v_C, p_a=p_A, q_a=q_A, p_b=p_B, q_b=q_B, p_c=p_C, q_c=q_C, omega_base=omega_base, ) # These placeholders store the PF-consistent phasor quantities as runtime # parameters first. The initialization stage then resolves ``delta`` from the # stored values, which keeps the generator compatible with the EMT builder # ordering where bus voltages are initialized after runtime parameters. phi_v = vf.add_var("phi_v_" + name) phi = vf.add_var("phi_" + name) Vpk = vf.add_var("Vpk_" + name) Ipk = vf.add_var("Ipk_" + name) Kp = vf.add_var("Kp_" + name) Ki = vf.add_var("Ki_" + name) # v_f0 = vf.add_var("v_f0" + name) # temporary fixed exciter output P_share_ref = vf.add_var("p_share_ref_" + name) Q_share_ref = vf.add_var("q_share_ref_" + name) Kq_share = vf.add_var("Kq_share_" + name) templ.block = Block( # -------------------------------------------------------------------------------------- # STATE EQUATIONS (seconds + pu) # -------------------------------------------------------------------------------------- state_eqs=[ -v_d - Ra * i_d + omega * psi_q, -v_q - Ra * i_q - omega * psi_d, -v_0 - R0 * i_0, v_f - Rf * i_f, omega_base * omega, (Tm - Te - D * (omega - omega_ref)) / (2 * H), # omega_base * (omega_ref - omega), (omega_ref - omega), ], state_vars=[psi_d, psi_q, psi_0, psi_f, theta, omega, et], # -------------------------------------------------------------------------------------- # ALGEBRAIC EQUATIONS # -------------------------------------------------------------------------------------- algebraic_eqs=[ psi_d - (Lmd * i_f - (Lmd + La) * i_d), psi_q - (-(Lmq + La) * i_q), psi_0 - (-L0 * i_0), psi_f - ((Lmd + Lf) * i_f - Lmd * i_d), v_d - (2 / 3) * ( inputs[0] * sym.sin(theta) + inputs[1] * sym.sin(theta - 2 * np.pi / 3) + inputs[2] * sym.sin(theta + 2 * np.pi / 3)), v_q - (2 / 3) * ( inputs[0] * sym.cos(theta) + inputs[1] * sym.cos(theta - 2 * np.pi / 3) + inputs[2] * sym.cos(theta + 2 * np.pi / 3)), v_0 - (1 / 3) * (inputs[0] + inputs[1] + inputs[2]), i_A - (i_d * sym.sin(theta) + i_q * sym.cos(theta) + i_0), i_B - (i_d * sym.sin(theta - 2 * np.pi / 3) + i_q * sym.cos(theta - 2 * np.pi / 3) + i_0), i_C - (i_d * sym.sin(theta + 2 * np.pi / 3) + i_q * sym.cos(theta + 2 * np.pi / 3) + i_0), Te - (3 / 2) * (psi_q * i_d - psi_d * i_q), # Averaged active and reactive powers in the synchronous dq frame. # These are the correct quantities to compare with PF sharing references. Pe - ((3 / 2) * (v_d * i_d + v_q * i_q) + 3 * v_0 * i_0), Qe - ((3 / 2) * (v_q * i_d - v_d * i_q)), # Active-power sharing: the mechanical power setpoint is the assigned share. Pm - P_share_ref, # Governor acting around the assigned active-power share. Tm - (Pm + Kp * (omega_ref - omega) + Ki * et), # Reactive-power sharing through the excitation voltage. # At equilibrium, if Qe == Q_share_ref, then v_f = Rf * i_f and d_psi_f = 0. v_f - (Rf * i_f + Kq_share * (Q_share_ref - Qe)), ], algebraic_vars=[ i_d, i_q, i_0, i_f, v_d, v_q, v_0, i_A, i_B, i_C, Te, Pe, Qe, Pm,Tm, v_f, ], in_vars=inputs, out_vars=[i_A, i_B, i_C, omega], # out_vars=[i_A, i_B, i_C], ) templ.block.diff_vars = [d_psi_d, d_psi_q, d_psi_0, d_psi_f, d_theta, d_omega, d_et] # -------------------------------------------------------------------------------------- # external mapping # -------------------------------------------------------------------------------------- templ.block.external_mapping = { VarPowerFlowReferenceType.v_A: v_A, VarPowerFlowReferenceType.v_B: v_B, VarPowerFlowReferenceType.v_C: v_C, VarPowerFlowReferenceType.i_A: i_A, VarPowerFlowReferenceType.i_B: i_B, VarPowerFlowReferenceType.i_C: i_C, VarPowerFlowReferenceType.d_v_A: d_v_A, VarPowerFlowReferenceType.d_v_B: d_v_B, VarPowerFlowReferenceType.d_v_C: d_v_C, VarPowerFlowReferenceType.P_A: p_A, VarPowerFlowReferenceType.Q_A: q_A, VarPowerFlowReferenceType.P_B: p_B, VarPowerFlowReferenceType.Q_B: q_B, VarPowerFlowReferenceType.P_C: p_C, VarPowerFlowReferenceType.Q_C: q_C, VarPowerFlowReferenceType.phi_v: phi_v, VarPowerFlowReferenceType.phi: phi, VarPowerFlowReferenceType.Vpk: Vpk, VarPowerFlowReferenceType.Ipk: Ipk, } # -------------------------------------------------------------------------------------- # Event dict (constants) # -------------------------------------------------------------------------------------- templ.block.event_dict = { H: vf.add_const(5.0), D: vf.add_const(2.0), La: vf.add_const(0.15), Lmq: vf.add_const(1.55), Lf: vf.add_const(0.10), Rf: vf.add_const(0.017), R0: vf.add_const(0.001), omega_ref: vf.add_const(1.0), Kp: vf.add_const(2.0), Ki: vf.add_const(2.0), # v_f0: v_f, Lmd: Ld - La, d_v_A: vf.add_const(None), d_v_B: vf.add_const(None), d_v_C: vf.add_const(None), p_A: vf.add_const(None), q_A: vf.add_const(None), p_B: vf.add_const(None), q_B: vf.add_const(None), p_C: vf.add_const(None), q_C: vf.add_const(None), # The phasor placeholders are resolved during explicit initialization # from the PF-derived EMT references before ``delta`` is consumed. phi_v: vf.add_const(None), phi: vf.add_const(None), Vpk: vf.add_const(None), Ipk: vf.add_const(None), # ``delta`` depends on PF-derived phasor values that are only available # after explicit initialization resolves the placeholder runtime refs. # Keeping it as a runtime placeholder prevents an invalid early evaluation # with zero-valued defaults during problem construction. delta: vf.add_const(None), Kq_share: vf.add_const(0.2), } templ.block.api_obj_mapping = { ParamPowerFlowReferenceType.omega_base : omega_base, ParamPowerFlowReferenceType.R1: Ra, ParamPowerFlowReferenceType.X1: Ld, ParamPowerFlowReferenceType.X0: L0, ParamPowerFlowReferenceType.generator_share_p_ref: P_share_ref, ParamPowerFlowReferenceType.generator_share_q_ref: Q_share_ref, } # -------------------------------------------------------------------------------------- # INIT EQUATIONS # -------------------------------------------------------------------------------------- templ.block.init_eqs = { et: vf.add_const(0.0), omega: omega_ref, # The initialization first materializes the PF-consistent phasor values # into runtime parameters. This makes the subsequent ``delta`` evaluation # independent from unresolved bus algebraic variables. phi_v: phi_v_init, phi: phi_init, Vpk: vpk_init, Ipk: ipk_init, delta: sym.atan( (Ra * Ipk * sym.sin(phi) - omega_ref * (Lmq + La) * Ipk * sym.cos(phi)) / (Vpk + Ra * Ipk * sym.cos(phi) + omega_ref * (Lmq + La) * Ipk * sym.sin(phi)) ), theta: phi_v + delta, v_d: 2 / 3 * (sym.sin(theta) * inputs[0] + sym.sin(theta - 2 * np.pi / 3) * inputs[1] + sym.sin(theta + 2 * np.pi / 3) * inputs[2]), v_q: 2 / 3 * (sym.cos(theta) * inputs[0] + sym.cos(theta - 2 * np.pi / 3) * inputs[1] + sym.cos(theta + 2 * np.pi / 3) * inputs[2]), v_0: (1 / 3) * (inputs[0] + inputs[1] + inputs[2]), i_d: 2 / 3 * (sym.sin(theta) * i_A + sym.sin(theta - 2 * np.pi / 3) * i_B + sym.sin(theta + 2 * np.pi / 3) * i_C), i_q: 2 / 3 * (sym.cos(theta) * i_A + sym.cos(theta - 2 * np.pi / 3) * i_B + sym.cos(theta + 2 * np.pi / 3) * i_C), i_0: (1 / 3) * (i_A + i_B + i_C), psi_q: (v_d + Ra * i_d), psi_d: -(v_q + Ra * i_q), psi_0: -L0 * i_0, i_f: (psi_d + (Lmd + La) * i_d) / Lmd, # v_f: i_f * Rf, psi_f: (Lmd + Lf) * i_f - Lmd * i_d, Pe: ((3 / 2) * (v_d * i_d + v_q * i_q) + 3 * v_0 * i_0), Qe: ((3 / 2) * (v_q * i_d - v_d * i_q)), Pm: P_share_ref, Te: (3 / 2) * (psi_q * i_d - psi_d * i_q), v_f: (Rf * i_f + Kq_share * (Q_share_ref - Qe)), } # -------------------------------------------------------------------------------------- # DIFF INIT EQS # -------------------------------------------------------------------------------------- c0 = vf.add_const(0.0) templ.block.diff_init_eqs = { d_theta: omega_base*omega, d_et: (omega_ref - omega), d_omega: c0, d_psi_d: c0, d_psi_q: c0, d_psi_0: c0, d_psi_f: c0, } return templ