Passed
Pull Request — dev (#850)
by Uwe
01:35
created

tuple_as_label   A

Complexity

Total Complexity 1

Size/Duplication

Total Lines 334
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 1
eloc 130
dl 0
loc 334
rs 10
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A Label.__str__() 0 3 1
1
# -*- coding: utf-8 -*-
2
3
"""
4
General description
5
-------------------
6
7
You should have grasped the basic_example to understand this one.
8
9
This is an example to show how the label attribute can be used with tuples to
10
manage the results of large energy system. Even though, the feature is
11
introduced in a small example it is made for large system.
12
13
In small energy system you normally address the node, you want your results
14
from, directly. In large systems you may want to group your results and collect
15
all power plants of a specific region or pv feed-in of all regions.
16
17
Therefore you can use named tuples as label. In a named tuple you need to
18
specify the fields:
19
20
>>> label = namedtuple('solph_label', ['region', 'tag1', 'tag2'])
21
22
>>> pv_label = label('region_1', 'renewable_source', 'pv')
23
>>> pp_gas_label = label('region_2', 'power_plant', 'natural_gas')
24
>>> demand_label = label('region_3', 'electricity', 'demand')
25
26
You always have to address all fields but you can use empty strings or None as
27
place holders.
28
29
>>> elec_bus = label('region_4', 'electricity', '')
30
>>> print(elec_bus)
31
solph_label(region='region_4', tag1='electricity', tag2='')
32
33
>>> elec_bus = label('region_4', 'electricity', None)
34
>>> print(elec_bus)
35
solph_label(region='region_4', tag1='electricity', tag2=None)
36
37
Now you can filter the results using the label or the instance:
38
39
>>> for key, value in results.items():  # Loop results (keys are tuples!)
40
...     if isinstance(key[0], comp.Sink) & (key[0].label.tag2 == 'demand'):
41
...         print("elec demand {0}: {1}".format(key[0].label.region,
42
...                                             value['sequences'].sum()))
43
44
elec demand region_1: 3456
45
elec demand region_2: 2467
46
...
47
48
In the example below a subclass is created to define ones own string output.
49
By default the output of a namedtuple is `field1=value1, field2=value2,...`:
50
51
>>> print(str(pv_label))
52
solph_label(region='region_1', tag1='renewable_source', tag2='pv')
53
54
With the subclass we created below the output is different, because we defined
55
our own string representation:
56
57
>>> new_pv_label = Label('region_1', 'renewable_source', 'pv')
58
>>> print(str(new_pv_label))
59
region_1_renewable_source_pv
60
61
You still will be able to get the original string using `repr`:
62
63
>>> print(repr(new_pv_label))
64
Label(tag1='region_1', tag2='renewable_source', tag3='pv')
65
66
This a helpful adaption for automatic plots etc..
67
68
Afterwards you can use `format` to define your own custom string.:
69
>>> print('{0}+{1}-{2}'.format(pv_label.region, pv_label.tag2, pv_label.tag1))
70
region_1+pv-renewable_source
71
72
Data
73
----
74
basic_example.csv
75
76
77
Installation requirements
78
-------------------------
79
This example requires oemof.solph (v0.5.x), install by:
80
81
    pip install oemof.solph[examples]
82
83
84
License
85
-------
86
`MIT license <https://github.com/oemof/oemof-solph/blob/dev/LICENSE>`_
87
88
"""
89
90
# ****************************************************************************
91
# ********** PART 1 - Define and optimise the energy system ******************
92
# ****************************************************************************
93
94
import logging
95
import os
96
import warnings
97
from collections import namedtuple
98
99
import pandas as pd
100
from oemof.tools import logger
101
102
from oemof.solph import EnergySystem
103
from oemof.solph import Model
104
from oemof.solph import buses
105
from oemof.solph import components as comp
106
from oemof.solph import create_time_index
107
from oemof.solph import flows
108
from oemof.solph import helpers
109
from oemof.solph import processing
110
111
112
# Subclass of the named tuple with its own __str__ method.
113
# You can add as many tags as you like
114
# For tag1, tag2 you can define your own fields like region, fuel, type...
115
class Label(namedtuple("solph_label", ["tag1", "tag2", "tag3"])):
116
    __slots__ = ()
117
118
    def __str__(self):
119
        """The string is used within solph as an ID, so it hast to be unique"""
120
        return "_".join(map(str, self._asdict().values()))
121
122
123
# Read data file
124
filename = os.path.join(os.getcwd(), "tuple_as_label.csv")
125
try:
126
    data = pd.read_csv(filename)
127
except FileNotFoundError:
128
    msg = "Data file not found: {0}. Only one value used!"
129
    warnings.warn(msg.format(filename), UserWarning)
130
    data = pd.DataFrame({"pv": [0.3], "wind": [0.6], "demand_el": [500]})
131
132
solver = "cbc"  # 'glpk', 'gurobi',....
133
debug = False  # Set number_of_timesteps to 3 to get a readable lp-file.
134
number_of_time_steps = len(data)
135
solver_verbose = False  # show/hide solver output
136
137
# initiate the logger (see the API docs for more information)
138
logger.define_logging(
139
    logfile="oemof_example.log",
140
    screen_level=logging.INFO,
141
    file_level=logging.WARNING,
142
)
143
144
logging.info("Initialize the energy system")
145
energysystem = EnergySystem(
146
    timeindex=create_time_index(2012, number=number_of_time_steps),
147
    infer_last_interval=False,
148
)
149
150
151
##########################################################################
152
# Create oemof object
153
##########################################################################
154
155
logging.info("Create oemof objects")
156
157
# The bus objects were assigned to variables which makes it easier to connect
158
# components to these buses (see below).
159
160
# create natural gas bus
161
bgas = buses.Bus(label=Label("bus", "gas", None))
162
163
# create electricity bus
164
bel = buses.Bus(label=Label("bus", "electricity", None))
165
166
# adding the buses to the energy system
167
energysystem.add(bgas, bel)
168
169
# create excess component for the electricity bus to allow overproduction
170
energysystem.add(
171
    comp.Sink(
172
        label=Label("sink", "electricity", "excess"),
173
        inputs={bel: flows.Flow()},
174
    )
175
)
176
177
# create source object representing the natural gas commodity (annual limit)
178
energysystem.add(
179
    comp.Source(
180
        label=Label("commodity_source", "gas", "commodity"),
181
        outputs={bgas: flows.Flow()},
182
    )
183
)
184
185
# create fixed source object representing wind pow er plants
186
energysystem.add(
187
    comp.Source(
188
        label=Label("ee_source", "electricity", "wind"),
189
        outputs={bel: flows.Flow(fix=data["wind"], nominal_value=2000)},
190
    )
191
)
192
193
# create fixed source object representing pv power plants
194
energysystem.add(
195
    comp.Source(
196
        label=Label("ee_source", "electricity", "pv"),
197
        outputs={bel: flows.Flow(fix=data["pv"], nominal_value=3000)},
198
    )
199
)
200
201
# create simple sink object representing the electrical demand
202
energysystem.add(
203
    comp.Sink(
204
        label=Label("sink", "electricity", "demand"),
205
        inputs={
206
            bel: flows.Flow(fix=data["demand_el"] / 1000, nominal_value=1)
207
        },
208
    )
209
)
210
211
# create simple transformer object representing a gas power plant
212
energysystem.add(
213
    comp.Transformer(
214
        label=Label("power plant", "electricity", "gas"),
215
        inputs={bgas: flows.Flow()},
216
        outputs={bel: flows.Flow(nominal_value=10000, variable_costs=50)},
217
        conversion_factors={bel: 0.58},
218
    )
219
)
220
221
# create storage object representing a battery
222
nominal_storage_capacity = 5000
223
storage = comp.GenericStorage(
224
    nominal_storage_capacity=nominal_storage_capacity,
225
    label=Label("storage", "electricity", "battery"),
226
    inputs={bel: flows.Flow(nominal_value=nominal_storage_capacity / 6)},
227
    outputs={bel: flows.Flow(nominal_value=nominal_storage_capacity / 6)},
228
    loss_rate=0.00,
229
    initial_storage_level=None,
230
    inflow_conversion_factor=1,
231
    outflow_conversion_factor=0.8,
232
)
233
234
energysystem.add(storage)
235
236
##########################################################################
237
# Optimise the energy system and plot the results
238
##########################################################################
239
240
logging.info("Optimise the energy system")
241
242
# initialise the operational model
243
model = Model(energysystem)
244
245
# This is for debugging only. It is not(!) necessary to solve the problem and
246
# should be set to False to save time and disc space in normal use. For
247
# debugging the timesteps should be set to 3, to increase the readability of
248
# the lp-file.
249
if debug:
250
    filename = os.path.join(
251
        helpers.extend_basic_path("lp_files"), "basic_example.lp"
252
    )
253
    logging.info("Store lp-file in {0}.".format(filename))
254
    model.write(filename, io_options={"symbolic_solver_labels": True})
255
256
# if tee_switch is true solver messages will be displayed
257
logging.info("Solve the optimization problem")
258
model.receive_duals()
259
model.solve(solver=solver, solve_kwargs={"tee": solver_verbose})
260
261
logging.info("Store the energy system with the results.")
262
263
# The processing module of the outputlib can be used to extract the results
264
# from the model transfer them into a homogeneous structured dictionary.
265
266
results = processing.results(model)
267
268
269
# ****** Create a table with all sequences and store it into a file (csv/xlsx)
270
flows_to_bus = pd.DataFrame(
271
    {
272
        str(k[0].label): v["sequences"]["flow"]
273
        for k, v in results.items()
274
        if k[1] is not None and k[1] == bel
275
    }
276
)
277
flows_from_bus = pd.DataFrame(
278
    {
279
        str(k[1].label): v["sequences"]["flow"]
280
        for k, v in results.items()
281
        if k[1] is not None and k[0] == bel
282
    }
283
)
284
285
storage = pd.DataFrame(
286
    {
287
        str(k[0].label): v["sequences"]["storage_content"]
288
        for k, v in results.items()
289
        if k[1] is None and k[0] == storage
290
    }
291
)
292
293
duals = pd.DataFrame(
294
    {
295
        str(k[0].label): v["sequences"]["duals"]
296
        for k, v in results.items()
297
        if k[1] is None and isinstance(k[0], buses.Bus)
298
    }
299
)
300
301
my_flows = pd.concat(
302
    [flows_to_bus, flows_from_bus, storage],
303
    keys=["to_bus", "from_bus", "content", "duals"],
304
    axis=1,
305
)
306
307
# Store the table to csv or excel file:
308
home_path = os.path.expanduser("~")
309
my_flows.to_csv(os.path.join(home_path, "my_flows.csv"))
310
# my_flows.to_excel(os.path.join(home_path, "my_flows.xlsx"))
311
print(my_flows.sum())
312
313
# ********* Use your tuple labels to filter the components
314
ee_sources = [
315
    str(f[0].label) for f in results.keys() if f[0].label.tag1 == "ee_source"
316
]
317
print(ee_sources)
318
319
# It is possible to filter components by the label tags and the class, so the
320
# label concepts is a result of the postprocessing. If it is necessary to get
321
# all components of a region, "region" should be a field of the label.
322
# To filter only by tags you can add a tag named "class" with the name of the
323
# class as value.
324
electricity_buses = list(
325
    set(
326
        [
327
            str(f[0].label)
328
            for f in results.keys()
329
            if f[0].label.tag2 == "electricity" and isinstance(f[0], buses.Bus)
330
        ]
331
    )
332
)
333
print(electricity_buses)
334