From 256dd2783ae0a54ccd18d98688607b8814f77ccb Mon Sep 17 00:00:00 2001 From: jnnr <32454596+jnnr@users.noreply.github.com> Date: Tue, 14 Feb 2023 12:46:24 +0100 Subject: [PATCH 01/88] Add Bev --- .../experimental/battery_electric_vehicle.py | 197 ++++++++++++++++++ tests/test_constraints.py | 25 +++ 2 files changed, 222 insertions(+) create mode 100644 src/oemof/tabular/facades/experimental/battery_electric_vehicle.py diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py new file mode 100644 index 00000000..3efb9483 --- /dev/null +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -0,0 +1,197 @@ +from oemof.solph import Bus, Flow, Sink, Transformer, sequence +from oemof.solph.components import GenericStorage + +from oemof.tabular._facade import Facade + + +class Bev(GenericStorage, Facade): + r"""A fleet of Battery electric vehicles with vehicle-to-grid. + + Note that the investment option is not available for this facade at + the current development state. + + Parameters + ---------- + bus: oemof.solph.Bus + An oemof bus instance where the storage unit is connected to. + storage_capacity: numeric + The total storage capacity of the vehicles (e.g. in MWh) + capacity: numeric + Total charging/discharging capacity of the vehicles. + availability : array-like + Ratio of available capacity for charging/vehicle-to-grid due to + grid connection. + drive_power : array-like + Profile of the load of the fleet through driving relative amount. + amount : numeric + Total amount of energy consumed by driving. The drive_power profile + will be scaled by this number. + efficiency_charging: numeric + Efficiency of charging the batteries, default: 1 + efficiency_discharging: numeric + Efficiency of discharging the batteries, default: 1 + efficiency_v2g: numeric + Efficiency of vehicle-to-grid, default: 1 + min_storage_level : array-like + Profile of minimum storage level. + max_storage_level : array-like + Profile of maximum storage level. + 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. + output_parameters: dict + see: input_parameters + + + The vehicle fleet is modelled as a storage together with an internal + sink with fixed flow: + + .. 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( + ... name='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 + ... ) + + """ + + def __init__(self, *args, **kwargs): + + kwargs.update( + { + "_facade_requires_": [ + "bus", + "carrier", + "tech", + "availability", + "drive_power", + "amount", + ] + } + ) + super().__init__(*args, **kwargs) + + self.storage_capacity = kwargs.get("storage_capacity") + + self.capacity = kwargs.get("capacity") + + self.efficiency_charging = kwargs.get("efficiency_charging", 1) + + self.efficiency_discharging = kwargs.get("efficiency_discharging", 1) + + self.efficiency_v2g = kwargs.get("efficiency_v2g", 1) + + self.profile = kwargs.get("profile") + + self.marginal_cost = kwargs.get("marginal_cost", 0) + + self.input_parameters = kwargs.get("input_parameters", {}) + + self.output_parameters = kwargs.get("output_parameters", {}) + + self.expandable = bool(kwargs.get("expandable", False)) + + self.build_solph_components() + + def build_solph_components(self): + + self.nominal_storage_capacity = self.storage_capacity + + self.inflow_conversion_factor = sequence(self.efficiency_charging) + + self.outflow_conversion_factor = sequence(self.efficiency_discharging) + + if self.expandable: + raise NotImplementedError( + "Investment for bev class is not implemented." + ) + + internal_bus = Bus(label=self.label + "-internal_bus") + + vehicle_to_grid = Transformer( + carrier=self.carrier, + tech=self.tech, + label=self.label + "-vehicle_to_grid", + inputs={internal_bus: Flow()}, + outputs={ + self.bus: Flow( + nominal_value=self.capacity, + max=self.availability, + variable_costs=self.marginal_cost, + **self.output_parameters + ) + }, + conversion_factors={internal_bus: self.efficiency_v2g}, + ) + + drive_power = Sink( + label=self.label + "-drive_power", + inputs={ + internal_bus: Flow( + nominal_value=self.amount, + actual_value=self.drive_power, + fixed=True, + ) + }, + ) + + self.inputs.update( + { + self.bus: Flow( + nominal_value=self.capacity, + max=self.availability, + **self.input_parameters + ) + } + ) + + self.outputs.update({internal_bus: Flow()}) + + self.subnodes = (internal_bus, drive_power, vehicle_to_grid) diff --git a/tests/test_constraints.py b/tests/test_constraints.py index 7f7a1834..760eebc7 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -21,6 +21,7 @@ Storage, Volatile, ) +from oemof.tabular.facades.experimental.battery_electric_vehicle import Bev def chop_trailing_whitespace(lines): @@ -484,3 +485,27 @@ def test_emission_constraint(self): emission_constraint.build_constraint(model) self.compare_to_reference_lp("emission_constraint.lp", my_om=model) + + def test_bev(self): + bus = solph.Bus("my_bus") + + bev = Bev( + name="my_bev", + bus=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, + ) + + self.energysystem.add(bus, bev) + + self.compare_to_reference_lp("bev.lp") From e3e66409500eebd3472dd995b7d2c5528abd3e84 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Fri, 4 Aug 2023 15:04:30 +0200 Subject: [PATCH 02/88] Update to working state This implementation works as in generating an lp-file. Different working state and LP file need further attention. --- .../experimental/battery_electric_vehicle.py | 152 ++++++++++-------- 1 file changed, 82 insertions(+), 70 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index 3efb9483..f17cbc42 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -1,9 +1,14 @@ -from oemof.solph import Bus, Flow, Sink, Transformer, sequence -from oemof.solph.components import GenericStorage +from dataclasses import field +from typing import Sequence, Union -from oemof.tabular._facade import Facade +from oemof.solph.buses import Bus +from oemof.solph.components import GenericStorage, Sink, Transformer +from oemof.solph.flows import Flow +from oemof.tabular._facade import Facade, dataclass_facade + +@dataclass_facade class Bev(GenericStorage, Facade): r"""A fleet of Battery electric vehicles with vehicle-to-grid. @@ -14,28 +19,26 @@ class Bev(GenericStorage, Facade): ---------- bus: oemof.solph.Bus An oemof bus instance where the storage unit is connected to. - storage_capacity: numeric + storage_capacity: int The total storage capacity of the vehicles (e.g. in MWh) - capacity: numeric - Total charging/discharging capacity of the vehicles. + drive_power: int + Total charging/discharging capacity of the vehicles (e.g. in MW) + drive_consumption : array-like + Profile of drive consumption of the fleet (relative to capacity). + max_charging_power : int + Max charging/discharging power of all vehicles (e.g. in MW) availability : array-like Ratio of available capacity for charging/vehicle-to-grid due to grid connection. - drive_power : array-like - Profile of the load of the fleet through driving relative amount. - amount : numeric - Total amount of energy consumed by driving. The drive_power profile - will be scaled by this number. - efficiency_charging: numeric + efficiency_charging: float Efficiency of charging the batteries, default: 1 - efficiency_discharging: numeric - Efficiency of discharging the batteries, default: 1 - efficiency_v2g: numeric - Efficiency of vehicle-to-grid, default: 1 + v2g: bool + If True, vehicle-to-grid is enabled, default: False + loss_rate: float min_storage_level : array-like - Profile of minimum storage level. + Profile of minimum storage level (min SOC) max_storage_level : array-like - Profile of maximum storage level. + Profile of maximum storage level (max SOC). 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. @@ -84,7 +87,7 @@ class Bev(GenericStorage, Facade): >>> from oemof.tabular import facades >>> my_bus = solph.Bus('my_bus') >>> my_bev = Bev( - ... name='my_bev', + ... label='my_bev', ... bus=el_bus, ... carrier='electricity', ... tech='bev', @@ -102,96 +105,105 @@ class Bev(GenericStorage, Facade): """ - def __init__(self, *args, **kwargs): - - kwargs.update( - { - "_facade_requires_": [ - "bus", - "carrier", - "tech", - "availability", - "drive_power", - "amount", - ] - } - ) - super().__init__(*args, **kwargs) + bus: Bus - self.storage_capacity = kwargs.get("storage_capacity") + storage_capacity: int - self.capacity = kwargs.get("capacity") + drive_power: int - self.efficiency_charging = kwargs.get("efficiency_charging", 1) + drive_consumption: Sequence - self.efficiency_discharging = kwargs.get("efficiency_discharging", 1) + max_charging_power: Union[float, Sequence[float]] - self.efficiency_v2g = kwargs.get("efficiency_v2g", 1) + availability: Sequence[float] - self.profile = kwargs.get("profile") + efficiency_charging: float = 1 - self.marginal_cost = kwargs.get("marginal_cost", 0) + v2g: bool = False - self.input_parameters = kwargs.get("input_parameters", {}) + input_parameters: dict = field(default_factory=dict) - self.output_parameters = kwargs.get("output_parameters", {}) + output_parameters: dict = field(default_factory=dict) - self.expandable = bool(kwargs.get("expandable", False)) - - self.build_solph_components() + expandable: bool = False def build_solph_components(self): + facade_label = self.label + self.label = self.label + "-storage" self.nominal_storage_capacity = self.storage_capacity - self.inflow_conversion_factor = sequence(self.efficiency_charging) - - self.outflow_conversion_factor = sequence(self.efficiency_discharging) - if self.expandable: raise NotImplementedError( "Investment for bev class is not implemented." ) - internal_bus = Bus(label=self.label + "-internal_bus") + internal_bus = Bus(label=facade_label + "-internal_bus") + subnodes = [internal_bus] + + # Discharging + if self.v2g: + vehicle_to_grid = Transformer( + label=facade_label + "-v2g", + inputs={internal_bus: Flow()}, + outputs={ + self.bus: Flow( + nominal_value=self.max_charging_power, + max=self.availability, + # **self.output_parameters + ) + }, + # Includes storage charging efficiencies + conversion_factors={self.bus: self.efficiency_charging}, + ) + subnodes.append(vehicle_to_grid) - vehicle_to_grid = Transformer( - carrier=self.carrier, - tech=self.tech, - label=self.label + "-vehicle_to_grid", - inputs={internal_bus: Flow()}, - outputs={ + # Charging + grid_to_vehicle = Transformer( + label=facade_label + "g2v", + inputs={ self.bus: Flow( - nominal_value=self.capacity, + nominal_value=self.max_charging_power, max=self.availability, - variable_costs=self.marginal_cost, - **self.output_parameters + # **self.output_parameters ) }, - conversion_factors={internal_bus: self.efficiency_v2g}, + outputs={internal_bus: Flow()}, + conversion_factors={self.bus: self.efficiency_charging}, ) + subnodes.append(grid_to_vehicle) - drive_power = Sink( - label=self.label + "-drive_power", + # Drive consumption + driving_consumption = Sink( + label=facade_label + "-consumption", inputs={ internal_bus: Flow( - nominal_value=self.amount, - actual_value=self.drive_power, - fixed=True, + nominal_value=self.drive_power, + fix=self.drive_consumption, ) }, ) + subnodes.append(driving_consumption) + # Storage input self.inputs.update( { self.bus: Flow( - nominal_value=self.capacity, + nominal_value=self.max_charging_power, max=self.availability, **self.input_parameters ) } ) + # Storage output + self.outputs.update( + { + internal_bus: Flow( + nominal_value=self.max_charging_power, + max=self.availability, + ) + } + ) - self.outputs.update({internal_bus: Flow()}) - - self.subnodes = (internal_bus, drive_power, vehicle_to_grid) + # many components in facade + self.subnodes = subnodes From 4cbd80b7fec3af677488540426441d3d6a129ba8 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Fri, 4 Aug 2023 15:07:06 +0200 Subject: [PATCH 03/88] Update constraint test --- tests/test_constraints.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/tests/test_constraints.py b/tests/test_constraints.py index 846d55ab..7ef575fd 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -103,7 +103,8 @@ def setup_method(cls): def get_om(self): return solph.Model( - self.energysystem, timeindex=self.energysystem.timeindex + self.energysystem, + timeindex=self.energysystem.timeindex, ) def compare_to_reference_lp(self, ref_filename, my_om=None): @@ -493,23 +494,21 @@ def test_emission_constraint(self): self.compare_to_reference_lp("emission_constraint.lp", my_om=model) def test_bev(self): - bus = solph.Bus("my_bus") + bus = solph.Bus("bus") bev = Bev( - name="my_bev", + label="bev", bus=bus, - carrier="electricity", - tech="bev", storage_capacity=1000, - capacity=50, + drive_power=50, + drive_consumption=[0.8, 0.7, 0.6], + max_charging_power=10, availability=[0.8, 0.7, 0.6], - drive_power=[0.3, 0.2, 0.5], - amount=450, + efficiency_charging=0.93, + v2g=True, 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, + min_storage_level=[0.1, 0.2, 0.15, 0.15], + max_storage_level=[0.9, 0.95, 0.92, 0.92], ) self.energysystem.add(bus, bev) From e16cd891808b4dbb233d409cea4c74ce4f401c6b Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Fri, 4 Aug 2023 15:17:58 +0200 Subject: [PATCH 04/88] Add storage conversion rate and balance parameter --- .../facades/experimental/battery_electric_vehicle.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index f17cbc42..a1458cd6 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -39,6 +39,9 @@ class Bev(GenericStorage, Facade): Profile of minimum storage level (min SOC) max_storage_level : array-like Profile of maximum storage level (max SOC). + 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. @@ -132,6 +135,9 @@ def build_solph_components(self): self.label = self.label + "-storage" self.nominal_storage_capacity = self.storage_capacity + self.inflow_conversion_factor = 1 + self.outflow_conversion_factor = 1 + self.balanced = self.balanced if self.expandable: raise NotImplementedError( From 0275ba7f202e616c34f6c006798c44c9fa7b7588 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Fri, 4 Aug 2023 15:23:50 +0200 Subject: [PATCH 05/88] Plumb inflow/outflow conversion rate with sequence --- .../tabular/facades/experimental/battery_electric_vehicle.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index a1458cd6..fa9edcad 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -1,6 +1,7 @@ from dataclasses import field from typing import Sequence, Union +from oemof.solph._plumbing import sequence as solph_sequence from oemof.solph.buses import Bus from oemof.solph.components import GenericStorage, Sink, Transformer from oemof.solph.flows import Flow @@ -135,8 +136,8 @@ def build_solph_components(self): self.label = self.label + "-storage" self.nominal_storage_capacity = self.storage_capacity - self.inflow_conversion_factor = 1 - self.outflow_conversion_factor = 1 + self.inflow_conversion_factor = solph_sequence(1) + self.outflow_conversion_factor = solph_sequence(1) self.balanced = self.balanced if self.expandable: From 8a2694c58234b9cfb0651332890b56142037607a Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 15 Aug 2023 16:13:18 +0200 Subject: [PATCH 06/88] Fix label --- .../tabular/facades/experimental/battery_electric_vehicle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index fa9edcad..a0e1d217 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -167,7 +167,7 @@ def build_solph_components(self): # Charging grid_to_vehicle = Transformer( - label=facade_label + "g2v", + label=facade_label + "-g2v", inputs={ self.bus: Flow( nominal_value=self.max_charging_power, @@ -192,7 +192,7 @@ def build_solph_components(self): ) subnodes.append(driving_consumption) - # Storage input + # Storage inputs self.inputs.update( { self.bus: Flow( From 75f8be0d3a23de9d2168679c2ebf5f9be2ccec80 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 15 Aug 2023 16:14:47 +0200 Subject: [PATCH 07/88] Draft for joint extension constraint --- src/oemof/tabular/constraint_facades.py | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/oemof/tabular/constraint_facades.py b/src/oemof/tabular/constraint_facades.py index 50cb6307..6f2f88fc 100644 --- a/src/oemof/tabular/constraint_facades.py +++ b/src/oemof/tabular/constraint_facades.py @@ -2,6 +2,7 @@ from dataclasses import dataclass from oemof.solph.constraints.integral_limit import generic_integral_limit +from oemof.solph.constraints.equate_variables import equate_variables class ConstraintFacade(abc.ABC): @@ -40,3 +41,34 @@ def build_constraint(self, model): CONSTRAINT_TYPE_MAP = {"generic_integral_limit": GenericIntegralLimit} + + +# @dataclass +# class JointExtension(ConstraintFacade): +# name: str +# type: str +# limit: float +# keyword: str = "joint_extension" +# +# +# def build_constraint(self, model): +# # components = {} +# # for i in model.NODES: +# # if hasattr(model.NODES[i], self.keyword): +# # components[(i, o)] = model.NODES[i] +# components = {i: model.NODES[i] for i in model.NODES if +# hasattr(model.NODES[i], self.keyword)} +# +# if not components: +# raise Warning(f"No components with keyword {self.keyword}") +# else: +# print( +# "These components will be extended jointly: " +# f"{components.keys()}" +# ) +# +# # add constraint to the model +# equate_variables( +# model, +# var1=model.InvestmentFlowBlock.invest[], +# var2=) From 82707e6f94bb07b4f41a0dfe9683098a8d4b0367 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 25 Sep 2023 10:52:46 +0200 Subject: [PATCH 08/88] Update converter --- .../experimental/battery_electric_vehicle.py | 44 +++++++++++++------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index a0e1d217..ca6530db 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -3,7 +3,7 @@ from oemof.solph._plumbing import sequence as solph_sequence from oemof.solph.buses import Bus -from oemof.solph.components import GenericStorage, Sink, Transformer +from oemof.solph.components import GenericStorage, Sink, Converter from oemof.solph.flows import Flow from oemof.tabular._facade import Facade, dataclass_facade @@ -150,7 +150,7 @@ def build_solph_components(self): # Discharging if self.v2g: - vehicle_to_grid = Transformer( + vehicle_to_grid = Converter( label=facade_label + "-v2g", inputs={internal_bus: Flow()}, outputs={ @@ -166,7 +166,7 @@ def build_solph_components(self): subnodes.append(vehicle_to_grid) # Charging - grid_to_vehicle = Transformer( + grid_to_vehicle = Converter( label=facade_label + "-g2v", inputs={ self.bus: Flow( @@ -181,16 +181,34 @@ def build_solph_components(self): subnodes.append(grid_to_vehicle) # Drive consumption - driving_consumption = Sink( - label=facade_label + "-consumption", - inputs={ - internal_bus: Flow( - nominal_value=self.drive_power, - fix=self.drive_consumption, - ) - }, - ) - subnodes.append(driving_consumption) + if self.transport_commodity_bus: + # if True: + transport_commodity = Converter( + label=facade_label + "-consumption-converter", + inputs={ + internal_bus: Flow( + nominal_value=self.max_charging_power, + max=self.availability, + # **self.output_parameters + ) + }, + outputs={self.transport_commodity_bus: Flow()}, + conversion_factors={self.bus: self.efficiency_charging}, + # TODO maybe add battery efficiency + charger efficiency + ) + subnodes.append(transport_commodity) + + else: + driving_consumption = Sink( + label=facade_label + "-consumption", + inputs={ + internal_bus: Flow( + nominal_value=self.drive_power, + fix=self.drive_consumption, + ) + }, + ) + subnodes.append(driving_consumption) # Storage inputs self.inputs.update( From 616ae7740426fdd735aa6ee4bf2e2a8db52b4a14 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 25 Sep 2023 10:54:07 +0200 Subject: [PATCH 09/88] Update buses --- .../experimental/battery_electric_vehicle.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index ca6530db..77e67a5d 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -43,6 +43,8 @@ class Bev(GenericStorage, Facade): balanced : boolean Couple storage level of first and last time step. (Total inflow and total outflow are balanced.) + transport_commodity: None + Bus for the transport commodity 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. @@ -109,7 +111,7 @@ class Bev(GenericStorage, Facade): """ - bus: Bus + electricity_bus: Bus storage_capacity: int @@ -125,6 +127,8 @@ class Bev(GenericStorage, Facade): v2g: bool = False + transport_commodity_bus: Bus = None + input_parameters: dict = field(default_factory=dict) output_parameters: dict = field(default_factory=dict) @@ -146,6 +150,7 @@ def build_solph_components(self): ) internal_bus = Bus(label=facade_label + "-internal_bus") + self.bus = internal_bus subnodes = [internal_bus] # Discharging @@ -154,14 +159,15 @@ def build_solph_components(self): label=facade_label + "-v2g", inputs={internal_bus: Flow()}, outputs={ - self.bus: Flow( + self.electricity_bus: Flow( nominal_value=self.max_charging_power, max=self.availability, # **self.output_parameters ) }, # Includes storage charging efficiencies - conversion_factors={self.bus: self.efficiency_charging}, + conversion_factors={self.electricity_bus: self.efficiency_charging}, + # TODO maybe add battery efficiency + charger efficiency ) subnodes.append(vehicle_to_grid) @@ -169,14 +175,15 @@ def build_solph_components(self): grid_to_vehicle = Converter( label=facade_label + "-g2v", inputs={ - self.bus: Flow( + self.electricity_bus: Flow( nominal_value=self.max_charging_power, max=self.availability, # **self.output_parameters ) }, outputs={internal_bus: Flow()}, - conversion_factors={self.bus: self.efficiency_charging}, + conversion_factors={self.electricity_bus: self.efficiency_charging}, + # TODO maybe add battery efficiency + charger efficiency ) subnodes.append(grid_to_vehicle) @@ -213,7 +220,7 @@ def build_solph_components(self): # Storage inputs self.inputs.update( { - self.bus: Flow( + internal_bus: Flow( nominal_value=self.max_charging_power, max=self.availability, **self.input_parameters From 89061db9760071a9d425337b3970aa1314c97ab7 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 25 Sep 2023 10:55:16 +0200 Subject: [PATCH 10/88] Comment sequencing --- .../tabular/facades/experimental/battery_electric_vehicle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index 77e67a5d..64c2049e 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -140,8 +140,8 @@ def build_solph_components(self): self.label = self.label + "-storage" self.nominal_storage_capacity = self.storage_capacity - self.inflow_conversion_factor = solph_sequence(1) - self.outflow_conversion_factor = solph_sequence(1) + # self.inflow_conversion_factor = solph_sequence(1) + # self.outflow_conversion_factor = solph_sequence(1) self.balanced = self.balanced if self.expandable: From be8c74307675cd07239bdc13f31c7e65db2d4f9c Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Thu, 28 Sep 2023 15:31:23 +0200 Subject: [PATCH 11/88] Rename labels --- .../experimental/battery_electric_vehicle.py | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index 64c2049e..c91a244b 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -7,6 +7,7 @@ from oemof.solph.flows import Flow from oemof.tabular._facade import Facade, dataclass_facade +from oemof.tabular.facades import Load @dataclass_facade @@ -102,7 +103,7 @@ class Bev(GenericStorage, Facade): ... availability=[0.8, 0.7, 0.6], ... drive_power=[0.3, 0.2, 0.5], ... amount=450, - ... loss_rate=0.01, + # ... 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], @@ -117,12 +118,12 @@ class Bev(GenericStorage, Facade): drive_power: int - drive_consumption: Sequence - max_charging_power: Union[float, Sequence[float]] availability: Sequence[float] + drive_consumption: Sequence = None + efficiency_charging: float = 1 v2g: bool = False @@ -142,14 +143,16 @@ def build_solph_components(self): self.nominal_storage_capacity = self.storage_capacity # self.inflow_conversion_factor = solph_sequence(1) # self.outflow_conversion_factor = solph_sequence(1) - self.balanced = self.balanced + # self.balanced = self.balanced # TODO not in multi-period + self.balanced = False + self.initial_storage_level=0 if self.expandable: raise NotImplementedError( "Investment for bev class is not implemented." ) - internal_bus = Bus(label=facade_label + "-internal_bus") + internal_bus = Bus(label=facade_label + "-bev-bus") self.bus = internal_bus subnodes = [internal_bus] @@ -157,16 +160,16 @@ def build_solph_components(self): if self.v2g: vehicle_to_grid = Converter( label=facade_label + "-v2g", - inputs={internal_bus: Flow()}, + inputs={internal_bus: Flow( + nominal_value=self.max_charging_power, + max=self.availability, + )}, outputs={ - self.electricity_bus: Flow( - nominal_value=self.max_charging_power, - max=self.availability, - # **self.output_parameters - ) + self.electricity_bus: Flow() }, # Includes storage charging efficiencies - conversion_factors={self.electricity_bus: self.efficiency_charging}, + conversion_factors={internal_bus: self.efficiency_charging}, + # conversion_factors={self.electricity_bus: self.efficiency_charging}, # TODO maybe add battery efficiency + charger efficiency ) subnodes.append(vehicle_to_grid) @@ -191,7 +194,7 @@ def build_solph_components(self): if self.transport_commodity_bus: # if True: transport_commodity = Converter( - label=facade_label + "-consumption-converter", + label=facade_label + "-to-pkm", inputs={ internal_bus: Flow( nominal_value=self.max_charging_power, From 6383a86c3f852582f7f9654ae9b22a9ed0597fc1 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Thu, 28 Sep 2023 15:32:02 +0200 Subject: [PATCH 12/88] Add mobility sector facade --- .../experimental/battery_electric_vehicle.py | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index c91a244b..2aaf2e12 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -242,3 +242,126 @@ def build_solph_components(self): # many components in facade self.subnodes = subnodes + + +@dataclass_facade +class individual_mobility_sector(Facade): + r"""A fleet of Battery electric vehicles with vehicle-to-grid. + + Note that the investment option is not available for this facade at + the current development state. + + Parameters + ---------- + bus: oemof.solph.Bus + An oemof bus instance where the storage unit is connected to. + storage_capacity: int + The total storage capacity of the vehicles (e.g. in MWh) + drive_power: int + Total charging/discharging capacity of the vehicles (e.g. in MW) + drive_consumption : array-like + Profile of drive consumption of the fleet (relative to capacity). + max_charging_power : int + Max charging/discharging power of all vehicles (e.g. in MW) + availability : array-like + Ratio of available capacity for charging/vehicle-to-grid due to + grid connection. + efficiency_charging: float + Efficiency of charging the batteries, default: 1 + v2g: bool + If True, vehicle-to-grid is enabled, default: False + loss_rate: float + min_storage_level : array-like + Profile of minimum storage level (min SOC) + max_storage_level : array-like + Profile of maximum storage level (max SOC). + balanced : boolean + Couple storage level of first and last time step. + (Total inflow and total outflow are balanced.) + transport_commodity: None + Bus for the transport commodity + 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. + output_parameters: dict + see: input_parameters + """ + + electricity_bus: Bus + + storage_capacity: int + + drive_power: int + + max_charging_power: Union[float, Sequence[float]] + + availability: Sequence[float] + + label: str + + # type: str = "ind_mob_sec" + + drive_consumption: Sequence = None + + efficiency_charging: float = 1 + + v2g: bool = False + + transport_commodity_bus: Bus = None + + input_parameters: dict = field(default_factory=dict) + + output_parameters: dict = field(default_factory=dict) + + expandable: bool = False + + def build_solph_components(self): + + transport_commodity_bus = Bus(label="transport_commodity") + transport_commodity_bus.type = "bus" + + mobility_nodes = [transport_commodity_bus] + + bev_controlled_v2g = Bev( + label="V2G", + electricity_bus=self.electricity_bus, + storage_capacity=self.storage_capacity, + drive_power=self.drive_power, + max_charging_power=self.max_charging_power, + availability=self.availability, + efficiency_charging=self.efficiency_charging, + v2g=True, + transport_commodity_bus=transport_commodity_bus, + ) + + mobility_nodes.append(bev_controlled_v2g) + + bev_controlled = Bev( + label="BEV", + electricity_bus=self.electricity_bus, + storage_capacity=self.storage_capacity, + drive_power=self.drive_power, + max_charging_power=self.max_charging_power, + availability=self.availability, + efficiency_charging=self.efficiency_charging, + v2g=False, + transport_commodity_bus=transport_commodity_bus, + ) + + mobility_nodes.append(bev_controlled) + + + pkm_demand = Load( + label="pkm_demand", + type="Load", + carrier="pkm", + bus=transport_commodity_bus, + amount=400, + profile=[0, 1, 0], + ) + + mobility_nodes.append(pkm_demand) + + # many components in facade + self.subnodes = mobility_nodes + From a7ff4ad1452d468025e16bf9ad9672c6a8ee07c0 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 24 Oct 2023 17:56:18 +0200 Subject: [PATCH 13/88] Add `bev` flag to _investment() If bev is True, all investment parameters are given but no ep_costs. This is because multiple components are linked within one bev facade. But only the in flow of the storage shall have the aggregated costs for the whole facade. --- src/oemof/tabular/_facade.py | 97 +++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 39 deletions(-) diff --git a/src/oemof/tabular/_facade.py b/src/oemof/tabular/_facade.py index 22b10bbe..2c1c50f3 100644 --- a/src/oemof/tabular/_facade.py +++ b/src/oemof/tabular/_facade.py @@ -151,7 +151,7 @@ def __init__(self, *args, **kwargs): add_subnodes, sender=self ) - def _nominal_value(self): + def _nominal_value(self, value=None): """Returns None if self.expandable ist True otherwise it returns the capacity """ @@ -168,58 +168,77 @@ def _nominal_value(self): "to_from": self.to_from_capacity, } else: - return self.capacity + if value: + return value + else: + return self.capacity - def _investment(self): + def _investment(self, bev=False): if not self.expandable: self.investment = None return self.investment - if self.capacity_cost is None: - msg = ( - "If you set `expandable`to True you need to set " - "attribute `capacity_cost` of component {}!" + + if bev: + self.investment = Investment( + ep_costs=0, + # ep_costs=self.bev_capacity_cost, + 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=getattr(self, "fixed_costs", None), ) - raise ValueError(msg.format(self.label)) - # If storage component - if isinstance(self, GenericStorage): - # If invest costs/MWH are given - if self.storage_capacity_cost is not None: - self.investment = Investment( - ep_costs=self.storage_capacity_cost, - 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=getattr(self, "fixed_costs", None), + else: + if self.capacity_cost is None: + msg = ( + "If you set `expandable`to True you need to set " + "attribute `capacity_cost` of component {}!" ) - # If invest costs/MWh are not given + raise ValueError(msg.format(self.label)) + + # If storage component + if isinstance(self, GenericStorage): + # If invest costs/MWH are given + if self.storage_capacity_cost is not None: + self.investment = Investment( + ep_costs=self.storage_capacity_cost, + 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=getattr(self, "fixed_costs", None), + ) + # If invest costs/MWh are not given + else: + self.investment = Investment( + 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=getattr(self, "fixed_costs", None), + ) + # If other component than storage or Bev else: self.investment = Investment( + ep_costs=self.capacity_cost, maximum=self._get_maximum_additional_invest( - "storage_capacity_potential", "storage_capacity" + "capacity_potential", "capacity" ), - minimum=getattr(self, "minimum_storage_capacity", 0), - existing=getattr(self, "storage_capacity", 0), + minimum=getattr(self, "capacity_minimum", 0), + existing=getattr(self, "capacity", 0), lifetime=getattr(self, "lifetime", None), age=getattr(self, "age", 0), fixed_costs=getattr(self, "fixed_costs", None), ) - # If other component than storage - else: - self.investment = Investment( - ep_costs=self.capacity_cost, - maximum=self._get_maximum_additional_invest( - "capacity_potential", "capacity" - ), - minimum=getattr(self, "capacity_minimum", 0), - existing=getattr(self, "capacity", 0), - lifetime=getattr(self, "lifetime", None), - age=getattr(self, "age", 0), - fixed_costs=getattr(self, "fixed_costs", None), - ) return self.investment def _get_maximum_additional_invest(self, attr_potential, attr_existing): From fef8d9332313178990e7e84f354c8f85dfb08b75 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 24 Oct 2023 18:11:19 +0200 Subject: [PATCH 14/88] Make bev expandable --- .../experimental/battery_electric_vehicle.py | 350 ++++++++---------- 1 file changed, 164 insertions(+), 186 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index 2aaf2e12..d682cd5b 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -1,13 +1,15 @@ from dataclasses import field from typing import Sequence, Union +from oemof.solph import Investment from oemof.solph._plumbing import sequence as solph_sequence from oemof.solph.buses import Bus -from oemof.solph.components import GenericStorage, Sink, Converter +from oemof.solph.components import Converter, GenericStorage, Sink from oemof.solph.flows import Flow from oemof.tabular._facade import Facade, dataclass_facade -from oemof.tabular.facades import Load + +# from oemof.tabular.facades import Load @dataclass_facade @@ -19,7 +21,7 @@ class Bev(GenericStorage, Facade): Parameters ---------- - bus: oemof.solph.Bus + electricity_bus: oemof.solph.Bus An oemof bus instance where the storage unit is connected to. storage_capacity: int The total storage capacity of the vehicles (e.g. in MWh) @@ -114,17 +116,17 @@ class Bev(GenericStorage, Facade): electricity_bus: Bus - storage_capacity: int + availability: Union[float, Sequence[float]] = 1 - drive_power: int + max_charging_power: Union[float, Sequence[float]] = 0 - max_charging_power: Union[float, Sequence[float]] + drive_power: int = 0 - availability: Sequence[float] + capacity: int = 0 - drive_consumption: Sequence = None + storage_capacity: int = 0 - efficiency_charging: float = 1 + drive_consumption: Sequence = None v2g: bool = False @@ -136,232 +138,208 @@ class Bev(GenericStorage, Facade): expandable: bool = False + bev_capacity_cost: Sequence[float] = None + + invest_c_rate: Sequence[float] = None + + marginal_cost: float = 0 + + pkm_conversion_rate: float = 1 + + efficiency_mob_g2v: float = 1 + + efficiency_mob_v2g: float = 1 + + efficiency_mob_electrical: float = 1 + + efficiency_sto_in: float = 1 + + efficiency_sto_out: float = 1 + def build_solph_components(self): - facade_label = self.label + self.facade_label = self.label self.label = self.label + "-storage" + self.availability = solph_sequence(self.availability) + self.max_charging_power = solph_sequence(self.max_charging_power) + self.nominal_storage_capacity = self.storage_capacity + # self.nominal_storage_capacity = self._nominal_value( + # self.storage_capacity) # self.inflow_conversion_factor = solph_sequence(1) # self.outflow_conversion_factor = solph_sequence(1) # self.balanced = self.balanced # TODO not in multi-period self.balanced = False - self.initial_storage_level=0 + self.initial_storage_level = 0 - if self.expandable: - raise NotImplementedError( - "Investment for bev class is not implemented." - ) - - internal_bus = Bus(label=facade_label + "-bev-bus") + internal_bus = Bus(label=self.facade_label + "-bus") self.bus = internal_bus subnodes = [internal_bus] - # Discharging + # ##### Vehicle2Grid Converter ##### if self.v2g: vehicle_to_grid = Converter( - label=facade_label + "-v2g", - inputs={internal_bus: Flow( - nominal_value=self.max_charging_power, - max=self.availability, - )}, + label=self.facade_label + "-v2g", + inputs={ + internal_bus: Flow( + # variable_costs=self.carrier_cost, + # **self.input_parameters + ) + }, outputs={ - self.electricity_bus: Flow() + self.electricity_bus: Flow( + nominal_value=self._nominal_value( + value=self.max_charging_power + ), + # max=self.availability, # doesn't work with investment + variable_costs=None, + investment=self._investment(bev=True), + ) }, # Includes storage charging efficiencies - conversion_factors={internal_bus: self.efficiency_charging}, - # conversion_factors={self.electricity_bus: self.efficiency_charging}, - # TODO maybe add battery efficiency + charger efficiency + conversion_factors={internal_bus: self.efficiency_mob_g2v}, + # TODO check efficiencies ) subnodes.append(vehicle_to_grid) - # Charging - grid_to_vehicle = Converter( - label=facade_label + "-g2v", - inputs={ - self.electricity_bus: Flow( - nominal_value=self.max_charging_power, - max=self.availability, - # **self.output_parameters - ) - }, - outputs={internal_bus: Flow()}, - conversion_factors={self.electricity_bus: self.efficiency_charging}, - # TODO maybe add battery efficiency + charger efficiency + # ##### Charging Converter ##### + + self.inflow_conversion_factor = solph_sequence( + self.efficiency_mob_g2v * self.efficiency_sto_in + ) + + self.outflow_conversion_factor = solph_sequence( + self.efficiency_sto_out ) - subnodes.append(grid_to_vehicle) + + # grid_to_vehicle = Converter( + # label=self.facade_label + "-g2v", + # inputs={ + # self.electricity_bus: Flow( + # # **self.output_parameters + # ) + # }, + # outputs={internal_bus: Flow( + # nominal_value=self._nominal_value( + # value=self.max_charging_power), + # max=self.availability, + # variable_costs=None, + # investment=self._investment(bev=True), + # )}, + # conversion_factors={ + # self.electricity_bus: self.efficiency_charging}, + # # TODO maybe add battery efficiency + charger efficiency + # ) + # subnodes.append(grid_to_vehicle) # Drive consumption if self.transport_commodity_bus: - # if True: + # ##### PKM Converter ##### transport_commodity = Converter( - label=facade_label + "-to-pkm", + label=self.facade_label + "-2pkm", inputs={ internal_bus: Flow( - nominal_value=self.max_charging_power, - max=self.availability, # **self.output_parameters ) }, - outputs={self.transport_commodity_bus: Flow()}, - conversion_factors={self.bus: self.efficiency_charging}, + outputs={ + self.transport_commodity_bus: Flow( + nominal_value=self._nominal_value( + self.max_charging_power + ), + max=self.availability, + variable_costs=None, + investment=self._investment(bev=True), + ) + }, + conversion_factors={self.bus: self.pkm_conversion_rate}, # TODO maybe add battery efficiency + charger efficiency ) subnodes.append(transport_commodity) else: + # ##### Consumption Sink ##### driving_consumption = Sink( - label=facade_label + "-consumption", + label=self.facade_label + "-consumption", inputs={ internal_bus: Flow( - nominal_value=self.drive_power, + nominal_value=self._nominal_value(self.drive_power), fix=self.drive_consumption, + investment=self._investment(bev=True), ) }, ) subnodes.append(driving_consumption) - # Storage inputs - self.inputs.update( - { - internal_bus: Flow( - nominal_value=self.max_charging_power, - max=self.availability, - **self.input_parameters - ) - } - ) - # Storage output - self.outputs.update( - { - internal_bus: Flow( - nominal_value=self.max_charging_power, - max=self.availability, - ) - } - ) - - # many components in facade - self.subnodes = subnodes - - -@dataclass_facade -class individual_mobility_sector(Facade): - r"""A fleet of Battery electric vehicles with vehicle-to-grid. - - Note that the investment option is not available for this facade at - the current development state. + # ##### Storage ##### - Parameters - ---------- - bus: oemof.solph.Bus - An oemof bus instance where the storage unit is connected to. - storage_capacity: int - The total storage capacity of the vehicles (e.g. in MWh) - drive_power: int - Total charging/discharging capacity of the vehicles (e.g. in MW) - drive_consumption : array-like - Profile of drive consumption of the fleet (relative to capacity). - max_charging_power : int - Max charging/discharging power of all vehicles (e.g. in MW) - availability : array-like - Ratio of available capacity for charging/vehicle-to-grid due to - grid connection. - efficiency_charging: float - Efficiency of charging the batteries, default: 1 - v2g: bool - If True, vehicle-to-grid is enabled, default: False - loss_rate: float - min_storage_level : array-like - Profile of minimum storage level (min SOC) - max_storage_level : array-like - Profile of maximum storage level (max SOC). - balanced : boolean - Couple storage level of first and last time step. - (Total inflow and total outflow are balanced.) - transport_commodity: None - Bus for the transport commodity - 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. - output_parameters: dict - see: input_parameters - """ - - electricity_bus: Bus - - storage_capacity: int - - drive_power: int - - max_charging_power: Union[float, Sequence[float]] - - availability: Sequence[float] - - label: str - - # type: str = "ind_mob_sec" - - drive_consumption: Sequence = None - - efficiency_charging: float = 1 - - v2g: bool = False - - transport_commodity_bus: Bus = None - - input_parameters: dict = field(default_factory=dict) - - output_parameters: dict = field(default_factory=dict) - - expandable: bool = False - - def build_solph_components(self): - - transport_commodity_bus = Bus(label="transport_commodity") - transport_commodity_bus.type = "bus" - - mobility_nodes = [transport_commodity_bus] - - bev_controlled_v2g = Bev( - label="V2G", - electricity_bus=self.electricity_bus, - storage_capacity=self.storage_capacity, - drive_power=self.drive_power, - max_charging_power=self.max_charging_power, - availability=self.availability, - efficiency_charging=self.efficiency_charging, - v2g=True, - transport_commodity_bus=transport_commodity_bus, - ) + if self.expandable: + self.capacity_cost = self.bev_capacity_cost + self.storage_capacity_cost = 1 + # 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 + ) # Power/Energy + self.invest_relation_output_capacity = ( + 1 / self.invest_c_rate + ) # 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) + ) - mobility_nodes.append(bev_controlled_v2g) - - bev_controlled = Bev( - label="BEV", - electricity_bus=self.electricity_bus, - storage_capacity=self.storage_capacity, - drive_power=self.drive_power, - max_charging_power=self.max_charging_power, - availability=self.availability, - efficiency_charging=self.efficiency_charging, - v2g=False, - transport_commodity_bus=transport_commodity_bus, - ) + # set capacity costs at one of the flows + flow_in = Flow( + # max=self.availability, + investment=Investment( + ep_costs=self.bev_capacity_cost, + maximum=self._get_maximum_additional_invest( + "capacity_potential", "capacity" + ), + existing=getattr(self, "capacity", 0), + lifetime=getattr(self, "lifetime", None), + age=getattr(self, "age", 0), + fixed_costs=getattr(self, "fixed_costs", None), + ), + **self.input_parameters, + ) + # set investment, but no costs (as relation input / output = 1) + flow_out = Flow( + investment=Investment( + existing=getattr(self, "capacity", 0), + lifetime=getattr(self, "lifetime", None), + age=getattr(self, "age", 0), + ), + variable_costs=self.marginal_cost, + **self.output_parameters, + ) + # required for correct grouping in oemof.solph.components + self._invest_group = True - mobility_nodes.append(bev_controlled) + else: + flow_in = Flow( + nominal_value=self._nominal_value(self.max_charging_power), + max=self.availability, + **self.input_parameters, + ) + flow_out = Flow( + nominal_value=self._nominal_value(self.max_charging_power), + # max=self.availability, + variable_costs=self.marginal_cost, + **self.output_parameters, + ) + self.inputs.update({self.electricity_bus: flow_in}) - pkm_demand = Load( - label="pkm_demand", - type="Load", - carrier="pkm", - bus=transport_commodity_bus, - amount=400, - profile=[0, 1, 0], - ) + self.outputs.update({self.bus: flow_out}) - mobility_nodes.append(pkm_demand) + self._set_flows() # many components in facade - self.subnodes = mobility_nodes - + self.subnodes = subnodes From 69754d02e327097533c5995f8d82d44b53b97242 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 24 Oct 2023 18:58:54 +0200 Subject: [PATCH 15/88] Add test script for mobility with bev --- tests/mobility.py | 397 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 397 insertions(+) create mode 100644 tests/mobility.py diff --git a/tests/mobility.py b/tests/mobility.py new file mode 100644 index 00000000..3d0b5816 --- /dev/null +++ b/tests/mobility.py @@ -0,0 +1,397 @@ +import mobility_plotting as mp +import pandas as pd +from pyomo import environ as po +from pyomo.core.base.block import ScalarBlock + +from oemof import solph +from oemof.tabular.facades import Excess, Load, Shortage, Volatile +from oemof.tabular.facades.experimental.battery_electric_vehicle import Bev +from oemof.tabular.postprocessing import calculations + + +def double_with_offset(lst): + result = [] + for i in range(len(lst) - 1): + result.append((lst[i], lst[i + 1])) + return result + + +def var2str(var): + return "_".join( + [i.label if not isinstance(i, int) else str(i) for i in var] + ) + + +def equate_bev_invest(model, energysystem): + for node in energysystem.nodes: + if isinstance(node, Bev): + invest_vars = list( + set( + inv + for inv in model.InvestmentFlowBlock.invest + for edge in inv[:2] + if node.facade_label in edge.label + ) + ) + # TODO take care of multi period, only chain within period + for var1, var2 in double_with_offset(invest_vars): + solph.constraints.equate_variables( + model=model, + var1=model.InvestmentFlowBlock.invest[var1], + var2=model.InvestmentFlowBlock.invest[var2], + name=f"equal_invest({var2str(var1)}__{var2str(var2)})", + ) + + +# def relate_bev_invest(model, energysystem): +# invest_vars = {} +# for node in energysystem.nodes: +# if isinstance(node, Bev): +# invest_vars.update({ +# node.facade_label: inv for inv in model.InvestmentFlowBlock.invest +# if f"{node.facade_label}-storage" in inv[1].label +# for edge in inv[:1] +# if node.electricity_bus.label in edge.label +# # and f"{node.facade_label}-storage" in edge.label) +# } ) +# factors = dict.fromkeys(invest_vars.keys(), 0.3) +def relate_bev_invest(model, energysystem): + invest_vars = [] + for node in energysystem.nodes: + if isinstance(node, Bev): + invest_vars.extend( + [ + inv + for inv in model.InvestmentFlowBlock.invest + if f"{node.facade_label}-storage" in inv[1].label + for edge in inv[:1] + if node.electricity_bus.label in edge.label + # and f"{node.facade_label}-storage" in edge.label) + ] + ) + market_share = dict(zip(invest_vars, [0.3, 0.1, 0.6])) + + model.TotalBevInvest = po.Var(model.PERIODS, within=po.NonNegativeReals) + # m.PERIODS, + + def total_bev_invest_rule(m): + return m.TotalBevInvest[p] == sum( + m.InvestmentFlowBlock.invest[inv_var] for inv_var in invest_vars + ) + + for p in model.PERIODS: + name = f"total_bev_invest-({p})" + setattr(model, name, po.Constraint(rule=total_bev_invest_rule)) + + # model.total_investment_constraint = po.Constraint( + # expr=sum(model.InvestmentFlowBlock.invest[inv_var] for inv_var in + # invest_vars) == model.TotalBevInvest[period] + # ) + + # model.investment_constraints = po.ConstraintList() + # for inv_var in invest_vars: + # model.investment_constraints.add( + # model.InvestmentFlowBlock.invest[inv_var] == factors[ + # inv_var] * model.total_bev_invest + # + # ) + + # Define the rule to set the investment constraints + def investment_constraints_rule(m): + return ( + m.InvestmentFlowBlock.invest[inv_var] + == market_share[inv_var] * m.TotalBevInvest[p] + ) + + for p in model.PERIODS: + for inv_var in invest_vars: + name = f"bev_invest_share({var2str(inv_var)})" + setattr( + model, name, po.Constraint(rule=investment_constraints_rule) + ) + + # model.BevBlock = OrderedScalarSet( + # ordered_constraints=[model.TotalBevInvest, model.constraint2, + # model.constraint3]) + + # setattr(model, name, po.Constraint(rule=relate_variables_rule)) + # # for var1, var2 in double_with_offset(invest_vars): + # relate_variables( + # model=model, + # var1=model.InvestmentFlowBlock.invest[invest_vars[0]], + # var2=model.InvestmentFlowBlock.invest[invest_vars[1]], + # var3=model.InvestmentFlowBlock.invest[invest_vars[2]], + # factor1=0.2, + # factor2=0.3, + # factor3=0.5, + # name="test" + # # name=f"fixed_bev_share({var2str(var1)}__{var2str(var2)})", + # ) + + +# def relate_variables(model, vars, factors, name=None +# ): +# +# if name is None: +# name = "_".join(["relate", str(var1), str(var2), str(var3)]) +# +# def relate_variables_rule(m): +# return var1 * factor1 + var2 * factor2 + var3 * factor3 == v +# +# setattr(model, name, po.Constraint(rule=relate_variables_rule)) + + +def relate_variables( + model, var1, var2, var3, factor1, factor2, factor3, name=None +): + if name is None: + name = "_".join(["relate", str(var1), str(var2), str(var3)]) + + def relate_variables_rule(m): + return ( + var1 * factor1 + var2 * factor2 + var3 * factor3 + == factor1 + factor2 + factor3 + ) + + setattr(model, name, po.Constraint(rule=relate_variables_rule)) + + +class BevInvestmentBlock(ScalarBlock): + CONSTRAINT_GROUP = True + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def _create(self, group=None): + if group is None: + return None + + m = self.parent_block() + + # Set of DSM Components + self.bev = po.Set(initialize=[n for n in group]) + + self.TotalBevInvest = po.Var(m.PERIODS, within=po.NonNegativeReals) + + def relate_bev_invest(self): + invest_vars = [] + for node in self.es.nodes: + if isinstance(node, Bev): + invest_vars.extend( + [ + inv + for inv in model.InvestmentFlowBlock.invest + if f"{node.facade_label}-storage" in inv[1].label + for edge in inv[:1] + if node.electricity_bus.label in edge.label + # and f"{node.facade_label}-storage" in edge.label) + ] + ) + market_share = dict(zip(invest_vars, [0.3, 0.1, 0.6])) + return invest_vars, market_share + + # m.PERIODS, + + def total_bev_invest_rule(m): + return m.TotalBevInvest[p] == sum( + m.InvestmentFlowBlock.invest[inv_var] + for inv_var in invest_vars + ) + + invest_vars, market_share = relate_bev_invest() + for p in model.PERIODS: + name = f"total_bev_invest-({p})" + setattr( + model, name, po.Constraint(group, rule=total_bev_invest_rule) + ) + + +if __name__ == "__main__": + date_time_index = pd.date_range("1/1/2012", periods=3, freq="H") + + energysystem = solph.EnergySystem( + groupings=solph.GROUPINGS, + timeindex=date_time_index, + infer_last_interval=True, + ) + + 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=[1, 0, 1], + ) + energysystem.add(volatile) + + load = Load( + label="load", + type="load", + carrier="electricity", + bus=el_bus, + amount=100, + profile=[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=100, + marginal_cost=1e6, + ) + energysystem.add(shortage) + + pkm_demand = Load( + label="pkm_demand", + type="Load", + carrier="pkm", + bus=indiv_mob, + amount=200, # PKM + profile=[0, 1, 0], # drive consumption + ) + + energysystem.add(pkm_demand) + + bev_v2g = Bev( + type="bev", + label="BEV-V2G", + electricity_bus=el_bus, + storage_capacity=150, + capacity=50, + drive_power=150, # nominal value sink + # drive_consumption=[1, 1, 1], # relative value sink + max_charging_power=0, # existing + availability=[1, 1, 1], + efficiency_charging=1, + v2g=True, + # 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], + transport_commodity_bus=indiv_mob, + expandable=True, + bev_capacity_cost=2, + invest_c_rate=0.5, + # marginal_cost=3, + pkm_conversion_rate=0.7, + ) + energysystem.add(bev_v2g) + + bev_flex = Bev( + type="bev", + label="BEV-FLEX", + electricity_bus=el_bus, + storage_capacity=200, + drive_power=100, + # drive_consumption=[0, 1, 0], + # max_charging_power=200, + availability=[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], + transport_commodity_bus=indiv_mob, + expandable=True, + bev_capacity_cost=2, + invest_c_rate=1, + # marginal_cost=3, + pkm_conversion_rate=0.7, + ) + energysystem.add(bev_flex) + + bev_fix = Bev( + type="bev", + label="BEV-FIX", + electricity_bus=el_bus, + storage_capacity=200, + drive_power=100, + # drive_consumption=[0, 1, 0], + # max_charging_power=200, + availability=[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], + transport_commodity_bus=indiv_mob, + expandable=True, + bev_capacity_cost=2, + invest_c_rate=60 / 20, # Capacity/Power + # marginal_cost=3, + pkm_conversion_rate=0.7, + input_parameters={"fix": [0, 0, 0]}, # fixed relative charging profile + ) + energysystem.add(bev_fix) + + mp.draw_graph(energysystem) + + model = solph.Model( + energysystem, + timeindex=energysystem.timeindex, + ) + + filepath = "./mobility.lp" + model.write(filepath, io_options={"symbolic_solver_labels": True}) + + # extra constraints + equate_bev_invest(model, energysystem) + # + # relate_bev_invest(model, energysystem) + BevInvestmentBlock._create(group="bev") + + 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}) + + 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) From 1cbafcc143ffa9bc0ce50fffa41e71c9fdcde8f7 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 24 Oct 2023 18:59:44 +0200 Subject: [PATCH 16/88] Add graph plotting helpers --- tests/mobility_plotting.py | 143 +++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 tests/mobility_plotting.py diff --git a/tests/mobility_plotting.py b/tests/mobility_plotting.py new file mode 100644 index 00000000..d65f7dd4 --- /dev/null +++ b/tests/mobility_plotting.py @@ -0,0 +1,143 @@ +import matplotlib.pyplot as plt +import networkx as nx + + +def plot_bev_results(energysystem, facade_label): + if not isinstance(facade_label, list): + facade_label = [facade_label] + + # energysystem.results[list(energysystem.results)[0]] + fig1, ax1 = plt.subplots(figsize=(10, 8)) + fig2, ax2 = plt.subplots(figsize=(10, 8)) + for c, r in energysystem.results.items(): + if not r["sequences"].empty: + if isinstance(c, tuple): + try: + name = str([i.label for i in c]) + except AttributeError: + name = "None" + else: + name = c + try: + for key in facade_label: + if key in c[0].label or key in c[1].label: + ax = ax1 + else: + ax = ax2 + column = [ + i for i in r["sequences"].columns if ("flow" in i) + ] + # r["sequences"]["flow"].plot(ax=ax, label=c) + r["sequences"][column].plot(ax=ax, label=c) + except: + print(c) + pass + + ax1.legend(title="Legend", bbox_to_anchor=(0.7, 1), loc="upper left") + ax1.set_title("Controlled") + # plt.tight_layout() + fig1.show() + + ax2.legend(title="Legend", bbox_to_anchor=(0.7, 1), loc="upper left") + ax2.set_title("Other") + plt.tight_layout() + fig2.show() + + fig, ax = plt.subplots(figsize=(10, 8)) + energysystem.new_results["shortage: el-bus"]["sequences"].plot( + ax=ax, label="shortage" + ) + energysystem.new_results["el-bus: excess"]["sequences"].plot( + ax=ax, label="excess" + ) + ax.set_title("Excess and shortage") + ax.legend(title="Legend", bbox_to_anchor=(0.7, 1), loc="upper left") + plt.tight_layout() + fig.show() + + for key in facade_label: + energysystem.new_results[f"{key}-storage: {key}-bus"][ + "sequences" + ].plot(ax=ax, label="storage: internal-bus") + ax.legend(title="Legend", bbox_to_anchor=(0.7, 1), loc="upper left") + ax.set_title("Storage") + plt.tight_layout() + fig.show() + + fig, ax = plt.subplots(figsize=(10, 8)) + for key in facade_label: + if "V2G" in key: + energysystem.new_results[f"{key}-v2g: el-bus"]["sequences"].plot( + ax=ax, label=f"{key}-v2g" + ) + ax.legend(title="Legend", bbox_to_anchor=(0.7, 1), loc="upper left") + ax.set_title("V2G to el-bus") + plt.tight_layout() + fig.show() + + fig, ax = plt.subplots(figsize=(10, 8)) + for key in facade_label: + energysystem.new_results[f"{key}-2pkm: pkm-bus"]["sequences"].plot( + ax=ax, label=f"{key}-2pkm-bus" + ) + ax.legend(title="Legend", bbox_to_anchor=(0.7, 1), loc="upper left") + ax.set_title("Flows 2 pkm-bus") + plt.tight_layout() + fig.show() + + +def draw_graph(energysystem): + # Draw the graph + + from oemof.network.graph import create_nx_graph + + G = create_nx_graph(energysystem) + + # Specify layout and draw the graph + pos = nx.drawing.nx_agraph.graphviz_layout( + G, prog="neato", args="-Gepsilon=0.0001" + ) + + fig, ax = plt.subplots(figsize=(10, 8)) + node_colors = list() + for i in list(G.nodes()): + if "BEV-V2G" in i: + node_colors.append("firebrick") + elif "BEV-FLEX" in i: + node_colors.append("lightblue") + elif "BEV-FIX" 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") + + nx.draw( + G, + pos, + # **options, + with_labels=True, + node_size=3000, + # node_color='lightblue', + font_size=10, + font_weight="bold", + node_color=node_colors, + # node_color=["red", "blue", "green", "yellow", "orange"], + ) + labels = nx.get_edge_attributes(G, "weight") + nx.draw_networkx_edge_labels(G, pos=pos, edge_labels=labels) + + # Customize the plot as needed + ax.set_title("OEMOF Energy System Graph") + + # Show the plot + plt.show() From 863b69c86e5e791d82456204007cf88ac0980796 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 30 Oct 2023 17:34:09 +0100 Subject: [PATCH 17/88] Add bev to typemap --- src/oemof/tabular/facades/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/oemof/tabular/facades/__init__.py b/src/oemof/tabular/facades/__init__.py index ff64bb39..0b4309a0 100644 --- a/src/oemof/tabular/facades/__init__.py +++ b/src/oemof/tabular/facades/__init__.py @@ -7,6 +7,7 @@ from .conversion import Conversion from .dispatchable import Dispatchable from .excess import Excess +from .experimental.battery_electric_vehicle import Bev from .extraction_turbine import ExtractionTurbine from .generator import Generator from .heatpump import HeatPump @@ -35,6 +36,7 @@ "shortage": Shortage, "storage": Storage, "volatile": Volatile, + "bev": Bev, } TECH_COLOR_MAP = { From 2d276e5be4b8f5f06aa417a7c02eca688cd09212 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 30 Oct 2023 17:59:40 +0100 Subject: [PATCH 18/88] Rework bev component --- .../experimental/battery_electric_vehicle.py | 51 +++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index d682cd5b..dca97729 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -9,8 +9,6 @@ from oemof.tabular._facade import Facade, dataclass_facade -# from oemof.tabular.facades import Load - @dataclass_facade class Bev(GenericStorage, Facade): @@ -126,6 +124,8 @@ class Bev(GenericStorage, Facade): storage_capacity: int = 0 + initial_storage_capacity: float = 0 + drive_consumption: Sequence = None v2g: bool = False @@ -138,12 +138,16 @@ class Bev(GenericStorage, Facade): expandable: bool = False + lifetime: int = None + bev_capacity_cost: Sequence[float] = None invest_c_rate: Sequence[float] = None marginal_cost: float = 0 + balanced: bool = False + pkm_conversion_rate: float = 1 efficiency_mob_g2v: float = 1 @@ -157,21 +161,22 @@ class Bev(GenericStorage, Facade): efficiency_sto_out: float = 1 def build_solph_components(self): + # 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) self.max_charging_power = solph_sequence(self.max_charging_power) + # TODO: check if this is correct self.nominal_storage_capacity = self.storage_capacity # self.nominal_storage_capacity = self._nominal_value( # self.storage_capacity) - # self.inflow_conversion_factor = solph_sequence(1) - # self.outflow_conversion_factor = solph_sequence(1) - # self.balanced = self.balanced # TODO not in multi-period - self.balanced = False - self.initial_storage_level = 0 + self.balanced = self.balanced # TODO to be false in multi-period + + # create internal bus internal_bus = Bus(label=self.facade_label + "-bus") self.bus = internal_bus subnodes = [internal_bus] @@ -204,14 +209,6 @@ def build_solph_components(self): # ##### Charging Converter ##### - self.inflow_conversion_factor = solph_sequence( - self.efficiency_mob_g2v * self.efficiency_sto_in - ) - - self.outflow_conversion_factor = solph_sequence( - self.efficiency_sto_out - ) - # grid_to_vehicle = Converter( # label=self.facade_label + "-g2v", # inputs={ @@ -235,7 +232,9 @@ def build_solph_components(self): # Drive consumption if self.transport_commodity_bus: # ##### PKM Converter ##### - transport_commodity = Converter( + # converts energy to e.g. pkm + # connects it to a special mobility bus + pkm_converter = Converter( label=self.facade_label + "-2pkm", inputs={ internal_bus: Flow( @@ -255,10 +254,11 @@ def build_solph_components(self): conversion_factors={self.bus: self.pkm_conversion_rate}, # TODO maybe add battery efficiency + charger efficiency ) - subnodes.append(transport_commodity) + subnodes.append(pkm_converter) else: # ##### Consumption Sink ##### + # fixed timeseries for this bev unit only driving_consumption = Sink( label=self.facade_label + "-consumption", inputs={ @@ -271,11 +271,10 @@ def build_solph_components(self): ) subnodes.append(driving_consumption) - # ##### Storage ##### - + # ##### Storage ######## if self.expandable: self.capacity_cost = self.bev_capacity_cost - self.storage_capacity_cost = 1 + self.storage_capacity_cost = 0 # self.investment = self._investment(bev=False) self.invest_relation_input_output = 1 # charge/discharge equal # invest_c_rate = Energy/Power = h @@ -294,7 +293,8 @@ def build_solph_components(self): ).format(attr, self.label) ) - # set capacity costs at one of the flows + # ##### Grid2Vehicle ##### + # containts the whole investmentcosts for bev flow_in = Flow( # max=self.availability, investment=Investment( @@ -335,6 +335,15 @@ def build_solph_components(self): **self.output_parameters, ) + # TODO check conversion factors + self.inflow_conversion_factor = solph_sequence( + self.efficiency_mob_g2v * self.efficiency_sto_in + ) + + self.outflow_conversion_factor = solph_sequence( + self.efficiency_sto_out + ) + self.inputs.update({self.electricity_bus: flow_in}) self.outputs.update({self.bus: flow_out}) From 62b734ea8b066068342a1d424166dff4d2ac05a7 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 30 Oct 2023 18:02:42 +0100 Subject: [PATCH 19/88] Add helper functions --- src/oemof/tabular/constraint_facades.py | 33 ++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/oemof/tabular/constraint_facades.py b/src/oemof/tabular/constraint_facades.py index 6f2f88fc..aea5982e 100644 --- a/src/oemof/tabular/constraint_facades.py +++ b/src/oemof/tabular/constraint_facades.py @@ -2,7 +2,38 @@ from dataclasses import dataclass from oemof.solph.constraints.integral_limit import generic_integral_limit -from oemof.solph.constraints.equate_variables import equate_variables +from pyomo.environ import Constraint + +from oemof.tabular.facades import Bev + + +def var2str(var): + return "_".join( + [i.label if not isinstance(i, int) else str(i) for i in var] + ) + + +def get_bev_label(var): + return var[1].label.split("-storage")[0] + + +def get_period(model, year, constraint_type=None): + if model.es.periods: + years = [period.year.min() for period in model.es.periods] + for period_index, period_year in enumerate(years): + if period_year == year: + return period_index + raise ValueError( + f"'{constraint_type}' constraint facade:\n" + f"Year {year} is not in model.PERIODS." + ) + elif year == model.es.timeindex.year.min(): + return 0 + else: + raise ValueError( + f"'{constraint_type}' constraint facade:\n" + f"Year {year} is not in model.timeindex." + ) class ConstraintFacade(abc.ABC): From 7019712d89cc3ca059bee75165d36b21aa467941 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 30 Oct 2023 18:05:03 +0100 Subject: [PATCH 20/88] Add equal invest and mob share constraint --- src/oemof/tabular/constraint_facades.py | 175 +++++++++++++++++++----- 1 file changed, 143 insertions(+), 32 deletions(-) diff --git a/src/oemof/tabular/constraint_facades.py b/src/oemof/tabular/constraint_facades.py index aea5982e..c11ffb53 100644 --- a/src/oemof/tabular/constraint_facades.py +++ b/src/oemof/tabular/constraint_facades.py @@ -71,35 +71,146 @@ def build_constraint(self, model): ) -CONSTRAINT_TYPE_MAP = {"generic_integral_limit": GenericIntegralLimit} - - -# @dataclass -# class JointExtension(ConstraintFacade): -# name: str -# type: str -# limit: float -# keyword: str = "joint_extension" -# -# -# def build_constraint(self, model): -# # components = {} -# # for i in model.NODES: -# # if hasattr(model.NODES[i], self.keyword): -# # components[(i, o)] = model.NODES[i] -# components = {i: model.NODES[i] for i in model.NODES if -# hasattr(model.NODES[i], self.keyword)} -# -# if not components: -# raise Warning(f"No components with keyword {self.keyword}") -# else: -# print( -# "These components will be extended jointly: " -# f"{components.keys()}" -# ) -# -# # add constraint to the model -# equate_variables( -# model, -# var1=model.InvestmentFlowBlock.invest[], -# var2=) +@dataclass +class BevShareMob(ConstraintFacade): + name: str + type: str + year: int + label: str + share_mob_flex_G2V: (int, float) + share_mob_flex_V2G: (int, float) + share_mob_inflex: (int, float) + + @staticmethod + def map_share2vars(model, share_mob, period): + invest_vars = [] + for node in model.es.nodes: + if isinstance(node, Bev): + invest_vars.extend( + [ + inv + for inv in model.InvestmentFlowBlock.invest + if node.electricity_bus.label == inv[0].label + and f"{node.facade_label}-storage" in inv[1].label + and period == inv[2] + ] + ) + + share_mob = { + inv_var: value + for key, value in share_mob.items() + for inv_var in invest_vars + if key in inv_var[1].label + } + return invest_vars, share_mob + + @staticmethod + def convert_share(share): + if 0 <= share <= 1: + return share + elif 0 <= share <= 100: + return share / 100 + else: + raise ValueError(f"Mob share: {share} not in [0,1] or [0,100]") + + def build_constraint(self, model): + period = get_period(model, self.year, self.__class__.__name__) + + if period > len(model.PERIODS): + raise ValueError( + f"Period {period} not in model.PERIODS {model.PERIODS}" + ) + + share_mob = { + f"{self.label}-G2V-storage": self.convert_share( + self.share_mob_flex_G2V + ), + f"{self.label}-V2G-storage": self.convert_share( + self.share_mob_flex_V2G + ), + f"{self.label}-inflex-storage": self.convert_share( + self.share_mob_inflex + ), + } + + invest_vars, share_mob = self.map_share2vars( + model=model, share_mob=share_mob, period=period + ) + + def investment_constraints_rule(InvestmentFlowBlock): + return InvestmentFlowBlock.invest[inv_var] == share_mob[ + inv_var + ] * sum(InvestmentFlowBlock.invest[iv] for iv in invest_vars) + + for inv_var in invest_vars: + name = f"mob_share_{get_bev_label(inv_var)}_{period}" + setattr( + model.InvestmentFlowBlock, + name, + Constraint(rule=investment_constraints_rule), + ) + + +@dataclass +class BevEqualInvest(ConstraintFacade): + name: str + type: str + year: int + + @staticmethod + def double_with_offset(lst): + result = [] + for i in range(len(lst) - 1): + result.append((lst[i], lst[i + 1])) + return result + + @staticmethod + def get_bev_invest_vars(model, period): + all_invest_vars = {} + for node in model.es.nodes: + if isinstance(node, Bev): + invest_vars_bev = list( + set( + inv + for inv in model.InvestmentFlowBlock.invest + if inv[2] == period + for edge in inv[:2] + if node.facade_label in edge.label + ) + ) + all_invest_vars[node.facade_label] = invest_vars_bev + return all_invest_vars + + def build_constraint(self, model): + # TODO add checks + period = get_period(model, self.year, self.__class__.__name__) + if period > len(model.PERIODS): + raise ValueError( + f"Period {period} not in model.PERIODS {model.PERIODS}" + ) + + invest_vars = self.get_bev_invest_vars(model, period) + + def equate_variables_rule(InvestmentFlowBlock): + return ( + InvestmentFlowBlock.invest[var1] + == InvestmentFlowBlock.invest[var2] + ) + + for bev_label, invest_vars in invest_vars.items(): + for i, (var1, var2) in enumerate( + self.double_with_offset(invest_vars) + ): + name = f"{bev_label}_equal_invest_{i}_({period})" + setattr( + model.InvestmentFlowBlock, + name, + Constraint(rule=equate_variables_rule), + ) + + +CONSTRAINT_TYPE_MAP = { + "generic_integral_limit": GenericIntegralLimit, + "bev_equal_invest": BevEqualInvest, + "bev_share_mob": BevShareMob, +} From dd5c5720dbbe2e6a8612da3199dc373634e46faa Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 30 Oct 2023 18:05:45 +0100 Subject: [PATCH 21/88] Add draft for bev constraint test --- tests/test_constraints.py | 136 ++++++++++++++++++++++++++++++++------ 1 file changed, 117 insertions(+), 19 deletions(-) diff --git a/tests/test_constraints.py b/tests/test_constraints.py index 7ef575fd..3f4bed60 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -7,9 +7,14 @@ from oemof.solph import helpers from oemof import solph -from oemof.tabular.constraint_facades import GenericIntegralLimit +from oemof.tabular.constraint_facades import ( + BevEqualInvest, + BevShareMob, + GenericIntegralLimit, +) from oemof.tabular.facades import ( BackpressureTurbine, + Bev, Commodity, Conversion, Dispatchable, @@ -21,7 +26,6 @@ Storage, Volatile, ) -from oemof.tabular.facades.experimental.battery_electric_vehicle import Bev def chop_trailing_whitespace(lines): @@ -491,26 +495,120 @@ def test_emission_constraint(self): emission_constraint.build_constraint(model) - self.compare_to_reference_lp("emission_constraint.lp", my_om=model) + def test_bev_trio(self): + el_bus = solph.Bus("el-bus") + el_bus.type = "bus" + self.energysystem.add(el_bus) + + indiv_mob = solph.Bus("pkm-bus") + indiv_mob.type = "bus" + self.energysystem.add(indiv_mob) + + pkm_demand = Load( + label="pkm_demand", + type="Load", + carrier="pkm", + bus=indiv_mob, + amount=200, # PKM + profile=[0, 1, 0], # drive consumption + ) - def test_bev(self): - bus = solph.Bus("bus") + self.energysystem.add(pkm_demand) - bev = Bev( - label="bev", - bus=bus, - storage_capacity=1000, - drive_power=50, - drive_consumption=[0.8, 0.7, 0.6], - max_charging_power=10, - availability=[0.8, 0.7, 0.6], - efficiency_charging=0.93, + bev_v2g = Bev( + type="bev", + label="BEV-V2G", + electricity_bus=el_bus, + storage_capacity=150, + capacity=50, + drive_power=150, # nominal value sink + # drive_consumption=[1, 1, 1], # relative value sink + max_charging_power=0, # existing + availability=[1, 1, 1], + efficiency_charging=1, v2g=True, - 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], + # 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], + transport_commodity_bus=indiv_mob, + expandable=True, + bev_capacity_cost=2, + invest_c_rate=60 / 20, + # marginal_cost=3, + pkm_conversion_rate=0.7, + lifetime=10, + ) + + self.energysystem.add(bev_v2g) + + bev_flex = Bev( + type="bev", + label="BEV-inflex", + electricity_bus=el_bus, + storage_capacity=200, + drive_power=100, + # drive_consumption=[0, 1, 0], + # max_charging_power=200, + availability=[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], + transport_commodity_bus=indiv_mob, + expandable=True, + bev_capacity_cost=2, + invest_c_rate=60 / 20, + # marginal_cost=3, + pkm_conversion_rate=0.7, + lifetime=10, + ) + self.energysystem.add(bev_flex) + + bev_fix = Bev( + type="bev", + label="BEV-G2V", + electricity_bus=el_bus, + storage_capacity=200, + drive_power=100, + # drive_consumption=[0, 1, 0], + # max_charging_power=200, + availability=[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], + transport_commodity_bus=indiv_mob, + expandable=True, + bev_capacity_cost=2, + invest_c_rate=60 / 20, # Capacity/Power + # marginal_cost=3, + pkm_conversion_rate=0.7, + input_parameters={"fix": [0, 0, 0]}, + # fixed relative charging profile + lifetime=10, + ) + self.energysystem.add(bev_fix) + + model = solph.Model(self.energysystem) + + year = self.date_time_index.year.min() + mob_share_constraint = BevShareMob( + name=f"mob_share_{year}", + type=None, + year=year, + label="BEV", + share_mob_flex_G2V=10, + share_mob_flex_V2G=20, + share_mob_inflex=70, + ) + mob_share_constraint.build_constraint(model) + + invest_constraint = BevEqualInvest( + name=f"bev_total_invest_{year}", + type=None, + year=year, ) - self.energysystem.add(bus, bev) + invest_constraint.build_constraint(model) - self.compare_to_reference_lp("bev.lp") + self.compare_to_reference_lp("bev_trio_constraint.lp", my_om=model) From a4aeea6b190e12026e17b612b7dcd4d7984c517c Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 30 Oct 2023 18:06:07 +0100 Subject: [PATCH 22/88] Add draft for bev multi-period constraint test --- tests/test_multi_period_constraints.py | 130 ++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/tests/test_multi_period_constraints.py b/tests/test_multi_period_constraints.py index 50f07b55..baa0d59b 100644 --- a/tests/test_multi_period_constraints.py +++ b/tests/test_multi_period_constraints.py @@ -7,9 +7,14 @@ from oemof.solph import buses, helpers from oemof import solph -from oemof.tabular.constraint_facades import GenericIntegralLimit +from oemof.tabular.constraint_facades import ( + BevEqualInvest, + BevShareMob, + GenericIntegralLimit, +) from oemof.tabular.facades import ( BackpressureTurbine, + Bev, Commodity, Conversion, Dispatchable, @@ -559,3 +564,126 @@ def test_emission_constraint(self): self.compare_to_reference_lp( "emission_constraint_multi_period.lp", my_om=model ) + + def test_bev_trio(self): + periods = len(self.periods) + + el_bus = solph.Bus("el-bus") + el_bus.type = "bus" + self.energysystem.add(el_bus) + + indiv_mob = solph.Bus("pkm-bus") + indiv_mob.type = "bus" + self.energysystem.add(indiv_mob) + + pkm_demand = Load( + label="pkm_demand", + type="Load", + carrier="pkm", + bus=indiv_mob, + amount=200, # PKM + profile=periods * [0, 1, 0], # drive consumption + ) + + self.energysystem.add(pkm_demand) + + bev_v2g = Bev( + type="bev", + label="BEV-V2G", + electricity_bus=el_bus, + storage_capacity=150, + capacity=50, + drive_power=150, # nominal value sink + # drive_consumption=[1, 1, 1], # relative value sink + max_charging_power=0, # existing + availability=periods * [1, 1, 1], + efficiency_charging=1, + v2g=True, + # 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], + transport_commodity_bus=indiv_mob, + expandable=True, + bev_capacity_cost=2, + invest_c_rate=60 / 20, + # marginal_cost=3, + pkm_conversion_rate=0.7, + lifetime=10, + ) + + self.energysystem.add(bev_v2g) + + bev_flex = Bev( + type="bev", + label="BEV-inflex", + electricity_bus=el_bus, + storage_capacity=200, + drive_power=100, + # drive_consumption=[0, 1, 0], + # max_charging_power=200, + availability=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], + transport_commodity_bus=indiv_mob, + expandable=True, + bev_capacity_cost=2, + invest_c_rate=60 / 20, + # marginal_cost=3, + pkm_conversion_rate=0.7, + lifetime=10, + ) + self.energysystem.add(bev_flex) + + bev_fix = Bev( + type="bev", + label="BEV-G2V", + electricity_bus=el_bus, + storage_capacity=200, + drive_power=100, + # drive_consumption=[0, 1, 0], + # max_charging_power=200, + availability=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], + transport_commodity_bus=indiv_mob, + expandable=True, + bev_capacity_cost=2, + invest_c_rate=60 / 20, # Capacity/Power + # marginal_cost=3, + pkm_conversion_rate=0.7, + input_parameters={"fix": periods * [0, 0, 0]}, + # fixed relative charging profile + lifetime=10, + ) + self.energysystem.add(bev_fix) + + model = solph.Model(self.energysystem) + + for p in self.periods: + year = p.year.min() + mob_share_constraint = BevShareMob( + name=f"mob_share_{year}", + type=None, + year=year, + label="BEV", + share_mob_flex_G2V=10, + share_mob_flex_V2G=20, + share_mob_inflex=70, + ) + mob_share_constraint.build_constraint(model) + + invest_constraint = BevEqualInvest( + name=f"bev_total_invest_{year}", + type=None, + year=year, + ) + + invest_constraint.build_constraint(model) + + self.compare_to_reference_lp( + "bev_trio_constraint_multi_period.lp", my_om=model + ) From bf4b260a5a513115ffec1db5b45aa5f14813c24b Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 30 Oct 2023 18:07:53 +0100 Subject: [PATCH 23/88] Remove constraints from mobility example --- tests/mobility.py | 268 +++++++++------------------------------------- 1 file changed, 48 insertions(+), 220 deletions(-) diff --git a/tests/mobility.py b/tests/mobility.py index 3d0b5816..c2c8690e 100644 --- a/tests/mobility.py +++ b/tests/mobility.py @@ -1,218 +1,36 @@ +import os + import mobility_plotting as mp import pandas as pd -from pyomo import environ as po -from pyomo.core.base.block import ScalarBlock 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 Bev from oemof.tabular.postprocessing import calculations - -def double_with_offset(lst): - result = [] - for i in range(len(lst) - 1): - result.append((lst[i], lst[i + 1])) - return result - - -def var2str(var): - return "_".join( - [i.label if not isinstance(i, int) else str(i) for i in var] - ) - - -def equate_bev_invest(model, energysystem): - for node in energysystem.nodes: - if isinstance(node, Bev): - invest_vars = list( - set( - inv - for inv in model.InvestmentFlowBlock.invest - for edge in inv[:2] - if node.facade_label in edge.label - ) - ) - # TODO take care of multi period, only chain within period - for var1, var2 in double_with_offset(invest_vars): - solph.constraints.equate_variables( - model=model, - var1=model.InvestmentFlowBlock.invest[var1], - var2=model.InvestmentFlowBlock.invest[var2], - name=f"equal_invest({var2str(var1)}__{var2str(var2)})", - ) - - -# def relate_bev_invest(model, energysystem): -# invest_vars = {} -# for node in energysystem.nodes: -# if isinstance(node, Bev): -# invest_vars.update({ -# node.facade_label: inv for inv in model.InvestmentFlowBlock.invest -# if f"{node.facade_label}-storage" in inv[1].label -# for edge in inv[:1] -# if node.electricity_bus.label in edge.label -# # and f"{node.facade_label}-storage" in edge.label) -# } ) -# factors = dict.fromkeys(invest_vars.keys(), 0.3) -def relate_bev_invest(model, energysystem): - invest_vars = [] - for node in energysystem.nodes: - if isinstance(node, Bev): - invest_vars.extend( - [ - inv - for inv in model.InvestmentFlowBlock.invest - if f"{node.facade_label}-storage" in inv[1].label - for edge in inv[:1] - if node.electricity_bus.label in edge.label - # and f"{node.facade_label}-storage" in edge.label) - ] - ) - market_share = dict(zip(invest_vars, [0.3, 0.1, 0.6])) - - model.TotalBevInvest = po.Var(model.PERIODS, within=po.NonNegativeReals) - # m.PERIODS, - - def total_bev_invest_rule(m): - return m.TotalBevInvest[p] == sum( - m.InvestmentFlowBlock.invest[inv_var] for inv_var in invest_vars - ) - - for p in model.PERIODS: - name = f"total_bev_invest-({p})" - setattr(model, name, po.Constraint(rule=total_bev_invest_rule)) - - # model.total_investment_constraint = po.Constraint( - # expr=sum(model.InvestmentFlowBlock.invest[inv_var] for inv_var in - # invest_vars) == model.TotalBevInvest[period] +if __name__ == "__main__": + # date_time_index = pd.date_range("1/1/2020", periods=3, freq="H") + # energysystem = solph.EnergySystem( + # timeindex=date_time_index, + # infer_last_interval=True, # ) - # model.investment_constraints = po.ConstraintList() - # for inv_var in invest_vars: - # model.investment_constraints.add( - # model.InvestmentFlowBlock.invest[inv_var] == factors[ - # inv_var] * model.total_bev_invest - # - # ) - - # Define the rule to set the investment constraints - def investment_constraints_rule(m): - return ( - m.InvestmentFlowBlock.invest[inv_var] - == market_share[inv_var] * m.TotalBevInvest[p] - ) - - for p in model.PERIODS: - for inv_var in invest_vars: - name = f"bev_invest_share({var2str(inv_var)})" - setattr( - model, name, po.Constraint(rule=investment_constraints_rule) - ) - - # model.BevBlock = OrderedScalarSet( - # ordered_constraints=[model.TotalBevInvest, model.constraint2, - # model.constraint3]) - - # setattr(model, name, po.Constraint(rule=relate_variables_rule)) - # # for var1, var2 in double_with_offset(invest_vars): - # relate_variables( - # model=model, - # var1=model.InvestmentFlowBlock.invest[invest_vars[0]], - # var2=model.InvestmentFlowBlock.invest[invest_vars[1]], - # var3=model.InvestmentFlowBlock.invest[invest_vars[2]], - # factor1=0.2, - # factor2=0.3, - # factor3=0.5, - # name="test" - # # name=f"fixed_bev_share({var2str(var1)}__{var2str(var2)})", - # ) - - -# def relate_variables(model, vars, factors, name=None -# ): -# -# if name is None: -# name = "_".join(["relate", str(var1), str(var2), str(var3)]) -# -# def relate_variables_rule(m): -# return var1 * factor1 + var2 * factor2 + var3 * factor3 == v -# -# setattr(model, name, po.Constraint(rule=relate_variables_rule)) - - -def relate_variables( - model, var1, var2, var3, factor1, factor2, factor3, name=None -): - if name is None: - name = "_".join(["relate", str(var1), str(var2), str(var3)]) - - def relate_variables_rule(m): - return ( - var1 * factor1 + var2 * factor2 + var3 * factor3 - == factor1 + factor2 + factor3 - ) - - setattr(model, name, po.Constraint(rule=relate_variables_rule)) - - -class BevInvestmentBlock(ScalarBlock): - CONSTRAINT_GROUP = True - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def _create(self, group=None): - if group is None: - return None - - m = self.parent_block() - - # Set of DSM Components - self.bev = po.Set(initialize=[n for n in group]) - - self.TotalBevInvest = po.Var(m.PERIODS, within=po.NonNegativeReals) - - def relate_bev_invest(self): - invest_vars = [] - for node in self.es.nodes: - if isinstance(node, Bev): - invest_vars.extend( - [ - inv - for inv in model.InvestmentFlowBlock.invest - if f"{node.facade_label}-storage" in inv[1].label - for edge in inv[:1] - if node.electricity_bus.label in edge.label - # and f"{node.facade_label}-storage" in edge.label) - ] - ) - market_share = dict(zip(invest_vars, [0.3, 0.1, 0.6])) - return invest_vars, market_share - - # m.PERIODS, - - def total_bev_invest_rule(m): - return m.TotalBevInvest[p] == sum( - m.InvestmentFlowBlock.invest[inv_var] - for inv_var in invest_vars - ) - - invest_vars, market_share = relate_bev_invest() - for p in model.PERIODS: - name = f"total_bev_invest-({p})" - setattr( - model, name, po.Constraint(group, rule=total_bev_invest_rule) - ) - - -if __name__ == "__main__": - date_time_index = pd.date_range("1/1/2012", periods=3, freq="H") + # 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") + timeindex = pd.concat([t_idx_1_series, t_idx_2_series]).index + periods = [t_idx_1, t_idx_2] energysystem = solph.EnergySystem( - groupings=solph.GROUPINGS, - timeindex=date_time_index, - infer_last_interval=True, + timeindex=timeindex, + infer_last_interval=False, + timeincrement=[1] * len(timeindex), + periods=periods, ) el_bus = solph.Bus("el-bus") @@ -234,7 +52,8 @@ def total_bev_invest_rule(m): expandable=True, # expandable=False, # capacity_potential=1e8, - profile=[1, 0, 1], + profile=2 * [1, 0, 1], + lifetime=20, ) energysystem.add(volatile) @@ -244,7 +63,7 @@ def total_bev_invest_rule(m): carrier="electricity", bus=el_bus, amount=100, - profile=[1, 1, 1], + profile=2 * [1, 1, 1], ) energysystem.add(load) @@ -265,7 +84,7 @@ def total_bev_invest_rule(m): bus=el_bus, carrier="electricity", tech="shortage", - capacity=100, + capacity=1000, marginal_cost=1e6, ) energysystem.add(shortage) @@ -276,7 +95,7 @@ def total_bev_invest_rule(m): carrier="pkm", bus=indiv_mob, amount=200, # PKM - profile=[0, 1, 0], # drive consumption + profile=2 * [0, 1, 0], # drive consumption ) energysystem.add(pkm_demand) @@ -290,7 +109,7 @@ def total_bev_invest_rule(m): drive_power=150, # nominal value sink # drive_consumption=[1, 1, 1], # relative value sink max_charging_power=0, # existing - availability=[1, 1, 1], + availability=2 * [1, 1, 1], efficiency_charging=1, v2g=True, # loss_rate=0.01, @@ -299,21 +118,22 @@ def total_bev_invest_rule(m): transport_commodity_bus=indiv_mob, expandable=True, bev_capacity_cost=2, - invest_c_rate=0.5, + invest_c_rate=60 / 20, # marginal_cost=3, pkm_conversion_rate=0.7, + lifetime=10, ) energysystem.add(bev_v2g) bev_flex = Bev( type="bev", - label="BEV-FLEX", + label="BEV-inflex", electricity_bus=el_bus, storage_capacity=200, drive_power=100, # drive_consumption=[0, 1, 0], # max_charging_power=200, - availability=[1, 1, 1], + availability=2 * [1, 1, 1], v2g=False, # loss_rate=0.01, # min_storage_level=[0.1, 0.2, 0.15, 0.15], @@ -321,21 +141,22 @@ def total_bev_invest_rule(m): transport_commodity_bus=indiv_mob, expandable=True, bev_capacity_cost=2, - invest_c_rate=1, + invest_c_rate=60 / 20, # marginal_cost=3, pkm_conversion_rate=0.7, + lifetime=10, ) energysystem.add(bev_flex) bev_fix = Bev( type="bev", - label="BEV-FIX", + label="BEV-G2V", electricity_bus=el_bus, storage_capacity=200, drive_power=100, # drive_consumption=[0, 1, 0], # max_charging_power=200, - availability=[1, 1, 1], + availability=2 * [1, 1, 1], v2g=False, # loss_rate=0.01, # min_storage_level=[0.1, 0.2, 0.15, 0.15], @@ -346,7 +167,10 @@ def total_bev_invest_rule(m): invest_c_rate=60 / 20, # Capacity/Power # marginal_cost=3, pkm_conversion_rate=0.7, - input_parameters={"fix": [0, 0, 0]}, # fixed relative charging profile + input_parameters={ + "fix": 2 * [0, 0, 0] + }, # fixed relative charging profile + lifetime=10, ) energysystem.add(bev_fix) @@ -360,17 +184,21 @@ def total_bev_invest_rule(m): filepath = "./mobility.lp" model.write(filepath, io_options={"symbolic_solver_labels": True}) - # extra constraints - equate_bev_invest(model, energysystem) - # - # relate_bev_invest(model, energysystem) - BevInvestmentBlock._create(group="bev") + 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"] From f0b9c0cdd7ec31318b1daaf68f3caf76b8a05de0 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 30 Oct 2023 18:08:34 +0100 Subject: [PATCH 24/88] Change storage color in bev system plot --- tests/mobility_plotting.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/mobility_plotting.py b/tests/mobility_plotting.py index d65f7dd4..b93cf12b 100644 --- a/tests/mobility_plotting.py +++ b/tests/mobility_plotting.py @@ -101,13 +101,14 @@ def draw_graph(energysystem): fig, ax = plt.subplots(figsize=(10, 8)) node_colors = list() for i in list(G.nodes()): - if "BEV-V2G" in i: + if "storage" in i: + node_colors.append("royalblue") + elif "BEV-V2G" in i: node_colors.append("firebrick") - elif "BEV-FLEX" in i: + elif "BEV-G2V" in i: node_colors.append("lightblue") - elif "BEV-FIX" in i: + elif "BEV-inflex" in i: node_colors.append("darkviolet") - elif "excess" in i: node_colors.append("green") elif "shortage" in i: From ea2579a4cbbb1b591156c5114de04e482262fd34 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 31 Oct 2023 16:33:27 +0100 Subject: [PATCH 25/88] Remove charging converter Charging converter is not necessary and replaced by storage inflow. This way self discharge losses are taken into account. --- .../experimental/battery_electric_vehicle.py | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index dca97729..856259fc 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -207,28 +207,6 @@ def build_solph_components(self): ) subnodes.append(vehicle_to_grid) - # ##### Charging Converter ##### - - # grid_to_vehicle = Converter( - # label=self.facade_label + "-g2v", - # inputs={ - # self.electricity_bus: Flow( - # # **self.output_parameters - # ) - # }, - # outputs={internal_bus: Flow( - # nominal_value=self._nominal_value( - # value=self.max_charging_power), - # max=self.availability, - # variable_costs=None, - # investment=self._investment(bev=True), - # )}, - # conversion_factors={ - # self.electricity_bus: self.efficiency_charging}, - # # TODO maybe add battery efficiency + charger efficiency - # ) - # subnodes.append(grid_to_vehicle) - # Drive consumption if self.transport_commodity_bus: # ##### PKM Converter ##### From 1615fefaae094e6d3b514682661b893677d6a5f8 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 31 Oct 2023 16:35:57 +0100 Subject: [PATCH 26/88] Modify test-case --- tests/mobility.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/tests/mobility.py b/tests/mobility.py index c2c8690e..9b8bef84 100644 --- a/tests/mobility.py +++ b/tests/mobility.py @@ -12,24 +12,26 @@ 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") - timeindex = pd.concat([t_idx_1_series, t_idx_2_series]).index + 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=timeindex, + timeindex=date_time_index, infer_last_interval=False, - timeincrement=[1] * len(timeindex), + timeincrement=[1] * len(date_time_index), periods=periods, ) @@ -52,7 +54,7 @@ expandable=True, # expandable=False, # capacity_potential=1e8, - profile=2 * [1, 0, 1], + profile=len(periods) * [1, 0, 1], lifetime=20, ) energysystem.add(volatile) @@ -63,7 +65,7 @@ carrier="electricity", bus=el_bus, amount=100, - profile=2 * [1, 1, 1], + profile=len(periods) * [1, 1, 1], ) energysystem.add(load) @@ -95,7 +97,7 @@ carrier="pkm", bus=indiv_mob, amount=200, # PKM - profile=2 * [0, 1, 0], # drive consumption + profile=len(periods) * [0, 1, 0], # drive consumption ) energysystem.add(pkm_demand) @@ -106,20 +108,20 @@ electricity_bus=el_bus, storage_capacity=150, capacity=50, - drive_power=150, # nominal value sink + # drive_power=150, # nominal value sink # drive_consumption=[1, 1, 1], # relative value sink max_charging_power=0, # existing - availability=2 * [1, 1, 1], + availability=len(periods) * [1, 1, 1], efficiency_charging=1, v2g=True, - # 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], + loss_rate=0.01, + min_storage_level=(len(date_time_index) + 0) * [0], + max_storage_level=(len(date_time_index) + 0) * [0.9], transport_commodity_bus=indiv_mob, expandable=True, bev_capacity_cost=2, - invest_c_rate=60 / 20, - # marginal_cost=3, + invest_c_rate=60 / 20, # Capacity/Power + marginal_cost=3, pkm_conversion_rate=0.7, lifetime=10, ) @@ -133,7 +135,7 @@ drive_power=100, # drive_consumption=[0, 1, 0], # max_charging_power=200, - availability=2 * [1, 1, 1], + availability=len(periods) * [1, 1, 1], v2g=False, # loss_rate=0.01, # min_storage_level=[0.1, 0.2, 0.15, 0.15], @@ -156,7 +158,7 @@ drive_power=100, # drive_consumption=[0, 1, 0], # max_charging_power=200, - availability=2 * [1, 1, 1], + availability=len(periods) * [1, 1, 1], v2g=False, # loss_rate=0.01, # min_storage_level=[0.1, 0.2, 0.15, 0.15], @@ -168,7 +170,7 @@ # marginal_cost=3, pkm_conversion_rate=0.7, input_parameters={ - "fix": 2 * [0, 0, 0] + "fix": len(periods) * [0, 0, 0] }, # fixed relative charging profile lifetime=10, ) @@ -198,7 +200,7 @@ # select solver 'gurobi', 'cplex', 'glpk' etc model.solve("cbc", solve_kwargs={"tee": True}) - model.display() + # model.display() energysystem.params = solph.processing.parameter_as_dict( energysystem, exclude_attrs=["subnodes"] From 7606c6f39e73dd907c9ba4af048f34c1b939638c Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Fri, 3 Nov 2023 16:37:52 +0100 Subject: [PATCH 27/88] Add private-transport example --- .../datapackages/private_transport/README.md | 5 + .../data/constraints/bev_mob_share.csv | 3 + .../data/constraints/bev_total_invest.csv | 3 + .../private_transport/data/elements/bus.csv | 2 + .../data/elements/dispatchable.csv | 4 + .../data/elements/excess.csv | 2 + .../private_transport/data/elements/load.csv | 2 + .../data/elements/storage.csv | 2 + .../data/elements/volatile.csv | 2 + .../data/sequences/load_profile.csv | 4 + .../data/sequences/volatile_profile.csv | 4 + .../private_transport/datapackage.json | 486 ++++++++++++++++++ .../private_transport/scripts/infer.py | 20 + 13 files changed, 539 insertions(+) create mode 100644 src/oemof/tabular/examples/datapackages/private_transport/README.md create mode 100644 src/oemof/tabular/examples/datapackages/private_transport/data/constraints/bev_mob_share.csv create mode 100644 src/oemof/tabular/examples/datapackages/private_transport/data/constraints/bev_total_invest.csv create mode 100644 src/oemof/tabular/examples/datapackages/private_transport/data/elements/bus.csv create mode 100644 src/oemof/tabular/examples/datapackages/private_transport/data/elements/dispatchable.csv create mode 100644 src/oemof/tabular/examples/datapackages/private_transport/data/elements/excess.csv create mode 100644 src/oemof/tabular/examples/datapackages/private_transport/data/elements/load.csv create mode 100644 src/oemof/tabular/examples/datapackages/private_transport/data/elements/storage.csv create mode 100644 src/oemof/tabular/examples/datapackages/private_transport/data/elements/volatile.csv create mode 100644 src/oemof/tabular/examples/datapackages/private_transport/data/sequences/load_profile.csv create mode 100644 src/oemof/tabular/examples/datapackages/private_transport/data/sequences/volatile_profile.csv create mode 100644 src/oemof/tabular/examples/datapackages/private_transport/datapackage.json create mode 100644 src/oemof/tabular/examples/datapackages/private_transport/scripts/infer.py diff --git a/src/oemof/tabular/examples/datapackages/private_transport/README.md b/src/oemof/tabular/examples/datapackages/private_transport/README.md new file mode 100644 index 00000000..1117ae81 --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/private_transport/README.md @@ -0,0 +1,5 @@ +# Emission constraint example for oemof-tabular + +Run `scripts/infer.py` from the datapackage root directory to add the +meta data file `datapackage.json` after updating the resources of the +datapackage. diff --git a/src/oemof/tabular/examples/datapackages/private_transport/data/constraints/bev_mob_share.csv b/src/oemof/tabular/examples/datapackages/private_transport/data/constraints/bev_mob_share.csv new file mode 100644 index 00000000..f39d1726 --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/private_transport/data/constraints/bev_mob_share.csv @@ -0,0 +1,3 @@ +name;type;year;label;share_mob_flex_G2V;share_mob_flex_V2G;share_mob_inflex +bev_share_mob_2020;bev_share_mob;2020;BEV;10;20;70 +bev_share_mob_2030;bev_share_mob;2030;BEV;20;30;50 diff --git a/src/oemof/tabular/examples/datapackages/private_transport/data/constraints/bev_total_invest.csv b/src/oemof/tabular/examples/datapackages/private_transport/data/constraints/bev_total_invest.csv new file mode 100644 index 00000000..ee73092e --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/private_transport/data/constraints/bev_total_invest.csv @@ -0,0 +1,3 @@ +name;type;year +bev_equal_invest;bev_equal_invest;2020 +bev_equal_invest;bev_equal_invest;2030 diff --git a/src/oemof/tabular/examples/datapackages/private_transport/data/elements/bus.csv b/src/oemof/tabular/examples/datapackages/private_transport/data/elements/bus.csv new file mode 100644 index 00000000..78a0cc29 --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/private_transport/data/elements/bus.csv @@ -0,0 +1,2 @@ +name;type;balanced +bus-electricity;bus;true diff --git a/src/oemof/tabular/examples/datapackages/private_transport/data/elements/dispatchable.csv b/src/oemof/tabular/examples/datapackages/private_transport/data/elements/dispatchable.csv new file mode 100644 index 00000000..f4d99f4f --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/private_transport/data/elements/dispatchable.csv @@ -0,0 +1,4 @@ +name;type;carrier;tech;capacity;bus;marginal_cost;profile;output_parameters +gas;dispatchable;gas;gt;1000;bus-electricity;40;1;{"custom_attributes": {"emission_factor": 10}} +coal;dispatchable;coal;st;1000;bus-electricity;40;1;{"custom_attributes": {"emission_factor": 20}} +lignite;dispatchable;lignite;st;500;bus-electricity;20;1;{"custom_attributes": {"emission_factor": 30}} diff --git a/src/oemof/tabular/examples/datapackages/private_transport/data/elements/excess.csv b/src/oemof/tabular/examples/datapackages/private_transport/data/elements/excess.csv new file mode 100644 index 00000000..aec66cde --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/private_transport/data/elements/excess.csv @@ -0,0 +1,2 @@ +name;type;bus +electricity-excess;excess;bus-electricity diff --git a/src/oemof/tabular/examples/datapackages/private_transport/data/elements/load.csv b/src/oemof/tabular/examples/datapackages/private_transport/data/elements/load.csv new file mode 100644 index 00000000..a527f0c8 --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/private_transport/data/elements/load.csv @@ -0,0 +1,2 @@ +name;amount;profile;type;bus +electricity-demand;5000;electricity-load-profile;load;bus-electricity diff --git a/src/oemof/tabular/examples/datapackages/private_transport/data/elements/storage.csv b/src/oemof/tabular/examples/datapackages/private_transport/data/elements/storage.csv new file mode 100644 index 00000000..1c824889 --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/private_transport/data/elements/storage.csv @@ -0,0 +1,2 @@ +name,carrier,tech,storage_capacity,capacity,capacity_cost,storage_capacity_initial,type,bus +el-storage,lithium,battery,100,10,10,0.5,storage,bus-electricity diff --git a/src/oemof/tabular/examples/datapackages/private_transport/data/elements/volatile.csv b/src/oemof/tabular/examples/datapackages/private_transport/data/elements/volatile.csv new file mode 100644 index 00000000..aaf5f273 --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/private_transport/data/elements/volatile.csv @@ -0,0 +1,2 @@ +name;type;carrier;tech;capacity;capacity_cost;bus;marginal_cost;profile;output_parameters +wind;volatile;wind;onshore;50;;bus-electricity;0;wind-profile;{} diff --git a/src/oemof/tabular/examples/datapackages/private_transport/data/sequences/load_profile.csv b/src/oemof/tabular/examples/datapackages/private_transport/data/sequences/load_profile.csv new file mode 100644 index 00000000..a3f46a20 --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/private_transport/data/sequences/load_profile.csv @@ -0,0 +1,4 @@ +timeindex,electricity-load-profile +2011-01-01T00:00:00Z,0.000745659236 +2011-01-01T01:00:00Z,0.000709651546 +2011-01-01T02:00:00Z,0.00068564642 diff --git a/src/oemof/tabular/examples/datapackages/private_transport/data/sequences/volatile_profile.csv b/src/oemof/tabular/examples/datapackages/private_transport/data/sequences/volatile_profile.csv new file mode 100644 index 00000000..8ab276f0 --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/private_transport/data/sequences/volatile_profile.csv @@ -0,0 +1,4 @@ +timeindex,wind-profile +2011-01-01T00:00:00Z,0.147532 +2011-01-01T01:00:00Z,0.184181 +2011-01-01T02:00:00Z,0.223937 diff --git a/src/oemof/tabular/examples/datapackages/private_transport/datapackage.json b/src/oemof/tabular/examples/datapackages/private_transport/datapackage.json new file mode 100644 index 00000000..ee69b983 --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/private_transport/datapackage.json @@ -0,0 +1,486 @@ +{ + "profile": "tabular-data-package", + "name": "oemof-tabular-dispatch-example", + "oemof_tabular_version": "0.0.5dev", + "resources": [ + { + "path": "data/elements/bus.csv", + "profile": "tabular-data-resource", + "name": "bus", + "format": "csv", + "mediatype": "text/csv", + "encoding": "utf-8", + "schema": { + "fields": [ + { + "name": "name", + "type": "string", + "format": "default" + }, + { + "name": "type", + "type": "string", + "format": "default" + }, + { + "name": "balanced", + "type": "boolean", + "format": "default" + } + ], + "missingValues": [ + "" + ], + "primaryKey": "name", + "foreignKeys": [] + } + }, + { + "path": "data/elements/dispatchable.csv", + "profile": "tabular-data-resource", + "name": "dispatchable", + "format": "csv", + "mediatype": "text/csv", + "encoding": "utf-8", + "schema": { + "fields": [ + { + "name": "name", + "type": "string", + "format": "default" + }, + { + "name": "type", + "type": "string", + "format": "default" + }, + { + "name": "carrier", + "type": "string", + "format": "default" + }, + { + "name": "tech", + "type": "string", + "format": "default" + }, + { + "name": "capacity", + "type": "integer", + "format": "default" + }, + { + "name": "bus", + "type": "string", + "format": "default" + }, + { + "name": "marginal_cost", + "type": "integer", + "format": "default" + }, + { + "name": "profile", + "type": "integer", + "format": "default" + }, + { + "name": "output_parameters", + "type": "object", + "format": "default" + } + ], + "missingValues": [ + "" + ], + "primaryKey": "name", + "foreignKeys": [ + { + "fields": "bus", + "reference": { + "resource": "bus", + "fields": "name" + } + } + ] + } + }, + { + "path": "data/elements/excess.csv", + "profile": "tabular-data-resource", + "name": "excess", + "format": "csv", + "mediatype": "text/csv", + "encoding": "utf-8", + "schema": { + "fields": [ + { + "name": "name", + "type": "string", + "format": "default" + }, + { + "name": "type", + "type": "string", + "format": "default" + }, + { + "name": "bus", + "type": "string", + "format": "default" + } + ], + "missingValues": [ + "" + ], + "primaryKey": "name", + "foreignKeys": [ + { + "fields": "bus", + "reference": { + "resource": "bus", + "fields": "name" + } + } + ] + } + }, + { + "path": "data/elements/load.csv", + "profile": "tabular-data-resource", + "name": "load", + "format": "csv", + "mediatype": "text/csv", + "encoding": "utf-8", + "schema": { + "fields": [ + { + "name": "name", + "type": "string", + "format": "default" + }, + { + "name": "amount", + "type": "integer", + "format": "default" + }, + { + "name": "profile", + "type": "string", + "format": "default" + }, + { + "name": "type", + "type": "string", + "format": "default" + }, + { + "name": "bus", + "type": "string", + "format": "default" + } + ], + "missingValues": [ + "" + ], + "primaryKey": "name", + "foreignKeys": [ + { + "fields": "bus", + "reference": { + "resource": "bus", + "fields": "name" + } + }, + { + "fields": "profile", + "reference": { + "resource": "load_profile" + } + } + ] + } + }, + { + "path": "data/elements/storage.csv", + "profile": "tabular-data-resource", + "name": "storage", + "format": "csv", + "mediatype": "text/csv", + "encoding": "utf-8", + "schema": { + "fields": [ + { + "name": "name", + "type": "string", + "format": "default" + }, + { + "name": "carrier", + "type": "string", + "format": "default" + }, + { + "name": "tech", + "type": "string", + "format": "default" + }, + { + "name": "storage_capacity", + "type": "integer", + "format": "default" + }, + { + "name": "capacity", + "type": "integer", + "format": "default" + }, + { + "name": "capacity_cost", + "type": "integer", + "format": "default" + }, + { + "name": "storage_capacity_initial", + "type": "number", + "format": "default" + }, + { + "name": "type", + "type": "string", + "format": "default" + }, + { + "name": "bus", + "type": "string", + "format": "default" + } + ], + "missingValues": [ + "" + ], + "primaryKey": "name", + "foreignKeys": [ + { + "fields": "bus", + "reference": { + "resource": "bus", + "fields": "name" + } + } + ] + } + }, + { + "path": "data/elements/volatile.csv", + "profile": "tabular-data-resource", + "name": "volatile", + "format": "csv", + "mediatype": "text/csv", + "encoding": "utf-8", + "schema": { + "fields": [ + { + "name": "name", + "type": "string", + "format": "default" + }, + { + "name": "type", + "type": "string", + "format": "default" + }, + { + "name": "carrier", + "type": "string", + "format": "default" + }, + { + "name": "tech", + "type": "string", + "format": "default" + }, + { + "name": "capacity", + "type": "integer", + "format": "default" + }, + { + "name": "capacity_cost", + "type": "string", + "format": "default" + }, + { + "name": "bus", + "type": "string", + "format": "default" + }, + { + "name": "marginal_cost", + "type": "integer", + "format": "default" + }, + { + "name": "profile", + "type": "string", + "format": "default" + }, + { + "name": "output_parameters", + "type": "object", + "format": "default" + } + ], + "missingValues": [ + "" + ], + "primaryKey": "name", + "foreignKeys": [ + { + "fields": "bus", + "reference": { + "resource": "bus", + "fields": "name" + } + }, + { + "fields": "profile", + "reference": { + "resource": "volatile_profile" + } + } + ] + } + }, + { + "path": "data/sequences/load_profile.csv", + "profile": "tabular-data-resource", + "name": "load_profile", + "format": "csv", + "mediatype": "text/csv", + "encoding": "utf-8", + "schema": { + "fields": [ + { + "name": "timeindex", + "type": "datetime", + "format": "default" + }, + { + "name": "electricity-load-profile", + "type": "number", + "format": "default" + } + ], + "missingValues": [ + "" + ] + } + }, + { + "path": "data/sequences/volatile_profile.csv", + "profile": "tabular-data-resource", + "name": "volatile_profile", + "format": "csv", + "mediatype": "text/csv", + "encoding": "utf-8", + "schema": { + "fields": [ + { + "name": "timeindex", + "type": "datetime", + "format": "default" + }, + { + "name": "wind-profile", + "type": "number", + "format": "default" + } + ], + "missingValues": [ + "" + ] + } + }, + { + "path": "data/constraints/bev_mob_share.csv", + "profile": "tabular-data-resource", + "name": "bev_mob_share", + "format": "csv", + "mediatype": "text/csv", + "encoding": "utf-8", + "schema": { + "fields": [ + { + "name": "name", + "type": "string", + "format": "default" + }, + { + "name": "type", + "type": "string", + "format": "default" + }, + { + "name": "year", + "type": "integer", + "format": "default" + }, + { + "name": "label", + "type": "string", + "format": "default" + }, + { + "name": "share_mob_flex_G2V", + "type": "integer", + "format": "default" + }, + { + "name": "share_mob_flex_V2G", + "type": "integer", + "format": "default" + }, + { + "name": "share_mob_inflex", + "type": "integer", + "format": "default" + } + ], + "missingValues": [ + "" + ] + } + }, + { + "path": "data/constraints/bev_total_invest.csv", + "profile": "tabular-data-resource", + "name": "bev_total_invest", + "format": "csv", + "mediatype": "text/csv", + "encoding": "utf-8", + "schema": { + "fields": [ + { + "name": "name", + "type": "string", + "format": "default" + }, + { + "name": "type", + "type": "string", + "format": "default" + }, + { + "name": "year", + "type": "integer", + "format": "default" + } + ], + "missingValues": [ + "" + ] + } + } + ] +} diff --git a/src/oemof/tabular/examples/datapackages/private_transport/scripts/infer.py b/src/oemof/tabular/examples/datapackages/private_transport/scripts/infer.py new file mode 100644 index 00000000..58321e9a --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/private_transport/scripts/infer.py @@ -0,0 +1,20 @@ +""" +Run this script from the root directory of the datapackage to update +or create meta data. +""" +from oemof.tabular.datapackage import building + +# This part is for testing only: It allows to pass +# the filename of inferred metadata other than the default. +if "kwargs" not in locals(): + kwargs = {} + + +building.infer_metadata( + package_name="oemof-tabular-dispatch-example", + foreign_keys={ + "bus": ["volatile", "dispatchable", "storage", "load", "excess"], + "profile": ["load", "volatile"], + }, + **kwargs, +) From 3629756bd1fe34d7af95391215f34849e81fa891 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 7 Nov 2023 17:18:43 +0100 Subject: [PATCH 28/88] Add facade test module incl first bev test draft --- tests/test_facades.py | 128 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 tests/test_facades.py diff --git a/tests/test_facades.py b/tests/test_facades.py new file mode 100644 index 00000000..09d984a5 --- /dev/null +++ b/tests/test_facades.py @@ -0,0 +1,128 @@ +import logging + +import pandas as pd +from oemof.solph import helpers + +from oemof import solph +from oemof.tabular.constraint_facades import BevEqualInvest, BevShareMob +from oemof.tabular.facades import Bev, Load, Volatile +from oemof.tabular.postprocessing import calculations + + +class TestFacades: + @classmethod + def setup_class(cls): + cls.date_time_index = pd.date_range("1/1/2020", periods=3, freq="H") + + cls.tmpdir = helpers.extend_basic_path("tmp") + logging.info(cls.tmpdir) + + @classmethod + def setup_method(cls): + cls.energysystem = solph.EnergySystem( + groupings=solph.GROUPINGS, timeindex=cls.date_time_index + ) + + def get_om(self): + self.model = solph.Model( + self.energysystem, + timeindex=self.energysystem.timeindex, + ) + + def solve_om(self): + opt_result = self.model.solve("cbc", solve_kwargs={"tee": True}) + self.results = self.model.results() + return opt_result + + def rename_results(self): + rename_mapping = { + oemof_tuple: f"{oemof_tuple[0]}->{oemof_tuple[1]}" + for oemof_tuple in self.results.keys() + } + for old_key, new_key in rename_mapping.items(): + self.results[new_key] = self.results.pop(old_key) + + def test_bev_v2g_dispatch(self): + el_bus = solph.Bus("el-bus") + el_bus.type = "bus" + self.energysystem.add(el_bus) + + indiv_mob = solph.Bus("pkm-bus") + indiv_mob.type = "bus" + self.energysystem.add(indiv_mob) + + volatile = Volatile( + label="wind", + bus=el_bus, + carrier="wind", + tech="onshore", + capacity=100, + profile=[1, 0, 0], + ) + self.energysystem.add(volatile) + + load = Load( + label="load", + carrier="electricity", + bus=el_bus, + amount=50, + profile=[0, 1, 0], + ) + self.energysystem.add(load) + + pkm_demand = Load( + label="pkm_demand", + type="Load", + carrier="pkm", + bus=indiv_mob, + amount=50, # PKM + profile=[0, 0, 1], # drive consumption + ) + + self.energysystem.add(pkm_demand) + + bev_v2g = Bev( + type="bev", + label="BEV-V2G", + v2g=True, + electricity_bus=el_bus, + transport_commodity_bus=indiv_mob, + storage_capacity=200, + capacity=200, # TODO replace by storage_capacity + loss_rate=0, + max_charging_power=50, + availability=[1, 1, 1], + # min_storage_level=[0.1, 0.2, 0.15, 0.15], + # max_storage_level=[0.9, 0.95, 0.92, 0.92], + efficiency_charging=1, + pkm_conversion_rate=1, + efficiency_mob_g2v=1, + efficiency_mob_v2g=1, + efficiency_mob_electrical=1, + efficiency_sto_in=1, + efficiency_sto_out=1, + ) + + self.energysystem.add(bev_v2g) + + self.get_om() + + solver_stats = self.solve_om() + + # TODO check why this is not working + # self.energysystem.params = solph.processing.parameter_as_dict( + # self.energysystem) + # postprocessed_results = calculations.run_postprocessing( + # self.energysystem) + + # rename results to make them accessible + self.rename_results() + + assert solver_stats["Solver"][0]["Status"] == "ok" + + # Check Storage level + cn = "BEV-V2G-storage->None" + assert self.results[cn]["sequences"]["storage_content"].iloc[0] == 0 + assert self.results[cn]["sequences"]["storage_content"].iloc[1] == 100 + assert self.results[cn]["sequences"]["storage_content"].iloc[2] == 50 + assert self.results[cn]["sequences"]["storage_content"].iloc[3] == 0 From 0570456da112e505cfe6fae9e6850fc31d2f214c Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 7 Nov 2023 17:39:49 +0100 Subject: [PATCH 29/88] Extend docstring --- .../experimental/battery_electric_vehicle.py | 114 ++++++++++++++---- 1 file changed, 92 insertions(+), 22 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index 856259fc..2bfd26af 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -12,45 +12,115 @@ @dataclass_facade class Bev(GenericStorage, Facade): - r"""A fleet of Battery electric vehicles with vehicle-to-grid. + r"""A fleet of Battery electric vehicles with controlled/flexible charging, + (G2V), vehicle-to-grid (V2G) or uncontrolled/fixed charging (inflex). - Note that the investment option is not available for this facade at - the current development state. + This facade consists of mulitple oemof.solph components: + + - a GenericStorage as storage unit + - a Bus as internal bus + - a Sink to model the drive consumption (if no mobility bus is + given) + - a Converter to convert the energy to the electricity bus (optional V2G) + - a Converter to convert the energy to e.g. pkm (optional if mobility bus + is given) + + Charging and discharging capacity is assumed to be equal. + Multiple fleets can be modelled and connected to a common bus + (mobility_bus) to apply one demand for all modelled fleets. Parameters ---------- electricity_bus: oemof.solph.Bus - An oemof bus instance where the storage unit is connected to. + The electricity bus where the BEV is connected to. + mobility_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. + 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 vehicles (e.g. in MWh) + 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. + min_storage_level : array of float + 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 + Relative profile of maximum storage level (max SOC). drive_power: int - Total charging/discharging capacity of the vehicles (e.g. in MW) - drive_consumption : array-like - Profile of drive consumption of the fleet (relative to capacity). - max_charging_power : int - Max charging/discharging power of all vehicles (e.g. in MW) - availability : array-like - Ratio of available capacity for charging/vehicle-to-grid due to - grid connection. - efficiency_charging: float - Efficiency of charging the batteries, default: 1 + The total driving capacity of the fleet (e.g. in MW) if no mobility_bus + is connected. + drive_consumption : array of float + Relative profile of drive consumption of the fleet v2g: bool - If True, vehicle-to-grid is enabled, default: False + If True, Vehicle-to-grid option is enabled, default: False loss_rate: float - min_storage_level : array-like - Profile of minimum storage level (min SOC) - max_storage_level : array-like - Profile of maximum storage level (max SOC). + The relative loss/self discharge of the storage content per time unit, + default: 0 + efficiency_mob_g2v: float + Efficiency at the charging station (grid-to-vehicle), default: 1 + efficiency_mob_v2g: float + Efficiency at the charging station (vehicle-to-grid), default: 1 + efficiency_sto_in: float + Efficiency of charging the batteries, default: 1 + efficiency_sto_out: 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 + Conversion rate from energy to e.g. pkm if mobility_bus passed + (optional) 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 + 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. + + 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)) + variable_costs: float, array of float + Variable costs of the fleet (e.g. in EUR/MWh). + fixed_investment_costs + + balanced : boolean Couple storage level of first and last time step. (Total inflow and total outflow are balanced.) - transport_commodity: None - Bus for the transport commodity + 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. + e.g. fixed charging timeseries for the storage can be passed with + {"fix": [1,0.5,...]} output_parameters: dict see: input_parameters + e.g. fixed discharging timeseries for the storage can be passed with + {"fix": [1,0.5,...]} The vehicle fleet is modelled as a storage together with an internal From 5c8bd01b130a8e33d50b36dba3089adec69c65c5 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 7 Nov 2023 17:41:42 +0100 Subject: [PATCH 30/88] Rework parameters --- .../experimental/battery_electric_vehicle.py | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index 2bfd26af..1f853e03 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -184,51 +184,57 @@ class Bev(GenericStorage, Facade): electricity_bus: Bus - availability: Union[float, Sequence[float]] = 1 + mobility_bus: Bus = None - max_charging_power: Union[float, Sequence[float]] = 0 + charging_power: int = 0 - drive_power: int = 0 + charging_potential: int = None - capacity: int = 0 + availability: Union[float, Sequence[float]] = 1 storage_capacity: int = 0 initial_storage_capacity: float = 0 - drive_consumption: Sequence = None + drive_power: int = 0 + + drive_consumption: Sequence[float] = None v2g: bool = False - transport_commodity_bus: Bus = None + efficiency_mob_g2v: float = 1 - input_parameters: dict = field(default_factory=dict) + efficiency_mob_v2g: float = 1 - output_parameters: dict = field(default_factory=dict) + efficiency_mob_electrical: float = 1 + + efficiency_sto_in: float = 1 + + efficiency_sto_out: float = 1 + + pkm_conversion_rate: float = 1 expandable: bool = False - lifetime: int = None + lifetime: int = 20 - bev_capacity_cost: Sequence[float] = None + age: int = 0 invest_c_rate: Sequence[float] = None - marginal_cost: float = 0 + bev_invest_costs: Sequence[float] = None - balanced: bool = False + variable_costs: Union[float, Sequence[float]] = 0 - pkm_conversion_rate: float = 1 + fixed_costs: Union[float, Sequence[float]] = 0 - efficiency_mob_g2v: float = 1 - - efficiency_mob_v2g: float = 1 + fixed_investment_costs: Union[float, Sequence[float]] = 0 - efficiency_mob_electrical: float = 1 + balanced: bool = False - efficiency_sto_in: float = 1 + input_parameters: dict = field(default_factory=dict) - efficiency_sto_out: float = 1 + output_parameters: dict = field(default_factory=dict) def build_solph_components(self): # use label as prefix for subnodes From 61db0adec2c9c1a4b89386c5d270d3900c651902 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 7 Nov 2023 17:52:11 +0100 Subject: [PATCH 31/88] Rename parameter charging_power --- .../experimental/battery_electric_vehicle.py | 14 ++++++-------- tests/mobility.py | 6 +++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index 1f853e03..740a5e5f 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -243,7 +243,7 @@ def build_solph_components(self): # convert to solph sequences self.availability = solph_sequence(self.availability) - self.max_charging_power = solph_sequence(self.max_charging_power) + self.charging_power = solph_sequence(self.charging_power) # TODO: check if this is correct self.nominal_storage_capacity = self.storage_capacity @@ -270,7 +270,7 @@ def build_solph_components(self): outputs={ self.electricity_bus: Flow( nominal_value=self._nominal_value( - value=self.max_charging_power + value=self.charging_power ), # max=self.availability, # doesn't work with investment variable_costs=None, @@ -296,10 +296,8 @@ def build_solph_components(self): ) }, outputs={ - self.transport_commodity_bus: Flow( - nominal_value=self._nominal_value( - self.max_charging_power - ), + self.mobility_bus: Flow( + nominal_value=self._nominal_value(self.charging_power), max=self.availability, variable_costs=None, investment=self._investment(bev=True), @@ -378,12 +376,12 @@ def build_solph_components(self): else: flow_in = Flow( - nominal_value=self._nominal_value(self.max_charging_power), + nominal_value=self._nominal_value(self.charging_power), max=self.availability, **self.input_parameters, ) flow_out = Flow( - nominal_value=self._nominal_value(self.max_charging_power), + nominal_value=self._nominal_value(self.charging_power), # max=self.availability, variable_costs=self.marginal_cost, **self.output_parameters, diff --git a/tests/mobility.py b/tests/mobility.py index 9b8bef84..831135e1 100644 --- a/tests/mobility.py +++ b/tests/mobility.py @@ -110,7 +110,7 @@ capacity=50, # drive_power=150, # nominal value sink # drive_consumption=[1, 1, 1], # relative value sink - max_charging_power=0, # existing + charging_power=0, # existing availability=len(periods) * [1, 1, 1], efficiency_charging=1, v2g=True, @@ -134,7 +134,7 @@ storage_capacity=200, drive_power=100, # drive_consumption=[0, 1, 0], - # max_charging_power=200, + # charging_power=200, availability=len(periods) * [1, 1, 1], v2g=False, # loss_rate=0.01, @@ -157,7 +157,7 @@ storage_capacity=200, drive_power=100, # drive_consumption=[0, 1, 0], - # max_charging_power=200, + # charging_power=200, availability=len(periods) * [1, 1, 1], v2g=False, # loss_rate=0.01, From 036c283204989ec74ce2611a046fd7f50a37920e Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 7 Nov 2023 17:52:46 +0100 Subject: [PATCH 32/88] Rename parameter mobility_bus --- .../facades/experimental/battery_electric_vehicle.py | 2 +- tests/mobility.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index 740a5e5f..462efa9c 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -284,7 +284,7 @@ def build_solph_components(self): subnodes.append(vehicle_to_grid) # Drive consumption - if self.transport_commodity_bus: + if self.mobility_bus: # ##### PKM Converter ##### # converts energy to e.g. pkm # connects it to a special mobility bus diff --git a/tests/mobility.py b/tests/mobility.py index 831135e1..d3f2f075 100644 --- a/tests/mobility.py +++ b/tests/mobility.py @@ -117,7 +117,7 @@ loss_rate=0.01, min_storage_level=(len(date_time_index) + 0) * [0], max_storage_level=(len(date_time_index) + 0) * [0.9], - transport_commodity_bus=indiv_mob, + mobility_bus=indiv_mob, expandable=True, bev_capacity_cost=2, invest_c_rate=60 / 20, # Capacity/Power @@ -140,7 +140,7 @@ # 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], - transport_commodity_bus=indiv_mob, + mobility_bus=indiv_mob, expandable=True, bev_capacity_cost=2, invest_c_rate=60 / 20, @@ -163,7 +163,7 @@ # 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], - transport_commodity_bus=indiv_mob, + mobility_bus=indiv_mob, expandable=True, bev_capacity_cost=2, invest_c_rate=60 / 20, # Capacity/Power From 8c8a1534df58ff8d3725dd09b957d8e1797c9b1e Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 7 Nov 2023 17:53:11 +0100 Subject: [PATCH 33/88] Rename parameter bev_invest_costs --- .../facades/experimental/battery_electric_vehicle.py | 4 ++-- tests/mobility.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index 462efa9c..7a869dc9 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -325,7 +325,7 @@ def build_solph_components(self): # ##### Storage ######## if self.expandable: - self.capacity_cost = self.bev_capacity_cost + # self.capacity_cost = self.bev_invest_costs self.storage_capacity_cost = 0 # self.investment = self._investment(bev=False) self.invest_relation_input_output = 1 # charge/discharge equal @@ -350,7 +350,7 @@ def build_solph_components(self): flow_in = Flow( # max=self.availability, investment=Investment( - ep_costs=self.bev_capacity_cost, + ep_costs=self.bev_invest_costs, maximum=self._get_maximum_additional_invest( "capacity_potential", "capacity" ), diff --git a/tests/mobility.py b/tests/mobility.py index d3f2f075..8bc07d2f 100644 --- a/tests/mobility.py +++ b/tests/mobility.py @@ -119,7 +119,7 @@ max_storage_level=(len(date_time_index) + 0) * [0.9], mobility_bus=indiv_mob, expandable=True, - bev_capacity_cost=2, + bev_invest_costs=2, invest_c_rate=60 / 20, # Capacity/Power marginal_cost=3, pkm_conversion_rate=0.7, @@ -142,7 +142,7 @@ # max_storage_level=[0.9, 0.95, 0.92, 0.92], mobility_bus=indiv_mob, expandable=True, - bev_capacity_cost=2, + bev_invest_costs=2, invest_c_rate=60 / 20, # marginal_cost=3, pkm_conversion_rate=0.7, @@ -165,7 +165,7 @@ # max_storage_level=[0.9, 0.95, 0.92, 0.92], mobility_bus=indiv_mob, expandable=True, - bev_capacity_cost=2, + bev_invest_costs=2, invest_c_rate=60 / 20, # Capacity/Power # marginal_cost=3, pkm_conversion_rate=0.7, From 656940c573d21ae34d7e9719cc3dde328d788a89 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 7 Nov 2023 17:54:34 +0100 Subject: [PATCH 34/88] Rename parameter variable_costs --- .../facades/experimental/battery_electric_vehicle.py | 4 ++-- tests/mobility.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index 7a869dc9..ca909915 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -368,7 +368,7 @@ def build_solph_components(self): lifetime=getattr(self, "lifetime", None), age=getattr(self, "age", 0), ), - variable_costs=self.marginal_cost, + variable_costs=self.variable_costs, **self.output_parameters, ) # required for correct grouping in oemof.solph.components @@ -383,7 +383,7 @@ def build_solph_components(self): flow_out = Flow( nominal_value=self._nominal_value(self.charging_power), # max=self.availability, - variable_costs=self.marginal_cost, + variable_costs=self.variable_costs, **self.output_parameters, ) diff --git a/tests/mobility.py b/tests/mobility.py index 8bc07d2f..23751091 100644 --- a/tests/mobility.py +++ b/tests/mobility.py @@ -121,7 +121,7 @@ expandable=True, bev_invest_costs=2, invest_c_rate=60 / 20, # Capacity/Power - marginal_cost=3, + variable_costs=3, pkm_conversion_rate=0.7, lifetime=10, ) @@ -144,7 +144,7 @@ expandable=True, bev_invest_costs=2, invest_c_rate=60 / 20, - # marginal_cost=3, + # variable_costs=3, pkm_conversion_rate=0.7, lifetime=10, ) @@ -167,7 +167,7 @@ expandable=True, bev_invest_costs=2, invest_c_rate=60 / 20, # Capacity/Power - # marginal_cost=3, + # variable_costs=3, pkm_conversion_rate=0.7, input_parameters={ "fix": len(periods) * [0, 0, 0] From ead80fb170a089695d3d058a658335c63be17a46 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 7 Nov 2023 23:26:27 +0100 Subject: [PATCH 35/88] Rename parameter charging_power --- .../facades/experimental/battery_electric_vehicle.py | 9 ++++----- tests/mobility.py | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index ca909915..80417abc 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -243,7 +243,6 @@ def build_solph_components(self): # convert to solph sequences self.availability = solph_sequence(self.availability) - self.charging_power = solph_sequence(self.charging_power) # TODO: check if this is correct self.nominal_storage_capacity = self.storage_capacity @@ -346,15 +345,15 @@ def build_solph_components(self): ) # ##### Grid2Vehicle ##### - # containts the whole investmentcosts for bev + # containts 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( - "capacity_potential", "capacity" + "charging_potential", "charging_power" ), - existing=getattr(self, "capacity", 0), + existing=getattr(self, "charging_power", 0), lifetime=getattr(self, "lifetime", None), age=getattr(self, "age", 0), fixed_costs=getattr(self, "fixed_costs", None), @@ -364,7 +363,7 @@ def build_solph_components(self): # set investment, but no costs (as relation input / output = 1) flow_out = Flow( investment=Investment( - existing=getattr(self, "capacity", 0), + existing=getattr(self, "charging_power", 0), lifetime=getattr(self, "lifetime", None), age=getattr(self, "age", 0), ), diff --git a/tests/mobility.py b/tests/mobility.py index 23751091..9d53cd17 100644 --- a/tests/mobility.py +++ b/tests/mobility.py @@ -110,7 +110,7 @@ capacity=50, # drive_power=150, # nominal value sink # drive_consumption=[1, 1, 1], # relative value sink - charging_power=0, # existing + charging_power=150, # existing availability=len(periods) * [1, 1, 1], efficiency_charging=1, v2g=True, @@ -134,7 +134,7 @@ storage_capacity=200, drive_power=100, # drive_consumption=[0, 1, 0], - # charging_power=200, + charging_power=200, availability=len(periods) * [1, 1, 1], v2g=False, # loss_rate=0.01, @@ -157,7 +157,7 @@ storage_capacity=200, drive_power=100, # drive_consumption=[0, 1, 0], - # charging_power=200, + charging_power=200, availability=len(periods) * [1, 1, 1], v2g=False, # loss_rate=0.01, From b7a99b59b64fc9f3eb95ab62b477d6f32381d174 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 7 Nov 2023 23:34:44 +0100 Subject: [PATCH 36/88] Consumption Sink for expandable not implemented yet --- .../experimental/battery_electric_vehicle.py | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index 80417abc..93987a58 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -309,17 +309,23 @@ def build_solph_components(self): else: # ##### Consumption Sink ##### - # fixed timeseries for this bev unit only - driving_consumption = Sink( - label=self.facade_label + "-consumption", - inputs={ - internal_bus: Flow( - nominal_value=self._nominal_value(self.drive_power), - fix=self.drive_consumption, - investment=self._investment(bev=True), - ) - }, - ) + # fixed demand for this fleet 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." + ) + else: + driving_consumption = Sink( + label=self.facade_label + "-consumption", + inputs={ + internal_bus: Flow( + nominal_value=self.drive_power, + fix=self.drive_consumption, + ) + }, + ) subnodes.append(driving_consumption) # ##### Storage ######## From f5915107b0aa9f32ca13680f773219b4db1a5b99 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 7 Nov 2023 23:36:37 +0100 Subject: [PATCH 37/88] Add fixed and variable costs --- .../facades/experimental/battery_electric_vehicle.py | 2 +- tests/mobility.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index 93987a58..68a1d0bb 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -362,7 +362,7 @@ def build_solph_components(self): existing=getattr(self, "charging_power", 0), lifetime=getattr(self, "lifetime", None), age=getattr(self, "age", 0), - fixed_costs=getattr(self, "fixed_costs", None), + fixed_costs=getattr(self, "fixed_investment_costs", None), ), **self.input_parameters, ) diff --git a/tests/mobility.py b/tests/mobility.py index 9d53cd17..18c0f6f6 100644 --- a/tests/mobility.py +++ b/tests/mobility.py @@ -107,7 +107,6 @@ label="BEV-V2G", electricity_bus=el_bus, storage_capacity=150, - capacity=50, # drive_power=150, # nominal value sink # drive_consumption=[1, 1, 1], # relative value sink charging_power=150, # existing @@ -122,6 +121,7 @@ bev_invest_costs=2, invest_c_rate=60 / 20, # Capacity/Power variable_costs=3, + fixed_investment_costs=1, pkm_conversion_rate=0.7, lifetime=10, ) @@ -144,7 +144,8 @@ expandable=True, bev_invest_costs=2, invest_c_rate=60 / 20, - # variable_costs=3, + variable_costs=3, + fixed_investment_costs=1, pkm_conversion_rate=0.7, lifetime=10, ) @@ -167,7 +168,8 @@ expandable=True, bev_invest_costs=2, invest_c_rate=60 / 20, # Capacity/Power - # variable_costs=3, + variable_costs=3, + fixed_investment_costs=1, pkm_conversion_rate=0.7, input_parameters={ "fix": len(periods) * [0, 0, 0] From b48b3f0f7ac95a0d1d29487beb83b76e11ab2ee3 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 7 Nov 2023 23:38:07 +0100 Subject: [PATCH 38/88] Conversion rate 2pkm and electrical eta --- .../facades/experimental/battery_electric_vehicle.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index 68a1d0bb..0811cd6c 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -302,8 +302,11 @@ def build_solph_components(self): investment=self._investment(bev=True), ) }, - conversion_factors={self.bus: self.pkm_conversion_rate}, - # TODO maybe add battery efficiency + charger efficiency + conversion_factors={ + self.bus: self.pkm_conversion_rate + * self.efficiency_mob_electrical + * 100 # TODO pro 100 km? + }, ) subnodes.append(pkm_converter) From d4e33aecafb1a15dd53ea768a7a93fc6d97f5c2b Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Wed, 8 Nov 2023 00:29:58 +0100 Subject: [PATCH 39/88] Fix conversion factor bug --- .../tabular/facades/experimental/battery_electric_vehicle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index 0811cd6c..e6fe61ba 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -305,7 +305,7 @@ def build_solph_components(self): conversion_factors={ self.bus: self.pkm_conversion_rate * self.efficiency_mob_electrical - * 100 # TODO pro 100 km? + # * 100 # TODO pro 100 km? }, ) subnodes.append(pkm_converter) From c0989a26ed6afadc8155d538eeaad88ffa923178 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Wed, 8 Nov 2023 00:32:34 +0100 Subject: [PATCH 40/88] Add excess & shortage commented --- tests/test_facades.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index 09d984a5..a643c46e 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -5,7 +5,7 @@ from oemof import solph from oemof.tabular.constraint_facades import BevEqualInvest, BevShareMob -from oemof.tabular.facades import Bev, Load, Volatile +from oemof.tabular.facades import Bev, Excess, Load, Shortage, Volatile from oemof.tabular.postprocessing import calculations @@ -105,6 +105,28 @@ def test_bev_v2g_dispatch(self): self.energysystem.add(bev_v2g) + # excess = Excess( + # type="excess", + # label="excess", + # bus=el_bus, + # carrier="electricity", + # tech="excess", + # capacity=1e6, + # marginal_cost=1e6, + # ) + # self.energysystem.add(excess) + # + # shortage = Shortage( + # type="shortage", + # label="shortage", + # bus=el_bus, + # carrier="electricity", + # tech="shortage", + # capacity=1e6, + # marginal_cost=1e6, + # ) + # self.energysystem.add(shortage) + self.get_om() solver_stats = self.solve_om() From a96fc034e11953aabe87ef056e2b0c1bd430e56a Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Wed, 8 Nov 2023 00:35:04 +0100 Subject: [PATCH 41/88] Describe parameter in bev test --- tests/test_facades.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index a643c46e..660872af 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -86,21 +86,20 @@ def test_bev_v2g_dispatch(self): label="BEV-V2G", v2g=True, electricity_bus=el_bus, - transport_commodity_bus=indiv_mob, + mobility_bus=indiv_mob, storage_capacity=200, - capacity=200, # TODO replace by storage_capacity - loss_rate=0, - max_charging_power=50, - availability=[1, 1, 1], + loss_rate=0, # self discharge of storage + charging_power=200, + availability=[1, 1, 1], # Vehicle availability at charger # min_storage_level=[0.1, 0.2, 0.15, 0.15], # max_storage_level=[0.9, 0.95, 0.92, 0.92], efficiency_charging=1, - pkm_conversion_rate=1, - efficiency_mob_g2v=1, - efficiency_mob_v2g=1, - efficiency_mob_electrical=1, - efficiency_sto_in=1, - efficiency_sto_out=1, + pkm_conversion_rate=1, # Energy to pkm + efficiency_mob_electrical=1, # Vehicle efficiency per 100km + efficiency_mob_g2v=1, # Charger efficiency + efficiency_mob_v2g=1, # V2G charger efficiency + efficiency_sto_in=1, # Storage charging efficiency + efficiency_sto_out=1, # Storage discharging efficiency ) self.energysystem.add(bev_v2g) From 8a6762de758a42bc6d86be8f15ef129bfae1fcd8 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Wed, 8 Nov 2023 00:48:07 +0100 Subject: [PATCH 42/88] Set variable costs at storage inflow --- .../tabular/facades/experimental/battery_electric_vehicle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index e6fe61ba..7e962d89 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -367,6 +367,7 @@ def build_solph_components(self): age=getattr(self, "age", 0), fixed_costs=getattr(self, "fixed_investment_costs", None), ), + variable_costs=self.variable_costs, **self.input_parameters, ) # set investment, but no costs (as relation input / output = 1) @@ -376,7 +377,6 @@ def build_solph_components(self): lifetime=getattr(self, "lifetime", None), age=getattr(self, "age", 0), ), - variable_costs=self.variable_costs, **self.output_parameters, ) # required for correct grouping in oemof.solph.components From 9e5e541f575dcdf0e2492d3828ffb767108d2ed0 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Wed, 8 Nov 2023 12:12:14 +0100 Subject: [PATCH 43/88] Fix efficiency g2v --- .../tabular/facades/experimental/battery_electric_vehicle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index 7e962d89..d72dd733 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -277,7 +277,7 @@ def build_solph_components(self): ) }, # Includes storage charging efficiencies - conversion_factors={internal_bus: self.efficiency_mob_g2v}, + conversion_factors={internal_bus: self.efficiency_mob_v2g}, # TODO check efficiencies ) subnodes.append(vehicle_to_grid) From a803d63bb49cdb9ffe5cce049a20316f828c192a Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Thu, 9 Nov 2023 17:40:40 +0100 Subject: [PATCH 44/88] Add docstring and comments to constraints --- src/oemof/tabular/constraint_facades.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/oemof/tabular/constraint_facades.py b/src/oemof/tabular/constraint_facades.py index c11ffb53..68bd03d0 100644 --- a/src/oemof/tabular/constraint_facades.py +++ b/src/oemof/tabular/constraint_facades.py @@ -73,6 +73,9 @@ def build_constraint(self, model): @dataclass class BevShareMob(ConstraintFacade): + # TODO: rework docstring + """This constraint is only feasible if the definition of one vehicle is the + same (charging_capacity/storage_capacity) for all three bev technologies""" name: str type: str year: int @@ -151,6 +154,7 @@ def investment_constraints_rule(InvestmentFlowBlock): ) +# TODO maybe move inside facade @dataclass class BevEqualInvest(ConstraintFacade): name: str From cad9013210cd0b87e656928a41b995b17b30a7cc Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Thu, 9 Nov 2023 17:42:04 +0100 Subject: [PATCH 45/88] Rename bus and conversion rate --- .../experimental/battery_electric_vehicle.py | 28 ++++++++++--------- tests/mobility.py | 12 ++++---- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index d72dd733..edd9a788 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -33,7 +33,7 @@ class Bev(GenericStorage, Facade): ---------- electricity_bus: oemof.solph.Bus The electricity bus where the BEV is connected to. - mobility_bus: oemof.solph.Bus + commodity_bus: oemof.solph.Bus A bus which is used to connect a common demand for multiple BEV instances (optional). charging_power : int @@ -184,11 +184,13 @@ class Bev(GenericStorage, Facade): electricity_bus: Bus - mobility_bus: Bus = None + commodity_bus: Bus = None - charging_power: int = 0 + charging_power: float = 0 - charging_potential: int = None + minimum_charging_power: float = None + + charging_potential: float = None availability: Union[float, Sequence[float]] = 1 @@ -212,7 +214,7 @@ class Bev(GenericStorage, Facade): efficiency_sto_out: float = 1 - pkm_conversion_rate: float = 1 + commodity_conversion_rate: float = 1 expandable: bool = False @@ -283,19 +285,19 @@ def build_solph_components(self): subnodes.append(vehicle_to_grid) # Drive consumption - if self.mobility_bus: - # ##### PKM Converter ##### - # converts energy to e.g. pkm + if self.commodity_bus: + # ##### Commodity Converter ##### + # converts energy to another commodity e.g. pkm # connects it to a special mobility bus - pkm_converter = Converter( - label=self.facade_label + "-2pkm", + commodity_converter = Converter( + label=self.facade_label + "-2com", inputs={ internal_bus: Flow( # **self.output_parameters ) }, outputs={ - self.mobility_bus: Flow( + self.commodity_bus: Flow( nominal_value=self._nominal_value(self.charging_power), max=self.availability, variable_costs=None, @@ -303,12 +305,12 @@ def build_solph_components(self): ) }, conversion_factors={ - self.bus: self.pkm_conversion_rate + self.bus: self.commodity_conversion_rate * self.efficiency_mob_electrical # * 100 # TODO pro 100 km? }, ) - subnodes.append(pkm_converter) + subnodes.append(commodity_converter) else: # ##### Consumption Sink ##### diff --git a/tests/mobility.py b/tests/mobility.py index 18c0f6f6..f8efb283 100644 --- a/tests/mobility.py +++ b/tests/mobility.py @@ -106,6 +106,7 @@ 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 @@ -116,13 +117,12 @@ loss_rate=0.01, min_storage_level=(len(date_time_index) + 0) * [0], max_storage_level=(len(date_time_index) + 0) * [0.9], - mobility_bus=indiv_mob, expandable=True, bev_invest_costs=2, invest_c_rate=60 / 20, # Capacity/Power variable_costs=3, fixed_investment_costs=1, - pkm_conversion_rate=0.7, + commodity_conversion_rate=0.7, lifetime=10, ) energysystem.add(bev_v2g) @@ -131,6 +131,7 @@ type="bev", label="BEV-inflex", electricity_bus=el_bus, + commodity_bus=indiv_mob, storage_capacity=200, drive_power=100, # drive_consumption=[0, 1, 0], @@ -140,13 +141,12 @@ # 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], - mobility_bus=indiv_mob, expandable=True, bev_invest_costs=2, invest_c_rate=60 / 20, variable_costs=3, fixed_investment_costs=1, - pkm_conversion_rate=0.7, + commodity_conversion_rate=0.7, lifetime=10, ) energysystem.add(bev_flex) @@ -155,6 +155,7 @@ type="bev", label="BEV-G2V", electricity_bus=el_bus, + commodity_bus=indiv_mob, storage_capacity=200, drive_power=100, # drive_consumption=[0, 1, 0], @@ -164,13 +165,12 @@ # 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], - mobility_bus=indiv_mob, expandable=True, bev_invest_costs=2, invest_c_rate=60 / 20, # Capacity/Power variable_costs=3, fixed_investment_costs=1, - pkm_conversion_rate=0.7, + commodity_conversion_rate=0.7, input_parameters={ "fix": len(periods) * [0, 0, 0] }, # fixed relative charging profile From 041c749a8656e7bfaa4d6054ed9d44bdd46fca49 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Thu, 9 Nov 2023 17:58:01 +0100 Subject: [PATCH 46/88] Use _converter_investment instead of modifying _investment --- .../experimental/battery_electric_vehicle.py | 44 +++++++++++++++++-- tests/test_facades.py | 7 +-- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index edd9a788..7801178c 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -194,7 +194,11 @@ class Bev(GenericStorage, Facade): availability: Union[float, Sequence[float]] = 1 - storage_capacity: int = 0 + storage_capacity: float = 0 + + minimum_storage_capacity: float = 0 + + storage_capacity_potential: float = None initial_storage_capacity: float = 0 @@ -238,6 +242,26 @@ class Bev(GenericStorage, Facade): output_parameters: dict = field(default_factory=dict) + def _converter_investment(self): + """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, + ) + return investment + else: + return None + def build_solph_components(self): # use label as prefix for subnodes self.facade_label = self.label @@ -258,6 +282,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, + ) + # ##### Vehicle2Grid Converter ##### if self.v2g: vehicle_to_grid = Converter( @@ -275,7 +311,8 @@ def build_solph_components(self): ), # max=self.availability, # doesn't work with investment variable_costs=None, - investment=self._investment(bev=True), + # investment=self._investment(bev=True), + investment=self._converter_investment(), ) }, # Includes storage charging efficiencies @@ -301,7 +338,8 @@ def build_solph_components(self): nominal_value=self._nominal_value(self.charging_power), max=self.availability, variable_costs=None, - investment=self._investment(bev=True), + # investment=self._investment(bev=True), + investment=self._converter_investment(), ) }, conversion_factors={ diff --git a/tests/test_facades.py b/tests/test_facades.py index 660872af..af5e37f4 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -86,20 +86,21 @@ def test_bev_v2g_dispatch(self): label="BEV-V2G", v2g=True, electricity_bus=el_bus, - mobility_bus=indiv_mob, + commodity_bus=indiv_mob, storage_capacity=200, loss_rate=0, # self discharge of storage charging_power=200, availability=[1, 1, 1], # Vehicle availability at charger # min_storage_level=[0.1, 0.2, 0.15, 0.15], # max_storage_level=[0.9, 0.95, 0.92, 0.92], - efficiency_charging=1, - pkm_conversion_rate=1, # Energy to pkm + # efficiency_charging=1, + commodity_conversion_rate=1, # Energy to pkm efficiency_mob_electrical=1, # Vehicle efficiency per 100km efficiency_mob_g2v=1, # Charger efficiency efficiency_mob_v2g=1, # V2G charger efficiency efficiency_sto_in=1, # Storage charging efficiency efficiency_sto_out=1, # Storage discharging efficiency + variable_costs=10, # Charging costs ) self.energysystem.add(bev_v2g) From 976e2184b1bc86ef9ea4164235a0cf46c10b6d46 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Thu, 9 Nov 2023 18:02:49 +0100 Subject: [PATCH 47/88] Remove bev-flag from _investment --- src/oemof/tabular/_facade.py | 88 +++++++++++++++--------------------- 1 file changed, 37 insertions(+), 51 deletions(-) diff --git a/src/oemof/tabular/_facade.py b/src/oemof/tabular/_facade.py index 2c1c50f3..67ad7619 100644 --- a/src/oemof/tabular/_facade.py +++ b/src/oemof/tabular/_facade.py @@ -178,67 +178,53 @@ def _investment(self, bev=False): self.investment = None return self.investment - if bev: - self.investment = Investment( - ep_costs=0, - # ep_costs=self.bev_capacity_cost, - 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=getattr(self, "fixed_costs", None), + if self.capacity_cost is None: + msg = ( + "If you set `expandable`to True you need to set " + "attribute `capacity_cost` of component {}!" ) - else: - if self.capacity_cost is None: - msg = ( - "If you set `expandable`to True you need to set " - "attribute `capacity_cost` of component {}!" + raise ValueError(msg.format(self.label)) + + # If storage component + if isinstance(self, GenericStorage): + # If invest costs/MWH are given + if self.storage_capacity_cost is not None: + self.investment = Investment( + ep_costs=self.storage_capacity_cost, + 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=getattr(self, "fixed_costs", None), ) - raise ValueError(msg.format(self.label)) - - # If storage component - if isinstance(self, GenericStorage): - # If invest costs/MWH are given - if self.storage_capacity_cost is not None: - self.investment = Investment( - ep_costs=self.storage_capacity_cost, - 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=getattr(self, "fixed_costs", None), - ) - # If invest costs/MWh are not given - else: - self.investment = Investment( - 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=getattr(self, "fixed_costs", None), - ) - # If other component than storage or Bev + # If invest costs/MWh are not given else: self.investment = Investment( - ep_costs=self.capacity_cost, maximum=self._get_maximum_additional_invest( - "capacity_potential", "capacity" + "storage_capacity_potential", "storage_capacity" ), - minimum=getattr(self, "capacity_minimum", 0), - existing=getattr(self, "capacity", 0), + 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=getattr(self, "fixed_costs", None), ) + # If other component than storage or Bev + else: + self.investment = Investment( + ep_costs=self.capacity_cost, + maximum=self._get_maximum_additional_invest( + "capacity_potential", "capacity" + ), + minimum=getattr(self, "capacity_minimum", 0), + existing=getattr(self, "capacity", 0), + lifetime=getattr(self, "lifetime", None), + age=getattr(self, "age", 0), + fixed_costs=getattr(self, "fixed_costs", None), + ) return self.investment def _get_maximum_additional_invest(self, attr_potential, attr_existing): From 72e477882aa3e19a0093646041e9ac3d1b6d83c2 Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Mon, 13 Nov 2023 14:53:51 +0100 Subject: [PATCH 48/88] Move conversion_factors to output flows --- .../facades/experimental/battery_electric_vehicle.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index 7801178c..e1d371b3 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -316,7 +316,9 @@ def build_solph_components(self): ) }, # Includes storage charging efficiencies - conversion_factors={internal_bus: self.efficiency_mob_v2g}, + conversion_factors={ + self.electricity_bus: (self.efficiency_mob_v2g) + }, # TODO check efficiencies ) subnodes.append(vehicle_to_grid) @@ -343,7 +345,7 @@ def build_solph_components(self): ) }, conversion_factors={ - self.bus: self.commodity_conversion_rate + self.commodity_bus: self.commodity_conversion_rate * self.efficiency_mob_electrical # * 100 # TODO pro 100 km? }, From 873da6c80a249c6c1d4fd75e1f6e5861f02d9538 Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Mon, 13 Nov 2023 15:07:02 +0100 Subject: [PATCH 49/88] Adapt basic dispatch test for bev_v2g. make flake8 pass --- tests/test_facades.py | 51 ++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index af5e37f4..60d864bd 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -4,15 +4,17 @@ from oemof.solph import helpers from oemof import solph -from oemof.tabular.constraint_facades import BevEqualInvest, BevShareMob -from oemof.tabular.facades import Bev, Excess, Load, Shortage, Volatile -from oemof.tabular.postprocessing import calculations + +# from oemof.tabular.constraint_facades import BevEqualInvest, BevShareMob +from oemof.tabular.facades import Bev, Load, Volatile # , Shortage, Excess + +# from oemof.tabular.postprocessing import calculations class TestFacades: @classmethod def setup_class(cls): - cls.date_time_index = pd.date_range("1/1/2020", periods=3, freq="H") + cls.date_time_index = pd.date_range("1/1/2020", periods=4, freq="H") cls.tmpdir = helpers.extend_basic_path("tmp") logging.info(cls.tmpdir) @@ -56,8 +58,9 @@ def test_bev_v2g_dispatch(self): bus=el_bus, carrier="wind", tech="onshore", - capacity=100, - profile=[1, 0, 0], + capacity=495.36, + profile=[1, 0, 0, 1 / 3], + variable_costs=10, ) self.energysystem.add(volatile) @@ -65,8 +68,8 @@ def test_bev_v2g_dispatch(self): label="load", carrier="electricity", bus=el_bus, - amount=50, - profile=[0, 1, 0], + amount=100, + profile=[0, 1, 0, 0.1], ) self.energysystem.add(load) @@ -75,8 +78,8 @@ def test_bev_v2g_dispatch(self): type="Load", carrier="pkm", bus=indiv_mob, - amount=50, # PKM - profile=[0, 0, 1], # drive consumption + amount=100, # PKM + profile=[0, 0, 1, 0.5], # drive consumption ) self.energysystem.add(pkm_demand) @@ -87,19 +90,19 @@ def test_bev_v2g_dispatch(self): v2g=True, electricity_bus=el_bus, commodity_bus=indiv_mob, - storage_capacity=200, + storage_capacity=500, loss_rate=0, # self discharge of storage - charging_power=200, - availability=[1, 1, 1], # Vehicle availability at charger + charging_power=500, + availability=[1, 1, 1, 1], # Vehicle availability at charger # min_storage_level=[0.1, 0.2, 0.15, 0.15], # max_storage_level=[0.9, 0.95, 0.92, 0.92], # efficiency_charging=1, - commodity_conversion_rate=1, # Energy to pkm - efficiency_mob_electrical=1, # Vehicle efficiency per 100km - efficiency_mob_g2v=1, # Charger efficiency - efficiency_mob_v2g=1, # V2G charger efficiency - efficiency_sto_in=1, # Storage charging efficiency - efficiency_sto_out=1, # Storage discharging efficiency + commodity_conversion_rate=5 / 6, # Energy to pkm + efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km + efficiency_mob_v2g=5 / 6, # V2G charger efficiency + efficiency_mob_g2v=5 / 6, # Charger efficiency + efficiency_sto_in=5 / 6, # Storage charging efficiency + efficiency_sto_out=5 / 6, # Storage discharging efficiency, variable_costs=10, # Charging costs ) @@ -145,6 +148,10 @@ def test_bev_v2g_dispatch(self): # Check Storage level cn = "BEV-V2G-storage->None" assert self.results[cn]["sequences"]["storage_content"].iloc[0] == 0 - assert self.results[cn]["sequences"]["storage_content"].iloc[1] == 100 - assert self.results[cn]["sequences"]["storage_content"].iloc[2] == 50 - assert self.results[cn]["sequences"]["storage_content"].iloc[3] == 0 + assert self.results[cn]["sequences"]["storage_content"].iloc[1] == 344 + assert self.results[cn]["sequences"]["storage_content"].iloc[2] == 200 + assert self.results[cn]["sequences"]["storage_content"].iloc[3] == 27.2 + assert ( + self.results[cn]["sequences"]["storage_content"].iloc[4] + == 48.522222 + ) From e6dfe630e55c8722b56c32489ec6d61acc67c6b6 Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Tue, 14 Nov 2023 13:23:09 +0100 Subject: [PATCH 50/88] Adapt bev facade v2g tests --- tests/test_facades.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index 60d864bd..aa943002 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -58,8 +58,8 @@ def test_bev_v2g_dispatch(self): bus=el_bus, carrier="wind", tech="onshore", - capacity=495.36, - profile=[1, 0, 0, 1 / 3], + capacity=725.76, + profile=[1, 0, 0, 0], variable_costs=10, ) self.energysystem.add(volatile) @@ -69,7 +69,7 @@ def test_bev_v2g_dispatch(self): carrier="electricity", bus=el_bus, amount=100, - profile=[0, 1, 0, 0.1], + profile=[0, 0.1, 0, 1], ) self.energysystem.add(load) @@ -79,7 +79,7 @@ def test_bev_v2g_dispatch(self): carrier="pkm", bus=indiv_mob, amount=100, # PKM - profile=[0, 0, 1, 0.5], # drive consumption + profile=[0.5, 0.5, 1, 0], # drive consumption ) self.energysystem.add(pkm_demand) @@ -90,11 +90,13 @@ def test_bev_v2g_dispatch(self): v2g=True, electricity_bus=el_bus, commodity_bus=indiv_mob, - storage_capacity=500, + storage_capacity=800, loss_rate=0, # self discharge of storage - charging_power=500, + charging_power=800, + balanced=True, + initial_storage_level=0, availability=[1, 1, 1, 1], # Vehicle availability at charger - # min_storage_level=[0.1, 0.2, 0.15, 0.15], + # min_storage_level=[0.0, 0.2, 0.15, 0.0], # max_storage_level=[0.9, 0.95, 0.92, 0.92], # efficiency_charging=1, commodity_conversion_rate=5 / 6, # Energy to pkm @@ -148,10 +150,11 @@ def test_bev_v2g_dispatch(self): # Check Storage level cn = "BEV-V2G-storage->None" assert self.results[cn]["sequences"]["storage_content"].iloc[0] == 0 - assert self.results[cn]["sequences"]["storage_content"].iloc[1] == 344 - assert self.results[cn]["sequences"]["storage_content"].iloc[2] == 200 - assert self.results[cn]["sequences"]["storage_content"].iloc[3] == 27.2 assert ( - self.results[cn]["sequences"]["storage_content"].iloc[4] - == 48.522222 + self.results[cn]["sequences"]["storage_content"].iloc[1] == 417.6 ) + assert ( + self.results[cn]["sequences"]["storage_content"].iloc[2] == 316.8 + ) + assert self.results[cn]["sequences"]["storage_content"].iloc[3] == 144 + assert self.results[cn]["sequences"]["storage_content"].iloc[4] == 0 From afbd8fdaf00429c79d90cf579b42b71c2d2e3ffa Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Tue, 14 Nov 2023 13:23:30 +0100 Subject: [PATCH 51/88] Remove unnecessary code and solved todos --- .../experimental/battery_electric_vehicle.py | 2 -- tests/test_facades.py | 28 ------------------- 2 files changed, 30 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index e1d371b3..1a939898 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -319,7 +319,6 @@ def build_solph_components(self): conversion_factors={ self.electricity_bus: (self.efficiency_mob_v2g) }, - # TODO check efficiencies ) subnodes.append(vehicle_to_grid) @@ -437,7 +436,6 @@ def build_solph_components(self): **self.output_parameters, ) - # TODO check conversion factors self.inflow_conversion_factor = solph_sequence( self.efficiency_mob_g2v * self.efficiency_sto_in ) diff --git a/tests/test_facades.py b/tests/test_facades.py index aa943002..bb195a80 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -110,38 +110,10 @@ def test_bev_v2g_dispatch(self): self.energysystem.add(bev_v2g) - # excess = Excess( - # type="excess", - # label="excess", - # bus=el_bus, - # carrier="electricity", - # tech="excess", - # capacity=1e6, - # marginal_cost=1e6, - # ) - # self.energysystem.add(excess) - # - # shortage = Shortage( - # type="shortage", - # label="shortage", - # bus=el_bus, - # carrier="electricity", - # tech="shortage", - # capacity=1e6, - # marginal_cost=1e6, - # ) - # self.energysystem.add(shortage) - self.get_om() solver_stats = self.solve_om() - # TODO check why this is not working - # self.energysystem.params = solph.processing.parameter_as_dict( - # self.energysystem) - # postprocessed_results = calculations.run_postprocessing( - # self.energysystem) - # rename results to make them accessible self.rename_results() From 2e559c81aadc5306b53dcc30a9f905527f70f77a Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Tue, 14 Nov 2023 13:39:30 +0100 Subject: [PATCH 52/88] Add docstring describing test_bev_v2g_dispatch --- tests/test_facades.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_facades.py b/tests/test_facades.py index bb195a80..f959feca 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -45,6 +45,24 @@ def rename_results(self): self.results[new_key] = self.results.pop(old_key) def test_bev_v2g_dispatch(self): + """ + Tests v2g bev facade in dispatch mode. + + The following energy quantities are used: + volatile +725.76 0 0 0 + load 0 -10 0 -100 + pkm_demand -50 -50 -100 0 + V2g storage 417.6 316.8 144 0 + + The following efficiencies are taken into consideration: + volatile --> v2g_storage: efficiency_sto_in * efficiency_mob_g2v + storage --> load: efficiency_sto_out * efficiency_mob_v2g + storage --> pkm_demand: + efficiency_sto_out * efficiency_mob_electrical * + commodity_conversion_rate + + todo, optional: show as table + """ el_bus = solph.Bus("el-bus") el_bus.type = "bus" self.energysystem.add(el_bus) From 6af1fa284333c32f380b6428eb47589a063cf23b Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Tue, 14 Nov 2023 14:11:56 +0100 Subject: [PATCH 53/88] Rename initial storage level as named in BEV class: initial_storage_capacity --- tests/test_facades.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index f959feca..d7b2b85d 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -112,7 +112,7 @@ def test_bev_v2g_dispatch(self): loss_rate=0, # self discharge of storage charging_power=800, balanced=True, - initial_storage_level=0, + initial_storage_capacity=0, availability=[1, 1, 1, 1], # Vehicle availability at charger # min_storage_level=[0.0, 0.2, 0.15, 0.0], # max_storage_level=[0.9, 0.95, 0.92, 0.92], From 03cbb5a9aca3d9dd623874acff7150802130c38d Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Tue, 14 Nov 2023 17:26:53 +0100 Subject: [PATCH 54/88] Uncomment availability in commodity converter and storage --- .../tabular/facades/experimental/battery_electric_vehicle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index 1a939898..66f900f8 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -337,7 +337,7 @@ def build_solph_components(self): outputs={ self.commodity_bus: Flow( nominal_value=self._nominal_value(self.charging_power), - max=self.availability, + # max=self.availability, variable_costs=None, # investment=self._investment(bev=True), investment=self._converter_investment(), @@ -426,7 +426,7 @@ def build_solph_components(self): else: flow_in = Flow( nominal_value=self._nominal_value(self.charging_power), - max=self.availability, + # max=self.availability, **self.input_parameters, ) flow_out = Flow( From 0a0c0cf9b73aef3b267f9b261c52df5e798971b0 Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Tue, 14 Nov 2023 17:27:16 +0100 Subject: [PATCH 55/88] Add todo and correction --- .../tabular/facades/experimental/battery_electric_vehicle.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index 66f900f8..b34b0f11 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -27,7 +27,7 @@ class Bev(GenericStorage, Facade): Charging and discharging capacity is assumed to be equal. Multiple fleets can be modelled and connected to a common bus - (mobility_bus) to apply one demand for all modelled fleets. + (commodity_bus) to apply one demand for all modelled fleets. Parameters ---------- @@ -126,6 +126,7 @@ class Bev(GenericStorage, Facade): The vehicle fleet is modelled as a storage together with an internal sink with fixed flow: + todo check formula .. math:: x^{level}(t) = From 0ded9ccdfa9cc7c914d0ffb337a4ba81d704a062 Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Tue, 14 Nov 2023 17:28:27 +0100 Subject: [PATCH 56/88] Add test case for v2g, inflex, g2v trio - note: adaptions needed --- tests/test_facades.py | 164 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/tests/test_facades.py b/tests/test_facades.py index d7b2b85d..dc8c2eb8 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -148,3 +148,167 @@ def test_bev_v2g_dispatch(self): ) assert self.results[cn]["sequences"]["storage_content"].iloc[3] == 144 assert self.results[cn]["sequences"]["storage_content"].iloc[4] == 0 + + def test_bev_trio_dispatch(self): + """ + Tests linked v2g, g2v and inflex bev facades in dispatch mode. + """ + el_bus = solph.Bus("el-bus") + el_bus.type = "bus" + self.energysystem.add(el_bus) + + indiv_mob = solph.Bus("pkm-bus") + indiv_mob.type = "bus" + self.energysystem.add(indiv_mob) + + volatile = Volatile( + label="wind", + bus=el_bus, + carrier="wind", + tech="onshore", + capacity=882.432, + profile=[1, 0, 0, 0], + variable_costs=10, + ) + self.energysystem.add(volatile) + + load = Load( + label="load", + carrier="electricity", + bus=el_bus, + amount=100, + profile=[0, 1, 0, 1], + ) + self.energysystem.add(load) + + pkm_demand = Load( + label="pkm_demand", + type="Load", + carrier="pkm", + bus=indiv_mob, + amount=100, # PKM + profile=[1, 1, 1, 0], # drive consumption + ) + self.energysystem.add(pkm_demand) + + bev_v2g = Bev( + type="bev", + label="BEV-V2G", + v2g=True, + electricity_bus=el_bus, + commodity_bus=indiv_mob, + storage_capacity=300, + loss_rate=0, # self discharge of storage + charging_power=300, + balanced=True, + expandable=False, + initial_storage_capacity=0, + availability=[1, 1, 1, 1], # Vehicle availability at charger + # min_storage_level=[0.0, 0.2, 0.15, 0.0], + # max_storage_level=[0.9, 0.95, 0.92, 0.92], + # efficiency_charging=1, + commodity_conversion_rate=5 / 6, # Energy to pkm + efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km + efficiency_mob_v2g=5 / 6, # V2G charger efficiency + efficiency_mob_g2v=5 / 6, # Charger efficiency + efficiency_sto_in=5 / 6, # Storage charging efficiency + efficiency_sto_out=5 / 6, # Storage discharging efficiency, + variable_costs=10, # Charging costs + ) + self.energysystem.add(bev_v2g) + + bev_inflex = Bev( + type="bev", + label="BEV-inflex", + electricity_bus=el_bus, + commodity_bus=indiv_mob, + storage_capacity=172.8, + loss_rate=0, # self discharge of storage + charging_power=172.8, + # drive_power=100, # total driving capacity of the fleet + availability=[1, 1, 1, 1], + v2g=False, + # min_storage_level=[0.1, 0.2, 0.15, 0.15], + # max_storage_level=[0.9, 0.95, 0.92, 0.92], + balanced=True, + expandable=False, + input_parameters={ + "fix": [1, 0, 0, 0] + }, # fixed relative charging profile + output_parameters={ + "fix": [0, 0.5, 0.5, 0] + }, # fixed relative discharging profile + commodity_conversion_rate=5 / 6, # Energy to pkm + efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km + efficiency_mob_g2v=5 / 6, # Charger efficiency + efficiency_sto_in=5 / 6, # Storage charging efficiency + efficiency_sto_out=5 / 6, # Storage discharging efficiency, + variable_costs=10, + ) + self.energysystem.add(bev_inflex) + + bev_g2v = Bev( + type="bev", + label="BEV-G2V", + electricity_bus=el_bus, + commodity_bus=indiv_mob, + storage_capacity=172.8, + loss_rate=0, # self discharge of storage + charging_power=172.8, + # drive_power=100, + # drive_consumption=[0, 1, 0, 0], + availability=[1, 1, 1, 1], + v2g=False, + # min_storage_level=[0.1, 0.2, 0.15, 0.15], + # max_storage_level=[0.9, 0.95, 0.92, 0.92], + balanced=True, + expandable=False, + commodity_conversion_rate=5 / 6, # Energy to pkm + efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km + efficiency_mob_g2v=5 / 6, # Charger efficiency + efficiency_sto_in=5 / 6, # Storage charging efficiency + efficiency_sto_out=5 / 6, # Storage discharging efficiency, + variable_costs=8, + ) + self.energysystem.add(bev_g2v) + + self.get_om() + + solver_stats = self.solve_om() + + # rename results to make them accessible + self.rename_results() + + assert solver_stats["Solver"][0]["Status"] == "ok" + + # Check Storage level + cn = "BEV-V2G-storage->None" + assert self.results[cn]["sequences"]["storage_content"].iloc[0] == 0 + assert self.results[cn]["sequences"]["storage_content"].iloc[1] == 288 + assert self.results[cn]["sequences"]["storage_content"].iloc[2] == 144 + assert self.results[cn]["sequences"]["storage_content"].iloc[3] == 144 + assert self.results[cn]["sequences"]["storage_content"].iloc[4] == 0 + + cn2 = "BEV-inflex-storage->None" + assert self.results[cn2]["sequences"]["storage_content"].iloc[0] == 0 + assert ( + self.results[cn2]["sequences"]["storage_content"].iloc[1] == 172.8 + ) + assert ( + self.results[cn2]["sequences"]["storage_content"].iloc[2] == 86.4 + ) + assert self.results[cn2]["sequences"]["storage_content"].iloc[3] == 0 + assert self.results[cn2]["sequences"]["storage_content"].iloc[4] == 0 + + cn3 = "BEV-G2V-storage->None" + assert self.results[cn3]["sequences"]["storage_content"].iloc[0] == 0 + assert ( + self.results[cn3]["sequences"]["storage_content"].iloc[1] == 417.6 + ) + assert ( + self.results[cn3]["sequences"]["storage_content"].iloc[2] == 172.8 + ) + assert ( + self.results[cn3]["sequences"]["storage_content"].iloc[3] == 86.4 + ) + assert self.results[cn3]["sequences"]["storage_content"].iloc[4] == 0 From 1e6b39b1052330435007e9b507921cc23fbc2c8f Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Tue, 14 Nov 2023 17:28:57 +0100 Subject: [PATCH 57/88] Add test case for bev inflex facade - note: adaptions needed --- tests/test_facades.py | 94 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/tests/test_facades.py b/tests/test_facades.py index dc8c2eb8..f128d68e 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -112,6 +112,7 @@ def test_bev_v2g_dispatch(self): loss_rate=0, # self discharge of storage charging_power=800, balanced=True, + expandable=False, initial_storage_capacity=0, availability=[1, 1, 1, 1], # Vehicle availability at charger # min_storage_level=[0.0, 0.2, 0.15, 0.0], @@ -149,6 +150,99 @@ def test_bev_v2g_dispatch(self): assert self.results[cn]["sequences"]["storage_content"].iloc[3] == 144 assert self.results[cn]["sequences"]["storage_content"].iloc[4] == 0 + def test_bev_inflex_dispatch(self): + """ + Tests linked v2g, g2v and inflex bev facades in dispatch mode. + """ + el_bus = solph.Bus("el-bus") + el_bus.type = "bus" + self.energysystem.add(el_bus) + + indiv_mob = solph.Bus("pkm-bus") + indiv_mob.type = "bus" + self.energysystem.add(indiv_mob) + + volatile = Volatile( + label="wind", + bus=el_bus, + carrier="wind", + tech="onshore", + capacity=545.6, + profile=[1, 0, 0, 0], + variable_costs=10, + ) + self.energysystem.add(volatile) + + load = Load( + label="load", + carrier="electricity", + bus=el_bus, + amount=100, + profile=[1, 0, 0, 0], + ) + self.energysystem.add(load) + + pkm_demand = Load( + label="pkm_demand", + type="Load", + carrier="pkm", + bus=indiv_mob, + amount=100, # PKM + profile=[1, 0.5, 1, 0.5], # drive consumption + ) + self.energysystem.add(pkm_demand) + + bev_inflex = Bev( + type="bev", + label="BEV-inflex", + electricity_bus=el_bus, + commodity_bus=indiv_mob, + storage_capacity=345.6, + loss_rate=0, # self discharge of storage + charging_power=345.6, + # drive_power=100, # total driving capacity of the fleet + availability=[1, 1, 1, 1], + v2g=False, + # min_storage_level=[0.1, 0.2, 0.15, 0.15], + # max_storage_level=[0.9, 0.95, 0.92, 0.92], + balanced=True, + expandable=False, + input_parameters={ + "fix": [1, 0, 0, 0] + }, # fixed relative charging profile + # output_parameters={ + # "fix": [0.25, 0.5, 0.25, 0] + # }, # fixed relative discharging profile + commodity_conversion_rate=5 / 6, # Energy to pkm + efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km + efficiency_mob_g2v=5 / 6, # Charger efficiency + efficiency_sto_in=5 / 6, # Storage charging efficiency + efficiency_sto_out=5 / 6, # Storage discharging efficiency, + variable_costs=8, + ) + self.energysystem.add(bev_inflex) + + self.get_om() + + solver_stats = self.solve_om() + + # rename results to make them accessible + self.rename_results() + + assert solver_stats["Solver"][0]["Status"] == "ok" + + # Check Storage level + cn = "BEV-inflex-storage->None" + assert self.results[cn]["sequences"]["storage_content"].iloc[0] == 0 + assert ( + self.results[cn]["sequences"]["storage_content"].iloc[1] == 345.6 + ) + assert ( + self.results[cn]["sequences"]["storage_content"].iloc[2] == 259.2 + ) + assert self.results[cn]["sequences"]["storage_content"].iloc[3] == 86.4 + assert self.results[cn]["sequences"]["storage_content"].iloc[4] == 0 + def test_bev_trio_dispatch(self): """ Tests linked v2g, g2v and inflex bev facades in dispatch mode. From 3ac45c2ca0322a024818d85c199b72ad48ff3fb9 Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Thu, 16 Nov 2023 13:43:37 +0100 Subject: [PATCH 58/88] Adapt bev inflex test and add description --- tests/test_facades.py | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index f128d68e..b0b01fc4 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -152,7 +152,22 @@ def test_bev_v2g_dispatch(self): def test_bev_inflex_dispatch(self): """ - Tests linked v2g, g2v and inflex bev facades in dispatch mode. + Tests inflex bev facade in dispatch mode. + + The following energy quantities are used: + volatile +908.704 0 0 0 + load -100 0 0 0 + pkm_demand -100 -50 -100 -75 + V2g storage 388.8 302.4 129.6 0 + + The following efficiencies are taken into consideration: + volatile --> v2g_storage: efficiency_sto_in * efficiency_mob_g2v + storage --> load: efficiency_sto_out * efficiency_mob_v2g + storage --> pkm_demand: + efficiency_sto_out * efficiency_mob_electrical * + commodity_conversion_rate + + todo, optional: show as table """ el_bus = solph.Bus("el-bus") el_bus.type = "bus" @@ -167,7 +182,7 @@ def test_bev_inflex_dispatch(self): bus=el_bus, carrier="wind", tech="onshore", - capacity=545.6, + capacity=908.704, profile=[1, 0, 0, 0], variable_costs=10, ) @@ -188,7 +203,7 @@ def test_bev_inflex_dispatch(self): carrier="pkm", bus=indiv_mob, amount=100, # PKM - profile=[1, 0.5, 1, 0.5], # drive consumption + profile=[1, 0.5, 1, 0.75], # drive consumption ) self.energysystem.add(pkm_demand) @@ -197,22 +212,23 @@ def test_bev_inflex_dispatch(self): label="BEV-inflex", electricity_bus=el_bus, commodity_bus=indiv_mob, - storage_capacity=345.6, + storage_capacity=900, loss_rate=0, # self discharge of storage - charging_power=345.6, + charging_power=900, # drive_power=100, # total driving capacity of the fleet availability=[1, 1, 1, 1], v2g=False, # min_storage_level=[0.1, 0.2, 0.15, 0.15], # max_storage_level=[0.9, 0.95, 0.92, 0.92], balanced=True, + initial_storage_capacity=0, expandable=False, input_parameters={ - "fix": [1, 0, 0, 0] + "fix": [0.89856, 0, 0, 0] }, # fixed relative charging profile - # output_parameters={ - # "fix": [0.25, 0.5, 0.25, 0] - # }, # fixed relative discharging profile + output_parameters={ + "fix": [0.16, 0.08, 0.16, 0.12] + }, # fixed relative discharging profile commodity_conversion_rate=5 / 6, # Energy to pkm efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km efficiency_mob_g2v=5 / 6, # Charger efficiency @@ -235,12 +251,14 @@ def test_bev_inflex_dispatch(self): cn = "BEV-inflex-storage->None" assert self.results[cn]["sequences"]["storage_content"].iloc[0] == 0 assert ( - self.results[cn]["sequences"]["storage_content"].iloc[1] == 345.6 + self.results[cn]["sequences"]["storage_content"].iloc[1] == 388.8 + ) + assert ( + self.results[cn]["sequences"]["storage_content"].iloc[2] == 302.4 ) assert ( - self.results[cn]["sequences"]["storage_content"].iloc[2] == 259.2 + self.results[cn]["sequences"]["storage_content"].iloc[3] == 129.6 ) - assert self.results[cn]["sequences"]["storage_content"].iloc[3] == 86.4 assert self.results[cn]["sequences"]["storage_content"].iloc[4] == 0 def test_bev_trio_dispatch(self): From 32ea252d52902921d1cc4ae344471376da8204e3 Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Mon, 20 Nov 2023 15:38:30 +0100 Subject: [PATCH 59/88] Add bev g2v dispatch test --- tests/test_facades.py | 92 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/tests/test_facades.py b/tests/test_facades.py index b0b01fc4..c03d08c3 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -261,6 +261,98 @@ def test_bev_inflex_dispatch(self): ) assert self.results[cn]["sequences"]["storage_content"].iloc[4] == 0 + def test_bev_g2v_dispatch(self): + """ + Tests g2v bev facade in dispatch mode. + + The same quantities as in `test_bev_inflex_dispatch()` are used. + """ + el_bus = solph.Bus("el-bus") + el_bus.type = "bus" + self.energysystem.add(el_bus) + + indiv_mob = solph.Bus("pkm-bus") + indiv_mob.type = "bus" + self.energysystem.add(indiv_mob) + + volatile = Volatile( + label="wind", + bus=el_bus, + carrier="wind", + tech="onshore", + capacity=908.704, + profile=[1, 0, 0, 0], + variable_costs=10, + ) + self.energysystem.add(volatile) + + load = Load( + label="load", + carrier="electricity", + bus=el_bus, + amount=100, + profile=[1, 0, 0, 0], + ) + self.energysystem.add(load) + + pkm_demand = Load( + label="pkm_demand", + type="Load", + carrier="pkm", + bus=indiv_mob, + amount=100, # PKM + profile=[1, 0.5, 1, 0.75], # drive consumption + ) + self.energysystem.add(pkm_demand) + + bev_g2v = Bev( + type="bev", + label="BEV-G2V", + electricity_bus=el_bus, + commodity_bus=indiv_mob, + storage_capacity=900, + loss_rate=0, # self discharge of storage + charging_power=900, + # drive_power=100, # total driving capacity of the fleet + availability=[1, 1, 1, 1], + v2g=False, + # min_storage_level=[0.1, 0.2, 0.15, 0.15], + # max_storage_level=[0.9, 0.95, 0.92, 0.92], + balanced=True, + initial_storage_capacity=0, + expandable=False, + commodity_conversion_rate=5 / 6, # Energy to pkm + efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km + efficiency_mob_g2v=5 / 6, # Charger efficiency + efficiency_sto_in=5 / 6, # Storage charging efficiency + efficiency_sto_out=5 / 6, # Storage discharging efficiency, + variable_costs=8, + ) + self.energysystem.add(bev_g2v) + + self.get_om() + + solver_stats = self.solve_om() + + # rename results to make them accessible + self.rename_results() + + assert solver_stats["Solver"][0]["Status"] == "ok" + + # Check Storage level + cn = "BEV-G2V-storage->None" + assert self.results[cn]["sequences"]["storage_content"].iloc[0] == 0 + assert ( + self.results[cn]["sequences"]["storage_content"].iloc[1] == 388.8 + ) + assert ( + self.results[cn]["sequences"]["storage_content"].iloc[2] == 302.4 + ) + assert ( + self.results[cn]["sequences"]["storage_content"].iloc[3] == 129.6 + ) + assert self.results[cn]["sequences"]["storage_content"].iloc[4] == 0 + def test_bev_trio_dispatch(self): """ Tests linked v2g, g2v and inflex bev facades in dispatch mode. From 9cc74c28cbd2832615564834d430dce1640b6e21 Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Mon, 20 Nov 2023 15:39:02 +0100 Subject: [PATCH 60/88] Adapt bev trio (v2g, g2v, inflex) test --- tests/test_facades.py | 62 +++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index c03d08c3..7fc0600f 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -370,7 +370,7 @@ def test_bev_trio_dispatch(self): bus=el_bus, carrier="wind", tech="onshore", - capacity=882.432, + capacity=2543.168, profile=[1, 0, 0, 0], variable_costs=10, ) @@ -380,8 +380,8 @@ def test_bev_trio_dispatch(self): label="load", carrier="electricity", bus=el_bus, - amount=100, - profile=[0, 1, 0, 1], + amount=200, + profile=[1, 0.05, 0, 0.5], ) self.energysystem.add(load) @@ -390,8 +390,8 @@ def test_bev_trio_dispatch(self): type="Load", carrier="pkm", bus=indiv_mob, - amount=100, # PKM - profile=[1, 1, 1, 0], # drive consumption + amount=250, # PKM + profile=[1, 0.6, 1.2, 0.6], # drive consumption ) self.energysystem.add(pkm_demand) @@ -401,9 +401,9 @@ def test_bev_trio_dispatch(self): v2g=True, electricity_bus=el_bus, commodity_bus=indiv_mob, - storage_capacity=300, + storage_capacity=800, loss_rate=0, # self discharge of storage - charging_power=300, + charging_power=800, balanced=True, expandable=False, initial_storage_capacity=0, @@ -426,28 +426,29 @@ def test_bev_trio_dispatch(self): label="BEV-inflex", electricity_bus=el_bus, commodity_bus=indiv_mob, - storage_capacity=172.8, + storage_capacity=900, loss_rate=0, # self discharge of storage - charging_power=172.8, + charging_power=900, # drive_power=100, # total driving capacity of the fleet availability=[1, 1, 1, 1], v2g=False, # min_storage_level=[0.1, 0.2, 0.15, 0.15], # max_storage_level=[0.9, 0.95, 0.92, 0.92], balanced=True, + initial_storage_capacity=0, expandable=False, input_parameters={ - "fix": [1, 0, 0, 0] + "fix": [0.89856, 0, 0, 0] }, # fixed relative charging profile output_parameters={ - "fix": [0, 0.5, 0.5, 0] + "fix": [0.16, 0.08, 0.16, 0.12] }, # fixed relative discharging profile commodity_conversion_rate=5 / 6, # Energy to pkm efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km efficiency_mob_g2v=5 / 6, # Charger efficiency efficiency_sto_in=5 / 6, # Storage charging efficiency efficiency_sto_out=5 / 6, # Storage discharging efficiency, - variable_costs=10, + variable_costs=20, # Charging costs ) self.energysystem.add(bev_inflex) @@ -456,23 +457,23 @@ def test_bev_trio_dispatch(self): label="BEV-G2V", electricity_bus=el_bus, commodity_bus=indiv_mob, - storage_capacity=172.8, + storage_capacity=808.704, loss_rate=0, # self discharge of storage - charging_power=172.8, - # drive_power=100, - # drive_consumption=[0, 1, 0, 0], + charging_power=808.704, + # drive_power=100, # total driving capacity of the fleet availability=[1, 1, 1, 1], v2g=False, # min_storage_level=[0.1, 0.2, 0.15, 0.15], # max_storage_level=[0.9, 0.95, 0.92, 0.92], balanced=True, + initial_storage_capacity=0, expandable=False, commodity_conversion_rate=5 / 6, # Energy to pkm efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km efficiency_mob_g2v=5 / 6, # Charger efficiency efficiency_sto_in=5 / 6, # Storage charging efficiency efficiency_sto_out=5 / 6, # Storage discharging efficiency, - variable_costs=8, + variable_costs=4, # Charging costs ) self.energysystem.add(bev_g2v) @@ -488,31 +489,28 @@ def test_bev_trio_dispatch(self): # Check Storage level cn = "BEV-V2G-storage->None" assert self.results[cn]["sequences"]["storage_content"].iloc[0] == 0 - assert self.results[cn]["sequences"]["storage_content"].iloc[1] == 288 - assert self.results[cn]["sequences"]["storage_content"].iloc[2] == 144 - assert self.results[cn]["sequences"]["storage_content"].iloc[3] == 144 assert self.results[cn]["sequences"]["storage_content"].iloc[4] == 0 cn2 = "BEV-inflex-storage->None" assert self.results[cn2]["sequences"]["storage_content"].iloc[0] == 0 assert ( - self.results[cn2]["sequences"]["storage_content"].iloc[1] == 172.8 + self.results[cn2]["sequences"]["storage_content"].iloc[1] == 388.8 + ) + assert ( + self.results[cn2]["sequences"]["storage_content"].iloc[2] == 302.4 ) assert ( - self.results[cn2]["sequences"]["storage_content"].iloc[2] == 86.4 + self.results[cn2]["sequences"]["storage_content"].iloc[3] == 129.6 ) - assert self.results[cn2]["sequences"]["storage_content"].iloc[3] == 0 assert self.results[cn2]["sequences"]["storage_content"].iloc[4] == 0 cn3 = "BEV-G2V-storage->None" assert self.results[cn3]["sequences"]["storage_content"].iloc[0] == 0 - assert ( - self.results[cn3]["sequences"]["storage_content"].iloc[1] == 417.6 - ) - assert ( - self.results[cn3]["sequences"]["storage_content"].iloc[2] == 172.8 - ) - assert ( - self.results[cn3]["sequences"]["storage_content"].iloc[3] == 86.4 - ) assert self.results[cn3]["sequences"]["storage_content"].iloc[4] == 0 + + # Check storage input flows + cn4 = "el-bus->BEV-V2G-storage" + assert self.results[cn4]["sequences"]["flow"].iloc[0] == 725.76 + + cn5 = "el-bus->BEV-G2V-storage" + assert self.results[cn5]["sequences"]["flow"].iloc[0] == 808.704 From 0f737d29cfdd5c1c20a0aad32182cecbcfecb6cd Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Mon, 20 Nov 2023 15:48:16 +0100 Subject: [PATCH 61/88] Add description to `test_bev_trio_dispatch()` --- tests/test_facades.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_facades.py b/tests/test_facades.py index 7fc0600f..c29d6676 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -356,6 +356,11 @@ def test_bev_g2v_dispatch(self): def test_bev_trio_dispatch(self): """ Tests linked v2g, g2v and inflex bev facades in dispatch mode. + + Energy quantities are taken from the single tests + (`test_bev_v2g_dispatch()`, `test_bev_inflex_dispatch()`, + `test_bev_g2v_dispatch()`) and summed up in this test. + """ el_bus = solph.Bus("el-bus") el_bus.type = "bus" From 1bd1569d695704064b243fb654f75081f3726cc5 Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Tue, 21 Nov 2023 14:58:27 +0100 Subject: [PATCH 62/88] Move variable_costs to input flow of storage also for expandable=False --- .../tabular/facades/experimental/battery_electric_vehicle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py index b34b0f11..ed677303 100644 --- a/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py +++ b/src/oemof/tabular/facades/experimental/battery_electric_vehicle.py @@ -428,12 +428,12 @@ def build_solph_components(self): flow_in = Flow( nominal_value=self._nominal_value(self.charging_power), # max=self.availability, + variable_costs=self.variable_costs, **self.input_parameters, ) flow_out = Flow( nominal_value=self._nominal_value(self.charging_power), # max=self.availability, - variable_costs=self.variable_costs, **self.output_parameters, ) From 68eaf44579004baf4061ffa03ff72fbd70353096 Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Tue, 21 Nov 2023 15:01:47 +0100 Subject: [PATCH 63/88] Add test class and first test case for BEV investment tests --- tests/test_facades.py | 120 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/tests/test_facades.py b/tests/test_facades.py index c29d6676..f8b5c605 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -519,3 +519,123 @@ def test_bev_trio_dispatch(self): cn5 = "el-bus->BEV-G2V-storage" assert self.results[cn5]["sequences"]["flow"].iloc[0] == 808.704 + + +class TestBevFacadesInvestment: + @classmethod + def setup_class(cls): + 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") + cls.date_time_index = pd.concat([t_idx_1_series, t_idx_2_series]).index + cls.periods = [t_idx_1, t_idx_2] + + cls.tmpdir = helpers.extend_basic_path("tmp") + logging.info(cls.tmpdir) + + @classmethod + def setup_method(cls): + cls.energysystem = solph.EnergySystem( + groupings=solph.GROUPINGS, + timeindex=cls.date_time_index, + infer_last_interval=False, + timeincrement=[1] * len(cls.date_time_index), + periods=cls.periods, + ) + + # todo: identical functions can be @fixtures or else (outside of classes) + + def get_om(self): + self.model = solph.Model( + self.energysystem, + timeindex=self.energysystem.timeindex, + ) + + def solve_om(self): + opt_result = self.model.solve("cbc", solve_kwargs={"tee": True}) + self.results = self.model.results() + return opt_result + + def rename_results(self): + rename_mapping = { + oemof_tuple: f"{oemof_tuple[0]}->{oemof_tuple[1]}" + for oemof_tuple in self.results.keys() + } + for old_key, new_key in rename_mapping.items(): + self.results[new_key] = self.results.pop(old_key) + + def test_bev_v2g_invest(self): + """ + Tests v2g bev facade in investment mode. + """ + el_bus = solph.Bus("el-bus") + el_bus.type = "bus" + self.energysystem.add(el_bus) + + indiv_mob = solph.Bus("pkm-bus") + indiv_mob.type = "bus" + self.energysystem.add(indiv_mob) + + volatile = Volatile( + label="wind", + bus=el_bus, + carrier="wind", + tech="onshore", + capacity=725.76, + # capacity_cost=1, + # expandable=True, + profile=len(self.periods) * [1, 0, 0], + lifetime=20, + variable_costs=10, + ) + self.energysystem.add(volatile) + + load = Load( + label="load", + carrier="electricity", + bus=el_bus, + amount=100, + profile=len(self.periods) * [1, 1, 1], + ) + self.energysystem.add(load) + + pkm_demand = Load( + label="pkm_demand", + type="Load", + carrier="pkm", + bus=indiv_mob, + amount=100, # PKM + profile=len(self.periods) * [0, 1, 0], # drive consumption + ) + self.energysystem.add(pkm_demand) + + bev_v2g = Bev( + type="bev", + label="BEV-V2G", + v2g=True, + electricity_bus=el_bus, + commodity_bus=indiv_mob, + storage_capacity=800, + loss_rate=0, # self discharge of storage + charging_power=800, + balanced=True, + expandable=True, + # initial_storage_capacity=0, + availability=len(self.periods) + * [1, 1, 1], # Vehicle availability at charger + commodity_conversion_rate=5 / 6, # Energy to pkm + efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km + efficiency_mob_v2g=5 / 6, # V2G charger efficiency + efficiency_mob_g2v=5 / 6, # Charger efficiency + efficiency_sto_in=5 / 6, # Storage charging efficiency + efficiency_sto_out=5 / 6, # Storage discharging efficiency, + variable_costs=10, # Charging costs + bev_invest_costs=2, + invest_c_rate=60 / 20, # Capacity/Power + fixed_investment_costs=1, + lifetime=10, + ) + self.energysystem.add(bev_v2g) + + # todo adapt test case and add assertments From 5bdc4d3302b8c0ec9eb339a45b33775e2cd858f6 Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Tue, 21 Nov 2023 15:02:28 +0100 Subject: [PATCH 64/88] Rename test class for bev dispatch tests to TestBevFacadesDispatch --- tests/test_facades.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index f8b5c605..f0ff77f0 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -11,7 +11,7 @@ # from oemof.tabular.postprocessing import calculations -class TestFacades: +class TestBevFacadesDispatch: @classmethod def setup_class(cls): cls.date_time_index = pd.date_range("1/1/2020", periods=4, freq="H") From f20244daf3127e5b0cb2f22eceb77162afc97b62 Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Tue, 21 Nov 2023 15:06:02 +0100 Subject: [PATCH 65/88] Add solving of test case and first assertion --- tests/test_facades.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index f0ff77f0..236dacdc 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -638,4 +638,13 @@ def test_bev_v2g_invest(self): ) self.energysystem.add(bev_v2g) - # todo adapt test case and add assertments + self.get_om() + + solver_stats = self.solve_om() + + # rename results to make them accessible + self.rename_results() + + assert solver_stats["Solver"][0]["Status"] == "ok" + + # todo adapt test case and add assertions From c22fc19c354c61b9a23ea3a990e00f76c0555e50 Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Tue, 21 Nov 2023 15:17:44 +0100 Subject: [PATCH 66/88] Add lines for constraints --- tests/test_facades.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_facades.py b/tests/test_facades.py index 236dacdc..8872cfd3 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -1,4 +1,5 @@ import logging +import os import pandas as pd from oemof.solph import helpers @@ -6,6 +7,7 @@ from oemof import solph # from oemof.tabular.constraint_facades import BevEqualInvest, BevShareMob +from oemof.tabular.datapackage.reading import deserialize_constraints from oemof.tabular.facades import Bev, Load, Volatile # , Shortage, Excess # from oemof.tabular.postprocessing import calculations @@ -640,6 +642,14 @@ def test_bev_v2g_invest(self): self.get_om() + datapackage_dir = "todo" + CONSTRAINT_TYPE_MAP = "todo" + deserialize_constraints( + model=self.model, + path=os.path.join(datapackage_dir, "datapackage.json"), + constraint_type_map=CONSTRAINT_TYPE_MAP, + ) + solver_stats = self.solve_om() # rename results to make them accessible From 246ee4b1d01018b3dc4eedf2b08c07d7ae54d077 Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Tue, 21 Nov 2023 15:20:16 +0100 Subject: [PATCH 67/88] Corrections --- tests/test_facades.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index 8872cfd3..a92e2244 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -5,6 +5,8 @@ from oemof.solph import helpers from oemof import solph +from oemof.tabular import __path__ as tabular_path +from oemof.tabular.constraint_facades import CONSTRAINT_TYPE_MAP # from oemof.tabular.constraint_facades import BevEqualInvest, BevShareMob from oemof.tabular.datapackage.reading import deserialize_constraints @@ -642,8 +644,9 @@ def test_bev_v2g_invest(self): self.get_om() - datapackage_dir = "todo" - CONSTRAINT_TYPE_MAP = "todo" + datapackage_dir = os.path.join( + tabular_path[0], "examples/own_examples/bev" + ) deserialize_constraints( model=self.model, path=os.path.join(datapackage_dir, "datapackage.json"), From 5380dc091ebc1831360b9c633082faf36cab2c1e Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Tue, 21 Nov 2023 23:22:51 +0100 Subject: [PATCH 68/88] Manually add bev constraints --- tests/test_facades.py | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index a92e2244..88313f88 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -1,15 +1,10 @@ import logging -import os import pandas as pd from oemof.solph import helpers from oemof import solph -from oemof.tabular import __path__ as tabular_path from oemof.tabular.constraint_facades import CONSTRAINT_TYPE_MAP - -# from oemof.tabular.constraint_facades import BevEqualInvest, BevShareMob -from oemof.tabular.datapackage.reading import deserialize_constraints from oemof.tabular.facades import Bev, Load, Volatile # , Shortage, Excess # from oemof.tabular.postprocessing import calculations @@ -644,14 +639,29 @@ def test_bev_v2g_invest(self): self.get_om() - datapackage_dir = os.path.join( - tabular_path[0], "examples/own_examples/bev" - ) - deserialize_constraints( - model=self.model, - path=os.path.join(datapackage_dir, "datapackage.json"), - constraint_type_map=CONSTRAINT_TYPE_MAP, - ) + for period in self.energysystem.periods: + year = period.year.min() + constraint = CONSTRAINT_TYPE_MAP["bev_equal_invest"] + constraint = constraint(name=None, type=None, year=year) + # build constraint for each facade & period + constraint.build_constraint(self.model) + + # This one is only for the bev trio + # ################################## + # for period in self.energysystem.periods: + # year = period.year.min() + # constraint = CONSTRAINT_TYPE_MAP["bev_share_mob"] + # constraint = constraint( + # name=None, + # type=None, + # label="BEV", + # year=year, + # share_mob_flex_G2V=0.3, + # share_mob_flex_V2G=0.2, + # share_mob_inflex=0.5, + # ) + # # build constraint for each facade & period + # constraint.build_constraint(self.model) solver_stats = self.solve_om() From fe2c39cec530a421f6ced2dc89828c210a763072 Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Thu, 23 Nov 2023 13:07:06 +0100 Subject: [PATCH 69/88] Working V2G BEV test with one time step, todos added --- tests/test_facades.py | 77 +++++++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index 88313f88..3e7979f5 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -523,8 +523,8 @@ def test_bev_trio_dispatch(self): class TestBevFacadesInvestment: @classmethod def setup_class(cls): - 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=1, freq="H") + t_idx_2 = pd.date_range("1/1/2030", periods=1, 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") cls.date_time_index = pd.concat([t_idx_1_series, t_idx_2_series]).index @@ -581,10 +581,10 @@ def test_bev_v2g_invest(self): bus=el_bus, carrier="wind", tech="onshore", - capacity=725.76, + capacity=248.832, # capacity_cost=1, # expandable=True, - profile=len(self.periods) * [1, 0, 0], + profile=len(self.periods) * [1], lifetime=20, variable_costs=10, ) @@ -594,8 +594,8 @@ def test_bev_v2g_invest(self): label="load", carrier="electricity", bus=el_bus, - amount=100, - profile=len(self.periods) * [1, 1, 1], + amount=0, + profile=len(self.periods) * [1], ) self.energysystem.add(load) @@ -605,7 +605,7 @@ def test_bev_v2g_invest(self): carrier="pkm", bus=indiv_mob, amount=100, # PKM - profile=len(self.periods) * [0, 1, 0], # drive consumption + profile=len(self.periods) * [1], # drive consumption ) self.energysystem.add(pkm_demand) @@ -615,14 +615,14 @@ def test_bev_v2g_invest(self): v2g=True, electricity_bus=el_bus, commodity_bus=indiv_mob, - storage_capacity=800, + storage_capacity=0, loss_rate=0, # self discharge of storage - charging_power=800, + charging_power=0, balanced=True, expandable=True, - # initial_storage_capacity=0, + initial_storage_capacity=0, availability=len(self.periods) - * [1, 1, 1], # Vehicle availability at charger + * [1], # Vehicle availability at charger commodity_conversion_rate=5 / 6, # Energy to pkm efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km efficiency_mob_v2g=5 / 6, # V2G charger efficiency @@ -646,23 +646,6 @@ def test_bev_v2g_invest(self): # build constraint for each facade & period constraint.build_constraint(self.model) - # This one is only for the bev trio - # ################################## - # for period in self.energysystem.periods: - # year = period.year.min() - # constraint = CONSTRAINT_TYPE_MAP["bev_share_mob"] - # constraint = constraint( - # name=None, - # type=None, - # label="BEV", - # year=year, - # share_mob_flex_G2V=0.3, - # share_mob_flex_V2G=0.2, - # share_mob_inflex=0.5, - # ) - # # build constraint for each facade & period - # constraint.build_constraint(self.model) - solver_stats = self.solve_om() # rename results to make them accessible @@ -670,4 +653,40 @@ def test_bev_v2g_invest(self): assert solver_stats["Solver"][0]["Status"] == "ok" - # todo adapt test case and add assertions + # Check storage level and invested storage capacity + cn = "BEV-V2G-storage->None" + # todo optional capacity as a variable + # todo 2020 2030 (infer_last_interval missing) + # todo add second time step to periods + assert self.results[cn]["sequences"]["storage_content"].iloc[0] == 0 + assert self.results[cn]["sequences"]["storage_content"].iloc[1] == 0 + assert self.results[cn]["period_scalars"]["invest"].iloc[0] == 746.496 + assert self.results[cn]["period_scalars"]["invest"].iloc[1] == 746.496 + + # Check invested v2g capacity + cn2 = "BEV-V2G-v2g->el-bus" + assert self.results[cn2]["period_scalars"]["invest"].iloc[0] == 248.832 + assert self.results[cn2]["period_scalars"]["invest"].iloc[1] == 248.832 + + # Check invested v2g capacity + cn2 = "BEV-V2G-2com->pkm-bus" + assert self.results[cn2]["period_scalars"]["invest"].iloc[0] == 248.832 + assert self.results[cn2]["period_scalars"]["invest"].iloc[1] == 248.832 + + +# This one is only for the bev trio +# ################################## +# for period in self.energysystem.periods: +# year = period.year.min() +# constraint = CONSTRAINT_TYPE_MAP["bev_share_mob"] +# constraint = constraint( +# name=None, +# type=None, +# label="BEV", +# year=year, +# share_mob_flex_G2V=0.3, +# share_mob_flex_V2G=0.2, +# share_mob_inflex=0.5, +# ) +# # build constraint for each facade & period +# constraint.build_constraint(self.model) From fbca239a86867da17ece5dad3bcaab2b7c8fe07b Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Tue, 28 Nov 2023 11:02:05 +0100 Subject: [PATCH 70/88] Join classes of dispatch and investment tests --- tests/test_facades.py | 50 +++---------------------------------------- 1 file changed, 3 insertions(+), 47 deletions(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index 3e7979f5..7d9264ed 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -10,7 +10,7 @@ # from oemof.tabular.postprocessing import calculations -class TestBevFacadesDispatch: +class TestBevFacades: @classmethod def setup_class(cls): cls.date_time_index = pd.date_range("1/1/2020", periods=4, freq="H") @@ -21,7 +21,8 @@ def setup_class(cls): @classmethod def setup_method(cls): cls.energysystem = solph.EnergySystem( - groupings=solph.GROUPINGS, timeindex=cls.date_time_index + groupings=solph.GROUPINGS, + timeindex=cls.date_time_index, ) def get_om(self): @@ -519,51 +520,6 @@ def test_bev_trio_dispatch(self): cn5 = "el-bus->BEV-G2V-storage" assert self.results[cn5]["sequences"]["flow"].iloc[0] == 808.704 - -class TestBevFacadesInvestment: - @classmethod - def setup_class(cls): - t_idx_1 = pd.date_range("1/1/2020", periods=1, freq="H") - t_idx_2 = pd.date_range("1/1/2030", periods=1, 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") - cls.date_time_index = pd.concat([t_idx_1_series, t_idx_2_series]).index - cls.periods = [t_idx_1, t_idx_2] - - cls.tmpdir = helpers.extend_basic_path("tmp") - logging.info(cls.tmpdir) - - @classmethod - def setup_method(cls): - cls.energysystem = solph.EnergySystem( - groupings=solph.GROUPINGS, - timeindex=cls.date_time_index, - infer_last_interval=False, - timeincrement=[1] * len(cls.date_time_index), - periods=cls.periods, - ) - - # todo: identical functions can be @fixtures or else (outside of classes) - - def get_om(self): - self.model = solph.Model( - self.energysystem, - timeindex=self.energysystem.timeindex, - ) - - def solve_om(self): - opt_result = self.model.solve("cbc", solve_kwargs={"tee": True}) - self.results = self.model.results() - return opt_result - - def rename_results(self): - rename_mapping = { - oemof_tuple: f"{oemof_tuple[0]}->{oemof_tuple[1]}" - for oemof_tuple in self.results.keys() - } - for old_key, new_key in rename_mapping.items(): - self.results[new_key] = self.results.pop(old_key) - def test_bev_v2g_invest(self): """ Tests v2g bev facade in investment mode. From 02760110aac0ec3a658e8c9edb2d89ec84ece97d Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Tue, 28 Nov 2023 13:50:53 +0100 Subject: [PATCH 71/88] Add BEV investment tests with constraints --- tests/test_facades.py | 288 +++++++++++++++++++++++++++++++++++------- 1 file changed, 240 insertions(+), 48 deletions(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index 7d9264ed..7e54c182 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -1,6 +1,7 @@ import logging import pandas as pd +import pytest from oemof.solph import helpers from oemof import solph @@ -522,7 +523,12 @@ def test_bev_trio_dispatch(self): def test_bev_v2g_invest(self): """ - Tests v2g bev facade in investment mode. + Tests v2g bev facade with investment optimization. + + Energy quantities and efficiencies are the same as in + `test_bev_v2g_dispatch`. + + The constraint "bev_equal_invest" is used. """ el_bus = solph.Bus("el-bus") el_bus.type = "bus" @@ -537,12 +543,12 @@ def test_bev_v2g_invest(self): bus=el_bus, carrier="wind", tech="onshore", - capacity=248.832, + capacity=725.76, + profile=[1, 0, 0, 0], + variable_costs=10, # capacity_cost=1, # expandable=True, - profile=len(self.periods) * [1], - lifetime=20, - variable_costs=10, + # lifetime=20, ) self.energysystem.add(volatile) @@ -550,8 +556,8 @@ def test_bev_v2g_invest(self): label="load", carrier="electricity", bus=el_bus, - amount=0, - profile=len(self.periods) * [1], + amount=100, + profile=[0, 0.1, 0, 1], ) self.energysystem.add(load) @@ -561,8 +567,9 @@ def test_bev_v2g_invest(self): carrier="pkm", bus=indiv_mob, amount=100, # PKM - profile=len(self.periods) * [1], # drive consumption + profile=[0.5, 0.5, 1, 0], # drive consumption ) + self.energysystem.add(pkm_demand) bev_v2g = Bev( @@ -576,9 +583,11 @@ def test_bev_v2g_invest(self): charging_power=0, balanced=True, expandable=True, - initial_storage_capacity=0, - availability=len(self.periods) - * [1], # Vehicle availability at charger + initial_storage_level=0, + availability=[1, 1, 1, 1], # Vehicle availability at charger + # min_storage_level=[0.0, 0.2, 0.15, 0.0], + # max_storage_level=[0.9, 0.95, 0.92, 0.92], + # efficiency_charging=1, commodity_conversion_rate=5 / 6, # Energy to pkm efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km efficiency_mob_v2g=5 / 6, # V2G charger efficiency @@ -595,12 +604,11 @@ def test_bev_v2g_invest(self): self.get_om() - for period in self.energysystem.periods: - year = period.year.min() - constraint = CONSTRAINT_TYPE_MAP["bev_equal_invest"] - constraint = constraint(name=None, type=None, year=year) - # build constraint for each facade & period - constraint.build_constraint(self.model) + # Add constraint bev_equal_invest + year = self.energysystem.timeindex.year[0] + constraint = CONSTRAINT_TYPE_MAP["bev_equal_invest"] + constraint = constraint(name=None, type=None, year=year) + constraint.build_constraint(self.model) solver_stats = self.solve_om() @@ -609,40 +617,224 @@ def test_bev_v2g_invest(self): assert solver_stats["Solver"][0]["Status"] == "ok" - # Check storage level and invested storage capacity + # Check Storage level cn = "BEV-V2G-storage->None" - # todo optional capacity as a variable - # todo 2020 2030 (infer_last_interval missing) - # todo add second time step to periods - assert self.results[cn]["sequences"]["storage_content"].iloc[0] == 0 - assert self.results[cn]["sequences"]["storage_content"].iloc[1] == 0 - assert self.results[cn]["period_scalars"]["invest"].iloc[0] == 746.496 - assert self.results[cn]["period_scalars"]["invest"].iloc[1] == 746.496 + # todo: why is first time step of storage level missing, but last time + # step is nan? + assert self.results[cn]["sequences"]["storage_content"][0] == 417.6 + assert self.results[cn]["sequences"]["storage_content"][1] == 316.8 + assert self.results[cn]["sequences"]["storage_content"][2] == 144 + assert self.results[cn]["sequences"]["storage_content"][3] == 0 + + # Check invested storage capacity + assert self.results[cn]["scalars"]["invest"] == 2177.28 # Check invested v2g capacity cn2 = "BEV-V2G-v2g->el-bus" - assert self.results[cn2]["period_scalars"]["invest"].iloc[0] == 248.832 - assert self.results[cn2]["period_scalars"]["invest"].iloc[1] == 248.832 + assert self.results[cn2]["scalars"]["invest"] == 725.76 + + # Check invested v2g-2com capacity + cn3 = "BEV-V2G-2com->pkm-bus" + assert self.results[cn3]["scalars"]["invest"] == 725.76 + + def test_bev_trio_invest(self): + """ + Tests linked v2g, g2v and inflex bev facades in dispatch mode. + + Energy quantities of load, pkm_demand and volatile and the efficiencies + are the same as in `test_bev_trio_dispatch`. + + The constraints "bev_equal_invest" and "bev_share_mob" are used. + + The checks include shares of invested storage capacities of the three + BEV components (v2g, g2v, inflex) and the invested capacities of the + inverters. + Storage levels were not calculated manually and therefore, are not + being checked. + """ + el_bus = solph.Bus("el-bus") + el_bus.type = "bus" + self.energysystem.add(el_bus) + + indiv_mob = solph.Bus("pkm-bus") + indiv_mob.type = "bus" + self.energysystem.add(indiv_mob) + + volatile = Volatile( + label="wind", + bus=el_bus, + carrier="wind", + tech="onshore", + capacity=2543.168, + profile=[1, 0, 0, 0], + variable_costs=10, + ) + self.energysystem.add(volatile) + + load = Load( + label="load", + carrier="electricity", + bus=el_bus, + amount=200, + profile=[1, 0.05, 0, 0.5], + ) + self.energysystem.add(load) + + pkm_demand = Load( + label="pkm_demand", + type="Load", + carrier="pkm", + bus=indiv_mob, + amount=250, # PKM + profile=[1, 0.6, 1.2, 0.6], # drive consumption + ) + self.energysystem.add(pkm_demand) + + bev_v2g = Bev( + type="bev", + label="BEV-V2G", + v2g=True, + electricity_bus=el_bus, + commodity_bus=indiv_mob, + storage_capacity=0, + loss_rate=0, # self discharge of storage + charging_power=0, + balanced=True, + expandable=True, + initial_storage_capacity=0, + availability=[1, 1, 1, 1], # Vehicle availability at charger + # min_storage_level=[0.0, 0.2, 0.15, 0.0], + # max_storage_level=[0.9, 0.95, 0.92, 0.92], + # efficiency_charging=1, + commodity_conversion_rate=5 / 6, # Energy to pkm + efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km + efficiency_mob_v2g=5 / 6, # V2G charger efficiency + efficiency_mob_g2v=5 / 6, # Charger efficiency + efficiency_sto_in=5 / 6, # Storage charging efficiency + efficiency_sto_out=5 / 6, # Storage discharging efficiency, + variable_costs=10, # Charging costs + bev_invest_costs=2, + invest_c_rate=60 / 20, # Capacity/Power + fixed_investment_costs=1, + lifetime=10, + ) + self.energysystem.add(bev_v2g) + + bev_inflex = Bev( + type="bev", + label="BEV-inflex", + electricity_bus=el_bus, + commodity_bus=indiv_mob, + storage_capacity=0, + loss_rate=0, # self discharge of storage + charging_power=0, + # drive_power=100, # total driving capacity of the fleet + availability=[1, 1, 1, 1], + v2g=False, + # min_storage_level=[0.1, 0.2, 0.15, 0.15], + # max_storage_level=[0.9, 0.95, 0.92, 0.92], + balanced=True, + initial_storage_capacity=0, + expandable=True, + input_parameters={ + "fix": [0.89856, 0, 0, 0] + }, # fixed relative charging profile + output_parameters={ + "fix": [0.16, 0.08, 0.16, 0.12] + }, # fixed relative discharging profile + commodity_conversion_rate=5 / 6, # Energy to pkm + efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km + efficiency_mob_g2v=5 / 6, # Charger efficiency + efficiency_sto_in=5 / 6, # Storage charging efficiency + efficiency_sto_out=5 / 6, # Storage discharging efficiency, + variable_costs=20, # Charging costs + bev_invest_costs=2, + invest_c_rate=60 / 20, # Capacity/Power + fixed_investment_costs=1, + lifetime=10, + ) + self.energysystem.add(bev_inflex) + + bev_g2v = Bev( + type="bev", + label="BEV-G2V", + electricity_bus=el_bus, + commodity_bus=indiv_mob, + storage_capacity=0, + loss_rate=0, # self discharge of storage + charging_power=0, + # drive_power=100, # total driving capacity of the fleet + availability=[1, 1, 1, 1], + v2g=False, + # min_storage_level=[0.1, 0.2, 0.15, 0.15], + # max_storage_level=[0.9, 0.95, 0.92, 0.92], + balanced=True, + initial_storage_capacity=0, + expandable=True, + commodity_conversion_rate=5 / 6, # Energy to pkm + efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km + efficiency_mob_g2v=5 / 6, # Charger efficiency + efficiency_sto_in=5 / 6, # Storage charging efficiency + efficiency_sto_out=5 / 6, # Storage discharging efficiency, + variable_costs=4, # Charging costs + bev_invest_costs=2, + invest_c_rate=60 / 20, # Capacity/Power + fixed_investment_costs=1, + lifetime=10, + ) + self.energysystem.add(bev_g2v) + + self.get_om() + + # Add constraint bev_equal_invest + year = self.energysystem.timeindex.year[0] + constraint = CONSTRAINT_TYPE_MAP["bev_equal_invest"] + constraint = constraint(name=None, type=None, year=year) + constraint.build_constraint(self.model) + + # Add constraint + constraint = CONSTRAINT_TYPE_MAP["bev_share_mob"] + constraint = constraint( + name=None, + type=None, + label="BEV", + year=year, + share_mob_flex_V2G=0.3, + share_mob_inflex=0.2, + share_mob_flex_G2V=0.5, + ) + constraint.build_constraint(self.model) + + solver_stats = self.solve_om() + + # rename results to make them accessible + self.rename_results() + + assert solver_stats["Solver"][0]["Status"] == "ok" + + # Check invested storage capacity shares + cn = "BEV-V2G-storage->None" + cn2 = "BEV-inflex-storage->None" + cn3 = "BEV-G2V-storage->None" + v2g_cap = self.results[cn]["scalars"]["invest"] + inflex_cap = self.results[cn2]["scalars"]["invest"] + g2v_cap = self.results[cn3]["scalars"]["invest"] + total_cap = v2g_cap + inflex_cap + g2v_cap + assert round(v2g_cap / total_cap, 1) == constraint.share_mob_flex_V2G + assert round(g2v_cap / total_cap, 1) == constraint.share_mob_flex_G2V + assert round(inflex_cap / total_cap, 1) == constraint.share_mob_inflex # Check invested v2g capacity - cn2 = "BEV-V2G-2com->pkm-bus" - assert self.results[cn2]["period_scalars"]["invest"].iloc[0] == 248.832 - assert self.results[cn2]["period_scalars"]["invest"].iloc[1] == 248.832 - - -# This one is only for the bev trio -# ################################## -# for period in self.energysystem.periods: -# year = period.year.min() -# constraint = CONSTRAINT_TYPE_MAP["bev_share_mob"] -# constraint = constraint( -# name=None, -# type=None, -# label="BEV", -# year=year, -# share_mob_flex_G2V=0.3, -# share_mob_flex_V2G=0.2, -# share_mob_inflex=0.5, -# ) -# # build constraint for each facade & period -# constraint.build_constraint(self.model) + cn4 = "BEV-V2G-v2g->el-bus" + assert round(self.results[cn4]["scalars"]["invest"], 4) == v2g_cap / 3 + + # Check invested v2g-2com capacity + cn5 = "BEV-V2G-2com->pkm-bus" + assert round(self.results[cn5]["scalars"]["invest"], 4) == v2g_cap / 3 + cn6 = "BEV-G2V-2com->pkm-bus" + assert self.results[cn6]["scalars"]["invest"] == g2v_cap / 3 + + cn7 = "BEV-inflex-2com->pkm-bus" + assert self.results[cn7]["scalars"]["invest"] == pytest.approx( + inflex_cap / 3 + ) # difference at 5th decimal place From 2d356ad1e4eea7eecad2b753c6576eda01988e7a Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Tue, 28 Nov 2023 13:53:54 +0100 Subject: [PATCH 72/88] Remove unused lines --- tests/test_facades.py | 37 +------------------------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index 7e54c182..db7b4d6f 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -6,9 +6,7 @@ from oemof import solph from oemof.tabular.constraint_facades import CONSTRAINT_TYPE_MAP -from oemof.tabular.facades import Bev, Load, Volatile # , Shortage, Excess - -# from oemof.tabular.postprocessing import calculations +from oemof.tabular.facades import Bev, Load, Volatile class TestBevFacades: @@ -116,9 +114,6 @@ def test_bev_v2g_dispatch(self): expandable=False, initial_storage_capacity=0, availability=[1, 1, 1, 1], # Vehicle availability at charger - # min_storage_level=[0.0, 0.2, 0.15, 0.0], - # max_storage_level=[0.9, 0.95, 0.92, 0.92], - # efficiency_charging=1, commodity_conversion_rate=5 / 6, # Energy to pkm efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km efficiency_mob_v2g=5 / 6, # V2G charger efficiency @@ -216,11 +211,8 @@ def test_bev_inflex_dispatch(self): storage_capacity=900, loss_rate=0, # self discharge of storage charging_power=900, - # drive_power=100, # total driving capacity of the fleet availability=[1, 1, 1, 1], v2g=False, - # min_storage_level=[0.1, 0.2, 0.15, 0.15], - # max_storage_level=[0.9, 0.95, 0.92, 0.92], balanced=True, initial_storage_capacity=0, expandable=False, @@ -314,11 +306,8 @@ def test_bev_g2v_dispatch(self): storage_capacity=900, loss_rate=0, # self discharge of storage charging_power=900, - # drive_power=100, # total driving capacity of the fleet availability=[1, 1, 1, 1], v2g=False, - # min_storage_level=[0.1, 0.2, 0.15, 0.15], - # max_storage_level=[0.9, 0.95, 0.92, 0.92], balanced=True, initial_storage_capacity=0, expandable=False, @@ -414,9 +403,6 @@ def test_bev_trio_dispatch(self): expandable=False, initial_storage_capacity=0, availability=[1, 1, 1, 1], # Vehicle availability at charger - # min_storage_level=[0.0, 0.2, 0.15, 0.0], - # max_storage_level=[0.9, 0.95, 0.92, 0.92], - # efficiency_charging=1, commodity_conversion_rate=5 / 6, # Energy to pkm efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km efficiency_mob_v2g=5 / 6, # V2G charger efficiency @@ -435,11 +421,8 @@ def test_bev_trio_dispatch(self): storage_capacity=900, loss_rate=0, # self discharge of storage charging_power=900, - # drive_power=100, # total driving capacity of the fleet availability=[1, 1, 1, 1], v2g=False, - # min_storage_level=[0.1, 0.2, 0.15, 0.15], - # max_storage_level=[0.9, 0.95, 0.92, 0.92], balanced=True, initial_storage_capacity=0, expandable=False, @@ -466,11 +449,8 @@ def test_bev_trio_dispatch(self): storage_capacity=808.704, loss_rate=0, # self discharge of storage charging_power=808.704, - # drive_power=100, # total driving capacity of the fleet availability=[1, 1, 1, 1], v2g=False, - # min_storage_level=[0.1, 0.2, 0.15, 0.15], - # max_storage_level=[0.9, 0.95, 0.92, 0.92], balanced=True, initial_storage_capacity=0, expandable=False, @@ -546,9 +526,6 @@ def test_bev_v2g_invest(self): capacity=725.76, profile=[1, 0, 0, 0], variable_costs=10, - # capacity_cost=1, - # expandable=True, - # lifetime=20, ) self.energysystem.add(volatile) @@ -585,9 +562,6 @@ def test_bev_v2g_invest(self): expandable=True, initial_storage_level=0, availability=[1, 1, 1, 1], # Vehicle availability at charger - # min_storage_level=[0.0, 0.2, 0.15, 0.0], - # max_storage_level=[0.9, 0.95, 0.92, 0.92], - # efficiency_charging=1, commodity_conversion_rate=5 / 6, # Energy to pkm efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km efficiency_mob_v2g=5 / 6, # V2G charger efficiency @@ -703,9 +677,6 @@ def test_bev_trio_invest(self): expandable=True, initial_storage_capacity=0, availability=[1, 1, 1, 1], # Vehicle availability at charger - # min_storage_level=[0.0, 0.2, 0.15, 0.0], - # max_storage_level=[0.9, 0.95, 0.92, 0.92], - # efficiency_charging=1, commodity_conversion_rate=5 / 6, # Energy to pkm efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km efficiency_mob_v2g=5 / 6, # V2G charger efficiency @@ -728,11 +699,8 @@ def test_bev_trio_invest(self): storage_capacity=0, loss_rate=0, # self discharge of storage charging_power=0, - # drive_power=100, # total driving capacity of the fleet availability=[1, 1, 1, 1], v2g=False, - # min_storage_level=[0.1, 0.2, 0.15, 0.15], - # max_storage_level=[0.9, 0.95, 0.92, 0.92], balanced=True, initial_storage_capacity=0, expandable=True, @@ -763,11 +731,8 @@ def test_bev_trio_invest(self): storage_capacity=0, loss_rate=0, # self discharge of storage charging_power=0, - # drive_power=100, # total driving capacity of the fleet availability=[1, 1, 1, 1], v2g=False, - # min_storage_level=[0.1, 0.2, 0.15, 0.15], - # max_storage_level=[0.9, 0.95, 0.92, 0.92], balanced=True, initial_storage_capacity=0, expandable=True, From c04c3f34ccc0c577325a4160be516bb13565bd2e Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Tue, 28 Nov 2023 14:51:37 +0100 Subject: [PATCH 73/88] Add to changelog and authors --- AUTHORS.rst | 1 + CHANGELOG.rst | 1 + 2 files changed, 2 insertions(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index e449183c..5250183d 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -13,3 +13,4 @@ Authors * Sarah Berendes * Marie-Claire Gering * Julian Endres +* Sabine Haas diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dead4858..785611da 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,7 @@ Unreleased Features * Remove facade relicts `#135 `_ +* Add tests for BEV facades developed in #94 `#142 `_ Fixes From 4d767e6e9f214e10e9a6a1df1ab749259416ffd5 Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Thu, 30 Nov 2023 09:43:58 +0100 Subject: [PATCH 74/88] Minor adaptions to docstrings --- tests/test_facades.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index db7b4d6f..21bb78e3 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -45,7 +45,7 @@ def rename_results(self): def test_bev_v2g_dispatch(self): """ - Tests v2g bev facade in dispatch mode. + Tests v2g bev facade in dispatch optimization. The following energy quantities are used: volatile +725.76 0 0 0 @@ -148,7 +148,7 @@ def test_bev_v2g_dispatch(self): def test_bev_inflex_dispatch(self): """ - Tests inflex bev facade in dispatch mode. + Tests inflex bev facade in dispatch optimization. The following energy quantities are used: volatile +908.704 0 0 0 @@ -256,7 +256,7 @@ def test_bev_inflex_dispatch(self): def test_bev_g2v_dispatch(self): """ - Tests g2v bev facade in dispatch mode. + Tests g2v bev facade in dispatch optimization. The same quantities as in `test_bev_inflex_dispatch()` are used. """ @@ -345,7 +345,7 @@ def test_bev_g2v_dispatch(self): def test_bev_trio_dispatch(self): """ - Tests linked v2g, g2v and inflex bev facades in dispatch mode. + Tests linked v2g, g2v and inflex bev facades in dispatch optimization. Energy quantities are taken from the single tests (`test_bev_v2g_dispatch()`, `test_bev_inflex_dispatch()`, @@ -503,7 +503,7 @@ def test_bev_trio_dispatch(self): def test_bev_v2g_invest(self): """ - Tests v2g bev facade with investment optimization. + Tests v2g bev facade in investment optimization. Energy quantities and efficiencies are the same as in `test_bev_v2g_dispatch`. @@ -613,7 +613,7 @@ def test_bev_v2g_invest(self): def test_bev_trio_invest(self): """ - Tests linked v2g, g2v and inflex bev facades in dispatch mode. + Tests linked v2g, g2v and inflex bev facades in invest optimization. Energy quantities of load, pkm_demand and volatile and the efficiencies are the same as in `test_bev_trio_dispatch`. From ba9e4550c5e16ecfea44f2bf54be426762e28079 Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Thu, 30 Nov 2023 10:00:35 +0100 Subject: [PATCH 75/88] Remove initial_storage_capacity from tests, and use initial_storage_level --- tests/test_facades.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index 21bb78e3..e6d03806 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -112,7 +112,6 @@ def test_bev_v2g_dispatch(self): charging_power=800, balanced=True, expandable=False, - initial_storage_capacity=0, availability=[1, 1, 1, 1], # Vehicle availability at charger commodity_conversion_rate=5 / 6, # Energy to pkm efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km @@ -214,7 +213,6 @@ def test_bev_inflex_dispatch(self): availability=[1, 1, 1, 1], v2g=False, balanced=True, - initial_storage_capacity=0, expandable=False, input_parameters={ "fix": [0.89856, 0, 0, 0] @@ -309,7 +307,6 @@ def test_bev_g2v_dispatch(self): availability=[1, 1, 1, 1], v2g=False, balanced=True, - initial_storage_capacity=0, expandable=False, commodity_conversion_rate=5 / 6, # Energy to pkm efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km @@ -401,7 +398,6 @@ def test_bev_trio_dispatch(self): charging_power=800, balanced=True, expandable=False, - initial_storage_capacity=0, availability=[1, 1, 1, 1], # Vehicle availability at charger commodity_conversion_rate=5 / 6, # Energy to pkm efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km @@ -424,7 +420,6 @@ def test_bev_trio_dispatch(self): availability=[1, 1, 1, 1], v2g=False, balanced=True, - initial_storage_capacity=0, expandable=False, input_parameters={ "fix": [0.89856, 0, 0, 0] @@ -452,7 +447,6 @@ def test_bev_trio_dispatch(self): availability=[1, 1, 1, 1], v2g=False, balanced=True, - initial_storage_capacity=0, expandable=False, commodity_conversion_rate=5 / 6, # Energy to pkm efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km @@ -675,7 +669,7 @@ def test_bev_trio_invest(self): charging_power=0, balanced=True, expandable=True, - initial_storage_capacity=0, + initial_storage_level=0, availability=[1, 1, 1, 1], # Vehicle availability at charger commodity_conversion_rate=5 / 6, # Energy to pkm efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km @@ -702,8 +696,8 @@ def test_bev_trio_invest(self): availability=[1, 1, 1, 1], v2g=False, balanced=True, - initial_storage_capacity=0, expandable=True, + initial_storage_level=0, input_parameters={ "fix": [0.89856, 0, 0, 0] }, # fixed relative charging profile @@ -734,8 +728,8 @@ def test_bev_trio_invest(self): availability=[1, 1, 1, 1], v2g=False, balanced=True, - initial_storage_capacity=0, expandable=True, + initial_storage_level=0, commodity_conversion_rate=5 / 6, # Energy to pkm efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km efficiency_mob_g2v=5 / 6, # Charger efficiency From 4c8899eae3524a4852b17ea9e053f12e7a31ca44 Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Thu, 30 Nov 2023 10:02:01 +0100 Subject: [PATCH 76/88] Add BEV multi-period investment tests (skip tests) --- tests/test_facades.py | 450 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 443 insertions(+), 7 deletions(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index e6d03806..dacd9737 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -6,7 +6,10 @@ from oemof import solph from oemof.tabular.constraint_facades import CONSTRAINT_TYPE_MAP -from oemof.tabular.facades import Bev, Load, Volatile +from oemof.tabular.facades import Bev, Excess, Load, Shortage, Volatile + +# todo remove constraint bev_equal_invest from tests when they are removed as +# feature class TestBevFacades: @@ -745,13 +748,8 @@ def test_bev_trio_invest(self): self.get_om() - # Add constraint bev_equal_invest + # Add constraint bev_share_mob year = self.energysystem.timeindex.year[0] - constraint = CONSTRAINT_TYPE_MAP["bev_equal_invest"] - constraint = constraint(name=None, type=None, year=year) - constraint.build_constraint(self.model) - - # Add constraint constraint = CONSTRAINT_TYPE_MAP["bev_share_mob"] constraint = constraint( name=None, @@ -764,6 +762,11 @@ def test_bev_trio_invest(self): ) constraint.build_constraint(self.model) + # Add constraint bev_equal_invest + constraint_eqin = CONSTRAINT_TYPE_MAP["bev_equal_invest"] + constraint_eqin = constraint_eqin(name=None, type=None, year=year) + constraint_eqin.build_constraint(self.model) + solver_stats = self.solve_om() # rename results to make them accessible @@ -797,3 +800,436 @@ def test_bev_trio_invest(self): assert self.results[cn7]["scalars"]["invest"] == pytest.approx( inflex_cap / 3 ) # difference at 5th decimal place + + +class TestBevFacadesMultiPeriodInvest: + @classmethod + def setup_class(cls): + t_idx_1 = pd.date_range("1/1/2020", periods=5, freq="H") + t_idx_2 = pd.date_range("1/1/2030", periods=5, 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") + cls.date_time_index = pd.concat([t_idx_1_series, t_idx_2_series]).index + cls.periods = [t_idx_1, t_idx_2] + + cls.tmpdir = helpers.extend_basic_path("tmp") + logging.info(cls.tmpdir) + + @classmethod + def setup_method(cls): + cls.energysystem = solph.EnergySystem( + groupings=solph.GROUPINGS, + timeindex=cls.date_time_index, + infer_last_interval=False, + timeincrement=[1] * len(cls.date_time_index), + periods=cls.periods, + ) + + # todo: identical functions can be @fixtures or else (outside of classes) + + def get_om(self): + self.model = solph.Model( + self.energysystem, + timeindex=self.energysystem.timeindex, + ) + + def solve_om(self): + opt_result = self.model.solve("cbc", solve_kwargs={"tee": True}) + self.results = self.model.results() + return opt_result + + def rename_results(self): + rename_mapping = { + oemof_tuple: f"{oemof_tuple[0]}->{oemof_tuple[1]}" + for oemof_tuple in self.results.keys() + } + for old_key, new_key in rename_mapping.items(): + self.results[new_key] = self.results.pop(old_key) + + @pytest.mark.skip( + reason="multi-period feature is not working as expected, see " + + "" # todo add issue + ) + def test_bev_v2g_invest_multi_period(self): + """ + Tests v2g bev facade with multi-period in investment optimization. + + Energy quantities of load, pkm_demand and volatile and the efficiencies + are the same as in `test_bev_v2g_invest`. It is added a 5th time step + with zero demand and production to reach a storage content of zero + after the first period, which is important for multi-period use. + + The constraints "bev_equal_invest" and "bev_share_mob" are used. + """ + el_bus = solph.Bus("el-bus") + el_bus.type = "bus" + self.energysystem.add(el_bus) + + indiv_mob = solph.Bus("pkm-bus") + indiv_mob.type = "bus" + self.energysystem.add(indiv_mob) + + volatile = Volatile( + label="wind", + bus=el_bus, + carrier="wind", + tech="onshore", + capacity=725.76, + # expandable=True, + profile=len(self.periods) * [1, 0, 0, 0, 0], + # lifetime=10, + variable_costs=10, + # capacity_cost=1, + ) + self.energysystem.add(volatile) + + load = Load( + label="load", + carrier="electricity", + bus=el_bus, + amount=100, + profile=len(self.periods) * [0, 0.1, 0, 1, 0], + ) + self.energysystem.add(load) + + pkm_demand = Load( + label="pkm_demand", + type="Load", + carrier="pkm", + bus=indiv_mob, + amount=100, # PKM + profile=len(self.periods) + * [0.5, 0.5, 1, 0, 0], # drive consumption + ) + self.energysystem.add(pkm_demand) + + bev_v2g = Bev( + type="bev", + label="BEV-V2G", + v2g=True, + electricity_bus=el_bus, + commodity_bus=indiv_mob, + storage_capacity=0, + loss_rate=0, # self discharge of storage + charging_power=0, + balanced=True, + expandable=True, + availability=len(self.periods) + * [1, 1, 1, 1, 1], # Vehicle availability at charger + commodity_conversion_rate=5 / 6, # Energy to pkm + efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km + efficiency_mob_v2g=5 / 6, # V2G charger efficiency + efficiency_mob_g2v=5 / 6, # Charger efficiency + efficiency_sto_in=5 / 6, # Storage charging efficiency + efficiency_sto_out=5 / 6, # Storage discharging efficiency, + variable_costs=10, # Charging costs + bev_invest_costs=2, + invest_c_rate=60 / 20, # Capacity/Power + fixed_investment_costs=1, + lifetime=10, + ) + self.energysystem.add(bev_v2g) + + # todo remove shortage when multi-period and test is fixed + shortage = Shortage( + type="shortage", + label="shortage", + bus=el_bus, + carrier="electricity", + tech="shortage", + capacity=1000, + marginal_cost=1e12, + ) + self.energysystem.add(shortage) + + self.get_om() + + # Add constraint bev_equal_invest for each period + for period in self.energysystem.periods: + year = period.year.min() + constraint = CONSTRAINT_TYPE_MAP["bev_equal_invest"] + constraint = constraint(name=None, type=None, year=year) + # build constraint for each facade & period + constraint.build_constraint(self.model) + + solver_stats = self.solve_om() + + # rename results to make them accessible + self.rename_results() + + assert solver_stats["Solver"][0]["Status"] == "ok" + + # Check storage level + cn = "BEV-V2G-storage->None" + assert self.results[cn]["sequences"]["storage_content"].iloc[0] == 0 + assert ( + self.results[cn]["sequences"]["storage_content"].iloc[1] == 417.6 + ) + assert ( + self.results[cn]["sequences"]["storage_content"].iloc[2] == 316.8 + ) + assert self.results[cn]["sequences"]["storage_content"].iloc[3] == 144 + assert self.results[cn]["sequences"]["storage_content"].iloc[4] == 0 + assert ( + self.results[cn]["sequences"]["storage_content"].iloc[5] == 417.6 + ) + assert ( + self.results[cn]["sequences"]["storage_content"].iloc[6] == 316.8 + ) + assert self.results[cn]["sequences"]["storage_content"].iloc[7] == 144 + assert self.results[cn]["sequences"]["storage_content"].iloc[8] == 0 + assert self.results[cn]["sequences"]["storage_content"].iloc[9] == 0 + # Check invested storage capacity + assert self.results[cn]["period_scalars"]["invest"].iloc[0] == 2177.28 + assert self.results[cn]["period_scalars"]["invest"].iloc[1] == 2177.28 + + # Check invested v2g capacity + cn2 = "BEV-V2G-v2g->el-bus" + assert self.results[cn2]["period_scalars"]["invest"].iloc[0] == 725.76 + assert self.results[cn2]["period_scalars"]["invest"].iloc[1] == 725.76 + + # Check invested v2g capacity + cn2 = "BEV-V2G-2com->pkm-bus" + assert self.results[cn2]["period_scalars"]["invest"].iloc[0] == 725.76 + assert self.results[cn2]["period_scalars"]["invest"].iloc[1] == 725.76 + + @pytest.mark.skip( + reason="multi-period feature is not working as expected, see " + + "" # todo add issue + ) + def test_bev_trio_invest_multi_period(self): + """ + Tests linked bev facades with multi-period in invest optimization. + + Energy quantities of load, pkm_demand and volatile and the efficiencies + are the same as in `test_bev_trio_invest`. It is added a 5th time step + with zero demand and production to reach a storage content of zero + after the first period, which is important for multi-period use. + + The constraints "bev_equal_invest" and "bev_share_mob" are used. + """ + el_bus = solph.Bus("el-bus") + el_bus.type = "bus" + self.energysystem.add(el_bus) + + indiv_mob = solph.Bus("pkm-bus") + indiv_mob.type = "bus" + self.energysystem.add(indiv_mob) + + volatile = Volatile( + label="wind", + bus=el_bus, + carrier="wind", + tech="onshore", + capacity=2543.168, + # expandable=True, + profile=len(self.periods) * [1, 0, 0, 0, 0], + # lifetime=10, + variable_costs=10, + # capacity_cost=1, + ) + self.energysystem.add(volatile) + + load = Load( + label="load", + carrier="electricity", + bus=el_bus, + amount=200, + profile=len(self.periods) * [1, 0.05, 0, 0.5, 0], + ) + self.energysystem.add(load) + + pkm_demand = Load( + label="pkm_demand", + type="Load", + carrier="pkm", + bus=indiv_mob, + amount=250, # PKM + profile=len(self.periods) + * [1, 0.6, 1.2, 0.6, 0], # drive consumption + ) + self.energysystem.add(pkm_demand) + + bev_v2g = Bev( + type="bev", + label="BEV-V2G", + v2g=True, + electricity_bus=el_bus, + commodity_bus=indiv_mob, + storage_capacity=0, + loss_rate=0, # self discharge of storage + charging_power=0, + balanced=True, + expandable=True, + availability=len(self.periods) + * [1, 1, 1, 1, 1], # Vehicle availability at charger + commodity_conversion_rate=5 / 6, # Energy to pkm + efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km + efficiency_mob_v2g=5 / 6, # V2G charger efficiency + efficiency_mob_g2v=5 / 6, # Charger efficiency + efficiency_sto_in=5 / 6, # Storage charging efficiency + efficiency_sto_out=5 / 6, # Storage discharging efficiency, + variable_costs=10, # Charging costs + bev_invest_costs=2, + invest_c_rate=60 / 20, # Capacity/Power + fixed_investment_costs=1, + lifetime=10, + ) + self.energysystem.add(bev_v2g) + + bev_inflex = Bev( + type="bev", + label="BEV-inflex", + electricity_bus=el_bus, + commodity_bus=indiv_mob, + storage_capacity=0, + loss_rate=0, # self discharge of storage + charging_power=0, + availability=len(self.periods) * [1, 1, 1, 1, 1], + v2g=False, + balanced=True, + expandable=True, + input_parameters={ + "fix": len(self.periods) * [0.89856, 0, 0, 0, 0] + }, # fixed relative charging profile + output_parameters={ + "fix": len(self.periods) * [0.16, 0.08, 0.16, 0.12, 0] + }, # fixed relative discharging profile + commodity_conversion_rate=5 / 6, # Energy to pkm + efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km + efficiency_mob_g2v=5 / 6, # Charger efficiency + efficiency_sto_in=5 / 6, # Storage charging efficiency + efficiency_sto_out=5 / 6, # Storage discharging efficiency, + variable_costs=20, # Charging costs + bev_invest_costs=2, + invest_c_rate=60 / 20, # Capacity/Power + fixed_investment_costs=1, + lifetime=10, + ) + self.energysystem.add(bev_inflex) + + bev_g2v = Bev( + type="bev", + label="BEV-G2V", + electricity_bus=el_bus, + commodity_bus=indiv_mob, + storage_capacity=0, + loss_rate=0, # self discharge of storage + charging_power=0, + availability=len(self.periods) * [1, 1, 1, 1, 1], + v2g=False, + balanced=True, + expandable=True, + commodity_conversion_rate=5 / 6, # Energy to pkm + efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km + efficiency_mob_g2v=5 / 6, # Charger efficiency + efficiency_sto_in=5 / 6, # Storage charging efficiency + efficiency_sto_out=5 / 6, # Storage discharging efficiency, + variable_costs=4, # Charging costs + bev_invest_costs=2, + invest_c_rate=60 / 20, # Capacity/Power + fixed_investment_costs=1, + lifetime=10, + ) + self.energysystem.add(bev_g2v) + + # todo remove shortage/excess when multi-period and test is fixed + shortage = Shortage( + type="shortage", + label="shortage", + bus=el_bus, + carrier="electricity", + tech="shortage", + capacity=1000, + marginal_cost=1e12, + ) + self.energysystem.add(shortage) + + excess = Excess( + type="excess", + label="excess", + bus=el_bus, + carrier="electricity", + tech="excess", + capacity=1000, + marginal_cost=1e12, + ) + self.energysystem.add(excess) + + self.get_om() + + # Add constraints for each period + for period in self.energysystem.periods: + year = period.year.min() + # Add constraint bev_share_mob + constraint = CONSTRAINT_TYPE_MAP["bev_share_mob"] + constraint = constraint( + name=None, + type=None, + label="BEV", + year=year, + share_mob_flex_V2G=0.3, + share_mob_inflex=0.2, + share_mob_flex_G2V=0.5, + ) + # build constraint for each facade & period + constraint.build_constraint(self.model) + + # Add constraint bev_equal_invest + constraint_eqin = CONSTRAINT_TYPE_MAP["bev_equal_invest"] + constraint_eqin = constraint_eqin(name=None, type=None, year=year) + # build constraint for each facade & period + constraint_eqin.build_constraint(self.model) + + solver_stats = self.solve_om() + + # rename results to make them accessible + self.rename_results() + + assert solver_stats["Solver"][0]["Status"] == "ok" + + # Check invested storage capacity shares + cn = "BEV-V2G-storage->None" + cn2 = "BEV-inflex-storage->None" + cn3 = "BEV-G2V-storage->None" + for i in range(len(self.energysystem.periods)): + v2g_cap = self.results[cn]["period_scalars"]["invest"].iloc[i] + inflex_cap = self.results[cn2]["period_scalars"]["invest"].iloc[i] + g2v_cap = self.results[cn3]["period_scalars"]["invest"].iloc[i] + total_cap = v2g_cap + inflex_cap + g2v_cap + assert ( + round(v2g_cap / total_cap, 1) == constraint.share_mob_flex_V2G + ) + assert ( + round(g2v_cap / total_cap, 1) == constraint.share_mob_flex_G2V + ) + assert ( + round(inflex_cap / total_cap, 1) == constraint.share_mob_inflex + ) + + # todo check if rounding / approx() is necessary here + # Check invested v2g capacity + cn4 = "BEV-V2G-v2g->el-bus" + assert ( + round(self.results[cn4]["period_scalars"]["invest"].iloc[i], 4) + == v2g_cap / 3 + ) + + # Check invested v2g-2com capacity + cn5 = "BEV-V2G-2com->pkm-bus" + assert ( + round(self.results[cn5]["period_scalars"]["invest"].iloc[i], 4) + == v2g_cap / 3 + ) + cn6 = "BEV-G2V-2com->pkm-bus" + assert ( + self.results[cn6]["period_scalars"]["invest"].iloc[i] + == g2v_cap / 3 + ) + + cn7 = "BEV-inflex-2com->pkm-bus" + assert self.results[cn7]["period_scalars"]["invest"].iloc[ + i + ] == pytest.approx( + inflex_cap / 3 + ) # difference at 5th decimal place From f1367f80094afb522df9c9681918659a22e4151e Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Thu, 30 Nov 2023 10:02:24 +0100 Subject: [PATCH 77/88] Add BEV multi-period dispatch test for V2G --- tests/test_facades.py | 112 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/tests/test_facades.py b/tests/test_facades.py index dacd9737..63c51cd2 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -1233,3 +1233,115 @@ def test_bev_trio_invest_multi_period(self): ] == pytest.approx( inflex_cap / 3 ) # difference at 5th decimal place + + def test_bev_v2g_dispatch_multi_period(self): + """ + Tests v2g bev facade with multi-period in dispatch optimization. + + Energy quantities of load, pkm_demand and volatile and the efficiencies + are the same as in `test_bev_v2g_invest_multi_period`. + """ + el_bus = solph.Bus("el-bus") + el_bus.type = "bus" + self.energysystem.add(el_bus) + + indiv_mob = solph.Bus("pkm-bus") + indiv_mob.type = "bus" + self.energysystem.add(indiv_mob) + + volatile = Volatile( + label="wind", + bus=el_bus, + carrier="wind", + tech="onshore", + capacity=725.76, + profile=len(self.periods) * [1, 0, 0, 0, 0], + variable_costs=10, + ) + self.energysystem.add(volatile) + + load = Load( + label="load", + carrier="electricity", + bus=el_bus, + amount=100, + profile=len(self.periods) * [0, 0.1, 0, 1, 0], + ) + self.energysystem.add(load) + + pkm_demand = Load( + label="pkm_demand", + type="Load", + carrier="pkm", + bus=indiv_mob, + amount=100, # PKM + profile=len(self.periods) + * [0.5, 0.5, 1, 0, 0], # drive consumption + ) + self.energysystem.add(pkm_demand) + + bev_v2g = Bev( + type="bev", + label="BEV-V2G", + v2g=True, + electricity_bus=el_bus, + commodity_bus=indiv_mob, + storage_capacity=800, + loss_rate=0, # self discharge of storage + charging_power=800, + balanced=True, + expandable=False, + initial_storage_level=0, + availability=len(self.periods) + * [1, 1, 1, 1, 1], # Vehicle availability at charger + commodity_conversion_rate=5 / 6, # Energy to pkm + efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km + efficiency_mob_v2g=5 / 6, # V2G charger efficiency + efficiency_mob_g2v=5 / 6, # Charger efficiency + efficiency_sto_in=5 / 6, # Storage charging efficiency + efficiency_sto_out=5 / 6, # Storage discharging efficiency, + variable_costs=10, # Charging costs + ) + self.energysystem.add(bev_v2g) + + self.get_om() + + solver_stats = self.solve_om() + + # rename results to make them accessible + self.rename_results() + + assert solver_stats["Solver"][0]["Status"] == "ok" + + # Check storage level + cn = "BEV-V2G-storage->None" + assert ( + round(self.results[cn]["sequences"]["storage_content"].iloc[0], 12) + == 0 + ) + assert ( + self.results[cn]["sequences"]["storage_content"].iloc[1] == 417.6 + ) + assert ( + self.results[cn]["sequences"]["storage_content"].iloc[2] == 316.8 + ) + assert self.results[cn]["sequences"]["storage_content"].iloc[3] == 144 + assert ( + round(self.results[cn]["sequences"]["storage_content"].iloc[4], 12) + == 0 + ) + assert ( + round(self.results[cn]["sequences"]["storage_content"].iloc[5], 12) + == 0 + ) + assert ( + self.results[cn]["sequences"]["storage_content"].iloc[6] == 417.6 + ) + assert ( + self.results[cn]["sequences"]["storage_content"].iloc[7] == 316.8 + ) + assert self.results[cn]["sequences"]["storage_content"].iloc[8] == 144 + assert ( + round(self.results[cn]["sequences"]["storage_content"].iloc[9], 12) + == 0 + ) From d83b685ad36ab63764c4d21435ff2a529a14e1ee Mon Sep 17 00:00:00 2001 From: SabineHaas Date: Thu, 30 Nov 2023 13:32:00 +0100 Subject: [PATCH 78/88] Name issue in reason of skipped tests --- tests/test_facades.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_facades.py b/tests/test_facades.py index 63c51cd2..35da14c4 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -847,8 +847,7 @@ def rename_results(self): self.results[new_key] = self.results.pop(old_key) @pytest.mark.skip( - reason="multi-period feature is not working as expected, see " - + "" # todo add issue + reason="multi-period feature is not working as expected, see #144" ) def test_bev_v2g_invest_multi_period(self): """ @@ -994,8 +993,7 @@ def test_bev_v2g_invest_multi_period(self): assert self.results[cn2]["period_scalars"]["invest"].iloc[1] == 725.76 @pytest.mark.skip( - reason="multi-period feature is not working as expected, see " - + "" # todo add issue + reason="multi-period feature is not working as expected, see #144" ) def test_bev_trio_invest_multi_period(self): """ From 7472a15df9c0677a1c2865644ebf9f5d01519f06 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 4 Dec 2023 15:29:47 +0100 Subject: [PATCH 79/88] Remove equal-invest constraint from bev constraint lp test The equal-invest constraint is not necessary anymore as the flow through the bev-facade is already restricted by the storage in & outflow. Costs are only applied for the storage. --- tests/test_constraints.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/tests/test_constraints.py b/tests/test_constraints.py index 3f4bed60..94b47e6c 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -7,11 +7,7 @@ from oemof.solph import helpers from oemof import solph -from oemof.tabular.constraint_facades import ( - BevEqualInvest, - BevShareMob, - GenericIntegralLimit, -) +from oemof.tabular.constraint_facades import BevShareMob, GenericIntegralLimit from oemof.tabular.facades import ( BackpressureTurbine, Bev, @@ -603,12 +599,4 @@ def test_bev_trio(self): ) mob_share_constraint.build_constraint(model) - invest_constraint = BevEqualInvest( - name=f"bev_total_invest_{year}", - type=None, - year=year, - ) - - invest_constraint.build_constraint(model) - self.compare_to_reference_lp("bev_trio_constraint.lp", my_om=model) From 7a9d9da94d292cda984b047e80800c9fbb9ad2eb Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 4 Dec 2023 15:36:25 +0100 Subject: [PATCH 80/88] Revert "Remove equal-invest constraint from bev constraint lp test" Although not necessary, we constraint will still be applied to check the lp files. This reverts commit 7472a15df9c0677a1c2865644ebf9f5d01519f06. --- tests/test_constraints.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/test_constraints.py b/tests/test_constraints.py index 94b47e6c..3f4bed60 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -7,7 +7,11 @@ from oemof.solph import helpers from oemof import solph -from oemof.tabular.constraint_facades import BevShareMob, GenericIntegralLimit +from oemof.tabular.constraint_facades import ( + BevEqualInvest, + BevShareMob, + GenericIntegralLimit, +) from oemof.tabular.facades import ( BackpressureTurbine, Bev, @@ -599,4 +603,12 @@ def test_bev_trio(self): ) mob_share_constraint.build_constraint(model) + invest_constraint = BevEqualInvest( + name=f"bev_total_invest_{year}", + type=None, + year=year, + ) + + invest_constraint.build_constraint(model) + self.compare_to_reference_lp("bev_trio_constraint.lp", my_om=model) From 9eb57a968fb0ac683dfb418e6db8fb03870aacba Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 4 Dec 2023 17:53:21 +0100 Subject: [PATCH 81/88] Add timesteps to docstring --- tests/test_facades.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_facades.py b/tests/test_facades.py index 35da14c4..63e4abf0 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -51,6 +51,9 @@ def test_bev_v2g_dispatch(self): Tests v2g bev facade in dispatch optimization. The following energy quantities are used: + + timestep 0 1 2 3 + ------------------------------------------------- volatile +725.76 0 0 0 load 0 -10 0 -100 pkm_demand -50 -50 -100 0 @@ -153,6 +156,8 @@ def test_bev_inflex_dispatch(self): Tests inflex bev facade in dispatch optimization. The following energy quantities are used: + timestep 0 1 2 3 + ------------------------------------------------- volatile +908.704 0 0 0 load -100 0 0 0 pkm_demand -100 -50 -100 -75 From 0c37c8a161c4af0aaafcf87568816d60822df5f7 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 4 Dec 2023 18:12:46 +0100 Subject: [PATCH 82/88] Add lines to table in docstrings --- tests/test_constraints.py | 6 +++--- tests/test_facades.py | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/test_constraints.py b/tests/test_constraints.py index 3f4bed60..796a6b6c 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -530,7 +530,7 @@ def test_bev_trio(self): # 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], - transport_commodity_bus=indiv_mob, + commodity_bus=indiv_mob, expandable=True, bev_capacity_cost=2, invest_c_rate=60 / 20, @@ -554,7 +554,7 @@ def test_bev_trio(self): # 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], - transport_commodity_bus=indiv_mob, + commodity_bus=indiv_mob, expandable=True, bev_capacity_cost=2, invest_c_rate=60 / 20, @@ -577,7 +577,7 @@ def test_bev_trio(self): # 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], - transport_commodity_bus=indiv_mob, + commodity_bus=indiv_mob, expandable=True, bev_capacity_cost=2, invest_c_rate=60 / 20, # Capacity/Power diff --git a/tests/test_facades.py b/tests/test_facades.py index 63e4abf0..3e4d44e0 100644 --- a/tests/test_facades.py +++ b/tests/test_facades.py @@ -52,12 +52,12 @@ def test_bev_v2g_dispatch(self): The following energy quantities are used: - timestep 0 1 2 3 - ------------------------------------------------- - volatile +725.76 0 0 0 - load 0 -10 0 -100 - pkm_demand -50 -50 -100 0 - V2g storage 417.6 316.8 144 0 + | timestep | 0 | 1 | 2 | 3 | + |-------------|---------|-------|------|------| + | volatile | +725.76 | 0 | 0 | 0 | + | load | 0 | -10 | 0 | -100 | + | pkm_demand | -50 | -50 | -100 | 0 | + | V2g storage | 417.6 | 316.8 | 144 | 0 | The following efficiencies are taken into consideration: volatile --> v2g_storage: efficiency_sto_in * efficiency_mob_g2v @@ -156,12 +156,12 @@ def test_bev_inflex_dispatch(self): Tests inflex bev facade in dispatch optimization. The following energy quantities are used: - timestep 0 1 2 3 - ------------------------------------------------- - volatile +908.704 0 0 0 - load -100 0 0 0 - pkm_demand -100 -50 -100 -75 - V2g storage 388.8 302.4 129.6 0 + | timestep | 0 | 1 | 2 | 3 | + |-------------|----------|-------|-------|-----| + | volatile | +908.704 | 0 | 0 | 0 | + | load | -100 | 0 | 0 | 0 | + | pkm_demand | -100 | -50 | -100 | -75 | + | V2g storage | 388.8 | 302.4 | 129.6 | 0 | The following efficiencies are taken into consideration: volatile --> v2g_storage: efficiency_sto_in * efficiency_mob_g2v From 74104216fdc2cd693cbe31949fa6214bc4952fc2 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 4 Dec 2023 18:17:41 +0100 Subject: [PATCH 83/88] Add explanation on invest constraint --- tests/test_constraints.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_constraints.py b/tests/test_constraints.py index 796a6b6c..98a8633b 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -603,6 +603,8 @@ def test_bev_trio(self): ) mob_share_constraint.build_constraint(model) + # these constraints are not mandatory as the energy flow through the + # facade is already limited by the in & outflow capactiy of the storage invest_constraint = BevEqualInvest( name=f"bev_total_invest_{year}", type=None, From 83f3ab72626f78cbc5a730c11315b540d42b0449 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 4 Dec 2023 18:31:46 +0100 Subject: [PATCH 84/88] Adapt bev constraint test --- tests/test_constraints.py | 125 ++++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 59 deletions(-) diff --git a/tests/test_constraints.py b/tests/test_constraints.py index 98a8633b..6c3f830c 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -7,11 +7,7 @@ from oemof.solph import helpers from oemof import solph -from oemof.tabular.constraint_facades import ( - BevEqualInvest, - BevShareMob, - GenericIntegralLimit, -) +from oemof.tabular.constraint_facades import CONSTRAINT_TYPE_MAP from oemof.tabular.facades import ( BackpressureTurbine, Bev, @@ -483,7 +479,8 @@ def test_emission_constraint(self): output_parameters={"custom_attributes": {"emission_factor": 2.5}}, ) - emission_constraint = GenericIntegralLimit( + emission_constraint = CONSTRAINT_TYPE_MAP["generic_integral_limit"] + emission_constraint = emission_constraint( name="emission_constraint", type="e", limit=1000, @@ -518,24 +515,26 @@ def test_bev_trio(self): bev_v2g = Bev( type="bev", label="BEV-V2G", - electricity_bus=el_bus, - storage_capacity=150, - capacity=50, - drive_power=150, # nominal value sink - # drive_consumption=[1, 1, 1], # relative value sink - max_charging_power=0, # existing - availability=[1, 1, 1], - efficiency_charging=1, v2g=True, - # 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], + electricity_bus=el_bus, commodity_bus=indiv_mob, + storage_capacity=0, + loss_rate=0, # self discharge of storage + charging_power=0, + balanced=True, expandable=True, - bev_capacity_cost=2, - invest_c_rate=60 / 20, - # marginal_cost=3, - pkm_conversion_rate=0.7, + initial_storage_level=0, + availability=[1, 1, 1, 1], # Vehicle availability at charger + commodity_conversion_rate=5 / 6, # Energy to pkm + efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km + efficiency_mob_v2g=5 / 6, # V2G charger efficiency + efficiency_mob_g2v=5 / 6, # Charger efficiency + efficiency_sto_in=5 / 6, # Storage charging efficiency + efficiency_sto_out=5 / 6, # Storage discharging efficiency, + variable_costs=10, # Charging costs + bev_invest_costs=2, + invest_c_rate=60 / 20, # Capacity/Power + fixed_investment_costs=1, lifetime=10, ) @@ -545,21 +544,30 @@ def test_bev_trio(self): type="bev", label="BEV-inflex", electricity_bus=el_bus, - storage_capacity=200, - drive_power=100, - # drive_consumption=[0, 1, 0], - # max_charging_power=200, - availability=[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], commodity_bus=indiv_mob, + storage_capacity=0, + loss_rate=0, # self discharge of storage + charging_power=0, + availability=[1, 1, 1, 1], + v2g=False, + balanced=True, expandable=True, - bev_capacity_cost=2, - invest_c_rate=60 / 20, - # marginal_cost=3, - pkm_conversion_rate=0.7, + initial_storage_level=0, + input_parameters={ + "fix": [0.89856, 0, 0, 0] + }, # fixed relative charging profile + output_parameters={ + "fix": [0.16, 0.08, 0.16, 0.12] + }, # fixed relative discharging profile + commodity_conversion_rate=5 / 6, # Energy to pkm + efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km + efficiency_mob_g2v=5 / 6, # Charger efficiency + efficiency_sto_in=5 / 6, # Storage charging efficiency + efficiency_sto_out=5 / 6, # Storage discharging efficiency, + variable_costs=20, # Charging costs + bev_invest_costs=2, + invest_c_rate=60 / 20, # Capacity/Power + fixed_investment_costs=1, lifetime=10, ) self.energysystem.add(bev_flex) @@ -568,23 +576,24 @@ def test_bev_trio(self): type="bev", label="BEV-G2V", electricity_bus=el_bus, - storage_capacity=200, - drive_power=100, - # drive_consumption=[0, 1, 0], - # max_charging_power=200, - availability=[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], commodity_bus=indiv_mob, + storage_capacity=0, + loss_rate=0, # self discharge of storage + charging_power=0, + availability=[1, 1, 1, 1], + v2g=False, + balanced=True, expandable=True, - bev_capacity_cost=2, + initial_storage_level=0, + commodity_conversion_rate=5 / 6, # Energy to pkm + efficiency_mob_electrical=5 / 6, # Vehicle efficiency per 100km + efficiency_mob_g2v=5 / 6, # Charger efficiency + efficiency_sto_in=5 / 6, # Storage charging efficiency + efficiency_sto_out=5 / 6, # Storage discharging efficiency, + variable_costs=4, # Charging costs + bev_invest_costs=2, invest_c_rate=60 / 20, # Capacity/Power - # marginal_cost=3, - pkm_conversion_rate=0.7, - input_parameters={"fix": [0, 0, 0]}, - # fixed relative charging profile + fixed_investment_costs=1, lifetime=10, ) self.energysystem.add(bev_fix) @@ -592,24 +601,22 @@ def test_bev_trio(self): model = solph.Model(self.energysystem) year = self.date_time_index.year.min() - mob_share_constraint = BevShareMob( - name=f"mob_share_{year}", + mob_share_constraint = CONSTRAINT_TYPE_MAP["bev_share_mob"] + mob_share_constraint = mob_share_constraint( + name=None, type=None, - year=year, label="BEV", - share_mob_flex_G2V=10, - share_mob_flex_V2G=20, - share_mob_inflex=70, + year=year, + share_mob_flex_V2G=0.3, + share_mob_inflex=0.2, + share_mob_flex_G2V=0.5, ) mob_share_constraint.build_constraint(model) # these constraints are not mandatory as the energy flow through the # facade is already limited by the in & outflow capactiy of the storage - invest_constraint = BevEqualInvest( - name=f"bev_total_invest_{year}", - type=None, - year=year, - ) + invest_constraint = CONSTRAINT_TYPE_MAP["bev_equal_invest"] + invest_constraint = invest_constraint(name=None, type=None, year=year) invest_constraint.build_constraint(model) From a64ef1ed2a90d59a86de5f5803f9c55641d48843 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 4 Dec 2023 18:32:25 +0100 Subject: [PATCH 85/88] Remove bev multi-period constraint test This test is not necessary as there are no further features compared to the single period one. --- tests/test_multi_period_constraints.py | 133 +------------------------ 1 file changed, 3 insertions(+), 130 deletions(-) diff --git a/tests/test_multi_period_constraints.py b/tests/test_multi_period_constraints.py index baa0d59b..d71c56a6 100644 --- a/tests/test_multi_period_constraints.py +++ b/tests/test_multi_period_constraints.py @@ -7,14 +7,9 @@ from oemof.solph import buses, helpers from oemof import solph -from oemof.tabular.constraint_facades import ( - BevEqualInvest, - BevShareMob, - GenericIntegralLimit, -) +from oemof.tabular.constraint_facades import CONSTRAINT_TYPE_MAP from oemof.tabular.facades import ( BackpressureTurbine, - Bev, Commodity, Conversion, Dispatchable, @@ -549,7 +544,8 @@ def test_emission_constraint(self): output_parameters={"custom_attributes": {"emission_factor": 2.5}}, ) - emission_constraint = GenericIntegralLimit( + emission_constraint = CONSTRAINT_TYPE_MAP["generic_integral_limit"] + emission_constraint = emission_constraint( name="emission_constraint", type="e", limit=1000, @@ -564,126 +560,3 @@ def test_emission_constraint(self): self.compare_to_reference_lp( "emission_constraint_multi_period.lp", my_om=model ) - - def test_bev_trio(self): - periods = len(self.periods) - - el_bus = solph.Bus("el-bus") - el_bus.type = "bus" - self.energysystem.add(el_bus) - - indiv_mob = solph.Bus("pkm-bus") - indiv_mob.type = "bus" - self.energysystem.add(indiv_mob) - - pkm_demand = Load( - label="pkm_demand", - type="Load", - carrier="pkm", - bus=indiv_mob, - amount=200, # PKM - profile=periods * [0, 1, 0], # drive consumption - ) - - self.energysystem.add(pkm_demand) - - bev_v2g = Bev( - type="bev", - label="BEV-V2G", - electricity_bus=el_bus, - storage_capacity=150, - capacity=50, - drive_power=150, # nominal value sink - # drive_consumption=[1, 1, 1], # relative value sink - max_charging_power=0, # existing - availability=periods * [1, 1, 1], - efficiency_charging=1, - v2g=True, - # 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], - transport_commodity_bus=indiv_mob, - expandable=True, - bev_capacity_cost=2, - invest_c_rate=60 / 20, - # marginal_cost=3, - pkm_conversion_rate=0.7, - lifetime=10, - ) - - self.energysystem.add(bev_v2g) - - bev_flex = Bev( - type="bev", - label="BEV-inflex", - electricity_bus=el_bus, - storage_capacity=200, - drive_power=100, - # drive_consumption=[0, 1, 0], - # max_charging_power=200, - availability=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], - transport_commodity_bus=indiv_mob, - expandable=True, - bev_capacity_cost=2, - invest_c_rate=60 / 20, - # marginal_cost=3, - pkm_conversion_rate=0.7, - lifetime=10, - ) - self.energysystem.add(bev_flex) - - bev_fix = Bev( - type="bev", - label="BEV-G2V", - electricity_bus=el_bus, - storage_capacity=200, - drive_power=100, - # drive_consumption=[0, 1, 0], - # max_charging_power=200, - availability=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], - transport_commodity_bus=indiv_mob, - expandable=True, - bev_capacity_cost=2, - invest_c_rate=60 / 20, # Capacity/Power - # marginal_cost=3, - pkm_conversion_rate=0.7, - input_parameters={"fix": periods * [0, 0, 0]}, - # fixed relative charging profile - lifetime=10, - ) - self.energysystem.add(bev_fix) - - model = solph.Model(self.energysystem) - - for p in self.periods: - year = p.year.min() - mob_share_constraint = BevShareMob( - name=f"mob_share_{year}", - type=None, - year=year, - label="BEV", - share_mob_flex_G2V=10, - share_mob_flex_V2G=20, - share_mob_inflex=70, - ) - mob_share_constraint.build_constraint(model) - - invest_constraint = BevEqualInvest( - name=f"bev_total_invest_{year}", - type=None, - year=year, - ) - - invest_constraint.build_constraint(model) - - self.compare_to_reference_lp( - "bev_trio_constraint_multi_period.lp", my_om=model - ) From 42223c2e7a914a15b20df4969e3541d8c1649044 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 4 Dec 2023 18:34:14 +0100 Subject: [PATCH 86/88] Add bev trio constraint lp file --- .../lp_files/bev_trio_constraint_tmp.lp | 694 ++++++++++++++++++ 1 file changed, 694 insertions(+) create mode 100644 tests/_files/lp_files/bev_trio_constraint_tmp.lp diff --git a/tests/_files/lp_files/bev_trio_constraint_tmp.lp b/tests/_files/lp_files/bev_trio_constraint_tmp.lp new file mode 100644 index 00000000..239cb06d --- /dev/null +++ b/tests/_files/lp_files/bev_trio_constraint_tmp.lp @@ -0,0 +1,694 @@ +\* Source Pyomo model name=Model *\ + +min +objective: ++2 InvestmentFlowBlock_invest(el_bus_BEV_inflex_storage_0) ++2 InvestmentFlowBlock_invest(el_bus_BEV_V2G_storage_0) ++2 InvestmentFlowBlock_invest(el_bus_BEV_G2V_storage_0) ++10 flow(el_bus_BEV_V2G_storage_0_0) ++10 flow(el_bus_BEV_V2G_storage_0_1) ++10 flow(el_bus_BEV_V2G_storage_0_2) ++20 flow(el_bus_BEV_inflex_storage_0_0) ++20 flow(el_bus_BEV_inflex_storage_0_1) ++20 flow(el_bus_BEV_inflex_storage_0_2) ++4 flow(el_bus_BEV_G2V_storage_0_0) ++4 flow(el_bus_BEV_G2V_storage_0_1) ++4 flow(el_bus_BEV_G2V_storage_0_2) + +s.t. + +c_e_BusBlock_balance(BEV_G2V_bus_0_0)_: ++1 flow(BEV_G2V_storage_BEV_G2V_bus_0_0) +-1 flow(BEV_G2V_bus_BEV_G2V_2com_0_0) += 0 + +c_e_BusBlock_balance(BEV_G2V_bus_0_1)_: ++1 flow(BEV_G2V_storage_BEV_G2V_bus_0_1) +-1 flow(BEV_G2V_bus_BEV_G2V_2com_0_1) += 0 + +c_e_BusBlock_balance(BEV_G2V_bus_0_2)_: ++1 flow(BEV_G2V_storage_BEV_G2V_bus_0_2) +-1 flow(BEV_G2V_bus_BEV_G2V_2com_0_2) += 0 + +c_e_BusBlock_balance(BEV_V2G_bus_0_0)_: ++1 flow(BEV_V2G_storage_BEV_V2G_bus_0_0) +-1 flow(BEV_V2G_bus_BEV_V2G_v2g_0_0) +-1 flow(BEV_V2G_bus_BEV_V2G_2com_0_0) += 0 + +c_e_BusBlock_balance(BEV_V2G_bus_0_1)_: ++1 flow(BEV_V2G_storage_BEV_V2G_bus_0_1) +-1 flow(BEV_V2G_bus_BEV_V2G_v2g_0_1) +-1 flow(BEV_V2G_bus_BEV_V2G_2com_0_1) += 0 + +c_e_BusBlock_balance(BEV_V2G_bus_0_2)_: ++1 flow(BEV_V2G_storage_BEV_V2G_bus_0_2) +-1 flow(BEV_V2G_bus_BEV_V2G_v2g_0_2) +-1 flow(BEV_V2G_bus_BEV_V2G_2com_0_2) += 0 + +c_e_BusBlock_balance(BEV_inflex_bus_0_0)_: ++1 flow(BEV_inflex_storage_BEV_inflex_bus_0_0) +-1 flow(BEV_inflex_bus_BEV_inflex_2com_0_0) += 0 + +c_e_BusBlock_balance(BEV_inflex_bus_0_1)_: ++1 flow(BEV_inflex_storage_BEV_inflex_bus_0_1) +-1 flow(BEV_inflex_bus_BEV_inflex_2com_0_1) += 0 + +c_e_BusBlock_balance(BEV_inflex_bus_0_2)_: ++1 flow(BEV_inflex_storage_BEV_inflex_bus_0_2) +-1 flow(BEV_inflex_bus_BEV_inflex_2com_0_2) += 0 + +c_e_BusBlock_balance(el_bus_0_0)_: +-1 flow(el_bus_BEV_V2G_storage_0_0) +-1 flow(el_bus_BEV_inflex_storage_0_0) +-1 flow(el_bus_BEV_G2V_storage_0_0) ++1 flow(BEV_V2G_v2g_el_bus_0_0) += 0 + +c_e_BusBlock_balance(el_bus_0_1)_: +-1 flow(el_bus_BEV_V2G_storage_0_1) +-1 flow(el_bus_BEV_inflex_storage_0_1) +-1 flow(el_bus_BEV_G2V_storage_0_1) ++1 flow(BEV_V2G_v2g_el_bus_0_1) += 0 + +c_e_BusBlock_balance(el_bus_0_2)_: +-1 flow(el_bus_BEV_V2G_storage_0_2) +-1 flow(el_bus_BEV_inflex_storage_0_2) +-1 flow(el_bus_BEV_G2V_storage_0_2) ++1 flow(BEV_V2G_v2g_el_bus_0_2) += 0 + +c_e_BusBlock_balance(pkm_bus_0_0)_: ++1 flow(BEV_V2G_2com_pkm_bus_0_0) ++1 flow(BEV_inflex_2com_pkm_bus_0_0) ++1 flow(BEV_G2V_2com_pkm_bus_0_0) += 0 + +c_e_BusBlock_balance(pkm_bus_0_1)_: ++1 flow(BEV_V2G_2com_pkm_bus_0_1) ++1 flow(BEV_inflex_2com_pkm_bus_0_1) ++1 flow(BEV_G2V_2com_pkm_bus_0_1) += 200 + +c_e_BusBlock_balance(pkm_bus_0_2)_: ++1 flow(BEV_V2G_2com_pkm_bus_0_2) ++1 flow(BEV_inflex_2com_pkm_bus_0_2) ++1 flow(BEV_G2V_2com_pkm_bus_0_2) += 0 + +c_e_ConverterBlock_relation(BEV_V2G_v2g_BEV_V2G_bus_el_bus_0_0)_: ++0.8333333333333334 flow(BEV_V2G_bus_BEV_V2G_v2g_0_0) +-1 flow(BEV_V2G_v2g_el_bus_0_0) += 0 + +c_e_ConverterBlock_relation(BEV_inflex_2com_BEV_inflex_bus_pkm_bus_0_0)_: ++0.6944444444444445 flow(BEV_inflex_bus_BEV_inflex_2com_0_0) +-1 flow(BEV_inflex_2com_pkm_bus_0_0) += 0 + +c_e_ConverterBlock_relation(BEV_G2V_2com_BEV_G2V_bus_pkm_bus_0_0)_: ++0.6944444444444445 flow(BEV_G2V_bus_BEV_G2V_2com_0_0) +-1 flow(BEV_G2V_2com_pkm_bus_0_0) += 0 + +c_e_ConverterBlock_relation(BEV_V2G_2com_BEV_V2G_bus_pkm_bus_0_0)_: ++0.6944444444444445 flow(BEV_V2G_bus_BEV_V2G_2com_0_0) +-1 flow(BEV_V2G_2com_pkm_bus_0_0) += 0 + +c_e_ConverterBlock_relation(BEV_V2G_v2g_BEV_V2G_bus_el_bus_0_1)_: ++0.8333333333333334 flow(BEV_V2G_bus_BEV_V2G_v2g_0_1) +-1 flow(BEV_V2G_v2g_el_bus_0_1) += 0 + +c_e_ConverterBlock_relation(BEV_inflex_2com_BEV_inflex_bus_pkm_bus_0_1)_: ++0.6944444444444445 flow(BEV_inflex_bus_BEV_inflex_2com_0_1) +-1 flow(BEV_inflex_2com_pkm_bus_0_1) += 0 + +c_e_ConverterBlock_relation(BEV_G2V_2com_BEV_G2V_bus_pkm_bus_0_1)_: ++0.6944444444444445 flow(BEV_G2V_bus_BEV_G2V_2com_0_1) +-1 flow(BEV_G2V_2com_pkm_bus_0_1) += 0 + +c_e_ConverterBlock_relation(BEV_V2G_2com_BEV_V2G_bus_pkm_bus_0_1)_: ++0.6944444444444445 flow(BEV_V2G_bus_BEV_V2G_2com_0_1) +-1 flow(BEV_V2G_2com_pkm_bus_0_1) += 0 + +c_e_ConverterBlock_relation(BEV_V2G_v2g_BEV_V2G_bus_el_bus_0_2)_: ++0.8333333333333334 flow(BEV_V2G_bus_BEV_V2G_v2g_0_2) +-1 flow(BEV_V2G_v2g_el_bus_0_2) += 0 + +c_e_ConverterBlock_relation(BEV_inflex_2com_BEV_inflex_bus_pkm_bus_0_2)_: ++0.6944444444444445 flow(BEV_inflex_bus_BEV_inflex_2com_0_2) +-1 flow(BEV_inflex_2com_pkm_bus_0_2) += 0 + +c_e_ConverterBlock_relation(BEV_G2V_2com_BEV_G2V_bus_pkm_bus_0_2)_: ++0.6944444444444445 flow(BEV_G2V_bus_BEV_G2V_2com_0_2) +-1 flow(BEV_G2V_2com_pkm_bus_0_2) += 0 + +c_e_ConverterBlock_relation(BEV_V2G_2com_BEV_V2G_bus_pkm_bus_0_2)_: ++0.6944444444444445 flow(BEV_V2G_bus_BEV_V2G_2com_0_2) +-1 flow(BEV_V2G_2com_pkm_bus_0_2) += 0 + +c_e_InvestmentFlowBlock_total_rule(el_bus_BEV_inflex_storage_0)_: +-1 InvestmentFlowBlock_invest(el_bus_BEV_inflex_storage_0) ++1 InvestmentFlowBlock_total(el_bus_BEV_inflex_storage_0) += 0 + +c_e_InvestmentFlowBlock_total_rule(el_bus_BEV_V2G_storage_0)_: +-1 InvestmentFlowBlock_invest(el_bus_BEV_V2G_storage_0) ++1 InvestmentFlowBlock_total(el_bus_BEV_V2G_storage_0) += 0 + +c_e_InvestmentFlowBlock_total_rule(BEV_G2V_storage_BEV_G2V_bus_0)_: +-1 InvestmentFlowBlock_invest(BEV_G2V_storage_BEV_G2V_bus_0) ++1 InvestmentFlowBlock_total(BEV_G2V_storage_BEV_G2V_bus_0) += 0 + +c_e_InvestmentFlowBlock_total_rule(el_bus_BEV_G2V_storage_0)_: +-1 InvestmentFlowBlock_invest(el_bus_BEV_G2V_storage_0) ++1 InvestmentFlowBlock_total(el_bus_BEV_G2V_storage_0) += 0 + +c_e_InvestmentFlowBlock_total_rule(BEV_inflex_2com_pkm_bus_0)_: +-1 InvestmentFlowBlock_invest(BEV_inflex_2com_pkm_bus_0) ++1 InvestmentFlowBlock_total(BEV_inflex_2com_pkm_bus_0) += 0 + +c_e_InvestmentFlowBlock_total_rule(BEV_V2G_storage_BEV_V2G_bus_0)_: +-1 InvestmentFlowBlock_invest(BEV_V2G_storage_BEV_V2G_bus_0) ++1 InvestmentFlowBlock_total(BEV_V2G_storage_BEV_V2G_bus_0) += 0 + +c_e_InvestmentFlowBlock_total_rule(BEV_inflex_storage_BEV_inflex_bus_0)_: +-1 InvestmentFlowBlock_invest(BEV_inflex_storage_BEV_inflex_bus_0) ++1 InvestmentFlowBlock_total(BEV_inflex_storage_BEV_inflex_bus_0) += 0 + +c_e_InvestmentFlowBlock_total_rule(BEV_V2G_2com_pkm_bus_0)_: +-1 InvestmentFlowBlock_invest(BEV_V2G_2com_pkm_bus_0) ++1 InvestmentFlowBlock_total(BEV_V2G_2com_pkm_bus_0) += 0 + +c_e_InvestmentFlowBlock_total_rule(BEV_V2G_v2g_el_bus_0)_: +-1 InvestmentFlowBlock_invest(BEV_V2G_v2g_el_bus_0) ++1 InvestmentFlowBlock_total(BEV_V2G_v2g_el_bus_0) += 0 + +c_e_InvestmentFlowBlock_total_rule(BEV_G2V_2com_pkm_bus_0)_: +-1 InvestmentFlowBlock_invest(BEV_G2V_2com_pkm_bus_0) ++1 InvestmentFlowBlock_total(BEV_G2V_2com_pkm_bus_0) += 0 + +c_e_InvestmentFlowBlock_fixed(el_bus_BEV_inflex_storage_0_0)_: ++1 flow(el_bus_BEV_inflex_storage_0_0) +-0.89856 InvestmentFlowBlock_total(el_bus_BEV_inflex_storage_0) += 0 + +c_e_InvestmentFlowBlock_fixed(el_bus_BEV_inflex_storage_0_1)_: ++1 flow(el_bus_BEV_inflex_storage_0_1) += 0 + +c_e_InvestmentFlowBlock_fixed(el_bus_BEV_inflex_storage_0_2)_: ++1 flow(el_bus_BEV_inflex_storage_0_2) += 0 + +c_e_InvestmentFlowBlock_fixed(BEV_inflex_storage_BEV_inflex_bus_0_0)_: ++1 flow(BEV_inflex_storage_BEV_inflex_bus_0_0) +-0.16 InvestmentFlowBlock_total(BEV_inflex_storage_BEV_inflex_bus_0) += 0 + +c_e_InvestmentFlowBlock_fixed(BEV_inflex_storage_BEV_inflex_bus_0_1)_: ++1 flow(BEV_inflex_storage_BEV_inflex_bus_0_1) +-0.08 InvestmentFlowBlock_total(BEV_inflex_storage_BEV_inflex_bus_0) += 0 + +c_e_InvestmentFlowBlock_fixed(BEV_inflex_storage_BEV_inflex_bus_0_2)_: ++1 flow(BEV_inflex_storage_BEV_inflex_bus_0_2) +-0.16 InvestmentFlowBlock_total(BEV_inflex_storage_BEV_inflex_bus_0) += 0 + +c_u_InvestmentFlowBlock_max(el_bus_BEV_V2G_storage_0_0)_: ++1 flow(el_bus_BEV_V2G_storage_0_0) +-1 InvestmentFlowBlock_total(el_bus_BEV_V2G_storage_0) +<= 0 + +c_u_InvestmentFlowBlock_max(el_bus_BEV_V2G_storage_0_1)_: ++1 flow(el_bus_BEV_V2G_storage_0_1) +-1 InvestmentFlowBlock_total(el_bus_BEV_V2G_storage_0) +<= 0 + +c_u_InvestmentFlowBlock_max(el_bus_BEV_V2G_storage_0_2)_: ++1 flow(el_bus_BEV_V2G_storage_0_2) +-1 InvestmentFlowBlock_total(el_bus_BEV_V2G_storage_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_G2V_storage_BEV_G2V_bus_0_0)_: ++1 flow(BEV_G2V_storage_BEV_G2V_bus_0_0) +-1 InvestmentFlowBlock_total(BEV_G2V_storage_BEV_G2V_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_G2V_storage_BEV_G2V_bus_0_1)_: ++1 flow(BEV_G2V_storage_BEV_G2V_bus_0_1) +-1 InvestmentFlowBlock_total(BEV_G2V_storage_BEV_G2V_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_G2V_storage_BEV_G2V_bus_0_2)_: ++1 flow(BEV_G2V_storage_BEV_G2V_bus_0_2) +-1 InvestmentFlowBlock_total(BEV_G2V_storage_BEV_G2V_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(el_bus_BEV_G2V_storage_0_0)_: ++1 flow(el_bus_BEV_G2V_storage_0_0) +-1 InvestmentFlowBlock_total(el_bus_BEV_G2V_storage_0) +<= 0 + +c_u_InvestmentFlowBlock_max(el_bus_BEV_G2V_storage_0_1)_: ++1 flow(el_bus_BEV_G2V_storage_0_1) +-1 InvestmentFlowBlock_total(el_bus_BEV_G2V_storage_0) +<= 0 + +c_u_InvestmentFlowBlock_max(el_bus_BEV_G2V_storage_0_2)_: ++1 flow(el_bus_BEV_G2V_storage_0_2) +-1 InvestmentFlowBlock_total(el_bus_BEV_G2V_storage_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_inflex_2com_pkm_bus_0_0)_: ++1 flow(BEV_inflex_2com_pkm_bus_0_0) +-1 InvestmentFlowBlock_total(BEV_inflex_2com_pkm_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_inflex_2com_pkm_bus_0_1)_: ++1 flow(BEV_inflex_2com_pkm_bus_0_1) +-1 InvestmentFlowBlock_total(BEV_inflex_2com_pkm_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_inflex_2com_pkm_bus_0_2)_: ++1 flow(BEV_inflex_2com_pkm_bus_0_2) +-1 InvestmentFlowBlock_total(BEV_inflex_2com_pkm_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_V2G_storage_BEV_V2G_bus_0_0)_: ++1 flow(BEV_V2G_storage_BEV_V2G_bus_0_0) +-1 InvestmentFlowBlock_total(BEV_V2G_storage_BEV_V2G_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_V2G_storage_BEV_V2G_bus_0_1)_: ++1 flow(BEV_V2G_storage_BEV_V2G_bus_0_1) +-1 InvestmentFlowBlock_total(BEV_V2G_storage_BEV_V2G_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_V2G_storage_BEV_V2G_bus_0_2)_: ++1 flow(BEV_V2G_storage_BEV_V2G_bus_0_2) +-1 InvestmentFlowBlock_total(BEV_V2G_storage_BEV_V2G_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_V2G_2com_pkm_bus_0_0)_: ++1 flow(BEV_V2G_2com_pkm_bus_0_0) +-1 InvestmentFlowBlock_total(BEV_V2G_2com_pkm_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_V2G_2com_pkm_bus_0_1)_: ++1 flow(BEV_V2G_2com_pkm_bus_0_1) +-1 InvestmentFlowBlock_total(BEV_V2G_2com_pkm_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_V2G_2com_pkm_bus_0_2)_: ++1 flow(BEV_V2G_2com_pkm_bus_0_2) +-1 InvestmentFlowBlock_total(BEV_V2G_2com_pkm_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_V2G_v2g_el_bus_0_0)_: ++1 flow(BEV_V2G_v2g_el_bus_0_0) +-1 InvestmentFlowBlock_total(BEV_V2G_v2g_el_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_V2G_v2g_el_bus_0_1)_: ++1 flow(BEV_V2G_v2g_el_bus_0_1) +-1 InvestmentFlowBlock_total(BEV_V2G_v2g_el_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_V2G_v2g_el_bus_0_2)_: ++1 flow(BEV_V2G_v2g_el_bus_0_2) +-1 InvestmentFlowBlock_total(BEV_V2G_v2g_el_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_G2V_2com_pkm_bus_0_0)_: ++1 flow(BEV_G2V_2com_pkm_bus_0_0) +-1 InvestmentFlowBlock_total(BEV_G2V_2com_pkm_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_G2V_2com_pkm_bus_0_1)_: ++1 flow(BEV_G2V_2com_pkm_bus_0_1) +-1 InvestmentFlowBlock_total(BEV_G2V_2com_pkm_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_G2V_2com_pkm_bus_0_2)_: ++1 flow(BEV_G2V_2com_pkm_bus_0_2) +-1 InvestmentFlowBlock_total(BEV_G2V_2com_pkm_bus_0) +<= 0 + +c_e_InvestmentFlowBlock_mob_share_BEV_V2G_0_: +-0.3 InvestmentFlowBlock_invest(el_bus_BEV_inflex_storage_0) ++0.7 InvestmentFlowBlock_invest(el_bus_BEV_V2G_storage_0) +-0.3 InvestmentFlowBlock_invest(el_bus_BEV_G2V_storage_0) += 0 + +c_e_InvestmentFlowBlock_mob_share_BEV_inflex_0_: ++0.8 InvestmentFlowBlock_invest(el_bus_BEV_inflex_storage_0) +-0.2 InvestmentFlowBlock_invest(el_bus_BEV_V2G_storage_0) +-0.2 InvestmentFlowBlock_invest(el_bus_BEV_G2V_storage_0) += 0 + +c_e_InvestmentFlowBlock_mob_share_BEV_G2V_0_: +-0.5 InvestmentFlowBlock_invest(el_bus_BEV_inflex_storage_0) +-0.5 InvestmentFlowBlock_invest(el_bus_BEV_V2G_storage_0) ++0.5 InvestmentFlowBlock_invest(el_bus_BEV_G2V_storage_0) += 0 + +c_e_InvestmentFlowBlock__BEV_V2G_equal_invest_0_(0)__: ++1 InvestmentFlowBlock_invest(BEV_V2G_storage_BEV_V2G_bus_0) +-1 InvestmentFlowBlock_invest(BEV_V2G_v2g_el_bus_0) += 0 + +c_e_InvestmentFlowBlock__BEV_V2G_equal_invest_1_(0)__: +-1 InvestmentFlowBlock_invest(el_bus_BEV_V2G_storage_0) ++1 InvestmentFlowBlock_invest(BEV_V2G_v2g_el_bus_0) += 0 + +c_e_InvestmentFlowBlock__BEV_V2G_equal_invest_2_(0)__: ++1 InvestmentFlowBlock_invest(el_bus_BEV_V2G_storage_0) +-1 InvestmentFlowBlock_invest(BEV_V2G_2com_pkm_bus_0) += 0 + +c_e_InvestmentFlowBlock__BEV_inflex_equal_invest_0_(0)__: ++1 InvestmentFlowBlock_invest(BEV_inflex_2com_pkm_bus_0) +-1 InvestmentFlowBlock_invest(BEV_inflex_storage_BEV_inflex_bus_0) += 0 + +c_e_InvestmentFlowBlock__BEV_inflex_equal_invest_1_(0)__: +-1 InvestmentFlowBlock_invest(el_bus_BEV_inflex_storage_0) ++1 InvestmentFlowBlock_invest(BEV_inflex_storage_BEV_inflex_bus_0) += 0 + +c_e_InvestmentFlowBlock__BEV_G2V_equal_invest_0_(0)__: ++1 InvestmentFlowBlock_invest(el_bus_BEV_G2V_storage_0) +-1 InvestmentFlowBlock_invest(BEV_G2V_2com_pkm_bus_0) += 0 + +c_e_InvestmentFlowBlock__BEV_G2V_equal_invest_1_(0)__: +-1 InvestmentFlowBlock_invest(BEV_G2V_storage_BEV_G2V_bus_0) ++1 InvestmentFlowBlock_invest(BEV_G2V_2com_pkm_bus_0) += 0 + +c_e_GenericInvestmentStorageBlock_total_storage_rule(BEV_inflex_storage_0)_: ++1 GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) +-1 GenericInvestmentStorageBlock_invest(BEV_inflex_storage_0) += 0 + +c_e_GenericInvestmentStorageBlock_total_storage_rule(BEV_G2V_storage_0)_: ++1 GenericInvestmentStorageBlock_total(BEV_G2V_storage_0) +-1 GenericInvestmentStorageBlock_invest(BEV_G2V_storage_0) += 0 + +c_e_GenericInvestmentStorageBlock_total_storage_rule(BEV_V2G_storage_0)_: ++1 GenericInvestmentStorageBlock_total(BEV_V2G_storage_0) +-1 GenericInvestmentStorageBlock_invest(BEV_V2G_storage_0) += 0 + +c_e_GenericInvestmentStorageBlock_init_content_fix(BEV_inflex_storage)_: ++1 GenericInvestmentStorageBlock_init_content(BEV_inflex_storage) += 0 + +c_e_GenericInvestmentStorageBlock_init_content_fix(BEV_G2V_storage)_: ++1 GenericInvestmentStorageBlock_init_content(BEV_G2V_storage) += 0 + +c_e_GenericInvestmentStorageBlock_init_content_fix(BEV_V2G_storage)_: ++1 GenericInvestmentStorageBlock_init_content(BEV_V2G_storage) += 0 + +c_e_GenericInvestmentStorageBlock_balance_first(BEV_inflex_storage)_: +-0.6944444444444445 flow(el_bus_BEV_inflex_storage_0_0) ++1.2 flow(BEV_inflex_storage_BEV_inflex_bus_0_0) +-1 GenericInvestmentStorageBlock_init_content(BEV_inflex_storage) ++1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_0) += 0 + +c_e_GenericInvestmentStorageBlock_balance_first(BEV_G2V_storage)_: +-0.6944444444444445 flow(el_bus_BEV_G2V_storage_0_0) ++1.2 flow(BEV_G2V_storage_BEV_G2V_bus_0_0) +-1 GenericInvestmentStorageBlock_init_content(BEV_G2V_storage) ++1 GenericInvestmentStorageBlock_storage_content(BEV_G2V_storage_0) += 0 + +c_e_GenericInvestmentStorageBlock_balance_first(BEV_V2G_storage)_: +-0.6944444444444445 flow(el_bus_BEV_V2G_storage_0_0) ++1.2 flow(BEV_V2G_storage_BEV_V2G_bus_0_0) +-1 GenericInvestmentStorageBlock_init_content(BEV_V2G_storage) ++1 GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_0) += 0 + +c_e_GenericInvestmentStorageBlock_balance(BEV_inflex_storage_0_1)_: +-0.6944444444444445 flow(el_bus_BEV_inflex_storage_0_1) ++1.2 flow(BEV_inflex_storage_BEV_inflex_bus_0_1) +-1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_0) ++1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_1) += 0 + +c_e_GenericInvestmentStorageBlock_balance(BEV_inflex_storage_0_2)_: +-0.6944444444444445 flow(el_bus_BEV_inflex_storage_0_2) ++1.2 flow(BEV_inflex_storage_BEV_inflex_bus_0_2) +-1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_1) ++1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_2) += 0 + +c_e_GenericInvestmentStorageBlock_balance(BEV_G2V_storage_0_1)_: +-0.6944444444444445 flow(el_bus_BEV_G2V_storage_0_1) ++1.2 flow(BEV_G2V_storage_BEV_G2V_bus_0_1) +-1 GenericInvestmentStorageBlock_storage_content(BEV_G2V_storage_0) ++1 GenericInvestmentStorageBlock_storage_content(BEV_G2V_storage_1) += 0 + +c_e_GenericInvestmentStorageBlock_balance(BEV_G2V_storage_0_2)_: +-0.6944444444444445 flow(el_bus_BEV_G2V_storage_0_2) ++1.2 flow(BEV_G2V_storage_BEV_G2V_bus_0_2) +-1 GenericInvestmentStorageBlock_storage_content(BEV_G2V_storage_1) ++1 GenericInvestmentStorageBlock_storage_content(BEV_G2V_storage_2) += 0 + +c_e_GenericInvestmentStorageBlock_balance(BEV_V2G_storage_0_1)_: +-0.6944444444444445 flow(el_bus_BEV_V2G_storage_0_1) ++1.2 flow(BEV_V2G_storage_BEV_V2G_bus_0_1) +-1 GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_0) ++1 GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_1) += 0 + +c_e_GenericInvestmentStorageBlock_balance(BEV_V2G_storage_0_2)_: +-0.6944444444444445 flow(el_bus_BEV_V2G_storage_0_2) ++1.2 flow(BEV_V2G_storage_BEV_V2G_bus_0_2) +-1 GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_1) ++1 GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_2) += 0 + +c_e_GenericInvestmentStorageBlock_balanced_cstr(BEV_inflex_storage)_: +-1 GenericInvestmentStorageBlock_init_content(BEV_inflex_storage) ++1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_2) += 0 + +c_e_GenericInvestmentStorageBlock_balanced_cstr(BEV_G2V_storage)_: +-1 GenericInvestmentStorageBlock_init_content(BEV_G2V_storage) ++1 GenericInvestmentStorageBlock_storage_content(BEV_G2V_storage_2) += 0 + +c_e_GenericInvestmentStorageBlock_balanced_cstr(BEV_V2G_storage)_: +-1 GenericInvestmentStorageBlock_init_content(BEV_V2G_storage) ++1 GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_2) += 0 + +c_e_GenericInvestmentStorageBlock_power_coupled(BEV_inflex_storage_0)_: +-1 InvestmentFlowBlock_total(el_bus_BEV_inflex_storage_0) ++1 InvestmentFlowBlock_total(BEV_inflex_storage_BEV_inflex_bus_0) += 0 + +c_e_GenericInvestmentStorageBlock_power_coupled(BEV_G2V_storage_0)_: ++1 InvestmentFlowBlock_total(BEV_G2V_storage_BEV_G2V_bus_0) +-1 InvestmentFlowBlock_total(el_bus_BEV_G2V_storage_0) += 0 + +c_e_GenericInvestmentStorageBlock_power_coupled(BEV_V2G_storage_0)_: +-1 InvestmentFlowBlock_total(el_bus_BEV_V2G_storage_0) ++1 InvestmentFlowBlock_total(BEV_V2G_storage_BEV_V2G_bus_0) += 0 + +c_e_GenericInvestmentStorageBlock_storage_capacity_inflow(BEV_inflex_storage_0)_: ++1 InvestmentFlowBlock_total(el_bus_BEV_inflex_storage_0) +-0.3333333333333333 GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) += 0 + +c_e_GenericInvestmentStorageBlock_storage_capacity_inflow(BEV_G2V_storage_0)_: ++1 InvestmentFlowBlock_total(el_bus_BEV_G2V_storage_0) +-0.3333333333333333 GenericInvestmentStorageBlock_total(BEV_G2V_storage_0) += 0 + +c_e_GenericInvestmentStorageBlock_storage_capacity_inflow(BEV_V2G_storage_0)_: ++1 InvestmentFlowBlock_total(el_bus_BEV_V2G_storage_0) +-0.3333333333333333 GenericInvestmentStorageBlock_total(BEV_V2G_storage_0) += 0 + +c_e_GenericInvestmentStorageBlock_storage_capacity_outflow(BEV_inflex_storage_0)_: ++1 InvestmentFlowBlock_total(BEV_inflex_storage_BEV_inflex_bus_0) +-0.3333333333333333 GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) += 0 + +c_e_GenericInvestmentStorageBlock_storage_capacity_outflow(BEV_G2V_storage_0)_: ++1 InvestmentFlowBlock_total(BEV_G2V_storage_BEV_G2V_bus_0) +-0.3333333333333333 GenericInvestmentStorageBlock_total(BEV_G2V_storage_0) += 0 + +c_e_GenericInvestmentStorageBlock_storage_capacity_outflow(BEV_V2G_storage_0)_: ++1 InvestmentFlowBlock_total(BEV_V2G_storage_BEV_V2G_bus_0) +-0.3333333333333333 GenericInvestmentStorageBlock_total(BEV_V2G_storage_0) += 0 + +c_u_GenericInvestmentStorageBlock_max_storage_content(BEV_inflex_storage_0_0)_: +-1 GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) ++1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_0) +<= 0 + +c_u_GenericInvestmentStorageBlock_max_storage_content(BEV_inflex_storage_0_1)_: +-1 GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) ++1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_1) +<= 0 + +c_u_GenericInvestmentStorageBlock_max_storage_content(BEV_inflex_storage_0_2)_: +-1 GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) ++1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_2) +<= 0 + +c_u_GenericInvestmentStorageBlock_max_storage_content(BEV_G2V_storage_0_0)_: +-1 GenericInvestmentStorageBlock_total(BEV_G2V_storage_0) ++1 GenericInvestmentStorageBlock_storage_content(BEV_G2V_storage_0) +<= 0 + +c_u_GenericInvestmentStorageBlock_max_storage_content(BEV_G2V_storage_0_1)_: +-1 GenericInvestmentStorageBlock_total(BEV_G2V_storage_0) ++1 GenericInvestmentStorageBlock_storage_content(BEV_G2V_storage_1) +<= 0 + +c_u_GenericInvestmentStorageBlock_max_storage_content(BEV_G2V_storage_0_2)_: +-1 GenericInvestmentStorageBlock_total(BEV_G2V_storage_0) ++1 GenericInvestmentStorageBlock_storage_content(BEV_G2V_storage_2) +<= 0 + +c_u_GenericInvestmentStorageBlock_max_storage_content(BEV_V2G_storage_0_0)_: +-1 GenericInvestmentStorageBlock_total(BEV_V2G_storage_0) ++1 GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_0) +<= 0 + +c_u_GenericInvestmentStorageBlock_max_storage_content(BEV_V2G_storage_0_1)_: +-1 GenericInvestmentStorageBlock_total(BEV_V2G_storage_0) ++1 GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_1) +<= 0 + +c_u_GenericInvestmentStorageBlock_max_storage_content(BEV_V2G_storage_0_2)_: +-1 GenericInvestmentStorageBlock_total(BEV_V2G_storage_0) ++1 GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_2) +<= 0 + +bounds + 0 <= InvestmentFlowBlock_invest(el_bus_BEV_inflex_storage_0) <= +inf + 0 <= InvestmentFlowBlock_invest(el_bus_BEV_V2G_storage_0) <= +inf + 0 <= InvestmentFlowBlock_invest(BEV_G2V_storage_BEV_G2V_bus_0) <= +inf + 0 <= InvestmentFlowBlock_invest(el_bus_BEV_G2V_storage_0) <= +inf + 0 <= InvestmentFlowBlock_invest(BEV_inflex_2com_pkm_bus_0) <= +inf + 0 <= InvestmentFlowBlock_invest(BEV_V2G_storage_BEV_V2G_bus_0) <= +inf + 0 <= InvestmentFlowBlock_invest(BEV_inflex_storage_BEV_inflex_bus_0) <= +inf + 0 <= InvestmentFlowBlock_invest(BEV_V2G_2com_pkm_bus_0) <= +inf + 0 <= InvestmentFlowBlock_invest(BEV_V2G_v2g_el_bus_0) <= +inf + 0 <= InvestmentFlowBlock_invest(BEV_G2V_2com_pkm_bus_0) <= +inf + 0 <= flow(el_bus_BEV_V2G_storage_0_0) <= +inf + 0 <= flow(el_bus_BEV_V2G_storage_0_1) <= +inf + 0 <= flow(el_bus_BEV_V2G_storage_0_2) <= +inf + 0 <= flow(el_bus_BEV_inflex_storage_0_0) <= +inf + 0 <= flow(el_bus_BEV_inflex_storage_0_1) <= +inf + 0 <= flow(el_bus_BEV_inflex_storage_0_2) <= +inf + 0 <= flow(el_bus_BEV_G2V_storage_0_0) <= +inf + 0 <= flow(el_bus_BEV_G2V_storage_0_1) <= +inf + 0 <= flow(el_bus_BEV_G2V_storage_0_2) <= +inf + 0 <= flow(BEV_V2G_storage_BEV_V2G_bus_0_0) <= +inf + 0 <= flow(BEV_V2G_storage_BEV_V2G_bus_0_1) <= +inf + 0 <= flow(BEV_V2G_storage_BEV_V2G_bus_0_2) <= +inf + 0 <= flow(BEV_V2G_bus_BEV_V2G_v2g_0_0) <= +inf + 0 <= flow(BEV_V2G_bus_BEV_V2G_v2g_0_1) <= +inf + 0 <= flow(BEV_V2G_bus_BEV_V2G_v2g_0_2) <= +inf + 0 <= flow(BEV_V2G_bus_BEV_V2G_2com_0_0) <= +inf + 0 <= flow(BEV_V2G_bus_BEV_V2G_2com_0_1) <= +inf + 0 <= flow(BEV_V2G_bus_BEV_V2G_2com_0_2) <= +inf + 0 <= flow(BEV_V2G_v2g_el_bus_0_0) <= +inf + 0 <= flow(BEV_V2G_v2g_el_bus_0_1) <= +inf + 0 <= flow(BEV_V2G_v2g_el_bus_0_2) <= +inf + 0 <= flow(BEV_V2G_2com_pkm_bus_0_0) <= +inf + 0 <= flow(BEV_V2G_2com_pkm_bus_0_1) <= +inf + 0 <= flow(BEV_V2G_2com_pkm_bus_0_2) <= +inf + 0 <= flow(BEV_inflex_storage_BEV_inflex_bus_0_0) <= +inf + 0 <= flow(BEV_inflex_storage_BEV_inflex_bus_0_1) <= +inf + 0 <= flow(BEV_inflex_storage_BEV_inflex_bus_0_2) <= +inf + 0 <= flow(BEV_inflex_bus_BEV_inflex_2com_0_0) <= +inf + 0 <= flow(BEV_inflex_bus_BEV_inflex_2com_0_1) <= +inf + 0 <= flow(BEV_inflex_bus_BEV_inflex_2com_0_2) <= +inf + 0 <= flow(BEV_inflex_2com_pkm_bus_0_0) <= +inf + 0 <= flow(BEV_inflex_2com_pkm_bus_0_1) <= +inf + 0 <= flow(BEV_inflex_2com_pkm_bus_0_2) <= +inf + 0 <= flow(BEV_G2V_storage_BEV_G2V_bus_0_0) <= +inf + 0 <= flow(BEV_G2V_storage_BEV_G2V_bus_0_1) <= +inf + 0 <= flow(BEV_G2V_storage_BEV_G2V_bus_0_2) <= +inf + 0 <= flow(BEV_G2V_bus_BEV_G2V_2com_0_0) <= +inf + 0 <= flow(BEV_G2V_bus_BEV_G2V_2com_0_1) <= +inf + 0 <= flow(BEV_G2V_bus_BEV_G2V_2com_0_2) <= +inf + 0 <= flow(BEV_G2V_2com_pkm_bus_0_0) <= +inf + 0 <= flow(BEV_G2V_2com_pkm_bus_0_1) <= +inf + 0 <= flow(BEV_G2V_2com_pkm_bus_0_2) <= +inf + 0 <= InvestmentFlowBlock_total(el_bus_BEV_inflex_storage_0) <= +inf + 0 <= InvestmentFlowBlock_total(el_bus_BEV_V2G_storage_0) <= +inf + 0 <= InvestmentFlowBlock_total(BEV_G2V_storage_BEV_G2V_bus_0) <= +inf + 0 <= InvestmentFlowBlock_total(el_bus_BEV_G2V_storage_0) <= +inf + 0 <= InvestmentFlowBlock_total(BEV_inflex_2com_pkm_bus_0) <= +inf + 0 <= InvestmentFlowBlock_total(BEV_V2G_storage_BEV_V2G_bus_0) <= +inf + 0 <= InvestmentFlowBlock_total(BEV_inflex_storage_BEV_inflex_bus_0) <= +inf + 0 <= InvestmentFlowBlock_total(BEV_V2G_2com_pkm_bus_0) <= +inf + 0 <= InvestmentFlowBlock_total(BEV_V2G_v2g_el_bus_0) <= +inf + 0 <= InvestmentFlowBlock_total(BEV_G2V_2com_pkm_bus_0) <= +inf + 0 <= GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) <= +inf + 0 <= GenericInvestmentStorageBlock_total(BEV_G2V_storage_0) <= +inf + 0 <= GenericInvestmentStorageBlock_total(BEV_V2G_storage_0) <= +inf + 0 <= GenericInvestmentStorageBlock_invest(BEV_inflex_storage_0) <= +inf + 0 <= GenericInvestmentStorageBlock_invest(BEV_G2V_storage_0) <= +inf + 0 <= GenericInvestmentStorageBlock_invest(BEV_V2G_storage_0) <= +inf + 0 <= GenericInvestmentStorageBlock_init_content(BEV_inflex_storage) <= +inf + 0 <= GenericInvestmentStorageBlock_init_content(BEV_G2V_storage) <= +inf + 0 <= GenericInvestmentStorageBlock_init_content(BEV_V2G_storage) <= +inf + 0 <= GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_0) <= +inf + 0 <= GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_1) <= +inf + 0 <= GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_2) <= +inf + 0 <= GenericInvestmentStorageBlock_storage_content(BEV_G2V_storage_0) <= +inf + 0 <= GenericInvestmentStorageBlock_storage_content(BEV_G2V_storage_1) <= +inf + 0 <= GenericInvestmentStorageBlock_storage_content(BEV_G2V_storage_2) <= +inf + 0 <= GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_0) <= +inf + 0 <= GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_1) <= +inf + 0 <= GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_2) <= +inf +end From 9774aea86151b36dca0a7304451fd73121779e86 Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 4 Dec 2023 18:35:35 +0100 Subject: [PATCH 87/88] Rename lp file --- .../{bev_trio_constraint_tmp.lp => bev_trio_constraint.lp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/_files/lp_files/{bev_trio_constraint_tmp.lp => bev_trio_constraint.lp} (100%) diff --git a/tests/_files/lp_files/bev_trio_constraint_tmp.lp b/tests/_files/lp_files/bev_trio_constraint.lp similarity index 100% rename from tests/_files/lp_files/bev_trio_constraint_tmp.lp rename to tests/_files/lp_files/bev_trio_constraint.lp From 3ea89f9592e4d280ebb38025c2e64a3b366ae4ca Mon Sep 17 00:00:00 2001 From: "Julian.Endres" Date: Mon, 4 Dec 2023 18:37:11 +0100 Subject: [PATCH 88/88] Update lp file --- tests/_files/lp_files/bev_trio_constraint.lp | 334 +++++++++---------- 1 file changed, 167 insertions(+), 167 deletions(-) diff --git a/tests/_files/lp_files/bev_trio_constraint.lp b/tests/_files/lp_files/bev_trio_constraint.lp index 239cb06d..1a42bd42 100644 --- a/tests/_files/lp_files/bev_trio_constraint.lp +++ b/tests/_files/lp_files/bev_trio_constraint.lp @@ -1,6 +1,6 @@ \* Source Pyomo model name=Model *\ -min +min objective: +2 InvestmentFlowBlock_invest(el_bus_BEV_inflex_storage_0) +2 InvestmentFlowBlock_invest(el_bus_BEV_V2G_storage_0) @@ -104,9 +104,9 @@ c_e_BusBlock_balance(pkm_bus_0_2)_: +1 flow(BEV_G2V_2com_pkm_bus_0_2) = 0 -c_e_ConverterBlock_relation(BEV_V2G_v2g_BEV_V2G_bus_el_bus_0_0)_: -+0.8333333333333334 flow(BEV_V2G_bus_BEV_V2G_v2g_0_0) --1 flow(BEV_V2G_v2g_el_bus_0_0) +c_e_ConverterBlock_relation(BEV_V2G_2com_BEV_V2G_bus_pkm_bus_0_0)_: ++0.6944444444444445 flow(BEV_V2G_bus_BEV_V2G_2com_0_0) +-1 flow(BEV_V2G_2com_pkm_bus_0_0) = 0 c_e_ConverterBlock_relation(BEV_inflex_2com_BEV_inflex_bus_pkm_bus_0_0)_: @@ -114,19 +114,19 @@ c_e_ConverterBlock_relation(BEV_inflex_2com_BEV_inflex_bus_pkm_bus_0_0)_: -1 flow(BEV_inflex_2com_pkm_bus_0_0) = 0 +c_e_ConverterBlock_relation(BEV_V2G_v2g_BEV_V2G_bus_el_bus_0_0)_: ++0.8333333333333334 flow(BEV_V2G_bus_BEV_V2G_v2g_0_0) +-1 flow(BEV_V2G_v2g_el_bus_0_0) += 0 + c_e_ConverterBlock_relation(BEV_G2V_2com_BEV_G2V_bus_pkm_bus_0_0)_: +0.6944444444444445 flow(BEV_G2V_bus_BEV_G2V_2com_0_0) -1 flow(BEV_G2V_2com_pkm_bus_0_0) = 0 -c_e_ConverterBlock_relation(BEV_V2G_2com_BEV_V2G_bus_pkm_bus_0_0)_: -+0.6944444444444445 flow(BEV_V2G_bus_BEV_V2G_2com_0_0) --1 flow(BEV_V2G_2com_pkm_bus_0_0) -= 0 - -c_e_ConverterBlock_relation(BEV_V2G_v2g_BEV_V2G_bus_el_bus_0_1)_: -+0.8333333333333334 flow(BEV_V2G_bus_BEV_V2G_v2g_0_1) --1 flow(BEV_V2G_v2g_el_bus_0_1) +c_e_ConverterBlock_relation(BEV_V2G_2com_BEV_V2G_bus_pkm_bus_0_1)_: ++0.6944444444444445 flow(BEV_V2G_bus_BEV_V2G_2com_0_1) +-1 flow(BEV_V2G_2com_pkm_bus_0_1) = 0 c_e_ConverterBlock_relation(BEV_inflex_2com_BEV_inflex_bus_pkm_bus_0_1)_: @@ -134,19 +134,19 @@ c_e_ConverterBlock_relation(BEV_inflex_2com_BEV_inflex_bus_pkm_bus_0_1)_: -1 flow(BEV_inflex_2com_pkm_bus_0_1) = 0 +c_e_ConverterBlock_relation(BEV_V2G_v2g_BEV_V2G_bus_el_bus_0_1)_: ++0.8333333333333334 flow(BEV_V2G_bus_BEV_V2G_v2g_0_1) +-1 flow(BEV_V2G_v2g_el_bus_0_1) += 0 + c_e_ConverterBlock_relation(BEV_G2V_2com_BEV_G2V_bus_pkm_bus_0_1)_: +0.6944444444444445 flow(BEV_G2V_bus_BEV_G2V_2com_0_1) -1 flow(BEV_G2V_2com_pkm_bus_0_1) = 0 -c_e_ConverterBlock_relation(BEV_V2G_2com_BEV_V2G_bus_pkm_bus_0_1)_: -+0.6944444444444445 flow(BEV_V2G_bus_BEV_V2G_2com_0_1) --1 flow(BEV_V2G_2com_pkm_bus_0_1) -= 0 - -c_e_ConverterBlock_relation(BEV_V2G_v2g_BEV_V2G_bus_el_bus_0_2)_: -+0.8333333333333334 flow(BEV_V2G_bus_BEV_V2G_v2g_0_2) --1 flow(BEV_V2G_v2g_el_bus_0_2) +c_e_ConverterBlock_relation(BEV_V2G_2com_BEV_V2G_bus_pkm_bus_0_2)_: ++0.6944444444444445 flow(BEV_V2G_bus_BEV_V2G_2com_0_2) +-1 flow(BEV_V2G_2com_pkm_bus_0_2) = 0 c_e_ConverterBlock_relation(BEV_inflex_2com_BEV_inflex_bus_pkm_bus_0_2)_: @@ -154,21 +154,31 @@ c_e_ConverterBlock_relation(BEV_inflex_2com_BEV_inflex_bus_pkm_bus_0_2)_: -1 flow(BEV_inflex_2com_pkm_bus_0_2) = 0 +c_e_ConverterBlock_relation(BEV_V2G_v2g_BEV_V2G_bus_el_bus_0_2)_: ++0.8333333333333334 flow(BEV_V2G_bus_BEV_V2G_v2g_0_2) +-1 flow(BEV_V2G_v2g_el_bus_0_2) += 0 + c_e_ConverterBlock_relation(BEV_G2V_2com_BEV_G2V_bus_pkm_bus_0_2)_: +0.6944444444444445 flow(BEV_G2V_bus_BEV_G2V_2com_0_2) -1 flow(BEV_G2V_2com_pkm_bus_0_2) = 0 -c_e_ConverterBlock_relation(BEV_V2G_2com_BEV_V2G_bus_pkm_bus_0_2)_: -+0.6944444444444445 flow(BEV_V2G_bus_BEV_V2G_2com_0_2) --1 flow(BEV_V2G_2com_pkm_bus_0_2) -= 0 - c_e_InvestmentFlowBlock_total_rule(el_bus_BEV_inflex_storage_0)_: -1 InvestmentFlowBlock_invest(el_bus_BEV_inflex_storage_0) +1 InvestmentFlowBlock_total(el_bus_BEV_inflex_storage_0) = 0 +c_e_InvestmentFlowBlock_total_rule(BEV_inflex_storage_BEV_inflex_bus_0)_: +-1 InvestmentFlowBlock_invest(BEV_inflex_storage_BEV_inflex_bus_0) ++1 InvestmentFlowBlock_total(BEV_inflex_storage_BEV_inflex_bus_0) += 0 + +c_e_InvestmentFlowBlock_total_rule(BEV_G2V_2com_pkm_bus_0)_: +-1 InvestmentFlowBlock_invest(BEV_G2V_2com_pkm_bus_0) ++1 InvestmentFlowBlock_total(BEV_G2V_2com_pkm_bus_0) += 0 + c_e_InvestmentFlowBlock_total_rule(el_bus_BEV_V2G_storage_0)_: -1 InvestmentFlowBlock_invest(el_bus_BEV_V2G_storage_0) +1 InvestmentFlowBlock_total(el_bus_BEV_V2G_storage_0) @@ -179,24 +189,14 @@ c_e_InvestmentFlowBlock_total_rule(BEV_G2V_storage_BEV_G2V_bus_0)_: +1 InvestmentFlowBlock_total(BEV_G2V_storage_BEV_G2V_bus_0) = 0 -c_e_InvestmentFlowBlock_total_rule(el_bus_BEV_G2V_storage_0)_: --1 InvestmentFlowBlock_invest(el_bus_BEV_G2V_storage_0) -+1 InvestmentFlowBlock_total(el_bus_BEV_G2V_storage_0) -= 0 - -c_e_InvestmentFlowBlock_total_rule(BEV_inflex_2com_pkm_bus_0)_: --1 InvestmentFlowBlock_invest(BEV_inflex_2com_pkm_bus_0) -+1 InvestmentFlowBlock_total(BEV_inflex_2com_pkm_bus_0) -= 0 - c_e_InvestmentFlowBlock_total_rule(BEV_V2G_storage_BEV_V2G_bus_0)_: -1 InvestmentFlowBlock_invest(BEV_V2G_storage_BEV_V2G_bus_0) +1 InvestmentFlowBlock_total(BEV_V2G_storage_BEV_V2G_bus_0) = 0 -c_e_InvestmentFlowBlock_total_rule(BEV_inflex_storage_BEV_inflex_bus_0)_: --1 InvestmentFlowBlock_invest(BEV_inflex_storage_BEV_inflex_bus_0) -+1 InvestmentFlowBlock_total(BEV_inflex_storage_BEV_inflex_bus_0) +c_e_InvestmentFlowBlock_total_rule(BEV_V2G_v2g_el_bus_0)_: +-1 InvestmentFlowBlock_invest(BEV_V2G_v2g_el_bus_0) ++1 InvestmentFlowBlock_total(BEV_V2G_v2g_el_bus_0) = 0 c_e_InvestmentFlowBlock_total_rule(BEV_V2G_2com_pkm_bus_0)_: @@ -204,14 +204,14 @@ c_e_InvestmentFlowBlock_total_rule(BEV_V2G_2com_pkm_bus_0)_: +1 InvestmentFlowBlock_total(BEV_V2G_2com_pkm_bus_0) = 0 -c_e_InvestmentFlowBlock_total_rule(BEV_V2G_v2g_el_bus_0)_: --1 InvestmentFlowBlock_invest(BEV_V2G_v2g_el_bus_0) -+1 InvestmentFlowBlock_total(BEV_V2G_v2g_el_bus_0) +c_e_InvestmentFlowBlock_total_rule(el_bus_BEV_G2V_storage_0)_: +-1 InvestmentFlowBlock_invest(el_bus_BEV_G2V_storage_0) ++1 InvestmentFlowBlock_total(el_bus_BEV_G2V_storage_0) = 0 -c_e_InvestmentFlowBlock_total_rule(BEV_G2V_2com_pkm_bus_0)_: --1 InvestmentFlowBlock_invest(BEV_G2V_2com_pkm_bus_0) -+1 InvestmentFlowBlock_total(BEV_G2V_2com_pkm_bus_0) +c_e_InvestmentFlowBlock_total_rule(BEV_inflex_2com_pkm_bus_0)_: +-1 InvestmentFlowBlock_invest(BEV_inflex_2com_pkm_bus_0) ++1 InvestmentFlowBlock_total(BEV_inflex_2com_pkm_bus_0) = 0 c_e_InvestmentFlowBlock_fixed(el_bus_BEV_inflex_storage_0_0)_: @@ -242,6 +242,21 @@ c_e_InvestmentFlowBlock_fixed(BEV_inflex_storage_BEV_inflex_bus_0_2)_: -0.16 InvestmentFlowBlock_total(BEV_inflex_storage_BEV_inflex_bus_0) = 0 +c_u_InvestmentFlowBlock_max(BEV_G2V_2com_pkm_bus_0_0)_: ++1 flow(BEV_G2V_2com_pkm_bus_0_0) +-1 InvestmentFlowBlock_total(BEV_G2V_2com_pkm_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_G2V_2com_pkm_bus_0_1)_: ++1 flow(BEV_G2V_2com_pkm_bus_0_1) +-1 InvestmentFlowBlock_total(BEV_G2V_2com_pkm_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_G2V_2com_pkm_bus_0_2)_: ++1 flow(BEV_G2V_2com_pkm_bus_0_2) +-1 InvestmentFlowBlock_total(BEV_G2V_2com_pkm_bus_0) +<= 0 + c_u_InvestmentFlowBlock_max(el_bus_BEV_V2G_storage_0_0)_: +1 flow(el_bus_BEV_V2G_storage_0_0) -1 InvestmentFlowBlock_total(el_bus_BEV_V2G_storage_0) @@ -272,36 +287,6 @@ c_u_InvestmentFlowBlock_max(BEV_G2V_storage_BEV_G2V_bus_0_2)_: -1 InvestmentFlowBlock_total(BEV_G2V_storage_BEV_G2V_bus_0) <= 0 -c_u_InvestmentFlowBlock_max(el_bus_BEV_G2V_storage_0_0)_: -+1 flow(el_bus_BEV_G2V_storage_0_0) --1 InvestmentFlowBlock_total(el_bus_BEV_G2V_storage_0) -<= 0 - -c_u_InvestmentFlowBlock_max(el_bus_BEV_G2V_storage_0_1)_: -+1 flow(el_bus_BEV_G2V_storage_0_1) --1 InvestmentFlowBlock_total(el_bus_BEV_G2V_storage_0) -<= 0 - -c_u_InvestmentFlowBlock_max(el_bus_BEV_G2V_storage_0_2)_: -+1 flow(el_bus_BEV_G2V_storage_0_2) --1 InvestmentFlowBlock_total(el_bus_BEV_G2V_storage_0) -<= 0 - -c_u_InvestmentFlowBlock_max(BEV_inflex_2com_pkm_bus_0_0)_: -+1 flow(BEV_inflex_2com_pkm_bus_0_0) --1 InvestmentFlowBlock_total(BEV_inflex_2com_pkm_bus_0) -<= 0 - -c_u_InvestmentFlowBlock_max(BEV_inflex_2com_pkm_bus_0_1)_: -+1 flow(BEV_inflex_2com_pkm_bus_0_1) --1 InvestmentFlowBlock_total(BEV_inflex_2com_pkm_bus_0) -<= 0 - -c_u_InvestmentFlowBlock_max(BEV_inflex_2com_pkm_bus_0_2)_: -+1 flow(BEV_inflex_2com_pkm_bus_0_2) --1 InvestmentFlowBlock_total(BEV_inflex_2com_pkm_bus_0) -<= 0 - c_u_InvestmentFlowBlock_max(BEV_V2G_storage_BEV_V2G_bus_0_0)_: +1 flow(BEV_V2G_storage_BEV_V2G_bus_0_0) -1 InvestmentFlowBlock_total(BEV_V2G_storage_BEV_V2G_bus_0) @@ -317,6 +302,21 @@ c_u_InvestmentFlowBlock_max(BEV_V2G_storage_BEV_V2G_bus_0_2)_: -1 InvestmentFlowBlock_total(BEV_V2G_storage_BEV_V2G_bus_0) <= 0 +c_u_InvestmentFlowBlock_max(BEV_V2G_v2g_el_bus_0_0)_: ++1 flow(BEV_V2G_v2g_el_bus_0_0) +-1 InvestmentFlowBlock_total(BEV_V2G_v2g_el_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_V2G_v2g_el_bus_0_1)_: ++1 flow(BEV_V2G_v2g_el_bus_0_1) +-1 InvestmentFlowBlock_total(BEV_V2G_v2g_el_bus_0) +<= 0 + +c_u_InvestmentFlowBlock_max(BEV_V2G_v2g_el_bus_0_2)_: ++1 flow(BEV_V2G_v2g_el_bus_0_2) +-1 InvestmentFlowBlock_total(BEV_V2G_v2g_el_bus_0) +<= 0 + c_u_InvestmentFlowBlock_max(BEV_V2G_2com_pkm_bus_0_0)_: +1 flow(BEV_V2G_2com_pkm_bus_0_0) -1 InvestmentFlowBlock_total(BEV_V2G_2com_pkm_bus_0) @@ -332,34 +332,34 @@ c_u_InvestmentFlowBlock_max(BEV_V2G_2com_pkm_bus_0_2)_: -1 InvestmentFlowBlock_total(BEV_V2G_2com_pkm_bus_0) <= 0 -c_u_InvestmentFlowBlock_max(BEV_V2G_v2g_el_bus_0_0)_: -+1 flow(BEV_V2G_v2g_el_bus_0_0) --1 InvestmentFlowBlock_total(BEV_V2G_v2g_el_bus_0) +c_u_InvestmentFlowBlock_max(el_bus_BEV_G2V_storage_0_0)_: ++1 flow(el_bus_BEV_G2V_storage_0_0) +-1 InvestmentFlowBlock_total(el_bus_BEV_G2V_storage_0) <= 0 -c_u_InvestmentFlowBlock_max(BEV_V2G_v2g_el_bus_0_1)_: -+1 flow(BEV_V2G_v2g_el_bus_0_1) --1 InvestmentFlowBlock_total(BEV_V2G_v2g_el_bus_0) +c_u_InvestmentFlowBlock_max(el_bus_BEV_G2V_storage_0_1)_: ++1 flow(el_bus_BEV_G2V_storage_0_1) +-1 InvestmentFlowBlock_total(el_bus_BEV_G2V_storage_0) <= 0 -c_u_InvestmentFlowBlock_max(BEV_V2G_v2g_el_bus_0_2)_: -+1 flow(BEV_V2G_v2g_el_bus_0_2) --1 InvestmentFlowBlock_total(BEV_V2G_v2g_el_bus_0) +c_u_InvestmentFlowBlock_max(el_bus_BEV_G2V_storage_0_2)_: ++1 flow(el_bus_BEV_G2V_storage_0_2) +-1 InvestmentFlowBlock_total(el_bus_BEV_G2V_storage_0) <= 0 -c_u_InvestmentFlowBlock_max(BEV_G2V_2com_pkm_bus_0_0)_: -+1 flow(BEV_G2V_2com_pkm_bus_0_0) --1 InvestmentFlowBlock_total(BEV_G2V_2com_pkm_bus_0) +c_u_InvestmentFlowBlock_max(BEV_inflex_2com_pkm_bus_0_0)_: ++1 flow(BEV_inflex_2com_pkm_bus_0_0) +-1 InvestmentFlowBlock_total(BEV_inflex_2com_pkm_bus_0) <= 0 -c_u_InvestmentFlowBlock_max(BEV_G2V_2com_pkm_bus_0_1)_: -+1 flow(BEV_G2V_2com_pkm_bus_0_1) --1 InvestmentFlowBlock_total(BEV_G2V_2com_pkm_bus_0) +c_u_InvestmentFlowBlock_max(BEV_inflex_2com_pkm_bus_0_1)_: ++1 flow(BEV_inflex_2com_pkm_bus_0_1) +-1 InvestmentFlowBlock_total(BEV_inflex_2com_pkm_bus_0) <= 0 -c_u_InvestmentFlowBlock_max(BEV_G2V_2com_pkm_bus_0_2)_: -+1 flow(BEV_G2V_2com_pkm_bus_0_2) --1 InvestmentFlowBlock_total(BEV_G2V_2com_pkm_bus_0) +c_u_InvestmentFlowBlock_max(BEV_inflex_2com_pkm_bus_0_2)_: ++1 flow(BEV_inflex_2com_pkm_bus_0_2) +-1 InvestmentFlowBlock_total(BEV_inflex_2com_pkm_bus_0) <= 0 c_e_InvestmentFlowBlock_mob_share_BEV_V2G_0_: @@ -381,43 +381,38 @@ c_e_InvestmentFlowBlock_mob_share_BEV_G2V_0_: = 0 c_e_InvestmentFlowBlock__BEV_V2G_equal_invest_0_(0)__: -+1 InvestmentFlowBlock_invest(BEV_V2G_storage_BEV_V2G_bus_0) --1 InvestmentFlowBlock_invest(BEV_V2G_v2g_el_bus_0) +-1 InvestmentFlowBlock_invest(BEV_V2G_storage_BEV_V2G_bus_0) ++1 InvestmentFlowBlock_invest(BEV_V2G_v2g_el_bus_0) = 0 c_e_InvestmentFlowBlock__BEV_V2G_equal_invest_1_(0)__: --1 InvestmentFlowBlock_invest(el_bus_BEV_V2G_storage_0) -+1 InvestmentFlowBlock_invest(BEV_V2G_v2g_el_bus_0) ++1 InvestmentFlowBlock_invest(BEV_V2G_storage_BEV_V2G_bus_0) +-1 InvestmentFlowBlock_invest(BEV_V2G_2com_pkm_bus_0) = 0 c_e_InvestmentFlowBlock__BEV_V2G_equal_invest_2_(0)__: -+1 InvestmentFlowBlock_invest(el_bus_BEV_V2G_storage_0) --1 InvestmentFlowBlock_invest(BEV_V2G_2com_pkm_bus_0) +-1 InvestmentFlowBlock_invest(el_bus_BEV_V2G_storage_0) ++1 InvestmentFlowBlock_invest(BEV_V2G_2com_pkm_bus_0) = 0 c_e_InvestmentFlowBlock__BEV_inflex_equal_invest_0_(0)__: -+1 InvestmentFlowBlock_invest(BEV_inflex_2com_pkm_bus_0) ++1 InvestmentFlowBlock_invest(el_bus_BEV_inflex_storage_0) -1 InvestmentFlowBlock_invest(BEV_inflex_storage_BEV_inflex_bus_0) = 0 c_e_InvestmentFlowBlock__BEV_inflex_equal_invest_1_(0)__: --1 InvestmentFlowBlock_invest(el_bus_BEV_inflex_storage_0) +1 InvestmentFlowBlock_invest(BEV_inflex_storage_BEV_inflex_bus_0) +-1 InvestmentFlowBlock_invest(BEV_inflex_2com_pkm_bus_0) = 0 c_e_InvestmentFlowBlock__BEV_G2V_equal_invest_0_(0)__: -+1 InvestmentFlowBlock_invest(el_bus_BEV_G2V_storage_0) --1 InvestmentFlowBlock_invest(BEV_G2V_2com_pkm_bus_0) ++1 InvestmentFlowBlock_invest(BEV_G2V_2com_pkm_bus_0) +-1 InvestmentFlowBlock_invest(el_bus_BEV_G2V_storage_0) = 0 c_e_InvestmentFlowBlock__BEV_G2V_equal_invest_1_(0)__: -1 InvestmentFlowBlock_invest(BEV_G2V_storage_BEV_G2V_bus_0) -+1 InvestmentFlowBlock_invest(BEV_G2V_2com_pkm_bus_0) -= 0 - -c_e_GenericInvestmentStorageBlock_total_storage_rule(BEV_inflex_storage_0)_: -+1 GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) --1 GenericInvestmentStorageBlock_invest(BEV_inflex_storage_0) ++1 InvestmentFlowBlock_invest(el_bus_BEV_G2V_storage_0) = 0 c_e_GenericInvestmentStorageBlock_total_storage_rule(BEV_G2V_storage_0)_: @@ -430,8 +425,9 @@ c_e_GenericInvestmentStorageBlock_total_storage_rule(BEV_V2G_storage_0)_: -1 GenericInvestmentStorageBlock_invest(BEV_V2G_storage_0) = 0 -c_e_GenericInvestmentStorageBlock_init_content_fix(BEV_inflex_storage)_: -+1 GenericInvestmentStorageBlock_init_content(BEV_inflex_storage) +c_e_GenericInvestmentStorageBlock_total_storage_rule(BEV_inflex_storage_0)_: ++1 GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) +-1 GenericInvestmentStorageBlock_invest(BEV_inflex_storage_0) = 0 c_e_GenericInvestmentStorageBlock_init_content_fix(BEV_G2V_storage)_: @@ -442,11 +438,8 @@ c_e_GenericInvestmentStorageBlock_init_content_fix(BEV_V2G_storage)_: +1 GenericInvestmentStorageBlock_init_content(BEV_V2G_storage) = 0 -c_e_GenericInvestmentStorageBlock_balance_first(BEV_inflex_storage)_: --0.6944444444444445 flow(el_bus_BEV_inflex_storage_0_0) -+1.2 flow(BEV_inflex_storage_BEV_inflex_bus_0_0) --1 GenericInvestmentStorageBlock_init_content(BEV_inflex_storage) -+1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_0) +c_e_GenericInvestmentStorageBlock_init_content_fix(BEV_inflex_storage)_: ++1 GenericInvestmentStorageBlock_init_content(BEV_inflex_storage) = 0 c_e_GenericInvestmentStorageBlock_balance_first(BEV_G2V_storage)_: @@ -463,18 +456,11 @@ c_e_GenericInvestmentStorageBlock_balance_first(BEV_V2G_storage)_: +1 GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_0) = 0 -c_e_GenericInvestmentStorageBlock_balance(BEV_inflex_storage_0_1)_: --0.6944444444444445 flow(el_bus_BEV_inflex_storage_0_1) -+1.2 flow(BEV_inflex_storage_BEV_inflex_bus_0_1) --1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_0) -+1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_1) -= 0 - -c_e_GenericInvestmentStorageBlock_balance(BEV_inflex_storage_0_2)_: --0.6944444444444445 flow(el_bus_BEV_inflex_storage_0_2) -+1.2 flow(BEV_inflex_storage_BEV_inflex_bus_0_2) --1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_1) -+1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_2) +c_e_GenericInvestmentStorageBlock_balance_first(BEV_inflex_storage)_: +-0.6944444444444445 flow(el_bus_BEV_inflex_storage_0_0) ++1.2 flow(BEV_inflex_storage_BEV_inflex_bus_0_0) +-1 GenericInvestmentStorageBlock_init_content(BEV_inflex_storage) ++1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_0) = 0 c_e_GenericInvestmentStorageBlock_balance(BEV_G2V_storage_0_1)_: @@ -505,8 +491,17 @@ c_e_GenericInvestmentStorageBlock_balance(BEV_V2G_storage_0_2)_: +1 GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_2) = 0 -c_e_GenericInvestmentStorageBlock_balanced_cstr(BEV_inflex_storage)_: --1 GenericInvestmentStorageBlock_init_content(BEV_inflex_storage) +c_e_GenericInvestmentStorageBlock_balance(BEV_inflex_storage_0_1)_: +-0.6944444444444445 flow(el_bus_BEV_inflex_storage_0_1) ++1.2 flow(BEV_inflex_storage_BEV_inflex_bus_0_1) +-1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_0) ++1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_1) += 0 + +c_e_GenericInvestmentStorageBlock_balance(BEV_inflex_storage_0_2)_: +-0.6944444444444445 flow(el_bus_BEV_inflex_storage_0_2) ++1.2 flow(BEV_inflex_storage_BEV_inflex_bus_0_2) +-1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_1) +1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_2) = 0 @@ -520,9 +515,9 @@ c_e_GenericInvestmentStorageBlock_balanced_cstr(BEV_V2G_storage)_: +1 GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_2) = 0 -c_e_GenericInvestmentStorageBlock_power_coupled(BEV_inflex_storage_0)_: --1 InvestmentFlowBlock_total(el_bus_BEV_inflex_storage_0) -+1 InvestmentFlowBlock_total(BEV_inflex_storage_BEV_inflex_bus_0) +c_e_GenericInvestmentStorageBlock_balanced_cstr(BEV_inflex_storage)_: +-1 GenericInvestmentStorageBlock_init_content(BEV_inflex_storage) ++1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_2) = 0 c_e_GenericInvestmentStorageBlock_power_coupled(BEV_G2V_storage_0)_: @@ -535,9 +530,9 @@ c_e_GenericInvestmentStorageBlock_power_coupled(BEV_V2G_storage_0)_: +1 InvestmentFlowBlock_total(BEV_V2G_storage_BEV_V2G_bus_0) = 0 -c_e_GenericInvestmentStorageBlock_storage_capacity_inflow(BEV_inflex_storage_0)_: -+1 InvestmentFlowBlock_total(el_bus_BEV_inflex_storage_0) --0.3333333333333333 GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) +c_e_GenericInvestmentStorageBlock_power_coupled(BEV_inflex_storage_0)_: +-1 InvestmentFlowBlock_total(el_bus_BEV_inflex_storage_0) ++1 InvestmentFlowBlock_total(BEV_inflex_storage_BEV_inflex_bus_0) = 0 c_e_GenericInvestmentStorageBlock_storage_capacity_inflow(BEV_G2V_storage_0)_: @@ -550,8 +545,8 @@ c_e_GenericInvestmentStorageBlock_storage_capacity_inflow(BEV_V2G_storage_0)_: -0.3333333333333333 GenericInvestmentStorageBlock_total(BEV_V2G_storage_0) = 0 -c_e_GenericInvestmentStorageBlock_storage_capacity_outflow(BEV_inflex_storage_0)_: -+1 InvestmentFlowBlock_total(BEV_inflex_storage_BEV_inflex_bus_0) +c_e_GenericInvestmentStorageBlock_storage_capacity_inflow(BEV_inflex_storage_0)_: ++1 InvestmentFlowBlock_total(el_bus_BEV_inflex_storage_0) -0.3333333333333333 GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) = 0 @@ -565,20 +560,10 @@ c_e_GenericInvestmentStorageBlock_storage_capacity_outflow(BEV_V2G_storage_0)_: -0.3333333333333333 GenericInvestmentStorageBlock_total(BEV_V2G_storage_0) = 0 -c_u_GenericInvestmentStorageBlock_max_storage_content(BEV_inflex_storage_0_0)_: --1 GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) -+1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_0) -<= 0 - -c_u_GenericInvestmentStorageBlock_max_storage_content(BEV_inflex_storage_0_1)_: --1 GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) -+1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_1) -<= 0 - -c_u_GenericInvestmentStorageBlock_max_storage_content(BEV_inflex_storage_0_2)_: --1 GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) -+1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_2) -<= 0 +c_e_GenericInvestmentStorageBlock_storage_capacity_outflow(BEV_inflex_storage_0)_: ++1 InvestmentFlowBlock_total(BEV_inflex_storage_BEV_inflex_bus_0) +-0.3333333333333333 GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) += 0 c_u_GenericInvestmentStorageBlock_max_storage_content(BEV_G2V_storage_0_0)_: -1 GenericInvestmentStorageBlock_total(BEV_G2V_storage_0) @@ -610,17 +595,32 @@ c_u_GenericInvestmentStorageBlock_max_storage_content(BEV_V2G_storage_0_2)_: +1 GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_2) <= 0 +c_u_GenericInvestmentStorageBlock_max_storage_content(BEV_inflex_storage_0_0)_: +-1 GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) ++1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_0) +<= 0 + +c_u_GenericInvestmentStorageBlock_max_storage_content(BEV_inflex_storage_0_1)_: +-1 GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) ++1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_1) +<= 0 + +c_u_GenericInvestmentStorageBlock_max_storage_content(BEV_inflex_storage_0_2)_: +-1 GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) ++1 GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_2) +<= 0 + bounds 0 <= InvestmentFlowBlock_invest(el_bus_BEV_inflex_storage_0) <= +inf + 0 <= InvestmentFlowBlock_invest(BEV_inflex_storage_BEV_inflex_bus_0) <= +inf + 0 <= InvestmentFlowBlock_invest(BEV_G2V_2com_pkm_bus_0) <= +inf 0 <= InvestmentFlowBlock_invest(el_bus_BEV_V2G_storage_0) <= +inf 0 <= InvestmentFlowBlock_invest(BEV_G2V_storage_BEV_G2V_bus_0) <= +inf - 0 <= InvestmentFlowBlock_invest(el_bus_BEV_G2V_storage_0) <= +inf - 0 <= InvestmentFlowBlock_invest(BEV_inflex_2com_pkm_bus_0) <= +inf 0 <= InvestmentFlowBlock_invest(BEV_V2G_storage_BEV_V2G_bus_0) <= +inf - 0 <= InvestmentFlowBlock_invest(BEV_inflex_storage_BEV_inflex_bus_0) <= +inf - 0 <= InvestmentFlowBlock_invest(BEV_V2G_2com_pkm_bus_0) <= +inf 0 <= InvestmentFlowBlock_invest(BEV_V2G_v2g_el_bus_0) <= +inf - 0 <= InvestmentFlowBlock_invest(BEV_G2V_2com_pkm_bus_0) <= +inf + 0 <= InvestmentFlowBlock_invest(BEV_V2G_2com_pkm_bus_0) <= +inf + 0 <= InvestmentFlowBlock_invest(el_bus_BEV_G2V_storage_0) <= +inf + 0 <= InvestmentFlowBlock_invest(BEV_inflex_2com_pkm_bus_0) <= +inf 0 <= flow(el_bus_BEV_V2G_storage_0_0) <= +inf 0 <= flow(el_bus_BEV_V2G_storage_0_1) <= +inf 0 <= flow(el_bus_BEV_V2G_storage_0_2) <= +inf @@ -664,31 +664,31 @@ bounds 0 <= flow(BEV_G2V_2com_pkm_bus_0_1) <= +inf 0 <= flow(BEV_G2V_2com_pkm_bus_0_2) <= +inf 0 <= InvestmentFlowBlock_total(el_bus_BEV_inflex_storage_0) <= +inf + 0 <= InvestmentFlowBlock_total(BEV_inflex_storage_BEV_inflex_bus_0) <= +inf + 0 <= InvestmentFlowBlock_total(BEV_G2V_2com_pkm_bus_0) <= +inf 0 <= InvestmentFlowBlock_total(el_bus_BEV_V2G_storage_0) <= +inf 0 <= InvestmentFlowBlock_total(BEV_G2V_storage_BEV_G2V_bus_0) <= +inf - 0 <= InvestmentFlowBlock_total(el_bus_BEV_G2V_storage_0) <= +inf - 0 <= InvestmentFlowBlock_total(BEV_inflex_2com_pkm_bus_0) <= +inf 0 <= InvestmentFlowBlock_total(BEV_V2G_storage_BEV_V2G_bus_0) <= +inf - 0 <= InvestmentFlowBlock_total(BEV_inflex_storage_BEV_inflex_bus_0) <= +inf - 0 <= InvestmentFlowBlock_total(BEV_V2G_2com_pkm_bus_0) <= +inf 0 <= InvestmentFlowBlock_total(BEV_V2G_v2g_el_bus_0) <= +inf - 0 <= InvestmentFlowBlock_total(BEV_G2V_2com_pkm_bus_0) <= +inf - 0 <= GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) <= +inf + 0 <= InvestmentFlowBlock_total(BEV_V2G_2com_pkm_bus_0) <= +inf + 0 <= InvestmentFlowBlock_total(el_bus_BEV_G2V_storage_0) <= +inf + 0 <= InvestmentFlowBlock_total(BEV_inflex_2com_pkm_bus_0) <= +inf 0 <= GenericInvestmentStorageBlock_total(BEV_G2V_storage_0) <= +inf 0 <= GenericInvestmentStorageBlock_total(BEV_V2G_storage_0) <= +inf - 0 <= GenericInvestmentStorageBlock_invest(BEV_inflex_storage_0) <= +inf + 0 <= GenericInvestmentStorageBlock_total(BEV_inflex_storage_0) <= +inf 0 <= GenericInvestmentStorageBlock_invest(BEV_G2V_storage_0) <= +inf 0 <= GenericInvestmentStorageBlock_invest(BEV_V2G_storage_0) <= +inf - 0 <= GenericInvestmentStorageBlock_init_content(BEV_inflex_storage) <= +inf + 0 <= GenericInvestmentStorageBlock_invest(BEV_inflex_storage_0) <= +inf 0 <= GenericInvestmentStorageBlock_init_content(BEV_G2V_storage) <= +inf 0 <= GenericInvestmentStorageBlock_init_content(BEV_V2G_storage) <= +inf - 0 <= GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_0) <= +inf - 0 <= GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_1) <= +inf - 0 <= GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_2) <= +inf + 0 <= GenericInvestmentStorageBlock_init_content(BEV_inflex_storage) <= +inf 0 <= GenericInvestmentStorageBlock_storage_content(BEV_G2V_storage_0) <= +inf 0 <= GenericInvestmentStorageBlock_storage_content(BEV_G2V_storage_1) <= +inf 0 <= GenericInvestmentStorageBlock_storage_content(BEV_G2V_storage_2) <= +inf 0 <= GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_0) <= +inf 0 <= GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_1) <= +inf 0 <= GenericInvestmentStorageBlock_storage_content(BEV_V2G_storage_2) <= +inf + 0 <= GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_0) <= +inf + 0 <= GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_1) <= +inf + 0 <= GenericInvestmentStorageBlock_storage_content(BEV_inflex_storage_2) <= +inf end