# 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, Tuple
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from VeraGridEngine.Devices.Substation.bus import Bus
from VeraGridEngine.enumerations import BuildStatus, DeviceType, SubObjectType, PrpCat, ParamPowerFlowReferenceType
from VeraGridEngine.Devices.Profiles import ProfileFloat
from VeraGridEngine.Devices.Parents.injection_parent import InjectionParent
from VeraGridEngine.Devices.admittance_matrix import AdmittanceMatrix
from VeraGridEngine.Devices.Parents.editable_device import get_at, GCProp
[docs]
class ShuntParent(InjectionParent):
"""
Template for objects that behave like shunts
"""
__slots__ = (
'_G',
'_G_prof',
'_B',
'_B_prof',
'_G0',
'_G0_prof',
'_B0',
'_B0_prof',
'_Ga',
'_Ga_prof',
'_Ba',
'_Ba_prof',
'_Gb',
'_Gb_prof',
'_Bb',
'_Bb_prof',
'_Gc',
'_Gc_prof',
'_Bc',
'_Bc_prof',
'_ysh',
)
LOCAL_PROPERTY_DECLARATIONS: Tuple[GCProp, ...] = (
GCProp(
prop_name='G',
units='MW',
tpe=float,
definition='Active power',
profile_name='G_prof',
cat=[PrpCat.PF],
dyn_ref=ParamPowerFlowReferenceType.shunt_g_pu,
),
GCProp(
prop_name='G0',
units='MW',
tpe=float,
definition='Zero sequence active power of the impedance component at V=1.0 p.u.',
profile_name='G0_prof',
cat=[PrpCat.PF3, PrpCat.SC],
dyn_ref=ParamPowerFlowReferenceType.shunt_g0_pu,
),
GCProp(
prop_name='Ga',
units='MW',
tpe=float,
definition='Active power',
profile_name='Ga_prof',
cat=[PrpCat.PF3],
dyn_ref=ParamPowerFlowReferenceType.shunt_ga_pu,
),
GCProp(
prop_name='Gb',
units='MW',
tpe=float,
definition='Active power',
profile_name='Gb_prof',
cat=[PrpCat.PF3],
dyn_ref=ParamPowerFlowReferenceType.shunt_gb_pu,
),
GCProp(
prop_name='Gc',
units='MW',
tpe=float,
definition='Active power',
profile_name='Gc_prof',
cat=[PrpCat.PF3],
dyn_ref=ParamPowerFlowReferenceType.shunt_gc_pu,
),
GCProp(
prop_name='B',
units='MVAr',
tpe=float,
definition='Reactive power',
profile_name='B_prof',
cat=[PrpCat.PF],
dyn_ref=ParamPowerFlowReferenceType.shunt_b_pu,
),
GCProp(
prop_name='B0',
units='MVAr',
tpe=float,
definition='Zero sequence reactive power of the impedance component at V=1.0 p.u.',
profile_name='B0_prof',
cat=[PrpCat.PF3, PrpCat.SC],
dyn_ref=ParamPowerFlowReferenceType.shunt_b0_pu,
),
GCProp(
prop_name='Ba',
units='MVAr',
tpe=float,
definition='Reactive power',
profile_name='Ba_prof',
cat=[PrpCat.PF3],
dyn_ref=ParamPowerFlowReferenceType.shunt_ba_pu,
),
GCProp(
prop_name='Bb',
units='MVAr',
tpe=float,
definition='Reactive power',
profile_name='Bb_prof',
cat=[PrpCat.PF3],
dyn_ref=ParamPowerFlowReferenceType.shunt_bb_pu,
),
GCProp(
prop_name='Bc',
units='MVAr',
tpe=float,
definition='Reactive power',
profile_name='Bc_prof',
cat=[PrpCat.PF3],
dyn_ref=ParamPowerFlowReferenceType.shunt_bc_pu,
),
GCProp(
prop_name='ysh',
units="p.u.",
tpe=SubObjectType.AdmittanceMatrix,
definition='Shunt admittance matrix of the branch',
editable=False,
display=False,
cat=[PrpCat.PF],
),
)
def __init__(self,
name: str,
idtag: Union[str, None],
code: str,
bus: Union[Bus, None],
active: bool,
G: float,
G1: float,
G2: float,
G3: float,
B: float,
B1: float,
B2: float,
B3: float,
G0: float,
B0: float,
Cost: float,
mttf: float,
mttr: float,
capex: float,
opex: float,
build_status: BuildStatus,
device_type: DeviceType):
"""
ShuntLikeTemplate
: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 G: positive conductance (MW @ v=1 p.u.)
:param G1: positive conductance (MW @ v=1 p.u.)
:param G2: positive conductance (MW @ v=1 p.u.)
:param G3: positive conductance (MW @ v=1 p.u.)
:param B: positive conductance (MVAr @ v=1 p.u.)
:param B1: positive conductance (MVAr @ v=1 p.u.)
:param B2: positive conductance (MVAr @ v=1 p.u.)
:param B3: positive conductance (MVAr @ v=1 p.u.)
:param G0: zero-sequence conductance (MW @ v=1 p.u.)
:param B0: zero-sequence conductance (MVAr @ v=1 p.u.)
: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 (maintenance cost)
:param build_status: BuildStatus
:param device_type: DeviceType
"""
InjectionParent.__init__(self,
name=name,
idtag=idtag,
code=code,
bus=bus,
active=active,
Cost=Cost,
mttf=mttf,
mttr=mttr,
capex=capex,
opex=opex,
build_status=build_status,
device_type=device_type)
self.G = float(G)
self._G_prof = ProfileFloat(default_value=self.G)
self.B = float(B)
self._B_prof = ProfileFloat(default_value=self.B)
self.G0 = float(G0)
self._G0_prof = ProfileFloat(default_value=self.G0)
self.B0 = float(B0)
self._B0_prof = ProfileFloat(default_value=self.B0)
self.Ga = float(G1)
self._Ga_prof = ProfileFloat(default_value=self.Ga)
self.Gb = float(G2)
self._Gb_prof = ProfileFloat(default_value=self.Gb)
self.Gc = float(G3)
self._Gc_prof = ProfileFloat(default_value=self.Gc)
self.Ba = float(B1)
self._Ba_prof = ProfileFloat(default_value=self.Ba)
self.Bb = float(B2)
self._Bb_prof = ProfileFloat(default_value=self.Bb)
self.Bc = float(B3)
self._Bc_prof = ProfileFloat(default_value=self.Bc)
self._ysh = AdmittanceMatrix()
@property
def G_prof(self) -> ProfileFloat:
"""
Cost profile
:return: Profile
"""
return self._G_prof
@G_prof.setter
def G_prof(self, val: Union[ProfileFloat, np.ndarray]):
if isinstance(val, ProfileFloat):
self._G_prof = val
elif isinstance(val, np.ndarray):
self._G_prof.set(arr=val)
else:
raise Exception(str(type(val)) + 'not supported to be set into a G_prof')
[docs]
def get_G_at(self, t: int | None) -> float:
"""
:param t:
:return:
"""
return get_at(self.G, self.G_prof, t)
@property
def Ga_prof(self) -> ProfileFloat:
"""
Cost profile
:return: Profile
"""
return self._Ga_prof
@Ga_prof.setter
def Ga_prof(self, val: Union[ProfileFloat, np.ndarray]):
if isinstance(val, ProfileFloat):
self._Ga_prof = val
elif isinstance(val, np.ndarray):
self._Ga_prof.set(arr=val)
else:
raise Exception(str(type(val)) + 'not supported to be set into a Ga_prof')
[docs]
def get_Ga_at(self, t: int | None) -> float:
"""
:param t:
:return:
"""
return get_at(self.Ga, self.Ga_prof, t)
@property
def Gb_prof(self) -> ProfileFloat:
"""
Cost profile
:return: Profile
"""
return self._Gb_prof
@Gb_prof.setter
def Gb_prof(self, val: Union[ProfileFloat, np.ndarray]):
if isinstance(val, ProfileFloat):
self._Gb_prof = val
elif isinstance(val, np.ndarray):
self._Gb_prof.set(arr=val)
else:
raise Exception(str(type(val)) + 'not supported to be set into a Gb_prof')
[docs]
def get_Gb_at(self, t: int | None) -> float:
"""
:param t:
:return:
"""
return get_at(self.Gb, self.Gb_prof, t)
@property
def Gc_prof(self) -> ProfileFloat:
"""
Cost profile
:return: Profile
"""
return self._Gc_prof
@Gc_prof.setter
def Gc_prof(self, val: Union[ProfileFloat, np.ndarray]):
if isinstance(val, ProfileFloat):
self._Gc_prof = val
elif isinstance(val, np.ndarray):
self._Gc_prof.set(arr=val)
else:
raise Exception(str(type(val)) + 'not supported to be set into a Gc_prof')
[docs]
def get_Gc_at(self, t: int | None) -> float:
"""
:param t:
:return:
"""
return get_at(self.Gc, self.Gc_prof, t)
@property
def B_prof(self) -> ProfileFloat:
"""
Cost profile
:return: Profile
"""
return self._B_prof
@B_prof.setter
def B_prof(self, val: Union[ProfileFloat, np.ndarray]):
if isinstance(val, ProfileFloat):
self._B_prof = val
elif isinstance(val, np.ndarray):
self._B_prof.set(arr=val)
else:
raise Exception(str(type(val)) + 'not supported to be set into a B_prof')
[docs]
def get_B_at(self, t: int | None) -> float:
"""
:param t:
:return:
"""
return get_at(self.B, self.B_prof, t)
@property
def Ba_prof(self) -> ProfileFloat:
"""
Cost profile
:return: Profile
"""
return self._Ba_prof
@Ba_prof.setter
def Ba_prof(self, val: Union[ProfileFloat, np.ndarray]):
if isinstance(val, ProfileFloat):
self._Ba_prof = val
elif isinstance(val, np.ndarray):
self._Ba_prof.set(arr=val)
else:
raise Exception(str(type(val)) + 'not supported to be set into a Ba_prof')
[docs]
def get_Ba_at(self, t: int | None) -> float:
"""
:param t:
:return:
"""
return get_at(self.Ba, self.Ba_prof, t)
@property
def Bb_prof(self) -> ProfileFloat:
"""
Cost profile
:return: Profile
"""
return self._Bb_prof
@Bb_prof.setter
def Bb_prof(self, val: Union[ProfileFloat, np.ndarray]):
if isinstance(val, ProfileFloat):
self._Bb_prof = val
elif isinstance(val, np.ndarray):
self._Bb_prof.set(arr=val)
else:
raise Exception(str(type(val)) + 'not supported to be set into a Bb_prof')
[docs]
def get_Bb_at(self, t: int | None) -> float:
"""
:param t:
:return:
"""
return get_at(self.Bb, self.Bb_prof, t)
@property
def Bc_prof(self) -> ProfileFloat:
"""
Cost profile
:return: Profile
"""
return self._Bc_prof
@Bc_prof.setter
def Bc_prof(self, val: Union[ProfileFloat, np.ndarray]):
if isinstance(val, ProfileFloat):
self._Bc_prof = val
elif isinstance(val, np.ndarray):
self._Bc_prof.set(arr=val)
else:
raise Exception(str(type(val)) + 'not supported to be set into a Bc_prof')
[docs]
def get_Bc_at(self, t: int | None) -> float:
"""
:param t:
:return:
"""
return get_at(self.Bc, self.Bc_prof, t)
@property
def G0_prof(self) -> ProfileFloat:
"""
Cost profile
:return: Profile
"""
return self._G0_prof
@G0_prof.setter
def G0_prof(self, val: Union[ProfileFloat, np.ndarray]):
if isinstance(val, ProfileFloat):
self._G0_prof = val
elif isinstance(val, np.ndarray):
self._G0_prof.set(arr=val)
else:
raise Exception(str(type(val)) + 'not supported to be set into a G_prof')
[docs]
def get_G0_at(self, t: int | None) -> float:
"""
:param t:
:return:
"""
return get_at(self.G0, self.G0_prof, t)
@property
def B0_prof(self) -> ProfileFloat:
"""
Cost profile
:return: Profile
"""
return self._B0_prof
@B0_prof.setter
def B0_prof(self, val: Union[ProfileFloat, np.ndarray]):
if isinstance(val, ProfileFloat):
self._B0_prof = val
elif isinstance(val, np.ndarray):
self._B0_prof.set(arr=val)
else:
raise Exception(str(type(val)) + 'not supported to be set into a B_prof')
[docs]
def get_B0_at(self, t: int | None) -> float:
"""
:param t:
:return:
"""
return get_at(self.B0, self.B0_prof, t)
@property
def ysh(self) -> AdmittanceMatrix:
"""
Shunt admittance matrix (4x4)
:return:
"""
if self._ysh.size <= 0:
self.fill_3_phase_from_sequence()
return self._ysh
@ysh.setter
def ysh(self, val: AdmittanceMatrix):
if isinstance(val, AdmittanceMatrix):
self._ysh = val
else:
raise ValueError(f'{val} is not a AdmittanceMatrix')
[docs]
def get_Y_at(self, t: int | None) -> complex:
"""
:param t:
:return:
"""
return complex(self.get_G_at(t), self.get_B_at(t))
[docs]
def get_Ya_at(self, t: int | None) -> complex:
"""
:param t:
:return:
"""
return complex(self.get_Ga_at(t), self.get_Ba_at(t))
[docs]
def get_Yb_at(self, t: int | None) -> complex:
"""
:param t:
:return:
"""
return complex(self.get_Gb_at(t), self.get_Bb_at(t))
[docs]
def get_Yc_at(self, t: int | None) -> complex:
"""
:param t:
:return:
"""
return complex(self.get_Gc_at(t), self.get_Bc_at(t))
[docs]
def plot_profiles(self, time=None, show_fig=True):
"""
Plot the time series results of this object
:param time: array of time values
:param show_fig: Show the figure?
"""
if time is not None:
fig = plt.figure(figsize=(12, 8))
ax_1 = fig.add_subplot(211)
ax_2 = fig.add_subplot(212, sharex=ax_1)
# G
y = self.G_prof.toarray()
df = pd.DataFrame(data=y, index=time, columns=[self.name])
ax_1.set_title('Conductance power', fontsize=14)
ax_1.set_ylabel('MW', fontsize=11)
df.plot(ax=ax_1)
# B
y = self.B_prof.toarray()
df = pd.DataFrame(data=y, index=time, columns=[self.name])
ax_2.set_title('Susceptance power', fontsize=14)
ax_2.set_ylabel('MVAr', fontsize=11)
df.plot(ax=ax_2)
plt.legend()
fig.suptitle(self.name, fontsize=20)
if show_fig:
plt.show()
[docs]
def fill_3_phase_from_sequence(self):
"""
Fill the admittance
:return:
"""
self.ysh = AdmittanceMatrix(size=4)
y1 = self.G + 1j * self.B
y0 = self.G0 + 1j * self.B0
diag = (2.0 * y1 + y0) / 3.0
off_diag = (y0 - y1) / 3.0
yabc = np.full((4, 4), off_diag)
np.fill_diagonal(yabc, diag)
self.ysh.values = yabc
self.ysh.phA = 1
self.ysh.phB = 1
self.ysh.phC = 1
# Scalar property accessors coerce assignments to the declared schema types.
@property
def G(self) -> float:
"""
Get ``G``.
:return: float
"""
return self._G
@G.setter
def G(self, val: float) -> None:
"""
Set ``G``.
:param val: Value to assign.
:return: None
"""
self._G = float(val)
@property
def G0(self) -> float:
"""
Get ``G0``.
:return: float
"""
return self._G0
@G0.setter
def G0(self, val: float) -> None:
"""
Set ``G0``.
:param val: Value to assign.
:return: None
"""
self._G0 = float(val)
@property
def Ga(self) -> float:
"""
Get ``Ga``.
:return: float
"""
return self._Ga
@Ga.setter
def Ga(self, val: float) -> None:
"""
Set ``Ga``.
:param val: Value to assign.
:return: None
"""
self._Ga = float(val)
@property
def Gb(self) -> float:
"""
Get ``Gb``.
:return: float
"""
return self._Gb
@Gb.setter
def Gb(self, val: float) -> None:
"""
Set ``Gb``.
:param val: Value to assign.
:return: None
"""
self._Gb = float(val)
@property
def Gc(self) -> float:
"""
Get ``Gc``.
:return: float
"""
return self._Gc
@Gc.setter
def Gc(self, val: float) -> None:
"""
Set ``Gc``.
:param val: Value to assign.
:return: None
"""
self._Gc = float(val)
@property
def B(self) -> float:
"""
Get ``B``.
:return: float
"""
return self._B
@B.setter
def B(self, val: float) -> None:
"""
Set ``B``.
:param val: Value to assign.
:return: None
"""
self._B = float(val)
@property
def B0(self) -> float:
"""
Get ``B0``.
:return: float
"""
return self._B0
@B0.setter
def B0(self, val: float) -> None:
"""
Set ``B0``.
:param val: Value to assign.
:return: None
"""
self._B0 = float(val)
@property
def Ba(self) -> float:
"""
Get ``Ba``.
:return: float
"""
return self._Ba
@Ba.setter
def Ba(self, val: float) -> None:
"""
Set ``Ba``.
:param val: Value to assign.
:return: None
"""
self._Ba = float(val)
@property
def Bb(self) -> float:
"""
Get ``Bb``.
:return: float
"""
return self._Bb
@Bb.setter
def Bb(self, val: float) -> None:
"""
Set ``Bb``.
:param val: Value to assign.
:return: None
"""
self._Bb = float(val)
@property
def Bc(self) -> float:
"""
Get ``Bc``.
:return: float
"""
return self._Bc
@Bc.setter
def Bc(self, val: float) -> None:
"""
Set ``Bc``.
:param val: Value to assign.
:return: None
"""
self._Bc = float(val)