Source code for VeraGridEngine.Devices.Injections.generator

# 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

import numpy as np
import pandas as pd
from typing import Union, Tuple, List
from matplotlib import pyplot as plt

from VeraGridEngine.basic_structures import Logger, CxVec
from VeraGridEngine.Devices.Substation.bus import Bus
from VeraGridEngine.enumerations import (DeviceType, BuildStatus, SubObjectType, GeneratorType, PrpCat,
                                         GeneratorControlMode)
from VeraGridEngine.Devices.Associations.association import Associations
from VeraGridEngine.Devices.Associations.fuel import Fuel
from VeraGridEngine.Devices.Associations.emission_gas import EmissionGas
from VeraGridEngine.Devices.Injections.generator_q_curve import GeneratorQCurve
from VeraGridEngine.Devices.Profiles import ProfileBool, ProfileFloat
from VeraGridEngine.Devices.Parents.editable_device import get_at, GCProp
from VeraGridEngine.Devices.Parents.injection_parent import InjectionParent


[docs] def compute_q(p: float, pf: float) -> float: """ Compute the reactive power from p and the power factor :param p: Active power :param pf: power factor :return: Reactive power """ sign = 1.0 if pf >= 0.0 else -1.0 if -1.0 <= pf <= 1.0: return sign * p * np.sqrt(1.0 / (pf * pf) - 1.0) else: return sign * p
[docs] def compute_pf(p: float, q: float) -> float: """ Compute the power factor from p and q :param p: Active power :param q: Reactive power :return: Power factor """ s = np.sqrt(p * p + q * q) sign = 1.0 if q >= 0.0 else -1.0 if s > 0: return sign * p / s else: return sign
[docs] class Generator(InjectionParent): __slots__ = ( 'control_bus', 'control_cn', '_P', '_P_prof', '_Pmax', '_Pmax_prof', '_Pmin', '_Pmin_prof', '_Q', '_Q_prof', '_Qmin_prof', '_Qmax_prof', 'qmin_set', 'qmax_set', '_srap_enabled', '_srap_enabled_prof', '_enabled_dispatch', '_enabled_dispatch_prof', '_R1', '_X1', '_R0', '_X0', '_R2', '_X2', '_Rs', '_Xs', '_Xm', '_Rr', '_Xr', '_Pf', '_Pf_prof', '_control_mode', '_Snom', '_Vset', '_Vset_prof', '_k_droop', '_dead_band', '_use_reactive_power_curve', 'q_curve', 'custom_q_points', '_Cost2', '_Cost0', '_StartupCost', '_ShutdownCost', '_MinTimeUp', '_MinTimeDown', '_RampUp', '_RampDown', '_Cost2_prof', '_Cost0_prof', 'emissions', 'fuels', 'Sbase', 'freq', '_must_run', '_must_run_prof', 'tpe' ) LOCAL_PROPERTY_DECLARATIONS: Tuple[GCProp, ...] = ( GCProp( prop_name='P', units='MW', tpe=float, definition='Active power', profile_name='P_prof', cat=[PrpCat.PF], ), GCProp( prop_name='Pmin', units='MW', tpe=float, definition='Minimum active power. Used in OPF.', profile_name='Pmin_prof', cat=[PrpCat.OPF], ), GCProp( prop_name='Pmax', units='MW', tpe=float, definition='Maximum active power. Used in OPF.', profile_name='Pmax_prof', cat=[PrpCat.OPF], ), GCProp( prop_name='Q', units='MVAr', tpe=float, definition='Reactive power', profile_name='Q_prof', cat=[PrpCat.PF], ), GCProp( prop_name='Qmin', units='MVAr', tpe=float, definition='Minimum reactive power.', profile_name='Qmin_prof', cat=[PrpCat.PF, PrpCat.OPF], ), GCProp( prop_name='Qmax', units='MVAr', tpe=float, definition='Maximum reactive power.', profile_name='Qmax_prof', cat=[PrpCat.PF, PrpCat.OPF], ), GCProp( prop_name='control_mode', units='', tpe=GeneratorControlMode, definition='Generator control mode', cat=[PrpCat.PF], old_names=("is_controlled",) ), GCProp( prop_name='control_bus', units='', tpe=DeviceType.BusDevice, definition='Control bus', editable=True, cat=[PrpCat.PF], ), GCProp( prop_name='Pf', units='', tpe=float, definition='Power factor (cos(phi)). This is used for non-controlled generators.', profile_name='Pf_prof', cat=[PrpCat.PF], ), GCProp( prop_name='Vset', units='p.u.', tpe=float, definition='Set voltage. This is used for controlled generators.', profile_name='Vset_prof', cat=[PrpCat.PF], ), GCProp( prop_name='k_droop', units='', tpe=float, definition='QV droop constant.', cat=[PrpCat.PF], ), GCProp( prop_name='dead_band', units='kV', tpe=float, definition='Droop dead band.', cat=[PrpCat.PF], ), GCProp( prop_name='Snom', units='MVA', tpe=float, definition='Nominal power.', cat=[PrpCat.TP], ), GCProp( prop_name='use_reactive_power_curve', units='', tpe=bool, definition='Use the reactive power capability curve?', cat=[PrpCat.PF], ), GCProp( prop_name='q_curve', units='MVAr', tpe=SubObjectType.GeneratorQCurve, definition='Capability curve data (double click on the generator to edit)', editable=False, display=False, cat=[PrpCat.PF], ), GCProp( prop_name='R1', units='p.u.', tpe=float, definition='Total positive sequence resistance.', cat=[PrpCat.SC], ), GCProp( prop_name='X1', units='p.u.', tpe=float, definition='Total positive sequence reactance.', cat=[PrpCat.SC], ), GCProp( prop_name='R0', units='p.u.', tpe=float, definition='Total zero sequence resistance.', cat=[PrpCat.SC], ), GCProp( prop_name='X0', units='p.u.', tpe=float, definition='Total zero sequence reactance.', cat=[PrpCat.SC], ), GCProp( prop_name='R2', units='p.u.', tpe=float, definition='Total negative sequence resistance.', cat=[PrpCat.SC], ), GCProp( prop_name='X2', units='p.u.', tpe=float, definition='Total negative sequence reactance.', cat=[PrpCat.SC], ), GCProp( prop_name='Rs', units='p.u.', tpe=float, definition='Stator winding resistance (AG).', cat=[PrpCat.PF], ), GCProp( prop_name='Xs', units='p.u.', tpe=float, definition='Stator leakage reactance (AG).', cat=[PrpCat.PF, PrpCat.SC], ), GCProp( prop_name='Xm', units='p.u.', tpe=float, definition='Magnetizing reactance (AG).', cat=[PrpCat.PF, PrpCat.SC], ), GCProp( prop_name='Rr', units='p.u.', tpe=float, definition='Rotor resistance (AG).', cat=[PrpCat.PF, PrpCat.SC], ), GCProp( prop_name='Xr', units='p.u.', tpe=float, definition='Rotor reactance (AG).', cat=[PrpCat.PF, PrpCat.SC], ), GCProp( prop_name='Cost2', units='e/MWΒ²/h', tpe=float, definition='Generation quadratic cost. Used in OPF.', profile_name='Cost2_prof', cat=[PrpCat.OPF], ), GCProp( prop_name='Cost0', units='e/h', tpe=float, definition='Generation constant cost. Used in OPF.', profile_name='Cost0_prof', cat=[PrpCat.OPF], ), GCProp( prop_name='startup_cost', units='e/h', tpe=float, definition='Generation start-up cost. Used in OPF.', old_names=["StartupCost"], cat=[PrpCat.OPF], ), GCProp( prop_name='shutdown_cost', units='e/h', tpe=float, definition='Generation shut-down cost. Used in OPF.', old_names=["ShutdownCost"], cat=[PrpCat.OPF], ), GCProp( prop_name='min_time_up', units='h', tpe=float, definition='Minimum time that the generator has to be on when started. Used in OPF.', old_names=["MinTimeUp"], cat=[PrpCat.OPF], ), GCProp( prop_name='min_time_down', units='h', tpe=float, definition='Minimum time that the generator has to be off when shut down. Used in OPF.', old_names=["MinTimeDown"], cat=[PrpCat.OPF], ), GCProp( prop_name='ramp_up', units='MW/h', tpe=float, definition='Maximum amount of generation increase per hour.', old_names=["RampUp"], cat=[PrpCat.OPF], ), GCProp( prop_name='ramp_down', units='MW/h', tpe=float, definition='Maximum amount of generation decrease per hour.', old_names=["RampDown"], cat=[PrpCat.OPF], ), GCProp( prop_name='enabled_dispatch', units='', tpe=bool, profile_name="enabled_dispatch_prof", definition='Enabled for dispatch? Used in OPF.', cat=[PrpCat.OPF], ), GCProp( prop_name='must_run', units='', tpe=bool, profile_name="must_run_prof", definition='P >= Pmin constraint. Used in OPF with unit commitment active.', cat=[PrpCat.OPF], ), GCProp( prop_name='emissions', units='t/MWh', tpe=SubObjectType.Associations, definition='List of emissions', display=False, cat=[PrpCat.OPF], ), GCProp( prop_name='fuels', units='t/MWh', tpe=SubObjectType.Associations, definition='List of fuels', display=False, cat=[PrpCat.OPF], ), GCProp( prop_name='srap_enabled', units='', tpe=bool, definition='Is the unit available for SRAP participation?', editable=True, profile_name="srap_enabled_prof", cat=[PrpCat.CON], ), GCProp( prop_name='tpe', units='', tpe=GeneratorType, definition='Machine type of the generator.', cat=[PrpCat.PF, PrpCat.TP], ), ) def __init__(self, name='gen', idtag: Union[str, None] = None, code: str = '', P: float = 0.0, Q: float = 0.0, power_factor: float = 0.8, vset: float = 1.0, control_mode: GeneratorControlMode = GeneratorControlMode.V, k_droop=1.0, dead_band=0.0, Qmin: float = -9999, Qmax: float = 9999, Snom: float = 9999, active: bool = True, Pmin: float = 0.0, Pmax: float = 9999.0, Cost: float = 1.0, Cost2: float = 0.0, Cost0: float = 0.0, Sbase: float = 100, enabled_dispatch=True, mttf: float = 0.0, mttr: float = 0.0, q_points=None, use_reactive_power_curve=False, r1: float = 1e-20, x1: float = 1e-20, r0: float = 1e-20, x0: float = 1e-20, r2: float = 1e-20, x2: float = 1e-20, Rs: float = 1e-20, Xs: float = 1e-20, Xm: float = 1e-20, Rr: float = 1e-20, Xr: float = 1e-20, freq=60.0, capex: float = 0, opex: float = 0, srap_enabled: bool = True, build_status: BuildStatus = BuildStatus.Commissioned, must_run: bool = False, startup_cost=0.0, shutdown_cost=0.0, min_time_up=0.0, min_time_down=0.0, ramp_up=1e20, ramp_down=1e20, tpe: GeneratorType = GeneratorType.Synchronous): """ :param name: Name of the generator :param idtag: UUID code :param code: secondary code :param P: Active power in MW :param power_factor: Power factor :param vset: Voltage set point in per unit :param control_mode: Generator control mode :param Qmin: Minimum reactive power in MVAr :param Qmax: Maximum reactive power in MVAr :param Snom: Nominal apparent power in MVA :param active: Is the generator active? :param Pmin: Minimum active power :param Pmax: Maximum active power :param Cost: Proportional cost [e/MWh] :param Cost2: Quadratic cost [e/MWh^2] :param Cost0: Fixed cost [e] :param Sbase: Nominal apparent power in MVA :param enabled_dispatch: Is the generator enabled for OPF? :param mttf: Mean time to failure [h] :param mttr: Mean time to recovery [h] :param q_points: list of reactive capability curve points [(P1, Qmin1, Qmax1), (P2, Qmin2, Qmax2), ...] :param use_reactive_power_curve: Use the reactive power curve? otherwise use the plain old limits :param r1: :param x1: :param r0: :param x0: :param r2: :param x2: :param Rs: Stator winding resistance [pu] :param Xs: Stator leakage reactance [pu] :param Xm: Magnetising reactance [pu] :param Rr: Rotor resistance [pu] :param Xr: Rotor reactance [pu] :param freq: :param capex: :param opex: :param srap_enabled: :param build_status: :param must_run: :param tpe: Machine type of the generator, as it can be synchronous or asynchronous """ InjectionParent.__init__(self, name=name, idtag=idtag, code=code, bus=None, active=active, Cost=Cost, mttf=mttf, mttr=mttr, capex=capex, opex=opex, build_status=build_status, device_type=DeviceType.GeneratorDevice) self.control_bus: Bus | None = None self.control_cn = None self._P = float(P) self._P_prof = ProfileFloat(default_value=self.P) self.Pmax = float(Pmax) self._Pmax_prof = ProfileFloat(default_value=self.Pmax) self.Pmin = float(Pmin) self._Pmin_prof = ProfileFloat(default_value=self.Pmin) self._Q = float(Q) self._Q_prof = ProfileFloat(default_value=self.Q) self._Qmin_prof = ProfileFloat(default_value=Qmin) self._Qmax_prof = ProfileFloat(default_value=Qmax) self.qmin_set = float(Qmin) self.qmax_set = float(Qmax) self.srap_enabled = bool(srap_enabled) self._srap_enabled_prof = ProfileBool(default_value=self.srap_enabled) # is the device active for active power dispatch? self.enabled_dispatch = bool(enabled_dispatch) self._enabled_dispatch_prof = ProfileBool(default_value=self.enabled_dispatch) self.must_run = bool(must_run) self._must_run_prof = ProfileBool(default_value=self.must_run) # positive sequence resistance self.R1 = float(r1) # positive sequence reactance self.X1 = float(x1) # zero sequence resistance self.R0 = float(r0) # zero sequence reactance self.X0 = float(x0) # negative sequence resistance self.R2 = float(r2) # negative sequence reactance self.X2 = float(x2) # stator winding resistance self.Rs = float(Rs) # stator leakage reactance self.Xs = float(Xs) # magnetizing reactance self.Xm = float(Xm) # rotor resistance self.Rr = float(Rr) # rotor reactance self.Xr = float(Xr) # Power factor self._Pf = float(power_factor) # voltage set profile for this load in p.u. self._Pf_prof = ProfileFloat(default_value=self.Pf) # If this generator is voltage controlled it produces a PV node, otherwise the node remains as PQ self._control_mode: GeneratorControlMode = control_mode # Nominal power in MVA (also the machine base) self._Snom = float(Snom) # 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.k_droop = k_droop self.dead_band = dead_band self.use_reactive_power_curve = bool(use_reactive_power_curve) # declare the generation curve self.q_curve = GeneratorQCurve() if q_points is not None: self.q_curve.set(np.array(q_points)) self.custom_q_points = True else: self.q_curve.make_default_q_curve(self.Snom, self.qmin_set, self.qmax_set, n=1) self.custom_q_points = False self.Cost2 = float(Cost2) # Cost of operation e/MWΒ² self.Cost0 = float(Cost0) # Cost of operation e self.startup_cost = startup_cost self.shutdown_cost = shutdown_cost self.min_time_up = min_time_up self.min_time_down = min_time_down self.ramp_up = ramp_up self.ramp_down = ramp_down self._Cost2_prof = ProfileFloat(default_value=self.Cost2) self._Cost0_prof = ProfileFloat(default_value=self.Cost0) self.emissions: Associations = Associations(device_type=DeviceType.EmissionGasDevice) self.fuels: Associations = Associations(device_type=DeviceType.FuelDevice) # system base power MVA self.Sbase = float(Sbase) self.freq = freq self.tpe: GeneratorType = tpe @property def P(self) -> float: """ Get the active power value :return: float """ return self._P @P.setter def P(self, val: float): """ Set active power value :param val: some float """ val = float(val) try: self._P = float(val) except ValueError: print("The value you're trying to set into P is not a float :(") @property def P_prof(self) -> ProfileFloat: """ Cost profile :return: Profile """ return self._P_prof @P_prof.setter def P_prof(self, val: Union[ProfileFloat, np.ndarray]): if isinstance(val, ProfileFloat): self._P_prof = val elif isinstance(val, np.ndarray): self._P_prof.set(arr=val) else: raise Exception(str(type(val)) + 'not supported to be set into a P_prof')
[docs] def get_P_at(self, t: int | None) -> float: """ :param t: :return: """ return get_at(self.P, self.P_prof, t)
@property def Q(self) -> float: """ Get the active power value :return: float """ return self._Q @Q.setter def Q(self, val: float): """ Set active power value :param val: some float """ val = float(val) try: self._Q = float(val) except ValueError: print("The value you're trying to set into Q is not a float :(") @property def Q_prof(self) -> ProfileFloat: """ Q profile :return: Profile """ return self._Q_prof @Q_prof.setter def Q_prof(self, val: Union[ProfileFloat, np.ndarray]): if isinstance(val, ProfileFloat): self._Q_prof = val elif isinstance(val, np.ndarray): self._Q_prof.set(arr=val) else: raise Exception(str(type(val)) + 'not supported to be set into a Q_prof')
[docs] def get_Q_at(self, t: int | None) -> float: """ :param t: :return: """ return get_at(self.Q, self.Q_prof, t)
@property def Qmin(self): """ Return the reactive power lower limit :return: value """ return self.qmin_set @Qmin.setter def Qmin(self, val): val = float(val) self.qmin_set = val @property def Qmin_prof(self) -> ProfileFloat: """ Qmin profile :return: Profile """ return self._Qmin_prof @Qmin_prof.setter def Qmin_prof(self, val: Union[ProfileFloat, np.ndarray]): if isinstance(val, ProfileFloat): self._Qmin_prof = val elif isinstance(val, np.ndarray): self._Qmin_prof.set(arr=val) else: raise Exception(str(type(val)) + 'not supported to be set into a Qmin_prof')
[docs] def get_Qmin_at(self, t: int | None) -> float: """ :param t: :return: """ return get_at(self.Qmin, self.Qmin_prof, t)
@property def Qmax(self): """ Return the reactive power upper limit :return: value """ return self.qmax_set @Qmax.setter def Qmax(self, val): val = float(val) self.qmax_set = val @property def Qmax_prof(self) -> ProfileFloat: """ Qmax profile :return: Profile """ return self._Qmax_prof @Qmax_prof.setter def Qmax_prof(self, val: Union[ProfileFloat, np.ndarray]): if isinstance(val, ProfileFloat): self._Qmax_prof = val elif isinstance(val, np.ndarray): self._Qmax_prof.set(arr=val) else: raise Exception(str(type(val)) + 'not supported to be set into a Qmax_prof')
[docs] def get_Qmax_at(self, t: int | None) -> float: """ :param t: :return: """ return get_at(self.Qmax, self.Qmax_prof, t)
@property def srap_enabled_prof(self) -> ProfileBool: """ Control bus profile :return: Profile """ return self._srap_enabled_prof @srap_enabled_prof.setter def srap_enabled_prof(self, val: Union[ProfileBool, np.ndarray]): if isinstance(val, ProfileBool): self._srap_enabled_prof = val elif isinstance(val, np.ndarray): self._srap_enabled_prof.set(arr=val) else: raise Exception(str(type(val)) + 'not supported to be set into srap_enabled_prof')
[docs] def get_srap_enabled_at(self, t: int | None) -> float: """ :param t: :return: """ return get_at(self.srap_enabled, self.srap_enabled_prof, t)
@property def Pmax_prof(self) -> ProfileFloat: """ Pmax profile :return: Profile """ return self._Pmax_prof @Pmax_prof.setter def Pmax_prof(self, val: Union[ProfileFloat, np.ndarray]): if isinstance(val, ProfileFloat): self._Pmax_prof = val elif isinstance(val, np.ndarray): self._Pmax_prof.set(arr=val) else: raise Exception(str(type(val)) + 'not supported to be set into a Pmax_prof')
[docs] def get_Pmax_at(self, t: int | None) -> float: """ :param t: :return: """ return get_at(self.Pmax, self.Pmax_prof, t)
@property def Pmin_prof(self) -> ProfileFloat: """ Pmin profile :return: Profile """ return self._Pmin_prof @Pmin_prof.setter def Pmin_prof(self, val: Union[ProfileFloat, np.ndarray]): if isinstance(val, ProfileFloat): self._Pmin_prof = val elif isinstance(val, np.ndarray): self._Pmin_prof.set(arr=val) else: raise Exception(str(type(val)) + 'not supported to be set into a Pmin_prof')
[docs] def get_Pmin_at(self, t: int | None) -> float: """ :param t: :return: """ return get_at(self.Pmin, self.Pmin_prof, t)
[docs] def get_S_with_sign(self) -> complex: """ :return: """ return complex(self.P, 0.0)
[docs] def get_Sprof_with_sign(self) -> CxVec: """ :return: """ return self.P_prof.toarray().astype(complex)
@property def Pmin(self) -> float: """ Get ``Pmin``. :return: float """ return self._Pmin @Pmin.setter def Pmin(self, val: float) -> None: """ Set ``Pmin``. :param val: Value to assign. :return: None """ self._Pmin = float(val) @property def Pmax(self) -> float: """ Get ``Pmax``. :return: float """ return self._Pmax @Pmax.setter def Pmax(self, val: float) -> None: """ Set ``Pmax``. :param val: Value to assign. :return: None """ self._Pmax = float(val) @property def srap_enabled(self) -> bool: """ Get ``srap_enabled``. :return: bool """ return self._srap_enabled @srap_enabled.setter def srap_enabled(self, val: bool) -> None: """ Set ``srap_enabled``. :param val: Value to assign. :return: None """ self._srap_enabled = bool(val) @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 Cost2_prof(self) -> ProfileFloat: """ Cost profile :return: Profile """ return self._Cost2_prof @Cost2_prof.setter def Cost2_prof(self, val: Union[ProfileFloat, np.ndarray]): if isinstance(val, ProfileFloat): self._Cost2_prof = val elif isinstance(val, np.ndarray): self._Cost2_prof.set(arr=val) else: raise Exception(str(type(val)) + 'not supported to be set into a Cost2_prof')
[docs] def get_Cost2_at(self, t: int | None) -> float: """ :param t: :return: """ return get_at(self.Cost2, self.Cost2_prof, t)
@property def Cost0_prof(self) -> ProfileFloat: """ Cost profile :return: Profile """ return self._Cost0_prof @Cost0_prof.setter def Cost0_prof(self, val: Union[ProfileFloat, np.ndarray]): if isinstance(val, ProfileFloat): self._Cost0_prof = val elif isinstance(val, np.ndarray): self._Cost0_prof.set(arr=val) else: raise Exception(str(type(val)) + 'not supported to be set into a Cost0_prof')
[docs] def get_Cost0_at(self, t: int | None) -> float: """ :param t: :return: """ return get_at(self.Cost0, self.Cost0_prof, t)
@property def enabled_dispatch_prof(self) -> ProfileBool: """ Cost profile :return: Profile """ return self._enabled_dispatch_prof @enabled_dispatch_prof.setter def enabled_dispatch_prof(self, val: Union[ProfileBool, np.ndarray]): if isinstance(val, ProfileBool): self._enabled_dispatch_prof = val elif isinstance(val, np.ndarray): self._enabled_dispatch_prof.set(arr=val) else: raise Exception(str(type(val)) + 'not supported to be set into a Cost0_prof')
[docs] def get_enabled_dispatch_at(self, t: int | None) -> float: """ :param t: :return: """ return get_at(self.enabled_dispatch, self.enabled_dispatch_prof, t)
@property def must_run_prof(self) -> ProfileBool: """ Cost profile :return: Profile """ return self._must_run_prof @must_run_prof.setter def must_run_prof(self, val: Union[ProfileBool, np.ndarray]): if isinstance(val, ProfileBool): self._must_run_prof = val elif isinstance(val, np.ndarray): self._must_run_prof.set(arr=val) else: raise Exception(str(type(val)) + 'not supported to be set into a Cost0_prof')
[docs] def get_must_run_at(self, t: int | None) -> float: """ :param t: :return: """ return get_at(self.must_run, self.must_run_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, sharex=ax_1) # P y = self.P_prof.toarray() df = pd.DataFrame(data=y, index=time, columns=[self.name]) ax_1.set_title('Active power', fontsize=14) ax_1.set_ylabel('MW', fontsize=11) df.plot(ax=ax_1) # V y = self.Vset_prof.toarray() df = pd.DataFrame(data=y, index=time, columns=[self.name]) ax_2.set_title('Voltage Set point', fontsize=14) ax_2.set_ylabel('p.u.', fontsize=11) df.plot(ax=ax_2) plt.legend() fig.suptitle(self.name, fontsize=20) if show_fig: plt.show()
[docs] def fix_inconsistencies(self, logger: Logger, min_vset=0.98, max_vset=1.02): """ Correct the voltage set points :param logger: logger to store the events :param min_vset: minimum voltage set point (p.u.) :param max_vset: maximum voltage set point (p.u.) :return: True if any correction happened """ errors = False if self.Vset > max_vset: logger.add_warning("Corrected generator set point", self.name, self.Vset, max_vset) self.Vset = max_vset errors = True elif self.Vset < min_vset: logger.add_warning("Corrected generator set point", self.name, self.Vset, min_vset) self.Vset = min_vset errors = True return errors
@property def Snom(self): """ Return the reactive power lower limit :return: value """ return self._Snom @Snom.setter def Snom(self, val): """ Set the generator nominal power if the reactive power curve was generated automatically, then it is refreshed :param val: float value """ val = float(val) self._Snom = val def __iadd__(self, other: "Generator"): """ Add another generator here :param other: Generator to add """ self.P += other.P self.P_prof = self.P_prof.toarray() + other.P_prof.toarray() self.Pmax += other.Pmax self.Pmin += other.Pmin self.Qmax += other.Qmax self.Qmin += other.Qmin # Scalar property accessors coerce assignments to the declared schema types. @property def is_controlled(self) -> bool: """ Get ``is_controlled``. :return: bool """ return self._control_mode == GeneratorControlMode.V @is_controlled.setter def is_controlled(self, val: bool) -> None: """ Set ``is_controlled``. :param val: Value to assign. :return: None """ self._control_mode = GeneratorControlMode.V if val else GeneratorControlMode.Q @property def control_mode(self) -> GeneratorControlMode: """ Get ``control_mode``. :return: """ return self._control_mode @control_mode.setter def control_mode(self, val: GeneratorControlMode) -> None: """ Set control mode :param val: :return: """ self._control_mode = val @property def Pf(self) -> float: """ Get ``Pf``. :return: float """ return self._Pf @Pf.setter def Pf(self, val: float) -> None: """ Set ``Pf``. :param val: Value to assign. :return: None """ self._Pf = float(val) if self.auto_update_enabled: self.Q = compute_q(p=self.P, pf=self._Pf) @property def Pf_prof(self) -> ProfileFloat: """ Cost profile :return: Profile """ return self._Pf_prof @Pf_prof.setter def Pf_prof(self, val: Union[ProfileFloat, np.ndarray]): if isinstance(val, ProfileFloat): self._Pf_prof = val elif isinstance(val, np.ndarray): self._Pf_prof.set(arr=val) else: raise Exception(str(type(val)) + 'not supported to be set into a Pf_prof')
[docs] def get_Pf_at(self, t: int | None) -> float: """ :param t: :return: """ return get_at(self.Pf, self.Pf_prof, t)
@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 k_droop(self) -> float: """ Get ``k_droop``. :return: float """ return self._k_droop @k_droop.setter def k_droop(self, val: float) -> None: """ Set ``k_droop``. :param val: Value to assign. :return: None """ self._k_droop = float(val) @property def dead_band(self) -> float: """ Get ``dead_band``. :return: float """ return self._dead_band @dead_band.setter def dead_band(self, val: float) -> None: """ Set ``dead_band``. :param val: Value to assign. :return: None """ self._dead_band = float(val) @property def use_reactive_power_curve(self) -> bool: """ Get ``use_reactive_power_curve``. :return: bool """ return self._use_reactive_power_curve @use_reactive_power_curve.setter def use_reactive_power_curve(self, val: bool) -> None: """ Set ``use_reactive_power_curve``. :param val: Value to assign. :return: None """ self._use_reactive_power_curve = bool(val) @property def R1(self) -> float: """ Get ``R1``. :return: float """ return self._R1 @R1.setter def R1(self, val: float) -> None: """ Set ``R1``. :param val: Value to assign. :return: None """ self._R1 = float(val) @property def X1(self) -> float: """ Get ``X1``. :return: float """ return self._X1 @X1.setter def X1(self, val: float) -> None: """ Set ``X1``. :param val: Value to assign. :return: None """ self._X1 = float(val) @property def R0(self) -> float: """ Get ``R0``. :return: float """ return self._R0 @R0.setter def R0(self, val: float) -> None: """ Set ``R0``. :param val: Value to assign. :return: None """ self._R0 = float(val) @property def X0(self) -> float: """ Get ``X0``. :return: float """ return self._X0 @X0.setter def X0(self, val: float) -> None: """ Set ``X0``. :param val: Value to assign. :return: None """ self._X0 = float(val) @property def R2(self) -> float: """ Get ``R2``. :return: float """ return self._R2 @R2.setter def R2(self, val: float) -> None: """ Set ``R2``. :param val: Value to assign. :return: None """ self._R2 = float(val) @property def X2(self) -> float: """ Get ``X2``. :return: float """ return self._X2 @X2.setter def X2(self, val: float) -> None: """ Set ``X2``. :param val: Value to assign. :return: None """ self._X2 = float(val) @property def Rs(self) -> float: """ Get ``Rs``. :return: float """ return self._Rs @Rs.setter def Rs(self, val: float) -> None: """ Set ``Rs``. :param val: Value to assign. :return: None """ self._Rs = float(val) @property def Xs(self) -> float: """ Get ``Xs``. :return: float """ return self._Xs @Xs.setter def Xs(self, val: float) -> None: """ Set ``Xs``. :param val: Value to assign. :return: None """ self._Xs = float(val) @property def Xm(self) -> float: """ Get ``Xm``. :return: float """ return self._Xm @Xm.setter def Xm(self, val: float) -> None: """ Set ``Xm``. :param val: Value to assign. :return: None """ self._Xm = float(val) @property def Rr(self) -> float: """ Get ``Rr``. :return: float """ return self._Rr @Rr.setter def Rr(self, val: float) -> None: """ Set ``Rr``. :param val: Value to assign. :return: None """ self._Rr = float(val) @property def Xr(self) -> float: """ Get ``Xr``. :return: float """ return self._Xr @Xr.setter def Xr(self, val: float) -> None: """ Set ``Xr``. :param val: Value to assign. :return: None """ self._Xr = float(val) @property def Cost2(self) -> float: """ Get ``Cost2``. :return: float """ return self._Cost2 @Cost2.setter def Cost2(self, val: float) -> None: """ Set ``Cost2``. :param val: Value to assign. :return: None """ self._Cost2 = float(val) @property def Cost0(self) -> float: """ Get ``Cost0``. :return: float """ return self._Cost0 @Cost0.setter def Cost0(self, val: float) -> None: """ Set ``Cost0``. :param val: Value to assign. :return: None """ self._Cost0 = float(val) @property def startup_cost(self) -> float: """ Get ``StartupCost``. :return: float """ return self._StartupCost @startup_cost.setter def startup_cost(self, val: float) -> None: """ Set ``StartupCost``. :param val: Value to assign. :return: None """ self._StartupCost = float(val) @property def shutdown_cost(self) -> float: """ Get ``ShutdownCost``. :return: float """ return self._ShutdownCost @shutdown_cost.setter def shutdown_cost(self, val: float) -> None: """ Set ``ShutdownCost``. :param val: Value to assign. :return: None """ self._ShutdownCost = float(val) @property def min_time_up(self) -> float: """ Get ``MinTimeUp``. :return: float """ return self._MinTimeUp @min_time_up.setter def min_time_up(self, val: float) -> None: """ Set ``MinTimeUp``. :param val: Value to assign. :return: None """ self._MinTimeUp = float(val) @property def min_time_down(self) -> float: """ Get ``MinTimeDown``. :return: float """ return self._MinTimeDown @min_time_down.setter def min_time_down(self, val: float) -> None: """ Set ``MinTimeDown``. :param val: Value to assign. :return: None """ self._MinTimeDown = float(val) @property def ramp_up(self) -> float: """ Get ``RampUp``. :return: float """ return self._RampUp @ramp_up.setter def ramp_up(self, val: float) -> None: """ Set ``RampUp``. :param val: Value to assign. :return: None """ self._RampUp = float(val) @property def ramp_down(self) -> float: """ Get ``RampDown``. :return: float """ return self._RampDown @ramp_down.setter def ramp_down(self, val: float) -> None: """ Set ``RampDown``. :param val: Value to assign. :return: None """ self._RampDown = float(val) @property def enabled_dispatch(self) -> bool: """ Get ``enabled_dispatch``. :return: bool """ return self._enabled_dispatch @enabled_dispatch.setter def enabled_dispatch(self, val: bool) -> None: """ Set ``enabled_dispatch``. :param val: Value to assign. :return: None """ self._enabled_dispatch = bool(val) @property def must_run(self) -> bool: """ Get ``must_run``. :return: bool """ return self._must_run @must_run.setter def must_run(self, val: bool) -> None: """ Set ``must_run``. :param val: Value to assign. :return: None """ self._must_run = bool(val) @property def fuels_list(self) -> List[Fuel]: """ get the Fuel list :return: Fuel """ return self.fuels.to_list()
[docs] def get_first_fuel(self) -> Fuel | None: """ Get the first fuels available :return: Technology """ for key, association in self.fuels.data.items(): return association.api_object return None
@property def emissions_list(self) -> List[EmissionGas]: """ get the EmissionGas list :return: EmissionGas list """ return self.emissions.to_list()
[docs] def get_first_emission(self) -> EmissionGas | None: """ Get the first emissions available :return: Technology """ for key, association in self.emissions.data.items(): return association.api_object return None