Source code for VeraGridEngine.DataStructures.hvdc_data

# 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

import numpy as np
from typing import List, Tuple, Set
from VeraGridEngine.DataStructures.branch_parent_data import BranchParentData
from VeraGridEngine.enumerations import HvdcControlType
from VeraGridEngine.basic_structures import Vec, IntVec, Logger


[docs] class HvdcData(BranchParentData): """ HvdcData """ def __init__(self, nelm: int, nbus: int): """ Hvdc data arrays :param nelm: number of hvdcs :param nbus: number of buses """ BranchParentData.__init__(self, nelm=nelm, nbus=nbus) self.dispatchable: IntVec = np.zeros(nelm, dtype=int) self.r: Vec = np.zeros(nelm, dtype=float) self.Pset: Vec = np.zeros(nelm, dtype=float) self.Pt: Vec = np.zeros(nelm, dtype=float) # voltage p.u. set points self.Vset_f: Vec = np.zeros(nelm, dtype=float) self.Vset_t: Vec = np.zeros(nelm, dtype=float) # nominal bus voltages at the from and to ends self.Vnf: Vec = np.zeros(nelm, dtype=float) self.Vnt: Vec = np.zeros(nelm, dtype=float) self.angle_droop: Vec = np.zeros(nelm, dtype=float) self.control_mode_int: IntVec = np.zeros(nelm, dtype=int) self.Qmin_f: Vec = np.zeros(nelm, dtype=float) self.Qmax_f: Vec = np.zeros(nelm, dtype=float) self.Qmin_t: Vec = np.zeros(nelm, dtype=float) self.Qmax_t: Vec = np.zeros(nelm, dtype=float)
[docs] def size(self) -> int: """ Get size of the structure :return: """ return self.nelm
[docs] def slice(self, elm_idx: IntVec, bus_idx: IntVec, bus_map: IntVec, logger: Logger | None) -> "HvdcData": """ Make a deep copy of this structure :return: new HvdcData instance """ data, bus_map = super().slice(elm_idx, bus_idx, bus_map, logger) data: HvdcData = data data.__class__ = HvdcData data.dispatchable = self.dispatchable[elm_idx] data.r = self.r[elm_idx] data.Pset = self.Pset[elm_idx] data.Pt = self.Pt[elm_idx] data.Vset_f = self.Vset_f[elm_idx] data.Vset_t = self.Vset_t[elm_idx] data.Vnf = self.Vnf[elm_idx] data.Vnt = self.Vnt[elm_idx] data.angle_droop = self.angle_droop[elm_idx] data.control_mode_int = self.control_mode_int[elm_idx] data.Qmin_f = self.Qmin_f[elm_idx] data.Qmax_f = self.Qmax_f[elm_idx] data.Qmin_t = self.Qmin_t[elm_idx] data.Qmax_t = self.Qmax_t[elm_idx] return data
[docs] def remap(self, bus_map_arr: IntVec): """ Remapping of the branch buses :param bus_map_arr: array of old-to-new buses """ for k in range(self.nelm): f = self.F[k] t = self.T[k] new_f = bus_map_arr[f] new_t = bus_map_arr[t] self.F[k] = new_f self.T[k] = new_t
[docs] def copy(self) -> "HvdcData": """ Make a deep copy of this structure :return: new HvdcData instance """ data: HvdcData = super().copy() data.__class__ = HvdcData data.dispatchable = self.dispatchable.copy() data.r = self.r.copy() data.Pset = self.Pset.copy() data.Pt = self.Pt.copy() data.Vset_f = self.Vset_f.copy() data.Vset_t = self.Vset_t.copy() data.Vnf = self.Vnf.copy() data.Vnt = self.Vnt.copy() data.angle_droop = self.angle_droop.copy() data.control_mode_int = self.control_mode_int.copy() data.Qmin_f = self.Qmin_f.copy() data.Qmax_f = self.Qmax_f.copy() data.Qmin_t = self.Qmin_t.copy() data.Qmax_t = self.Qmax_t.copy() return data
[docs] def get_bus_indices_f(self) -> IntVec: """ Get bus indices "from" :return: """ return self.F
[docs] def get_bus_indices_t(self) -> IntVec: """ Get bus indices "to" :return: """ return self.T
[docs] def get_qmax_from_per_bus(self) -> Vec: """ Max reactive power in the From Bus :return: (nbus, nt) Qmax From """ val = np.zeros(self.nbus) for k in range(self.nelm): i = self.F[k] val[i] += self.Qmax_f[k] * self.active[k] return val
[docs] def get_qmin_from_per_bus(self) -> Vec: """ Min reactive power in the From Bus :return: (nbus, nt) Qmin From """ val = np.zeros(self.nbus) for k in range(self.nelm): i = self.F[k] val[i] += self.Qmin_f[k] * self.active[k] return val
[docs] def get_qmax_to_per_bus(self) -> Vec: """ Max reactive power in the To Bus :return: (nbus, nt) Qmax To """ val = np.zeros(self.nbus) for k in range(self.nelm): i = self.T[k] val[i] += self.Qmax_t[k] * self.active[k] return val
[docs] def get_qmin_to_per_bus(self) -> Vec: """ Min reactive power in the To Bus :return: (nbus, nt) Qmin To """ val = np.zeros(self.nbus) for k in range(self.nelm): i = self.T[k] val[i] += self.Qmin_t[k] * self.active[k] return val
[docs] def get_angle_droop_in_pu_rad(self, Sbase: float): """ Get the angle droop in pu/rad :param Sbase: base power :return: """ # convert MW/deg to pu/rad # MW 180 deg 1 # --- x ------- x ------ = 360 * 180 / pi / 100 = 206.26 aprox # deg pi rad 100 MVA return self.angle_droop * 57.295779513 / Sbase
[docs] def get_angle_droop_in_pu_rad_at(self, i: int, Sbase: float): """ Get the angle droop in pu/rad :param i: index :param Sbase: base power :return: """ # convert MW/deg to pu/rad # MW 180 deg 1 # --- x ------- x ------ = 360 * 180 / pi / 100 = 206.26 aprox # deg pi rad 100 MVA return self.angle_droop[i] * 57.295779513 / Sbase
[docs] def get_power(self, Sbase: float, theta: Vec) -> Tuple[Vec, Vec, Vec, Vec, Vec, int]: """ Get hvdc power :param Sbase: base power :param theta: bus angles array :return: Pbus, Losses, Pf, Pt, loading, nfree """ Pbus = np.zeros(self.nbus) Losses = np.zeros(self.nelm) loading = np.zeros(self.nelm) Pf = np.zeros(self.nelm) Pt = np.zeros(self.nelm) nfree = 0 for i in range(self.nelm): if self.active[i]: if self.control_mode_int[i] == HvdcControlType.type_1_Pset.idx(): Pcalc = self.Pset[i] elif self.control_mode_int[i] == HvdcControlType.type_0_free.idx(): Pcalc = self.Pset[i] + self.angle_droop[i] * np.rad2deg(theta[self.F[i]] - theta[self.T[i]]) nfree += 1 if Pcalc > self.rates[i]: Pcalc = self.rates[i] if Pcalc < -self.rates[i]: Pcalc = -self.rates[i] else: Pcalc = 0.0 # depending on the value of Pcalc, assign the from and to values if Pcalc > 0.0: I = Pcalc / (self.Vnf[i] * self.Vset_f[i]) # current in kA Losses[i] = self.r[i] * I * I # losses in MW Pf[i] = -Pcalc Pt[i] = Pcalc - Losses[i] elif Pcalc < 0.0: I = Pcalc / (self.Vnt[i] * self.Vset_t[i]) # current in kA Losses[i] = self.r[i] * I * I # losses in MW Pf[i] = -Pcalc - Losses[i] Pt[i] = Pcalc else: Losses[i] = 0 Pf[i] = 0 Pt[i] = 0 # compute loading loading[i] = Pf[i] / (self.rates[i] + 1e-20) # Pbus Pbus[self.F[i]] += Pf[i] / Sbase Pbus[self.T[i]] += Pt[i] / Sbase # to p.u. Pf /= Sbase Pt /= Sbase Losses /= Sbase # Shvdc, Losses_hvdc, Pf_hvdc, Pt_hvdc, loading_hvdc, n_free return Pbus, Losses, Pf, Pt, loading, nfree
[docs] def get_inter_areas(self, bus_idx_from: IntVec | Set[int], bus_idx_to: IntVec | Set[int]): """ Get the hvdcs that join two areas :param bus_idx_from: Area from :param bus_idx_to: Area to :return: List of (branch index, flow sense w.r.t the area exchange) """ lst: List[Tuple[int, float]] = list() for k in range(self.nelm): if self.F[k] in bus_idx_from and self.T[k] in bus_idx_to: lst.append((k, 1.0)) elif self.F[k] in bus_idx_to and self.T[k] in bus_idx_from: lst.append((k, -1.0)) return lst
def __len__(self) -> int: """ Get hvdc count :return: """ return self.nelm