# 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 typing import List
import math
from VeraGridEngine.enumerations import DeviceType, VarPowerFlowReferenceType, ParamPowerFlowReferenceType
from VeraGridEngine.Devices.Dynamic.rms_template import RmsModelTemplate
from VeraGridEngine.Utils.Symbolic.block import (Block, Var)
from VeraGridEngine.Utils.Symbolic.block_helpers import tf_to_block
from VeraGridEngine.Devices.Dynamic.var_factory import VarFactory
import VeraGridEngine.Utils.Symbolic.symbolic as sym
from VeraGridEngine.enumerations import ConverterControlType
TYPE_CHECKING = True
if TYPE_CHECKING:
from VeraGridEngine.Devices.Substation.bus import Bus
from VeraGridEngine.Devices.Branches.transformer import Transformer2W, WindingType, 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]
def build_vsc_rms(vfactory: VarFactory, name:str = ''):
"""
Build power control loop model for Grid Following Converter.
Supports multiple control modes via ConverterControlType.
from bus is DC
to bus is AC
"""
templ = RmsModelTemplate()
templ.tpe = DeviceType.VscDevice
templ.name = name
vm_t = vfactory.add_var("Vm_t_" + name)
inputs: List[Var] = [vm_t]
Pf = vfactory.add_var("Pf_vsc", VarPowerFlowReferenceType.Pf)
Pt = vfactory.add_var("Pt", VarPowerFlowReferenceType.Pt)
Qt_ref = vfactory.add_var("Qt_ref", VarPowerFlowReferenceType.Qt)
alpha1 = vfactory.add_var("alpha1")
alpha2 = vfactory.add_var("alpha2")
alpha3 = vfactory.add_var("alpha3")
block = Block()
block.parameters[alpha1] = vfactory.add_const(0.0)
block.parameters[alpha2] = vfactory.add_const(0.0)
block.parameters[alpha3] = vfactory.add_const(0.0)
im = sym.sqrt(Pt * Pt + Qt_ref * Qt_ref) / (vm_t + vfactory.add_const(1e-9))
block.algebraic_vars = [Pf, Pt]
block.event_dict[Qt_ref] = vfactory.add_const(0.0)
#Active power is conserved Reactive isnt
block.algebraic_eqs = [
Pf + Pt - 1.0 * (alpha1 + alpha2 * im + alpha3 * im ** 2),
]
block.external_mapping = {
VarPowerFlowReferenceType.Vm: vm_t,
VarPowerFlowReferenceType.Pf: Pf,
VarPowerFlowReferenceType.Pt: Pt,
}
block.api_obj_mapping= {
ParamPowerFlowReferenceType.alpha1 : alpha1,
ParamPowerFlowReferenceType.alpha2 : alpha2,
ParamPowerFlowReferenceType.alpha3 : alpha3,
}
block.in_vars = inputs
templ.block = block
return templ
[docs]
def build_trafo_vsc(vf:VarFactory, trafo:Transformer2W, name:str = ''):
#from bus is GRID
#to bus is CONVERTER
templ = RmsModelTemplate()
model_name = name or trafo.name
templ.name = model_name
vdc = vf.add_var('vdc_' + model_name, VarPowerFlowReferenceType.Vdc)
vmf = vf.add_var('vmf_' + model_name, VarPowerFlowReferenceType.Vmf)
vmt = vf.add_var('vmt_' + model_name, VarPowerFlowReferenceType.Vmt)
vaf = vf.add_var('vaf_' + model_name, VarPowerFlowReferenceType.Vaf)
vat = vf.add_var('vat_' + model_name, VarPowerFlowReferenceType.Vat)
m = vf.add_var('m')
inputs = [vdc, vmf, vmt, vaf, vat, m]
k = vf.add_const(np.sqrt(3/8))
control_block, am, phi = build_vsc_transformer_control(vf=vf, Vm=vmt, vdc=vdc, name=model_name)
#Variables
Qf = vf.add_var("Qf_" + model_name, VarPowerFlowReferenceType.Qf)
Qt = vf.add_var("Qt_" + model_name, VarPowerFlowReferenceType.Qt)
Pf = vf.add_var("Pf_" + model_name, VarPowerFlowReferenceType.Pf)
Pt = vf.add_var("Pt_" + model_name, VarPowerFlowReferenceType.Pt)
Im = vf.add_var("Im_" + model_name)
#Parameters
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')
phi0 = vf.add_var('phi0')
Q_ref = vf.add_var('Q_ref')
P_ref = vf.add_var('P_ref')
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}')
# 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, Im],
algebraic_eqs=[
# From side: Pf = Re(Vf * (Yff*Vf + Yft*Vt)*)
vmt - vdc*am*k,
Pf - ((vmf ** 2 * (gFe + gt)) / (m * vtap_f) ** 2 - gt / (m * vtap_f * vtap_t) * vmf * vmt * sym.cos(
theta_hk - phi) - bt / (m * vtap_f * vtap_t) * vmf * vmt * sym.sin(
theta_hk - phi - phase_displacement)),
Qf - (-vmf ** 2 * (bmu / 2 + bt) / (m * vtap_f) ** 2 - gt / (m * vtap_f * vtap_t) * vmf * vmt * sym.sin(
theta_hk - phi) + bt / (m * vtap_f * vtap_t) * vmf * vmt * sym.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 * sym.cos(
theta_hk - phi) + bt / (m * vtap_f * vtap_t) * vmt * vmf * sym.sin(
theta_hk - phi - phase_displacement)),
Qt - (-vmt ** 2 * (bmu / 2 + bt) / vtap_t ** 2 + gt / (m * vtap_f * vtap_t) * vmt * vmf * sym.sin(
theta_hk - phi) + bt / (m * vtap_f * vtap_t) * vmt * vmf * sym.cos(
theta_hk - phi - phase_displacement)),
Im - sym.sqrt(Pt**2 + Qt**2)/vmf,
(vat) - (vaf + phi0 + phi),
],
init_eqs={
am: vmt/k*vdc,
Im: sym.sqrt(Pt**2 + Qt**2)/vmf,
phi0: vat - vaf,
P_ref: Pf,
},
event_dict={
m : vf.add_const(trafo.tap_module),
P_ref : vf.add_const(None),
phi0 : vf.add_const(None),
},
in_vars=inputs,
)
block.children.append(control_block)
block.external_mapping = {
VarPowerFlowReferenceType.Vaf: vaf,
VarPowerFlowReferenceType.Vat: vat,
VarPowerFlowReferenceType.Vmf: vmf,
VarPowerFlowReferenceType.Vmt: vmt,
VarPowerFlowReferenceType.Pf: Pf,
VarPowerFlowReferenceType.Pt: Pt,
VarPowerFlowReferenceType.Qf: Qf,
VarPowerFlowReferenceType.Qt: Qt,
}
block.api_obj_mapping = {
ParamPowerFlowReferenceType.g: gt,
ParamPowerFlowReferenceType.b: bt,
ParamPowerFlowReferenceType.bsh: bmu,
}
block.parameters = {
gt:vf.add_const(ys.real),
bt:vf.add_const(ys.imag),
gFe:vf.add_const(ysh.real),
bmu:vf.add_const(ysh.imag)
}
block.out_vars = [Im, Pt]
templ.block = block
return templ