ConniKLu commited on
Commit
70eed2b
·
verified ·
1 Parent(s): 0f551a5

Upload 3 files

Browse files
Files changed (3) hide show
  1. Input_Jahr_2023.xlsx +2 -2
  2. app.py +589 -116
  3. language.csv +10 -2
Input_Jahr_2023.xlsx CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:071c0d7133608f15f5daf16663cd7cbd172113d50fc9ec6026fa40f9f7d91128
3
- size 1050705
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c095435c12bc2aff1a321fae0bce3a651fdebd521d1d738ab24f359bfaeed911
3
+ size 1050697
app.py CHANGED
@@ -4,7 +4,7 @@ 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
@@ -72,7 +72,7 @@ def main():
72
 
73
 
74
 
75
-
76
  # Load settings and initial configurations
77
  def load_settings():
78
  """
@@ -117,13 +117,62 @@ def setup_page():
117
  st.set_page_config(layout="wide", page_title="Investment tool", page_icon="media/favicon.ico", initial_sidebar_state="expanded")
118
 
119
 
120
- # Sidebar for language and links
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  def setup_sidebar(df):
122
  """
123
- Set up the sidebar with language options and external links.
 
124
  """
125
- st.session_state.lang = st.sidebar.selectbox("Language", ["🇬🇧 EN", "🇩🇪 DE"], key="foo", label_visibility="collapsed")[-2:]
126
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  st.sidebar.markdown("""
128
  <style>
129
  text-align: center;
@@ -137,9 +186,9 @@ def setup_sidebar(df):
137
  with st.sidebar:
138
  left_co, cent_co, last_co = st.columns([0.1, 0.8, 0.1])
139
  with cent_co:
140
- st.text(" ") # add vertical empty space
141
- ""+df.loc['menu_text', st.session_state.lang]
142
- st.text(" ") # add vertical empty space
143
 
144
  if st.session_state.lang == "DE":
145
  st.write("Schaue vorbei beim")
@@ -152,7 +201,6 @@ def setup_sidebar(df):
152
  st.image("media/Logo_HEMF.svg", width=200)
153
  st.image("media/Logo_UDE.svg", width=200)
154
 
155
-
156
  # Load model input data
157
  def load_model_input(df, write_pickle_from_standard_excel):
158
  """
@@ -320,7 +368,7 @@ def page_about_us():
320
  """
321
  st.write("About Us/Impressum")
322
 
323
-
324
  def page_model(): #, write_pickle_from_standard_excel, color_dict):
325
  """
326
  Display the main model page for energy system optimization.
@@ -400,7 +448,12 @@ def page_model(): #, write_pickle_from_standard_excel, color_dict):
400
  with st.form("input_custom"):
401
 
402
  col1form, col2form, col3form = st.columns([0.25, 0.25, 0.50])
403
-
 
 
 
 
 
404
  # colum 1 form
405
  l_co2 = col1form.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)
406
  price_h2 = col1form.slider(value=100, min_value=0, max_value=300, label=df.loc['model_label_h2',st.session_state.lang], step=10)
@@ -408,41 +461,60 @@ def page_model(): #, write_pickle_from_standard_excel, color_dict):
408
  if i_idx in ['Lignite']:
409
  params_dict['c_fuel_i'].loc[i_idx] = col1form.slider(value=int(params_dict['c_fuel_i'].loc[i_idx]),
410
  min_value=0, max_value=300, label=df.loc[f'model_label_{i_idx}',st.session_state.lang], step=10)
411
-
412
- # colum 1 form
413
- for i_idx in params_dict['c_fuel_i'].get_index('i'):
414
- if i_idx in ['Fossil Hard coal', 'Fossil Oil', 'CCGT']:
415
  params_dict['c_fuel_i'].loc[i_idx] = col2form.slider(value=int(params_dict['c_fuel_i'].loc[i_idx]),
416
- min_value=0, max_value=300, label=df.loc[f'model_label_{i_idx}',st.session_state.lang], step=10)
417
  params_dict['c_fuel_i'].loc['OCGT'] = params_dict['c_fuel_i'].loc['CCGT']
418
- # Create a dictionary to map German names to English names
419
- tech_mapping_de_to_en = {
420
- df.loc[f'tech_{tech.lower()}', 'DE']: df.loc[f'tech_{tech.lower()}', 'EN']
421
- for tech in sets_dict['i'] if f'tech_{tech.lower()}' in df.index
422
- }
423
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
  # Set options and default values based on the selected language
425
- if st.session_state.lang == 'DE':
426
- # German options for the user interface
427
- options = [
428
- df.loc[f'tech_{tech.lower()}', 'DE'] for tech in sets_dict['i'] if f'tech_{tech.lower()}' in df.index
429
- ]
430
- default = [
431
- df.loc[f'tech_{tech.lower()}', 'DE'] for tech in ['Lignite', 'CCGT', 'OCGT', 'Fossil Hard coal', 'Fossil Oil', 'PV', 'WindOff', 'WindOn', 'H2', 'Pumped Hydro Storage', 'Battery storages', 'Electrolyzer']
432
- if f'tech_{tech.lower()}' in df.index
433
- ]
434
- else:
435
- # English options for the user interface
436
- options = sets_dict['i']
437
- default = ['Lignite', 'CCGT', 'OCGT', 'Fossil Hard coal', 'Fossil Oil', 'PV', 'WindOff', 'WindOn', 'H2', 'Pumped Hydro Storage', 'Battery storages', 'Electrolyzer']
438
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
439
  # Multiselect for technology options in the user interface
440
  selected_technologies = col3form.multiselect(
441
  label=df.loc['model_label_tech', st.session_state.lang],
442
  options=options,
443
  default=[tech for tech in default if tech in options]
444
  )
445
-
446
  # If language is German, map selected German names back to their English equivalents
447
  if st.session_state.lang == 'DE':
448
  technologies_invest = [tech_mapping_de_to_en[tech] for tech in selected_technologies]
@@ -590,22 +662,49 @@ def page_model(): #, write_pickle_from_standard_excel, color_dict):
590
  # Generate and display figures
591
  st.markdown("---")
592
 
593
- df_total_costs = plot_total_costs(m, colb1, df)
594
- df_CO2_price = plot_co2_price(m, colb2, df)
595
- df_new_capacities = plot_new_capacities(m, color_dict, colb1, df)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
596
 
597
- # Only plot production for technologies with capacity
598
- i_with_capacity = m.solution['K'].where((m.solution['K'] > 0) & (m.solution['i'] != 'Electrolyzer')).dropna(dim='i').get_index('i')
599
- df_production = plot_production(m, i_with_capacity, dt, color_dict, colb2, df)
600
- # df_price = plot_electricity_prices(m, dt, colb2, df)
601
- df_curtailment = plot_curtailment(m, iRes, color_dict, colb1, df)
602
- df_residual_load_duration = plot_residual_load_duration(m, dt, colb1, df, D_t, i_with_capacity, iRes, color_dict, df_curtailment, iConv)
603
- df_price = plot_electricity_prices(m, dt, colb2, df, df_residual_load_duration)
604
-
605
- df_contr_marg = plot_contribution_margin(m, dt, i_with_capacity, color_dict, colb1, df)
606
- # df_curtailment = plot_curtailment(m, iRes, color_dict, colb1, df)
607
- df_charging = plot_storage_charging(m, iSto, color_dict, colb2, df)
608
- df_h2_prod = plot_hydrogen_production(m, iPtG, color_dict, colb1, df)
609
 
610
  # df_stackplot = plot_stackplot(m)
611
 
@@ -618,13 +717,13 @@ def page_model(): #, write_pickle_from_standard_excel, color_dict):
618
  disaggregate_df(df_total_costs, t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_total_costs', st.session_state.lang], index=False)
619
  disaggregate_df(df_CO2_price, t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_co2_price', st.session_state.lang], index=False)
620
  disaggregate_df(df_price, t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_prices', st.session_state.lang], index=False)
621
- disaggregate_df(df_contr_marg, t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_contribution_margin', st.session_state.lang], index=False)
622
  disaggregate_df(df_new_capacities, t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_capacities', st.session_state.lang], index=False)
623
  disaggregate_df(df_production, t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_production', st.session_state.lang], index=False)
624
  disaggregate_df(df_charging, t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_charging', st.session_state.lang], index=False)
625
  disaggregate_df(D_t.to_dataframe().reset_index(), t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_demand', st.session_state.lang], index=False)
626
  disaggregate_df(df_curtailment, t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_curtailment', st.session_state.lang], index=False)
627
- disaggregate_df(df_h2_prod, t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_h2_production', st.session_state.lang], index=False)
628
 
629
  with col1:
630
  st.title(df.loc['model_title2', st.session_state.lang])
@@ -721,23 +820,24 @@ def plot_new_capacities(m, color_dict, col, df):
721
  with col:
722
  st.plotly_chart(fig_bar)
723
 
724
- # Pie chart for new capacities (only show technologies with K > 0 in pie chart)
725
- df_new_capacities_filtered = df_new_capacities[df_new_capacities["K"] > 0]
726
- fig_pie = px.pie(df_new_capacities_filtered, names='i', values='K',
727
- title=df.loc['plot_label_new_capacities_pie', st.session_state.lang],
728
- color='i_en', color_discrete_map=color_dict)
 
729
 
730
- # Remove English labels (i_en) from the pie chart legend
731
- fig_pie.update_layout(legend_title_text=df.loc['label_technology', st.session_state.lang])
732
- 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))
733
 
734
- with col:
735
- st.plotly_chart(fig_pie)
736
 
737
  return df_new_capacities
738
 
739
 
740
- def plot_production(m, i_with_capacity, dt, color_dict, col, df):
741
  """
742
  Plots the energy production for technologies with capacity as an area chart.
743
  Supports both German and English labels for technologies while ensuring color consistency.
@@ -790,22 +890,23 @@ def plot_production(m, i_with_capacity, dt, color_dict, col, df):
790
  st.plotly_chart(fig)
791
 
792
  # Pie chart for total production
793
- df_production_sum = (df_production.groupby(['i', 'i_en'])['y'].sum() * dt / 1000).round(0).reset_index()
 
794
 
795
- # If the language is set to German, display German labels, otherwise use English
796
- pie_column = 'i' if st.session_state.lang == 'DE' else 'i_en'
797
 
798
- # Pie chart for total production
799
- fig_pie = px.pie(df_production_sum, names=pie_column, values='y',
800
- title=df.loc['plot_label_total_production_pie', st.session_state.lang],
801
- color='i_en', # Ensure the coloring stays consistent using the 'i_en' column
802
- color_discrete_map=color_dict)
803
 
804
- # Update legend title to reflect the correct language
805
- fig_pie.update_layout(legend_title_text=df.loc['label_technology', st.session_state.lang])
806
-
807
- with col:
808
- st.plotly_chart(fig_pie)
809
 
810
  return df_production
811
 
@@ -822,13 +923,14 @@ def plot_electricity_prices(m, dt, col, df, df_residual_load_duration):
822
  df_price['t'] = df_price['t'].str.strip("'")
823
  df_price['t'] = pd.to_datetime(df_price['t'], format='%Y-%m-%d %H:%M %z')
824
 
825
- # Line plot for electricity prices
826
- fig_price = px.line(df_price, y='dual', x='t',
827
- title=df.loc['plot_label_electricity_prices', st.session_state.lang],
828
- labels={'dual': '', 't': ''}
829
- )
830
- with col:
831
- st.plotly_chart(fig_price)
 
832
 
833
  # Create the price duration curve
834
  df_sorted_price = df_price["dual"].repeat(dt).sort_values(ascending=False).reset_index(drop=True) / int(dt)
@@ -845,8 +947,8 @@ def plot_electricity_prices(m, dt, col, df, df_residual_load_duration):
845
  x=df_sorted_price.index,
846
  y=df_sorted_price,
847
  mode='lines',
848
- name=df.loc['plot_label_price_duration_curve', st.session_state.lang],
849
- line=dict(color='blue', width=2)
850
  ))
851
 
852
  # Add secondary y-axis trace (Residual load)
@@ -854,48 +956,60 @@ def plot_electricity_prices(m, dt, col, df, df_residual_load_duration):
854
  x=df_axis2.index,
855
  y=df_axis2,
856
  mode='lines',
857
- name=df.loc['plot_label_residual_load', st.session_state.lang],
858
- line=dict(color='red', width=2),
859
- yaxis='y2'
860
  ))
861
 
862
- # Layout mit neuen title-formaten (kein titlefont mehr!)
863
  fig_duration.update_layout(
864
  title=df.loc['plot_label_price_duration_curve', st.session_state.lang],
865
  xaxis=dict(
866
- title=df.loc['label_hours', st.session_state.lang]
867
  ),
868
  yaxis=dict(
869
- title=dict(
870
- text=df.loc['plot_label_price_duration_curve', st.session_state.lang],
871
- font=dict(color='blue')
872
- ),
873
- range=[-(100/(ax2_max/(ax2_max-ax2_min))-100), 100],
874
- tickfont=dict(color='blue')
875
  ),
876
  yaxis2=dict(
877
- title=dict(
878
- text=df.loc['plot_label_residual_load', st.session_state.lang],
879
- font=dict(color='red')
880
- ),
881
- range=[ax2_min, ax2_max],
882
- tickfont=dict(color='red'),
883
- overlaying='y',
884
- side='right'
885
  ),
886
- legend=dict(
887
- x=1,
888
- y=1,
889
- xanchor='right',
890
- yanchor='top',
891
- bgcolor='rgba(255, 255, 255, 0.5)',
892
- bordercolor='black',
893
- borderwidth=1
894
- )
895
  )
896
 
 
897
  with col:
898
  st.plotly_chart(fig_duration)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
899
 
900
  return df_price
901
 
@@ -1072,7 +1186,7 @@ def plot_contribution_margin(m, dt, i_with_capacity, color_dict, col, df):
1072
 
1073
 
1074
 
1075
- def plot_curtailment(m, iRes, color_dict, col, df):
1076
  """
1077
  Plots the curtailment of renewable energy.
1078
  Supports both German and English labels for titles and axes while ensuring color consistency.
@@ -1117,8 +1231,9 @@ def plot_curtailment(m, iRes, color_dict, col, df):
1117
  fig.for_each_trace(lambda t: t.update(name=df_curtailment.loc[df_curtailment['i_en'] == t.name, 'i'].values[0]))
1118
 
1119
  # Display the plot
1120
- with col:
1121
- st.plotly_chart(fig)
 
1122
 
1123
  return df_curtailment
1124
 
@@ -1231,6 +1346,364 @@ def plot_hydrogen_production(m, iPtG, color_dict, col, df):
1231
 
1232
 
1233
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1234
  def disaggregate_df(df, t, t_original, dt):
1235
  """
1236
  Disaggregates the DataFrame based on the original time steps.
 
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
 
72
 
73
 
74
 
75
+ # %%
76
  # Load settings and initial configurations
77
  def load_settings():
78
  """
 
117
  st.set_page_config(layout="wide", page_title="Investment tool", page_icon="media/favicon.ico", initial_sidebar_state="expanded")
118
 
119
 
120
+ # # Sidebar for language and links
121
+ # def setup_sidebar(df):
122
+ # """
123
+ # Set up the sidebar with language options and external links.
124
+ # """
125
+ # st.session_state.lang = st.sidebar.selectbox("Language", ["🇬🇧 EN", "🇩🇪 DE"], key="foo", label_visibility="collapsed")[-2:]
126
+
127
+ # st.sidebar.markdown("""
128
+ # <style>
129
+ # text-align: center;
130
+ # display: block;
131
+ # margin-left: auto;
132
+ # margin-right: auto;
133
+ # width: 100%;
134
+ # </style>
135
+ # """, unsafe_allow_html=True)
136
+
137
+ # with st.sidebar:
138
+ # left_co, cent_co, last_co = st.columns([0.1, 0.8, 0.1])
139
+ # with cent_co:
140
+ # st.text(" ") # add vertical empty space
141
+ # ""+df.loc['menu_text', st.session_state.lang]
142
+ # st.text(" ") # add vertical empty space
143
+
144
+ # if st.session_state.lang == "DE":
145
+ # st.write("Schaue vorbei beim")
146
+ # st.markdown(r'[Lehrstuhl für Energiewirtschaft](https://www.ewl.wiwi.uni-due.de)', unsafe_allow_html=True)
147
+ # elif st.session_state.lang == "EN":
148
+ # st.write("Get in touch with the")
149
+ # st.markdown(r'[Chair of Management Science and Energy Economics](https://www.ewl.wiwi.uni-due.de/en)', unsafe_allow_html=True)
150
+
151
+ # st.text(" ") # add vertical empty space
152
+ # st.image("media/Logo_HEMF.svg", width=200)
153
+ # st.image("media/Logo_UDE.svg", width=200)
154
+
155
+
156
  def setup_sidebar(df):
157
  """
158
+ Set up the sidebar with language and level options as two-step selection,
159
+ using localized text from the loaded dataframe.
160
  """
 
161
 
162
+ # Step 1: Language selection
163
+ lang_choice = st.sidebar.selectbox("Language", ["🇩🇪 DE", "🇬🇧 EN"], key="lang_select", label_visibility="collapsed")
164
+ st.session_state.lang = lang_choice[-2:] # 'EN' or 'DE'
165
+
166
+ # Step 2: Localized level selection
167
+ level_options = {
168
+ f"🎓 {df.loc['menu_untergraduate', st.session_state.lang]}": "undergraduate",
169
+ f"🎓 {df.loc['menu_graduate', st.session_state.lang]}": "graduate"
170
+ }
171
+
172
+ level_choice = st.sidebar.selectbox(df.loc['menu_level', st.session_state.lang], list(level_options.keys()), key="level_select")
173
+ st.session_state.level = level_options[level_choice]
174
+
175
+ # Optional styling and centered sidebar content
176
  st.sidebar.markdown("""
177
  <style>
178
  text-align: center;
 
186
  with st.sidebar:
187
  left_co, cent_co, last_co = st.columns([0.1, 0.8, 0.1])
188
  with cent_co:
189
+ st.text(" ")
190
+ st.markdown(df.loc['menu_text', st.session_state.lang])
191
+ st.text(" ")
192
 
193
  if st.session_state.lang == "DE":
194
  st.write("Schaue vorbei beim")
 
201
  st.image("media/Logo_HEMF.svg", width=200)
202
  st.image("media/Logo_UDE.svg", width=200)
203
 
 
204
  # Load model input data
205
  def load_model_input(df, write_pickle_from_standard_excel):
206
  """
 
368
  """
369
  st.write("About Us/Impressum")
370
 
371
+ # %%
372
  def page_model(): #, write_pickle_from_standard_excel, color_dict):
373
  """
374
  Display the main model page for energy system optimization.
 
448
  with st.form("input_custom"):
449
 
450
  col1form, col2form, col3form = st.columns([0.25, 0.25, 0.50])
451
+ # Create a dictionary to map German names to English names
452
+ tech_mapping_de_to_en = {
453
+ df.loc[f'tech_{tech.lower()}', 'DE']: df.loc[f'tech_{tech.lower()}', 'EN']
454
+ for tech in sets_dict['i'] if f'tech_{tech.lower()}' in df.index
455
+ }
456
+
457
  # colum 1 form
458
  l_co2 = col1form.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)
459
  price_h2 = col1form.slider(value=100, min_value=0, max_value=300, label=df.loc['model_label_h2',st.session_state.lang], step=10)
 
461
  if i_idx in ['Lignite']:
462
  params_dict['c_fuel_i'].loc[i_idx] = col1form.slider(value=int(params_dict['c_fuel_i'].loc[i_idx]),
463
  min_value=0, max_value=300, label=df.loc[f'model_label_{i_idx}',st.session_state.lang], step=10)
464
+ elif i_idx in ['Fossil Hard coal', 'Fossil Oil', 'CCGT']:
 
 
 
465
  params_dict['c_fuel_i'].loc[i_idx] = col2form.slider(value=int(params_dict['c_fuel_i'].loc[i_idx]),
466
+ min_value=0, max_value=300, label=df.loc[f'model_label_{i_idx}',st.session_state.lang], step=10)
467
  params_dict['c_fuel_i'].loc['OCGT'] = params_dict['c_fuel_i'].loc['CCGT']
 
 
 
 
 
468
 
469
+
470
+ # # Set options and default values based on the selected language
471
+ # if st.session_state.lang == 'DE':
472
+ # # German options for the user interface
473
+ # options = [
474
+ # df.loc[f'tech_{tech.lower()}', 'DE'] for tech in sets_dict['i'] if f'tech_{tech.lower()}' in df.index
475
+ # ]
476
+ # default = [
477
+ # df.loc[f'tech_{tech.lower()}', 'DE'] for tech in ['Lignite', 'CCGT', 'OCGT', 'Fossil Hard coal', 'Fossil Oil', 'PV', 'WindOff', 'WindOn', 'H2', 'Pumped Hydro Storage', 'Battery storages', 'Electrolyzer']
478
+ # if f'tech_{tech.lower()}' in df.index
479
+ # ]
480
+ # else:
481
+ # # English options for the user interface
482
+ # options = sets_dict['i']
483
+ # default = ['Lignite', 'CCGT', 'OCGT', 'Fossil Hard coal', 'Fossil Oil', 'PV', 'WindOff', 'WindOn', 'H2', 'Pumped Hydro Storage', 'Battery storages', 'Electrolyzer']
484
  # Set options and default values based on the selected language
485
+ # Set core technology list (will later depend on level)
 
 
 
 
 
 
 
 
 
 
 
 
486
 
487
+ if st.session_state.level == 'undergraduate':
488
+ # Exclude specific technologies for undergraduates
489
+ excluded_techs = {'Lignite', 'Pumped Hydro Storage', 'Electrolyzer'}
490
+ tech_list = [tech for tech in sets_dict['i'] if tech not in excluded_techs]
491
+ # tech_list = sets_dict['i'] # same for now
492
+ else:
493
+ tech_list = sets_dict['i'] # original set
494
+
495
+ # Localize display labels based on selected language
496
+ lang = st.session_state.lang
497
+ options = [
498
+ df.loc[f'tech_{tech.lower()}', lang] if f'tech_{tech.lower()}' in df.index else tech
499
+ for tech in tech_list
500
+ ]
501
+
502
+ # Define default technologies (internal names) — same across all users
503
+ default_techs = ['Lignite', 'CCGT', 'OCGT', 'Fossil Hard coal', 'Fossil Oil', 'PV', 'WindOff', 'WindOn', 'H2', 'Pumped Hydro Storage', 'Battery storages', 'Electrolyzer']
504
+
505
+ # Translate default selections for UI (still uses internal list for logic)
506
+ default = [
507
+ df.loc[f'tech_{tech.lower()}', lang] if f'tech_{tech.lower()}' in df.index else tech
508
+ for tech in default_techs if tech in tech_list
509
+ ]
510
+
511
  # Multiselect for technology options in the user interface
512
  selected_technologies = col3form.multiselect(
513
  label=df.loc['model_label_tech', st.session_state.lang],
514
  options=options,
515
  default=[tech for tech in default if tech in options]
516
  )
517
+
518
  # If language is German, map selected German names back to their English equivalents
519
  if st.session_state.lang == 'DE':
520
  technologies_invest = [tech_mapping_de_to_en[tech] for tech in selected_technologies]
 
662
  # Generate and display figures
663
  st.markdown("---")
664
 
665
+
666
+ if st.session_state.level == "undergraduate":
667
+ i_with_capacity = m.solution['K'].where((m.solution['K'] > 0) & (m.solution['i'] != 'Electrolyzer')).dropna(dim='i').get_index('i')
668
+ df_curtailment = plot_curtailment(m, iRes, color_dict, colb1, df, show = False)
669
+ # df_production = plot_production(m, i_with_capacity, dt, color_dict, colb1, df, show = False)
670
+ df_total_costs = plot_total_costs(m, colb1, df)
671
+ df_CO2_price = plot_co2_price(m, colb2, df)
672
+
673
+ df_new_capacities = plot_new_capacities(m, color_dict, colb1, df)
674
+ df_residual_load_duration = plot_residual_load_duration(m, dt, colb2, df, D_t, i_with_capacity, iRes, color_dict, df_curtailment, iConv)
675
+
676
+ df_fullload = calculate_and_plot_fullload_hours(m, dt, color_dict, colb1)
677
+ # df_production = plot_production(m, i_with_capacity, dt, color_dict, colb1, df)s
678
+ df_price = plot_electricity_prices(m, dt, colb2, df, df_residual_load_duration)
679
+
680
+ df_production = plot_production(m, i_with_capacity, dt, color_dict, colb1, df)
681
+
682
+ df_emissions = calculate_and_plot_emissions(m, eff_i, co2_factor_i, dt=1, color_dict=color_dict, col=colb1)
683
+ df_emissions_cumulative = calculate_and_plot_cumulative_emissions(m, eff_i, co2_factor_i,dt, color_dict, colb2, df)
684
+ # df_residual_load_duration = plot_residual_load_duration(m, dt, colb2, df, D_t, i_with_capacity, iRes, color_dict, df_curtailment, iConv)
685
+
686
+
687
+ df_curtailment = plot_curtailment(m, iRes, color_dict, colb1, df, show= True)
688
+ df_charging = plot_storage_charging(m, iSto, color_dict, colb2, df)
689
+ # df_filtered = calculate_and_plot_investment_costs(m,df_new_capacities, c_inv_i, color_dict, colb1, df)
690
+ # df_contr_marg_sum = calculate_and_plot_contribution_margin(m, i, iRes, dt, color_dict, colb2, df)
691
+ else:
692
+ df_total_costs = plot_total_costs(m, colb1, df)
693
+ df_CO2_price = plot_co2_price(m, colb2, df)
694
+ df_new_capacities = plot_new_capacities(m, color_dict, colb1, df)
695
 
696
+ # Only plot production for technologies with capacity
697
+ i_with_capacity = m.solution['K'].where((m.solution['K'] > 0) & (m.solution['i'] != 'Electrolyzer')).dropna(dim='i').get_index('i')
698
+ df_production = plot_production(m, i_with_capacity, dt, color_dict, colb2, df)
699
+ # df_price = plot_electricity_prices(m, dt, colb2, df)
700
+ df_curtailment = plot_curtailment(m, iRes, color_dict, colb1, df)
701
+ df_residual_load_duration = plot_residual_load_duration(m, dt, colb1, df, D_t, i_with_capacity, iRes, color_dict, df_curtailment, iConv)
702
+ df_price = plot_electricity_prices(m, dt, colb2, df, df_residual_load_duration)
703
+
704
+ df_contr_marg = plot_contribution_margin(m, dt, i_with_capacity, color_dict, colb1, df)
705
+ # df_curtailment = plot_curtailment(m, iRes, color_dict, colb1, df)
706
+ df_charging = plot_storage_charging(m, iSto, color_dict, colb2, df)
707
+ df_h2_prod = plot_hydrogen_production(m, iPtG, color_dict, colb1, df)
708
 
709
  # df_stackplot = plot_stackplot(m)
710
 
 
717
  disaggregate_df(df_total_costs, t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_total_costs', st.session_state.lang], index=False)
718
  disaggregate_df(df_CO2_price, t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_co2_price', st.session_state.lang], index=False)
719
  disaggregate_df(df_price, t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_prices', st.session_state.lang], index=False)
720
+ # disaggregate_df(df_contr_marg, t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_contribution_margin', st.session_state.lang], index=False)
721
  disaggregate_df(df_new_capacities, t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_capacities', st.session_state.lang], index=False)
722
  disaggregate_df(df_production, t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_production', st.session_state.lang], index=False)
723
  disaggregate_df(df_charging, t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_charging', st.session_state.lang], index=False)
724
  disaggregate_df(D_t.to_dataframe().reset_index(), t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_demand', st.session_state.lang], index=False)
725
  disaggregate_df(df_curtailment, t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_curtailment', st.session_state.lang], index=False)
726
+ # disaggregate_df(df_h2_prod, t, t_original, dt).to_excel(writer, sheet_name=df.loc['sheet_name_h2_production', st.session_state.lang], index=False)
727
 
728
  with col1:
729
  st.title(df.loc['model_title2', st.session_state.lang])
 
820
  with col:
821
  st.plotly_chart(fig_bar)
822
 
823
+ if st.session_state.level == "graduate":
824
+ # Pie chart for new capacities (only show technologies with K > 0 in pie chart)
825
+ df_new_capacities_filtered = df_new_capacities[df_new_capacities["K"] > 0]
826
+ fig_pie = px.pie(df_new_capacities_filtered, names='i', values='K',
827
+ title=df.loc['plot_label_new_capacities_pie', st.session_state.lang],
828
+ color='i_en', color_discrete_map=color_dict)
829
 
830
+ # Remove English labels (i_en) from the pie chart legend
831
+ fig_pie.update_layout(legend_title_text=df.loc['label_technology', st.session_state.lang])
832
+ 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))
833
 
834
+ with col:
835
+ st.plotly_chart(fig_pie)
836
 
837
  return df_new_capacities
838
 
839
 
840
+ def plot_production(m, i_with_capacity, dt, color_dict, col, df, show=True):
841
  """
842
  Plots the energy production for technologies with capacity as an area chart.
843
  Supports both German and English labels for technologies while ensuring color consistency.
 
890
  st.plotly_chart(fig)
891
 
892
  # Pie chart for total production
893
+ if st.session_state.level == "graduate":
894
+ df_production_sum = (df_production.groupby(['i', 'i_en'])['y'].sum() * dt / 1000).round(0).reset_index()
895
 
896
+ # If the language is set to German, display German labels, otherwise use English
897
+ pie_column = 'i' if st.session_state.lang == 'DE' else 'i_en'
898
 
899
+ # Pie chart for total production
900
+ fig_pie = px.pie(df_production_sum, names=pie_column, values='y',
901
+ title=df.loc['plot_label_total_production_pie', st.session_state.lang],
902
+ color='i_en', # Ensure the coloring stays consistent using the 'i_en' column
903
+ color_discrete_map=color_dict)
904
 
905
+ # Update legend title to reflect the correct language
906
+ fig_pie.update_layout(legend_title_text=df.loc['label_technology', st.session_state.lang])
907
+
908
+ with col:
909
+ st.plotly_chart(fig_pie)
910
 
911
  return df_production
912
 
 
923
  df_price['t'] = df_price['t'].str.strip("'")
924
  df_price['t'] = pd.to_datetime(df_price['t'], format='%Y-%m-%d %H:%M %z')
925
 
926
+ # # Line plot for electricity prices
927
+ # fig_price = px.line(df_price, y='dual', x='t',
928
+ # title=df.loc['plot_label_electricity_prices', st.session_state.lang],
929
+ # # range_y=[0, 250],
930
+ # labels={'dual': '', 't': ''}
931
+ # )
932
+ # with col:
933
+ # st.plotly_chart(fig_price)
934
 
935
  # Create the price duration curve
936
  df_sorted_price = df_price["dual"].repeat(dt).sort_values(ascending=False).reset_index(drop=True) / int(dt)
 
947
  x=df_sorted_price.index,
948
  y=df_sorted_price,
949
  mode='lines',
950
+ name=df.loc['plot_label_price_duration_curve', st.session_state.lang], # Price duration label
951
+ line=dict(color='blue', width=2) # Blue line for primary y-axis
952
  ))
953
 
954
  # Add secondary y-axis trace (Residual load)
 
956
  x=df_axis2.index,
957
  y=df_axis2,
958
  mode='lines',
959
+ name=df.loc['plot_label_residual_load', st.session_state.lang], # Residual load label
960
+ line=dict(color='red', width=2), # Red line for secondary y-axis
961
+ yaxis='y2' # Link this trace to the secondary y-axis
962
  ))
963
 
964
+ # Layout mit separaten Achsen
965
  fig_duration.update_layout(
966
  title=df.loc['plot_label_price_duration_curve', st.session_state.lang],
967
  xaxis=dict(
968
+ title=df.loc['label_hours', st.session_state.lang] # Common x-axis
969
  ),
970
  yaxis=dict(
971
+ title=df.loc['plot_label_price_duration_curve', st.session_state.lang], # Title for primary y-axis
972
+ range=[-(100/(ax2_max/(ax2_max-ax2_min))-100), 100], # Primary y-axis range
973
+ titlefont=dict(color='blue'), # Blue color for primary axis title
974
+ tickfont=dict(color='blue') # Blue ticks for primary axis
 
 
975
  ),
976
  yaxis2=dict(
977
+ title=df.loc['plot_label_residual_load', st.session_state.lang], # Title for secondary y-axis
978
+ range=[ax2_min, ax2_max], # Secondary y-axis range
979
+ titlefont=dict(color='red'), # Red color for secondary axis title
980
+ tickfont=dict(color='red'), # Red ticks for secondary axis
981
+ overlaying='y', # Overlay secondary axis on primary
982
+ side='right' # Place secondary y-axis on the right side
 
 
983
  ),
984
+ legend=dict(
985
+ x=1, # Positioniert die Legende am rechten Rand
986
+ y=1, # Positioniert die Legende am oberen Rand
987
+ xanchor='right', # Verankert die Legende am rechten Rand
988
+ yanchor='top', # Verankert die Legende am oberen Rand
989
+ bgcolor='rgba(255, 255, 255, 0.5)', # Weißer Hintergrund mit Transparenz
990
+ bordercolor='black',
991
+ borderwidth=1
992
+ )
993
  )
994
 
995
+
996
  with col:
997
  st.plotly_chart(fig_duration)
998
+
999
+ # Set y-axis range conditionally
1000
+ range_y = [0, 250] if st.session_state.level == "graduate" else None
1001
+ # Line plot for electricity prices
1002
+ fig_price = px.line(df_price, y='dual', x='t',
1003
+ title=df.loc['plot_label_electricity_prices', st.session_state.lang],
1004
+ labels={'dual': '', 't': ''}
1005
+ )
1006
+
1007
+ # Apply axis range if needed
1008
+ if range_y is not None:
1009
+ fig_price.update_yaxes(range=range_y)
1010
+
1011
+ with col:
1012
+ st.plotly_chart(fig_price)
1013
 
1014
  return df_price
1015
 
 
1186
 
1187
 
1188
 
1189
+ def plot_curtailment(m, iRes, color_dict, col, df, show=True):
1190
  """
1191
  Plots the curtailment of renewable energy.
1192
  Supports both German and English labels for titles and axes while ensuring color consistency.
 
1231
  fig.for_each_trace(lambda t: t.update(name=df_curtailment.loc[df_curtailment['i_en'] == t.name, 'i'].values[0]))
1232
 
1233
  # Display the plot
1234
+ if show:
1235
+ with col:
1236
+ st.plotly_chart(fig)
1237
 
1238
  return df_curtailment
1239
 
 
1346
 
1347
 
1348
 
1349
+ def calculate_and_plot_fullload_hours(m, dt, color_dict, col):
1350
+ """
1351
+ Calculates full load hours for units with positive capacity and plots the result.
1352
+
1353
+ Parameters:
1354
+ - m: Optimization model object containing solution['K'] (capacity) and solution['y'] (production).
1355
+ - dt: Time resolution of the production data (e.g., in hours).
1356
+ - color_dict: Dictionary mapping unit identifiers (i) to colors for plotting.
1357
+ - col: Streamlit column or panel where the plot should be rendered (e.g., colb1).
1358
+ """
1359
+ # Filter indices with positive capacity
1360
+ i_with_capacity = m.solution['K'].where(m.solution['K'] > 0).dropna(dim='i').get_index('i')
1361
+
1362
+ # Extract production and capacity data
1363
+ df_production = m.solution['y'].sel(i=i_with_capacity).to_dataframe().reset_index()
1364
+ df_capacity = m.solution['K'].sel(i=i_with_capacity).to_dataframe().reset_index()
1365
+
1366
+ # Sum production and calculate full load hours
1367
+ df_production_sum = (df_production.groupby('i')['y'].sum() * dt).round(0).reset_index()
1368
+ df_production_sum = df_production_sum.set_index('i').loc[i_with_capacity].reset_index()
1369
+
1370
+ df_fullload = df_production_sum['y'] / df_capacity['K']
1371
+ df_fullload = df_fullload.to_frame(name='fullload')
1372
+ df_fullload['i'] = df_production_sum['i']
1373
+ df_fullload = df_fullload[['i', 'fullload']]
1374
+
1375
+ # Plot
1376
+ fig = px.bar(
1377
+ df_fullload,
1378
+ y='i',
1379
+ x='fullload',
1380
+ orientation='h',
1381
+ title='Volllaststunden [h]',
1382
+ color='i',
1383
+ color_discrete_map=color_dict
1384
+ )
1385
+ col.plotly_chart(fig)
1386
+
1387
+ return df_fullload
1388
+
1389
+
1390
+
1391
+ def calculate_and_plot_emissions(m, eff_i, co2_factor_i, dt, color_dict, col):
1392
+ """
1393
+ Calculates emissions from model output and plots an area chart of emissions over time.
1394
+
1395
+ Parameters:
1396
+ - m: Optimization model with solution['y'] (production) and solution['K'] (capacity).
1397
+ - eff_i: xarray DataArray of efficiency values indexed by 'i'.
1398
+ - co2_factor_i: xarray DataArray of CO₂ emission factors indexed by 'i'.
1399
+ - dt: Time resolution of the data (e.g., in hours).
1400
+ - color_dict: Dictionary mapping unit identifiers (i) to colors.
1401
+ - col: Streamlit column or panel for rendering the plot.
1402
+
1403
+ Returns:
1404
+ - df_production_emissions_unpivot: DataFrame with emissions per unit and time.
1405
+ """
1406
+
1407
+ # Filter technologies with positive capacity
1408
+ i_with_capacity = m.solution['K'].where(m.solution['K'] > 0).dropna(dim='i').get_index('i')
1409
+
1410
+ # Get production data for those technologies
1411
+ df_production = m.solution['y'].sel(i=i_with_capacity).to_dataframe().reset_index()
1412
+
1413
+ # Pivot production data
1414
+ df_production_pivot = df_production.pivot(index='t', columns='i', values='y')
1415
+ available_columns = df_production_pivot.columns.intersection(i_with_capacity)
1416
+ df_production_pivot = df_production_pivot[available_columns]
1417
+
1418
+ # Get efficiency and CO₂ factors only for available technologies
1419
+ df_efficiency = eff_i.sel(i=available_columns)
1420
+ co2_factor_i_with_capacity = co2_factor_i.sel(i=available_columns)
1421
+ color_dict_with_capacity = {i: color_dict[i] for i in available_columns}
1422
+ desired_order = available_columns.tolist()
1423
+
1424
+ # Compute emissions
1425
+ df_production_emissions = df_production_pivot / df_efficiency * co2_factor_i_with_capacity * dt
1426
+
1427
+ # Unpivot for plotting
1428
+ df_production_emissions_unpivot = df_production_emissions.reset_index().melt(
1429
+ id_vars='t', var_name='i', value_name='y'
1430
+ )
1431
+ df_production_emissions_unpivot['i'] = pd.Categorical(
1432
+ df_production_emissions_unpivot['i'],
1433
+ categories=desired_order,
1434
+ ordered=True
1435
+ )
1436
+ df_production_emissions_unpivot = df_production_emissions_unpivot.sort_values(by=['t', 'i'])
1437
+
1438
+ # Plot
1439
+ fig = px.area(
1440
+ df_production_emissions_unpivot,
1441
+ y='y',
1442
+ x='t',
1443
+ title='CO₂-Emissionen [t]',
1444
+ color='i',
1445
+ color_discrete_map=color_dict_with_capacity
1446
+ )
1447
+ fig.update_traces(line=dict(width=0))
1448
+ fig.for_each_trace(lambda trace: trace.update(fillcolor=trace.line.color))
1449
+
1450
+ # Display in Streamlit
1451
+ col.plotly_chart(fig)
1452
+
1453
+ return df_production_emissions_unpivot
1454
+
1455
+
1456
+ def calculate_and_plot_cumulative_emissions(m, eff_i, co2_factor_i, dt, color_dict, col, df):
1457
+ """
1458
+ Calculates and plots cumulative CO₂ emissions sorted by time for units with capacity > 0,
1459
+ with support for multilingual labels and consistent coloring.
1460
+
1461
+ Parameters:
1462
+ - m: Optimization model with 'K' and 'y'
1463
+ - eff_i: DataArray of efficiencies indexed by 'i'
1464
+ - co2_factor_i: DataArray of CO₂ factors indexed by 'i'
1465
+ - dt: Time resolution
1466
+ - color_dict: Dict mapping English tech names to colors
1467
+ - col: Streamlit column for plot
1468
+ - df: DataFrame for label translations (e.g., from Excel)
1469
+
1470
+ Returns:
1471
+ - df_cumulative_emissions_unpivot: Cumulative emissions DataFrame (melted format)
1472
+ """
1473
+
1474
+ # Step 1: Filter technologies with capacity > 0
1475
+ i_with_capacity = m.solution['K'].where(m.solution['K'] > 0).dropna(dim='i').get_index('i')
1476
+
1477
+ # Step 2: Get production data
1478
+ df_production = m.solution['y'].sel(i=i_with_capacity).to_dataframe().reset_index()
1479
+ df_production['i_en'] = df_production['i']
1480
+
1481
+ # Translate if needed
1482
+ if st.session_state.lang == 'DE':
1483
+ tech_mapping_en_to_de = {
1484
+ df.loc[f'tech_{tech.lower()}', 'EN']: df.loc[f'tech_{tech.lower()}', 'DE']
1485
+ for tech in df_production['i_en'].unique()
1486
+ if f'tech_{tech.lower()}' in df.index
1487
+ }
1488
+ df_production['i'] = df_production['i_en'].replace(tech_mapping_en_to_de)
1489
+
1490
+ # Step 3: Pivot and align
1491
+ df_production_pivot = df_production.pivot(index='t', columns='i_en', values='y')
1492
+ available_columns = df_production_pivot.columns.intersection(i_with_capacity)
1493
+ df_production_pivot = df_production_pivot[available_columns]
1494
+
1495
+ # Step 4: Emissions calculation
1496
+ df_efficiency = eff_i.sel(i=available_columns)
1497
+ co2_factor_i_with_capacity = co2_factor_i.sel(i=available_columns)
1498
+ df_emissions = df_production_pivot / df_efficiency * co2_factor_i_with_capacity * dt
1499
+
1500
+ # Step 5: Add total and sort
1501
+ df_emissions['total'] = df_emissions.sum(axis=1)
1502
+ df_emissions_sorted = df_emissions.sort_values(by='total', ascending=True)
1503
+
1504
+ # Step 6: Cumulative sum and cleanup
1505
+ df_cumsum = df_emissions_sorted.drop(columns='total').cumsum(axis=0)
1506
+ df_cumsum = df_cumsum.loc[:, (df_cumsum != 0).any(axis=0)]
1507
+
1508
+ # Step 7: Unpivot
1509
+ df_cumulative_emissions_unpivot = df_cumsum.reset_index().melt(
1510
+ id_vars='t', var_name='i_en', value_name='y'
1511
+ )
1512
+
1513
+ # Step 8: Translate display names (if needed)
1514
+ if st.session_state.lang == 'DE':
1515
+ tech_mapping_en_to_de = {
1516
+ df.loc[f'tech_{tech.lower()}', 'EN']: df.loc[f'tech_{tech.lower()}', 'DE']
1517
+ for tech in df_cumulative_emissions_unpivot['i_en'].unique()
1518
+ if f'tech_{tech.lower()}' in df.index
1519
+ }
1520
+ df_cumulative_emissions_unpivot['i'] = df_cumulative_emissions_unpivot['i_en'].replace(tech_mapping_en_to_de)
1521
+ else:
1522
+ df_cumulative_emissions_unpivot['i'] = df_cumulative_emissions_unpivot['i_en']
1523
+
1524
+ # Set 0 → NaN for plotting
1525
+ df_cumulative_emissions_unpivot['y'] = df_cumulative_emissions_unpivot['y'].replace(0, np.nan)
1526
+
1527
+ # Step 9: Plot
1528
+ color_dict_with_capacity = {i: color_dict[i] for i in df_cumulative_emissions_unpivot['i_en'].unique() if i in color_dict}
1529
+
1530
+ fig = px.area(
1531
+ df_cumulative_emissions_unpivot,
1532
+ y='y',
1533
+ x='t',
1534
+ title=df.loc['plot_label_cumulative_emissions', st.session_state.lang],
1535
+ color='i_en',
1536
+ color_discrete_map=color_dict_with_capacity,
1537
+ labels={'i': '', 'y': ''}
1538
+ )
1539
+
1540
+ # Rename legend entries to display translated names
1541
+ legend_map = dict(zip(
1542
+ df_cumulative_emissions_unpivot['i_en'],
1543
+ df_cumulative_emissions_unpivot['i']
1544
+ ))
1545
+
1546
+ fig.for_each_trace(
1547
+ lambda t: t.update(name=legend_map.get(t.name, t.name),
1548
+ legendgroup=legend_map.get(t.name, t.name),
1549
+ hovertemplate=t.hovertemplate.replace(t.name, legend_map.get(t.name, t.name)))
1550
+ )
1551
+ fig.update_traces(line=dict(width=0))
1552
+ fig.for_each_trace(lambda trace: trace.update(fillcolor=trace.line.color))
1553
+ fig.update_layout(legend_title_text=None)
1554
+
1555
+ col.plotly_chart(fig)
1556
+
1557
+ return df_cumulative_emissions_unpivot
1558
+
1559
+
1560
+
1561
+ def calculate_and_plot_investment_costs(m, df_new_capacities, c_inv_i, color_dict, col, df):
1562
+ """
1563
+ Calculates and plots investment costs per technology, with multilingual support.
1564
+ - Uses 'i_en' for color consistency.
1565
+ - Displays 'i' in the selected language.
1566
+ - Filters out 'Battery storages'.
1567
+
1568
+ Parameters:
1569
+ - df_new_capacities: DataFrame with columns ['i', 'K'] for new capacities.
1570
+ - c_inv_i: xarray DataArray or Series with investment costs indexed by 'i'.
1571
+ - color_dict: Dictionary mapping English technology identifiers (i_en) to colors.
1572
+ - col: Streamlit column to display the plot.
1573
+ - df: Translation/label lookup DataFrame with keys like 'tech_pv', 'tech_windon' etc.
1574
+
1575
+ Returns:
1576
+ - df_invest_costs: DataFrame with investment costs per technology.
1577
+ """
1578
+
1579
+ # Ensure 'i_en' is available for color mapping
1580
+ df_new_capacities = df_new_capacities.copy()
1581
+ df_new_capacities['i_en'] = df_new_capacities['i']
1582
+ i_with_capacity = m.solution['K'].where(m.solution['K'] > 0).dropna(dim='i').get_index('i')
1583
+ available_columns = df_new_capacities.columns.intersection(i_with_capacity)
1584
+
1585
+ # Translate if language is German
1586
+ if st.session_state.lang == 'DE':
1587
+ tech_mapping_en_to_de = {
1588
+ df.loc[f'tech_{tech.lower()}', 'EN']: df.loc[f'tech_{tech.lower()}', 'DE']
1589
+ for tech in df_new_capacities['i_en'] if f'tech_{tech.lower()}' in df.index
1590
+ }
1591
+ df_new_capacities['i'] = df_new_capacities['i_en'].replace(tech_mapping_en_to_de)
1592
+
1593
+ # Filter out 'Battery storages'
1594
+ df_filtered = df_new_capacities[df_new_capacities['i_en'] != 'Battery storages'].copy()
1595
+
1596
+ # # Calculate investment costs
1597
+ df_filtered['Investment'] = df_filtered['K'] * c_inv_i
1598
+ color_dict_with_capacity = {i: color_dict[i] for i in available_columns}
1599
+ # Plot
1600
+ fig = px.bar(
1601
+ df_filtered,
1602
+ y='i',
1603
+ x='Investment',
1604
+ orientation='h',
1605
+ title=df.loc['plot_label_investment_costs', st.session_state.lang],
1606
+ color='i_en',
1607
+ color_discrete_map=color_dict_with_capacity,
1608
+ labels={'i': '', 'Investment': ''}
1609
+ )
1610
+ fig.update_layout(showlegend=False)
1611
+ col.plotly_chart(fig)
1612
+
1613
+ return df_filtered
1614
+
1615
+
1616
+ def calculate_and_plot_contribution_margin(m, i, iRes, dt, color_dict, col, df):
1617
+ """
1618
+ Calculates and plots the contribution margin (Deckungsbeitrag) per technology.
1619
+ Mirrors original working logic, with optional language and coloring.
1620
+
1621
+ Parameters:
1622
+ - m: Optimization model with 'y', 'max_cap', 'load'
1623
+ - i: Full list of technologies (for ordering and reindexing)
1624
+ - iRes: Subset of residual techs (e.g. PV, WindOn)
1625
+ - dt: Time step length in hours
1626
+ - color_dict: Dictionary of colors (keyed by technology names)
1627
+ - col: Streamlit column to display the plot
1628
+ - df: Translation table with tech labels and plot titles
1629
+
1630
+ Returns:
1631
+ - df_contr_marg_sum: DataFrame with contribution margin results
1632
+ """
1633
+
1634
+ # Production data for all technologies
1635
+ df_production_all = m.solution['y'].sel(i=i).to_dataframe().reset_index()
1636
+
1637
+ # Dual values from max_cap constraint
1638
+ df_contr_marg = m.constraints['max_cap'].dual.to_dataframe().reset_index()
1639
+
1640
+ # Merge and multiply
1641
+ df_merged = pd.merge(df_production_all, df_contr_marg, on=['t', 'i'])
1642
+ df_merged['y_new'] = df_merged['y'] * df_merged['dual']
1643
+ df_merged = df_merged[['t', 'i', 'y_new']]
1644
+ df_contr_marg_sum = df_merged.groupby('i')['y_new'].sum().reset_index()
1645
+
1646
+ # Handle residual technologies with load constraint duals
1647
+ df_production_res = m.solution['y'].sel(i=iRes).to_dataframe().reset_index()
1648
+ df_price_res = m.constraints['load'].dual.to_dataframe().reset_index()
1649
+ df_merged_res = pd.merge(df_production_res, df_price_res, on='t')
1650
+ df_merged_res['multiplied_value'] = df_merged_res['y'] * df_merged_res['dual']
1651
+ df_merged_res = df_merged_res[['t', 'i', 'multiplied_value']]
1652
+ df_contr_marg_res = df_merged_res.groupby('i')['multiplied_value'].sum().reset_index()
1653
+ df_contr_marg_res['multiplied_value'] = df_contr_marg_res['multiplied_value'] * -dt
1654
+
1655
+ # Combine both results
1656
+ df_contr_marg_sum = pd.merge(df_contr_marg_sum, df_contr_marg_res, on='i', how='left')
1657
+ df_contr_marg_sum['y_new'] = df_contr_marg_sum['multiplied_value'].combine_first(df_contr_marg_sum['y_new'])
1658
+ df_contr_marg_sum = df_contr_marg_sum.drop(columns=['multiplied_value'])
1659
+ df_contr_marg_sum['y'] = df_contr_marg_sum['y_new'] * -1
1660
+
1661
+ # Translate labels if needed
1662
+ if st.session_state.lang == 'DE':
1663
+ tech_mapping = {
1664
+ df.loc[f'tech_{tech.lower()}', 'EN']: df.loc[f'tech_{tech.lower()}', 'DE']
1665
+ for tech in i if f'tech_{tech.lower()}' in df.index
1666
+ }
1667
+ df_contr_marg_sum['i_en'] = df_contr_marg_sum['i']
1668
+ df_contr_marg_sum['i'] = df_contr_marg_sum['i_en'].replace(tech_mapping)
1669
+ else:
1670
+ df_contr_marg_sum['i_en'] = df_contr_marg_sum['i']
1671
+
1672
+ # Reorder by original list
1673
+ # df_contr_marg_sum = df_contr_marg_sum.set_index('i_en').loc[i].reset_index(drop=False)
1674
+ if 'i' in df_contr_marg_sum.columns:
1675
+ df_contr_marg_sum = df_contr_marg_sum.drop(columns='i')
1676
+
1677
+ df_contr_marg_sum = df_contr_marg_sum.set_index('i_en').loc[i].reset_index()
1678
+
1679
+ # Plot
1680
+ title = df.loc['plot_label_contribution_margin', st.session_state.lang]
1681
+ fig = px.bar(
1682
+ df_contr_marg_sum,
1683
+ y='i',
1684
+ x='y',
1685
+ orientation='h',
1686
+ title=title,
1687
+ color='i_en',
1688
+ color_discrete_map=color_dict,
1689
+ labels={'i': '', 'y': ''}
1690
+ )
1691
+
1692
+ # Localized legend
1693
+ legend_map = dict(zip(df_contr_marg_sum['i_en'], df_contr_marg_sum['i']))
1694
+ fig.for_each_trace(lambda t: t.update(
1695
+ name=legend_map.get(t.name, t.name),
1696
+ legendgroup=legend_map.get(t.name, t.name),
1697
+ hovertemplate=t.hovertemplate.replace(t.name, legend_map.get(t.name, t.name))
1698
+ ))
1699
+ fig.update_layout(legend_title_text=None)
1700
+
1701
+ col.plotly_chart(fig)
1702
+
1703
+ return df_contr_marg_sum
1704
+
1705
+
1706
+
1707
  def disaggregate_df(df, t, t_original, dt):
1708
  """
1709
  Disaggregates the DataFrame based on the original time steps.
language.csv CHANGED
@@ -2,6 +2,9 @@ Label;DE;EN
2
  menu_modell;Modell;Model
3
  menu_doku;Dokumentation;Documentation
4
  menu_impressum;�ber uns;About
 
 
 
5
  menu_text;Ein Browser-Tool, das f�r ein besseres Verst�ndnis der mathematischen Optimierung in der Energiewirtschaft bestimmt ist. Sei neugierig!;A tool designated for better understanding of mathematical optimization in the context of energy economics. Be eager!
6
  toast_text;�ffne das Men� links um zur Dokumentation und Sprachwahl zu gelangen!;Open the menu on the left side to access the documentation and to change the language!
7
  model_title1;Input aus Datei;Settings from file
@@ -26,8 +29,8 @@ model_label_tech;Technologien;Technologies for investment
26
  tech_nuclear;Atomkraft;Nuclear
27
  tech_biomass;Biomasse;Biomass
28
  tech_lignite;Braunkohle;Lignite
29
- tech_CCGT;Gas- und Dampfkraftwerk;Combined Cycle Gas Turbine
30
- tech_OCGT;Gasturbine;Gasturbine
31
  tech_fossil hard coal;Steinkohle;Fossil Hard coal
32
  tech_fossil oil;�l;Fossil Oil
33
  tech_ror;Laufwasser;RoR
@@ -50,6 +53,11 @@ plot_label_total_production_pie;Gesamtproduktion [GWh] als Kreisdiagramm;Total P
50
  plot_label_electricity_prices;Strompreise [EUR/MWh];Electricity prices [EUR/MWh]
51
  plot_label_price_duration_curve;Preisdauerlinie [EUR/MWh];Price duration curve [EUR/MWh]
52
  plot_label_load_duration_curve;Lastdauerlinie [MWh];Load duration curve [MWh]
 
 
 
 
 
53
  label_electricity_price;Strompreis;Electricity price
54
  label_time;Zeit;Time
55
  label_technology;Technologien;Technologies
 
2
  menu_modell;Modell;Model
3
  menu_doku;Dokumentation;Documentation
4
  menu_impressum;�ber uns;About
5
+ menu_level;Auswahl Schwierigkeitsgrad;Select level
6
+ menu_graduate;Master;Graduate
7
+ menu_untergraduate;Bachelor;Undergraduate
8
  menu_text;Ein Browser-Tool, das f�r ein besseres Verst�ndnis der mathematischen Optimierung in der Energiewirtschaft bestimmt ist. Sei neugierig!;A tool designated for better understanding of mathematical optimization in the context of energy economics. Be eager!
9
  toast_text;�ffne das Men� links um zur Dokumentation und Sprachwahl zu gelangen!;Open the menu on the left side to access the documentation and to change the language!
10
  model_title1;Input aus Datei;Settings from file
 
29
  tech_nuclear;Atomkraft;Nuclear
30
  tech_biomass;Biomasse;Biomass
31
  tech_lignite;Braunkohle;Lignite
32
+ tech_ccgt;GuD-Kraftwerk;CCGT
33
+ tech_ocgt;Gasturbine;OCGT
34
  tech_fossil hard coal;Steinkohle;Fossil Hard coal
35
  tech_fossil oil;�l;Fossil Oil
36
  tech_ror;Laufwasser;RoR
 
53
  plot_label_electricity_prices;Strompreise [EUR/MWh];Electricity prices [EUR/MWh]
54
  plot_label_price_duration_curve;Preisdauerlinie [EUR/MWh];Price duration curve [EUR/MWh]
55
  plot_label_load_duration_curve;Lastdauerlinie [MWh];Load duration curve [MWh]
56
+ plot_label_fullload-hours;Volllaststunden [h/a];Full load hours [h/a]
57
+ plot_label_emissions;Emittierte CO2-Emissionen im Jahresverlauf [t];CO2-Emissions [t]
58
+ plot_label_cumulative_emissions;Kumulierte CO2-Emissionen [t];Cumulative CO2-Emissions [t]
59
+ plot_label_investment_costs;Investitionskosten [EUR];Investment costs [EUR]
60
+ plot_label_contribution_margin_undergrad;Deckungsbeitrag [EUR];Contribution Margin [EUR]
61
  label_electricity_price;Strompreis;Electricity price
62
  label_time;Zeit;Time
63
  label_technology;Technologien;Technologies