Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,347 +1,940 @@
|
|
1 |
-
# %%
|
2 |
# -*- coding: utf-8 -*-
|
3 |
"""
|
4 |
-
|
5 |
|
6 |
-
|
7 |
"""
|
8 |
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
from numpy import arange
|
13 |
-
import xarray as xr
|
14 |
-
import highspy
|
15 |
-
from linopy import Model, EQUAL
|
16 |
import pandas as pd
|
|
|
17 |
import plotly.express as px
|
18 |
import streamlit as st
|
|
|
|
|
|
|
19 |
import sourced as src
|
20 |
-
st.set_page_config(layout="wide")
|
21 |
-
# you can create columns to better manage the flow of your page
|
22 |
-
# this command makes 3 columns of equal width
|
23 |
-
col1, col2, col3, col4 = st.columns(4)
|
24 |
-
col1.header("Data Input")
|
25 |
-
col4.header("Download Results")
|
26 |
-
|
27 |
-
# Color dictionary for figures
|
28 |
-
color_dict = {'Biomass': 'lightgreen',
|
29 |
-
'Lignite': 'brown',
|
30 |
-
'Fossil Gas': 'grey',
|
31 |
-
'Fossil Hard coal': 'darkgrey',
|
32 |
-
'Fossil Oil': 'maroon',
|
33 |
-
'RoR': 'aquamarine',
|
34 |
-
'Hydro Water Reservoir': 'azure',
|
35 |
-
'Nuclear': 'orange',
|
36 |
-
'PV': 'yellow',
|
37 |
-
'WindOff': 'darkblue',
|
38 |
-
'WindOn': 'green',
|
39 |
-
'H2': 'crimson',
|
40 |
-
'Pumped Hydro Storage': 'lightblue',
|
41 |
-
'Battery storages': 'red',
|
42 |
-
'Electrolyzer': 'olive'}
|
43 |
-
|
44 |
-
# %%
|
45 |
-
with col1:
|
46 |
-
with open('Input_Jahr_2021.xlsx', 'rb') as f:
|
47 |
-
st.download_button('Download Excel Template', f, file_name='Input_Jahr_2021.xlsx') # Defaults to 'application/octet-stream'
|
48 |
-
|
49 |
-
#url_excel = r'Input_Jahr_2021.xlsx'
|
50 |
-
url_excel = st.file_uploader(label = 'Excel Upload')
|
51 |
-
|
52 |
-
|
53 |
-
if url_excel == None:
|
54 |
-
url_excel = r'Input_Jahr_2021.xlsx'
|
55 |
-
sets_dict, params_dict= src.load_data_from_excel(url_excel, load_from_pickle_flag = True)
|
56 |
-
with col4:
|
57 |
-
st.write('Running with standard data')
|
58 |
-
else:
|
59 |
-
sets_dict, params_dict= src.load_data_from_excel(url_excel, load_from_pickle_flag = False)
|
60 |
-
with col4:
|
61 |
-
st.write('Running with user data')
|
62 |
-
|
63 |
-
# # %%
|
64 |
-
|
65 |
-
def timstep_aggregate(time_steps_aggregate, xr ):
|
66 |
-
return xr.rolling( t = time_steps_aggregate).mean().sel(t = t[0::time_steps_aggregate])
|
67 |
-
|
68 |
-
#s_t_r_iRes = timstep_aggregate(6,s_t_r_iRes)
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
# %%
|
73 |
-
#sets_dict, params_dict= src.load_data_from_excel(url_excel,write_to_pickle_flag=True)
|
74 |
-
|
75 |
-
# %%
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
#sets_dict, params_dict= load_data_from_excel(url_excel, load_from_pickle_flag = False)
|
80 |
-
|
81 |
-
|
82 |
-
dt = 6
|
83 |
-
# Unpack sets_dict into the workspace
|
84 |
-
t = sets_dict['t']
|
85 |
-
i = sets_dict['i']
|
86 |
-
iSto = sets_dict['iSto']
|
87 |
-
iConv = sets_dict['iConv']
|
88 |
-
iPtG = sets_dict['iPtG']
|
89 |
-
iRes = sets_dict['iRes']
|
90 |
-
iHyRes = sets_dict['iHyRes']
|
91 |
-
|
92 |
-
# Unpack params_dict into the workspace
|
93 |
-
l_co2 = params_dict['l_co2']
|
94 |
-
p_co2 = params_dict['p_co2']
|
95 |
-
|
96 |
-
eff_i = params_dict['eff_i']
|
97 |
-
c_fuel_i = params_dict['c_fuel_i']
|
98 |
-
c_other_i = params_dict['c_other_i']
|
99 |
-
c_inv_i = params_dict['c_inv_i']
|
100 |
-
co2_factor_i = params_dict['co2_factor_i']
|
101 |
-
#c_var_i = params_dict['c_var_i']
|
102 |
-
K_0_i = params_dict['K_0_i']
|
103 |
-
e2p_iSto = params_dict['e2p_iSto']
|
104 |
-
# Aggregate time series
|
105 |
-
D_t = timstep_aggregate(dt,params_dict['D_t'])
|
106 |
-
s_t_r_iRes = timstep_aggregate(dt,params_dict['s_t_r_iRes'])
|
107 |
-
h_t = timstep_aggregate(dt,params_dict['h_t'])
|
108 |
-
t = D_t.get_index('t')
|
109 |
-
partial_year_factor = (8760/len(t))/dt
|
110 |
-
|
111 |
-
# Sliders and input boxes for parameters
|
112 |
-
with col2:
|
113 |
-
# Slider for CO2 limit [mio. t]
|
114 |
-
l_co2 = st.slider(value=int(params_dict['l_co2']), min_value=0, max_value=750, label="CO2 limit [mio. t]", step=50)
|
115 |
-
|
116 |
-
# Slider for H2 price / usevalue [€/MWH_th]
|
117 |
-
price_h2 = st.slider(value=100, min_value=0, max_value=300, label="Hydrogen price [€/MWh]", step=10)
|
118 |
|
119 |
-
for i_idx in c_fuel_i.get_index('i'):
|
120 |
-
if i_idx in ['Lignite']:
|
121 |
-
c_fuel_i.loc[i_idx] = st.slider(value=int(c_fuel_i.loc[i_idx]), min_value=0, max_value=300, label=i_idx + ' Price' , step=10)
|
122 |
|
123 |
-
|
|
|
|
|
|
|
|
|
|
|
124 |
|
125 |
-
|
126 |
-
# Slider for CO2 limit [mio. t]
|
127 |
-
for i_idx in c_fuel_i.get_index('i'):
|
128 |
-
if i_idx in ['Fossil Hard coal', 'Fossil Oil','Fossil Gas']:
|
129 |
-
c_fuel_i.loc[i_idx] = st.slider(value=int(c_fuel_i.loc[i_idx]), min_value=0, max_value=300, label=i_idx + ' Price' , step=10)
|
130 |
|
131 |
-
|
132 |
-
|
|
|
|
|
|
|
|
|
|
|
133 |
|
134 |
-
|
135 |
-
|
136 |
-
|
|
|
|
|
137 |
|
138 |
-
# Aggregate time series
|
139 |
-
#D_t = timstep_aggregate(dt,params_dict['D_t'])
|
140 |
-
#s_t_r_iRes = timstep_aggregate(dt,params_dict['s_t_r_iRes'])
|
141 |
-
#h_t = timstep_aggregate(dt,params_dict['h_t'])
|
142 |
-
#t = D_t.get_index('t')
|
143 |
-
#partial_year_factor = (8760/len(t))/dt
|
144 |
|
145 |
-
|
146 |
-
#technologies_no_invest = ['Electrolyzer','Biomass','RoR','Hydro Water Reservoir','Nuclear']
|
147 |
-
# %%
|
148 |
-
### Variables
|
149 |
-
m = Model()
|
150 |
|
151 |
-
C_tot = m.add_variables(name = 'C_tot') # Total costs
|
152 |
-
C_op = m.add_variables(name = 'C_op', lower = 0) # Operational costs
|
153 |
-
C_inv = m.add_variables(name = 'C_inv', lower = 0) # Investment costs
|
154 |
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
185 |
|
186 |
-
## Maximum storage charging and discharging
|
187 |
-
maxcha_iSto_t = m.add_constraints((y.sel(i = iSto) + y_ch.sel(i = iSto) - K.sel(i = iSto) <= K_0_i.sel(i = iSto)), name = 'max_cha')
|
188 |
|
189 |
-
|
190 |
-
|
|
|
|
|
|
|
191 |
|
192 |
-
## PtG H2 production
|
193 |
-
h2_prod_iPtG_t = m.add_constraints(y_ch.sel(i = iPtG) * eff_i.sel(i = iPtG) == y_h2.sel(i = iPtG), name = 'ptg_h2_prod')
|
194 |
|
195 |
-
|
196 |
-
|
|
|
197 |
|
198 |
-
|
199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
200 |
|
201 |
-
## Filling level restriction hydro reservoir
|
202 |
-
filling_iHydro_t = m.add_constraints(l.sel(i = iHyRes) - l.sel(i = iHyRes).roll(t = -1) + y.sel(i = iHyRes) * dt == h_t.sel(t = t) * dt, name = 'filling_level_hydro')
|
203 |
|
204 |
-
|
205 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
206 |
|
207 |
-
## CO2 limit
|
208 |
-
CO2_limit = m.add_constraints(((y / eff_i) * co2_factor_i * dt).sum() <= l_co2 * 1_000_000 , name = 'CO2_limit')
|
209 |
|
|
|
210 |
|
211 |
-
# %%
|
212 |
-
m.solve(solver_name = 'highs')
|
213 |
|
214 |
-
st.
|
|
|
|
|
|
|
215 |
|
216 |
-
|
|
|
217 |
|
218 |
-
# %%
|
219 |
-
#c_var_i.to_dataframe(name='VarCosts')
|
220 |
-
# %%
|
221 |
-
# Installed Cap
|
222 |
-
# Assuming df_excel has columns 'All' and 'Capacities'
|
223 |
|
224 |
-
|
225 |
-
|
|
|
|
|
226 |
|
227 |
-
|
228 |
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
233 |
|
234 |
-
|
235 |
-
|
236 |
|
237 |
-
#
|
238 |
-
|
239 |
-
CO2_price = float(m.constraints['CO2_limit'].dual.values) * (-1)
|
240 |
-
CO2_price_rounded = round(CO2_price, 2)
|
241 |
-
df_CO2_price = pd.DataFrame({'CO2 price':[CO2_price]})
|
242 |
|
243 |
-
|
244 |
-
|
245 |
-
|
|
|
|
|
246 |
|
247 |
-
#
|
248 |
-
|
249 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
250 |
|
251 |
-
|
252 |
-
|
|
|
253 |
|
254 |
-
|
255 |
-
i_with_capacity = m.solution['K'].where( m.solution['K'] > 0).dropna(dim = 'i').get_index('i')
|
256 |
-
df_production = m.solution['y'].sel(i = i_with_capacity).to_dataframe().reset_index()
|
257 |
-
fig = px.area(m.solution['y'].sel(i = i_with_capacity).to_dataframe().reset_index(), y='y', x='t', title='Production [MWh]', color='i', color_discrete_map=color_dict)
|
258 |
-
fig.update_traces(line=dict(width=0))
|
259 |
-
fig.for_each_trace(lambda trace: trace.update(fillcolor = trace.line.color))
|
260 |
|
261 |
-
with colb2:
|
262 |
-
fig
|
263 |
|
264 |
-
# %%
|
265 |
|
266 |
-
df_price = m.constraints['load'].dual.to_dataframe().reset_index()
|
267 |
-
#df_price['dual'] = df_price['dual']
|
268 |
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
|
|
|
|
|
|
273 |
|
274 |
-
#
|
|
|
275 |
|
276 |
-
|
277 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
278 |
|
279 |
-
#
|
|
|
|
|
|
|
|
|
280 |
|
281 |
-
|
282 |
-
|
283 |
-
fig
|
284 |
|
285 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
286 |
|
287 |
-
#
|
288 |
-
|
289 |
-
fig
|
290 |
-
|
291 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
292 |
|
293 |
-
|
294 |
-
|
|
|
295 |
|
296 |
-
|
297 |
-
df_charging = m.solution['y_ch'].sel(i = iSto).to_dataframe().reset_index()
|
298 |
-
fig = px.area(m.solution['y_ch'].sel(i = iSto).to_dataframe().reset_index(), y='y_ch', x='t', title='Storage charging [MWh]', color='i', color_discrete_map=color_dict)
|
299 |
-
fig.update_traces(line=dict(width=0))
|
300 |
-
fig.for_each_trace(lambda trace: trace.update(fillcolor = trace.line.color))
|
301 |
|
302 |
-
with colb2:
|
303 |
-
fig
|
304 |
|
305 |
-
# %%
|
306 |
-
df_h2_prod = m.solution['y_h2'].sel(i = iPtG).to_dataframe().reset_index()
|
307 |
-
fig = px.area(m.solution['y_h2'].sel(i = iPtG).to_dataframe().reset_index(), y='y_h2', x='t', title='Hydrogen production [MWh_th]', color='i', color_discrete_map=color_dict)
|
308 |
-
fig.update_traces(line=dict(width=0))
|
309 |
-
fig.for_each_trace(lambda trace: trace.update(fillcolor = trace.line.color))
|
310 |
|
311 |
-
|
312 |
-
|
|
|
|
|
|
|
|
|
313 |
|
314 |
-
|
315 |
-
((
|
316 |
-
|
|
|
317 |
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
import streamlit as st
|
322 |
-
import xlsxwriter
|
323 |
-
# %%
|
324 |
-
output = BytesIO()
|
325 |
-
|
326 |
-
|
327 |
-
# Create a Pandas Excel writer using XlsxWriter as the engine
|
328 |
-
with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
|
329 |
-
# Write each DataFrame to a different sheet
|
330 |
-
df_total_costs.to_excel(writer, sheet_name='Total costs', index=False)
|
331 |
-
df_CO2_price.to_excel(writer, sheet_name='CO2 price', index=False)
|
332 |
-
df_price.to_excel(writer, sheet_name='Prices', index=False)
|
333 |
-
df_contr_marg.to_excel(writer, sheet_name='Contribution Margin', index=False)
|
334 |
-
df_new_capacities.to_excel(writer, sheet_name='Capacities', index=False)
|
335 |
-
df_production.to_excel(writer, sheet_name='Production', index=False)
|
336 |
-
df_charging.to_excel(writer, sheet_name='Charging', index=False)
|
337 |
-
D_t.to_dataframe().reset_index().to_excel(writer, sheet_name='Demand', index=False)
|
338 |
-
df_curtailment.to_excel(writer, sheet_name='Curtailment', index=False)
|
339 |
-
df_h2_prod.to_excel(writer, sheet_name='H2 production', index=False)
|
340 |
-
|
341 |
-
with col4:
|
342 |
-
st.download_button(
|
343 |
-
label="Download Excel workbook Results",
|
344 |
-
data=output.getvalue(),
|
345 |
-
file_name="workbook.xlsx",
|
346 |
-
mime="application/vnd.ms-excel"
|
347 |
-
)
|
|
|
|
|
1 |
# -*- coding: utf-8 -*-
|
2 |
"""
|
3 |
+
Energy system optimization model
|
4 |
|
5 |
+
HEMF EWL: Christopher Jahns, Julian Radek, Hendrik Kramer, Cornelia Klüter, Yannik Pflugfelder
|
6 |
"""
|
7 |
|
8 |
+
import numpy as np
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
import pandas as pd
|
10 |
+
import xarray as xr
|
11 |
import plotly.express as px
|
12 |
import streamlit as st
|
13 |
+
from io import BytesIO
|
14 |
+
import xlsxwriter
|
15 |
+
from linopy import Model
|
16 |
import sourced as src
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
|
|
|
|
|
|
18 |
|
19 |
+
# Main function to run the Streamlit app
|
20 |
+
def main():
|
21 |
+
"""
|
22 |
+
Main function to set up and solve the energy system optimization model, and handle user inputs and outputs.
|
23 |
+
"""
|
24 |
+
setup_page()
|
25 |
|
26 |
+
settings = load_settings()
|
|
|
|
|
|
|
|
|
27 |
|
28 |
+
# fill session space with variables that are needed on all pages
|
29 |
+
if 'settings' not in st.session_state:
|
30 |
+
st.session_state.df = load_settings()
|
31 |
+
st.session_state.settings = settings
|
32 |
+
|
33 |
+
if 'url_excel' not in st.session_state:
|
34 |
+
st.session_state.url_excel = None
|
35 |
|
36 |
+
if 'ui_model' not in st.session_state:
|
37 |
+
st.session_state.url_excel = None
|
38 |
+
|
39 |
+
if 'output' not in st.session_state:
|
40 |
+
st.session_state.output = BytesIO()
|
41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
|
43 |
+
setup_sidebar(st.session_state.settings["df"])
|
|
|
|
|
|
|
|
|
44 |
|
|
|
|
|
|
|
45 |
|
46 |
+
|
47 |
+
# Navigation
|
48 |
+
pg = st.navigation([st.Page(page_model, title=st.session_state.settings["df"].loc['menu_modell',st.session_state.lang], icon="📊"),
|
49 |
+
st.Page(page_documentation, title=st.session_state.settings["df"].loc['menu_doku',st.session_state.lang], icon="📓"),
|
50 |
+
st.Page(page_about_us, title=st.session_state.settings["df"].loc['menu_impressum',st.session_state.lang], icon="💬")],
|
51 |
+
expanded=True)
|
52 |
+
|
53 |
+
# # Run the app
|
54 |
+
pg.run()
|
55 |
+
|
56 |
+
|
57 |
+
|
58 |
+
|
59 |
+
|
60 |
+
# Load settings and initial configurations
|
61 |
+
def load_settings():
|
62 |
+
"""
|
63 |
+
Load settings for the app, including colors and language information.
|
64 |
+
"""
|
65 |
+
settings = {
|
66 |
+
'write_pickle_from_standard_excel': True,
|
67 |
+
'df': pd.read_csv("language.csv", encoding="iso-8859-1", index_col="Label", sep=";"),
|
68 |
+
'color_dict': {
|
69 |
+
'Biomass': 'lightgreen',
|
70 |
+
'Lignite': 'brown',
|
71 |
+
'Fossil Gas': 'grey',
|
72 |
+
'Fossil Hard coal': 'darkgrey',
|
73 |
+
'Fossil Oil': 'maroon',
|
74 |
+
'RoR': 'aquamarine',
|
75 |
+
'Hydro Water Reservoir': 'azure',
|
76 |
+
'Nuclear': 'orange',
|
77 |
+
'PV': 'yellow',
|
78 |
+
'WindOff': 'darkblue',
|
79 |
+
'WindOn': 'green',
|
80 |
+
'H2': 'crimson',
|
81 |
+
'Pumped Hydro Storage': 'lightblue',
|
82 |
+
'Battery storages': 'red',
|
83 |
+
'Electrolyzer': 'olive'
|
84 |
+
},
|
85 |
+
'colors': {
|
86 |
+
'hemf_blau_dunkel': "#00386c",
|
87 |
+
'hemf_blau_hell': "#00529f",
|
88 |
+
'hemf_rot_dunkel': "#8b310d",
|
89 |
+
'hemf_rot_hell': "#d04119",
|
90 |
+
'hemf_grau': "#dadada"
|
91 |
+
}
|
92 |
+
}
|
93 |
+
return settings
|
94 |
+
|
95 |
+
# Initialize Streamlit app
|
96 |
+
def setup_page():
|
97 |
+
"""
|
98 |
+
Set up the Streamlit page with a specific layout, title, and favicon.
|
99 |
+
"""
|
100 |
+
st.set_page_config(layout="wide", page_title="Investment tool", page_icon="media/favicon.ico", initial_sidebar_state="expanded")
|
101 |
+
|
102 |
+
|
103 |
+
# Sidebar for language and links
|
104 |
+
def setup_sidebar(df):
|
105 |
+
"""
|
106 |
+
Set up the sidebar with language options and external links.
|
107 |
+
"""
|
108 |
+
st.session_state.lang = st.sidebar.selectbox("Language", ["🇬🇧 EN", "🇩🇪 DE"], key="foo", label_visibility="collapsed")[-2:]
|
109 |
+
|
110 |
+
st.sidebar.markdown("""
|
111 |
+
<style>
|
112 |
+
text-align: center;
|
113 |
+
display: block;
|
114 |
+
margin-left: auto;
|
115 |
+
margin-right: auto;
|
116 |
+
width: 100%;
|
117 |
+
</style>
|
118 |
+
""", unsafe_allow_html=True)
|
119 |
+
|
120 |
+
with st.sidebar:
|
121 |
+
left_co, cent_co, last_co = st.columns([0.1, 0.8, 0.1])
|
122 |
+
with cent_co:
|
123 |
+
st.text(" ") # add vertical empty space
|
124 |
+
""+df.loc['menu_text', st.session_state.lang]
|
125 |
+
st.text(" ") # add vertical empty space
|
126 |
+
|
127 |
+
if st.session_state.lang == "DE":
|
128 |
+
st.write("Schaue vorbei beim")
|
129 |
+
st.markdown(r'[Lehrstuhl für Energiewirtschaft](https://www.ewl.wiwi.uni-due.de)', unsafe_allow_html=True)
|
130 |
+
elif st.session_state.lang == "EN":
|
131 |
+
st.write("Get in touch with the")
|
132 |
+
st.markdown(r'[Chair of Management Science and Energy Economics](https://www.ewl.wiwi.uni-due.de/en)', unsafe_allow_html=True)
|
133 |
+
|
134 |
+
st.text(" ") # add vertical empty space
|
135 |
+
st.image("media/Logo_HEMF.svg", width=200)
|
136 |
+
st.image("media/Logo_UDE.svg", width=200)
|
137 |
+
|
138 |
+
|
139 |
+
# Load model input data
|
140 |
+
def load_model_input(df, write_pickle_from_standard_excel):
|
141 |
+
"""
|
142 |
+
Load model input data from Excel or Pickle based on user input.
|
143 |
+
"""
|
144 |
+
if st.session_state.url_excel is None:
|
145 |
+
if write_pickle_from_standard_excel:
|
146 |
+
url_excel = r'Input_Jahr_2021.xlsx'
|
147 |
+
sets_dict, params_dict = src.load_data_from_excel(url_excel, write_to_pickle_flag=True)
|
148 |
+
sets_dict, params_dict = src.load_from_pickle()
|
149 |
+
#st.write(df.loc['model_title1.1', st.session_state.lang])
|
150 |
+
# st.write('Running with standard data')
|
151 |
+
else:
|
152 |
+
url_excel = st.session_state.url_excel
|
153 |
+
sets_dict, params_dict = src.load_data_from_excel(url_excel, load_from_pickle_flag=False)
|
154 |
+
st.write(df.loc['model_title1.2', st.session_state.lang])
|
155 |
+
|
156 |
+
return sets_dict, params_dict
|
157 |
+
|
158 |
+
|
159 |
+
|
160 |
+
def page_documentation():
|
161 |
+
"""
|
162 |
+
Display documentation and mathematical model details.
|
163 |
+
"""
|
164 |
+
|
165 |
+
df = st.session_state.settings["df"]
|
166 |
+
|
167 |
+
st.header(df.loc['constr_header1', st.session_state.lang])
|
168 |
+
st.write(df.loc['constr_header2', st.session_state.lang])
|
169 |
+
|
170 |
+
col1, col2 = st.columns([6, 4])
|
171 |
+
|
172 |
+
with col1:
|
173 |
+
st.header(df.loc['constr_header3', st.session_state.lang])
|
174 |
+
|
175 |
+
with st.container():
|
176 |
+
|
177 |
+
# Objective function
|
178 |
+
st.subheader(df.loc['constr_subheader_obj_func', st.session_state.lang])
|
179 |
+
st.write(df.loc['constr_subheader_obj_func_descr', st.session_state.lang])
|
180 |
+
st.latex(r''' \text{min } C^{tot} = C^{op} + C^{inv}''')
|
181 |
+
|
182 |
+
# Operational costs minus revenue for produced hydrogen
|
183 |
+
st.write(df.loc['constr_c_op', st.session_state.lang])
|
184 |
+
st.latex(r''' \sum_{i} y_{t,i} \cdot \left( \frac{c^{fuel}_{i}}{\eta_i} + c_{i}^{other} \right) \cdot \Delta t - \sum_{i \in \mathcal{I}^{PtG}} y^{h2}_{t,i} \cdot p^{h2} \cdot \Delta t = C^{op}''')
|
185 |
+
|
186 |
+
# Investment costs
|
187 |
+
st.write(df.loc['constr_c_inv', st.session_state.lang])
|
188 |
+
st.latex(r''' \sum_{i} a_{i} \cdot K_{i} \cdot c^{inv}_{i} = C^{inv}''')
|
189 |
+
|
190 |
+
# Load-serving constraint
|
191 |
+
st.write(df.loc['constr_load_serve', st.session_state.lang])
|
192 |
+
st.latex(r''' \left( \sum_{i} y_{t,i} - \sum_{i} y_{t,i}^{ch} \right) \cdot \Delta t = D_t \cdot \Delta t, \quad \forall t \in \mathcal{T}''')
|
193 |
+
|
194 |
+
# Maximum capacity limit
|
195 |
+
st.write(df.loc['constr_max_cap', st.session_state.lang])
|
196 |
+
st.latex(r''' y_{t,i} - K_{i} \leq K_{0,i}, \quad \forall i \in \mathcal{I}''')
|
197 |
+
|
198 |
+
# Capacity limits for investment
|
199 |
+
st.write(df.loc['constr_inv_cap', st.session_state.lang])
|
200 |
+
st.latex(r''' K_{i} \leq 0, \quad \forall i \in \mathcal{I}^{no\_invest}''')
|
201 |
+
|
202 |
+
# Prevent power production by PtG
|
203 |
+
st.write(df.loc['constr_prevent_ptg', st.session_state.lang])
|
204 |
+
st.latex(r''' y_{t,i} = 0, \quad \forall i \in \mathcal{I}^{PtG}''')
|
205 |
+
|
206 |
+
# Prevent charging for non-storage technologies
|
207 |
+
st.write(df.loc['constr_prevent_chg', st.session_state.lang])
|
208 |
+
st.latex(r''' y_{t,i}^{ch} = 0, \quad \forall i \in \mathcal{I} \setminus \{ \mathcal{I}^{PtG} \cup \mathcal{I}^{Sto} \}''')
|
209 |
+
|
210 |
+
# Maximum storage charging and discharging
|
211 |
+
st.write(df.loc['constr_max_chg', st.session_state.lang])
|
212 |
+
st.latex(r''' y_{t,i} + y_{t,i}^{ch} - K_{i} \leq K_{0,i}, \quad \forall i \in \mathcal{I}^{Sto}''')
|
213 |
+
|
214 |
+
# Maximum electrolyzer capacity
|
215 |
+
st.write(df.loc['constr_max_cap_electrolyzer', st.session_state.lang])
|
216 |
+
st.latex(r''' y_{t,i}^{ch} - K_{i} \leq K_{0,i}, \quad \forall i \in \mathcal{I}^{PtG}''')
|
217 |
+
|
218 |
+
# PtG H2 production
|
219 |
+
st.write(df.loc['constr_prod_ptg', st.session_state.lang])
|
220 |
+
st.latex(r''' y_{t,i}^{ch} \cdot \eta_i = y_{t,i}^{h2}, \quad \forall i \in \mathcal{I}^{PtG}''')
|
221 |
+
|
222 |
+
# Infeed of renewables
|
223 |
+
st.write(df.loc['constr_inf_res', st.session_state.lang])
|
224 |
+
st.latex(r''' y_{t,i} + y_{t,i}^{curt} = s_{t,r,i} \cdot (K_{0,i} + K_i), \quad \forall i \in \mathcal{I}^{Res}''')
|
225 |
+
|
226 |
+
# Maximum filling level restriction for storage power plants
|
227 |
+
st.write(df.loc['constr_max_fil_sto', st.session_state.lang])
|
228 |
+
# st.latex(r''' l_{t,i} \leq K_{0,i} \cdot e2p_i, \quad \forall i \in \mathcal{I}^{Sto}''')
|
229 |
+
st.latex(r''' l_{t,i} \leq (K_{0,i} + K_{i}) \cdot \gamma_i^{Sto}, \quad \forall i \in \mathcal{I}^{Sto}''')
|
230 |
+
|
231 |
+
# Filling level restriction for hydro reservoir
|
232 |
+
st.write(df.loc['constr_fil_hyres', st.session_state.lang])
|
233 |
+
st.latex(r''' l_{t+1,i} = l_{t,i} + ( h_{t,i} - y_{t,i}) \cdot \Delta t, \quad \forall i \in \mathcal{I}^{HyRes}''')
|
234 |
+
|
235 |
+
# Filling level restriction for other storages
|
236 |
+
st.write(df.loc['constr_fil_sto', st.session_state.lang])
|
237 |
+
st.latex(r''' l_{t+1,i} = l_{t,i} - \left(\frac{y_{t,i}}{\eta_i} - y_{t,i}^{ch} \cdot \eta_i \right) \cdot \Delta t, \quad \forall i \in \mathcal{I}^{Sto}''')
|
238 |
+
|
239 |
+
# CO2 emission constraint
|
240 |
+
st.write(df.loc['constr_co2_lim', st.session_state.lang])
|
241 |
+
st.latex(r''' \sum_{t} \sum_{i} \frac{y_{t,i}}{\eta_i} \cdot \chi^{CO2}_i \cdot \Delta t \leq L^{CO2}''')
|
242 |
+
|
243 |
+
|
244 |
+
with col2:
|
245 |
+
|
246 |
+
symbols_container = st.container()
|
247 |
+
with symbols_container:
|
248 |
+
st.header(df.loc['symb_header1', st.session_state.lang])
|
249 |
+
st.write(df.loc['symb_header2', st.session_state.lang])
|
250 |
+
|
251 |
+
st.subheader(df.loc['symb_header_sets', st.session_state.lang])
|
252 |
+
st.write(f"$\mathcal{{T}}$: {df.loc['symb_time_steps', st.session_state.lang]}")
|
253 |
+
st.write(f"$\mathcal{{I}}$: {df.loc['symb_tech', st.session_state.lang]}")
|
254 |
+
st.write(f"$\mathcal{{I}}^{{\\text{{Sto}}}}$: {df.loc['symb_sto_tech', st.session_state.lang]}")
|
255 |
+
st.write(f"$\mathcal{{I}}^{{\\text{{Conv}}}}$: {df.loc['symb_conv_tech', st.session_state.lang]}")
|
256 |
+
st.write(f"$\mathcal{{I}}^{{\\text{{PtG}}}}$: {df.loc['symb_ptg', st.session_state.lang]}")
|
257 |
+
st.write(f"$\mathcal{{I}}^{{\\text{{Res}}}}$: {df.loc['symb_res', st.session_state.lang]}")
|
258 |
+
st.write(f"$\mathcal{{I}}^{{\\text{{HyRes}}}}$: {df.loc['symb_hyres', st.session_state.lang]}")
|
259 |
+
st.write(f"$\mathcal{{I}}^{{\\text{{no\_invest}}}}$: {df.loc['symb_no_inv', st.session_state.lang]}")
|
260 |
+
|
261 |
+
|
262 |
+
|
263 |
+
# Variables section
|
264 |
+
st.subheader(df.loc['symb_header_variables', st.session_state.lang])
|
265 |
+
st.write(f"$C^{{tot}}$: {df.loc['symb_tot_costs', st.session_state.lang]}")
|
266 |
+
st.write(f"$C^{{op}}$: {df.loc['symb_c_op', st.session_state.lang]}")
|
267 |
+
st.write(f"$C^{{inv}}$: {df.loc['symb_c_inv', st.session_state.lang]}")
|
268 |
+
st.write(f"$K_i$: {df.loc['symb_inst_cap', st.session_state.lang]}")
|
269 |
+
st.write(f"$y_{{t,i}}$: {df.loc['symb_el_prod', st.session_state.lang]}")
|
270 |
+
st.write(f"$y_{{t, i}}^{{ch}}$: {df.loc['symb_el_ch', st.session_state.lang]}")
|
271 |
+
st.write(f"$l_{{t,i}}$: {df.loc['symb_sto_fil', st.session_state.lang]}")
|
272 |
+
st.write(f"$y_{{t, i}}^{{curt}}$: {df.loc['symb_curt', st.session_state.lang]}")
|
273 |
+
st.write(f"$y_{{t, i}}^{{h2}}$: {df.loc['symb_h2_ptg', st.session_state.lang]}")
|
274 |
+
|
275 |
+
|
276 |
+
# Parameters section
|
277 |
+
st.subheader(df.loc['symb_header_parameters', st.session_state.lang])
|
278 |
+
st.write(f"$D_t$: {df.loc['symb_energy_demand', st.session_state.lang]}")
|
279 |
+
st.write(f"$p^{{h2}}$: {df.loc['symb_price_h2', st.session_state.lang]}")
|
280 |
+
st.write(f"$c^{{fuel}}_{{i}}$: {df.loc['symb_fuel_costs', st.session_state.lang]}")
|
281 |
+
st.write(f"$c_{{i}}^{{other}}$: {df.loc['symb_c_op_other', st.session_state.lang]}")
|
282 |
+
st.write(f"$c^{{inv}}_{{i}}$: {df.loc['symb_c_inv_tech', st.session_state.lang]}")
|
283 |
+
st.write(f"$a_{{i}}$: {df.loc['symb_annuity', st.session_state.lang]}")
|
284 |
+
st.write(f"$\eta_i$: {df.loc['symb_eff_fac', st.session_state.lang]}")
|
285 |
+
st.write(f"$K_{{0,i}}$: {df.loc['symb_max_cap_tech', st.session_state.lang]}")
|
286 |
+
st.write(f"$\chi^{{CO2}}_i$: {df.loc['symb_co2_fac', st.session_state.lang]}")
|
287 |
+
st.write(f"$L^{{CO2}}$: {df.loc['symb_co2_limit', st.session_state.lang]}")
|
288 |
+
# st.write(f"$e2p_{{\\text{{Sto}}, i}}$: {df.loc['symb_etp', st.session_state.lang]}")
|
289 |
+
st.write(f"$\gamma^{{\\text{{Sto}}}}_{{i}}$: {df.loc['symb_etp', st.session_state.lang]}")
|
290 |
+
st.write(f"$s_{{t, r, i}}$: {df.loc['symb_res_supply', st.session_state.lang]}")
|
291 |
+
st.write(f"$h_{{t, i}}$: {df.loc['symb_hyRes_inflow', st.session_state.lang]}")
|
292 |
+
|
293 |
+
# css = float_css_helper(top="50")
|
294 |
+
# symbols_container.float(css)
|
295 |
|
|
|
|
|
296 |
|
297 |
+
def page_about_us():
|
298 |
+
"""
|
299 |
+
Display information about the team and the project.
|
300 |
+
"""
|
301 |
+
st.write("About Us/Impressum")
|
302 |
|
|
|
|
|
303 |
|
304 |
+
def page_model(): #, write_pickle_from_standard_excel, color_dict):
|
305 |
+
"""
|
306 |
+
Display the main model page for energy system optimization.
|
307 |
|
308 |
+
This function sets up the user interface for the model input parameters, loads data, and configures the
|
309 |
+
optimization model before solving it and presenting the results.
|
310 |
+
"""
|
311 |
+
|
312 |
+
df = st.session_state.settings["df"]
|
313 |
+
color_dict = st.session_state.settings["color_dict"]
|
314 |
+
write_pickle_from_standard_excel = st.session_state.settings["write_pickle_from_standard_excel"]
|
315 |
+
|
316 |
+
|
317 |
|
|
|
|
|
318 |
|
319 |
+
# Load data from Excel or Pickle
|
320 |
+
sets_dict, params_dict = load_model_input(df, write_pickle_from_standard_excel)
|
321 |
+
|
322 |
+
# Unpack sets_dict into the workspace
|
323 |
+
t = sets_dict['t']
|
324 |
+
t_original = sets_dict['t']
|
325 |
+
i = sets_dict['i']
|
326 |
+
iSto = sets_dict['iSto']
|
327 |
+
iConv = sets_dict['iConv']
|
328 |
+
iPtG = sets_dict['iPtG']
|
329 |
+
iRes = sets_dict['iRes']
|
330 |
+
iHyRes = sets_dict['iHyRes']
|
331 |
+
|
332 |
+
# Unpack params_dict into the workspace
|
333 |
+
l_co2 = params_dict['l_co2']
|
334 |
+
p_co2 = params_dict['p_co2']
|
335 |
+
eff_i = params_dict['eff_i']
|
336 |
+
life_i = params_dict['life_i']
|
337 |
+
c_fuel_i = params_dict['c_fuel_i']
|
338 |
+
c_other_i = params_dict['c_other_i']
|
339 |
+
c_inv_i = params_dict['c_inv_i']
|
340 |
+
co2_factor_i = params_dict['co2_factor_i']
|
341 |
+
K_0_i = params_dict['K_0_i']
|
342 |
+
e2p_iSto = params_dict['e2p_iSto']
|
343 |
+
|
344 |
+
# Adjust efficiency for storage technologies
|
345 |
+
eff_i.loc[iSto] = np.sqrt(eff_i.loc[iSto]) # Apply square root to cycle efficiency for storage technologies
|
346 |
+
|
347 |
+
# Create columns for UI layout
|
348 |
+
col1, col2 = st.columns([0.30, 0.70], gap="large")
|
349 |
+
|
350 |
+
# Load input data
|
351 |
+
with col1:
|
352 |
+
|
353 |
+
st.title(df.loc['model_title1', st.session_state.lang])
|
354 |
+
|
355 |
+
with open('Input_Jahr_2021.xlsx', 'rb') as f:
|
356 |
+
st.download_button(df.loc['model_title1.3',st.session_state.lang], f, file_name='Input_Jahr_2021.xlsx') # Download button for Excel template
|
357 |
+
|
358 |
+
st.session_state.url_excel = st.file_uploader(label=df.loc['model_title1.4',st.session_state.lang]) # File uploader for user Excel file
|
359 |
+
|
360 |
+
st.title(df.loc['model_title2', st.session_state.lang])
|
361 |
+
|
362 |
+
st.download_button(label=df.loc['model_title2.1',st.session_state.lang], disabled=(st.session_state.output.getbuffer().nbytes==0), data=st.session_state.output.getvalue(), file_name="workbook.xlsx", mime="application/vnd.ms-excel")
|
363 |
|
|
|
|
|
364 |
|
365 |
+
st.title(df.loc['model_title4', st.session_state.lang])
|
366 |
|
|
|
|
|
367 |
|
368 |
+
if st.session_state.url_excel:
|
369 |
+
run_model = st.button(df.loc['model_run_info_excel', st.session_state.lang], key="run_model_button", help=df.loc['run_model_button_info',st.session_state.lang])
|
370 |
+
else:
|
371 |
+
run_model = st.button(df.loc['model_run_info_gui', st.session_state.lang], key="run_model_button", help=df.loc['run_model_button_info',st.session_state.lang])
|
372 |
|
373 |
+
|
374 |
+
|
375 |
|
|
|
|
|
|
|
|
|
|
|
376 |
|
377 |
+
# Set up user interface for parameters
|
378 |
+
with col2:
|
379 |
+
|
380 |
+
st.title(df.loc['model_title3', st.session_state.lang])
|
381 |
|
382 |
+
col1param, col2param = st.columns(2)
|
383 |
|
384 |
+
with col1param:
|
385 |
+
l_co2 = st.slider(value=int(params_dict['l_co2']), min_value=0, max_value=750, label=df.loc['model_label_co2',st.session_state.lang], step=50)
|
386 |
+
price_h2 = st.slider(value=100, min_value=0, max_value=300, label=df.loc['model_label_h2',st.session_state.lang], step=10)
|
387 |
+
for i_idx in params_dict['c_fuel_i'].get_index('i'):
|
388 |
+
if i_idx in ['Lignite']:
|
389 |
+
params_dict['c_fuel_i'].loc[i_idx] = st.slider(value=int(params_dict['c_fuel_i'].loc[i_idx]),
|
390 |
+
min_value=0, max_value=300, label=df.loc[f'model_label_{i_idx}',st.session_state.lang], step=10)
|
391 |
+
|
392 |
+
dt = st.number_input(label=df.loc['model_label_t',st.session_state.lang], min_value=1, max_value=len(t), value=6,
|
393 |
+
help=df.loc['model_label_t_info',st.session_state.lang])
|
394 |
+
|
395 |
+
with col2param:
|
396 |
+
for i_idx in params_dict['c_fuel_i'].get_index('i'):
|
397 |
+
if i_idx in ['Fossil Hard coal', 'Fossil Oil', 'Fossil Gas']:
|
398 |
+
params_dict['c_fuel_i'].loc[i_idx] = st.slider(value=int(params_dict['c_fuel_i'].loc[i_idx]),
|
399 |
+
min_value=0, max_value=300, label=df.loc[f'model_label_{i_idx}',st.session_state.lang], step=10)
|
400 |
+
|
401 |
+
# Create a dictionary to map German names to English names
|
402 |
+
tech_mapping_de_to_en = {
|
403 |
+
df.loc[f'tech_{tech.lower()}', 'DE']: df.loc[f'tech_{tech.lower()}', 'EN']
|
404 |
+
for tech in sets_dict['i'] if f'tech_{tech.lower()}' in df.index
|
405 |
+
}
|
406 |
+
|
407 |
+
# Set options and default values based on the selected language
|
408 |
+
if st.session_state.lang == 'DE':
|
409 |
+
# German options for the user interface
|
410 |
+
options = [
|
411 |
+
df.loc[f'tech_{tech.lower()}', 'DE'] for tech in sets_dict['i'] if f'tech_{tech.lower()}' in df.index
|
412 |
+
]
|
413 |
+
default = [
|
414 |
+
df.loc[f'tech_{tech.lower()}', 'DE'] for tech in ['Lignite', 'Fossil Gas', 'Fossil Hard coal', 'Fossil Oil', 'PV', 'WindOff', 'WindOn', 'H2', 'Pumped Hydro Storage', 'Battery storages', 'Electrolyzer']
|
415 |
+
if f'tech_{tech.lower()}' in df.index
|
416 |
+
]
|
417 |
+
else:
|
418 |
+
# English options for the user interface
|
419 |
+
options = sets_dict['i']
|
420 |
+
default = ['Lignite', 'Fossil Gas', 'Fossil Hard coal', 'Fossil Oil', 'PV', 'WindOff', 'WindOn', 'H2', 'Pumped Hydro Storage', 'Battery storages', 'Electrolyzer']
|
421 |
+
|
422 |
+
# Multiselect for technology options in the user interface
|
423 |
+
selected_technologies = st.multiselect(
|
424 |
+
label=df.loc['model_label_tech', st.session_state.lang],
|
425 |
+
options=options,
|
426 |
+
default=[tech for tech in default if tech in options]
|
427 |
+
)
|
428 |
+
|
429 |
+
# If language is German, map selected German names back to their English equivalents
|
430 |
+
if st.session_state.lang == 'DE':
|
431 |
+
technologies_invest = [tech_mapping_de_to_en[tech] for tech in selected_technologies]
|
432 |
+
else:
|
433 |
+
technologies_invest = selected_technologies
|
434 |
+
|
435 |
+
# Technologies that will not be invested in (based on English names)
|
436 |
+
technologies_no_invest = [tech for tech in sets_dict['i'] if tech not in technologies_invest]
|
437 |
+
|
438 |
+
|
439 |
+
st.markdown("-------")
|
440 |
+
|
441 |
+
|
442 |
+
# Time series aggregation for various parameters
|
443 |
+
D_t = timstep_aggregate(dt, params_dict['D_t'], t)
|
444 |
+
s_t_r_iRes = timstep_aggregate(dt, params_dict['s_t_r_iRes'], t)
|
445 |
+
h_t = timstep_aggregate(dt, params_dict['h_t'], t)
|
446 |
+
t = D_t.get_index('t')
|
447 |
+
partial_year_factor = (8760 / len(t)) / dt
|
448 |
+
|
449 |
+
|
450 |
+
|
451 |
+
|
452 |
+
if run_model:
|
453 |
+
# Model setup
|
454 |
+
m = Model()
|
455 |
+
|
456 |
+
# Define Variables
|
457 |
+
C_tot = m.add_variables(name='C_tot') # Total costs
|
458 |
+
C_op = m.add_variables(name='C_op', lower=0) # Operational costs
|
459 |
+
C_inv = m.add_variables(name='C_inv', lower=0) # Investment costs
|
460 |
+
K = m.add_variables(coords=[i], name='K', lower=0) # Endogenous capacity
|
461 |
+
y = m.add_variables(coords=[t, i], name='y', lower=0) # Electricity production
|
462 |
+
y_ch = m.add_variables(coords=[t, i], name='y_ch', lower=0) # Electricity consumption
|
463 |
+
l = m.add_variables(coords=[t, i], name='l', lower=0) # Storage filling level
|
464 |
+
y_curt = m.add_variables(coords=[t, i], name='y_curt', lower=0) # RES curtailment
|
465 |
+
y_h2 = m.add_variables(coords=[t, i], name='y_h2', lower=0) # H2 production
|
466 |
+
|
467 |
+
# Define Objective function
|
468 |
+
C_tot = C_op + C_inv
|
469 |
+
m.add_objective(C_tot)
|
470 |
+
|
471 |
+
# Define Constraints
|
472 |
+
# Operational costs minus revenue for produced hydrogen
|
473 |
+
m.add_constraints((y * c_fuel_i / eff_i).sum() * dt - (y_h2.sel(i=iPtG) * price_h2).sum() * dt == C_op, name='C_op_sum')
|
474 |
+
|
475 |
+
# Investment costs
|
476 |
+
m.add_constraints((K * c_inv_i).sum() == C_inv, name='C_inv_sum')
|
477 |
+
|
478 |
+
# Load serving
|
479 |
+
m.add_constraints((((y).sum(dims='i') - y_ch.sum(dims='i')) * dt == D_t.sel(t=t) * dt), name='load')
|
480 |
+
|
481 |
+
# Maximum capacity limit
|
482 |
+
m.add_constraints((y - K <= K_0_i), name='max_cap')
|
483 |
+
|
484 |
+
# Capacity limits for investment
|
485 |
+
m.add_constraints((K.sel(i=technologies_no_invest) <= 0), name='max_cap_invest')
|
486 |
+
|
487 |
+
# Prevent power production by PtG
|
488 |
+
m.add_constraints((y.sel(i=iPtG) <= 0), name='prevent_ptg_prod')
|
489 |
+
|
490 |
+
# Prevent charging for non-storage technologies
|
491 |
+
m.add_constraints((y_ch.sel(i=[x for x in i if x not in iPtG and x not in iSto]) <= 0), name='no_charging')
|
492 |
+
|
493 |
+
# Maximum storage charging and discharging
|
494 |
+
m.add_constraints((y.sel(i=iSto) + y_ch.sel(i=iSto) - K.sel(i=iSto) <= K_0_i.sel(i=iSto)), name='max_cha')
|
495 |
+
|
496 |
+
# Maximum electrolyzer capacity
|
497 |
+
m.add_constraints((y_ch.sel(i=iPtG) - K.sel(i=iPtG) <= K_0_i.sel(i=iPtG)), name='max_cha_ptg')
|
498 |
+
|
499 |
+
# PtG H2 production
|
500 |
+
m.add_constraints(y_ch.sel(i=iPtG) * eff_i.sel(i=iPtG) == y_h2.sel(i=iPtG), name='ptg_h2_prod')
|
501 |
+
|
502 |
+
# Infeed of renewables
|
503 |
+
m.add_constraints((y.sel(i=iRes) - s_t_r_iRes.sel(i=iRes).sel(t=t) * K.sel(i=iRes) + y_curt.sel(i=iRes) == s_t_r_iRes.sel(i=iRes).sel(t=t) * K_0_i.sel(i=iRes)), name='infeed')
|
504 |
+
|
505 |
+
# Maximum filling level restriction for storage power plants
|
506 |
+
m.add_constraints((l.sel(i=iSto) - K.sel(i=iSto) * e2p_iSto.sel(i=iSto) <= K_0_i.sel(i=iSto) * e2p_iSto.sel(i=iSto)), name='max_sto_filling')
|
507 |
+
|
508 |
+
# Filling level restriction for hydro reservoir
|
509 |
+
m.add_constraints(l.sel(i=iHyRes) - l.sel(i=iHyRes).roll(t=-1) + y.sel(i=iHyRes) * dt == h_t.sel(t=t) * dt, name='filling_level_hydro')
|
510 |
+
|
511 |
+
# Filling level restriction for other storages
|
512 |
+
m.add_constraints(l.sel(i=iSto) - (l.sel(i=iSto).roll(t=-1) - (y.sel(i=iSto) / eff_i.sel(i=iSto)) * dt + y_ch.sel(i=iSto) * eff_i.sel(i=iSto) * dt) == 0, name='filling_level')
|
513 |
+
|
514 |
+
# CO2 limit
|
515 |
+
m.add_constraints(((y / eff_i) * co2_factor_i * dt).sum() <= l_co2 * 1_000_000, name='CO2_limit')
|
516 |
+
|
517 |
+
# Solve the model
|
518 |
+
m.solve(solver_name='highs')
|
519 |
+
|
520 |
+
# Prepare columns for figures
|
521 |
+
colb1, colb2 = st.columns(2)
|
522 |
+
|
523 |
+
# Generate and display figures
|
524 |
+
st.markdown("---")
|
525 |
+
|
526 |
+
df_total_costs = plot_total_costs(m, colb1, df)
|
527 |
+
df_CO2_price = plot_co2_price(m, colb2, df)
|
528 |
+
plot_installed_capacities(m, K_0_i, color_dict)
|
529 |
+
df_new_capacities = plot_new_capacities(m, color_dict, colb1, df)
|
530 |
+
|
531 |
+
# Only plot production for technologies with capacity
|
532 |
+
i_with_capacity = m.solution['K'].where(m.solution['K'] > 0).dropna(dim='i').get_index('i')
|
533 |
+
df_production = plot_production(m, i_with_capacity, dt, color_dict, colb2, df)
|
534 |
+
|
535 |
+
df_price = plot_electricity_prices(m, dt, colb1, df)
|
536 |
+
df_contr_marg = plot_contribution_margin(m, dt, color_dict, colb2, df)
|
537 |
+
df_curtailment = plot_curtailment(m, iRes, color_dict, colb1, df)
|
538 |
+
df_charging = plot_storage_charging(m, iSto, color_dict, colb2, df)
|
539 |
+
df_h2_prod = plot_hydrogen_production(m, iPtG, color_dict, colb2, df)
|
540 |
+
|
541 |
+
# Export results
|
542 |
+
st.session_state.output = BytesIO()
|
543 |
+
with pd.ExcelWriter(st.session_state.output, engine='xlsxwriter') as writer:
|
544 |
+
disaggregate_df(df_total_costs, t, t_original, dt).to_excel(writer, sheet_name='Total costs', index=False)
|
545 |
+
disaggregate_df(df_CO2_price, t, t_original, dt).to_excel(writer, sheet_name='CO2 price', index=False)
|
546 |
+
disaggregate_df(df_price, t, t_original, dt).to_excel(writer, sheet_name='Prices', index=False)
|
547 |
+
disaggregate_df(df_contr_marg, t, t_original, dt).to_excel(writer, sheet_name='Contribution Margin', index=False)
|
548 |
+
disaggregate_df(df_new_capacities, t, t_original, dt).to_excel(writer, sheet_name='Capacities', index=False)
|
549 |
+
disaggregate_df(df_production, t, t_original, dt).to_excel(writer, sheet_name='Production', index=False)
|
550 |
+
disaggregate_df(df_charging, t, t_original, dt).to_excel(writer, sheet_name='Charging', index=False)
|
551 |
+
disaggregate_df(D_t.to_dataframe().reset_index(), t, t_original, dt).to_excel(writer, sheet_name='Demand', index=False)
|
552 |
+
disaggregate_df(df_curtailment, t, t_original, dt).to_excel(writer, sheet_name='Curtailment', index=False)
|
553 |
+
disaggregate_df(df_h2_prod, t, t_original, dt).to_excel(writer, sheet_name='H2 production', index=False)
|
554 |
+
|
555 |
+
st.rerun()
|
556 |
+
|
557 |
+
|
558 |
+
def timstep_aggregate(time_steps_aggregate, xr_data, t):
|
559 |
+
"""
|
560 |
+
Aggregates time steps in the data using rolling mean and selects based on step size.
|
561 |
+
"""
|
562 |
+
return xr_data.rolling(t=time_steps_aggregate).mean().sel(t=t[0::time_steps_aggregate])
|
563 |
+
|
564 |
+
# Visualization functions
|
565 |
+
def plot_installed_capacities(m, K_0_i, color_dict):
|
566 |
+
"""
|
567 |
+
Plots the total installed capacities.
|
568 |
+
"""
|
569 |
+
df_installed_cap = (m.solution['K'] + K_0_i).to_dataframe(name='K').reset_index()
|
570 |
+
fig = px.bar(df_installed_cap, y='i', x='K', orientation='h',
|
571 |
+
title='Total Installed Capacities [MW]', color='i', color_discrete_map=color_dict)
|
572 |
+
|
573 |
+
return fig
|
574 |
+
|
575 |
+
|
576 |
+
def plot_total_costs(m, col, df):
|
577 |
+
"""
|
578 |
+
Displays the total costs.
|
579 |
+
"""
|
580 |
+
total_costs = float(m.solution['C_inv'].values) + float(m.solution['C_op'].values)
|
581 |
+
total_costs_rounded = round(total_costs / 1e9, 2)
|
582 |
+
with col:
|
583 |
+
st.write(f"{df.loc['plot_label_total_costs', st.session_state.lang]} {total_costs_rounded}")
|
584 |
+
# st.write(f'Total costs: {total_costs_rounded} bn. €')
|
585 |
+
|
586 |
+
df_total_costs = pd.DataFrame({'Total costs':[total_costs]})
|
587 |
+
return df_total_costs
|
588 |
+
|
589 |
+
def plot_co2_price(m, col, df):
|
590 |
+
"""
|
591 |
+
Displays the CO2 price based on the CO2 constraint dual values.
|
592 |
+
"""
|
593 |
+
CO2_price = float(m.constraints['CO2_limit'].dual.values) * (-1)
|
594 |
+
CO2_price_rounded = round(CO2_price, 2)
|
595 |
+
df_CO2_price = pd.DataFrame({'CO2 price': [CO2_price]})
|
596 |
+
with col:
|
597 |
+
st.write(f"{df.loc['plot_label_co2_price', st.session_state.lang]} {CO2_price_rounded}")
|
598 |
+
|
599 |
+
return df_CO2_price
|
600 |
+
|
601 |
+
|
602 |
+
def plot_new_capacities(m, color_dict, col, df):
|
603 |
+
"""
|
604 |
+
Plots the new capacities installed in MW as a bar chart and pie chart.
|
605 |
+
Includes technologies with 0 MW capacity in the bar chart.
|
606 |
+
Supports both German and English labels for technologies while ensuring color consistency.
|
607 |
+
"""
|
608 |
+
# Convert the solution for new capacities to a DataFrame
|
609 |
+
df_new_capacities = m.solution['K'].round(0).to_dataframe().reset_index()
|
610 |
+
|
611 |
+
# Store the English technology names in a separate column to maintain color consistency
|
612 |
+
df_new_capacities['i_en'] = df_new_capacities['i']
|
613 |
+
|
614 |
+
# Check if the language is German and map English names to German for display
|
615 |
+
if st.session_state.lang == 'DE':
|
616 |
+
tech_mapping_en_to_de = {
|
617 |
+
df.loc[f'tech_{tech.lower()}', 'EN']: df.loc[f'tech_{tech.lower()}', 'DE']
|
618 |
+
for tech in df_new_capacities['i_en'] if f'tech_{tech.lower()}' in df.index
|
619 |
+
}
|
620 |
+
# Replace the English technology names with German ones for display
|
621 |
+
df_new_capacities['i'] = df_new_capacities['i_en'].replace(tech_mapping_en_to_de)
|
622 |
+
|
623 |
+
# Bar plot for new capacities (including technologies with 0 MW)
|
624 |
+
fig_bar = px.bar(df_new_capacities, y='i', x='K', orientation='h',
|
625 |
+
title=df.loc['plot_label_new_capacities', st.session_state.lang],
|
626 |
+
color='i_en', # Use the English names for consistent coloring
|
627 |
+
color_discrete_map=color_dict)
|
628 |
+
|
629 |
+
# Hide the legend completely since the labels are already next to the bars
|
630 |
+
fig_bar.update_layout(showlegend=False)
|
631 |
+
|
632 |
+
with col:
|
633 |
+
st.plotly_chart(fig_bar)
|
634 |
+
|
635 |
+
# Pie chart for new capacities (only show technologies with K > 0 in pie chart)
|
636 |
+
df_new_capacities_filtered = df_new_capacities[df_new_capacities["K"] > 0]
|
637 |
+
fig_pie = px.pie(df_new_capacities_filtered, names='i', values='K',
|
638 |
+
title=df.loc['plot_label_new_capacities_pie', st.session_state.lang],
|
639 |
+
color='i_en', color_discrete_map=color_dict)
|
640 |
+
|
641 |
+
# Remove English labels (i_en) from the pie chart legend
|
642 |
+
fig_pie.update_layout(legend_title_text=df.loc['label_technology', st.session_state.lang])
|
643 |
+
fig_pie.for_each_trace(lambda t: t.update(name=df_new_capacities_filtered['i'].iloc[0] if st.session_state.lang == 'DE' else t.name))
|
644 |
+
|
645 |
+
with col:
|
646 |
+
st.plotly_chart(fig_pie)
|
647 |
+
|
648 |
+
return df_new_capacities
|
649 |
+
|
650 |
+
|
651 |
+
def plot_production(m, i_with_capacity, dt, color_dict, col, df):
|
652 |
+
"""
|
653 |
+
Plots the energy production for technologies with capacity as an area chart.
|
654 |
+
Supports both German and English labels for technologies while ensuring color consistency.
|
655 |
+
"""
|
656 |
+
# Convert the production data to a DataFrame
|
657 |
+
df_production = m.solution['y'].sel(i=i_with_capacity).to_dataframe().reset_index()
|
658 |
+
|
659 |
+
# Store the English technology names in a separate column to maintain color consistency
|
660 |
+
df_production['i_en'] = df_production['i']
|
661 |
+
|
662 |
+
# Check if the language is German and map English names to German for display
|
663 |
+
if st.session_state.lang == 'DE':
|
664 |
+
tech_mapping_en_to_de = {
|
665 |
+
df.loc[f'tech_{tech.lower()}', 'EN']: df.loc[f'tech_{tech.lower()}', 'DE']
|
666 |
+
for tech in df_production['i_en'] if f'tech_{tech.lower()}' in df.index
|
667 |
+
}
|
668 |
+
# Replace the English technology names with German ones for display
|
669 |
+
df_production['i'] = df_production['i_en'].replace(tech_mapping_en_to_de)
|
670 |
+
|
671 |
+
# Area plot for energy production
|
672 |
+
fig = px.area(df_production, y='y', x='t',
|
673 |
+
title=df.loc['plot_label_production', st.session_state.lang],
|
674 |
+
color='i_en', # Use the English names for consistent coloring
|
675 |
+
color_discrete_map=color_dict)
|
676 |
+
|
677 |
+
fig.update_traces(line=dict(width=0))
|
678 |
+
fig.for_each_trace(lambda trace: trace.update(fillcolor=trace.line.color))
|
679 |
+
|
680 |
+
with col:
|
681 |
+
st.plotly_chart(fig)
|
682 |
|
683 |
+
# Pie chart for total production
|
684 |
+
df_production_sum = (df_production.groupby(['i', 'i_en'])['y'].sum() * dt / 1000).round(0).reset_index()
|
685 |
|
686 |
+
# If the language is set to German, display German labels, otherwise use English
|
687 |
+
pie_column = 'i' if st.session_state.lang == 'DE' else 'i_en'
|
|
|
|
|
|
|
688 |
|
689 |
+
# Pie chart for total production
|
690 |
+
fig_pie = px.pie(df_production_sum, names=pie_column, values='y',
|
691 |
+
title=df.loc['plot_label_total_production_pie', st.session_state.lang],
|
692 |
+
color='i_en', # Ensure the coloring stays consistent using the 'i_en' column
|
693 |
+
color_discrete_map=color_dict)
|
694 |
|
695 |
+
# Update legend title to reflect the correct language
|
696 |
+
fig_pie.update_layout(legend_title_text=df.loc['label_technology', st.session_state.lang])
|
697 |
+
|
698 |
+
with col:
|
699 |
+
st.plotly_chart(fig_pie)
|
700 |
+
|
701 |
+
return df_production
|
702 |
+
|
703 |
+
|
704 |
+
def plot_electricity_prices(m, dt, col, df):
|
705 |
+
"""
|
706 |
+
Plots the electricity price and the price duration curve.
|
707 |
+
Supports both German and English labels for the plot titles and axis labels.
|
708 |
+
"""
|
709 |
+
# Convert the dual constraints to a DataFrame
|
710 |
+
df_price = m.constraints['load'].dual.to_dataframe().reset_index()
|
711 |
+
|
712 |
+
# Line plot for electricity prices
|
713 |
+
fig_price = px.line(df_price, y='dual', x='t',
|
714 |
+
title=df.loc['plot_label_electricity_prices', st.session_state.lang],
|
715 |
+
# range_y=[0, 250],
|
716 |
+
labels={'dual': df.loc['label_electricity_price', st.session_state.lang],
|
717 |
+
't': df.loc['label_time', st.session_state.lang]})
|
718 |
+
with col:
|
719 |
+
st.plotly_chart(fig_price)
|
720 |
+
|
721 |
+
# Create the price duration curve
|
722 |
+
df_sorted_price = df_price["dual"].repeat(dt).sort_values(ascending=False).reset_index(drop=True) / int(dt)
|
723 |
+
fig_duration = px.line(y=df_sorted_price, x=df_sorted_price.index,
|
724 |
+
title=df.loc['plot_label_price_duration_curve', st.session_state.lang],
|
725 |
+
# labels={"x": df.loc['label_hours_of_year', st.session_state.lang]},
|
726 |
+
# range_y=[0, 250])
|
727 |
+
)
|
728 |
+
with col:
|
729 |
+
st.plotly_chart(fig_duration)
|
730 |
+
|
731 |
+
return df_price
|
732 |
+
|
733 |
+
|
734 |
+
def plot_contribution_margin(m, dt, color_dict, col, df):
|
735 |
+
"""
|
736 |
+
Plots the contribution margin for each technology.
|
737 |
+
Supports both German and English labels for titles and axes while ensuring color consistency.
|
738 |
+
"""
|
739 |
+
# Convert the dual constraints to a DataFrame
|
740 |
+
df_contr_marg = m.constraints['max_cap'].dual.to_dataframe().reset_index()
|
741 |
+
|
742 |
+
# Adjust the 'dual' values for the contribution margin calculation
|
743 |
+
df_contr_marg['dual'] = df_contr_marg['dual'] / dt * (-1)
|
744 |
+
|
745 |
+
# Store the English technology names in a separate column to maintain color consistency
|
746 |
+
df_contr_marg['i_en'] = df_contr_marg['i']
|
747 |
+
|
748 |
+
# Check if the language is German and map English names to German for display
|
749 |
+
if st.session_state.lang == 'DE':
|
750 |
+
tech_mapping_en_to_de = {
|
751 |
+
df.loc[f'tech_{tech.lower()}', 'EN']: df.loc[f'tech_{tech.lower()}', 'DE']
|
752 |
+
for tech in df_contr_marg['i_en'] if f'tech_{tech.lower()}' in df.index
|
753 |
+
}
|
754 |
+
# Replace the English technology names with German ones for display
|
755 |
+
df_contr_marg['i'] = df_contr_marg['i_en'].replace(tech_mapping_en_to_de)
|
756 |
+
|
757 |
+
# Plot contribution margin for each technology
|
758 |
+
fig = px.line(df_contr_marg, y='dual', x='t',
|
759 |
+
title=df.loc['plot_label_contribution_margin', st.session_state.lang],
|
760 |
+
color='i_en', # Use the English names for consistent coloring
|
761 |
+
range_y=[0, 250], color_discrete_map=color_dict,
|
762 |
+
# labels={
|
763 |
+
# 'dual': df.loc['label_contribution_margin', st.session_state.lang],
|
764 |
+
# 't': df.loc['label_time', st.session_state.lang],
|
765 |
+
# 'i_en': df.loc['label_technology', st.session_state.lang]
|
766 |
+
# }
|
767 |
+
)
|
768 |
+
|
769 |
+
# Update legend to display the correct language
|
770 |
+
fig.update_layout(legend_title_text=df.loc['label_technology', st.session_state.lang])
|
771 |
+
|
772 |
+
# For German language, update the legend to show German technology names
|
773 |
+
if st.session_state.lang == 'DE':
|
774 |
+
fig.for_each_trace(lambda t: t.update(name=df_contr_marg.loc[df_contr_marg['i_en'] == t.name, 'i'].values[0]))
|
775 |
|
776 |
+
# Display the plot
|
777 |
+
with col:
|
778 |
+
st.plotly_chart(fig)
|
779 |
|
780 |
+
return df_contr_marg
|
|
|
|
|
|
|
|
|
|
|
781 |
|
|
|
|
|
782 |
|
|
|
783 |
|
|
|
|
|
784 |
|
785 |
+
def plot_curtailment(m, iRes, color_dict, col, df):
|
786 |
+
"""
|
787 |
+
Plots the curtailment of renewable energy.
|
788 |
+
Supports both German and English labels for titles and axes while ensuring color consistency.
|
789 |
+
"""
|
790 |
+
# Convert the curtailment solution to a DataFrame
|
791 |
+
df_curtailment = m.solution['y_curt'].sel(i=iRes).to_dataframe().reset_index()
|
792 |
|
793 |
+
# Store the English technology names in a separate column to maintain color consistency
|
794 |
+
df_curtailment['i_en'] = df_curtailment['i']
|
795 |
|
796 |
+
# Check if the language is German and map English names to German for display
|
797 |
+
if st.session_state.lang == 'DE':
|
798 |
+
tech_mapping_en_to_de = {
|
799 |
+
df.loc[f'tech_{tech.lower()}', 'EN']: df.loc[f'tech_{tech.lower()}', 'DE']
|
800 |
+
for tech in df_curtailment['i_en'] if f'tech_{tech.lower()}' in df.index
|
801 |
+
}
|
802 |
+
# Replace the English technology names with German ones for display
|
803 |
+
df_curtailment['i'] = df_curtailment['i_en'].replace(tech_mapping_en_to_de)
|
804 |
+
else:
|
805 |
+
df_curtailment['i'] = df_curtailment['i_en'] # Use English names if not German
|
806 |
|
807 |
+
# Area plot for curtailment of renewable energy
|
808 |
+
fig = px.area(df_curtailment, y='y_curt', x='t',
|
809 |
+
title=df.loc['plot_label_curtailment', st.session_state.lang],
|
810 |
+
color='i_en', # Use the English names for consistent coloring
|
811 |
+
color_discrete_map=color_dict)
|
812 |
|
813 |
+
# Remove line traces and use fill colors for the area plot
|
814 |
+
fig.update_traces(line=dict(width=0))
|
815 |
+
fig.for_each_trace(lambda trace: trace.update(fillcolor=trace.line.color))
|
816 |
|
817 |
+
# Update the legend title to reflect the correct language (German or English)
|
818 |
+
fig.update_layout(legend_title_text=df.loc['label_technology', st.session_state.lang])
|
819 |
+
|
820 |
+
# For German language, update the legend to show German technology names
|
821 |
+
if st.session_state.lang == 'DE':
|
822 |
+
fig.for_each_trace(lambda t: t.update(name=df_curtailment.loc[df_curtailment['i_en'] == t.name, 'i'].values[0]))
|
823 |
+
|
824 |
+
# Display the plot
|
825 |
+
with col:
|
826 |
+
st.plotly_chart(fig)
|
827 |
+
|
828 |
+
return df_curtailment
|
829 |
+
|
830 |
+
|
831 |
+
|
832 |
+
def plot_storage_charging(m, iSto, color_dict, col, df):
|
833 |
+
"""
|
834 |
+
Plots the charging of storage technologies.
|
835 |
+
Supports both German and English labels for titles and axes while ensuring color consistency.
|
836 |
+
"""
|
837 |
+
# Convert the storage charging solution to a DataFrame
|
838 |
+
df_charging = m.solution['y_ch'].sel(i=iSto).to_dataframe().reset_index()
|
839 |
+
|
840 |
+
# Store the English technology names in a separate column to maintain color consistency
|
841 |
+
df_charging['i_en'] = df_charging['i']
|
842 |
+
|
843 |
+
# Check if the language is German and map English names to German for display
|
844 |
+
if st.session_state.lang == 'DE':
|
845 |
+
tech_mapping_en_to_de = {
|
846 |
+
df.loc[f'tech_{tech.lower()}', 'EN']: df.loc[f'tech_{tech.lower()}', 'DE']
|
847 |
+
for tech in df_charging['i_en'] if f'tech_{tech.lower()}' in df.index
|
848 |
+
}
|
849 |
+
# Replace the English technology names with German ones for display
|
850 |
+
df_charging['i'] = df_charging['i_en'].replace(tech_mapping_en_to_de)
|
851 |
+
else:
|
852 |
+
df_charging['i'] = df_charging['i_en'] # Use English names if not German
|
853 |
+
|
854 |
+
# Area plot for storage charging
|
855 |
+
fig = px.area(df_charging, y='y_ch', x='t',
|
856 |
+
title=df.loc['plot_label_storage_charging', st.session_state.lang],
|
857 |
+
color='i_en', # Use the English names for consistent coloring
|
858 |
+
color_discrete_map=color_dict)
|
859 |
+
|
860 |
+
# Remove line traces and use fill colors for the area plot
|
861 |
+
fig.update_traces(line=dict(width=0))
|
862 |
+
fig.for_each_trace(lambda trace: trace.update(fillcolor=trace.line.color))
|
863 |
+
|
864 |
+
# Update the legend title to reflect the correct language (German or English)
|
865 |
+
fig.update_layout(legend_title_text=df.loc['label_technology', st.session_state.lang])
|
866 |
|
867 |
+
# For German language, update the legend to show German technology names
|
868 |
+
if st.session_state.lang == 'DE':
|
869 |
+
fig.for_each_trace(lambda t: t.update(name=df_charging.loc[df_charging['i_en'] == t.name, 'i'].values[0]))
|
870 |
+
|
871 |
+
# Display the plot
|
872 |
+
with col:
|
873 |
+
st.plotly_chart(fig)
|
874 |
+
|
875 |
+
return df_charging
|
876 |
+
|
877 |
+
|
878 |
+
|
879 |
+
def plot_hydrogen_production(m, iPtG, color_dict, col, df):
|
880 |
+
"""
|
881 |
+
Plots the hydrogen production.
|
882 |
+
Supports both German and English labels for titles and axes while ensuring color consistency.
|
883 |
+
"""
|
884 |
+
# Convert the hydrogen production data to a DataFrame
|
885 |
+
df_h2_prod = m.solution['y_h2'].sel(i=iPtG).to_dataframe().reset_index()
|
886 |
+
|
887 |
+
# Store the English technology names in a separate column to maintain color consistency
|
888 |
+
df_h2_prod['i_en'] = df_h2_prod['i']
|
889 |
+
|
890 |
+
# Check if the language is German and map English names to German for display
|
891 |
+
if st.session_state.lang == 'DE':
|
892 |
+
tech_mapping_en_to_de = {
|
893 |
+
df.loc[f'tech_{tech.lower()}', 'EN']: df.loc[f'tech_{tech.lower()}', 'DE']
|
894 |
+
for tech in df_h2_prod['i_en'] if f'tech_{tech.lower()}' in df.index
|
895 |
+
}
|
896 |
+
# Replace the English technology names with German ones for display
|
897 |
+
df_h2_prod['i'] = df_h2_prod['i_en'].replace(tech_mapping_en_to_de)
|
898 |
+
else:
|
899 |
+
df_h2_prod['i'] = df_h2_prod['i_en'] # Keep English names if not German
|
900 |
+
|
901 |
+
# Area plot for hydrogen production
|
902 |
+
fig = px.area(df_h2_prod, y='y_h2', x='t',
|
903 |
+
title=df.loc['plot_label_hydrogen_production', st.session_state.lang],
|
904 |
+
color='i_en', # Use the English names for consistent coloring
|
905 |
+
color_discrete_map=color_dict)
|
906 |
+
|
907 |
+
# Remove line traces and use fill colors for the area plot
|
908 |
+
fig.update_traces(line=dict(width=0))
|
909 |
+
fig.for_each_trace(lambda trace: trace.update(fillcolor=trace.line.color))
|
910 |
+
|
911 |
+
# Update the legend title to reflect the correct language (German or English)
|
912 |
+
fig.update_layout(legend_title_text=df.loc['label_technology', st.session_state.lang])
|
913 |
+
|
914 |
+
# For German language, update the legend to show German technology names
|
915 |
+
if st.session_state.lang == 'DE':
|
916 |
+
fig.for_each_trace(lambda t: t.update(name=df_h2_prod.loc[df_h2_prod['i_en'] == t.name, 'i'].values[0]))
|
917 |
|
918 |
+
# Display the plot
|
919 |
+
with col:
|
920 |
+
st.plotly_chart(fig)
|
921 |
|
922 |
+
return df_h2_prod
|
|
|
|
|
|
|
|
|
923 |
|
|
|
|
|
924 |
|
|
|
|
|
|
|
|
|
|
|
925 |
|
926 |
+
def disaggregate_df(df, t, t_original, dt):
|
927 |
+
"""
|
928 |
+
Disaggregates the DataFrame based on the original time steps.
|
929 |
+
"""
|
930 |
+
if "t" not in list(df.columns):
|
931 |
+
return df
|
932 |
|
933 |
+
df_t_all = pd.DataFrame({"t_all": t_original.to_series(), 't': t.repeat(dt)}).reset_index(drop=True)
|
934 |
+
df_output = df.merge(df_t_all, on='t').drop('t', axis=1).rename({'t_all': 't'}, axis=1)
|
935 |
+
df_output = df_output[[df_output.columns[-1]] + list(df_output.columns[:-1])]
|
936 |
+
return df_output.sort_values('t')
|
937 |
|
938 |
+
|
939 |
+
if __name__ == "__main__":
|
940 |
+
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|