diff --git a/src/oemof/tabular/constraint_facades.py b/src/oemof/tabular/constraint_facades.py index 68bd03d0..426f537e 100644 --- a/src/oemof/tabular/constraint_facades.py +++ b/src/oemof/tabular/constraint_facades.py @@ -4,7 +4,7 @@ from oemof.solph.constraints.integral_limit import generic_integral_limit from pyomo.environ import Constraint -from oemof.tabular.facades import Bev +from oemof.tabular.facades import BevTech def var2str(var): @@ -88,7 +88,7 @@ class BevShareMob(ConstraintFacade): def map_share2vars(model, share_mob, period): invest_vars = [] for node in model.es.nodes: - if isinstance(node, Bev): + if isinstance(node, BevTech): invest_vars.extend( [ inv @@ -172,7 +172,7 @@ def double_with_offset(lst): def get_bev_invest_vars(model, period): all_invest_vars = {} for node in model.es.nodes: - if isinstance(node, Bev): + if isinstance(node, BevTech): invest_vars_bev = list( set( inv diff --git a/src/oemof/tabular/datapackage/reading.py b/src/oemof/tabular/datapackage/reading.py index 157758fe..92a5b52a 100644 --- a/src/oemof/tabular/datapackage/reading.py +++ b/src/oemof/tabular/datapackage/reading.py @@ -482,12 +482,13 @@ def unpack_sequences(facade, period_data): facade """ - yearly_values = ["fixed_costs", "marginal_costs"] + yearly_values = ["fixed_costs", "marginal_cost"] periodical_values = [ "capacity", "capacity_cost", "capacity_potential", "storage_capacity", + "year", ] for value_name, value in facade.items(): diff --git a/src/oemof/tabular/facades/__init__.py b/src/oemof/tabular/facades/__init__.py index 0b4309a0..baac513d 100644 --- a/src/oemof/tabular/facades/__init__.py +++ b/src/oemof/tabular/facades/__init__.py @@ -7,7 +7,7 @@ from .conversion import Conversion from .dispatchable import Dispatchable from .excess import Excess -from .experimental.battery_electric_vehicle import Bev +from .experimental.battery_electric_vehicle import BevFleet, BevTech from .extraction_turbine import ExtractionTurbine from .generator import Generator from .heatpump import HeatPump @@ -36,7 +36,8 @@ "shortage": Shortage, "storage": Storage, "volatile": Volatile, - "bev": Bev, + "bev_tech": BevTech, + "bev_fleet": BevFleet, } TECH_COLOR_MAP = { @@ -60,7 +61,6 @@ "reservoir": "slateblue", "biomass": "olivedrab", "storage": "lightsalmon", - "battery": "lightsalmon", "import": "crimson", } diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index ed677303..ce647731 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -1,3 +1,5 @@ +import logging +from collections.abc import Iterable from dataclasses import field from typing import Sequence, Union @@ -11,11 +13,12 @@ @dataclass_facade -class Bev(GenericStorage, Facade): - r"""A fleet of Battery electric vehicles with controlled/flexible charging, - (G2V), vehicle-to-grid (V2G) or uncontrolled/fixed charging (inflex). +class BevTech(GenericStorage, Facade): + r"""A Battery electric vehicle technology with either controlled/flexible + charging, (G2V), vehicle-to-grid (V2G) or uncontrolled/fixed charging + (inflex). - This facade consists of mulitple oemof.solph components: + This facade consists of multiple oemof.solph components: - a GenericStorage as storage unit - a Bus as internal bus @@ -26,36 +29,39 @@ class Bev(GenericStorage, Facade): is given) Charging and discharging capacity is assumed to be equal. - Multiple fleets can be modelled and connected to a common bus - (commodity_bus) to apply one demand for all modelled fleets. + Multiple bev technologies can be combined and connected to a common bus + (commodity_bus) to apply one demand for all modelled bev techs. Use + :class:`BevFleet` class for this Parameters ---------- electricity_bus: oemof.solph.Bus The electricity bus where the BEV is connected to. - commodity_bus: oemof.solph.Bus + demand_bus: oemof.solph.Bus A bus which is used to connect a common demand for multiple BEV instances (optional). charging_power : int - The total charging/discharging power of the fleet (e.g. in MW). - charging_potential: int - Maximum charging potential in investment optimization. + If `expandable` is set to True, this value represents the average + charging power in kW. Otherwise, it denotes the charging power for the + entire fleet in MW. + todo: check units + maximum_charging_power_investment: float or sequence + Maximum charging power addition in investment optimization. Defined per + period p for a multi-period model. availability : float, array of float Availability of the fleet at the charging stations (e.g. 0.8). storage_capacity: int - The total storage capacity of the fleet (e.g. in MWh). - initial_storage_capacity: float - The relative storage content in the timestep before the first - time step of optimization (between 0 and 1). - - Note: When investment mode is used in a multi-period model, - `initial_storage_level` is not supported. - Storage output is forced to zero until the storage unit is invested in. + If `expandable` is set to True, this value represents the average + storage capacity in kWh. Otherwise, it denotes the charging power for + the entire fleet in MWh. + todo: check units min_storage_level : array of float + This parameter is inherited from the :class:`GenericStorage` class. Relative profile of minimum storage level (min SOC).The normed minimum storage content as fraction of the storage capacity or the capacity that has been invested into (between 0 and 1). max_storage_level : array of float + This parameter is inherited from the :class:`GenericStorage` class. Relative profile of maximum storage level (max SOC). drive_power: int The total driving capacity of the fleet (e.g. in MW) if no mobility_bus @@ -65,20 +71,18 @@ class Bev(GenericStorage, Facade): v2g: bool If True, Vehicle-to-grid option is enabled, default: False loss_rate: float + This parameter is inherited from the :class:`GenericStorage` class. The relative loss/self discharge of the storage content per time unit, default: 0 - efficiency_mob_g2v: float + efficiency_mob_g2v: float, array of float Efficiency at the charging station (grid-to-vehicle), default: 1 - efficiency_mob_v2g: float + efficiency_mob_v2g: float, array of float Efficiency at the charging station (vehicle-to-grid), default: 1 - efficiency_sto_in: float + efficiency_sto_in: float, array of float Efficiency of charging the batteries, default: 1 - efficiency_sto_out: float + efficiency_sto_out: float, array of float Efficiency of discharging the batteries, default: 1 - efficiency_mob_electrical: float - Efficiency of the electrical drive train per 100 km (optional). - default: 1 - pkm_conversion_rate: float + commodity_conversion_rate: float, array of float Conversion rate from energy to e.g. pkm if mobility_bus passed (optional) default: 1 expandable: bool @@ -91,27 +95,26 @@ class Bev(GenericStorage, Facade): Age of the existing fleet at the first investment period in years. invest_c_rate: float - Invested storage capacity per power rate - (e.g. 60/20 = 3h charging/discharging time) - bev_storage_capacity: int - Storage capacity of one vehicle in kWh. - bev_capacity: int - Charging capacity of one vehicle in kW. - + The rate of storage capacity invested per unit of charging power. + For example, invest_c_rate = 3 indicates that 3 units of storage + capacity are invested for every unit of charging/discharging power. If + invest_c_rate is not provided, it is calculated based on the ratio of + storage_capacity to charging_power. If invest_c_rate, storage_capacity, + and charging_power are provided, invest_c_rate is validated against the + calculated value and a warning is issued if they do not match. + Note: This rate is fixed over all periods. bev_invest_costs: float, array of float Investment costs for new vehicle unit. EUR/vehicle + variable_costs: float, array of float + Variable costs of the fleet (e.g. in EUR/MWh). fixed_costs: float, array of float Operation independent costs for existing and new vehicle units. (e.g. EUR/(vehicle*a)) - variable_costs: float, array of float - Variable costs of the fleet (e.g. in EUR/MWh). - fixed_investment_costs - - + fixed_investment_costs: float, array of float + todo: add description. balanced : boolean Couple storage level of first and last time step. (Total inflow and total outflow are balanced.) - input_parameters: dict Dictionary to specify parameters on the input edge. You can use all keys that are available for the oemof.solph.network.Flow class. @@ -123,85 +126,34 @@ class Bev(GenericStorage, Facade): {"fix": [1,0.5,...]} - The vehicle fleet is modelled as a storage together with an internal - sink with fixed flow: - - todo check formula - .. math:: - - x^{level}(t) = - x^{level}(t-1) \cdot (1 - c^{loss\_rate}(t)) - + c^{efficiency\_charging}(t) \cdot x^{flow, in}(t) - - \frac{x^{drive\_power}(t)}{c^{efficiency\_discharging}(t)} - - \frac{x^{flow, v2g}(t)} - {c^{efficiency\_discharging}(t) \cdot c^{efficiency\_v2g}(t)} - \qquad \forall t \in T - Note ---- As the Bev is a sub-class of `oemof.solph.GenericStorage` you also pass all arguments of this class. - The concept is similar to the one described in the following publications - with the difference that uncontrolled charging is not (yet) considered. - - Wulff, N., Steck, F., Gils, H. C., Hoyer-Klick, C., van den Adel, - B., & Anderson, J. E. (2020). - Comparing power-system and user-oriented battery electric vehicle - charging representation and - its implications on energy system modeling. - Energies, 13(5). https://doi.org/10.3390/en13051093 - - Diego Luca de Tena Costales. (2014). - Large Scale Renewable Power Integration with Electric Vehicles. - https://doi.org/10.04.2014 - - Examples - -------- - Basic usage example of the Bev class with an arbitrary selection of - attributes. - - >>> from oemof import solph - >>> from oemof.tabular import facades - >>> my_bus = solph.Bus('my_bus') - >>> my_bev = Bev( - ... label='my_bev', - ... bus=el_bus, - ... carrier='electricity', - ... tech='bev', - ... storage_capacity=1000, - ... capacity=50, - ... availability=[0.8, 0.7, 0.6], - ... drive_power=[0.3, 0.2, 0.5], - ... amount=450, - # ... loss_rate=0.01, - ... initial_storage_level=0, - ... min_storage_level=[0.1, 0.2, 0.15], - ... max_storage_level=[0.9, 0.95, 0.92], - ... efficiency=0.93 - ... ) + - Costs are determined by the charging converter (storage input converter) + costs, assuming the size equality of storage input and output converters. + Additionally, it guarantees that the V2G converter remains smaller than or + equal to the storage output converter. + - Access to investment variables is restricted until the component is added + to the energy system. + - Utilizes the constraint facade :class:`BevEqualInvest`. """ - electricity_bus: Bus + label: str = None - commodity_bus: Bus = None + electricity_bus: Bus = None - charging_power: float = 0 + demand_bus: Bus = None - minimum_charging_power: float = None + charging_power: float = None - charging_potential: float = None + maximum_charging_power_investment: Union[float, Sequence[float]] = None availability: Union[float, Sequence[float]] = 1 - storage_capacity: float = 0 - - minimum_storage_capacity: float = 0 - - storage_capacity_potential: float = None - - initial_storage_capacity: float = 0 + storage_capacity: float = None drive_power: int = 0 @@ -209,17 +161,15 @@ class Bev(GenericStorage, Facade): v2g: bool = False - efficiency_mob_g2v: float = 1 - - efficiency_mob_v2g: float = 1 + efficiency_mob_g2v: Union[float, Sequence[float]] = 1 - efficiency_mob_electrical: float = 1 + efficiency_mob_v2g: Union[float, Sequence[float]] = 1 - efficiency_sto_in: float = 1 + efficiency_sto_in: Union[float, Sequence[float]] = 1 - efficiency_sto_out: float = 1 + efficiency_sto_out: Union[float, Sequence[float]] = 1 - commodity_conversion_rate: float = 1 + commodity_conversion_rate: Union[float, Sequence[float]] = 1 expandable: bool = False @@ -227,7 +177,7 @@ class Bev(GenericStorage, Facade): age: int = 0 - invest_c_rate: Sequence[float] = None + invest_c_rate: float = None bev_invest_costs: Sequence[float] = None @@ -243,38 +193,105 @@ class Bev(GenericStorage, Facade): output_parameters: dict = field(default_factory=dict) - def _converter_investment(self): + def _converter_investment(self, limit=False): """All parameters are passed, but no investment cost is considered. The investment cost will be considered by the storage inflow only. """ if self.expandable: - investment = Investment( - ep_costs=0, - maximum=self._get_maximum_additional_invest( - "charging_potential", "charging_power" - ), - minimum=getattr(self, "minimum_charging_power", 0), - existing=getattr(self, "charging_power", 0), - lifetime=getattr(self, "lifetime", None), - age=getattr(self, "age", 0), - fixed_costs=0, - ) + if limit: + investment = Investment( + ep_costs=0, + maximum=getattr( + self, "maximum_charging_power_investment", None + ), + minimum=getattr( + self, "minimum_charging_power_investment", None + ), + lifetime=getattr(self, "lifetime", None), + age=getattr(self, "age", 0), + fixed_costs=0, + ) + else: + # no investment limitation e.g. drive power strain + # only charging is limited by charger but not drive power + investment = Investment( + ep_costs=0, + lifetime=getattr(self, "lifetime", None), + age=getattr(self, "age", 0), + fixed_costs=0, + ) + return investment else: return None + def _invest_c_rate(self): + """Determines the investment rate (invest_c_rate) based on the passed + parameters: + + - If no parameters are passed, the default value of 1 is assigned. + - If only invest_c_rate is passed, it is assigned directly. + - If only storage_capacity and charging_power are passed, the + invest_c_rate is calculated. + - If invest_c_rate, storage_capacity and charging_power are passed, it + ensures that the calculated and passed invest_c_rate are equal. If + not, the passed value is assigned and a warning is issued. + """ + if ( + self.invest_c_rate is None + and self.storage_capacity is None + and self.charging_power is None + ): + self.invest_c_rate = 1 + elif ( + self.invest_c_rate is None + and self.storage_capacity is not None + and self.charging_power is not None + ): + self.invest_c_rate = self.storage_capacity / self.charging_power + elif ( + self.invest_c_rate is not None + and self.storage_capacity is not None + and self.charging_power is not None + ): + calculated_invest_c_rate = ( + self.storage_capacity / self.charging_power + ) + if calculated_invest_c_rate != self.invest_c_rate: + logging.warning( + f"Warning: The passed invest_c_rate ({self.invest_c_rate})" + " does not match the calculated value " + f"({calculated_invest_c_rate})." + ) + else: + self.invest_c_rate = 1 + logging.warning( + "Warning: invest_c_rate could not be calculated. Standard " + "value of 1 was assigned." + ) + + @staticmethod + def multiply_scalar_or_iterable(a, b): + """Multiply scalars or each value of list""" + if all([isinstance(i, Iterable) for i in [a, b]]): + results = [x * y for x, y in zip(a, b)] + elif all([isinstance(i, float) for i in [a, b]]): + results = a * b + else: + raise ValueError( + "Value of 'a' and 'b' must be iterables of" + "float, or scalar float." + ) + return results + def build_solph_components(self): + self.type = "bev" + # use label as prefix for subnodes self.facade_label = self.label - self.label = self.label + "-storage" - - # convert to solph sequences - self.availability = solph_sequence(self.availability) - # TODO: check if this is correct - self.nominal_storage_capacity = self.storage_capacity - # self.nominal_storage_capacity = self._nominal_value( - # self.storage_capacity) + # Label of storage + self.label = self.label + "-storage" self.balanced = self.balanced # TODO to be false in multi-period @@ -283,17 +300,18 @@ def build_solph_components(self): self.bus = internal_bus subnodes = [internal_bus] - self.investment = Investment( - ep_costs=0, - maximum=self._get_maximum_additional_invest( - "storage_capacity_potential", "storage_capacity" - ), - minimum=getattr(self, "minimum_storage_capacity", 0), - existing=getattr(self, "storage_capacity", 0), - lifetime=getattr(self, "lifetime", None), - age=getattr(self, "age", 0), - fixed_costs=0, - ) + # Calculate invest_c_rate + self._invest_c_rate() + + if self.expandable: + self.investment = Investment( + ep_costs=0, + lifetime=getattr(self, "lifetime", None), + age=getattr(self, "age", 0), + fixed_costs=0, + ) + else: + self.nominal_storage_capacity = self.storage_capacity # ##### Vehicle2Grid Converter ##### if self.v2g: @@ -303,6 +321,13 @@ def build_solph_components(self): internal_bus: Flow( # variable_costs=self.carrier_cost, # **self.input_parameters + # nominal_value=self._nominal_value( + # value=self.charging_power + # ), + # max=solph_sequence(self.availability), + # variable_costs=None, + # # investment=self._investment(bev=True), + # investment=self._converter_investment(limit=True), ) }, outputs={ @@ -310,56 +335,62 @@ def build_solph_components(self): nominal_value=self._nominal_value( value=self.charging_power ), - # max=self.availability, # doesn't work with investment + max=solph_sequence(self.availability), variable_costs=None, # investment=self._investment(bev=True), - investment=self._converter_investment(), + investment=self._converter_investment(limit=True), ) }, # Includes storage charging efficiencies conversion_factors={ - self.electricity_bus: (self.efficiency_mob_v2g) + self.electricity_bus: solph_sequence( + self.efficiency_mob_v2g + ) }, ) subnodes.append(vehicle_to_grid) # Drive consumption - if self.commodity_bus: + if self.demand_bus: # ##### Commodity Converter ##### # converts energy to another commodity e.g. pkm # connects it to a special mobility bus commodity_converter = Converter( - label=self.facade_label + "-2com", + label=self.facade_label + "-conversion", inputs={ internal_bus: Flow( # **self.output_parameters ) }, outputs={ - self.commodity_bus: Flow( + self.demand_bus: Flow( nominal_value=self._nominal_value(self.charging_power), - # max=self.availability, variable_costs=None, - # investment=self._investment(bev=True), - investment=self._converter_investment(), + # no limitations for drive strain/power + investment=self._converter_investment(limit=False), ) }, conversion_factors={ - self.commodity_bus: self.commodity_conversion_rate - * self.efficiency_mob_electrical + self.demand_bus: solph_sequence( + self.commodity_conversion_rate + ) + # * 1/self.efficiency_mob_electrical # * 100 # TODO pro 100 km? }, ) subnodes.append(commodity_converter) else: + logging.warning( + "BEV without demand-bus is not tested yet." "Take caution!" + ) # ##### Consumption Sink ##### - # fixed demand for this fleet only + # fixed demand for this bev tech only if self.expandable: raise NotImplementedError( "Consumption sink for expandable BEV not implemented yet!" "Please use a `mobility_bus` + `Sink` instead. Optimizing" - "one fleet alone may not yield meaningful results." + "one bev-tech alone may not yield meaningful results." ) else: driving_consumption = Sink( @@ -380,31 +411,25 @@ def build_solph_components(self): # self.investment = self._investment(bev=False) self.invest_relation_input_output = 1 # charge/discharge equal # invest_c_rate = Energy/Power = h - self.invest_relation_input_capacity = ( - 1 / self.invest_c_rate + self.invest_relation_input_capacity = 1 / getattr( + self, "invest_c_rate", 1 ) # Power/Energy - self.invest_relation_output_capacity = ( - 1 / self.invest_c_rate + self.invest_relation_output_capacity = 1 / getattr( + self, "invest_c_rate", 1 ) # Power/Energy - for attr in ["invest_relation_input_output"]: - if getattr(self, attr) is None: - raise AttributeError( - ( - "You need to set attr " "`{}` " "for component {}" - ).format(attr, self.label) - ) - # ##### Grid2Vehicle ##### - # containts the whole investment costs for bev + # contains the whole investment costs for bev flow_in = Flow( # max=self.availability, investment=Investment( ep_costs=self.bev_invest_costs, - maximum=self._get_maximum_additional_invest( - "charging_potential", "charging_power" + maximum=getattr( + self, "maximum_charging_power_investment", None + ), + minimum=getattr( + self, "minimum_charging_power_investment", None ), - existing=getattr(self, "charging_power", 0), lifetime=getattr(self, "lifetime", None), age=getattr(self, "age", 0), fixed_costs=getattr(self, "fixed_investment_costs", None), @@ -415,13 +440,12 @@ def build_solph_components(self): # set investment, but no costs (as relation input / output = 1) flow_out = Flow( investment=Investment( - existing=getattr(self, "charging_power", 0), lifetime=getattr(self, "lifetime", None), age=getattr(self, "age", 0), ), **self.output_parameters, ) - # required for correct grouping in oemof.solph.components + # Trigger GenericInvestmentStorageBlock self._invest_group = True else: @@ -438,7 +462,9 @@ def build_solph_components(self): ) self.inflow_conversion_factor = solph_sequence( - self.efficiency_mob_g2v * self.efficiency_sto_in + self.multiply_scalar_or_iterable( + a=self.efficiency_mob_g2v, b=self.efficiency_sto_in + ) ) self.outflow_conversion_factor = solph_sequence( @@ -453,3 +479,294 @@ def build_solph_components(self): # many components in facade self.subnodes = subnodes + + +# ToDo: update docstring +@dataclass_facade +class BevFleet(Facade): + r"""A fleet of Battery electric vehicles of different :class:`BevTech` + facades with controlled/flexible charging (G2V), vehicle-to-grid (V2G) + and uncontrolled/fixed charging (inflex). + + Parameters + ---------- + + label: str + A string representing the label for the IndividualMobilitySector + instance. + electricity_bus: Bus + An oemof bus instance representing the connection to the electricity + grid. + demand_bus: Bus + An oemof bus instance representing a common demand for the multiple BEV + technologies. + charging_power_flex: float + The charging power for grid-to-vehicle (G2V) and vehicle-to-grid (V2G) + operations. If `expandable` is set to True, this value represents the + average charging power in kW. Otherwise, it denotes the charging power + for the entire fleet in MW. + todo: check units + charging_power_inflex: float + The charging power for uncontrolled/fixed charging (inflex) operations. + If `expandable` is set to True, this value represents the average + charging power in kW. Otherwise, it denotes the charging power for the + entire fleet in MW. + todo: check units + minimum_charging_power_investment: float or sequence + Minimum charging power addition in investment optimization. Defined per + period p for a multi-period model. + maximum_charging_power_investment: float or sequence + Maximum charging power addition in investment optimization. Defined per + period p for a multi-period model. + availability_flex: Union[float, Sequence[float]] + The ratio of available capacity for charging/vehicle-to-grid due to + grid connection. todo: für flex/inflex + availability_inflex: Union[float, Sequence[float]] + Time series of fixed connection capacity. + storage_capacity: float + This value represents the average storage capacity in kWh if expandable + is true. Otherwise it denotes the storage capacity for the entire fleet + in MWh. + todo: check units + min_storage_level: Union[float, Sequence[float]] + The profile of minimum storage level (min SOC). + max_storage_level: Union[float, Sequence[float]] + The profile of maximum storage level (max SOC). + drive_power: int + The total driving capacity of the fleet (e.g. in MW) if no mobility_bus + is connected. + drive_consumption: Sequence[float] + The drive consumption profil of the fleet (relative to drive_power). + loss_rate: float + The relative loss of the storage content per time unit (e.g. hour). + efficiency_mob_g2v: float, array of float + Efficiency at the charging station (grid-to-vehicle), default: 1 + efficiency_mob_v2g: float, array of float + Efficiency at the charging station (vehicle-to-grid), default: 1 + efficiency_sto_in: float, array of float + Efficiency of charging the batteries, default: 1 + efficiency_sto_out: float, array of float + Efficiency of discharging the batteries, default: 1 + commodity_conversion_rate: float, array of float + Conversion rate from energy to e.g. pkm if demand_bus is passed + (optional). Could also just be eletrical_drive_efficiency. default: 1 + expandable: bool + If True, the fleet is expandable, default: False + Charging_power and storage_capacity are then interpreted as existing + capacities at the first investment period. + lifetime: int + Total lifetime of the fleet in years. + age: int + Age of the existing fleet at the first investment period in years. + invest_c_rate: float + The rate of storage capacity invested per unit of charging power. + For example, if invest_c_rate is 3, it indicates that 3 units of + storage capacity are invested for every unit of charging/discharging + power. If invest_c_rate is not provided, it is calculated based on the + ratio of storage_capacity to charging_power. If invest_c_rate, + storage_capacity, and charging_power are provided, invest_c_rate is + validated against the calculated value and a warning is issued if they + do not match. + bev_invest_costs: float, array of float + Investment costs for new vehicle unit. EUR/vehicle + fixed_costs: float, array of float + Operation independent costs for existing and new vehicle units. + (e.g. EUR/(vehicle*a)) + fixed_investment_costs: float, array of float + todo: add description. + variable_costs: float, array of float + Variable costs of the fleet (e.g. in EUR/MWh). + balanced : boolean + Couple storage level of first and last time step. + (Total inflow and total outflow are balanced.) + input_parameters_inflex: dict + Dictionary to specify parameters on the input edge for + uncontrolled/fixed charging. You can use all keys that are available + for the oemof.solph.network.Flow class. e.g. fixed charging timeseries + for the storage can be passed with {"fix": [1,0.5,...]} + output_parameters: dict + Dictionary to specify parameters on the output edge. You can use + all keys that are available for the oemof.solph.network.Flow class + e.g. fixed discharging timeseries for the storage can be passed + with {"fix": [1,0.5,...]} + + """ + + # TODO: match data formats with actual data + # Todo: wo müssen werte angegeben werden? + + label: str = None + + electricity_bus: Bus = None + + demand_bus: Bus = None + + charging_power_flex: float = None + + charging_power_inflex: float = None + + minimum_charging_power_investment: Union[float, Sequence[float]] = None + + maximum_charging_power_investment: Union[float, Sequence[float]] = None + + availability_flex: Union[float, Sequence[float]] = 1 + + availability_inflex: Union[float, Sequence[float]] = 1 + + storage_capacity: float = 0 + + min_storage_level: Union[float, Sequence[float]] = 0 + + max_storage_level: Union[float, Sequence[float]] = 1 + + drive_power: int = 0 + + drive_consumption: Sequence[float] = None + + loss_rate: float = 0 + + efficiency_mob_g2v: Union[float, Sequence[float]] = 1 + + efficiency_mob_v2g: Union[float, Sequence[float]] = 1 + + efficiency_sto_in: Union[float, Sequence[float]] = 1 + + efficiency_sto_out: Union[float, Sequence[float]] = 1 + + commodity_conversion_rate: Union[float, Sequence[float]] = 1 # + + expandable: bool = False + + lifetime: int = 20 + + age: int = 0 + + invest_c_rate: Sequence[float] = None + + bev_invest_costs: Sequence[float] = None + + fixed_costs: Union[float, Sequence[float]] = 0 + + fixed_investment_costs: float = 0 + + variable_costs: Union[float, Sequence[float]] = 0 + + balanced: bool = False + + input_parameters_inflex: dict = field(default_factory=dict) + + output_parameters: dict = field(default_factory=dict) + + def _demand_bus(self, bus): + if self.demand_bus: + return bus + + def build_solph_components(self): + subnodes = [] + + # BEV Fleet Node connected to eletricity bus, bidirectional + self.inputs.update({self.electricity_bus: Flow()}) + self.outputs.update({self.electricity_bus: Flow()}) + + # BEV Fleet connector + # merges BEV techs flows and is connected to demand bus + if self.demand_bus: + fleet = Bus(label=self.label + "-total") + fleet.outputs.update({self.demand_bus: Flow()}) + subnodes.append(fleet) + + # BEV controlled flexibility + bev_controlled_g2v = BevTech( + label=self.label + "_G2V", + electricity_bus=self, + demand_bus=self._demand_bus(fleet), + charging_power=self.charging_power_flex, + maximum_charging_power_investment=self.maximum_charging_power_investment, # noqa + availability=self.availability_flex, + storage_capacity=self.storage_capacity, + min_storage_level=self.min_storage_level, + max_storage_level=self.max_storage_level, + drive_consumption=self.drive_consumption, + v2g=False, + loss_rate=self.loss_rate, + efficiency_mob_g2v=self.efficiency_mob_g2v, + efficiency_sto_in=self.efficiency_sto_in, + efficiency_sto_out=self.efficiency_sto_out, + commodity_conversion_rate=self.commodity_conversion_rate, + expandable=self.expandable, + lifetime=self.lifetime, + age=self.age, + invest_c_rate=self.invest_c_rate, + bev_invest_costs=self.bev_invest_costs, + fixed_costs=self.fixed_costs, + fixed_investment_costs=self.fixed_investment_costs, # todo: added for test # noqa + variable_costs=self.variable_costs, + balanced=self.balanced, + ) + subnodes.append(bev_controlled_g2v) + + # BEV controlled flexibility with vehicle to grid + bev_controlled_v2g = BevTech( + label=self.label + "_V2G", + electricity_bus=self, + demand_bus=self._demand_bus(fleet), + charging_power=self.charging_power_flex, + maximum_charging_power_investment=self.maximum_charging_power_investment, # noqa + availability=self.availability_flex, + storage_capacity=self.storage_capacity, + min_storage_level=self.min_storage_level, + max_storage_level=self.max_storage_level, + drive_consumption=self.drive_consumption, + v2g=True, + loss_rate=self.loss_rate, + efficiency_mob_g2v=self.efficiency_mob_g2v, + efficiency_mob_v2g=self.efficiency_mob_v2g, + efficiency_sto_in=self.efficiency_sto_in, + efficiency_sto_out=self.efficiency_sto_out, + commodity_conversion_rate=self.commodity_conversion_rate, + expandable=self.expandable, + lifetime=self.lifetime, + age=self.age, + invest_c_rate=self.invest_c_rate, + bev_invest_costs=self.bev_invest_costs, + fixed_costs=self.fixed_costs, + fixed_investment_costs=self.fixed_investment_costs, # todo: added for test # noqa + variable_costs=self.variable_costs, + balanced=self.balanced, + ) + subnodes.append(bev_controlled_v2g) + + # BEV uncontrolled with no flexibility but storage buffer + bev_inflex = BevTech( + label=self.label + "_inflex", + electricity_bus=self, + demand_bus=self._demand_bus(fleet), + charging_power=self.charging_power_inflex, + maximum_charging_power_investment=self.maximum_charging_power_investment, # noqa + availability=self.availability_inflex, + storage_capacity=self.storage_capacity, + min_storage_level=self.min_storage_level, + max_storage_level=self.max_storage_level, + drive_consumption=self.drive_consumption, + v2g=False, + loss_rate=self.loss_rate, + efficiency_mob_g2v=self.efficiency_mob_g2v, + efficiency_mob_v2g=0, + efficiency_sto_in=self.efficiency_sto_in, + efficiency_sto_out=self.efficiency_sto_out, + commodity_conversion_rate=self.commodity_conversion_rate, + expandable=self.expandable, + lifetime=self.lifetime, + age=self.age, + invest_c_rate=self.invest_c_rate, + bev_invest_costs=self.bev_invest_costs, + fixed_costs=self.fixed_costs, + fixed_investment_costs=self.fixed_investment_costs, # todo: added for test # noqa + variable_costs=self.variable_costs, + balanced=self.balanced, + input_parameters=self.input_parameters_inflex, + ) + subnodes.append(bev_inflex) + + # Add subnodes to facade + self.subnodes = subnodes diff --git a/tests/mobility.py b/tests/mobility.py index f8efb283..8e7fecd4 100644 --- a/tests/mobility.py +++ b/tests/mobility.py @@ -8,7 +8,10 @@ from oemof.tabular.constraint_facades import CONSTRAINT_TYPE_MAP from oemof.tabular.datapackage.reading import deserialize_constraints from oemof.tabular.facades import Excess, Load, Shortage, Volatile -from oemof.tabular.facades.experimental.battery_electric_vehicle import Bev +from oemof.tabular.facades.experimental.battery_electric_vehicle import ( + BevFleet, + BevTech, +) from oemof.tabular.postprocessing import calculations if __name__ == "__main__": @@ -21,8 +24,8 @@ # periods=[2020] # Multi-period example - t_idx_1 = pd.date_range("1/1/2020", periods=3, freq="H") - t_idx_2 = pd.date_range("1/1/2030", periods=3, freq="H") + t_idx_1 = pd.date_range("1/1/2020", periods=3, freq="h") + t_idx_2 = pd.date_range("1/1/2030", periods=3, freq="h") t_idx_1_series = pd.Series(index=t_idx_1, dtype="float64") t_idx_2_series = pd.Series(index=t_idx_2, dtype="float64") date_time_index = pd.concat([t_idx_1_series, t_idx_2_series]).index @@ -102,128 +105,169 @@ energysystem.add(pkm_demand) - bev_v2g = Bev( - type="bev", - label="BEV-V2G", - electricity_bus=el_bus, - commodity_bus=indiv_mob, - storage_capacity=150, - # drive_power=150, # nominal value sink - # drive_consumption=[1, 1, 1], # relative value sink - charging_power=150, # existing - availability=len(periods) * [1, 1, 1], - efficiency_charging=1, - v2g=True, - loss_rate=0.01, - min_storage_level=(len(date_time_index) + 0) * [0], - max_storage_level=(len(date_time_index) + 0) * [0.9], - expandable=True, - bev_invest_costs=2, - invest_c_rate=60 / 20, # Capacity/Power - variable_costs=3, - fixed_investment_costs=1, - commodity_conversion_rate=0.7, - lifetime=10, - ) - energysystem.add(bev_v2g) + # bev_v2g = BevTech( + # type="bev", + # label="BEV-V2G", + # electricity_bus=el_bus, + # commodity_bus=indiv_mob, + # storage_capacity=150, + # # drive_power=150, # nominal value sink + # # drive_consumption=[1, 1, 1], # relative value sink + # charging_power=150, # existing + # availability=len(periods) * [1, 1, 1], + # efficiency_charging=1, + # v2g=True, + # loss_rate=0.01, + # min_storage_level=(len(date_time_index) + 0) * [0], + # max_storage_level=(len(date_time_index) + 0) * [0.9], + # expandable=True, + # bev_invest_costs=2, + # invest_c_rate=60 / 20, # Capacity/Power + # variable_costs=3, + # fixed_investment_costs=1, + # commodity_conversion_rate=0.7, + # lifetime=10, + # ) + # energysystem.add(bev_v2g) + # + # bev_flex = BevTech( + # type="bev", + # label="BEV-inflex", + # electricity_bus=el_bus, + # commodity_bus=indiv_mob, + # storage_capacity=200, + # drive_power=100, + # # drive_consumption=[0, 1, 0], + # charging_power=200, + # availability=len(periods) * [1, 1, 1], + # v2g=False, + # # loss_rate=0.01, + # # min_storage_level=[0.1, 0.2, 0.15, 0.15], + # # max_storage_level=[0.9, 0.95, 0.92, 0.92], + # expandable=True, + # bev_invest_costs=2, + # invest_c_rate=60 / 20, + # variable_costs=3, + # fixed_investment_costs=1, + # commodity_conversion_rate=0.7, + # lifetime=10, + # ) + # energysystem.add(bev_flex) + # + # bev_fix = BevTech( + # type="bev", + # label="BEV-G2V", + # electricity_bus=el_bus, + # commodity_bus=indiv_mob, + # storage_capacity=200, + # drive_power=100, + # # drive_consumption=[0, 1, 0], + # charging_power=200, + # availability=len(periods) * [1, 1, 1], + # v2g=False, + # # loss_rate=0.01, + # # min_storage_level=[0.1, 0.2, 0.15, 0.15], + # # max_storage_level=[0.9, 0.95, 0.92, 0.92], + # expandable=True, + # bev_invest_costs=2, + # invest_c_rate=60 / 20, # Capacity/Power + # variable_costs=3, + # fixed_investment_costs=1, + # commodity_conversion_rate=0.7, + # input_parameters={ + # "fix": len(periods) * [0, 0, 0] + # }, # fixed relative charging profile + # lifetime=10, + # ) + # energysystem.add(bev_fix) - bev_flex = Bev( - type="bev", - label="BEV-inflex", + bevfleet = BevFleet( + label="BEV", electricity_bus=el_bus, - commodity_bus=indiv_mob, - storage_capacity=200, - drive_power=100, - # drive_consumption=[0, 1, 0], - charging_power=200, - availability=len(periods) * [1, 1, 1], - v2g=False, - # loss_rate=0.01, - # min_storage_level=[0.1, 0.2, 0.15, 0.15], - # max_storage_level=[0.9, 0.95, 0.92, 0.92], - expandable=True, - bev_invest_costs=2, + demand_bus=indiv_mob, + charging_power_flex=200, + charging_power_inflex=150, + minimum_charging_power_investment=0, + maximum_charging_power_investment=1000, + availability_flex=[0.8] * len(date_time_index), + availability_inflex=[0.8] * len(date_time_index), + # storage_capacity=50, + min_storage_level=[0] * len(date_time_index), + max_storage_level=[0] * len(date_time_index), + # drive_power: int = 0, + # drive_consumption: Sequence[float] = None + loss_rate=0.1, + efficiency_mob_g2v=[1] * len(date_time_index), + efficiency_mob_v2g=[1] * len(date_time_index), + efficiency_sto_in=[1] * len(date_time_index), + efficiency_sto_out=[1] * len(date_time_index), + # efficiency_mob_electrical=1,#[1]*len(date_time_index), + commodity_conversion_rate=[1] * len(date_time_index), + expandable=False, + lifetime=20, + age=0, invest_c_rate=60 / 20, - variable_costs=3, - fixed_investment_costs=1, - commodity_conversion_rate=0.7, - lifetime=10, + bev_invest_costs=[5] * len(periods), + fixed_costs=[10] + * len( + [2020, 2030] + ), # eigentlich für jedes explizite und implizite Jahr + fixed_investment_costs=[20] * len(periods), + variable_costs=[2] * len(date_time_index), + balanced=True, + input_parameters_inflex={"fix": len(periods) * [0, 0, 0]}, + # output_parameters: dict = field(default_factory=dict) ) - energysystem.add(bev_flex) - bev_fix = Bev( - type="bev", - label="BEV-G2V", - electricity_bus=el_bus, - commodity_bus=indiv_mob, - storage_capacity=200, - drive_power=100, - # drive_consumption=[0, 1, 0], - charging_power=200, - availability=len(periods) * [1, 1, 1], - v2g=False, - # loss_rate=0.01, - # min_storage_level=[0.1, 0.2, 0.15, 0.15], - # max_storage_level=[0.9, 0.95, 0.92, 0.92], - expandable=True, - bev_invest_costs=2, - invest_c_rate=60 / 20, # Capacity/Power - variable_costs=3, - fixed_investment_costs=1, - commodity_conversion_rate=0.7, - input_parameters={ - "fix": len(periods) * [0, 0, 0] - }, # fixed relative charging profile - lifetime=10, - ) - energysystem.add(bev_fix) + energysystem.add(bevfleet) mp.draw_graph(energysystem) - model = solph.Model( - energysystem, - timeindex=energysystem.timeindex, - ) - - filepath = "./mobility.lp" - model.write(filepath, io_options={"symbolic_solver_labels": True}) - - datapackage_dir = os.path.join( - tabular_path[0], "examples/own_examples/bev" - ) - deserialize_constraints( - model=model, - path=os.path.join(datapackage_dir, "datapackage.json"), - constraint_type_map=CONSTRAINT_TYPE_MAP, - ) - - filepath = "./mobility_constrained.lp" - model.write(filepath, io_options={"symbolic_solver_labels": True}) - - # select solver 'gurobi', 'cplex', 'glpk' etc - model.solve("cbc", solve_kwargs={"tee": True}) - # model.display() - - energysystem.params = solph.processing.parameter_as_dict( - energysystem, exclude_attrs=["subnodes"] - ) - energysystem.results = model.results() - - # Rename results for easy access - energysystem.new_results = {} - for r in energysystem.results: - if r[1] is not None: - energysystem.new_results[ - f"{r[0].label}: {r[1].label}" - ] = energysystem.results[r] - - # postprocessing - postprocessed_results = calculations.run_postprocessing(energysystem) - - # # plot bev results - # mp.plot_bev_results( - # energysystem=energysystem, - # facade_label=["BEV-V2G", "BEV-FLEX"] + print("test") + # model = solph.Model( + # energysystem, + # timeindex=energysystem.timeindex, # ) - - print(postprocessed_results) + # + # filepath = "./mobility.lp" + # model.write(filepath, io_options={"symbolic_solver_labels": True}) + # + # datapackage_dir = os.path.join( + # tabular_path[0], "examples/own_examples/bev" + # ) + # deserialize_constraints( + # model=model, + # path=os.path.join(datapackage_dir, "datapackage.json"), + # constraint_type_map=CONSTRAINT_TYPE_MAP, + # ) + # + # filepath = "./mobility_constrained.lp" + # model.write(filepath, io_options={"symbolic_solver_labels": True}) + # + # # select solver 'gurobi', 'cplex', 'glpk' etc + # model.solve("cbc", solve_kwargs={"tee": True}) + # # model.display() + # + # energysystem.params = solph.processing.parameter_as_dict( + # energysystem, exclude_attrs=["subnodes"] + # ) + # energysystem.results = model.results() + # + # # Rename results for easy access + # energysystem.new_results = {} + # for r in energysystem.results: + # if r[1] is not None: + # energysystem.new_results[f"{r[0].label}: {r[1].label}"] = ( + # energysystem.results[r] + # ) + # + # # postprocessing + # postprocessed_results = calculations.run_postprocessing(energysystem) + # + # # # plot bev results + # # mp.plot_bev_results( + # # energysystem=energysystem, + # # facade_label=["BEV-V2G", "BEV-FLEX"] + # # ) + # + # print(postprocessed_results) diff --git a/tests/mobility_facade.py b/tests/mobility_facade.py new file mode 100644 index 00000000..314e0992 --- /dev/null +++ b/tests/mobility_facade.py @@ -0,0 +1,263 @@ +import os + +import mobility_plotting as mp +import pandas as pd + +from oemof import solph +from oemof.tabular import __path__ as tabular_path +from oemof.tabular.constraint_facades import CONSTRAINT_TYPE_MAP +from oemof.tabular.datapackage.reading import deserialize_constraints +from oemof.tabular.facades import Excess, Load, Shortage, Volatile +from oemof.tabular.facades.experimental.battery_electric_vehicle import ( + IndividualMobilitySector, +) +from oemof.tabular.postprocessing import calculations + +if __name__ == "__main__": + # Single-period example + # date_time_index = pd.date_range("1/1/2020", periods=3, freq="H") + # energysystem = solph.EnergySystem( + # timeindex=date_time_index, + # infer_last_interval=True, + # ) + # periods=[2020] + + # Multi-period example + t_idx_1 = pd.date_range("1/1/2020", periods=3, freq="H") + t_idx_2 = pd.date_range("1/1/2030", periods=3, freq="H") + t_idx_1_series = pd.Series(index=t_idx_1, dtype="float64") + t_idx_2_series = pd.Series(index=t_idx_2, dtype="float64") + date_time_index = pd.concat([t_idx_1_series, t_idx_2_series]).index + periods = [t_idx_1, t_idx_2] + + energysystem = solph.EnergySystem( + timeindex=date_time_index, + infer_last_interval=False, + timeincrement=[1] * len(date_time_index), + periods=periods, + ) + + el_bus = solph.Bus("el-bus") + el_bus.type = "bus" + energysystem.add(el_bus) + + indiv_mob = solph.Bus("pkm-bus") + indiv_mob.type = "bus" + energysystem.add(indiv_mob) + + volatile = Volatile( + type="volatile", + label="wind", + bus=el_bus, + carrier="wind", + tech="onshore", + capacity=200, + capacity_cost=1, + expandable=True, + # expandable=False, + # capacity_potential=1e8, + profile=len(periods) * [1, 0, 1], + lifetime=20, + ) + energysystem.add(volatile) + + load = Load( + label="load", + type="load", + carrier="electricity", + bus=el_bus, + amount=100, + profile=len(periods) * [1, 1, 1], + ) + energysystem.add(load) + + excess = Excess( + type="excess", + label="excess", + bus=el_bus, + carrier="electricity", + tech="excess", + capacity=100, + marginal_cost=10, + ) + energysystem.add(excess) + + shortage = Shortage( + type="shortage", + label="shortage", + bus=el_bus, + carrier="electricity", + tech="shortage", + capacity=1000, + marginal_cost=1e6, + ) + energysystem.add(shortage) + + pkm_demand = Load( + label="pkm_demand", + type="Load", + carrier="pkm", + bus=indiv_mob, + amount=200, # PKM + profile=len(periods) * [0, 1, 0], # drive consumption + ) + + energysystem.add(pkm_demand) + + """bev_v2g = BevTech( + type="bev", + label="BEV-V2G", + electricity_bus=el_bus, + commodity_bus=indiv_mob, + storage_capacity=150, + # drive_power=150, # nominal value sink + # drive_consumption=[1, 1, 1], # relative value sink + charging_power=150, # existing + availability=len(periods) * [1, 1, 1], + efficiency_charging=1, + v2g=True, + loss_rate=0.01, + min_storage_level=(len(date_time_index) + 0) * [0], + max_storage_level=(len(date_time_index) + 0) * [0.9], + expandable=True, + bev_invest_costs=2, + invest_c_rate=60 / 20, # Capacity/Power + variable_costs=3, + fixed_investment_costs=1, + commodity_conversion_rate=0.7, + lifetime=10, + ) + energysystem.add(bev_v2g) + + bev_flex = BevTech( + type="bev", + label="BEV-inflex", + electricity_bus=el_bus, + commodity_bus=indiv_mob, + storage_capacity=200, + drive_power=100, + # drive_consumption=[0, 1, 0], + charging_power=200, + availability=len(periods) * [1, 1, 1], + v2g=False, + # loss_rate=0.01, + # min_storage_level=[0.1, 0.2, 0.15, 0.15], + # max_storage_level=[0.9, 0.95, 0.92, 0.92], + expandable=True, + bev_invest_costs=2, + invest_c_rate=60 / 20, + variable_costs=3, + fixed_investment_costs=1, + commodity_conversion_rate=0.7, + lifetime=10, + ) + energysystem.add(bev_flex) + + bev_fix = BevTech( + type="bev", + label="BEV-G2V", + electricity_bus=el_bus, + commodity_bus=indiv_mob, + storage_capacity=200, + drive_power=100, + # drive_consumption=[0, 1, 0], + charging_power=200, + availability=len(periods) * [1, 1, 1], + v2g=False, + # loss_rate=0.01, + # min_storage_level=[0.1, 0.2, 0.15, 0.15], + # max_storage_level=[0.9, 0.95, 0.92, 0.92], + expandable=True, + bev_invest_costs=2, + invest_c_rate=60 / 20, # Capacity/Power + variable_costs=3, + fixed_investment_costs=1, + commodity_conversion_rate=0.7, + input_parameters={ + "fix": len(periods) * [0, 0, 0] + }, # fixed relative charging profile + lifetime=10, + ) + energysystem.add(bev_fix)""" + + bev = IndividualMobilitySector( + label="ind_mob_sec", + electricity_bus=el_bus, + transport_commodity_bus=indiv_mob, + charging_flex=200, + charging_power_inflex=200, + availability=len(periods) * [1, 1, 1], + storage_capacity=400, + min_storage_level=(len(date_time_index) + 0) * [0], + max_storage_level=(len(date_time_index) + 0) * [0.9], + drive_consumption=[1, 1, 1], + loss_rate=0.01, + efficiency_mob_g2v=1, + efficiency_mob_v2g=1, + efficiency_sto_in=1, + efficiency_sto_out=1, + efficiency_mob_electrical=1, # todo: unit? + commodity_conversion_rate=0.7, + expandable=True, # todo: ? + lifetime=10, + age=0, + invest_c_rate=40 / 20, + bev_invest_costs=2, + fixed_costs=1, + # fixed_investment_costs=1, + variable_costs=3, + input_parameters_inflex={ + "fix": len(periods) * [0, 0, 0] + }, # fixed relative charging profile + ) + energysystem.add(bev) + + mp.draw_graph(energysystem) + + model = solph.Model( + energysystem, + timeindex=energysystem.timeindex, + ) + + filepath = "./mobility.lp" + model.write(filepath, io_options={"symbolic_solver_labels": True}) + + datapackage_dir = os.path.join( + tabular_path[0], "examples/own_examples/bev" + ) + deserialize_constraints( + model=model, + path=os.path.join(datapackage_dir, "datapackage.json"), + constraint_type_map=CONSTRAINT_TYPE_MAP, + ) + + filepath = "./mobility_constrained.lp" + model.write(filepath, io_options={"symbolic_solver_labels": True}) + + # select solver 'gurobi', 'cplex', 'glpk' etc + model.solve("cbc", solve_kwargs={"tee": True}) + # model.display() + + energysystem.params = solph.processing.parameter_as_dict( + energysystem, exclude_attrs=["subnodes"] + ) + energysystem.results = model.results() + + # Rename results for easy access + energysystem.new_results = {} + for r in energysystem.results: + if r[1] is not None: + energysystem.new_results[f"{r[0].label}: {r[1].label}"] = ( + energysystem.results[r] + ) + + # postprocessing + postprocessed_results = calculations.run_postprocessing(energysystem) + + # # plot bev results + # mp.plot_bev_results( + # energysystem=energysystem, + # facade_label=["BEV-V2G", "BEV-FLEX"] + # ) + + print(postprocessed_results) diff --git a/tests/mobility_plotting.py b/tests/mobility_plotting.py index b93cf12b..136271fe 100644 --- a/tests/mobility_plotting.py +++ b/tests/mobility_plotting.py @@ -88,6 +88,30 @@ def plot_bev_results(energysystem, facade_label): def draw_graph(energysystem): # Draw the graph + def assign_color(nodes): + node_colors = list() + for n in nodes: + if "BEV_V2G" in n: + node_colors.append("firebrick") + elif "BEV_G2V" in n: + node_colors.append("lightblue") + elif "BEV_inflex" in n: + node_colors.append("darkviolet") + elif "excess" in n: + node_colors.append("green") + elif "shortage" in n: + node_colors.append("yellow") + elif "load" in n: + node_colors.append("orange") + elif "wind" in n: + node_colors.append("pink") + elif "bus" in n: + node_colors.append("grey") + elif "pkm_demand" in n: + node_colors.append("dodgerblue") + else: + node_colors.append("violet") + return node_colors from oemof.network.graph import create_nx_graph @@ -98,29 +122,8 @@ def draw_graph(energysystem): G, prog="neato", args="-Gepsilon=0.0001" ) - fig, ax = plt.subplots(figsize=(10, 8)) - node_colors = list() - for i in list(G.nodes()): - if "storage" in i: - node_colors.append("royalblue") - elif "BEV-V2G" in i: - node_colors.append("firebrick") - elif "BEV-G2V" in i: - node_colors.append("lightblue") - elif "BEV-inflex" in i: - node_colors.append("darkviolet") - elif "excess" in i: - node_colors.append("green") - elif "shortage" in i: - node_colors.append("yellow") - elif "load" in i: - node_colors.append("orange") - elif "wind" in i: - node_colors.append("pink") - elif "bus" in i: - node_colors.append("grey") - else: - node_colors.append("violet") + fig, ax = plt.subplots(figsize=(20, 16)) + node_colors = assign_color(list(G.nodes())) nx.draw( G, @@ -134,11 +137,56 @@ def draw_graph(energysystem): node_color=node_colors, # node_color=["red", "blue", "green", "yellow", "orange"], ) + + squares = [n for n in G.nodes() if "storage" in n] + square_colors = assign_color(squares) + + # Draw square nodes + nx.draw_networkx_nodes( + G, + pos, + nodelist=squares, + node_shape="s", # Square shape + node_size=3000, + node_color=square_colors, # Assign colors based on the list + ) + + diamonds = [n for n in G.nodes() if "conversion" in n or "v2g" in n] + diamond_colors = assign_color(diamonds) + + # Draw square nodes + nx.draw_networkx_nodes( + G, + pos, + nodelist=diamonds, + node_shape="D", # Square shape + node_size=3000, + node_color=diamond_colors, # Assign colors based on the list + ) + + triangle_circles = [n for n in G.nodes() if "demand" in n] + dtriangle_circle_colors = assign_color(triangle_circles) + + # Draw square nodes + nx.draw_networkx_nodes( + G, + pos, + nodelist=triangle_circles, + node_shape="^", # Square shape + node_size=3000, + node_color=dtriangle_circle_colors, # Assign colors based on the list + ) + labels = nx.get_edge_attributes(G, "weight") - nx.draw_networkx_edge_labels(G, pos=pos, edge_labels=labels) + # Adjust label positions + # label_pos = {key: (value[0], value[1] - 1e8) for key, value in pos.items()} # Lower the y-coordinate + + nx.draw_networkx_edge_labels( + G, pos=pos, edge_labels=labels, verticalalignment="top" + ) # Customize the plot as needed - ax.set_title("OEMOF Energy System Graph") + ax.set_title("Energy System Graph for BEV Flett") # Show the plot plt.show() diff --git a/tests/test_constraints.py b/tests/test_constraints.py index 026cf3b8..4ae53cc0 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -10,7 +10,7 @@ from oemof.tabular.constraint_facades import CONSTRAINT_TYPE_MAP from oemof.tabular.facades import ( BackpressureTurbine, - Bev, + BevTech, Commodity, Conversion, Dispatchable, @@ -510,7 +510,7 @@ def test_bev_trio(self): self.energysystem.add(pkm_demand) - bev_v2g = Bev( + bev_v2g = BevTech( type="bev", label="BEV-V2G", v2g=True, @@ -538,7 +538,7 @@ def test_bev_trio(self): self.energysystem.add(bev_v2g) - bev_flex = Bev( + bev_flex = BevTech( type="bev", label="BEV-inflex", electricity_bus=el_bus, @@ -570,7 +570,7 @@ def test_bev_trio(self): ) self.energysystem.add(bev_flex) - bev_fix = Bev( + bev_fix = BevTech( type="bev", label="BEV-G2V", electricity_bus=el_bus, diff --git a/tests/test_facades.py b/tests/test_facades.py index 3e4d44e0..fc9b9dc1 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -6,7 +6,7 @@ from oemof import solph from oemof.tabular.constraint_facades import CONSTRAINT_TYPE_MAP -from oemof.tabular.facades import Bev, Excess, Load, Shortage, Volatile +from oemof.tabular.facades import BevTech, Excess, Load, Shortage, Volatile # todo remove constraint bev_equal_invest from tests when they are removed as # feature @@ -107,7 +107,7 @@ def test_bev_v2g_dispatch(self): self.energysystem.add(pkm_demand) - bev_v2g = Bev( + bev_v2g = BevTech( type="bev", label="BEV-V2G", v2g=True, @@ -210,7 +210,7 @@ def test_bev_inflex_dispatch(self): ) self.energysystem.add(pkm_demand) - bev_inflex = Bev( + bev_inflex = BevTech( type="bev", label="BEV-inflex", electricity_bus=el_bus, @@ -304,7 +304,7 @@ def test_bev_g2v_dispatch(self): ) self.energysystem.add(pkm_demand) - bev_g2v = Bev( + bev_g2v = BevTech( type="bev", label="BEV-G2V", electricity_bus=el_bus, @@ -395,7 +395,7 @@ def test_bev_trio_dispatch(self): ) self.energysystem.add(pkm_demand) - bev_v2g = Bev( + bev_v2g = BevTech( type="bev", label="BEV-V2G", v2g=True, @@ -417,7 +417,7 @@ def test_bev_trio_dispatch(self): ) self.energysystem.add(bev_v2g) - bev_inflex = Bev( + bev_inflex = BevTech( type="bev", label="BEV-inflex", electricity_bus=el_bus, @@ -444,7 +444,7 @@ def test_bev_trio_dispatch(self): ) self.energysystem.add(bev_inflex) - bev_g2v = Bev( + bev_g2v = BevTech( type="bev", label="BEV-G2V", electricity_bus=el_bus, @@ -551,7 +551,7 @@ def test_bev_v2g_invest(self): self.energysystem.add(pkm_demand) - bev_v2g = Bev( + bev_v2g = BevTech( type="bev", label="BEV-V2G", v2g=True, @@ -666,7 +666,7 @@ def test_bev_trio_invest(self): ) self.energysystem.add(pkm_demand) - bev_v2g = Bev( + bev_v2g = BevTech( type="bev", label="BEV-V2G", v2g=True, @@ -693,7 +693,7 @@ def test_bev_trio_invest(self): ) self.energysystem.add(bev_v2g) - bev_inflex = Bev( + bev_inflex = BevTech( type="bev", label="BEV-inflex", electricity_bus=el_bus, @@ -725,7 +725,7 @@ def test_bev_trio_invest(self): ) self.energysystem.add(bev_inflex) - bev_g2v = Bev( + bev_g2v = BevTech( type="bev", label="BEV-G2V", electricity_bus=el_bus, @@ -907,7 +907,7 @@ def test_bev_v2g_invest_multi_period(self): ) self.energysystem.add(pkm_demand) - bev_v2g = Bev( + bev_v2g = BevTech( type="bev", label="BEV-V2G", v2g=True, @@ -1053,7 +1053,7 @@ def test_bev_trio_invest_multi_period(self): ) self.energysystem.add(pkm_demand) - bev_v2g = Bev( + bev_v2g = BevTech( type="bev", label="BEV-V2G", v2g=True, @@ -1080,7 +1080,7 @@ def test_bev_trio_invest_multi_period(self): ) self.energysystem.add(bev_v2g) - bev_inflex = Bev( + bev_inflex = BevTech( type="bev", label="BEV-inflex", electricity_bus=el_bus, @@ -1111,7 +1111,7 @@ def test_bev_trio_invest_multi_period(self): ) self.energysystem.add(bev_inflex) - bev_g2v = Bev( + bev_g2v = BevTech( type="bev", label="BEV-G2V", electricity_bus=el_bus, @@ -1283,7 +1283,7 @@ def test_bev_v2g_dispatch_multi_period(self): ) self.energysystem.add(pkm_demand) - bev_v2g = Bev( + bev_v2g = BevTech( type="bev", label="BEV-V2G", v2g=True,