Source code for VeraGridEngine.Templates.Rms.transformer_rms_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.enumerations import DeviceType, TapPhaseControl, TapModuleControl, WindingType, ParamPowerFlowReferenceType
from VeraGridEngine.Devices.Dynamic.rms_template import RmsModelTemplate
from VeraGridEngine.Devices.Dynamic.var_factory import VarFactory
from VeraGridEngine.Devices.Branches.transformer import Transformer2W
from VeraGridEngine.Utils.Symbolic.block import Block, VarPowerFlowReferenceType
from VeraGridEngine.Utils.Symbolic.symbolic import sin, cos
from VeraGridEngine.enumerations import WindingsConnection


[docs] def parse_windings_connection(conn: WindingsConnection) -> tuple[WindingType, WindingType]: """ Parse a WindingsConnection enum into (conn_f, conn_t) WindingType values. WindingsConnection values are two-character strings: - G: GroundedStar - S: NeutralStar (ungrounded star) - D: Delta Examples: - GG -> (GroundedStar, GroundedStar) - GD -> (GroundedStar, Delta) - DD -> (Delta, Delta) """ conn_str = str(conn) if len(conn_str) != 2: raise ValueError(f"Invalid WindingsConnection: {conn}") char_to_winding = { 'G': WindingType.GroundedStar, 'S': WindingType.NeutralStar, 'D': WindingType.Delta, } conn_f = char_to_winding.get(conn_str[0]) conn_t = char_to_winding.get(conn_str[1]) if conn_f is None or conn_t is None: raise ValueError(f"Invalid WindingsConnection characters: {conn_str}") return conn_f, conn_t
[docs] class TrafoRmsTemplate(RmsModelTemplate): __slots__ = ( "tpe", "_block", ) def __init__(self, vf: VarFactory, name: str = "rms_trafo_template"): """ Created the RMS Template of a Bus :param vf: VarFactory :param name: Name of the RMS Model """ super().__init__(name=name) vf = vf self.tpe: DeviceType = DeviceType.Transformer2WDevice Qf = vf.add_var("Qf") Qt = vf.add_var("Qt") Pf = vf.add_var("Pf") Pt = vf.add_var("Pt") m = vf.add_var('m') phi = vf.add_var('phi') gt = vf.add_var("g") bt = vf.add_var("b") gFe = vf.add_var('gFe') bmu = vf.add_var('bmu') vtap_f = vf.add_var('vtap_f') vtap_t = vf.add_var('vtap_t') # print(f"DEBUG Trafo: R={trafo.R}, X={trafo.X}, G={trafo.G}, B={trafo.B}") # print(f"DEBUG Trafo: tap_mod={trafo.tap_module}, tap_phase={trafo.tap_phase}, vtap_f={vtap_f}, vtap_t={vtap_t}") # print(f"vtap p is {vtap_f}") # print(f"vtap t is {vtap_t}") # print(f'ys is {ys} ysh is {ysh}') Vmf = vf.add_var('Vmf', VarPowerFlowReferenceType.Vmf) Vaf = vf.add_var('Vaf', VarPowerFlowReferenceType.Vaf) Vmt = vf.add_var('Vmt', VarPowerFlowReferenceType.Vmt) Vat = vf.add_var('Vat', VarPowerFlowReferenceType.Vat) inputs = [Vmf, Vaf, Vmt, Vat] # Calculate phase displacement matching transformer_admittance logic # Use conn attribute (preserves user intent) instead of conn_f/conn_t (may be overwritten by template) # conn_f, conn_t = parse_windings_connection(trafo.conn) # conn_y_from = conn_f == WindingType.NeutralStar or conn_f == WindingType.GroundedStar # conn_y_to = conn_t == WindingType.NeutralStar or conn_t == WindingType.GroundedStar # if conn_f == WindingType.Delta and conn_y_to: # Dy # phase_displacement = np.deg2rad(60.0) # elif conn_y_from and conn_t == WindingType.Delta: # Yd # phase_displacement = np.deg2rad(0.0) # else: # phase_displacement = 0.0 phase_displacement = 0 theta_hk = Vaf - Vat block = Block( algebraic_vars=[Pf, Pt, Qf, Qt], algebraic_eqs=[ # From side: Pf = Re(Vf * (Yff*Vf + Yft*Vt)*) Pf - ((Vmf ** 2 * (gFe + gt)) / (m * vtap_f) ** 2 - gt / (m * vtap_f * vtap_t) * Vmf * Vmt * cos( theta_hk - phi) - bt / (m * vtap_f * vtap_t) * Vmf * Vmt * sin( theta_hk - phi - phase_displacement)), Qf - (-Vmf ** 2 * (bmu / 2 + bt) / (m * vtap_f) ** 2 - gt / (m * vtap_f * vtap_t) * Vmf * Vmt * sin( theta_hk - phi) + bt / (m * vtap_f * vtap_t) * Vmf * Vmt * cos( theta_hk - phi - phase_displacement)), # To side: Pt = Re(Vt * (Ytf*Vf + Ytt*Vt)*) Pt - ((Vmt ** 2 * (gFe + gt)) / vtap_t ** 2 - gt / (m * vtap_f * vtap_t) * Vmt * Vmf * cos( theta_hk - phi) + bt / (m * vtap_f * vtap_t) * Vmt * Vmf * sin( theta_hk - phi - phase_displacement)), Qt - (-Vmt ** 2 * (bmu / 2 + bt) / vtap_t ** 2 + gt / (m * vtap_f * vtap_t) * Vmt * Vmf * sin( theta_hk - phi) + bt / (m * vtap_f * vtap_t) * Vmt * Vmf * cos( theta_hk - phi - phase_displacement)), ], in_vars=[Vmf, Vaf, Vmt, Vat], ) block.external_mapping = { VarPowerFlowReferenceType.Pf: Pf, VarPowerFlowReferenceType.Pt: Pt, VarPowerFlowReferenceType.Qf: Qf, VarPowerFlowReferenceType.Qt: Qt, VarPowerFlowReferenceType.Vaf: Vaf, VarPowerFlowReferenceType.Vmf: Vmf, VarPowerFlowReferenceType.Vmt: Vmt, VarPowerFlowReferenceType.Vat: Vat, } block.api_obj_mapping = { ParamPowerFlowReferenceType.g: gt, ParamPowerFlowReferenceType.b: bt, ParamPowerFlowReferenceType.gFe: gFe, ParamPowerFlowReferenceType.bsh: bmu, ParamPowerFlowReferenceType.tap_module: m, ParamPowerFlowReferenceType.tap_phase: phi, ParamPowerFlowReferenceType.vtap_f: vtap_f, ParamPowerFlowReferenceType.vtap_t: vtap_t, } block.parameters[gt] = vf.add_const(0.0) block.parameters[bt] = vf.add_const(0.0) block.parameters[gFe] = vf.add_const(0.0) block.parameters[bmu] = vf.add_const(0.0) block.parameters[m] = vf.add_const(1.0) block.parameters[phi] = vf.add_const(0.0) block.parameters[vtap_f] = vf.add_const(1.0) block.parameters[vtap_t] = vf.add_const(1.0) # # if trafo.tap_module_control_mode != TapModuleControl.fixed: # del block.event_dict[m] # block.init_eqs[m] = vf.add_const(trafo.tap_module) # Ki = vf.add_var('Ki_mod') # Kd = vf.add_var('Kd_mod') # var_ref = vf.add_var('var_ref') # block.event_dict[Ki] = vf.add_const(0.1) # block.event_dict[Kd] = vf.add_const(0.1) # if trafo.tap_module_control_mode == TapModuleControl.Qf: # control_var = Qf # block.event_dict[var_ref] = vf.add_const(trafo.Qset) # elif trafo.tap_module_control_mode == TapModuleControl.Qt: # control_var = Qt # block.event_dict[var_ref] = vf.add_const(trafo.Qset) # elif trafo.tap_module_control_mode == TapModuleControl.Vm: # control_var = Vmt # block.event_dict[var_ref] = vf.add_const(trafo.vset) # PI_control_block, _, _ = tf_to_block( # var_factory=vf, # num=[Ki], # den=[Kd, vf.add_const(1)], # x=control_var - var_ref, # y=m, # ) # block.add(PI_control_block) # if trafo.tap_phase_control_mode != TapPhaseControl.fixed: # discretized_control = True # if discretized_control: # del block.event_dict[phi] # block.init_eqs[phi] = vf.add_const(trafo.tap_phase) # Ki = vf.add_var('Ki_phase') # Kp = vf.add_var('Kp_phase') # Tm = vf.add_var('Tm') # var_ref = vf.add_var('var_ref') # block.event_dict[Ki] = vf.add_const(0.1) # block.event_dict[Kp] = vf.add_const(0.1) # block.event_dict[Tm] = vf.add_const(0.1) # if trafo.tap_phase_control_mode == TapPhaseControl.Pf: # control_var = Pf # block.event_dict[var_ref] = vf.add_const(trafo.Pset / Sbase) # elif trafo.tap_phase_control_mode == TapPhaseControl.Pt: # control_var = Pt # block.event_dict[var_ref] = vf.add_const(trafo.Pset / Sbase) # discretized_control,_ = discrete_control_block( # var_factory=vf, # m=m, # m_min=trafo.tap_phase_min, # m_max=trafo.tap_phase_max, # delta_m=trafo.tap_phase_step, # v=control_var, # v_ref=var_ref, # delta_v=0.01, # ts=Tm, # ) # block.children.append(discretized_control) # else: # del block.event_dict[phi] # block.init_eqs[phi] = vf.add_const(trafo.tap_phase) # Ki = vf.add_var('Ki_phase') # Kp = vf.add_var('Kp_phase') # var_ref = vf.add_var('var_ref') # block.event_dict[Ki] = vf.add_const(0.1) # block.event_dict[Kp] = vf.add_const(0.1) # if trafo.tap_phase_control_mode == TapPhaseControl.Pf: # control_var = Pf # block.event_dict[var_ref] = vf.add_const(trafo.Pset / Sbase) # elif trafo.tap_phase_control_mode == TapPhaseControl.Pt: # control_var = Pt # block.event_dict[var_ref] = vf.add_const(trafo.Pset / Sbase) # PI_control_block, _ = tf_to_block( # var_factory=vf, # num=[Ki, Kp], # den=[0, vf.add_const(1)], # x=control_var - var_ref, # y=phi, # name="Phase_Control_PI" # ) # block.children.append(PI_control_block) # block.unify_blocks() block.in_vars = inputs self._block = block
[docs] class TrafoPhasorRmsTemplate(RmsModelTemplate): def __init__(self, vf: VarFactory, name: str = "rms_trafo_phasor_template"): """ Current-based phasor RMS template for a 2-winding transformer. Inputs use rectangular bus voltages (Vrf, Vif, Vrt, Vit) and outputs branch currents directly (Irf, Iif, Irt, Iit), matching the line phasor template interface used by current-balance RMS formulations. """ super().__init__(name=name) self.tpe: DeviceType = DeviceType.Transformer2WDevice Vrf = vf.add_var("Vrf_" + name, VarPowerFlowReferenceType.Vrf) Vif = vf.add_var("Vif_" + name, VarPowerFlowReferenceType.Vif) Vrt = vf.add_var("Vrt_" + name, VarPowerFlowReferenceType.Vrt) Vit = vf.add_var("Vit_" + name, VarPowerFlowReferenceType.Vit) Irf = vf.add_var("Irf") Iif = vf.add_var("Iif") Irt = vf.add_var("Irt") Iit = vf.add_var("Iit") gt = vf.add_var("g") bt = vf.add_var("b") gFe = vf.add_var("gFe") bmu = vf.add_var("bmu") tap_module = vf.add_var("tap_module") tap_phase = vf.add_var("tap_phase") vtap_f = vf.add_var("vtap_f") vtap_t = vf.add_var("vtap_t") phase_displacement = vf.add_var("phase_displacement") theta0 = tap_phase + phase_displacement cos_theta0 = cos(theta0) sin_theta0 = sin(theta0) k_from = tap_module * vtap_f k_cross = tap_module * vtap_f * vtap_t k_to = vtap_t # Admittance coefficients in rectangular form gff = (gFe + gt) / (k_from ** 2) bff = (bmu / 2 + bt) / (k_from ** 2) gtt = (gFe + gt) / (k_to ** 2) btt = (bmu / 2 + bt) / (k_to ** 2) # Yft = -(gt + j*bt) / (m*vtap_f*vtap_t*exp(-j*theta)) gft = -(gt * cos_theta0 - bt * sin_theta0) / k_cross bft = -(gt * sin_theta0 + bt * cos_theta0) / k_cross # Ytf = -(gt + j*bt) / (m*vtap_f*vtap_t*exp(j*theta)) gtf = -(gt * cos_theta0 + bt * sin_theta0) / k_cross btf = (gt * sin_theta0 - bt * cos_theta0) / k_cross block = Block( algebraic_vars=[Irf, Iif, Irt, Iit], algebraic_eqs=[ Irf - (gff * Vrf - bff * Vif + gft * Vrt - bft * Vit), Iif - (bff * Vrf + gff * Vif + bft * Vrt + gft * Vit), Irt - (gtf * Vrf - btf * Vif + gtt * Vrt - btt * Vit), Iit - (btf * Vrf + gtf * Vif + btt * Vrt + gtt * Vit), ], in_vars=[Vrf, Vif, Vrt, Vit], ) block.external_mapping = { VarPowerFlowReferenceType.Vrf: Vrf, VarPowerFlowReferenceType.Vif: Vif, VarPowerFlowReferenceType.Vrt: Vrt, VarPowerFlowReferenceType.Vit: Vit, VarPowerFlowReferenceType.Irf: Irf, VarPowerFlowReferenceType.Iif: Iif, VarPowerFlowReferenceType.Irt: Irt, VarPowerFlowReferenceType.Iit: Iit, } block.api_obj_mapping = { ParamPowerFlowReferenceType.g: gt, ParamPowerFlowReferenceType.b: bt, ParamPowerFlowReferenceType.bsh: bmu, ParamPowerFlowReferenceType.gFe: gFe, ParamPowerFlowReferenceType.tap_module: tap_module, ParamPowerFlowReferenceType.transformer_tap_ratio: tap_phase, ParamPowerFlowReferenceType.vtap_f: vtap_f, ParamPowerFlowReferenceType.vtap_t: vtap_t, } block.parameters[gt] = vf.add_const(0.0) block.parameters[bt] = vf.add_const(0.0) block.parameters[gFe] = vf.add_const(0.0) block.parameters[bmu] = vf.add_const(0.0) block.parameters[tap_module] = vf.add_const(1.0) block.parameters[tap_phase] = vf.add_const(0.0) block.parameters[vtap_f] = vf.add_const(1.0) block.parameters[vtap_t] = vf.add_const(1.0) block.parameters[phase_displacement] = vf.add_const(0) self._block = block
[docs] def get_transformer2w_rms(vf: VarFactory, use_phasor_template: bool = False): if use_phasor_template: return TrafoPhasorRmsTemplate(vf=vf) return TrafoRmsTemplate(vf=vf)
[docs] def initialize_trafo_rms(trafo: Transformer2W, vf: VarFactory): """ :param trafo: :param vf: :return: """ templ = TrafoRmsTemplate(vf=vf) trafo.rms_model = templ.block return templ