Tuple as label¶
Using tuples as names for components¶
General description¶
You should have grasped the basic_example to understand this one.
This is an example to show how the label attribute can be used with tuples to manage the results of large energy system. Even though, the feature is introduced in a small example it is made for large system.
In small energy system you normally address the node, you want your results from, directly. In large systems you may want to group your results and collect all power plants of a specific region or pv feed-in of all regions.
Therefore you can use named tuples as label. In a named tuple you need to specify the fields:
>>> label = namedtuple('solph_label', ['region', 'tag1', 'tag2'])
>>> pv_label = label('region_1', 'renewable_source', 'pv')
>>> pp_gas_label = label('region_2', 'power_plant', 'natural_gas')
>>> demand_label = label('region_3', 'electricity', 'demand')
You always have to address all fields but you can use empty strings or None as place holders.
>>> elec_bus = label('region_4', 'electricity', '')
>>> print(elec_bus)
solph_label(region='region_4', tag1='electricity', tag2='')
>>> elec_bus = label('region_4', 'electricity', None)
>>> print(elec_bus)
solph_label(region='region_4', tag1='electricity', tag2=None)
Now you can filter the results using the label or the instance:
>>> for key, value in results.items(): # Loop results (keys are tuples!)
... if isinstance(key[0], comp.Sink) & (key[0].label.tag2 == 'demand'):
... print("elec demand {0}: {1}".format(key[0].label.region,
... value['sequences'].sum()))
elec demand region_1: 3456 elec demand region_2: 2467 …
In the example below a subclass is created to define ones own string output. By default the output of a namedtuple is field1=value1, field2=value2,…:
>>> print(str(pv_label))
solph_label(region='region_1', tag1='renewable_source', tag2='pv')
With the subclass we created below the output is different, because we defined our own string representation:
>>> new_pv_label = Label('region_1', 'renewable_source', 'pv')
>>> print(str(new_pv_label))
region_1_renewable_source_pv
You still will be able to get the original string using repr:
>>> print(repr(new_pv_label))
Label(tag1='region_1', tag2='renewable_source', tag3='pv')
This a helpful adaption for automatic plots etc..
Afterwards you can use format to define your own custom string.:
>>> print('{0}+{1}-{2}'.format(pv_label.region, pv_label.tag2, pv_label.tag1))
region_1+pv-renewable_source
Code¶
Download source code: tuple_as_label.py
Click to display code
import logging
import os
import warnings
from collections import namedtuple
import pandas as pd
from oemof.tools import logger
from oemof.solph import EnergySystem
from oemof.solph import Model
from oemof.solph import buses
from oemof.solph import components as comp
from oemof.solph import create_time_index
from oemof.solph import flows
from oemof.solph import helpers
from oemof.solph import processing
# Subclass of the named tuple with its own __str__ method.
# You can add as many tags as you like
# For tag1, tag2 you can define your own fields like region, fuel, type...
class Label(namedtuple("solph_label", ["tag1", "tag2", "tag3"])):
__slots__ = ()
def __str__(self):
"""The string is used within solph as an ID, so it hast to be unique"""
return "_".join(map(str, self._asdict().values()))
def main():
# Read data file
filename = os.path.join(os.getcwd(), "tuple_as_label.csv")
try:
data = pd.read_csv(filename)
except FileNotFoundError:
msg = "Data file not found: {0}. Only one value used!"
warnings.warn(msg.format(filename), UserWarning)
data = pd.DataFrame({"pv": [0.3], "wind": [0.6], "demand_el": [500]})
solver = "cbc" # 'glpk', 'gurobi',....
debug = False # Set number_of_timesteps to 3 to get a readable lp-file.
number_of_time_steps = len(data)
solver_verbose = False # show/hide solver output
# initiate the logger (see the API docs for more information)
logger.define_logging(
logfile="oemof_example.log",
screen_level=logging.INFO,
file_level=logging.WARNING,
)
logging.info("Initialize the energy system")
energysystem = EnergySystem(
timeindex=create_time_index(2012, number=number_of_time_steps),
infer_last_interval=False,
)
##########################################################################
# Create oemof object
##########################################################################
logging.info("Create oemof objects")
# The bus objects were assigned to variables which makes it easier to
# connect components to these buses (see below).
# create natural gas bus
bgas = buses.Bus(label=Label("bus", "gas", None))
# create electricity bus
bel = buses.Bus(label=Label("bus", "electricity", None))
# adding the buses to the energy system
energysystem.add(bgas, bel)
# create excess component for the electricity bus to allow overproduction
energysystem.add(
comp.Sink(
label=Label("sink", "electricity", "excess"),
inputs={bel: flows.Flow()},
)
)
# create source object representing the gas commodity (annual limit)
energysystem.add(
comp.Source(
label=Label("commodity_source", "gas", "commodity"),
outputs={bgas: flows.Flow()},
)
)
# create fixed source object representing wind pow er plants
energysystem.add(
comp.Source(
label=Label("ee_source", "electricity", "wind"),
outputs={bel: flows.Flow(fix=data["wind"], nominal_capacity=2000)},
)
)
# create fixed source object representing pv power plants
energysystem.add(
comp.Source(
label=Label("ee_source", "electricity", "pv"),
outputs={bel: flows.Flow(fix=data["pv"], nominal_capacity=3000)},
)
)
# create simple sink object representing the electrical demand
energysystem.add(
comp.Sink(
label=Label("sink", "electricity", "demand"),
inputs={
bel: flows.Flow(
fix=data["demand_el"] / 1000, nominal_capacity=1
)
},
)
)
# create simple Converter object representing a gas power plant
energysystem.add(
comp.Converter(
label=Label("power plant", "electricity", "gas"),
inputs={bgas: flows.Flow()},
outputs={
bel: flows.Flow(nominal_capacity=10000, variable_costs=50)
},
conversion_factors={bel: 0.58},
)
)
# create storage object representing a battery
nominal_capacity = 5000
storage = comp.GenericStorage(
nominal_capacity=nominal_capacity,
label=Label("storage", "electricity", "battery"),
inputs={bel: flows.Flow(nominal_capacity=nominal_capacity / 6)},
outputs={bel: flows.Flow(nominal_capacity=nominal_capacity / 6)},
loss_rate=0.00,
initial_storage_level=None,
inflow_conversion_factor=1,
outflow_conversion_factor=0.8,
)
energysystem.add(storage)
##########################################################################
# Optimise the energy system and plot the results
##########################################################################
logging.info("Optimise the energy system")
# initialise the operational model
model = Model(energysystem)
# This is for debugging only. It is not(!) necessary to solve the problem
# and should be set to False to save time and disc space in normal use. For
# debugging the timesteps should be set to 3, to increase the readability
# of the lp-file.
if debug:
filename = os.path.join(
helpers.extend_basic_path("lp_files"), "basic_example.lp"
)
logging.info("Store lp-file in {0}.".format(filename))
model.write(filename, io_options={"symbolic_solver_labels": True})
# if tee_switch is true solver messages will be displayed
logging.info("Solve the optimization problem")
model.receive_duals()
model.solve(solver=solver, solve_kwargs={"tee": solver_verbose})
logging.info("Store the energy system with the results.")
# The processing module of the outputlib can be used to extract the results
# from the model transfer them into a homogeneous structured dictionary.
results = processing.results(model)
# ** Create a table with all sequences and store it into a file (csv/xlsx)
flows_to_bus = pd.DataFrame(
{
str(k[0].label): v["sequences"]["flow"]
for k, v in results.items()
if k[1] is not None and k[1] == bel
}
)
flows_from_bus = pd.DataFrame(
{
str(k[1].label): v["sequences"]["flow"]
for k, v in results.items()
if k[1] is not None and k[0] == bel
}
)
storage = pd.DataFrame(
{
str(k[0].label): v["sequences"]["storage_content"]
for k, v in results.items()
if k[1] is None and k[0] == storage
}
)
duals = pd.DataFrame(
{
str(k[0].label): v["sequences"]["duals"]
for k, v in results.items()
if k[1] is None and isinstance(k[0], buses.Bus)
}
)
my_flows = pd.concat(
[flows_to_bus, flows_from_bus, storage, duals],
keys=["to_bus", "from_bus", "content", "duals"],
axis=1,
)
# Store the table to csv or excel file:
home_path = os.path.expanduser("~")
my_flows.to_csv(os.path.join(home_path, "my_flows.csv"))
# my_flows.to_excel(os.path.join(home_path, "my_flows.xlsx"))
print(my_flows.sum())
# ********* Use your tuple labels to filter the components
ee_sources = [
str(f[0].label)
for f in results.keys()
if f[0].label.tag1 == "ee_source"
]
print(ee_sources)
# It is possible to filter components by the label tags and the class, so
# the label concepts is a result of the postprocessing. If it is necessary
# to get all components of a region, "region" should be a field of the
# label. To filter only by tags you can add a tag named "class" with the
# name of the class as value.
electricity_buses = list(
set(
[
str(f[0].label)
for f in results.keys()
if f[0].label.tag2 == "electricity"
and isinstance(f[0], buses.Bus)
]
)
)
print(electricity_buses)
if __name__ == "__main__":
main()
Data¶
Download data: tuple_as_label.csv
Installation requirements¶
This example requires oemof.solph (v0.5.x), install by:
pip install oemof.solph[examples]