Spaces:
Sleeping
Sleeping
Upload 2 files
Browse filesundergraduate level: added bar plots for investment costs and contribution margin
- app.py +40 -80
- 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 |
-
|
| 690 |
-
|
| 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 |
-
|
|
|
|
| 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 |
-
#
|
| 1595 |
-
df_new_capacities =
|
| 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 |
-
#
|
| 1609 |
-
|
|
|
|
| 1610 |
|
| 1611 |
-
|
| 1612 |
-
|
| 1613 |
-
|
| 1614 |
-
# Plot
|
| 1615 |
fig = px.bar(
|
| 1616 |
-
|
| 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=
|
| 1623 |
-
labels={'i': '',
|
|
|
|
| 1624 |
)
|
| 1625 |
fig.update_layout(showlegend=False)
|
| 1626 |
col.plotly_chart(fig)
|
| 1627 |
|
| 1628 |
-
return
|
| 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 |
-
|
| 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
|
| 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 |
-
#
|
|
|
|
| 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['
|
| 1686 |
|
| 1687 |
-
#
|
| 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 |
-
#
|
| 1695 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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=
|
| 1704 |
-
labels={'i': '',
|
|
|
|
| 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;
|
| 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
|