# 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.enumerations import DeviceType, BuildStatus, ExternalGridMode, PrpCat
from VeraGridEngine.Devices.Parents.load_parent import LoadParent
from VeraGridEngine.Devices.Profiles import ProfileFloat
from VeraGridEngine.Devices.Parents.editable_device import get_at, GCProp
[docs]
class ExternalGrid(LoadParent):
__slots__ = (
'mode',
'substituted_device_id',
'_Vm',
'_Va',
'_Vm_prof',
'_Va_prof',
)
LOCAL_PROPERTY_DECLARATIONS: Tuple[GCProp, ...] = (
GCProp(
prop_name='mode',
units='',
tpe=ExternalGridMode,
definition='Operation mode of the external grid (voltage or load)',
cat=[PrpCat.PF],
),
GCProp(
prop_name='substituted_device_id',
units='',
tpe=str,
definition='idtag of the device that was substituted by this external grid equivalent',
),
GCProp(
prop_name='Vm',
units='p.u.',
tpe=float,
definition='Active power',
profile_name='Vm_prof',
cat=[PrpCat.PF],
),
GCProp(
prop_name='Va',
units='radians',
tpe=float,
definition='Reactive power',
profile_name='Va_prof',
cat=[PrpCat.PF],
),
)
def __init__(self, name='External grid', idtag=None, code='', active=True, substituted_device_id: str = '',
Vm=1.0, Va=0.0, P=0.0, Q=0.0,
P1=0.0, P2=0.0, P3=0.0, Q1=0.0, Q2=0.0, Q3=0.0,
mttf=0.0, mttr=0.0, mode: ExternalGridMode = ExternalGridMode.PQ,
capex=0, opex=0, build_status: BuildStatus = BuildStatus.Commissioned):
"""
External grid device
In essence, this is a slack-enforcer device
:param name:
:param idtag:
:param code:
:param active:
:param substituted_device_id:
:param Vm:
:param Va:
:param P:
:param Q:
:param mttf:
:param mttr:
:param mode:
:param capex:
:param opex:
:param build_status:
"""
LoadParent.__init__(self,
name=name,
idtag=idtag,
code=code,
bus=None,
active=active,
P=P,
P1=P1,
P2=P2,
P3=P3,
Q=Q,
Q1=Q1,
Q2=Q2,
Q3=Q3,
Cost=0,
mttf=mttf,
mttr=mttr,
capex=capex,
opex=opex,
build_status=build_status,
device_type=DeviceType.ExternalGridDevice)
self.mode: ExternalGridMode = mode
self.substituted_device_id: str = str(substituted_device_id)
# Impedance in equivalent MVA
self.Vm = float(Vm)
self.Va = float(Va)
self._Vm_prof = ProfileFloat(default_value=self.Vm)
self._Va_prof = ProfileFloat(default_value=self.Va)
@property
def Vm_prof(self) -> ProfileFloat:
"""
Cost profile
:return: Profile
"""
return self._Vm_prof
@Vm_prof.setter
def Vm_prof(self, val: Union[ProfileFloat, np.ndarray]):
if isinstance(val, ProfileFloat):
self._Vm_prof = val
elif isinstance(val, np.ndarray):
self._Vm_prof.set(arr=val)
else:
raise Exception(str(type(val)) + 'not supported to be set into a Vm_prof')
[docs]
def get_Vm_at(self, t: int | None) -> float:
"""
:param t:
:return:
"""
return get_at(self.Vm, self.Vm_prof, t)
@property
def Va_prof(self) -> ProfileFloat:
"""
Cost profile
:return: Profile
"""
return self._Va_prof
@Va_prof.setter
def Va_prof(self, val: Union[ProfileFloat, np.ndarray]):
if isinstance(val, ProfileFloat):
self._Va_prof = val
elif isinstance(val, np.ndarray):
self._Va_prof.set(arr=val)
else:
raise Exception(str(type(val)) + 'not supported to be set into a Va_prof')
[docs]
def get_Va_at(self, t: int | None) -> float:
"""
:param t:
:return:
"""
return get_at(self.Va, self.Va_prof, 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)
if self.mode == ExternalGridMode.VD:
y1 = self.Vm_prof.toarray()
title_1 = 'Voltage module'
units_1 = 'p.u'
y2 = self.Va_prof.toarray()
title_2 = 'Voltage angle'
units_2 = 'radians'
elif self.mode == ExternalGridMode.PQ:
y1 = self.P_prof.toarray()
title_1 = 'Active Power'
units_1 = 'MW'
y2 = self.Q_prof.toarray()
title_2 = 'Reactive power'
units_2 = 'MVAr'
else:
raise Exception('Unrecognised external grid mode: ' + str(self.mode))
ax_1.set_title(title_1, fontsize=14)
ax_1.set_ylabel(units_1, fontsize=11)
df = pd.DataFrame(data=y1, index=time, columns=[self.name])
df.plot(ax=ax_1)
df = pd.DataFrame(data=y2, index=time, columns=[self.name])
ax_2.set_title(title_2, fontsize=14)
ax_2.set_ylabel(units_2, fontsize=11)
df.plot(ax=ax_2)
plt.legend()
fig.suptitle(self.name, fontsize=20)
if show_fig:
plt.show()
# Scalar property accessors coerce assignments to the declared schema types.
@property
def Vm(self) -> float:
"""
Get ``Vm``.
:return: float
"""
return self._Vm
@Vm.setter
def Vm(self, val: float) -> None:
"""
Set ``Vm``.
:param val: Value to assign.
:return: None
"""
self._Vm = float(val)
@property
def Va(self) -> float:
"""
Get ``Va``.
:return: float
"""
return self._Va
@Va.setter
def Va(self, val: float) -> None:
"""
Set ``Va``.
:param val: Value to assign.
:return: None
"""
self._Va = float(val)