diff --git a/AUTHORS.rst b/AUTHORS.rst index 761a1d08..9fd7ba93 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -15,3 +15,4 @@ Authors * Julian Endres * Felix Maurer * Pierre-Francois Duc +* Sabine Haas diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ceebef4f..3c3fbc83 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,7 @@ Unreleased Features * Improve the function to infer package metadata `#173 `_ +* Add facades `CommodityGHG` and `ConversionGHG` to enable multiple output flows (emissions) `#180 `_ Fixes diff --git a/docs/usage.rst b/docs/usage.rst index 00f7d608..46cdb11f 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -56,9 +56,11 @@ Currently we provide the following facades: * :py:class:`~oemof.tabular.facades.ExtractionTurbine` * :py:class:`~oemof.tabular.facades.Commodity` * :py:class:`~oemof.tabular.facades.Conversion` -* :py:class:`~oemof.tabular.facades.Load`. +* :py:class:`~oemof.tabular.facades.Load` * :py:class:`~oemof.tabular.facades.Link` * :py:class:`~oemof.tabular.facades.Excess` +* :py:class:`~oemof.tabular.facades.CommodityGHG`: a commodity unit with green house gases +* :py:class:`~oemof.tabular.facades.ConversionGHG`: a conversion unit with green house gases. These can be mixed with all oemof solph classes if your are scripting. diff --git a/src/oemof/tabular/examples/datapackages/GHG/README.md b/src/oemof/tabular/examples/datapackages/GHG/README.md new file mode 100644 index 00000000..ad9b3e8a --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/GHG/README.md @@ -0,0 +1,5 @@ +# Green house gases (GHG) 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/GHG/data/elements/bus.csv b/src/oemof/tabular/examples/datapackages/GHG/data/elements/bus.csv new file mode 100644 index 00000000..a1eb6e35 --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/GHG/data/elements/bus.csv @@ -0,0 +1,6 @@ +name;type;balanced +co2;bus;false +ch4;bus;false +n2o;bus;false +el_bus;bus;true +gas_bus;bus;true diff --git a/src/oemof/tabular/examples/datapackages/GHG/data/elements/commodityGHG_import.csv b/src/oemof/tabular/examples/datapackages/GHG/data/elements/commodityGHG_import.csv new file mode 100644 index 00000000..fc5e7c20 --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/GHG/data/elements/commodityGHG_import.csv @@ -0,0 +1,3 @@ +name;type;carrier;amount;bus;emission_bus_0;marginal_cost;output_parameters;emission_factor_co2 +green_gas_import;commodity_ghg;gas;300;gas_bus;co2;20;{"max": [1, 1, 1]};-56 +fossile_gas_import;commodity_ghg;gas;200;gas_bus;co2;10;{"max": [1, 0, 0]};56 diff --git a/src/oemof/tabular/examples/datapackages/GHG/data/elements/conversionGHG.csv b/src/oemof/tabular/examples/datapackages/GHG/data/elements/conversionGHG.csv new file mode 100644 index 00000000..9db64f03 --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/GHG/data/elements/conversionGHG.csv @@ -0,0 +1,2 @@ +name;type;carrier;tech;capacity;from_bus;to_bus;emission_bus_0;emission_bus_1;emission_bus_2;marginal_cost;efficiency;emission_factor_co2;emission_factor_ch4;emission_factor_n2o +gtGHG;conversion_ghg;electricity;gt;300;gas_bus;el_bus;co2;ch4;n2o;10;0.5;56;0.4;0.017 diff --git a/src/oemof/tabular/examples/datapackages/GHG/data/elements/excess.csv b/src/oemof/tabular/examples/datapackages/GHG/data/elements/excess.csv new file mode 100644 index 00000000..38f2b05d --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/GHG/data/elements/excess.csv @@ -0,0 +1,2 @@ +name;type;bus;capacity +gas_excess;excess;gas_bus;1000000000 diff --git a/src/oemof/tabular/examples/datapackages/GHG/data/elements/load.csv b/src/oemof/tabular/examples/datapackages/GHG/data/elements/load.csv new file mode 100644 index 00000000..97ecea55 --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/GHG/data/elements/load.csv @@ -0,0 +1,2 @@ +name;amount;profile;type;bus +demand0;100;electricity-load-profile;load;el_bus diff --git a/src/oemof/tabular/examples/datapackages/GHG/data/elements/shortage.csv b/src/oemof/tabular/examples/datapackages/GHG/data/elements/shortage.csv new file mode 100644 index 00000000..63b5bb56 --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/GHG/data/elements/shortage.csv @@ -0,0 +1,2 @@ +name;type;carrier;tech;bus;capacity;marginal_cost +el_shortage;shortage;electricity;shortage;el_bus;100000000;100000 diff --git a/src/oemof/tabular/examples/datapackages/GHG/data/sequences/load_profile.csv b/src/oemof/tabular/examples/datapackages/GHG/data/sequences/load_profile.csv new file mode 100644 index 00000000..ea470ad8 --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/GHG/data/sequences/load_profile.csv @@ -0,0 +1,4 @@ +timeindex,electricity-load-profile +2011-01-01T00:00:00Z,1 +2011-01-01T01:00:00Z,0.5 +2011-01-01T02:00:00Z,0.1 diff --git a/src/oemof/tabular/examples/datapackages/GHG/datapackage.json b/src/oemof/tabular/examples/datapackages/GHG/datapackage.json new file mode 100644 index 00000000..6050686a --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/GHG/datapackage.json @@ -0,0 +1,325 @@ +{ + "profile": "tabular-data-package", + "name": "GHG-test", + "oemof_tabular_version": "0.0.6dev", + "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/conversionGHG.csv", + "profile": "tabular-data-resource", + "name": "conversionGHG", + "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": "from_bus", + "type": "string", + "format": "default" + }, + { + "name": "to_bus", + "type": "string", + "format": "default" + }, + { + "name": "emission_bus_0", + "type": "string", + "format": "default" + }, + { + "name": "emission_bus_1", + "type": "string", + "format": "default" + }, + { + "name": "emission_bus_2", + "type": "string", + "format": "default" + }, + { + "name": "marginal_cost", + "type": "integer", + "format": "default" + }, + { + "name": "efficiency", + "type": "number", + "format": "default" + }, + { + "name": "emission_factor_co2", + "type": "number", + "format": "default" + }, + { + "name": "emission_factor_ch4", + "type": "number", + "format": "default" + }, + { + "name": "emission_factor_n2o", + "type": "number", + "format": "default" + } + ], + "foreignKeys": [ + { + "fields": "from_bus", + "reference": { + "resource": "bus", + "fields": "name" + } + }, + { + "fields": "to_bus", + "reference": { + "resource": "bus", + "fields": "name" + } + }, + { + "fields": "emission_bus_0", + "reference": { + "resource": "bus", + "fields": "name" + } + }, + { + "fields": "emission_bus_1", + "reference": { + "resource": "bus", + "fields": "name" + } + }, + { + "fields": "emission_bus_2", + "reference": { + "resource": "bus", + "fields": "name" + } + } + ], + "missingValues": [ + "" + ], + "primaryKey": "name" + } + }, + { + "path": "data/elements/commodityGHG_import.csv", + "profile": "tabular-data-resource", + "name": "commodityGHG_import", + "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": "amount", + "type": "integer", + "format": "default" + }, + { + "name": "bus", + "type": "string", + "format": "default" + }, + { + "name": "emission_bus_0", + "type": "string", + "format": "default" + }, + { + "name": "marginal_cost", + "type": "integer", + "format": "default" + }, + { + "name": "output_parameters", + "type": "object", + "format": "default" + }, + { + "name": "emission_factor_co2", + "type": "number", + "format": "default" + } + ], + "missingValues": [ + "" + ], + "primaryKey": "name", + "foreignKeys": [ + { + "fields": "bus", + "reference": { + "resource": "bus", + "fields": "name" + } + }, + { + "fields": "emission_bus_0", + "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/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": [ + "" + ] + } + } + ] +} diff --git a/src/oemof/tabular/examples/datapackages/GHG/scripts/infer.py b/src/oemof/tabular/examples/datapackages/GHG/scripts/infer.py new file mode 100644 index 00000000..87aa7d77 --- /dev/null +++ b/src/oemof/tabular/examples/datapackages/GHG/scripts/infer.py @@ -0,0 +1,25 @@ +""" +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="GHG-test", + foreign_keys={ + "bus": ["commodity_ghg", "load", "excess", "gas_import"], + "profile": ["load"], + "from_bus": ["conversion_ghg"], + "to_bus": ["conversion_ghg"], + "emission_bus_0": ["conversion_ghg", "commodity_ghg"], + "emission_bus_1": ["conversion_ghg"], + "emission_bus_2": ["conversion_ghg"], + }, + **kwargs, +) diff --git a/src/oemof/tabular/examples/scripting/compute.py b/src/oemof/tabular/examples/scripting/compute.py index 18f1303b..8d85b461 100644 --- a/src/oemof/tabular/examples/scripting/compute.py +++ b/src/oemof/tabular/examples/scripting/compute.py @@ -19,6 +19,7 @@ "investment_multi_period", "foreignkeys", "emission_constraint", + "GHG", ] for example in examples: print("Running compute example with datapackage {}".format(example)) diff --git a/src/oemof/tabular/facades/__init__.py b/src/oemof/tabular/facades/__init__.py index ff64bb39..4114c87d 100644 --- a/src/oemof/tabular/facades/__init__.py +++ b/src/oemof/tabular/facades/__init__.py @@ -4,7 +4,9 @@ from .backpressure_turbine import BackpressureTurbine from .commodity import Commodity +from .commodity_ghg import CommodityGHG from .conversion import Conversion +from .conversion_ghg import ConversionGHG from .dispatchable import Dispatchable from .excess import Excess from .extraction_turbine import ExtractionTurbine @@ -22,7 +24,9 @@ "bus": Bus, "heatpump": HeatPump, "commodity": Commodity, + "commodity_ghg": CommodityGHG, "conversion": Conversion, + "conversion_ghg": ConversionGHG, "dispatchable": Dispatchable, "electrical bus": ElectricalBus, "electrical line": ElectricalLine, diff --git a/src/oemof/tabular/facades/commodity.py b/src/oemof/tabular/facades/commodity.py index 0659ee10..3c19aab2 100644 --- a/src/oemof/tabular/facades/commodity.py +++ b/src/oemof/tabular/facades/commodity.py @@ -16,11 +16,11 @@ class Commodity(Source, Facade): bus: oemof.solph.Bus An oemof bus instance where the unit is connected to with its output amount: numeric - Total available amount to be used within the complete timehorzion + Total available amount to be used within the complete time horizon of the problem marginal_cost: numeric Marginal cost for one unit used commodity - output_paramerters: dict (optional) + output_parameters: dict (optional) Parameters to set on the output edge of the component (see. oemof.solph Edge/Flow class for possible arguments) diff --git a/src/oemof/tabular/facades/commodity_ghg.py b/src/oemof/tabular/facades/commodity_ghg.py new file mode 100644 index 00000000..a454608b --- /dev/null +++ b/src/oemof/tabular/facades/commodity_ghg.py @@ -0,0 +1,211 @@ +import dataclasses + +from oemof.solph._plumbing import sequence +from oemof.solph.flows import Flow +from pyomo.core import BuildAction, Constraint +from pyomo.core.base.block import ScalarBlock + +from oemof import solph + +from .commodity import Commodity + + +@dataclasses.dataclass(unsafe_hash=False, frozen=False, eq=False) +class CommodityGHG(Commodity): + r""" + Commodity element with one output and additionally green house gas outputs. + + Parameters + ---------- + bus: oemof.solph.Bus + An oemof bus instance where the unit is connected to with its output + amount: numeric + Total available amount to be used within the complete time horizon + of the problem + marginal_cost: numeric + Marginal cost for one unit used commodity + output_parameters: dict (optional) + Parameters to set on the output edge of the component (see. oemof.solph + Edge/Flow class for possible arguments) + + + .. math:: + \sum_{t} x^{flow}(t) \leq c^{amount} + + Notes + ----- + Emission buses carring the green house gases (GHG) are defined by starting + with 'emission_bus', see Examples section. + Emission factors are defined by the following naming convention: + 'emission_factor_. + The realation between the main output (`bus`) and the emission buses are + set via :class:`~oemof.tabular.facades.commodity_ghg.CommodityGHGBlock`. + + For additional constraints set through `output_parameters` see + oemof.solph.Flow class. + + Examples + --------- + Defining a ConversionGHG: + + >>> from oemof import solph + + >>> bus_gas = solph.Bus("gas") + >>> bus_co2 = solph.Bus("co2") + >>> bus_gas.type, bus_co2.type = "bus", "bus" + + >>> commodity = CommodityGHG( + ... label="gas-commodity", + ... bus=bus_gas, + ... emission_bus_0=bus_co2, + ... carrier="gas", + ... amount=1000, + ... marginal_cost=10, + ... output_parameters={"max": [0.9, 0.5, 0.4]}, + ... emission_factor_co2=56) + + >>> commodity.emission_factors[bus_co2].default + 56 + """ + + def __init__(self, **kwargs): + + super().__init__( + **kwargs, + ) + + buses = { + key: value + for key, value in kwargs.items() + if type(value) is type(solph.Bus()) + } + + self.build_solph_components() + self.init_emission_buses(kwargs) + self.emission_factors = self.init_emission_factors(buses, kwargs) + + def init_emission_buses(self, kwargs): + """Adds emissions buses as output flows and drops them from kwargs""" + for key, value in list(kwargs.items()): + if key.startswith("emission_bus"): + # then value is a solph.Bus object and is added to self.outputs + self.outputs.update({value: Flow(bidirectional=True)}) + kwargs.pop(key) + + def init_emission_factors(self, buses, kwargs): + """Returns emission factors as values in dict with buses as keys""" + emission_factors = {} + for key, value in list(kwargs.items()): + if key.startswith("emission_factor"): + bus_label = key.split("_")[-1] + try: + bus = [ + bus + for bus in buses.items() + if bus[1].label == bus_label + ][0][1] + except IndexError: + raise Warning( + f"Emission factor is given for a non-existent emission" + f" bus: '{bus_label}'. Check your inputs for " + f"'{self.label}' of type '{self.type}'. " + ) + emission_factors.update({bus: sequence(value)}) + kwargs.pop(key) + return emission_factors + + def constraint_group(self): + return CommodityGHGBlock + + +class CommodityGHGBlock(ScalarBlock): + r""" + Block for the linear relation of nodes with type + :class:`~oemof.tabular.facades.commodity_ghg.CommodityGHGBlock` + + **The following sets are created:** + + CommodityGHGs + A set with all + :class:`~oemof.tabular.facades.commodity_ghg.CommodityGHGBlock` + objects. + + **The following constraints are created:** + + Linear relation :attr:`om.CommodityGHGBlock.relation[o,t]` + .. math:: + P_{n.bus}(p, t) \cdot \eta_{o}(t) = P_{o}(p, t), \\ + \forall p, t \in \textrm{TIMEINDEX}, \\ + \forall n \in \textrm{CommodityGHGs}, \\ + \forall o \in \textrm{OUTPUTS} + + While OUPUTS the set of Bus objects connected with the output of + the CommodityGHG. The constraint above will be created for all OUTPUTS for + all TIMESTEPS. A CommodityGHG with two outflows for one day with an hourly + resolution will lead to 48 constraints. + + The index :math: n is the index for the Source node itself. Therefore, + a `flow[i, n, p, t]` is a flow from the Bus i to the Source n at + time index p, t. + + ====================== ============================ ==================== + symbol attribute explanation + ====================== ============================ ==================== + :math:`P_{n,n.bus}(p, t)` `flow[n, n.bus, p, t]` CommodityGHG, outflow + + :math:`P_{n,o}(p, t)` `flow[n, o, p, t]` CommodityGHG, outflow + + :math:`\eta_{o}(t)` `emission_factor[n, o, t]` Outflow, efficiency + + ====================== ============================ ==================== + + """ + + CONSTRAINT_GROUP = True + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def _create(self, group=None): + """ + Creates the linear constraint for the class:`CommodityGHGBlock` block. + """ + if group is None: + return None + + m = self.parent_block() + + out_flows = {n: [o for o in n.outputs.keys()] for n in group} + + self.relation = Constraint( + [ + (n, o, p, t) + for p, t in m.TIMEINDEX + for n in group + for o in out_flows[n] + ], + noruleinit=True, + ) + + def _emission_relation(block): + for p, t in m.TIMEINDEX: + for n in group: + for o in out_flows[n]: + # only emission buses + if o is not n.bus: + try: + lhs = ( + m.flow[n, n.bus, p, t] + * n.emission_factors[o][t] + ) + rhs = m.flow[n, o, p, t] + block.relation.add((n, o, p, t), (lhs == rhs)) + except KeyError: + raise KeyError( + "Error in constraint creation", + "source: {0}, target: {1}. You supposedly " + "forgot to define an emission factor for " + "this target.".format(n.label, o.label), + ) + + self.relation_build = BuildAction(rule=_emission_relation) diff --git a/src/oemof/tabular/facades/conversion_ghg.py b/src/oemof/tabular/facades/conversion_ghg.py new file mode 100644 index 00000000..2e34530c --- /dev/null +++ b/src/oemof/tabular/facades/conversion_ghg.py @@ -0,0 +1,138 @@ +import dataclasses + +from oemof.solph._plumbing import sequence +from oemof.solph.flows import Flow + +from oemof import solph + +from .conversion import Conversion + + +@dataclasses.dataclass(unsafe_hash=False, frozen=False, eq=False) +class ConversionGHG(Conversion): + r""" + Conversion unit with one input, one output and green house gas outputs. + + Cost parameters like `carrier_cost` are associated with `from_bus` like in + Conversion facade. + The emission factors are also associated to `from_bus` + + + Parameters + ---------- + from_bus: oemof.solph.Bus + An oemof bus instance where the conversion unit is connected to with + its input. + to_bus: oemof.solph.Bus + An oemof bus instance where the conversion unit is connected to with + its output. + capacity: numeric + The conversion capacity (output side) of the unit. + efficiency: numeric + Efficiency of the conversion unit (0 <= efficiency <= 1). Default: 1 + marginal_cost: numeric + Marginal cost for one unit of produced output. Default: 0 + carrier_cost: numeric + Carrier cost for one unit of used input. Default: 0 + capacity_cost: numeric + Investment costs per unit of output capacity. + If capacity is not set, this value will be used for optimizing the + conversion output capacity. + expandable: boolean or numeric (binary) + True, if capacity can be expanded within optimization. Default: False. + capacity_potential: numeric + Maximum invest capacity in unit of output capacity. + capacity_minimum: numeric + Minimum invest capacity in unit of output capacity. + input_parameters: dict (optional) + Set parameters on the input edge of the conversion unit + (see oemof.solph for more information on possible parameters) + ouput_parameters: dict (optional) + Set parameters on the output edge of the conversion unit + (see oemof.solph for more information on possible parameters) + + Notes + ----- + Emission buses carring the green house gases (GHG) are defined by starting + with 'emission_bus', see Examples section. + Emission factors are defined by the following naming convention: + 'emission_factor_. + + Examples + --------- + Defining a ConversionGHG: + + >>> from oemof import solph + + >>> bus_biomass = solph.Bus("biomass") + >>> bus_heat = solph.Bus("heat") + >>> bus_co2 = solph.Bus("co2") + + >>> bus_biomass.type, bus_heat.type, bus_co2.type = "bus", "bus", "bus" + + >>> conversion = ConversionGHG( + ... label="biomass_plant", + ... carrier="biomass", + ... tech="st", + ... from_bus=bus_biomass, + ... to_bus=bus_heat, + ... emission_bus_0=bus_co2, + ... capacity=100, + ... efficiency=0.4, + ... emission_factor_co2=56) + >>> conversion.conversion_factors[bus_co2].default + 56 + """ + + def __init__(self, **kwargs): + super().__init__( + **kwargs, + ) + + buses = { + key: value + for key, value in kwargs.items() + if type(value) is type(solph.Bus()) + } + + self.build_solph_components() # inputs, outputs, conversion_factors + self.init_emission_buses(kwargs) + self.init_emission_factors(buses, kwargs) + + def init_emission_buses(self, kwargs): + """Adds emissions buses as output flows and drops them from kwargs""" + for key, value in list(kwargs.items()): + if key.startswith("emission_bus"): + # then value is a solph.Bus object and is added to self.outputs + self.outputs.update({value: Flow()}) + kwargs.pop(key) + + def init_emission_factors(self, buses, kwargs): + """Adds emission factors as `conversion_factors""" + for key, value in list(kwargs.items()): + if key.startswith("emission_factor"): + bus_label = key.split("_")[-1] + try: + bus = [ + bus + for bus in buses.items() + if bus[1].label == bus_label + ][0][1] + except IndexError: + raise Warning( + f"Emission factor is given for a non-existent emission" + f" bus: '{bus_label}'. Check your inputs for " + f"'{self.label}' of type '{self.type}'. " + ) + self.conversion_factors.update({bus: sequence(value)}) + kwargs.pop(key) + + # check that every bus has a conversion factor, otherwise an error + # occurs in oemof.solph.components._converter.py + if not len(self.outputs) + len(self.inputs) == len( + self.conversion_factors + ): + raise Warning( + f"Every emission_bus needs an emission_factor. Check your " + f"inputs for '{self.label}' of type '{self.type}'." + ) diff --git a/tests/_files/lp_files/commodity_ghg.lp b/tests/_files/lp_files/commodity_ghg.lp new file mode 100644 index 00000000..48399040 --- /dev/null +++ b/tests/_files/lp_files/commodity_ghg.lp @@ -0,0 +1,63 @@ +\* Source Pyomo model name=Model *\ + +min +objective: ++10 flow(gas_commodity_gas_0_0) ++10 flow(gas_commodity_gas_0_1) ++10 flow(gas_commodity_gas_0_2) + +s.t. + +c_e_BusBlock_balance(co2_0_0)_: ++1 flow(gas_commodity_co2_0_0) += 0 + +c_e_BusBlock_balance(co2_0_1)_: ++1 flow(gas_commodity_co2_0_1) += 0 + +c_e_BusBlock_balance(co2_0_2)_: ++1 flow(gas_commodity_co2_0_2) += 0 + +c_e_BusBlock_balance(gas_0_0)_: ++1 flow(gas_commodity_gas_0_0) += 0 + +c_e_BusBlock_balance(gas_0_1)_: ++1 flow(gas_commodity_gas_0_1) += 0 + +c_e_BusBlock_balance(gas_0_2)_: ++1 flow(gas_commodity_gas_0_2) += 0 + +c_u_SimpleFlowBlock_full_load_time_max_constr(gas_commodity_gas)_: ++1 flow(gas_commodity_gas_0_0) ++1 flow(gas_commodity_gas_0_1) ++1 flow(gas_commodity_gas_0_2) +<= 1000 + +c_e_CommodityGHGBlock_relation(gas_commodity_co2_0_0)_: +-2 flow(gas_commodity_gas_0_0) +-1 flow(gas_commodity_co2_0_0) += 0 + +c_e_CommodityGHGBlock_relation(gas_commodity_co2_0_1)_: +-2 flow(gas_commodity_gas_0_1) +-1 flow(gas_commodity_co2_0_1) += 0 + +c_e_CommodityGHGBlock_relation(gas_commodity_co2_0_2)_: +-2 flow(gas_commodity_gas_0_2) +-1 flow(gas_commodity_co2_0_2) += 0 + +bounds + -inf <= flow(gas_commodity_co2_0_0) <= +inf + -inf <= flow(gas_commodity_co2_0_1) <= +inf + -inf <= flow(gas_commodity_co2_0_2) <= +inf + 0 <= flow(gas_commodity_gas_0_0) <= 900.0 + 0 <= flow(gas_commodity_gas_0_1) <= 500.0 + 0 <= flow(gas_commodity_gas_0_2) <= 400.0 +end diff --git a/tests/_files/lp_files/conversion_ghg.lp b/tests/_files/lp_files/conversion_ghg.lp new file mode 100644 index 00000000..6d75b53f --- /dev/null +++ b/tests/_files/lp_files/conversion_ghg.lp @@ -0,0 +1,86 @@ +\* Source Pyomo model name=Model *\ + +min +objective: ++0 ONE_VAR_CONSTANT + +s.t. + +c_e_BusBlock_balance(biomass_0_0)_: ++1 flow(biomass_biomass_plant_0_0) += 0 + +c_e_BusBlock_balance(biomass_0_1)_: ++1 flow(biomass_biomass_plant_0_1) += 0 + +c_e_BusBlock_balance(biomass_0_2)_: ++1 flow(biomass_biomass_plant_0_2) += 0 + +c_e_BusBlock_balance(co2_0_0)_: ++1 flow(biomass_plant_co2_0_0) += 0 + +c_e_BusBlock_balance(co2_0_1)_: ++1 flow(biomass_plant_co2_0_1) += 0 + +c_e_BusBlock_balance(co2_0_2)_: ++1 flow(biomass_plant_co2_0_2) += 0 + +c_e_BusBlock_balance(heat_0_0)_: ++1 flow(biomass_plant_heat_0_0) += 0 + +c_e_BusBlock_balance(heat_0_1)_: ++1 flow(biomass_plant_heat_0_1) += 0 + +c_e_BusBlock_balance(heat_0_2)_: ++1 flow(biomass_plant_heat_0_2) += 0 + +c_e_ConverterBlock_relation(biomass_plant_biomass_heat_0_0)_: ++0.4 flow(biomass_biomass_plant_0_0) +-1 flow(biomass_plant_heat_0_0) += 0 + +c_e_ConverterBlock_relation(biomass_plant_biomass_co2_0_0)_: ++56 flow(biomass_biomass_plant_0_0) +-1 flow(biomass_plant_co2_0_0) += 0 + +c_e_ConverterBlock_relation(biomass_plant_biomass_heat_0_1)_: ++0.4 flow(biomass_biomass_plant_0_1) +-1 flow(biomass_plant_heat_0_1) += 0 + +c_e_ConverterBlock_relation(biomass_plant_biomass_co2_0_1)_: ++56 flow(biomass_biomass_plant_0_1) +-1 flow(biomass_plant_co2_0_1) += 0 + +c_e_ConverterBlock_relation(biomass_plant_biomass_heat_0_2)_: ++0.4 flow(biomass_biomass_plant_0_2) +-1 flow(biomass_plant_heat_0_2) += 0 + +c_e_ConverterBlock_relation(biomass_plant_biomass_co2_0_2)_: ++56 flow(biomass_biomass_plant_0_2) +-1 flow(biomass_plant_co2_0_2) += 0 + +bounds + 1 <= ONE_VAR_CONSTANT <= 1 + 0 <= flow(biomass_biomass_plant_0_0) <= +inf + 0 <= flow(biomass_biomass_plant_0_1) <= +inf + 0 <= flow(biomass_biomass_plant_0_2) <= +inf + 0 <= flow(biomass_plant_heat_0_0) <= 100 + 0 <= flow(biomass_plant_heat_0_1) <= 100 + 0 <= flow(biomass_plant_heat_0_2) <= 100 + 0 <= flow(biomass_plant_co2_0_0) <= +inf + 0 <= flow(biomass_plant_co2_0_1) <= +inf + 0 <= flow(biomass_plant_co2_0_2) <= +inf +end diff --git a/tests/test_constraints.py b/tests/test_constraints.py index 6111e93d..e9b723a7 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -11,7 +11,9 @@ from oemof.tabular.facades import ( BackpressureTurbine, Commodity, + CommodityGHG, Conversion, + ConversionGHG, Dispatchable, Excess, ExtractionTurbine, @@ -319,6 +321,25 @@ def test_commodity(self): self.compare_to_reference_lp("commodity.lp") + def test_commodity_ghg(self): + r""" """ + bus_gas = solph.Bus("gas") + bus_co2 = solph.Bus("co2") + + commodity = CommodityGHG( + label="gas-commodity", + bus=bus_gas, + emission_bus_0=bus_co2, + carrier="gas", + amount=1000, + marginal_cost=10, + output_parameters={"max": [0.9, 0.5, 0.4]}, + emission_factor_co2=-2, + ) + self.energysystem.add(bus_gas, bus_co2, commodity) + + self.compare_to_reference_lp("commodity_ghg.lp") + def test_conversion(self): r""" """ bus_biomass = solph.Bus("biomass") @@ -337,6 +358,27 @@ def test_conversion(self): self.compare_to_reference_lp("conversion.lp") + def test_conversion_ghg(self): + r""" """ + bus_biomass = solph.Bus("biomass") + bus_heat = solph.Bus("heat") + bus_co2 = solph.Bus("co2") + + conversion = ConversionGHG( + label="biomass_plant", + carrier="biomass", + tech="st", + from_bus=bus_biomass, + to_bus=bus_heat, + emission_bus_0=bus_co2, + capacity=100, + efficiency=0.4, + emission_factor_co2=56, + ) + self.energysystem.add(bus_heat, bus_biomass, bus_co2, conversion) + + self.compare_to_reference_lp("conversion_ghg.lp") + def test_dispatchable(self): bus = solph.Bus("electricity") diff --git a/tests/test_examples.py b/tests/test_examples.py index 4e10e694..4d21a3c3 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -91,32 +91,40 @@ def test_examples_datapackages_scripts_infer(): script_path = datapackage_path / "scripts" / script if script_path.exists(): - print( - "Running infer script for {} ...".format(example_datapackage) - ) - exec( - "kwargs = {} \n" - f"kwargs['path'] = '{datapackage_path}' \n" - f"kwargs['metadata_filename'] = " - f"'datapackage_{example_datapackage}.json' \n" - + open(script_path).read(), - ) + if script_path.parts[-3] == "GHG": + print( + "Example 'GHG' is not tested so far with infer.py script." + ) + else: + print( + "Running infer script for {} ...".format( + example_datapackage + ) + ) + exec( + "kwargs = {} \n" + f"kwargs['path'] = '{datapackage_path}' \n" + f"kwargs['metadata_filename'] = " + f"'datapackage_{example_datapackage}.json' \n" + + open(script_path).read(), + ) - # Move metadata string to .oemof directory - test_filepath = ( - datapackage_path / f"datapackage_{example_datapackage}.json" - ) - new_filepath = ( - pathlib.PosixPath(helpers.extend_basic_path("metadata")) - / f"datapackage_{example_datapackage}.json" - ) - os.rename(test_filepath, new_filepath) + # Move metadata string to .oemof directory + test_filepath = ( + datapackage_path + / f"datapackage_{example_datapackage}.json" + ) + new_filepath = ( + pathlib.PosixPath(helpers.extend_basic_path("metadata")) + / f"datapackage_{example_datapackage}.json" + ) + os.rename(test_filepath, new_filepath) - ref_filepath = datapackage_path / "datapackage.json" + ref_filepath = datapackage_path / "datapackage.json" - with open(new_filepath) as new_file: - with open(ref_filepath) as ref_file: - compare_json_files(new_file, ref_file) + with open(new_filepath) as new_file: + with open(ref_filepath) as ref_file: + compare_json_files(new_file, ref_file) def test_custom_foreign_keys(monkeypatch):