# 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 List, Union, TYPE_CHECKING
import numpy as np
import VeraGridEngine.Devices as dev
from VeraGridEngine.enumerations import DeviceType
from VeraGridEngine.Devices.types import BRANCH_TYPES
from VeraGridEngine.Topology.topology import find_islands, get_adjacency_matrix
from VeraGridEngine.basic_structures import IntVec
from scipy.sparse import lil_matrix
if TYPE_CHECKING:
from VeraGridEngine.Devices.multi_circuit import MultiCircuit
[docs]
def get_bus_group_substation(bus_indices: IntVec, buses: List[dev.Bus]) -> Union[dev.Substation, None]:
"""
Given a list of buses, return the first substation available
:param bus_indices:
:param buses: list of bus objects
:return:
"""
for i in bus_indices:
bus = buses[i]
if bus.substation is not None:
return bus.substation
return None
[docs]
def detect_substations(grid: MultiCircuit, r_x_threshold=1e-3) -> None:
"""
Given a Grid with buses, it will detect all the missing substations and voltage levels
:param grid: MultiCircuit
:param r_x_threshold: sum of r+x under which a line is considered a jumper
:return: (in-place)
"""
buses = grid.get_buses()
# create a connectivity matrix only with transformers and lines with small (r+x)
branches: List[BRANCH_TYPES] = list()
reducible_types = [
DeviceType.Transformer2WDevice,
DeviceType.WindingDevice,
DeviceType.SeriesReactanceDevice,
DeviceType.VscDevice
] # these are devices that are supposed to "live" within a substation
for br in grid.get_branches(add_hvdc=True, add_vsc=True, add_switch=True):
if br.reducible or br.device_type in reducible_types:
branches.append(br)
elif br.device_type == DeviceType.LineDevice:
if br.R > 0.0 or br.X > 0.0:
if (br.R + br.X) <= r_x_threshold:
branches.append(br)
# build the connectivity matrix
nbr = len(branches)
nbus = grid.get_bus_number()
bus_dict = grid.get_bus_index_dict()
# declare the matrices
Cf = lil_matrix((nbr, nbus))
Ct = lil_matrix((nbr, nbus))
br_active = np.ones(nbr, dtype=int) # we will consider all branches as active for this
bus_active = np.ones(nbus, dtype=int)
# fill matrices appropriately
for i, elm in enumerate(branches):
if elm.bus_from is not None:
f = bus_dict[elm.bus_from]
Cf[i, f] = 1
if elm.bus_to is not None:
t = bus_dict[elm.bus_to]
Ct[i, t] = 1
# compose the adjacency matrix from the connectivity information
A = get_adjacency_matrix(C_branch_bus_f=Cf.tocsc(),
C_branch_bus_t=Ct.tocsc(),
branch_active=br_active,
bus_active=bus_active)
# perform the topology search, each island is a substation
islands = find_islands(adj=A, active=bus_active)
for is_idx, island in enumerate(islands):
bus0 = grid.get_buses()[island[0]]
sub = get_bus_group_substation(bus_indices=island, buses=buses)
if sub is None:
sub = dev.Substation(name=bus0.name,
latitude=bus0.latitude,
longitude=bus0.longitude,
area=bus0.area if bus0.area is not None else None,
zone=bus0.zone if bus0.zone is not None else None,
country=bus0.country if bus0.country is not None else None)
grid.add_substation(sub)
# set the substation, and get the different voltages
voltages = set()
for i in island:
bus = buses[i]
bus.substation = sub
voltages.add(bus.Vnom)
# add the voltage levels
voltages_vl = dict()
for voltage in voltages:
vl = dev.VoltageLevel(name=sub.name + f"_{int(voltage)}",
Vnom=voltage,
substation=sub)
grid.add_voltage_level(vl)
voltages_vl[voltage] = vl
# assign the VL's
for i in island:
bus = buses[i]
bus.voltage_level = voltages_vl[bus.Vnom]
[docs]
def detect_facilities(grid: MultiCircuit) -> None:
"""
Create facilities automatically
In essence is packing all the injections connected to the same bus into a facility object
:param grid: MultiCircuit
"""
dict_bus_inj = grid.get_injection_devices_grouped_by_bus()
for bus, inj_list in dict_bus_inj.items():
if len(inj_list):
lon, lat = bus.try_to_find_coordinates()
plant = dev.Facility(name=f"Facility at bus {bus.name}", longitude=lon, latitude=lat)
grid.add_facility(obj=plant)
for elm in inj_list:
elm.facility = plant