Source code for VeraGridEngine.Templates.Rms.hvdc_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
import math

from VeraGridEngine.Utils.Symbolic.block_helpers import tf_to_block
from VeraGridEngine.enumerations import DeviceType, VarPowerFlowReferenceType
from VeraGridEngine.Devices.Dynamic.rms_template import RmsModelTemplate
from VeraGridEngine.Devices.Dynamic.var_factory import VarFactory
from VeraGridEngine.Devices.Branches.hvdc_line import HvdcLine
from VeraGridEngine.Utils.Symbolic.block import Block
from VeraGridEngine.Utils.Symbolic.symbolic import sin, cos, sqrt
from VeraGridEngine.enumerations import HvdcControlType
from VeraGridEngine.DataStructures.numerical_circuit import NumericalCircuit

[docs] class HvdcRmsTemplate(RmsModelTemplate): """ RMS Template for HVDC Line with fixed power setpoint control. """ __slots__ = ( "tpe", "_block", ) def __init__(self, nc: NumericalCircuit, hvdc: HvdcLine, hvdc_idx: int, vf: VarFactory, name: str = "hvdc_rms_template"): """ Create the RMS Template of an HVDC Line :param nc: NumericalCircuit with compiled data :param hvdc: HVDC Line device :param hvdc_idx: Index of the HVDC in nc.hvdc_data :param vf: VarFactory for creating symbolic variables :param name: Name of the RMS Model """ super().__init__(name=name) self.tpe: DeviceType = DeviceType.HVDCLineDevice if hvdc.rms_model.empty(): # Create algebraic variables for power flow Pf = vf.add_var("Pf") Pt = vf.add_var("Pt") Qf = vf.add_var("Qf") Qt = vf.add_var("Qt") # Get voltage magnitude references from connected buses # These are symbolic variables linked to bus voltages via .E() Vmf = vf.add_var(f"Vmf_{hvdc.name}") Vaf = vf.add_var(f"Vaf_{hvdc.name}") Vmt = vf.add_var(f"Vmt_{hvdc.name}") Vat = vf.add_var(f"Vat_{hvdc.name}") #Parameters Ti = vf.add_var('Ti') # Time constant for control dynamics Tr = vf.add_var('Tr') # Time constant for control dynamics Ki = vf.add_var('Ki') # Time constant for control dynamics Kp = vf.add_var('Kp') # Time constant for control dynamics vdc_ref = vf.add_var('vdc_ref') # Time constant for control dynamics vac_ref = vf.add_var('vac_ref') # Time constant for control dynamics gamma_ref = vf.add_var('gamma_ref') # Time constant for control dynamics #We initialize parameters parameters={ vac_ref: vf.add_const(hvdc.Vset_f), vdc_ref: vf.add_const(hvdc.dc_link_voltage), gamma_ref: vf.add_const(1.0), Ti: vf.add_const(0.01), # Time constant for DC voltage control (s) Tr: vf.add_const(0.01), # Time constant for AC voltage control (s) Ki: vf.add_const(0.1), # Integral gain for PI controller Kp: vf.add_const(1.0), # Proportional gain for PI controller } #Internal HVDC states pi = math.pi phi_f = vf.add_var(f"phi_h_{hvdc.name}") #Phase for reactive power control alpha = vf.add_var(f"alpha_{hvdc.name}") # Firing angle for reactive power control gamma = vf.add_var(f"gamma_{hvdc.name}") # Firing angle for reactive power control phi_t = vf.add_var(f"phi_t_{hvdc.name}") #Phase for reactive power control ir_dc = vf.add_var(f"ir_dc_{hvdc.name}") # DC current variable k = vf.add_const(0.9995*3/pi)*sqrt(2) K_droop = vf.add_const(0.05) # Droop gain for frequency control x_r = vf.add_var(f"m_f_{hvdc.name}") # Tap for from side (rectifier control) x_i = vf.add_var(f"m_t_{hvdc.name}") # Tap for to side (inverter control) ii_dc = ir_dc m_f = x_r m_t = x_i r = vf.add_const(nc.hvdc_data.r[hvdc_idx]) vr_dc = k*m_f*Vmf*cos(phi_f) vi_dc = k*m_t*Vmt*cos(phi_t) P_loss = r*(Pf/Vmf)**2 block = Block( algebraic_vars=[Pf, Pt, Qf, Qt, gamma, phi_f, phi_t, ir_dc], algebraic_eqs=[ Pf - (-vr_dc*ir_dc), Qf - (-k*m_f*Vmf*ir_dc*sin(phi_f)), k*m_f*Vmt*(cos(alpha) - cos(phi_f)) - vf.add_const(3/pi)*x_r*ir_dc, Pt - (-vi_dc*ii_dc), Qt - (k*m_t*Vmt*ii_dc*sin(phi_t)), k*m_t*Vmt*(cos(pi-gamma) + cos(phi_t)) - vf.add_const(3/pi)*x_i*ii_dc, gamma - gamma_ref, Pf + P_loss - Pt, ], parameters=parameters, init_eqs={ phi_f: vf.add_const(pi/2), # Initial phase for reactive power control phi_t: vf.add_const(pi/2), # Initial phase for reactive power control alpha: vf.add_const(pi/2), # Initial firing angle gamma: vf.add_const(pi/2), # Initial firing angle ir_dc: vf.add_const(hvdc.Pset/hvdc.dc_link_voltage), # Initial DC current based on power setpoint and voltage gamma_ref: vf.add_const(1) # Initial reference for gamma (can be adjusted based on control mode) }, in_vars=[Vmf, Vaf, Vmt, Vat], ) if hvdc.control_mode == HvdcControlType.type_0_free: Pdc_ref = hvdc.Pset/nc.Sbase + K_droop*(Vaf - Vat) elif hvdc.control_mode == HvdcControlType.type_1_Pset: Pdc_ref = hvdc.Pset/nc.Sbase else: # Default to Pset mode Pdc_ref = hvdc.Pset/nc.Sbase idc_ref = Pdc_ref/vdc_ref control_block = Block( state_vars=[x_i, x_r], state_eqs=[ (vdc_ref - vi_dc)/Ti, # dx_i/dt: DC voltage control (inverter side) (Vmf - vac_ref)/Tr, # dx_r/dt: AC voltage control (rectifier side) ] ) PI_block, _ = tf_to_block( var_factory = vf, x = idc_ref - ir_dc, y = alpha, num=[Ki, Kp], den=[0, 1] ) block.children.append(control_block) block.children.append(PI_block) # External mapping for power flow references block.external_mapping = { VarPowerFlowReferenceType.Pf: Pf, VarPowerFlowReferenceType.Pt: Pt, VarPowerFlowReferenceType.Qf: Qf, VarPowerFlowReferenceType.Qt: Qt, } block.unify_blocks() self._block = block
[docs] def initialize_hvdc_rms(hvdc: HvdcLine, hvdc_idx: int, vf: VarFactory, nc: NumericalCircuit): """ Initialize the RMS model for an HVDC Line :param hvdc: HVDC Line device :param hvdc_idx: Index of the HVDC in nc.hvdc_data :param vf: VarFactory :param nc: NumericalCircuit with compiled data """ hvdc.rms_model = HvdcRmsTemplate(nc, hvdc=hvdc, hvdc_idx=hvdc_idx, vf=vf).block