Source code for VeraGridEngine.Templates.Emt.bridge_2level_3ph_emt_multilinear_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

from typing import Tuple

import numpy as np

import VeraGridEngine.Utils.Symbolic.symbolic as sym
from VeraGridEngine.Devices.Dynamic.emt_template import EmtModelTemplate
from VeraGridEngine.Devices.Dynamic.var_factory import VarFactory
from VeraGridEngine.Utils.Symbolic.block import Block
from VeraGridEngine.Utils.Symbolic.symbolic import Const, Expr, Var
from VeraGridEngine.Utils.procedural_logic import ThreePhaseCarrierPwmLogic
from VeraGridEngine.enumerations import DeviceType


def _inverse_dq0_to_abc_with_trig_identities(
    d_value: Expr,
    q_value: Expr,
    zero_value: Expr,
    u_cos: Expr,
    u_sin: Expr,
) -> Tuple[Expr, Expr, Expr]:
    """Return dq0->abc using one cosine/sine pair plus +/-120 identities."""
    c120 = Const(float(np.cos(2.0 * np.pi / 3.0)))
    s120 = Const(float(np.sin(2.0 * np.pi / 3.0)))
    sin_t_m120 = u_sin * c120 - u_cos * s120
    cos_t_m120 = u_cos * c120 + u_sin * s120
    sin_t_p120 = u_sin * c120 + u_cos * s120
    cos_t_p120 = u_cos * c120 - u_sin * s120

    a_value: Expr = d_value * u_sin - q_value * u_cos + zero_value
    b_value: Expr = d_value * sin_t_m120 - q_value * cos_t_m120 + zero_value
    c_value: Expr = d_value * sin_t_p120 - q_value * cos_t_p120 + zero_value
    return a_value, b_value, c_value


[docs] def get_bridge_2level_3ph_emt_multilinear_template(vf: VarFactory, name: str = "bridge_2level_3ph_emt_ml") -> EmtModelTemplate: """ Build one switched 2-level bridge EMT template with explicit trig states. This is a first-step multilinear bridge variant for the switched-converter path: - keeps the procedural PWM logic unchanged, - keeps the electrical equations/layout unchanged, - replaces direct trig usage around ``theta_pll`` with explicit dynamic ``u_cos/u_sin`` states and +/-120 identities. """ templ: EmtModelTemplate = EmtModelTemplate() templ.tpe = DeviceType.DynamicModelHostDevice templ.name = name templ.block.name = name theta_pll: Var = vf.add_var(name=f"theta_pll_in_{name}") omega_base: Var = vf.add_var(name=f"omega_base_in_{name}") v_cmd_d: Var = vf.add_var(name=f"v_cmd_d_in_{name}") v_cmd_q: Var = vf.add_var(name=f"v_cmd_q_in_{name}") v_cmd_0: Var = vf.add_var(name=f"v_cmd_0_in_{name}") v_dc: Var = vf.add_var(name=f"v_dc_in_{name}") k_v_conv: Var = vf.add_var(name=f"k_v_conv_in_{name}") m_max: Var = vf.add_var(name=f"m_max_in_{name}") vdc_floor: Var = vf.add_var(name=f"vdc_floor_in_{name}") omega_sw: Var = vf.add_var(name=f"omega_sw_in_{name}") carrier_phase: Var = vf.add_var(name=f"carrier_phase_in_{name}") u_cos_pll: Var = vf.add_var(name=f"u_cos_pll_{name}") u_sin_pll: Var = vf.add_var(name=f"u_sin_pll_{name}") d_u_cos_pll: Var = vf.add_diff_var(name=f"d_u_cos_pll_{name}", base_var=u_cos_pll) d_u_sin_pll: Var = vf.add_diff_var(name=f"d_u_sin_pll_{name}", base_var=u_sin_pll) u_cos_pwm: Var = vf.add_var(name=f"u_cos_pwm_{name}") u_sin_pwm: Var = vf.add_var(name=f"u_sin_pwm_{name}") d_u_cos_pwm: Var = vf.add_diff_var(name=f"d_u_cos_pwm_{name}", base_var=u_cos_pwm) d_u_sin_pwm: Var = vf.add_diff_var(name=f"d_u_sin_pwm_{name}", base_var=u_sin_pwm) v_ref_a_raw: Var = vf.add_var(name=f"v_ref_a_raw_{name}") v_ref_b_raw: Var = vf.add_var(name=f"v_ref_b_raw_{name}") v_ref_c_raw: Var = vf.add_var(name=f"v_ref_c_raw_{name}") v_ref_common_inj: Var = vf.add_var(name=f"v_ref_common_inj_{name}") v_ref_a: Var = vf.add_var(name=f"v_ref_a_{name}") v_ref_b: Var = vf.add_var(name=f"v_ref_b_{name}") v_ref_c: Var = vf.add_var(name=f"v_ref_c_{name}") theta_pwm_sample: Var = vf.add_var(name=f"theta_pwm_sample_{name}") v_ref_a_pwm: Var = vf.add_var(name=f"v_ref_a_pwm_{name}") v_ref_b_pwm: Var = vf.add_var(name=f"v_ref_b_pwm_{name}") v_ref_c_pwm: Var = vf.add_var(name=f"v_ref_c_pwm_{name}") m_a_u: Var = vf.add_var(name=f"m_a_u_{name}") m_b_u: Var = vf.add_var(name=f"m_b_u_{name}") m_c_u: Var = vf.add_var(name=f"m_c_u_{name}") m_a: Var = vf.add_var(name=f"m_a_{name}") m_b: Var = vf.add_var(name=f"m_b_{name}") m_c: Var = vf.add_var(name=f"m_c_{name}") gate_a_mode: Var = vf.add_var(name=f"gate_a_mode_{name}") gate_b_mode: Var = vf.add_var(name=f"gate_b_mode_{name}") gate_c_mode: Var = vf.add_var(name=f"gate_c_mode_{name}") gate_a: Var = vf.add_var(name=f"gate_a_{name}") gate_b: Var = vf.add_var(name=f"gate_b_{name}") gate_c: Var = vf.add_var(name=f"gate_c_{name}") v_p: Var = vf.add_var(name=f"v_p_{name}") v_n: Var = vf.add_var(name=f"v_n_{name}") v_leg_a: Var = vf.add_var(name=f"v_leg_a_{name}") v_leg_b: Var = vf.add_var(name=f"v_leg_b_{name}") v_leg_c: Var = vf.add_var(name=f"v_leg_c_{name}") v_common_mode: Var = vf.add_var(name=f"v_common_mode_{name}") v_conv_a: Var = vf.add_var(name=f"v_conv_a_{name}") v_conv_b: Var = vf.add_var(name=f"v_conv_b_{name}") v_conv_c: Var = vf.add_var(name=f"v_conv_c_{name}") v_conv_d: Var = vf.add_var(name=f"v_conv_d_{name}") v_conv_q: Var = vf.add_var(name=f"v_conv_q_{name}") v_conv_0: Var = vf.add_var(name=f"v_conv_0_{name}") eps: Const = Const(1.0e-10) c_half: Const = Const(0.5) c_one: Const = Const(1.0) c_two: Const = Const(2.0) c_three: Const = Const(3.0) c_pi: Const = Const(np.pi) c13: Const = Const(1.0 / 3.0) c23: Const = Const(2.0 / 3.0) c120: Const = Const(float(np.cos(2.0 * np.pi / 3.0))) s120: Const = Const(float(np.sin(2.0 * np.pi / 3.0))) v_dc_eff: Expr = sym.max(v_dc, vdc_floor) v_mod_scale: Expr = sym.max(k_v_conv * v_dc_eff, eps) v_leg_scale: Expr = k_v_conv * v_dc_eff pwm_sample_enable: Expr = omega_sw / sym.max(omega_sw, eps) pwm_sample_phase_lead: Expr = pwm_sample_enable * omega_base * c_pi / (c_two * sym.max(omega_sw, eps)) theta_pwm_sample_expr: Expr = theta_pll + pwm_sample_phase_lead v_ref_a_raw_expr, v_ref_b_raw_expr, v_ref_c_raw_expr = _inverse_dq0_to_abc_with_trig_identities( d_value=v_cmd_d, q_value=v_cmd_q, zero_value=v_cmd_0, u_cos=u_cos_pll, u_sin=u_sin_pll, ) sin_pwm_m120 = u_sin_pwm * c120 - u_cos_pwm * s120 cos_pwm_m120 = u_cos_pwm * c120 + u_sin_pwm * s120 sin_pwm_p120 = u_sin_pwm * c120 + u_cos_pwm * s120 cos_pwm_p120 = u_cos_pwm * c120 - u_sin_pwm * s120 v_ref_a_pwm_raw_expr: Expr = v_cmd_d * u_sin_pwm - v_cmd_q * u_cos_pwm + v_cmd_0 v_ref_b_pwm_raw_expr: Expr = v_cmd_d * sin_pwm_m120 - v_cmd_q * cos_pwm_m120 + v_cmd_0 v_ref_c_pwm_raw_expr: Expr = v_cmd_d * sin_pwm_p120 - v_cmd_q * cos_pwm_p120 + v_cmd_0 v_ref_common_inj_expr = Const(0.5) * ( sym.max(sym.max(v_ref_a_raw_expr, v_ref_b_raw_expr), v_ref_c_raw_expr) + sym.min(sym.min(v_ref_a_raw_expr, v_ref_b_raw_expr), v_ref_c_raw_expr) ) v_ref_common_inj_pwm_expr = Const(0.5) * ( sym.max(sym.max(v_ref_a_pwm_raw_expr, v_ref_b_pwm_raw_expr), v_ref_c_pwm_raw_expr) + sym.min(sym.min(v_ref_a_pwm_raw_expr, v_ref_b_pwm_raw_expr), v_ref_c_pwm_raw_expr) ) v_ref_a_expr = v_ref_a_raw_expr - v_ref_common_inj_expr v_ref_b_expr = v_ref_b_raw_expr - v_ref_common_inj_expr v_ref_c_expr = v_ref_c_raw_expr - v_ref_common_inj_expr v_ref_a_pwm_expr = v_ref_a_pwm_raw_expr - v_ref_common_inj_pwm_expr v_ref_b_pwm_expr = v_ref_b_pwm_raw_expr - v_ref_common_inj_pwm_expr v_ref_c_pwm_expr = v_ref_c_pwm_raw_expr - v_ref_common_inj_pwm_expr m_a_u_expr: Expr = v_ref_a_pwm_expr / v_mod_scale m_b_u_expr: Expr = v_ref_b_pwm_expr / v_mod_scale m_c_u_expr: Expr = v_ref_c_pwm_expr / v_mod_scale m_a_expr: Expr = sym.hard_sat(m_a_u_expr, -m_max, m_max) m_b_expr: Expr = sym.hard_sat(m_b_u_expr, -m_max, m_max) m_c_expr: Expr = sym.hard_sat(m_c_u_expr, -m_max, m_max) v_p_expr: Expr = c_half * v_dc_eff v_n_expr: Expr = -c_half * v_dc_eff v_leg_a_expr: Expr = (c_two * gate_a_mode - c_one) * v_leg_scale v_leg_b_expr: Expr = (c_two * gate_b_mode - c_one) * v_leg_scale v_leg_c_expr: Expr = (c_two * gate_c_mode - c_one) * v_leg_scale v_common_mode_expr: Expr = (v_leg_a_expr + v_leg_b_expr + v_leg_c_expr) / c_three v_conv_a_expr: Expr = v_leg_a_expr - v_common_mode_expr v_conv_b_expr: Expr = v_leg_b_expr - v_common_mode_expr v_conv_c_expr: Expr = v_leg_c_expr - v_common_mode_expr sin_t_m120 = u_sin_pll * c120 - u_cos_pll * s120 cos_t_m120 = u_cos_pll * c120 + u_sin_pll * s120 sin_t_p120 = u_sin_pll * c120 + u_cos_pll * s120 cos_t_p120 = u_cos_pll * c120 - u_sin_pll * s120 v_conv_d_expr: Expr = c23 * (u_sin_pll * v_conv_a_expr + sin_t_m120 * v_conv_b_expr + sin_t_p120 * v_conv_c_expr) v_conv_q_expr: Expr = -c23 * (u_cos_pll * v_conv_a_expr + cos_t_m120 * v_conv_b_expr + cos_t_p120 * v_conv_c_expr) v_conv_0_expr: Expr = c13 * (v_conv_a_expr + v_conv_b_expr + v_conv_c_expr) templ.block = Block( name=name, state_eqs=[ -(omega_base * u_sin_pll), omega_base * u_cos_pll, -(omega_base * u_sin_pwm), omega_base * u_cos_pwm, ], state_vars=[u_cos_pll, u_sin_pll, u_cos_pwm, u_sin_pwm], diff_vars=[d_u_cos_pll, d_u_sin_pll, d_u_cos_pwm, d_u_sin_pwm], algebraic_eqs=[ v_ref_a_raw - v_ref_a_raw_expr, v_ref_b_raw - v_ref_b_raw_expr, v_ref_c_raw - v_ref_c_raw_expr, v_ref_common_inj - v_ref_common_inj_expr, v_ref_a - v_ref_a_expr, v_ref_b - v_ref_b_expr, v_ref_c - v_ref_c_expr, theta_pwm_sample - theta_pwm_sample_expr, v_ref_a_pwm - v_ref_a_pwm_expr, v_ref_b_pwm - v_ref_b_pwm_expr, v_ref_c_pwm - v_ref_c_pwm_expr, m_a_u - m_a_u_expr, m_b_u - m_b_u_expr, m_c_u - m_c_u_expr, m_a - m_a_expr, m_b - m_b_expr, m_c - m_c_expr, v_p - v_p_expr, v_n - v_n_expr, gate_a - gate_a_mode, gate_b - gate_b_mode, gate_c - gate_c_mode, v_leg_a - v_leg_a_expr, v_leg_b - v_leg_b_expr, v_leg_c - v_leg_c_expr, v_common_mode - v_common_mode_expr, v_conv_a - v_conv_a_expr, v_conv_b - v_conv_b_expr, v_conv_c - v_conv_c_expr, v_conv_d - v_conv_d_expr, v_conv_q - v_conv_q_expr, v_conv_0 - v_conv_0_expr, ], algebraic_vars=[ v_ref_a_raw, v_ref_b_raw, v_ref_c_raw, v_ref_common_inj, v_ref_a, v_ref_b, v_ref_c, theta_pwm_sample, v_ref_a_pwm, v_ref_b_pwm, v_ref_c_pwm, m_a_u, m_b_u, m_c_u, m_a, m_b, m_c, v_p, v_n, gate_a, gate_b, gate_c, v_leg_a, v_leg_b, v_leg_c, v_common_mode, v_conv_a, v_conv_b, v_conv_c, v_conv_d, v_conv_q, v_conv_0, ], event_dict={ omega_sw: Const(2.0 * np.pi * 1000.0), carrier_phase: Const(0.0), }, mode_dict={ gate_a_mode: Const(0.0), gate_b_mode: Const(0.0), gate_c_mode: Const(0.0), }, init_eqs={ u_cos_pll: sym.cos(theta_pll), u_sin_pll: sym.sin(theta_pll), u_cos_pwm: sym.cos(theta_pwm_sample_expr), u_sin_pwm: sym.sin(theta_pwm_sample_expr), v_ref_a_raw: v_ref_a_raw_expr, v_ref_b_raw: v_ref_b_raw_expr, v_ref_c_raw: v_ref_c_raw_expr, v_ref_common_inj: v_ref_common_inj_expr, v_ref_a: v_ref_a_expr, v_ref_b: v_ref_b_expr, v_ref_c: v_ref_c_expr, theta_pwm_sample: theta_pwm_sample_expr, v_ref_a_pwm: v_ref_a_pwm_expr, v_ref_b_pwm: v_ref_b_pwm_expr, v_ref_c_pwm: v_ref_c_pwm_expr, m_a_u: m_a_u_expr, m_b_u: m_b_u_expr, m_c_u: m_c_u_expr, m_a: m_a_expr, m_b: m_b_expr, m_c: m_c_expr, v_p: v_p_expr, v_n: v_n_expr, gate_a: Const(0.0), gate_b: Const(0.0), gate_c: Const(0.0), v_leg_a: -v_leg_scale, v_leg_b: -v_leg_scale, v_leg_c: -v_leg_scale, v_common_mode: Const(0.0), v_conv_a: v_conv_a_expr, v_conv_b: v_conv_b_expr, v_conv_c: v_conv_c_expr, v_conv_d: v_conv_d_expr, v_conv_q: v_conv_q_expr, v_conv_0: v_conv_0_expr, }, diff_init_eqs={ d_u_cos_pll: -(omega_base * u_sin_pll), d_u_sin_pll: omega_base * u_cos_pll, d_u_cos_pwm: -(omega_base * u_sin_pwm), d_u_sin_pwm: omega_base * u_cos_pwm, }, in_vars=[theta_pll, omega_base, v_cmd_d, v_cmd_q, v_cmd_0, v_dc, k_v_conv, m_max, vdc_floor, omega_sw, carrier_phase], out_vars=[ gate_a, gate_b, gate_c, v_conv_a, v_conv_b, v_conv_c, m_a, m_b, m_c, v_conv_d, v_conv_q, v_conv_0, v_ref_a_raw, v_ref_b_raw, v_ref_c_raw, v_ref_common_inj, v_ref_a, v_ref_b, v_ref_c, m_a_u, m_b_u, m_c_u, ], procedural_logic=[ ThreePhaseCarrierPwmLogic( mod_a_var_name=m_a.name, mod_b_var_name=m_b.name, mod_c_var_name=m_c.name, gate_a_mode_var_name=gate_a_mode.name, gate_b_mode_var_name=gate_b_mode.name, gate_c_mode_var_name=gate_c_mode.name, omega_sw_var_name=omega_sw.name, carrier_phase_var_name=carrier_phase.name, name=f"three_phase_pwm_{name}", ), ], ) return templ