Source code for VeraGridEngine.Topology.simulation_indices

# 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 numba as nb
from typing import Tuple, List
from VeraGridEngine.enumerations import BusMode, TapPhaseControl, TapModuleControl
from VeraGridEngine.basic_structures import Vec, IntVec, BoolVec


[docs] @nb.njit(cache=True) def compile_types(Pbus: Vec, types: IntVec) -> Tuple[IntVec, IntVec, IntVec, IntVec, IntVec, IntVec]: """ Compile the types. :param Pbus: array of real power Injections per node used to choose the slack as the node with greater generation if no slack is provided :param types: array of tentative node types (it may be modified internally) :return: ref, pq, pv, pqpv """ # check that Sbus is a 1D array assert (len(Pbus.shape) == 1) # Define what number is what pq_val = 1 pv_val = 2 vd_val = 3 pqv_val = 4 p_val = 5 pq = np.where(types == pq_val)[0] pv = np.where(types == pv_val)[0] pqv = np.where(types == pqv_val)[0] p = np.where(types == p_val)[0] ref = np.where(types == vd_val)[0] if len(ref) == 0: # there is no slack! if len(pv) == 0: # there are no pv neither -> blackout grid pass else: # select the first PV generator as the slack mx = max(Pbus[pv]) if mx > 0: # find the generator that is injecting the most i = np.where(Pbus == mx)[0][0] else: # all the generators are injecting zero, pick the first pv i = pv[0] # delete the selected pv bus from the pv list and put it in the slack list pv = np.delete(pv, np.where(pv == i)[0]) ref = np.array([i]) for r in ref: types[r] = BusMode.Slack_tpe.value else: pass # no problem :) no_slack = np.concatenate((pv, pq, p, pqv)) no_slack.sort() return ref, pq, pv, pqv, p, no_slack
[docs] @nb.njit(cache=True) def replace_bus_types(bus_types, pq_val=1, pv_val=2, pqv_val=4, p_val=5): """ :param bus_types: :param pq_val: :param pv_val: :param pqv_val: :param p_val: :return: """ for i in range(len(bus_types)): if bus_types[i] == pqv_val: bus_types[i] = pq_val elif bus_types[i] == p_val: bus_types[i] = pv_val
[docs] class SimulationIndices: """ Class to handle the simulation indices """ __slots__ = ( "bus_types", "tap_module_control_mode", "tap_controlled_buses", "tap_phase_control_mode", "F", "T", "ac", "dc", "pq", "pv", "p", "pqv", "vd", "no_slack", ) def __init__(self, bus_types: IntVec, Pbus: Vec, tap_module_control_mode: IntVec, # List[TapModuleControl], tap_phase_control_mode: IntVec, # List[TapPhaseControl], tap_controlled_buses: IntVec, F: IntVec, T: IntVec, is_dc_bus: BoolVec, force_only_pq_pv_vd_types=False): """ :param bus_types: Array of Bus type initial guess :param Pbus: Array of Active power per bus :param tap_module_control_mode: Array of TapModuleControl control mode :param tap_phase_control_mode: Array of TapPhaseControl control mode :param tap_controlled_buses: Array of bus indices where the tap module control occurs :param is_dc_bus: Array of is DC ? per bus :param force_only_pq_pv_vd_types: if true, all bus types are forced into PQ PV or VD, for certain types of simulations that cannot handle other bus types """ # master aray of bus types (nbus) self.bus_types = bus_types # arrays for branch control types (nbr) self.tap_module_control_mode = tap_module_control_mode self.tap_controlled_buses = tap_controlled_buses self.tap_phase_control_mode = tap_phase_control_mode self.F = F self.T = T # AC and DC indices self.ac: IntVec = np.where(~is_dc_bus)[0] self.dc: IntVec = np.where(is_dc_bus)[0] # determine the bus indices self.pq: IntVec = np.zeros(0, dtype=int) self.pv: IntVec = np.zeros(0, dtype=int) # PV-local self.p: IntVec = np.zeros(0, dtype=int) # PV-remote self.pqv: IntVec = np.zeros(0, dtype=int) # PV-remote pair self.vd: IntVec = np.zeros(0, dtype=int) # slack self.no_slack: IntVec = np.zeros(0, dtype=int) # all bus indices that are not slack, sorted if force_only_pq_pv_vd_types: replace_bus_types(self.bus_types) self.vd, self.pq, self.pv, self.pqv, self.p, self.no_slack = compile_types( Pbus=Pbus, types=self.bus_types )
[docs] def get_branch_controls_indices(self) -> Tuple[IntVec, IntVec, IntVec]: """ Analyze the control branches and compute the indices :return: k_m, k_tau, k_mtau """ k_pf_tau = list() k_pt_tau = list() k_qf_m = list() k_qt_m = list() k_v_m = list() nbr = len(self.F) for k in range(nbr): ctrl_m = self.tap_module_control_mode[k] ctrl_tau = self.tap_phase_control_mode[k] # analyze tap-module controls if ctrl_m == TapModuleControl.Vm.idx(): # In any other case, the voltage is managed by the tap module k_v_m.append(k) elif ctrl_m == TapModuleControl.Qf.idx(): k_qf_m.append(k) elif ctrl_m == TapModuleControl.Qt.idx(): k_qt_m.append(k) elif ctrl_m == TapModuleControl.fixed.idx(): pass elif ctrl_m == 0: pass else: raise Exception(f"Unknown tap phase module mode {ctrl_m}") # analyze tap-phase controls if ctrl_tau == TapPhaseControl.Pf.idx(): k_pf_tau.append(k) elif ctrl_tau == TapPhaseControl.Pt.idx(): k_pt_tau.append(k) elif ctrl_tau == TapPhaseControl.fixed.idx(): pass elif ctrl_tau == 0: pass else: raise Exception(f"Unknown tap phase control mode {ctrl_tau}") # convert lists to integer arrays k_pf_tau = np.array(k_pf_tau, dtype=int) k_pt_tau = np.array(k_pt_tau, dtype=int) k_qf_m = np.array(k_qf_m, dtype=int) k_qt_m = np.array(k_qt_m, dtype=int) k_v_m = np.array(k_v_m, dtype=int) k_m = np.r_[k_v_m, k_qf_m, k_qt_m] k_tau = np.r_[k_pf_tau, k_pt_tau] k_mtau = np.intersect1d(k_m, k_tau) return k_m, k_tau, k_mtau