# 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 Union, List, TYPE_CHECKING, Tuple
import numpy as np
from VeraGridEngine.Devices.Parents.dynamic_parent import DynamicDevice
from VeraGridEngine.Devices.Associations.association import Associations
from VeraGridEngine.Devices.Substation.bus import Bus
from VeraGridEngine.enumerations import BuildStatus, DeviceType, SubObjectType, ShuntConnectionType, PrpCat
from VeraGridEngine.basic_structures import CxVec
from VeraGridEngine.Devices.Profiles import ProfileBool, ProfileFloat
from VeraGridEngine.Devices.Aggregation.facility import Facility
from VeraGridEngine.Devices.Parents.editable_device import get_at, GCProp
if TYPE_CHECKING:
from VeraGridEngine.Devices.Associations.technology import Technology
from VeraGridEngine.Devices.types import ALL_DEV_TYPES
[docs]
class InjectionParent(DynamicDevice):
"""
Parent class for Injections
"""
__slots__ = (
'_bus',
'_active',
'_active_prof',
'_mttf',
'_mttr',
'_Cost',
'_Cost_prof',
'_capex',
'_opex',
'facility',
'technologies',
'_scalable',
'_shift_key',
'_longitude',
'_latitude',
'_shift_key_prof',
'_use_kw',
'_bus_pos',
'_conn',
'color',
)
LOCAL_PROPERTY_DECLARATIONS: Tuple[GCProp, ...] = (
GCProp(
prop_name='bus',
units='',
tpe=DeviceType.BusDevice,
definition='Connection bus',
editable=False,
cat=[PrpCat.TP],
),
GCProp(
prop_name='active',
units='',
tpe=bool,
definition='Is the load active?',
profile_name='active_prof',
cat=[PrpCat.PF, PrpCat.OPF, PrpCat.CON],
),
GCProp(
prop_name='color',
units='',
tpe=str,
definition='Color to paint the element in the map diagram',
is_color=True,
),
GCProp(
prop_name='mttf',
units='h',
tpe=float,
definition='Mean time to failure',
cat=[PrpCat.REL],
),
GCProp(
prop_name='mttr',
units='h',
tpe=float,
definition='Mean time to recovery',
cat=[PrpCat.REL],
),
GCProp(
prop_name='capex',
units='e/MW',
tpe=float,
definition='Cost of investment. Used in expansion planning.',
cat=[PrpCat.INV],
),
GCProp(
prop_name='opex',
units='e/MWh',
tpe=float,
definition='Cost of operation. Used in expansion planning.',
cat=[PrpCat.INV],
),
GCProp(
prop_name='Cost',
units='e/MWh',
tpe=float,
definition='Cost of not served energy. Used in OPF.',
profile_name='Cost_prof',
cat=[PrpCat.OPF],
),
GCProp(
prop_name='facility',
units='',
tpe=DeviceType.FacilityDevice,
definition='Facility where this is located',
editable=True,
),
GCProp(
prop_name='technologies',
units='p.u.',
tpe=SubObjectType.Associations,
definition='Technologies associations to injections',
display=False,
cat=[PrpCat.OPF],
),
GCProp(
prop_name='scalable',
units='',
tpe=bool,
definition='Is the injection scalable?',
cat=[PrpCat.OPF],
),
GCProp(
prop_name='shift_key',
units='',
tpe=float,
definition='Shift key for net transfer capacity',
profile_name="shift_key_prof",
cat=[PrpCat.NTC],
),
GCProp(
prop_name='longitude',
units='deg',
tpe=float,
definition='longitude of the injection.',
profile_name='',
cat=[PrpCat.TP],
),
GCProp(
prop_name='latitude',
units='deg',
tpe=float,
definition='latitude of the injection.',
profile_name='',
cat=[PrpCat.TP],
),
GCProp(
prop_name='use_kw',
units='',
tpe=bool,
definition='Consider the injections in kW and kVAr?',
cat=[PrpCat.PF, PrpCat.OPF],
),
GCProp(
prop_name='conn',
units='',
tpe=ShuntConnectionType,
definition='Connection type for 3-phase studies',
cat=[PrpCat.SC, PrpCat.PF3],
),
GCProp(
prop_name='bus_pos',
units='',
tpe=int,
definition='Aid to locate devices on a busbar',
display=False,
),
)
def __init__(self,
name: str,
idtag: Union[str, None],
code: str,
bus: Union[Bus, None],
active: bool,
Cost: float,
mttf: float,
mttr: float,
capex: float,
opex: float,
build_status: BuildStatus,
device_type: DeviceType,
longitude=0.0,
latitude=0.0,
color: str | None = None):
"""
InjectionTemplate
:param name: Name of the device
:param idtag: unique id of the device (if None or "" a new one is generated)
:param code: secondary code for compatibility
:param bus: snapshot bus object
:param active:active state
:param Cost: cost associated with various actions (dispatch or shedding)
:param mttf: mean time to failure (h)
:param mttr: mean time to recovery (h)
:param capex: capital expenditures (investment cost)
:param opex: operational expenditures (maintainance cost)
:param build_status: BuildStatus
:param device_type: DeviceType
"""
DynamicDevice.__init__(self,
name=name,
idtag=idtag,
code=code,
device_type=device_type,
build_status=build_status)
self._bus = bus
self.active = bool(active)
self._active_prof = ProfileBool(default_value=self.active)
self.mttf = mttf
self.mttr = mttr
self.Cost = float(Cost)
self._Cost_prof = ProfileFloat(default_value=self.Cost)
self.capex = capex
self.opex = opex
self.facility: Facility | None = None
self.technologies: Associations = Associations(device_type=DeviceType.Technology)
self.scalable: bool = True
self.shift_key: float = 1.0
self._shift_key_prof = ProfileFloat(default_value=self.shift_key)
self.longitude = float(longitude)
self.latitude = float(latitude)
self._use_kw: bool = False
self._conn: ShuntConnectionType = ShuntConnectionType.GroundedStar
self.bus_pos: int = 0
self.color = color if color is not None else "#909090" # light gray
@property
def bus(self) -> Bus | None:
"""
Bus
:return: Bus
"""
return self._bus
@bus.setter
def bus(self, val: Bus):
if val is None:
self._bus = val
else:
if isinstance(val, Bus):
self._bus = val
else:
raise Exception(str(type(val)) + 'not supported to be set into a bus')
@property
def active_prof(self) -> ProfileBool:
"""
Cost profile
:return: Profile
"""
return self._active_prof
@active_prof.setter
def active_prof(self, val: Union[ProfileBool, np.ndarray]):
if isinstance(val, ProfileBool):
self._active_prof = val
elif isinstance(val, np.ndarray):
self._active_prof.set(arr=val)
else:
raise Exception(str(type(val)) + 'not supported to be set into a active_prof')
[docs]
def get_active_at(self, t: int | None) -> float:
"""
:param t:
:return:
"""
return get_at(self.active, self.active_prof, t)
@property
def Cost_prof(self) -> ProfileFloat:
"""
Cost profile
:return: Profile
"""
return self._Cost_prof
@Cost_prof.setter
def Cost_prof(self, val: Union[ProfileFloat, np.ndarray]):
if isinstance(val, ProfileFloat):
self._Cost_prof = val
elif isinstance(val, np.ndarray):
self._Cost_prof.set(arr=val)
else:
raise Exception(str(type(val)) + 'not supported to be set into a Cost_prof')
[docs]
def get_Cost_at(self, t: int | None) -> float:
"""
:param t:
:return:
"""
return get_at(self.Cost, self.Cost_prof, t)
@property
def shift_key_prof(self) -> ProfileFloat:
"""
Cost profile
:return: Profile
"""
return self._shift_key_prof
@shift_key_prof.setter
def shift_key_prof(self, val: Union[ProfileFloat, np.ndarray]):
if isinstance(val, ProfileFloat):
self._shift_key_prof = val
elif isinstance(val, np.ndarray):
self._shift_key_prof.set(arr=val)
else:
raise Exception(str(type(val)) + 'not supported to be set into a shift_key_prof')
[docs]
def get_shift_key_at(self, t: int | None) -> float:
"""
:param t:
:return:
"""
return get_at(self.shift_key, self.shift_key_prof, t)
@property
def use_kw(self):
"""
:return:
"""
return self._use_kw
@use_kw.setter
def use_kw(self, val: bool):
"""
Setter
:param val: bool
"""
val = bool(val)
if self.auto_update_enabled:
if val != self._use_kw:
self._use_kw = val
else:
pass
else:
self._use_kw = val
@property
def conn(self) -> ShuntConnectionType:
"""
:return:
"""
return self._conn
@conn.setter
def conn(self, val: ShuntConnectionType):
if isinstance(val, ShuntConnectionType):
self._conn = val
[docs]
def get_S_with_sign(self) -> complex:
"""
:return:
"""
return complex(0.0, 0.0)
[docs]
def get_Sprof_with_sign(self) -> CxVec:
"""
:return:
"""
return np.zeros(self.active_prof.size(), dtype=complex)
[docs]
def associate_technology(self, tech: Technology, val=1.0):
"""
Associate a technology with this injection device
:param tech:
:param val:
:return:
"""
self.technologies.add_object(tech, val=val)
@property
def tech_list(self) -> List[ALL_DEV_TYPES]:
"""
Bus
:return: Bus
"""
return self.technologies.to_list()
[docs]
def get_first_technology(self) -> Technology | None:
"""
Get the first technology available
:return: Technology
"""
for key, association in self.technologies.data.items():
return association.api_object
return None
[docs]
def get_bus_pos(self, bus: Bus | None = None) -> int:
"""
Get the bus position
NOTE: don't remove the void bus argument
:param bus: Bus
:return: bus_pos
"""
return self.bus_pos
[docs]
def try_to_find_coordinates(self) -> Tuple[float, float]:
"""
Get the latitude and
:return: longitude, latitude
"""
if self.latitude == 0.0 and self.longitude == 0.0:
lon, lat = self.bus.get_coordinates()
if lon == 0.0 and lat == 0.0:
lon, lat = self.bus.try_to_find_coordinates()
return lon, lat
else:
return lon, lat
else:
return self.longitude, self.latitude
[docs]
def color_by_main_technology(self):
"""
Set the color of the dominant technology
"""
mx = 0.0
col = self.color
for _, val in self.technologies.data.items():
if val.value > mx:
col = val.api_object.color
self.color = col
[docs]
def color_by_main_owner(self):
"""
Set the color of the dominant owner
"""
mx = 0.0
col = self.color
for _, val in self.owners.data.items():
if val.value > mx:
col = val.api_object.color
self.color = col
# Scalar property accessors coerce assignments to the declared schema types.
@property
def active(self) -> bool:
"""
Get ``active``.
:return: bool
"""
return self._active
@active.setter
def active(self, val: bool) -> None:
"""
Set ``active``.
:param val: Value to assign.
:return: None
"""
self._active = bool(val)
@property
def mttf(self) -> float:
"""
Get ``mttf``.
:return: float
"""
return self._mttf
@mttf.setter
def mttf(self, val: float) -> None:
"""
Set ``mttf``.
:param val: Value to assign.
:return: None
"""
self._mttf = float(val)
@property
def mttr(self) -> float:
"""
Get ``mttr``.
:return: float
"""
return self._mttr
@mttr.setter
def mttr(self, val: float) -> None:
"""
Set ``mttr``.
:param val: Value to assign.
:return: None
"""
self._mttr = float(val)
@property
def capex(self) -> float:
"""
Get ``capex``.
:return: float
"""
return self._capex
@capex.setter
def capex(self, val: float) -> None:
"""
Set ``capex``.
:param val: Value to assign.
:return: None
"""
self._capex = float(val)
@property
def opex(self) -> float:
"""
Get ``opex``.
:return: float
"""
return self._opex
@opex.setter
def opex(self, val: float) -> None:
"""
Set ``opex``.
:param val: Value to assign.
:return: None
"""
self._opex = float(val)
@property
def Cost(self) -> float:
"""
Get ``Cost``.
:return: float
"""
return self._Cost
@Cost.setter
def Cost(self, val: float) -> None:
"""
Set ``Cost``.
:param val: Value to assign.
:return: None
"""
self._Cost = float(val)
@property
def scalable(self) -> bool:
"""
Get ``scalable``.
:return: bool
"""
return self._scalable
@scalable.setter
def scalable(self, val: bool) -> None:
"""
Set ``scalable``.
:param val: Value to assign.
:return: None
"""
self._scalable = bool(val)
@property
def shift_key(self) -> float:
"""
Get ``shift_key``.
:return: float
"""
return self._shift_key
@shift_key.setter
def shift_key(self, val: float) -> None:
"""
Set ``shift_key``.
:param val: Value to assign.
:return: None
"""
self._shift_key = float(val)
@property
def longitude(self) -> float:
"""
Get ``longitude``.
:return: float
"""
return self._longitude
@longitude.setter
def longitude(self, val: float) -> None:
"""
Set ``longitude``.
:param val: Value to assign.
:return: None
"""
self._longitude = float(val)
@property
def latitude(self) -> float:
"""
Get ``latitude``.
:return: float
"""
return self._latitude
@latitude.setter
def latitude(self, val: float) -> None:
"""
Set ``latitude``.
:param val: Value to assign.
:return: None
"""
self._latitude = float(val)
@property
def bus_pos(self) -> int:
"""
Get ``bus_pos``.
:return: int
"""
return self._bus_pos
@bus_pos.setter
def bus_pos(self, val: int) -> None:
"""
Set ``bus_pos``.
:param val: Value to assign.
:return: None
"""
self._bus_pos = int(val)