# 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
from VeraGridEngine.enumerations import DeviceType, BuildStatus, SubObjectType, ShuntControlMode, PrpCat
from VeraGridEngine.Devices.Parents.shunt_parent import ShuntParent
from VeraGridEngine.Devices.Profiles import ProfileFloat, ProfileInt
from VeraGridEngine.Devices.Substation.bus import Bus
from VeraGridEngine.Devices.Parents.editable_device import get_at, GCProp
from VeraGridEngine.basic_structures import Vec
[docs]
class ControllableShunt(ShuntParent):
"""
Controllable Shunt
"""
__slots__ = (
'_Bmin',
'_Bmax',
'_Gmin',
'_Gmax',
'g_per_step',
'b_per_step',
'_active_steps',
'_g_steps',
'_b_steps',
'_step',
'_step_prof',
'control_bus',
'_control_bus_prof',
'_Vset',
'_Vmin',
'_Vmax',
'_Vset_prof',
'control_mode',
)
LOCAL_PROPERTY_DECLARATIONS: Tuple[GCProp, ...] = (
GCProp(
prop_name='control_mode',
units='',
tpe=ShuntControlMode,
definition='Shunt control mode',
cat=[PrpCat.PF],
),
GCProp(
prop_name='control_bus',
units='',
tpe=DeviceType.BusDevice,
definition='Alternative control bus',
cat=[PrpCat.PF],
),
GCProp(
prop_name='g_steps',
units='MW@v=1p.u.',
tpe=SubObjectType.Array,
definition='Conductance steps',
editable=False,
cat=[PrpCat.PF],
),
GCProp(
prop_name='b_steps',
units='MVAr@v=1p.u.',
tpe=SubObjectType.Array,
definition='Susceptance steps',
editable=False,
cat=[PrpCat.PF],
),
GCProp(
prop_name='Gmax',
units='MW',
tpe=float,
definition='Maximum conductance',
editable=True,
cat=[PrpCat.PF, PrpCat.OPF],
),
GCProp(
prop_name='Gmin',
units='MW',
tpe=float,
definition='Minimum conductance',
editable=True,
cat=[PrpCat.PF, PrpCat.OPF],
),
GCProp(
prop_name='Bmax',
units='MVAr',
tpe=float,
definition='Maximum susceptance',
editable=True,
cat=[PrpCat.PF, PrpCat.OPF],
),
GCProp(
prop_name='Bmin',
units='MVAr',
tpe=float,
definition='Minimum susceptance',
editable=True,
cat=[PrpCat.PF, PrpCat.OPF],
),
GCProp(
prop_name='active_steps',
units='',
tpe=SubObjectType.Array,
definition='steps active?',
editable=False,
cat=[PrpCat.PF],
),
GCProp(
prop_name='step',
units='',
tpe=int,
definition='Device step position (0~N-1)',
profile_name='step_prof',
cat=[PrpCat.PF],
),
GCProp(
prop_name='Vmin',
units='p.u.',
tpe=float,
definition='Lower range voltage value when regulating with Discrete mode',
cat=[PrpCat.PF, PrpCat.OPF],
),
GCProp(
prop_name='Vset',
units='p.u.',
tpe=float,
definition='Set voltage. This is used for controlled shunts.',
profile_name='Vset_prof',
cat=[PrpCat.PF],
),
GCProp(
prop_name='Vmax',
units='p.u.',
tpe=float,
definition='Upper range voltage value when regulating with Discrete mode.',
cat=[PrpCat.PF, PrpCat.OPF],
),
)
def __init__(self,
name='Controllable Shunt',
idtag: Union[None, str] = None,
code: str = '',
number_of_steps: int = 1,
step: int = 1,
g_per_step: float = 0.0,
b_per_step: float = 0.0,
Bmin: float = -9999.0,
Bmax: float = 9999.0,
Gmin: float = -9999.0,
Gmax: float = 9999.0,
Cost: float = 1200.0,
active: bool = True,
G: float = 1e-20,
G1: float = 1e-20,
G2: float = 1e-20,
G3: float = 1e-20,
B: float = 1e-20,
B1: float = 1e-20,
B2: float = 1e-20,
B3: float = 1e-20,
G0: float = 1e-20,
B0: float = 1e-20,
vset: float = 1.0,
vmin: float = 0.9,
vmax: float = 1.1,
mttf: float = 0.0,
mttr: float = 0.0,
capex: float = 0.0,
opex: float = 0.0,
control_bus: Bus = None,
control_mode: ShuntControlMode = ShuntControlMode.Continuous,
build_status: BuildStatus = BuildStatus.Commissioned):
"""
The controllable shunt object implements the so-called ZIP model, in which the load can be
represented by a combination of power (P), current(I), and impedance (Z).
The sign convention is: Positive to act as a load, negative to act as a generator.
:param name: Name of the load
:param idtag: UUID code
:param code: secondary ID code
:param Cost: Cost of load shedding
:param active: Is the load active?
:param mttf: Mean time to failure in hours
:param mttr: Mean time to recovery in hours
"""
ShuntParent.__init__(self,
name=name,
idtag=idtag,
code=code,
bus=None,
active=active,
G=G,
G1=G1,
G2=G2,
G3=G3,
B=B,
B1=B1,
B2=B2,
B3=B3,
G0=G0,
B0=B0,
Cost=Cost,
mttf=mttf,
mttr=mttr,
capex=capex,
opex=opex,
build_status=build_status,
device_type=DeviceType.ControllableShuntDevice)
self.Bmin = Bmin
self.Bmax = Bmax
self.Gmin = Gmin
self.Gmax = Gmax
if number_of_steps > 0:
self.g_per_step = (self.Gmin + self.Gmax) / number_of_steps
self.b_per_step = (self.Bmin + self.Bmax) / number_of_steps
self._active_steps = np.ones(number_of_steps, dtype=int)
else:
self.g_per_step = 0
self.b_per_step = 0
self._active_steps = np.ones(0, dtype=int)
self._g_steps = np.full(number_of_steps, g_per_step)
self._b_steps = np.full(number_of_steps, b_per_step)
self._step = int(step)
self._step_prof = ProfileInt(default_value=self._step)
self.control_bus = control_bus
# Voltage module set point (p.u.)
self.Vset = float(vset)
# voltage set profile for this load in p.u.
self._Vset_prof = ProfileFloat(default_value=self.Vset)
self.Vmin = float(vmin)
self.Vmax = float(vmax)
self.control_mode: ShuntControlMode = control_mode
@property
def step(self):
"""
Step
:return:
"""
return self._step
@step.setter
def step(self, value: int):
value = int(value)
if self.auto_update_enabled:
if 0 <= value < len(self._b_steps):
self._step = int(value)
# override value on change
self.B = np.sum(self._b_steps[:self._step + 1] * self._active_steps[:self._step + 1])
self.G = np.sum(self._g_steps[:self._step + 1] * self._active_steps[:self._step + 1])
else:
self._step = int(value)
@property
def g_steps(self):
"""
G steps
:return:
"""
return self._g_steps
@g_steps.setter
def g_steps(self, value: np.ndarray):
assert isinstance(value, np.ndarray)
self._g_steps = value
@property
def active_steps(self):
"""
G steps
:return:
"""
return self._active_steps
@active_steps.setter
def active_steps(self, value: np.ndarray):
assert isinstance(value, np.ndarray)
self._active_steps = value.astype(int)
[docs]
def set_blocks(self, n_list: list[int], b_list: list[float]):
"""
Initialize the steps from block data
:param n_list: list of number of blocks per step
:param b_list: list of unit impedance block at each step
"""
assert len(n_list) == len(b_list)
nn = len(n_list)
self._active_steps = np.ones(nn, dtype=int)
self._b_steps = np.zeros(nn)
self._g_steps = np.zeros(nn)
for i in range(nn):
self._b_steps[i] = n_list[i] * b_list[i]
[docs]
def get_block_points(self):
"""
Get B points for CGMES export.
:return:
:rtype:
"""
return self._b_steps, self._g_steps
[docs]
def get_cumulative_b(self) -> Vec:
"""
Get the cumulative B values
:return:
"""
return np.cumsum(self._b_steps * self._active_steps).astype(float)
[docs]
def get_cumulative_g(self) -> Vec:
"""
Get the cumulative G values
:return:
"""
return np.cumsum(self._g_steps * self._active_steps).astype(float)
@property
def b_steps(self):
"""
B steps
:return:
"""
return self._b_steps
@b_steps.setter
def b_steps(self, value: np.ndarray):
assert isinstance(value, np.ndarray)
self._b_steps = value
# self.Bmax = value.max()
# self.Bmin = value.min()
@property
def Vset_prof(self) -> ProfileFloat:
"""
Cost profile
:return: Profile
"""
return self._Vset_prof
@Vset_prof.setter
def Vset_prof(self, val: Union[ProfileFloat, np.ndarray]):
if isinstance(val, ProfileFloat):
self._Vset_prof = val
elif isinstance(val, np.ndarray):
self._Vset_prof.set(arr=val)
else:
raise Exception(str(type(val)) + 'not supported to be set into a Vset_prof')
[docs]
def get_Vset_at(self, t: int | None) -> float:
"""
:param t:
:return:
"""
return get_at(self.Vset, self.Vset_prof, t)
@property
def step_prof(self) -> ProfileInt:
"""
Cost profile
:return: Profile
"""
return self._step_prof
@step_prof.setter
def step_prof(self, val: Union[ProfileInt, np.ndarray]):
if isinstance(val, ProfileInt):
self._step_prof = val
elif isinstance(val, np.ndarray):
self._step_prof.set(arr=val)
else:
raise Exception(str(type(val)) + 'not supported to be set into a step_prof')
[docs]
def get_step_at(self, t: int | None) -> float:
"""
:param t:
:return:
"""
return get_at(self.step, self.step_prof, t)
[docs]
def get_linear_g_steps(self):
"""
:return:
"""
if len(self._g_steps) == 1:
return self._g_steps
else:
return np.diff(self._g_steps)
[docs]
def get_linear_b_steps(self):
"""
:return:
"""
if len(self._b_steps) == 1:
return self._b_steps
else:
return np.diff(self._b_steps)
# Scalar property accessors coerce assignments to the declared schema types.
@property
def Gmax(self) -> float:
"""
Get ``Gmax``.
:return: float
"""
return self._Gmax
@Gmax.setter
def Gmax(self, val: float) -> None:
"""
Set ``Gmax``.
:param val: Value to assign.
:return: None
"""
self._Gmax = float(val)
@property
def Gmin(self) -> float:
"""
Get ``Gmin``.
:return: float
"""
return self._Gmin
@Gmin.setter
def Gmin(self, val: float) -> None:
"""
Set ``Gmin``.
:param val: Value to assign.
:return: None
"""
self._Gmin = float(val)
@property
def Bmax(self) -> float:
"""
Get ``Bmax``.
:return: float
"""
return self._Bmax
@Bmax.setter
def Bmax(self, val: float) -> None:
"""
Set ``Bmax``.
:param val: Value to assign.
:return: None
"""
self._Bmax = float(val)
@property
def Bmin(self) -> float:
"""
Get ``Bmin``.
:return: float
"""
return self._Bmin
@Bmin.setter
def Bmin(self, val: float) -> None:
"""
Set ``Bmin``.
:param val: Value to assign.
:return: None
"""
self._Bmin = float(val)
@property
def Vmin(self) -> float:
"""
Get ``Vmin``.
:return: float
"""
return self._Vmin
@Vmin.setter
def Vmin(self, val: float) -> None:
"""
Set ``Vmin``.
:param val: Value to assign.
:return: None
"""
self._Vmin = float(val)
@property
def Vset(self) -> float:
"""
Get ``Vset``.
:return: float
"""
return self._Vset
@Vset.setter
def Vset(self, val: float) -> None:
"""
Set ``Vset``.
:param val: Value to assign.
:return: None
"""
self._Vset = float(val)
@property
def Vmax(self) -> float:
"""
Get ``Vmax``.
:return: float
"""
return self._Vmax
@Vmax.setter
def Vmax(self, val: float) -> None:
"""
Set ``Vmax``.
:param val: Value to assign.
:return: None
"""
self._Vmax = float(val)