ConniKLu commited on
Commit
0b6e1a9
·
verified ·
1 Parent(s): a688fb8

Upload 2 files

Browse files

undergraduate level: added bar plots for investment costs and contribution margin

Files changed (2) hide show
  1. app.py +40 -80
  2. language.csv +2 -2
app.py CHANGED
@@ -500,7 +500,8 @@ def page_model(): #, write_pickle_from_standard_excel, color_dict):
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 = [
@@ -686,8 +687,8 @@ def page_model(): #, write_pickle_from_standard_excel, color_dict):
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)
@@ -811,7 +812,8 @@ def plot_new_capacities(m, color_dict, col, df):
811
  title=df.loc['plot_label_new_capacities', st.session_state.lang],
812
  color='i_en', # Use the English names for consistent coloring
813
  color_discrete_map=color_dict,
814
- labels={'K': '', 'i': ''} # Delete double labeling
 
815
  )
816
 
817
  # Hide the legend completely since the labels are already next to the bars
@@ -954,26 +956,6 @@ def plot_electricity_prices(m, dt, col, df, df_residual_load_duration):
954
  yaxis='y2' # Link this trace to the secondary y-axis
955
  ))
956
 
957
- # Layout mit separaten Achsen
958
- # fig_duration.update_layout(
959
- # title=df.loc['plot_label_price_duration_curve', st.session_state.lang],
960
- # xaxis=dict(
961
- # title=df.loc['label_hours', st.session_state.lang] # Common x-axis
962
- # ),
963
- # yaxis=dict(
964
- # title=df.loc['plot_label_price_duration_curve', st.session_state.lang], # Title for primary y-axis
965
- # range=[-(100/(ax2_max/(ax2_max-ax2_min))-100), 100], # Primary y-axis range
966
- # titlefont=dict(color='blue'), # Blue color for primary axis title
967
- # tickfont=dict(color='blue') # Blue ticks for primary axis
968
- # ),
969
- # yaxis2=dict(
970
- # title=df.loc['plot_label_residual_load', st.session_state.lang], # Title for secondary y-axis
971
- # range=[ax2_min, ax2_max], # Secondary y-axis range
972
- # titlefont=dict(color='red'), # Red color for secondary axis title
973
- # tickfont=dict(color='red'), # Red ticks for secondary axis
974
- # overlaying='y', # Overlay secondary axis on primary
975
- # side='right' # Place secondary y-axis on the right side
976
- # ),
977
  fig_duration.update_layout(
978
  title=df.loc['plot_label_price_duration_curve', st.session_state.lang],
979
  xaxis=dict(
@@ -1580,24 +1562,15 @@ def calculate_and_plot_investment_costs(m, df_new_capacities, c_inv_i, color_dic
1580
  - Displays 'i' in the selected language.
1581
  - Filters out 'Battery storages'.
1582
 
1583
- Parameters:
1584
- - df_new_capacities: DataFrame with columns ['i', 'K'] for new capacities.
1585
- - c_inv_i: xarray DataArray or Series with investment costs indexed by 'i'.
1586
- - color_dict: Dictionary mapping English technology identifiers (i_en) to colors.
1587
- - col: Streamlit column to display the plot.
1588
- - df: Translation/label lookup DataFrame with keys like 'tech_pv', 'tech_windon' etc.
1589
-
1590
  Returns:
1591
  - df_invest_costs: DataFrame with investment costs per technology.
1592
  """
1593
 
1594
- # Ensure 'i_en' is available for color mapping
1595
- df_new_capacities = df_new_capacities.copy()
1596
  df_new_capacities['i_en'] = df_new_capacities['i']
1597
- i_with_capacity = m.solution['K'].where(m.solution['K'] > 0).dropna(dim='i').get_index('i')
1598
- available_columns = df_new_capacities.columns.intersection(i_with_capacity)
1599
 
1600
- # Translate if language is German
1601
  if st.session_state.lang == 'DE':
1602
  tech_mapping_en_to_de = {
1603
  df.loc[f'tech_{tech.lower()}', 'EN']: df.loc[f'tech_{tech.lower()}', 'DE']
@@ -1605,45 +1578,35 @@ def calculate_and_plot_investment_costs(m, df_new_capacities, c_inv_i, color_dic
1605
  }
1606
  df_new_capacities['i'] = df_new_capacities['i_en'].replace(tech_mapping_en_to_de)
1607
 
1608
- # Filter out 'Battery storages'
1609
- df_filtered = df_new_capacities[df_new_capacities['i_en'] != 'Battery storages'].copy()
 
1610
 
1611
- # # Calculate investment costs
1612
- df_filtered['Investment'] = df_filtered['K'] * c_inv_i
1613
- color_dict_with_capacity = {i: color_dict[i] for i in available_columns}
1614
- # Plot
1615
  fig = px.bar(
1616
- df_filtered,
1617
  y='i',
1618
  x='Investment',
1619
  orientation='h',
1620
  title=df.loc['plot_label_investment_costs', st.session_state.lang],
1621
  color='i_en',
1622
- color_discrete_map=color_dict_with_capacity,
1623
- labels={'i': '', 'Investment': ''}
 
1624
  )
1625
  fig.update_layout(showlegend=False)
1626
  col.plotly_chart(fig)
1627
 
1628
- return df_filtered
1629
 
1630
 
1631
  def calculate_and_plot_contribution_margin(m, i, iRes, dt, color_dict, col, df):
1632
  """
1633
  Calculates and plots the contribution margin (Deckungsbeitrag) per technology.
1634
- Mirrors original working logic, with optional language and coloring.
1635
-
1636
- Parameters:
1637
- - m: Optimization model with 'y', 'max_cap', 'load'
1638
- - i: Full list of technologies (for ordering and reindexing)
1639
- - iRes: Subset of residual techs (e.g. PV, WindOn)
1640
- - dt: Time step length in hours
1641
- - color_dict: Dictionary of colors (keyed by technology names)
1642
- - col: Streamlit column to display the plot
1643
- - df: Translation table with tech labels and plot titles
1644
-
1645
- Returns:
1646
- - df_contr_marg_sum: DataFrame with contribution margin results
1647
  """
1648
 
1649
  # Production data for all technologies
@@ -1658,7 +1621,7 @@ def calculate_and_plot_contribution_margin(m, i, iRes, dt, color_dict, col, df):
1658
  df_merged = df_merged[['t', 'i', 'y_new']]
1659
  df_contr_marg_sum = df_merged.groupby('i')['y_new'].sum().reset_index()
1660
 
1661
- # Handle residual technologies with load constraint duals
1662
  df_production_res = m.solution['y'].sel(i=iRes).to_dataframe().reset_index()
1663
  df_price_res = m.constraints['load'].dual.to_dataframe().reset_index()
1664
  df_merged_res = pd.merge(df_production_res, df_price_res, on='t')
@@ -1673,26 +1636,30 @@ def calculate_and_plot_contribution_margin(m, i, iRes, dt, color_dict, col, df):
1673
  df_contr_marg_sum = df_contr_marg_sum.drop(columns=['multiplied_value'])
1674
  df_contr_marg_sum['y'] = df_contr_marg_sum['y_new'] * -1
1675
 
1676
- # Translate labels if needed
 
1677
  if st.session_state.lang == 'DE':
1678
  tech_mapping = {
1679
  df.loc[f'tech_{tech.lower()}', 'EN']: df.loc[f'tech_{tech.lower()}', 'DE']
1680
  for tech in i if f'tech_{tech.lower()}' in df.index
1681
  }
1682
- df_contr_marg_sum['i_en'] = df_contr_marg_sum['i']
1683
  df_contr_marg_sum['i'] = df_contr_marg_sum['i_en'].replace(tech_mapping)
1684
  else:
1685
- df_contr_marg_sum['i_en'] = df_contr_marg_sum['i']
1686
 
1687
- # Reorder by original list
1688
- # df_contr_marg_sum = df_contr_marg_sum.set_index('i_en').loc[i].reset_index(drop=False)
1689
  if 'i' in df_contr_marg_sum.columns:
1690
  df_contr_marg_sum = df_contr_marg_sum.drop(columns='i')
1691
-
1692
  df_contr_marg_sum = df_contr_marg_sum.set_index('i_en').loc[i].reset_index()
 
1693
 
1694
- # Plot
1695
- title = df.loc['plot_label_contribution_margin', st.session_state.lang]
 
 
 
 
 
1696
  fig = px.bar(
1697
  df_contr_marg_sum,
1698
  y='i',
@@ -1700,25 +1667,18 @@ def calculate_and_plot_contribution_margin(m, i, iRes, dt, color_dict, col, df):
1700
  orientation='h',
1701
  title=title,
1702
  color='i_en',
1703
- color_discrete_map=color_dict,
1704
- labels={'i': '', 'y': ''}
 
1705
  )
1706
-
1707
- # Localized legend
1708
- legend_map = dict(zip(df_contr_marg_sum['i_en'], df_contr_marg_sum['i']))
1709
- fig.for_each_trace(lambda t: t.update(
1710
- name=legend_map.get(t.name, t.name),
1711
- legendgroup=legend_map.get(t.name, t.name),
1712
- hovertemplate=t.hovertemplate.replace(t.name, legend_map.get(t.name, t.name))
1713
- ))
1714
- fig.update_layout(legend_title_text=None)
1715
-
1716
  col.plotly_chart(fig)
1717
 
1718
  return df_contr_marg_sum
1719
 
1720
 
1721
 
 
1722
  def disaggregate_df(df, t, t_original, dt):
1723
  """
1724
  Disaggregates the DataFrame based on the original time steps.
 
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
+ default_techs = ['Lignite', 'CCGT', 'OCGT', 'Fossil Hard coal', 'Nuclear', 'PV', 'WindOff', 'WindOn', 'H2', 'Pumped Hydro Storage', 'Battery storages', 'Electrolyzer']
505
 
506
  # Translate default selections for UI (still uses internal list for logic)
507
  default = [
 
687
 
688
  df_curtailment = plot_curtailment(m, iRes, color_dict, colb1, df, show= True)
689
  df_charging = plot_storage_charging(m, iSto, color_dict, colb2, df)
690
+ df_investment = calculate_and_plot_investment_costs(m,df_new_capacities, c_inv_i, color_dict, colb1, df)
691
+ df_contr_marg_sum = calculate_and_plot_contribution_margin(m, i, iRes, dt, color_dict, colb2, df)
692
  else:
693
  df_total_costs = plot_total_costs(m, colb1, df)
694
  df_CO2_price = plot_co2_price(m, colb2, df)
 
812
  title=df.loc['plot_label_new_capacities', st.session_state.lang],
813
  color='i_en', # Use the English names for consistent coloring
814
  color_discrete_map=color_dict,
815
+ # labels={'K': '', 'i': ''} # Delete double labeling
816
+ labels={'i': ''}
817
  )
818
 
819
  # Hide the legend completely since the labels are already next to the bars
 
956
  yaxis='y2' # Link this trace to the secondary y-axis
957
  ))
958
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
959
  fig_duration.update_layout(
960
  title=df.loc['plot_label_price_duration_curve', st.session_state.lang],
961
  xaxis=dict(
 
1562
  - Displays 'i' in the selected language.
1563
  - Filters out 'Battery storages'.
1564
 
 
 
 
 
 
 
 
1565
  Returns:
1566
  - df_invest_costs: DataFrame with investment costs per technology.
1567
  """
1568
 
1569
+ # Step 1: Get new capacities from model
1570
+ df_new_capacities = m.solution['K'].round(0).to_dataframe().reset_index()
1571
  df_new_capacities['i_en'] = df_new_capacities['i']
 
 
1572
 
1573
+ # Step 2: Translate technology names (if language is German)
1574
  if st.session_state.lang == 'DE':
1575
  tech_mapping_en_to_de = {
1576
  df.loc[f'tech_{tech.lower()}', 'EN']: df.loc[f'tech_{tech.lower()}', 'DE']
 
1578
  }
1579
  df_new_capacities['i'] = df_new_capacities['i_en'].replace(tech_mapping_en_to_de)
1580
 
1581
+ # Step 3: Calculate investment cost in bn. EUR
1582
+ c_inv_series = c_inv_i.to_series() if hasattr(c_inv_i, 'to_series') else c_inv_i
1583
+ df_new_capacities['Investment'] = df_new_capacities['i_en'].map(c_inv_series) * df_new_capacities['K']* 1e-9 # Convert to billion EUR
1584
 
1585
+ df_investment = df_new_capacities.copy()
1586
+ local_color_dict = color_dict.copy()
1587
+ local_color_dict['Battery storages'] = 'rgba(0,0,0,0)' # Fully transparent
1588
+ # Step 5: Plot
1589
  fig = px.bar(
1590
+ df_investment,
1591
  y='i',
1592
  x='Investment',
1593
  orientation='h',
1594
  title=df.loc['plot_label_investment_costs', st.session_state.lang],
1595
  color='i_en',
1596
+ color_discrete_map=local_color_dict,
1597
+ labels={'i': ''},
1598
+ hover_data={'i_en': False,'i': True} # Show i and investment with 2 decimal places
1599
  )
1600
  fig.update_layout(showlegend=False)
1601
  col.plotly_chart(fig)
1602
 
1603
+ return df_investment
1604
 
1605
 
1606
  def calculate_and_plot_contribution_margin(m, i, iRes, dt, color_dict, col, df):
1607
  """
1608
  Calculates and plots the contribution margin (Deckungsbeitrag) per technology.
1609
+ Now mirrors visual logic of investment cost plot with transparent color for 'Battery storages'.
 
 
 
 
 
 
 
 
 
 
 
 
1610
  """
1611
 
1612
  # Production data for all technologies
 
1621
  df_merged = df_merged[['t', 'i', 'y_new']]
1622
  df_contr_marg_sum = df_merged.groupby('i')['y_new'].sum().reset_index()
1623
 
1624
+ # Handle residual technologies
1625
  df_production_res = m.solution['y'].sel(i=iRes).to_dataframe().reset_index()
1626
  df_price_res = m.constraints['load'].dual.to_dataframe().reset_index()
1627
  df_merged_res = pd.merge(df_production_res, df_price_res, on='t')
 
1636
  df_contr_marg_sum = df_contr_marg_sum.drop(columns=['multiplied_value'])
1637
  df_contr_marg_sum['y'] = df_contr_marg_sum['y_new'] * -1
1638
 
1639
+ # Language translation and color setup
1640
+ df_contr_marg_sum['i_en'] = df_contr_marg_sum['i']
1641
  if st.session_state.lang == 'DE':
1642
  tech_mapping = {
1643
  df.loc[f'tech_{tech.lower()}', 'EN']: df.loc[f'tech_{tech.lower()}', 'DE']
1644
  for tech in i if f'tech_{tech.lower()}' in df.index
1645
  }
 
1646
  df_contr_marg_sum['i'] = df_contr_marg_sum['i_en'].replace(tech_mapping)
1647
  else:
1648
+ df_contr_marg_sum['i'] = df_contr_marg_sum['i_en']
1649
 
1650
+ # Sort by full technology list
 
1651
  if 'i' in df_contr_marg_sum.columns:
1652
  df_contr_marg_sum = df_contr_marg_sum.drop(columns='i')
 
1653
  df_contr_marg_sum = df_contr_marg_sum.set_index('i_en').loc[i].reset_index()
1654
+ df_contr_marg_sum['y'] = df_contr_marg_sum['y'] * 1e-9 # Convert to billion EUR
1655
 
1656
+ # Transparent battery color
1657
+ color_dict_local = color_dict.copy()
1658
+ color_dict_local['Battery storages'] = 'rgba(0,0,0,0)'
1659
+ if 'i_en' not in df_contr_marg_sum.columns:
1660
+ df_contr_marg_sum['i_en'] = df_contr_marg_sum['i']
1661
+ # Bar plot
1662
+ title = df.loc['plot_label_contribution_margin_undergrad', st.session_state.lang]
1663
  fig = px.bar(
1664
  df_contr_marg_sum,
1665
  y='i',
 
1667
  orientation='h',
1668
  title=title,
1669
  color='i_en',
1670
+ color_discrete_map=color_dict_local,
1671
+ labels={'i': ''},
1672
+ hover_data={'i_en': False, 'i': True}
1673
  )
1674
+ fig.update_layout(showlegend=False)
 
 
 
 
 
 
 
 
 
1675
  col.plotly_chart(fig)
1676
 
1677
  return df_contr_marg_sum
1678
 
1679
 
1680
 
1681
+
1682
  def disaggregate_df(df, t, t_original, dt):
1683
  """
1684
  Disaggregates the DataFrame based on the original time steps.
language.csv CHANGED
@@ -56,8 +56,8 @@ 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
 
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 pro Jahr [Mrd. EUR];Investment costs per Year [bn. EUR]
60
+ plot_label_contribution_margin_undergrad;Deckungsbeitr�ge pro Jahr [Mrd. EUR];Contribution Margin [bn. EUR]
61
  label_electricity_price;Strompreis;Electricity price
62
  label_time;Zeit;Time
63
  label_technology;Technologien;Technologies