# 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
from __future__ import annotations
from typing import Dict, List, Tuple
from VeraGridEngine.enumerations import (
DeviceType,
WindingType,
VarPowerFlowReferenceType,
ParamPowerFlowReferenceType,
)
from VeraGridEngine.Devices.Dynamic.var_factory import VarFactory
from VeraGridEngine.Devices.Dynamic.emt_template import EmtModelTemplate
from VeraGridEngine.Templates.Emt.load_RLC_emt_template import get_grounding_link_emt_template
from VeraGridEngine.Templates.Emt.transformer_emt_template import _attach_transformer_editor_diagram
from VeraGridEngine.Utils.Symbolic.block import Block, Var, Expr
import VeraGridEngine.Utils.Symbolic.symbolic as sym
def _project_currents(
coeffs: list[list[Expr]],
currents: list[Expr],
c0: Expr,
) -> list[Expr]:
"""
Project winding-frame currents to terminal-frame currents.
The voltage transformation used in the template is::
v_w = C^T v_terminal
and thus the consistent current back-projection is::
i_terminal = C i_w
:param coeffs: Connection matrix as symbolic expressions.
:param currents: Winding-frame currents.
:param c0: Zero-like symbolic constant.
:return: Terminal-frame current expressions.
"""
out: list[Expr] = []
for i in range(3):
rhs: Expr = c0
for j in range(3):
rhs = rhs + coeffs[i][j] * currents[j]
out.append(rhs)
return out
[docs]
def get_xfmr_emt_template(
vf: VarFactory,
conn_f: WindingType | None = None,
conn_t: WindingType | None = None,
name: str = "xfmr_emt_template",
) -> EmtModelTemplate:
"""
Build the EMT DAE template of a basic ATP-like transformer model.
Overview
--------
This transformer model is organized as:
1. one series leakage branch between the two transformer terminals,
2. one magnetizing/core branch referred to the from side only,
3. one linear core-loss branch referred to the from side only,
4. optional terminal shunt capacitances represented through charge states.
The model is intentionally kept close to the classical robust transformer
equivalent used in EMT-type formulations:
* the leakage path couples the from and to terminals,
* the core is excited from the from-side winding voltage only,
* the core current is injected only on the from side.
This avoids the algebraic/numerical issues caused by splitting the magnetic
branch symmetrically between both sides.
Parameter ownership
-------------------
This template follows the requested VeraGrid architecture:
* api_obj_mapping:
only parameters that come from the static transformer object or are passed
almost directly by the EMT assembler.
* event_dict:
parameters of the dynamic transformer model that are not explicit static
data and can be overridden through set_parameter_in_model().
* init_eqs:
initialization equations of internal algebraic and state variables.
* diff_init_eqs:
initialization equations of internal derivatives.
Static mapped parameters
------------------------
Expected from the EMT assembler through api_obj_mapping:
* omega_base
* transformer_rated_power_mva
* transformer_open_circuit_current_pct
* transformer_open_circuit_loss_kw
* transformer_short_circuit_voltage_pct
* transformer_short_circuit_loss_kw
* tap_module
* transformer_from_connection_**
* transformer_to_connection_**
Dynamic local parameters
------------------------
Local model parameters stored in event_dict:
* xfmr_core_topology_code
* xfmr_yoke_area_rel
* xfmr_yoke_length_rel
* xfmr_outer_leg_area_rel
* xfmr_outer_leg_length_rel
* xfmr_c_term
* xfmr_use_linear_core
* xfmr_core_knee_flux_mult
* xfmr_core_knee_current_mult
* xfmr_sc_resistance_pct
* xfmr_core_linear_l_pu
* xfmr_core_a_prime
* xfmr_core_b_prime
Core modelling
--------------
The magnetic branch is referred only to the from side:
* v_core_w = v_f_w
* i_core is injected only at the from terminal
This is more robust and closer to the classical transformer equivalent than
the previous symmetric split of the core branch between both terminals.
Capacitive path
---------------
Terminal capacitances are represented through:
* state charges q_f and q_t
* algebraic currents i_cap_f and i_cap_t
* q = C v
* dq/dt = i_cap
If xfmr_c_term = 0, these states collapse consistently to zero.
Sign convention
---------------
Exported branch currents follow the VeraGrid branch convention:
current is positive when it leaves the bus.
:param vf: EMT variable factory.
:param conn_f: Optional from-side winding connection.
:param conn_t: Optional to-side winding connection.
:param name: Symbolic model name.
:return: EMT transformer template.
"""
templ = EmtModelTemplate()
templ.tpe = DeviceType.Transformer2WDevice
templ.name = name
templ.block.name = name
if conn_f in {WindingType.GroundedStar, WindingType.NeutralStar}:
from_has_neutral_port: bool = True
else:
from_has_neutral_port = False
if conn_t in {WindingType.GroundedStar, WindingType.NeutralStar}:
to_has_neutral_port: bool = True
else:
to_has_neutral_port = False
c0: Expr = vf.add_const(0.0)
c1: Expr = vf.add_const(1.0)
c2: Expr = vf.add_const(2.0)
c3: Expr = vf.add_const(3.0)
c_eps: Expr = vf.add_const(1e-9)
c_smooth: Expr = vf.add_const(1e-8)
c_sqrt2: Expr = sym.sqrt(vf.add_const(2.0))
# ------------------------------------------------------------------
# Static parameters mapped from the transformer API object.
# ------------------------------------------------------------------
omega_base: Var = vf.add_var(name=f"omega_base_{name}")
xfmr_s_rated_mva: Var = vf.add_var(name=f"xfmr_s_rated_mva_{name}")
xfmr_oc_current_pct: Var = vf.add_var(name=f"xfmr_oc_current_pct_{name}")
xfmr_oc_loss_kw: Var = vf.add_var(name=f"xfmr_oc_loss_kw_{name}")
xfmr_sc_voltage_pct: Var = vf.add_var(name=f"xfmr_sc_voltage_pct_{name}")
xfmr_sc_loss_kw: Var = vf.add_var(name=f"xfmr_sc_loss_kw_{name}")
xfmr_tap_module: Var = vf.add_var(name=f"xfmr_tap_module_{name}")
templ.block.api_obj_mapping[ParamPowerFlowReferenceType.omega_base] = omega_base
templ.block.api_obj_mapping[ParamPowerFlowReferenceType.transformer_rated_power_mva] = xfmr_s_rated_mva
templ.block.api_obj_mapping[ParamPowerFlowReferenceType.transformer_open_circuit_current_pct] = xfmr_oc_current_pct
templ.block.api_obj_mapping[ParamPowerFlowReferenceType.transformer_open_circuit_loss_kw] = xfmr_oc_loss_kw
templ.block.api_obj_mapping[ParamPowerFlowReferenceType.transformer_short_circuit_voltage_pct] = xfmr_sc_voltage_pct
templ.block.api_obj_mapping[ParamPowerFlowReferenceType.transformer_short_circuit_loss_kw] = xfmr_sc_loss_kw
templ.block.api_obj_mapping[ParamPowerFlowReferenceType.tap_module] = xfmr_tap_module
# ------------------------------------------------------------------
# Mapped winding-connection matrices.
# ------------------------------------------------------------------
cf_enums: list[list[ParamPowerFlowReferenceType]] = [
[
ParamPowerFlowReferenceType.transformer_from_connection_aa,
ParamPowerFlowReferenceType.transformer_from_connection_ab,
ParamPowerFlowReferenceType.transformer_from_connection_ac,
],
[
ParamPowerFlowReferenceType.transformer_from_connection_ba,
ParamPowerFlowReferenceType.transformer_from_connection_bb,
ParamPowerFlowReferenceType.transformer_from_connection_bc,
],
[
ParamPowerFlowReferenceType.transformer_from_connection_ca,
ParamPowerFlowReferenceType.transformer_from_connection_cb,
ParamPowerFlowReferenceType.transformer_from_connection_cc,
],
]
ct_enums: list[list[ParamPowerFlowReferenceType]] = [
[
ParamPowerFlowReferenceType.transformer_to_connection_aa,
ParamPowerFlowReferenceType.transformer_to_connection_ab,
ParamPowerFlowReferenceType.transformer_to_connection_ac,
],
[
ParamPowerFlowReferenceType.transformer_to_connection_ba,
ParamPowerFlowReferenceType.transformer_to_connection_bb,
ParamPowerFlowReferenceType.transformer_to_connection_bc,
],
[
ParamPowerFlowReferenceType.transformer_to_connection_ca,
ParamPowerFlowReferenceType.transformer_to_connection_cb,
ParamPowerFlowReferenceType.transformer_to_connection_cc,
],
]
c_f_expr: list[list[Expr]] = []
c_t_expr: list[list[Expr]] = []
for i in range(3):
c_f_row: list[Expr] = []
c_t_row: list[Expr] = []
for j in range(3):
c_f_var: Var = vf.add_var(name=f"xfmr_cf_{i}_{j}_{name}")
c_t_var: Var = vf.add_var(name=f"xfmr_ct_{i}_{j}_{name}")
templ.block.api_obj_mapping[cf_enums[i][j]] = c_f_var
templ.block.api_obj_mapping[ct_enums[i][j]] = c_t_var
c_f_row.append(c_f_var)
c_t_row.append(c_t_var)
c_f_expr.append(c_f_row)
c_t_expr.append(c_t_row)
# ------------------------------------------------------------------
# Local dynamic-model parameters.
# ------------------------------------------------------------------
xfmr_core_topology_code: Var = vf.add_var(name=f"xfmr_core_topology_code_{name}")
xfmr_yoke_area_rel: Var = vf.add_var(name=f"xfmr_yoke_area_rel_{name}")
xfmr_yoke_length_rel: Var = vf.add_var(name=f"xfmr_yoke_length_rel_{name}")
xfmr_outer_leg_area_rel: Var = vf.add_var(name=f"xfmr_outer_leg_area_rel_{name}")
xfmr_outer_leg_length_rel: Var = vf.add_var(name=f"xfmr_outer_leg_length_rel_{name}")
xfmr_c_term: Var = vf.add_var(name=f"xfmr_c_term_{name}")
xfmr_use_linear_core: Var = vf.add_var(name=f"xfmr_use_linear_core_{name}")
xfmr_core_knee_flux_mult: Var = vf.add_var(name=f"xfmr_core_knee_flux_mult_{name}")
xfmr_core_knee_current_mult: Var = vf.add_var(name=f"xfmr_core_knee_current_mult_{name}")
xfmr_sc_resistance_pct: Var = vf.add_var(name=f"xfmr_sc_resistance_pct_{name}")
xfmr_core_linear_l_pu: Var = vf.add_var(name=f"xfmr_core_linear_l_pu_{name}")
xfmr_core_a_prime: Var = vf.add_var(name=f"xfmr_core_a_prime_{name}")
xfmr_core_b_prime: Var = vf.add_var(name=f"xfmr_core_b_prime_{name}")
templ.block.event_dict[xfmr_core_topology_code] = vf.add_const(3.0)
templ.block.event_dict[xfmr_yoke_area_rel] = vf.add_const(1.0)
templ.block.event_dict[xfmr_yoke_length_rel] = vf.add_const(1.0)
templ.block.event_dict[xfmr_outer_leg_area_rel] = vf.add_const(1.0)
templ.block.event_dict[xfmr_outer_leg_length_rel] = vf.add_const(1.0)
templ.block.event_dict[xfmr_c_term] = vf.add_const(1e-6)
templ.block.event_dict[xfmr_use_linear_core] = vf.add_const(1.0)
templ.block.event_dict[xfmr_core_knee_flux_mult] = vf.add_const(1.50)
templ.block.event_dict[xfmr_core_knee_current_mult] = vf.add_const(8.0)
# ------------------------------------------------------------------
# Derived local parameters of the dynamic model.
# ------------------------------------------------------------------
templ.block.event_dict[xfmr_sc_resistance_pct] = xfmr_sc_loss_kw / (vf.add_const(10.0) * xfmr_s_rated_mva + c_eps)
oc_loss_pu: Expr = (xfmr_oc_loss_kw / vf.add_const(1000.0)) / (xfmr_s_rated_mva + c_eps)
oc_current_pu: Expr = xfmr_oc_current_pct / vf.add_const(100.0)
i_mag_pu: Expr = sym.sqrt(oc_current_pu * oc_current_pu - oc_loss_pu * oc_loss_pu + c_eps)
templ.block.event_dict[xfmr_core_linear_l_pu] = c1 / (i_mag_pu + c_eps)
lambda_nom_peak: Expr = c_sqrt2
i_nom_peak_from_linear: Expr = lambda_nom_peak / (xfmr_core_linear_l_pu + c_eps)
a_prime_auto: Expr = c1 / (xfmr_core_linear_l_pu + c_eps)
lambda_knee: Expr = xfmr_core_knee_flux_mult * lambda_nom_peak
i_knee: Expr = xfmr_core_knee_current_mult * i_nom_peak_from_linear
b_prime_raw: Expr = (i_knee / (lambda_knee + c_eps) - a_prime_auto) / (i_knee + c_eps)
b_prime_auto: Expr = b_prime_raw * b_prime_raw / (sym.sqrt(b_prime_raw * b_prime_raw + c_smooth) + c_eps)
templ.block.event_dict[xfmr_core_a_prime] = a_prime_auto
templ.block.event_dict[xfmr_core_b_prime] = b_prime_auto
# ------------------------------------------------------------------
# Leakage-branch parameters.
# ------------------------------------------------------------------
z_sc_pu: Expr = xfmr_sc_voltage_pct / vf.add_const(100.0)
r_sc_pu: Expr = xfmr_sc_resistance_pct / vf.add_const(100.0)
x_sc_pu: Expr = sym.sqrt(z_sc_pu * z_sc_pu - r_sc_pu * r_sc_pu + c_eps)
l_sigma: Expr = x_sc_pu / (omega_base + c_eps)
l_inv: Expr = c1 / (l_sigma + c_eps)
# ------------------------------------------------------------------
# Core-loss conductance from open-circuit losses.
# ------------------------------------------------------------------
g_core_total: Expr = (xfmr_oc_loss_kw / vf.add_const(1000.0)) / (xfmr_s_rated_mva + c_eps)
g_core_leg: Expr = g_core_total / c3
# ------------------------------------------------------------------
# Terminal electrical interface variables in abc frame.
# ------------------------------------------------------------------
vf_keys: Dict[str, VarPowerFlowReferenceType] = {
"N": VarPowerFlowReferenceType.vf_N,
"A": VarPowerFlowReferenceType.vf_A,
"B": VarPowerFlowReferenceType.vf_B,
"C": VarPowerFlowReferenceType.vf_C,
}
vt_keys: Dict[str, VarPowerFlowReferenceType] = {
"N": VarPowerFlowReferenceType.vt_N,
"A": VarPowerFlowReferenceType.vt_A,
"B": VarPowerFlowReferenceType.vt_B,
"C": VarPowerFlowReferenceType.vt_C,
}
v_f_n: Var | None = None
v_t_n: Var | None = None
if from_has_neutral_port:
v_f_n = vf.add_var(name=f"vf_N_{name}", reference=vf_keys["N"])
else:
pass
if to_has_neutral_port:
v_t_n = vf.add_var(name=f"vt_N_{name}", reference=vt_keys["N"])
else:
pass
v_f: list[Var] = [
vf.add_var(name=f"vf_A_{name}", reference=vf_keys["A"]),
vf.add_var(name=f"vf_B_{name}", reference=vf_keys["B"]),
vf.add_var(name=f"vf_C_{name}", reference=vf_keys["C"]),
]
v_t: list[Var] = [
vf.add_var(name=f"vt_A_{name}", reference=vt_keys["A"]),
vf.add_var(name=f"vt_B_{name}", reference=vt_keys["B"]),
vf.add_var(name=f"vt_C_{name}", reference=vt_keys["C"]),
]
# ------------------------------------------------------------------
# Dynamic states and derivatives.
# ------------------------------------------------------------------
i_leak: list[Var] = [vf.add_var(name=f"i_leak_{ph}_{name}") for ph in ("A", "B", "C")]
lam_leg: list[Var] = [vf.add_var(name=f"lam_leg_{ph}_{name}") for ph in ("A", "B", "C")]
q_f: list[Var] = [vf.add_var(name=f"qf_{ph}_{name}") for ph in ("A", "B", "C")]
q_t: list[Var] = [vf.add_var(name=f"qt_{ph}_{name}") for ph in ("A", "B", "C")]
di_leak: list[Var] = [vf.add_diff_var(name=f"di_leak_{ph}_{name}", base_var=i_leak[k]) for k, ph in enumerate(("A", "B", "C"))]
dlam_leg: list[Var] = [vf.add_diff_var(name=f"dlam_leg_{ph}_{name}", base_var=lam_leg[k]) for k, ph in enumerate(("A", "B", "C"))]
dq_f: list[Var] = [vf.add_diff_var(name=f"dqf_{ph}_{name}", base_var=q_f[k]) for k, ph in enumerate(("A", "B", "C"))]
dq_t: list[Var] = [vf.add_diff_var(name=f"dqt_{ph}_{name}", base_var=q_t[k]) for k, ph in enumerate(("A", "B", "C"))]
# ------------------------------------------------------------------
# Algebraic variables.
# ------------------------------------------------------------------
i_leg_core: list[Var] = [vf.add_var(name=f"i_leg_core_{ph}_{name}") for ph in ("A", "B", "C")]
i_mag: list[Var] = [vf.add_var(name=f"i_mag_{ph}_{name}") for ph in ("A", "B", "C")]
i_loss_leg: list[Var] = [vf.add_var(name=f"i_loss_leg_{ph}_{name}") for ph in ("A", "B", "C")]
i_cap_f: list[Var] = [vf.add_var(name=f"i_cap_f_{ph}_{name}") for ph in ("A", "B", "C")]
i_cap_t: list[Var] = [vf.add_var(name=f"i_cap_t_{ph}_{name}") for ph in ("A", "B", "C")]
if_act: list[Var] = [vf.add_var(name=f"if_{ph}_{name}") for ph in ("A", "B", "C")]
it_act: list[Var] = [vf.add_var(name=f"it_{ph}_{name}") for ph in ("A", "B", "C")]
if_n_act: Var | None = vf.add_var(name=f"if_N_{name}") if from_has_neutral_port else None
it_n_act: Var | None = vf.add_var(name=f"it_N_{name}") if to_has_neutral_port else None
from_ground_current: Var | None = None
to_ground_current: Var | None = None
from_grounding_link_block: Block | None = None
to_grounding_link_block: Block | None = None
i_return_path: Var = vf.add_var(name=f"i_return_path_{name}")
i_return_total: Var = vf.add_var(name=f"i_return_total_{name}")
input_vars: list[Var] = list()
if v_f_n is not None:
input_vars.append(v_f_n)
else:
pass
input_vars.extend(v_f)
if v_t_n is not None:
input_vars.append(v_t_n)
else:
pass
input_vars.extend(v_t)
if conn_f == WindingType.GroundedStar and v_f_n is not None:
from_grounding_link_template: EmtModelTemplate = get_grounding_link_emt_template(
vf=vf,
include_r=False,
include_l=False,
include_c=False,
solid_connection=True,
nested=True,
name=name + "_from_grounding_link",
)
vf.add_connections(from_grounding_link_template.block.in_vars, [v_f_n])
from_ground_current = from_grounding_link_template.block.out_vars[0]
from_grounding_link_block = from_grounding_link_template.block
templ.block.add(from_grounding_link_block)
else:
pass
if conn_t == WindingType.GroundedStar and v_t_n is not None:
to_grounding_link_template: EmtModelTemplate = get_grounding_link_emt_template(
vf=vf,
include_r=False,
include_l=False,
include_c=False,
solid_connection=True,
nested=True,
name=name + "_to_grounding_link",
)
vf.add_connections(to_grounding_link_template.block.in_vars, [v_t_n])
to_ground_current = to_grounding_link_template.block.out_vars[0]
to_grounding_link_block = to_grounding_link_template.block
templ.block.add(to_grounding_link_block)
else:
pass
algebraic_vars: list[Var] = (
i_leg_core
+ [i_return_path, i_return_total]
+ i_mag
+ i_loss_leg
+ i_cap_f
+ i_cap_t
+ if_act
+ it_act
)
if if_n_act is not None:
algebraic_vars.append(if_n_act)
else:
pass
if it_n_act is not None:
algebraic_vars.append(it_n_act)
else:
pass
templ.block.in_vars = input_vars
templ.block.state_vars = i_leak + lam_leg + q_f + q_t
templ.block.diff_vars = di_leak + dlam_leg + dq_f + dq_t
templ.block.algebraic_vars = algebraic_vars
v_f_term: list[Expr] = list()
v_t_term: list[Expr] = list()
k: int
for k in range(3):
if v_f_n is not None:
v_f_term.append(v_f[k] - v_f_n)
else:
v_f_term.append(v_f[k])
if v_t_n is not None:
v_t_term.append(v_t[k] - v_t_n)
else:
v_t_term.append(v_t[k])
# ------------------------------------------------------------------
# Voltage transformation from terminal abc frame to winding frame.
# ------------------------------------------------------------------
c_f_expr_t: list[list[Expr]] = [[c_f_expr[j][i] for j in range(3)] for i in range(3)]
c_t_expr_t: list[list[Expr]] = [[c_t_expr[j][i] for j in range(3)] for i in range(3)]
v_f_w: list[Expr] = []
v_t_w: list[Expr] = []
for i in range(3):
expr_vfw: Expr = c0
expr_vtw: Expr = c0
for j in range(3):
expr_vfw = expr_vfw + c_f_expr_t[i][j] * v_f_term[j]
expr_vtw = expr_vtw + c_t_expr_t[i][j] * v_t_term[j]
v_f_w.append(expr_vfw)
v_t_w.append(expr_vtw)
# ------------------------------------------------------------------
# Internal winding-side voltages.
#
# Leakage branch:
# v_leak = v_f_w - n_tap * v_t_w
#
# Core branch:
# referred only to the from side
# ------------------------------------------------------------------
n_tap: Expr = xfmr_tap_module
v_leak: list[Expr] = [v_f_w[k] - n_tap * v_t_w[k] for k in range(3)]
v_core_w: list[Expr] = [v_f_w[k] for k in range(3)]
# ------------------------------------------------------------------
# State equations.
# ------------------------------------------------------------------
state_eqs: list[Expr] = []
# Leakage-current dynamics.
for k in range(3):
state_eqs.append(l_inv * (v_leak[k] - r_sc_pu * i_leak[k]))
# Core-flux dynamics referred to the from side.
for k in range(3):
state_eqs.append(omega_base * v_core_w[k])
# Charge dynamics of terminal capacitances.
for k in range(3):
state_eqs.append(i_cap_f[k])
for k in range(3):
state_eqs.append(i_cap_t[k])
templ.block.state_eqs = state_eqs
# ------------------------------------------------------------------
# Core topology selectors.
#
# Supported values:
# 3.0 -> three-legged core
# 5.0 -> five-legged core
# ------------------------------------------------------------------
lam_return_total: Expr = -(lam_leg[0] + lam_leg[1] + lam_leg[2])
five_leg_selector: Expr = (xfmr_core_topology_code - c3) / c2
three_leg_selector: Expr = c1 - five_leg_selector
yoke_area_rel: Expr = xfmr_yoke_area_rel
yoke_length_rel: Expr = xfmr_yoke_length_rel
outer_leg_area_rel: Expr = xfmr_outer_leg_area_rel
outer_leg_length_rel: Expr = xfmr_outer_leg_length_rel
yoke_linear_coeff: Expr = xfmr_core_linear_l_pu * yoke_area_rel / (yoke_length_rel + c_eps)
outer_linear_coeff: Expr = xfmr_core_linear_l_pu * outer_leg_area_rel / (outer_leg_length_rel + c_eps)
yoke_frolich_lam: Expr = yoke_area_rel * i_return_path / (
xfmr_core_a_prime * yoke_length_rel
+ xfmr_core_b_prime * sym.sqrt(i_return_path * i_return_path + c_smooth)
+ c_eps
)
outer_frolich_lam: Expr = outer_leg_area_rel * i_return_path / (
xfmr_core_a_prime * outer_leg_length_rel
+ xfmr_core_b_prime * sym.sqrt(i_return_path * i_return_path + c_smooth)
+ c_eps
)
# ------------------------------------------------------------------
# Projection of winding-frame currents to terminal-frame currents.
# ------------------------------------------------------------------
i_series_from: list[Expr] = _project_currents(c_f_expr, i_leak, c0)
i_series_to: list[Expr] = _project_currents(c_t_expr, i_leak, c0)
# Core branch referred only to the from side.
i_core_from_w: list[Expr] = [i_mag[k] + i_loss_leg[k] for k in range(3)]
i_core_to_w: list[Expr] = [c0, c0, c0]
i_core_from_term: list[Expr] = _project_currents(c_f_expr, i_core_from_w, c0)
i_core_to_term: list[Expr] = _project_currents(c_t_expr, i_core_to_w, c0)
# ------------------------------------------------------------------
# Algebraic equations.
# ------------------------------------------------------------------
alg_eqs: list[Expr] = []
# Leg constitutive laws.
for k in range(3):
linear_leg_eq: Expr = lam_leg[k] - xfmr_core_linear_l_pu * i_leg_core[k]
nonlinear_leg_eq: Expr = lam_leg[k] - i_leg_core[k] / (
xfmr_core_a_prime
+ xfmr_core_b_prime * sym.sqrt(i_leg_core[k] * i_leg_core[k] + c_smooth)
+ c_eps
)
alg_eqs.append(
xfmr_use_linear_core * linear_leg_eq
+ (c1 - xfmr_use_linear_core) * nonlinear_leg_eq
)
# Return-path constitutive law.
five_leg_eq_linear: Expr = c0
five_leg_eq_linear = c0
five_leg_eq_linear = (c1 / c2) * lam_return_total - (
c2 * yoke_linear_coeff * i_return_path + outer_linear_coeff * i_return_path
)
five_leg_eq_nonlinear: Expr = (c1 / c2) * lam_return_total - (
c2 * yoke_frolich_lam + outer_frolich_lam
)
three_leg_eq_linear: Expr = lam_return_total - c2 * yoke_linear_coeff * i_return_path
three_leg_eq_nonlinear: Expr = lam_return_total - c2 * yoke_frolich_lam
alg_eqs.append(
five_leg_selector * (
xfmr_use_linear_core * five_leg_eq_linear
+ (c1 - xfmr_use_linear_core) * five_leg_eq_nonlinear
)
+ three_leg_selector * (
xfmr_use_linear_core * three_leg_eq_linear
+ (c1 - xfmr_use_linear_core) * three_leg_eq_nonlinear
)
)
alg_eqs.append(
i_return_total - (
five_leg_selector * c2 * i_return_path
+ three_leg_selector * i_return_path
)
)
# Magnetizing currents.
for k in range(3):
alg_eqs.append(i_mag[k] - (i_leg_core[k] - i_return_total))
# Core-loss currents referred only to the from side.
for k in range(3):
alg_eqs.append(i_loss_leg[k] - g_core_leg * v_f_w[k])
# Charge-voltage relations of terminal capacitances.
for k in range(3):
alg_eqs.append(q_f[k] - xfmr_c_term * v_f_term[k])
alg_eqs.append(q_t[k] - xfmr_c_term * v_t_term[k])
# Terminal current assembly.
for k in range(3):
alg_eqs.append(
if_act[k] - (
i_series_from[k]
+ i_core_from_term[k]
+ i_cap_f[k]
)
)
alg_eqs.append(
it_act[k] - (
-n_tap * i_series_to[k]
+ i_cap_t[k]
)
)
if if_n_act is not None:
neutral_from_kcl: Expr = if_n_act + if_act[0] + if_act[1] + if_act[2]
if from_ground_current is not None:
neutral_from_kcl = neutral_from_kcl + from_ground_current
else:
pass
alg_eqs.append(neutral_from_kcl)
else:
pass
if it_n_act is not None:
neutral_to_kcl: Expr = it_n_act + it_act[0] + it_act[1] + it_act[2]
if to_ground_current is not None:
neutral_to_kcl = neutral_to_kcl + to_ground_current
else:
pass
alg_eqs.append(neutral_to_kcl)
else:
pass
templ.block.algebraic_eqs = alg_eqs
output_vars: list[Var] = list()
if if_n_act is not None:
output_vars.append(if_n_act)
else:
pass
output_vars.extend(if_act)
if it_n_act is not None:
output_vars.append(it_n_act)
else:
pass
output_vars.extend(it_act)
output_vars.extend(i_mag)
templ.block.out_vars = output_vars
# ------------------------------------------------------------------
# External mapping.
# ------------------------------------------------------------------
if_keys: Dict[str, VarPowerFlowReferenceType] = {
"N": VarPowerFlowReferenceType.if_N,
"A": VarPowerFlowReferenceType.if_A,
"B": VarPowerFlowReferenceType.if_B,
"C": VarPowerFlowReferenceType.if_C,
}
it_keys: Dict[str, VarPowerFlowReferenceType] = {
"N": VarPowerFlowReferenceType.it_N,
"A": VarPowerFlowReferenceType.it_A,
"B": VarPowerFlowReferenceType.it_B,
"C": VarPowerFlowReferenceType.it_C,
}
Sf_keys: Dict[str, VarPowerFlowReferenceType] = {
"A": VarPowerFlowReferenceType.Sf_A,
"B": VarPowerFlowReferenceType.Sf_B,
"C": VarPowerFlowReferenceType.Sf_C,
}
St_keys: Dict[str, VarPowerFlowReferenceType] = {
"A": VarPowerFlowReferenceType.St_A,
"B": VarPowerFlowReferenceType.St_B,
"C": VarPowerFlowReferenceType.St_C,
}
d_vf_keys: Dict[str, VarPowerFlowReferenceType] = {
"N": VarPowerFlowReferenceType.d_v_N_f,
"A": VarPowerFlowReferenceType.d_v_A_f,
"B": VarPowerFlowReferenceType.d_v_B_f,
"C": VarPowerFlowReferenceType.d_v_C_f,
}
d_vt_keys: Dict[str, VarPowerFlowReferenceType] = {
"N": VarPowerFlowReferenceType.d_v_N_t,
"A": VarPowerFlowReferenceType.d_v_A_t,
"B": VarPowerFlowReferenceType.d_v_B_t,
"C": VarPowerFlowReferenceType.d_v_C_t,
}
mapping: Dict[VarPowerFlowReferenceType, Var | None] = {
if_keys["N"]: None,
if_keys["A"]: None,
if_keys["B"]: None,
if_keys["C"]: None,
it_keys["N"]: None,
it_keys["A"]: None,
it_keys["B"]: None,
it_keys["C"]: None,
vf_keys["N"]: None,
vf_keys["A"]: None,
vf_keys["B"]: None,
vf_keys["C"]: None,
vt_keys["N"]: None,
vt_keys["A"]: None,
vt_keys["B"]: None,
vt_keys["C"]: None,
}
mapping[vf_keys["N"]] = v_f_n
mapping[vf_keys["A"]] = v_f[0]
mapping[vf_keys["B"]] = v_f[1]
mapping[vf_keys["C"]] = v_f[2]
mapping[vt_keys["N"]] = v_t_n
mapping[vt_keys["A"]] = v_t[0]
mapping[vt_keys["B"]] = v_t[1]
mapping[vt_keys["C"]] = v_t[2]
mapping[if_keys["N"]] = if_n_act
mapping[if_keys["A"]] = if_act[0]
mapping[if_keys["B"]] = if_act[1]
mapping[if_keys["C"]] = if_act[2]
mapping[it_keys["N"]] = it_n_act
mapping[it_keys["A"]] = it_act[0]
mapping[it_keys["B"]] = it_act[1]
mapping[it_keys["C"]] = it_act[2]
templ.block.external_mapping = mapping
# ------------------------------------------------------------------
# Initialization equations.
#
# PF-derived terminal currents if_act / it_act are initialized before
# init_eqs through external_mapping. The magnetic branch is initialized
# from the from-side winding voltage only.
# ------------------------------------------------------------------
c_inv_sqrt3: Expr = c1 / sym.sqrt(c3)
lam_leg_init: list[Expr] = [
-(v_f_w[2] - v_f_w[1]) * c_inv_sqrt3,
-(v_f_w[0] - v_f_w[2]) * c_inv_sqrt3,
-(v_f_w[1] - v_f_w[0]) * c_inv_sqrt3,
]
init_eqs: Dict[Var, Expr] = {}
# Charge states.
for k in range(3):
init_eqs[q_f[k]] = xfmr_c_term * v_f_term[k]
init_eqs[q_t[k]] = xfmr_c_term * v_t_term[k]
# Core flux-linkage states from the from-side winding voltage set.
for k in range(3):
init_eqs[lam_leg[k]] = lam_leg_init[k]
# Core leg currents from the constitutive law.
# for k in range(3):
# init_eqs[i_leg_core[k]] = (
# xfmr_use_linear_core * (lam_leg[k] / (xfmr_core_linear_l_pu + c_eps))
# + (c1 - xfmr_use_linear_core) * (lam_leg[k] / (xfmr_core_a_prime + c_eps))
# )
for k in range(3):
abs_lam_k: Expr = sym.sqrt(lam_leg[k] * lam_leg[k] + c_smooth)
i_leg_core_nl_init: Expr = (
xfmr_core_a_prime * lam_leg[k]
/ (c1 - xfmr_core_b_prime * abs_lam_k + c_eps)
)
init_eqs[i_leg_core[k]] = (
xfmr_use_linear_core * (lam_leg[k] / (xfmr_core_linear_l_pu + c_eps))
+ (c1 - xfmr_use_linear_core) * i_leg_core_nl_init
)
# Return-path currents from initialized fluxes.
init_eqs[i_return_path] = (
five_leg_selector * (lam_return_total / (c2 * yoke_linear_coeff + outer_linear_coeff + c_eps))
+ three_leg_selector * (lam_return_total / (c2 * yoke_linear_coeff + c_eps))
)
init_eqs[i_return_total] = (
five_leg_selector * c2 * i_return_path
+ three_leg_selector * i_return_path
)
# Net magnetizing currents.
for k in range(3):
init_eqs[i_mag[k]] = i_leg_core[k] - i_return_total
# Core-loss currents referred to the from side.
for k in range(3):
init_eqs[i_loss_leg[k]] = g_core_leg * v_f_w[k]
# Leakage-current seed from PF-initialized from-side terminal current.
for k in range(3):
init_eqs[i_leak[k]] = if_act[k]
# Capacitor-current seeds from terminal current balance.
for k in range(3):
init_eqs[i_cap_f[k]] = if_act[k] - (i_series_from[k] + i_core_from_term[k])
init_eqs[i_cap_t[k]] = it_act[k] - (-n_tap * i_series_to[k])
templ.block.init_eqs = init_eqs
# ------------------------------------------------------------------
# Differential initialization equations.
# ------------------------------------------------------------------
diff_init_eqs: Dict[Var, Expr] = {}
for k in range(3):
diff_init_eqs[di_leak[k]] = l_inv * (v_leak[k] - r_sc_pu * i_leak[k])
diff_init_eqs[dlam_leg[k]] = omega_base * v_f_w[k]
diff_init_eqs[dq_f[k]] = i_cap_f[k]
diff_init_eqs[dq_t[k]] = i_cap_t[k]
templ.block.diff_init_eqs = diff_init_eqs
grounding_pairs: List[Tuple[Var, Block]] = list()
if v_f_n is not None and from_grounding_link_block is not None:
grounding_pairs.append((v_f_n, from_grounding_link_block))
else:
pass
if v_t_n is not None and to_grounding_link_block is not None:
grounding_pairs.append((v_t_n, to_grounding_link_block))
else:
pass
_attach_transformer_editor_diagram(
root_block=templ.block,
input_vars=input_vars,
output_vars=output_vars,
grounding_pairs=grounding_pairs,
)
return templ