Source code for VeraGridEngine.Templates.Rms.transformer_rms_template_legacy
# 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.Utils.Symbolic.block_helpers import tf_to_block, tf_to_diffblock_with_output, discrete_control_block
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, trafo:Transformer2W, vf: VarFactory, Sbase:float = 100, name: str = "rms_bus_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.TransformerTypeDevice
if trafo.rms_model.empty():
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')
ys = 1.0 / complex(trafo.R, trafo.X)
ysh = trafo.G + 1j * trafo.B
gt = vf.add_var("g")
bt = vf.add_var("b")
gFe = vf.add_var('gFe')
bmu = vf.add_var('bmu')
vtap_f, vtap_t = trafo.get_virtual_taps()
#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 = trafo.bus_from.rms_model.E(VarPowerFlowReferenceType.Vm)
Vaf = trafo.bus_from.rms_model.E(VarPowerFlowReferenceType.Va)
Vmt = trafo.bus_to.rms_model.E(VarPowerFlowReferenceType.Vm)
Vat = trafo.bus_to.rms_model.E(VarPowerFlowReferenceType.Va)
# 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
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)),
],
event_dict={
m: vf.add_const(trafo.tap_module),
phi: vf.add_const(trafo.tap_phase)
},
in_vars=[Vmf, Vaf, Vmt, Vat],
)
block.external_mapping = {
VarPowerFlowReferenceType.Pf: Pf,
VarPowerFlowReferenceType.Pt: Pt,
VarPowerFlowReferenceType.Qf: Qf,
VarPowerFlowReferenceType.Qt: Qt,
}
block.parameters[gt] = vf.add_const(ys.real)
block.parameters[bt] = vf.add_const(ys.imag)
block.parameters[gFe] = vf.add_const(ysh.real)
block.parameters[bmu] = vf.add_const(ysh.imag)
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()
self._block = block
[docs]
class TrafoPhasorRmsTemplate(RmsModelTemplate):
def __init__(self, trafo: Transformer2W, 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.TransformerTypeDevice
if trafo.rms_model.empty():
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")
ys = 1.0 / complex(trafo.R, trafo.X)
ysh = trafo.G + 1j * trafo.B
gt = vf.add_var("g")
bt = vf.add_var("b")
gFe = vf.add_var("gFe")
bmu = vf.add_var("bmu")
vtap_f, vtap_t = trafo.get_virtual_taps()
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
theta0 = trafo.tap_phase + phase_displacement
cos_theta0 = np.cos(theta0)
sin_theta0 = np.sin(theta0)
k_from = trafo.tap_module * vtap_f
k_cross = trafo.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,
}
block.parameters[gt] = vf.add_const(ys.real)
block.parameters[bt] = vf.add_const(ys.imag)
block.parameters[gFe] = vf.add_const(ysh.real)
block.parameters[bmu] = vf.add_const(ysh.imag)
self._block = block
[docs]
def initialize_trafo_rms(trafo: Transformer2W, vf: VarFactory, Sbase: float = 100, use_phasor_template: bool = False):
"""
:param trafo:
:param vf:
:param Sbase:
:return:
"""
if not trafo.bus_from.rms_model.empty():
use_phasor_template = any(v.ref == VarPowerFlowReferenceType.Vr for v in trafo.bus_from.rms_model.out_vars)
if use_phasor_template:
templ = TrafoPhasorRmsTemplate(vf=vf, trafo=trafo)
trafo.rms_model = templ.block
return templ
else:
templ = TrafoRmsTemplate(vf=vf, trafo=trafo, Sbase=Sbase)
trafo.rms_model = templ.block
return templ