1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
|
3
|
|
|
""" |
4
|
|
|
General description: |
5
|
|
|
--------------------- |
6
|
|
|
|
7
|
|
|
The example models the following energy system: |
8
|
|
|
|
9
|
|
|
input/output bgas bel |
10
|
|
|
| | | | |
11
|
|
|
| | | | |
12
|
|
|
wind(FixedSource) |------------------>| | |
13
|
|
|
| | | | |
14
|
|
|
pv(FixedSource) |------------------>| | |
15
|
|
|
| | | | |
16
|
|
|
rgas(Commodity) |--------->| | | |
17
|
|
|
| | | | |
18
|
|
|
demand(Sink) |<------------------| | |
19
|
|
|
| | | | |
20
|
|
|
| | | | |
21
|
|
|
pp_gas(Converter) |<---------| | | |
22
|
|
|
|------------------>| | |
23
|
|
|
| | | | |
24
|
|
|
storage(Storage) |<------------------| | |
25
|
|
|
|------------------>| | |
26
|
|
|
|
27
|
|
|
|
28
|
|
|
|
29
|
|
|
This file is part of project oemof (github.com/oemof/oemof). It's copyrighted |
30
|
|
|
by the contributors recorded in the version control history of the file, |
31
|
|
|
available from its original location oemof/tests/test_scripts/test_solph/ |
32
|
|
|
test_storage_investment/test_storage_investment.py |
33
|
|
|
|
34
|
|
|
SPDX-License-Identifier: MIT |
35
|
|
|
""" |
36
|
|
|
|
37
|
|
|
import logging |
38
|
|
|
import os |
39
|
|
|
from collections import namedtuple |
40
|
|
|
|
41
|
|
|
import pandas as pd |
42
|
|
|
import pytest |
43
|
|
|
|
44
|
|
|
from oemof import solph as solph |
45
|
|
|
from oemof.solph import processing |
46
|
|
|
from oemof.solph import views |
47
|
|
|
|
48
|
|
|
|
49
|
|
|
class Label(namedtuple("solph_label", ["tag1", "tag2", "tag3"])): |
50
|
|
|
__slots__ = () |
51
|
|
|
|
52
|
|
|
def __str__(self): |
53
|
|
|
return "_".join(map(str, self._asdict().values())) |
54
|
|
|
|
55
|
|
|
|
56
|
|
|
def test_label(): |
57
|
|
|
my_label = Label("arg", 5, None) |
58
|
|
|
assert str(my_label) == "arg_5_None" |
59
|
|
|
assert repr(my_label) == "Label(tag1='arg', tag2=5, tag3=None)" |
60
|
|
|
|
61
|
|
|
|
62
|
|
|
def test_tuples_as_labels_example( |
63
|
|
|
filename="storage_investment.csv", solver="cbc" |
64
|
|
|
): |
65
|
|
|
logging.info("Initialize the energy system") |
66
|
|
|
date_time_index = pd.date_range("1/1/2012", periods=40, freq="h") |
67
|
|
|
|
68
|
|
|
energysystem = solph.EnergySystem( |
69
|
|
|
timeindex=date_time_index, |
70
|
|
|
infer_last_interval=True, |
71
|
|
|
) |
72
|
|
|
|
73
|
|
|
full_filename = os.path.join(os.path.dirname(__file__), filename) |
74
|
|
|
data = pd.read_csv(full_filename, sep=",") |
75
|
|
|
|
76
|
|
|
# Buses |
77
|
|
|
bgas = solph.buses.Bus(label=Label("bus", "natural_gas", None)) |
78
|
|
|
bel = solph.buses.Bus(label=Label("bus", "electricity", "")) |
79
|
|
|
energysystem.add(bgas, bel) |
80
|
|
|
|
81
|
|
|
# Sinks |
82
|
|
|
energysystem.add( |
83
|
|
|
solph.components.Sink( |
84
|
|
|
label=Label("sink", "electricity", "excess"), |
85
|
|
|
inputs={bel: solph.flows.Flow()}, |
86
|
|
|
) |
87
|
|
|
) |
88
|
|
|
|
89
|
|
|
energysystem.add( |
90
|
|
|
solph.components.Sink( |
91
|
|
|
label=Label("sink", "electricity", "demand"), |
92
|
|
|
inputs={ |
93
|
|
|
bel: solph.flows.Flow( |
94
|
|
|
fix=data["demand_el"], nominal_capacity=1 |
95
|
|
|
) |
96
|
|
|
}, |
97
|
|
|
) |
98
|
|
|
) |
99
|
|
|
|
100
|
|
|
# Sources |
101
|
|
|
energysystem.add( |
102
|
|
|
solph.components.Source( |
103
|
|
|
label=Label("source", "natural_gas", "commodity"), |
104
|
|
|
outputs={ |
105
|
|
|
bgas: solph.flows.Flow( |
106
|
|
|
nominal_capacity=194397000 * 400 / 8760, |
107
|
|
|
full_load_time_max=1, |
108
|
|
|
) |
109
|
|
|
}, |
110
|
|
|
) |
111
|
|
|
) |
112
|
|
|
|
113
|
|
|
energysystem.add( |
114
|
|
|
solph.components.Source( |
115
|
|
|
label=Label("renewable", "electricity", "wind"), |
116
|
|
|
outputs={ |
117
|
|
|
bel: solph.flows.Flow( |
118
|
|
|
fix=data["wind"], nominal_capacity=1000000 |
119
|
|
|
) |
120
|
|
|
}, |
121
|
|
|
) |
122
|
|
|
) |
123
|
|
|
|
124
|
|
|
energysystem.add( |
125
|
|
|
solph.components.Source( |
126
|
|
|
label=Label("renewable", "electricity", "pv"), |
127
|
|
|
outputs={ |
128
|
|
|
bel: solph.flows.Flow( |
129
|
|
|
fix=data["pv"], |
130
|
|
|
nominal_capacity=582000, |
131
|
|
|
) |
132
|
|
|
}, |
133
|
|
|
) |
134
|
|
|
) |
135
|
|
|
|
136
|
|
|
# Converter |
137
|
|
|
energysystem.add( |
138
|
|
|
solph.components.Converter( |
139
|
|
|
label=Label("pp", "electricity", "natural_gas"), |
140
|
|
|
inputs={bgas: solph.flows.Flow()}, |
141
|
|
|
outputs={ |
142
|
|
|
bel: solph.flows.Flow( |
143
|
|
|
nominal_capacity=10e10, variable_costs=50 |
144
|
|
|
) |
145
|
|
|
}, |
146
|
|
|
conversion_factors={bel: 0.58}, |
147
|
|
|
) |
148
|
|
|
) |
149
|
|
|
|
150
|
|
|
# Investment storage |
151
|
|
|
energysystem.add( |
152
|
|
|
solph.components.GenericStorage( |
153
|
|
|
label=Label("storage", "electricity", "battery"), |
154
|
|
|
nominal_capacity=204685, |
155
|
|
|
inputs={bel: solph.flows.Flow(variable_costs=10e10)}, |
156
|
|
|
outputs={bel: solph.flows.Flow(variable_costs=10e10)}, |
157
|
|
|
loss_rate=0.00, |
158
|
|
|
initial_storage_level=0, |
159
|
|
|
invest_relation_input_capacity=1 / 6, |
160
|
|
|
invest_relation_output_capacity=1 / 6, |
161
|
|
|
inflow_conversion_factor=1, |
162
|
|
|
outflow_conversion_factor=0.8, |
163
|
|
|
) |
164
|
|
|
) |
165
|
|
|
|
166
|
|
|
# Solve model |
167
|
|
|
om = solph.Model(energysystem) |
168
|
|
|
om.solve(solver=solver) |
169
|
|
|
energysystem.results["main"] = processing.results(om) |
170
|
|
|
energysystem.results["meta"] = processing.meta_results(om) |
171
|
|
|
|
172
|
|
|
# Check dump and restore |
173
|
|
|
energysystem.dump() |
174
|
|
|
es = solph.EnergySystem() |
175
|
|
|
es.restore() |
176
|
|
|
|
177
|
|
|
# Results |
178
|
|
|
results = es.results["main"] |
179
|
|
|
meta = es.results["meta"] |
180
|
|
|
|
181
|
|
|
electricity_bus = views.node(results, "bus_electricity_") |
182
|
|
|
my_results = electricity_bus["sequences"].sum(axis=0).to_dict() |
183
|
|
|
storage = es.groups["storage_electricity_battery"] |
184
|
|
|
storage_node = views.node(results, storage) |
185
|
|
|
my_results["max_load"] = ( |
186
|
|
|
storage_node["sequences"] |
187
|
|
|
.max()[[((storage, None), "storage_content")]] |
188
|
|
|
.iloc[0] |
189
|
|
|
) |
190
|
|
|
commodity_bus = views.node(results, "bus_natural_gas_None") |
191
|
|
|
|
192
|
|
|
gas_usage = commodity_bus["sequences"][ |
193
|
|
|
(("source_natural_gas_commodity", "bus_natural_gas_None"), "flow") |
194
|
|
|
] |
195
|
|
|
|
196
|
|
|
my_results["gas_usage"] = gas_usage.sum() |
197
|
|
|
|
198
|
|
|
stor_invest_dict = { |
199
|
|
|
"gas_usage": 1304112, |
200
|
|
|
"max_load": 0, |
201
|
|
|
(("bus_electricity_", "sink_electricity_demand"), "flow"): 8239764, |
202
|
|
|
(("bus_electricity_", "sink_electricity_excess"), "flow"): 22036732, |
203
|
|
|
(("bus_electricity_", "storage_electricity_battery"), "flow"): 0, |
204
|
|
|
(("pp_electricity_natural_gas", "bus_electricity_"), "flow"): 756385, |
205
|
|
|
(("renewable_electricity_pv", "bus_electricity_"), "flow"): 744132, |
206
|
|
|
(("renewable_electricity_wind", "bus_electricity_"), "flow"): 28775978, |
207
|
|
|
( |
208
|
|
|
( |
209
|
|
|
"storage_electricity_battery", |
210
|
|
|
"bus_electricity_", |
211
|
|
|
), |
212
|
|
|
"flow", |
213
|
|
|
): 0, |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
for key in stor_invest_dict.keys(): |
217
|
|
|
assert my_results[key] == pytest.approx(stor_invest_dict[key]) |
218
|
|
|
|
219
|
|
|
# Solver results |
220
|
|
|
assert str(meta["solver"]["Termination condition"]) == "optimal" |
221
|
|
|
assert meta["solver"]["Error rc"] == 0 |
222
|
|
|
assert str(meta["solver"]["Status"]) == "ok" |
223
|
|
|
|
224
|
|
|
# Problem results |
225
|
|
|
assert int(meta["problem"]["Lower bound"]) == 37819254 |
226
|
|
|
assert int(meta["problem"]["Upper bound"]) == 37819254 |
227
|
|
|
assert meta["problem"]["Number of variables"] == 320 |
228
|
|
|
assert meta["problem"]["Number of constraints"] == 202 |
229
|
|
|
assert meta["problem"]["Number of nonzeros"] == 116 |
230
|
|
|
assert meta["problem"]["Number of objectives"] == 1 |
231
|
|
|
assert str(meta["problem"]["Sense"]) == "minimize" |
232
|
|
|
|
233
|
|
|
# Objective function |
234
|
|
|
assert meta["objective"] == pytest.approx(37819254, abs=0.5) |
235
|
|
|
|