Completed
Push — dev ( 468d85...f4c061 )
by Patrik
23s queued 17s
created

basic_example.main()   C

Complexity

Conditions 5

Size

Total Lines 230
Code Lines 106

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 106
dl 0
loc 230
rs 6.5333
c 0
b 0
f 0
cc 5
nop 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
# -*- coding: utf-8 -*-
2
3
"""
4
General description
5
-------------------
6
7
A basic example to show how to model a simple energy system with oemof.solph.
8
9
The following energy system is modeled:
10
11
.. code-block:: text
12
13
                     input/output  bgas     bel
14
                         |          |        |
15
                         |          |        |
16
     wind(FixedSource)   |------------------>|
17
                         |          |        |
18
     pv(FixedSource)     |------------------>|
19
                         |          |        |
20
     rgas(Commodity)     |--------->|        |
21
                         |          |        |
22
     demand(Sink)        |<------------------|
23
                         |          |        |
24
                         |          |        |
25
     pp_gas(Converter)   |<---------|        |
26
                         |------------------>|
27
                         |          |        |
28
     storage(Storage)    |<------------------|
29
                         |------------------>|
30
31
Code
32
----
33
Download source code: :download:`basic_example.py </../examples/basic_example/basic_example.py>`
34
35
.. dropdown:: Click to display code
36
37
    .. literalinclude:: /../examples/basic_example/basic_example.py
38
        :language: python
39
        :lines: 61-
40
41
Data
42
----
43
Download data: :download:`basic_example.csv </../examples/basic_example/basic_example.csv>`
44
45
Installation requirements
46
-------------------------
47
This example requires oemof.solph (v0.5.x), install by:
48
49
.. code:: bash
50
51
    pip install oemof.solph[examples]
52
53
License
54
-------
55
`MIT license <https://github.com/oemof/oemof-solph/blob/dev/LICENSE>`_
56
"""
57
###########################################################################
58
# imports
59
###########################################################################
60
61
import logging
62
import os
63
import pprint as pp
64
from datetime import datetime
65
66
import matplotlib.pyplot as plt
67
import pandas as pd
68
from oemof.tools import logger
69
70
from oemof.solph import EnergySystem
71
from oemof.solph import Model
72
from oemof.solph import buses
73
from oemof.solph import components
74
from oemof.solph import create_time_index
75
from oemof.solph import flows
76
from oemof.solph import helpers
77
from oemof.solph import processing
78
from oemof.solph import views
79
80
STORAGE_LABEL = "battery_storage"
81
82
83
def get_data_from_file_path(file_path: str) -> pd.DataFrame:
84
    try:
85
        data = pd.read_csv(file_path)
86
    except FileNotFoundError:
87
        dir = os.path.dirname(os.path.abspath(__file__))
88
        data = pd.read_csv(dir + "/" + file_path)
89
    return data
90
91
92
def plot_figures_for(element: dict) -> None:
93
    figure, axes = plt.subplots(figsize=(10, 5))
94
    element["sequences"].plot(ax=axes, kind="line", drawstyle="steps-post")
95
    plt.legend(
96
        loc="upper center",
97
        prop={"size": 8},
98
        bbox_to_anchor=(0.5, 1.25),
99
        ncol=2,
100
    )
101
    figure.subplots_adjust(top=0.8)
102
    plt.show()
103
104
105
def main(dump_and_restore=False, optimize=True):
106
    # For models that need a long time to optimise, saving and loading the
107
    # EnergySystem might be advised. By default, we do not do this here. Feel
108
    # free to experiment with this once you understood the rest of the code.
109
    dump_results = restore_results = dump_and_restore
110
111
    # *************************************************************************
112
    # ********** PART 1 - Define and optimise the energy system ***************
113
    # *************************************************************************
114
115
    # Read data file
116
    file_name = "basic_example.csv"
117
    data = get_data_from_file_path(file_name)
118
119
    solver = "cbc"  # 'glpk', 'gurobi',....
120
    debug = False  # Set number_of_timesteps to 3 to get a readable lp-file.
121
    number_of_time_steps = len(data)
122
    solver_verbose = False  # show/hide solver output
123
124
    # initiate the logger (see the API docs for more information)
125
    logger.define_logging(
126
        logfile="oemof_example.log",
127
        screen_level=logging.INFO,
128
        file_level=logging.INFO,
129
    )
130
131
    logging.info("Initialize the energy system")
132
    date_time_index = create_time_index(2012, number=number_of_time_steps)
133
134
    # create the energysystem and assign the time index
135
    energysystem = EnergySystem(
136
        timeindex=date_time_index, infer_last_interval=False
137
    )
138
139
    ##########################################################################
140
    # Create oemof objects
141
    ##########################################################################
142
143
    logging.info("Create oemof objects")
144
145
    # The bus objects were assigned to variables which makes it easier to
146
    # connect components to these buses (see below).
147
148
    # create natural gas bus
149
    bus_gas = buses.Bus(label="natural_gas")
150
151
    # create electricity bus
152
    bus_electricity = buses.Bus(label="electricity")
153
154
    # adding the buses to the energy system
155
    energysystem.add(bus_gas, bus_electricity)
156
157
    # create excess component for the electricity bus to allow overproduction
158
    energysystem.add(
159
        components.Sink(
160
            label="excess_bus_electricity",
161
            inputs={bus_electricity: flows.Flow()},
162
        )
163
    )
164
165
    # create source object representing the gas commodity
166
    energysystem.add(
167
        components.Source(
168
            label="rgas",
169
            outputs={bus_gas: flows.Flow()},
170
        )
171
    )
172
173
    # create fixed source object representing wind power plants
174
    energysystem.add(
175
        components.Source(
176
            label="wind",
177
            outputs={
178
                bus_electricity: flows.Flow(
179
                    fix=data["wind"], nominal_capacity=1000000
180
                )
181
            },
182
        )
183
    )
184
185
    # create fixed source object representing pv power plants
186
    energysystem.add(
187
        components.Source(
188
            label="pv",
189
            outputs={
190
                bus_electricity: flows.Flow(
191
                    fix=data["pv"], nominal_capacity=582000
192
                )
193
            },
194
        )
195
    )
196
197
    # create simple sink object representing the electrical demand
198
    # nominal_capacity is set to 1 because demand_el is not a normalised series
199
    energysystem.add(
200
        components.Sink(
201
            label="demand",
202
            inputs={
203
                bus_electricity: flows.Flow(
204
                    fix=data["demand_el"], nominal_capacity=1
205
                )
206
            },
207
        )
208
    )
209
210
    # create simple converter object representing a gas power plant
211
    energysystem.add(
212
        components.Converter(
213
            label="pp_gas",
214
            inputs={bus_gas: flows.Flow()},
215
            outputs={
216
                bus_electricity: flows.Flow(
217
                    nominal_capacity=10e10, variable_costs=50
218
                )
219
            },
220
            conversion_factors={bus_electricity: 0.58},
221
        )
222
    )
223
224
    # create storage object representing a battery
225
    nominal_capacity = 10077997
226
    nominal_capacity = nominal_capacity / 6
227
228
    battery_storage = components.GenericStorage(
229
        nominal_capacity=nominal_capacity,
230
        label=STORAGE_LABEL,
231
        inputs={
232
            bus_electricity: flows.Flow(nominal_capacity=nominal_capacity)
233
        },
234
        outputs={
235
            bus_electricity: flows.Flow(
236
                nominal_capacity=nominal_capacity, variable_costs=0.001
237
            )
238
        },
239
        loss_rate=0.00,
240
        initial_storage_level=None,
241
        inflow_conversion_factor=1,
242
        outflow_conversion_factor=0.8,
243
    )
244
245
    energysystem.add(battery_storage)
246
247
    ##########################################################################
248
    # Optimise the energy system and plot the results
249
    ##########################################################################
250
251
    if optimize is False:
252
        return energysystem
253
254
    logging.info("Optimise the energy system")
255
256
    # initialise the operational model
257
    energysystem_model = Model(energysystem)
258
259
    # This is for debugging only. It is not(!) necessary to solve the problem
260
    # and should be set to False to save time and disc space in normal use. For
261
    # debugging the timesteps should be set to 3, to increase the readability
262
    # of the lp-file.
263
    if debug:
264
        file_path = os.path.join(
265
            helpers.extend_basic_path("lp_files"), "basic_example.lp"
266
        )
267
        logging.info(f"Store lp-file in {file_path}.")
268
        io_option = {"symbolic_solver_labels": True}
269
        energysystem_model.write(file_path, io_options=io_option)
270
271
    # if tee_switch is true solver messages will be displayed
272
    logging.info("Solve the optimization problem")
273
    energysystem_model.solve(
274
        solver=solver, solve_kwargs={"tee": solver_verbose}
275
    )
276
277
    logging.info("Store the energy system with the results.")
278
279
    # The processing module of the outputlib can be used to extract the results
280
    # from the model transfer them into a homogeneous structured dictionary.
281
282
    # add results to the energy system to make it possible to store them.
283
    energysystem.results["main"] = processing.results(energysystem_model)
284
    energysystem.results["meta"] = processing.meta_results(energysystem_model)
285
286
    # The default path is the '.oemof' folder in your $HOME directory.
287
    # The default filename is 'es_dump.oemof'.
288
    # You can omit the attributes (as None is the default value) for testing
289
    # cases. You should use unique names/folders for valuable results to avoid
290
    # overwriting.
291
    if dump_results:
292
        energysystem.dump(dpath=None, filename=None)
293
294
    # *************************************************************************
295
    # ********** PART 2 - Processing the results ******************************
296
    # *************************************************************************
297
298
    # Saved data can be restored in a second script. So you can work on the
299
    # data analysis without re-running the optimisation every time. If you do
300
    # so, make sure that you really load the results you want. For example,
301
    # if dumping fails, you might exidentially load outdated results.
302
    if restore_results:
303
        logging.info("**** The script can be divided into two parts here.")
304
        logging.info("Restore the energy system and the results.")
305
306
        energysystem = EnergySystem()
307
        energysystem.restore(dpath=None, filename=None)
308
309
    # define an alias for shorter calls below (optional)
310
    results = energysystem.results["main"]
311
    storage = energysystem.groups[STORAGE_LABEL]
312
313
    # print a time slice of the state of charge
314
    start_time = datetime(2012, 2, 25, 8, 0, 0)
315
    end_time = datetime(2012, 2, 25, 17, 0, 0)
316
317
    print("\n********* State of Charge (slice) *********")
318
    print(f"{results[(storage, None)]['sequences'][start_time : end_time]}\n")
319
320
    # get all variables of a specific component/bus
321
    custom_storage = views.node(results, STORAGE_LABEL)
322
    electricity_bus = views.node(results, "electricity")
323
324
    # plot the time series (sequences) of a specific component/bus
325
    plot_figures_for(custom_storage)
326
    plot_figures_for(electricity_bus)
327
328
    # print the solver results
329
    print("********* Meta results *********")
330
    pp.pprint(f"{energysystem.results['meta']}\n")
331
332
    # print the sums of the flows around the electricity bus
333
    print("********* Main results *********")
334
    print(electricity_bus["sequences"].sum(axis=0))
335
336
337
if __name__ == "__main__":
338
    main()
339