1
|
|
|
import pandas as pd |
2
|
|
|
from helpers import LCOH |
3
|
|
|
from helpers import epc |
4
|
|
|
|
5
|
|
|
import oemof.solph as solph |
6
|
|
|
|
7
|
|
|
|
8
|
|
|
data = pd.read_csv("input_data.csv", sep=";", index_col=0, parse_dates=True) |
9
|
|
|
|
10
|
|
|
district_heating_system = solph.EnergySystem( |
11
|
|
|
timeindex=data.index, infer_last_interval=False |
12
|
|
|
) |
13
|
|
|
|
14
|
|
|
heat_bus = solph.Bus(label="heat network") |
15
|
|
|
gas_bus = solph.Bus(label="gas network") |
16
|
|
|
waste_heat_bus = solph.Bus(label="waste heat network") |
17
|
|
|
electricity_bus = solph.Bus(label="electricity network") |
18
|
|
|
|
19
|
|
|
district_heating_system.add(heat_bus, gas_bus, waste_heat_bus, electricity_bus) |
20
|
|
|
|
21
|
|
|
gas_source = solph.components.Source( |
22
|
|
|
label="gas source", |
23
|
|
|
outputs={gas_bus: solph.flows.Flow(variable_costs=data["gas price"])}, |
24
|
|
|
) |
25
|
|
|
|
26
|
|
|
electricity_source = solph.components.Source( |
27
|
|
|
label="electricity source", |
28
|
|
|
outputs={ |
29
|
|
|
electricity_bus: solph.flows.Flow(variable_costs=data["el_spot_price"]) |
30
|
|
|
}, |
31
|
|
|
) |
32
|
|
|
|
33
|
|
|
heat_sink = solph.components.Sink( |
34
|
|
|
label="heat sink", |
35
|
|
|
inputs={ |
36
|
|
|
heat_bus: solph.flows.Flow( |
37
|
|
|
nominal_value=data["heat demand"].max(), |
38
|
|
|
fix=data["heat demand"] / data["heat demand"].max(), |
39
|
|
|
) |
40
|
|
|
}, |
41
|
|
|
) |
42
|
|
|
|
43
|
|
|
district_heating_system.add(gas_source, electricity_source, heat_sink) |
44
|
|
|
|
45
|
|
|
# %%[sec_1_start] |
46
|
|
|
waste_heat_source = solph.components.Source( |
47
|
|
|
label="waste heat source", |
48
|
|
|
outputs={waste_heat_bus: solph.flows.Flow(nominal_value=1, fix=2.5)}, |
49
|
|
|
) |
50
|
|
|
|
51
|
|
|
district_heating_system.add(waste_heat_source) |
52
|
|
|
# %%[sec_1_end] |
53
|
|
|
|
54
|
|
|
spec_inv_gas_boiler = 60000 |
55
|
|
|
var_cost_gas_boiler = 1.10 |
56
|
|
|
|
57
|
|
|
gas_boiler = solph.components.Converter( |
58
|
|
|
label="gas boiler", |
59
|
|
|
inputs={gas_bus: solph.flows.Flow()}, |
60
|
|
|
outputs={ |
61
|
|
|
heat_bus: solph.flows.Flow( |
62
|
|
|
nominal_value=solph.Investment( |
63
|
|
|
ep_costs=epc(spec_inv_gas_boiler), maximum=50 |
64
|
|
|
), |
65
|
|
|
variable_costs=var_cost_gas_boiler, |
66
|
|
|
) |
67
|
|
|
}, |
68
|
|
|
conversion_factors={gas_bus: 0.95}, |
69
|
|
|
) |
70
|
|
|
|
71
|
|
|
district_heating_system.add(gas_boiler) |
72
|
|
|
|
73
|
|
|
spec_inv_storage = 1060 |
74
|
|
|
var_cost_storage = 0.1 |
75
|
|
|
|
76
|
|
|
heat_storage = solph.components.GenericStorage( |
77
|
|
|
label="heat storage", |
78
|
|
|
nominal_capacity=solph.Investment(ep_costs=epc(spec_inv_storage)), |
79
|
|
|
inputs={heat_bus: solph.flows.Flow(variable_costs=var_cost_storage)}, |
80
|
|
|
outputs={heat_bus: solph.flows.Flow(variable_costs=var_cost_storage)}, |
81
|
|
|
invest_relation_input_capacity=1 / 24, |
82
|
|
|
invest_relation_output_capacity=1 / 24, |
83
|
|
|
balanced=True, |
84
|
|
|
loss_rate=0.001, |
85
|
|
|
) |
86
|
|
|
|
87
|
|
|
district_heating_system.add(heat_storage) |
88
|
|
|
|
89
|
|
|
cop = 3.5 |
90
|
|
|
spec_inv_heat_pump = 500000 |
91
|
|
|
var_cost_heat_pump = 1.2 |
92
|
|
|
|
93
|
|
|
|
94
|
|
|
heat_pump = solph.components.Converter( |
95
|
|
|
label="heat pump", |
96
|
|
|
inputs={ |
97
|
|
|
electricity_bus: solph.flows.Flow(), |
98
|
|
|
waste_heat_bus: solph.flows.Flow(), |
99
|
|
|
}, |
100
|
|
|
outputs={ |
101
|
|
|
heat_bus: solph.flows.Flow( |
102
|
|
|
nominal_value=solph.Investment( |
103
|
|
|
ep_costs=epc(spec_inv_heat_pump), maximum=999 |
104
|
|
|
), |
105
|
|
|
variable_costs=1.2, |
106
|
|
|
min=0.5, |
107
|
|
|
nonconvex=solph.NonConvex(), |
108
|
|
|
) |
109
|
|
|
}, |
110
|
|
|
conversion_factors={ |
111
|
|
|
electricity_bus: 1 / cop, |
112
|
|
|
waste_heat_bus: (cop - 1) / cop, |
113
|
|
|
}, |
114
|
|
|
) |
115
|
|
|
district_heating_system.add(heat_pump) |
116
|
|
|
|
117
|
|
|
# solve model |
118
|
|
|
model = solph.Model(district_heating_system) |
119
|
|
|
model.solve( |
120
|
|
|
solver="cbc", |
121
|
|
|
solve_kwargs={"tee": True}, |
122
|
|
|
cmdline_options={"ratio": 0.01}, |
123
|
|
|
allow_nonoptimal=True, |
124
|
|
|
) |
125
|
|
|
|
126
|
|
|
# results |
127
|
|
|
results = solph.processing.results(model) |
128
|
|
|
|
129
|
|
|
data_gas_bus = solph.views.node(results, "gas network")["sequences"] |
130
|
|
|
data_heat_bus = solph.views.node(results, "heat network")["sequences"] |
131
|
|
|
data_el_bus = solph.views.node(results, "electricity network")["sequences"] |
132
|
|
|
data_caps = solph.views.node(results, "heat network")["scalars"] |
133
|
|
|
|
134
|
|
|
cap_gas_boiler = data_caps[("gas boiler", "heat network"), "invest"] |
135
|
|
|
cap_heat_pump = data_caps[("heat pump", "heat network"), "invest"] |
136
|
|
|
cap_storage = solph.views.node(results, "heat storage")["scalars"][ |
137
|
|
|
(("heat storage", "None"), "invest") |
138
|
|
|
] |
139
|
|
|
cap_storage_out = data_caps[("heat storage", "heat network"), "invest"] |
140
|
|
|
|
141
|
|
|
print(f"capacity gas boiler: {cap_gas_boiler:.1f} MW") |
142
|
|
|
print(f"capacity heat pump: {cap_heat_pump:.1f} MW") |
143
|
|
|
print(f"capacity heat storage: {cap_storage:.1f} MWh") |
144
|
|
|
print(f"capacity heat storage (out): {cap_storage_out:.1f} MW") |
145
|
|
|
|
146
|
|
|
invest_cost = ( |
147
|
|
|
spec_inv_gas_boiler * cap_gas_boiler |
148
|
|
|
+ spec_inv_storage * cap_storage |
149
|
|
|
+ spec_inv_heat_pump * cap_heat_pump |
150
|
|
|
) |
151
|
|
|
operation_cost = ( |
152
|
|
|
var_cost_gas_boiler |
153
|
|
|
* data_heat_bus[(("gas boiler", "heat network"), "flow")].sum() |
154
|
|
|
+ ( |
155
|
|
|
data["gas price"] |
156
|
|
|
* data_gas_bus[(("gas network", "gas boiler"), "flow")] |
157
|
|
|
).sum() |
158
|
|
|
+ var_cost_heat_pump |
159
|
|
|
* data_heat_bus[(("heat pump", "heat network"), "flow")].sum() |
160
|
|
|
+ ( |
161
|
|
|
data["el_spot_price"] |
162
|
|
|
* data_el_bus[(("electricity network", "heat pump"), "flow")] |
163
|
|
|
).sum() |
164
|
|
|
+ var_cost_storage |
165
|
|
|
* data_heat_bus[(("heat storage", "heat network"), "flow")].sum() |
166
|
|
|
+ var_cost_storage |
167
|
|
|
* data_heat_bus[(("heat network", "heat storage"), "flow")].sum() |
168
|
|
|
) |
169
|
|
|
heat_produced = data_heat_bus[(("heat network", "heat sink"), "flow")].sum() |
170
|
|
|
|
171
|
|
|
lcoh = LCOH(invest_cost, operation_cost, heat_produced) |
172
|
|
|
print(f"LCOH: {lcoh:.2f} €/MWh") |
173
|
|
|
|
174
|
|
|
import matplotlib.pyplot as plt |
175
|
|
|
|
176
|
|
|
# plt.style.use('dark_background') |
177
|
|
|
|
178
|
|
|
unit_colors = { |
179
|
|
|
"gas boiler": "#EC6707", |
180
|
|
|
"heat pump": "#B54036", |
181
|
|
|
"heat storage (discharge)": "#BFBFBF", |
182
|
|
|
"heat storage (charge)": "#696969", |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
fig, ax = plt.subplots(figsize=[10, 6]) |
186
|
|
|
|
187
|
|
|
bottom = 0 |
188
|
|
|
for unit in ["heat pump", "gas boiler", "heat storage"]: |
189
|
|
|
unit_label = f"{unit} (discharge)" if "storage" in unit else unit |
190
|
|
|
ax.bar( |
191
|
|
|
data_heat_bus.index, |
192
|
|
|
data_heat_bus[((unit, "heat network"), "flow")], |
193
|
|
|
label=unit_label, |
194
|
|
|
color=unit_colors[unit_label], |
195
|
|
|
bottom=bottom, |
196
|
|
|
) |
197
|
|
|
bottom += data_heat_bus[((unit, "heat network"), "flow")] |
198
|
|
|
|
199
|
|
|
unit_label = "heat storage (charge)" |
200
|
|
|
ax.bar( |
201
|
|
|
data_heat_bus.index, |
202
|
|
|
-1 * data_heat_bus[(("heat network", "heat storage"), "flow")], |
203
|
|
|
label=unit_label, |
204
|
|
|
color=unit_colors[unit_label], |
205
|
|
|
) |
206
|
|
|
|
207
|
|
|
ax.legend(loc="upper center", ncol=2) |
208
|
|
|
ax.grid(axis="y") |
209
|
|
|
ax.set_ylabel("Hourly heat production in MWh") |
210
|
|
|
|
211
|
|
|
# plt.tight_layout() |
212
|
|
|
# plt.savefig('intro_tut_dhs_4_hourly_heat_production.svg') |
213
|
|
|
|
214
|
|
|
|
215
|
|
|
fig, ax = plt.subplots(figsize=[10, 6]) |
216
|
|
|
|
217
|
|
|
data_heat_storage = solph.views.node(results, "heat storage")["sequences"] |
218
|
|
|
|
219
|
|
|
ax.plot( |
220
|
|
|
data_heat_storage[(("heat storage", "None"), "storage_content")], |
221
|
|
|
color="#00395B", |
222
|
|
|
) |
223
|
|
|
|
224
|
|
|
ax.grid(axis="y") |
225
|
|
|
ax.set_ylabel("Hourly heat storage content in MWh") |
226
|
|
|
|
227
|
|
|
# plt.tight_layout() |
228
|
|
|
# plt.savefig('intro_tut_dhs_4_hourly_storage_content.svg') |
229
|
|
|
|
230
|
|
|
plt.show() |
231
|
|
|
|