CRRA_optimisation_tool / tabs /tab4_optimization.py
CarlyneB's picture
[FEAT] Possibility to choose between running the optimization with manually entered values or by adding a pre-existing scenario (JSON) -> in this case, the values are automatically populated in all other input tabs
fbd166f
Raw History Blame
11.1 kB
import io
import json
import unicodedata
import zipfile
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import streamlit as st
from config.config import method_order, resource_order, resource_units
from optimization.optimization import run_optimization
from utils.ui_helpers import format_results_summary
from utils.data_utils import is_valid_scenario
def render_tab():
st.header("Portfolio generation")
st.caption("Tab 4 - Seaborn fallback for vertical histograms")
st.info("""
**Objective:**
Run the optimization tool, explore results, and save scenarios for comparison or export.
""")
# Initialize necessary session state variables if they don't exist
if "resource_caps" not in st.session_state:
st.session_state.resource_caps = {}
if "constraints" not in st.session_state:
st.session_state.constraints = {}
if "costs" not in st.session_state:
st.session_state.costs = {}
if "scenarios" not in st.session_state:
st.session_state.scenarios = {}
# Run optimization
st.markdown("### Run optimization")
col_run, col_load = st.columns(2)
with col_run:
st.markdown("**From current input values**")
run_clicked = st.button("▶️ Run Optimization", use_container_width=False)
with col_load:
st.markdown("**From a saved JSON scenario**")
uploaded_scenario = st.file_uploader("Upload a saved JSON scenario", type="json", key="scenario_loader")
if uploaded_scenario is not None:
try:
data = json.load(uploaded_scenario)
if is_valid_scenario(data):
if st.button("↩️ Reload this scenario into all tabs", use_container_width=True):
st.session_state.resource_caps = data["resource_caps"].copy()
st.session_state.constraints = data["method_constraints"].copy()
st.session_state.costs = data["method_costs"].copy()
keys_to_delete = [k for k in st.session_state if any(
k.startswith(prefix) for prefix in ["slider_", "manual_", "toggle_"]
)]
for k in keys_to_delete:
del st.session_state[k]
st.session_state.latest_result = data
st.success("Scenario loaded. All tabs have been updated.")
st.rerun()
else:
st.error("Invalid scenario format.")
except Exception as e:
st.error(f"Error loading scenario: {e}")
# Vérifications et exécution — uniquement si Run Optimization cliqué
if run_clicked:
if sum(st.session_state.resource_caps.values()) <= 0:
st.warning("⚠️ All resources are currently set to 0. Please increase availability or load a saved scenario.")
else:
success, result = run_optimization(
resource_caps=st.session_state.resource_caps,
method_constraints=st.session_state.constraints,
method_costs=st.session_state.costs,
)
if success:
allocations = result["method_usage"]
total_removed = result["total_removed"]
if total_removed <= 0:
st.warning("❌ Optimization removed 0 tCO₂. Relax constraints or increase resources.")
else:
total_cost = sum(
allocations[m] * sum(st.session_state.costs[m].values())
for m in allocations
)
st.session_state.latest_result = {
"summary": {"total_co2_removed": total_removed, "total_cost": total_cost},
"allocations": allocations,
"resource_caps": st.session_state.resource_caps.copy(),
"method_constraints": st.session_state.constraints,
"method_costs": st.session_state.costs,
}
st.success(f"✅ Optimization successful: {total_removed:,.0f} tCO₂ removed")
else:
st.error("❌ Optimization failed")
if "message" in result:
st.error(result["message"])
if "latest_result" not in st.session_state:
return
latest = st.session_state.latest_result
allocations = latest["allocations"]
resource_caps = latest["resource_caps"]
method_costs = latest["method_costs"]
if not allocations or sum(allocations.values()) == 0:
return
# Metrics
col1, col2 = st.columns(2)
col1.metric("Total CO₂ Removed", f"{latest['summary']['total_co2_removed']:,.0f} t")
col2.metric("Methods Used", len(allocations))
# Summary table
st.subheader("Optimization Summary")
df_result = pd.DataFrame.from_dict(allocations, orient="index", columns=["tCO₂ removed"])
df_result["tCO₂ removed"] = pd.to_numeric(df_result["tCO₂ removed"], errors="coerce")
total = df_result["tCO₂ removed"].sum()
if total > 0:
df_result["Share (%)"] = (df_result["tCO₂ removed"] / total * 100).round(2)
df_result = df_result.sort_values(by="tCO₂ removed", ascending=False)
st.table(format_results_summary(df_result))
# Donut chart
st.markdown("### Method allocation breakdown")
labels = df_result.index.tolist()
values = df_result["tCO₂ removed"].tolist()
import plotly.graph_objects as go
fig_donut = go.Figure(data=[go.Pie(
labels=labels,
values=values,
hole=0.4,
textinfo="label+percent",
textposition="outside",
marker=dict(line=dict(color="#000000", width=1)),
showlegend=False
)])
fig_donut.update_layout(margin=dict(t=10, b=10))
st.plotly_chart(fig_donut, use_container_width=True)
# Heatmap
st.markdown("### Resource usage heatmap (% of cap)")
heatmap_matrix, heatmap_y = [], []
available_resources = [r for r in resource_order if resource_caps.get(r, 0) > 0]
for method in df_result.index:
row = []
for r in available_resources:
usage = method_costs.get(method, {}).get(r, 0.0) * allocations.get(method, 0.0)
row.append((usage / resource_caps[r]) * 100 if resource_caps[r] > 0 else 0)
heatmap_matrix.append(row)
heatmap_y.append(method)
fig_heatmap = go.Figure(data=go.Heatmap(
z=heatmap_matrix,
x=available_resources,
y=heatmap_y,
colorscale="Blues",
hovertemplate="Method: %{y}<br>Resource: %{x}<br>% Used: %{z:.2f}%<extra></extra>",
))
fig_heatmap.update_layout(
title="Resource Usage vs. Cap",
xaxis_title="Resource",
yaxis_title="Method",
height=400,
)
st.plotly_chart(fig_heatmap, use_container_width=True)
# Absolute usage (fallback with seaborn)
st.markdown("### Absolute resource usage by unit (Seaborn)")
df_abs = []
for m, co2_removed in allocations.items():
for unit, resources in resource_units.items():
for r in resources:
if r in method_costs.get(m, {}):
used = method_costs[m][r] * co2_removed
if used > 0:
df_abs.append({
"Method": m,
"Resource": r,
"Used": used,
"Unit": unit
})
if df_abs:
df_abs = pd.DataFrame(df_abs)
for unit in df_abs["Unit"].unique():
st.markdown(f"#### Resources in {unit}")
df_unit = df_abs[df_abs["Unit"] == unit]
fig, ax = plt.subplots(figsize=(8, 4 + 0.3 * df_unit["Resource"].nunique()))
sns.barplot(data=df_unit, x="Resource", y="Used", hue="Method", ax=ax)
ax.set_ylabel(f"Used ({unit})")
ax.set_xlabel("Resource")
ax.tick_params(axis='x', rotation=45)
ax.legend(title="Method", bbox_to_anchor=(1.05, 1), loc='upper left')
st.pyplot(fig)
# Save scenario
st.markdown("### Save scenario")
st.text_input("Scenario name", key="scenario_name")
if st.button("Save Scenario"):
name = st.session_state.get("scenario_name", "").strip()
if not name:
st.warning("Please enter a name before saving.")
elif name in st.session_state.scenarios:
st.warning("Scenario name already exists.")
else:
st.session_state.scenarios[name] = latest
st.success(f"Scenario '{name}' saved.")
# Preview saved scenario
current_name = st.session_state.get("scenario_name", "").strip()
if current_name and current_name in st.session_state.scenarios:
scenario = st.session_state.scenarios[current_name]
st.markdown(f"#### Scenario: {current_name}")
df_saved = pd.DataFrame.from_dict(
scenario["allocations"], orient="index", columns=["tCO₂ removed"]
)
df_saved["tCO₂ removed"] = pd.to_numeric(df_saved["tCO₂ removed"], errors="coerce")
total = df_saved["tCO₂ removed"].sum()
if total > 0:
df_saved["Share (%)"] = (df_saved["tCO₂ removed"] / total * 100).round(2)
df_saved = df_saved.sort_values(by="tCO₂ removed", ascending=False)
st.table(format_results_summary(df_saved))
# Export buttons
safe_name = unicodedata.normalize("NFKD", current_name).encode("ascii", "ignore").decode("ascii")
st.download_button(
label="Export JSON",
data=json.dumps(scenario, indent=2),
file_name=f"{safe_name}_scenario.json",
mime="application/json"
)
inputs_df = pd.DataFrame.from_dict(scenario["resource_caps"], orient="index", columns=["Available"])
constraints_df = pd.DataFrame.from_dict({m: v.get("resources", {}) for m, v in scenario["method_constraints"].items()}, orient="index").fillna(0)
allocations_df = pd.DataFrame.from_dict(scenario["allocations"], orient="index", columns=["tCO₂ removed"])
csv_buffer = io.BytesIO()
with zipfile.ZipFile(csv_buffer, "w") as zipf:
zipf.writestr("resource_caps.csv", inputs_df.to_csv())
zipf.writestr("method_constraints.csv", constraints_df.to_csv())
zipf.writestr("allocations.csv", allocations_df.to_csv())
st.download_button(
label="Export CSV",
data=csv_buffer.getvalue(),
file_name=f"{safe_name}_scenario.zip",
mime="application/zip"
)