Spaces:
Sleeping
Sleeping
[FIX] tab5: exclude zero-cap resources and inactive methods from sensitivity selectors, remove redundant session state guards
Browse files- tabs/tab5_sensitivity.py +44 -27
tabs/tab5_sensitivity.py
CHANGED
|
@@ -27,8 +27,18 @@ def compute_baseline():
|
|
| 27 |
)
|
| 28 |
baseline_removed = baseline_result["total_removed"] if baseline_success else 0
|
| 29 |
|
| 30 |
-
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
resource_to_unit = get_resource_to_unit()
|
| 34 |
for b in st.session_state.get("custom_resources", []):
|
|
@@ -121,7 +131,8 @@ def panel_method_sensitivity(baseline_removed):
|
|
| 121 |
|
| 122 |
Sweeps each selected method's cap from 0 % to 100 % of its current value
|
| 123 |
in 10 % steps, re-solves the LP at each point, and plots total CO₂ removed.
|
| 124 |
-
|
|
|
|
| 125 |
|
| 126 |
Args:
|
| 127 |
baseline_removed (float): total CO₂ removed at current inputs.
|
|
@@ -138,14 +149,38 @@ def panel_method_sensitivity(baseline_removed):
|
|
| 138 |
# independent variants (e.g. "Enhanced weathering - Other mineral: Ganite")
|
| 139 |
# by run_expanded_optimization — offer those variants as selectable
|
| 140 |
# entries too, alongside the standard method names, so their own cap can
|
| 141 |
-
# be swept just like any other method.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
custom_resources = st.session_state.get("custom_resources", [])
|
| 143 |
method_options = []
|
| 144 |
for m in method_order:
|
| 145 |
variants = get_other_method_variants(m, custom_resources)
|
| 146 |
if variants:
|
| 147 |
-
method_options.extend(
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
| 149 |
method_options.append(m)
|
| 150 |
|
| 151 |
selected_methods = st.multiselect(
|
|
@@ -158,18 +193,11 @@ def panel_method_sensitivity(baseline_removed):
|
|
| 158 |
|
| 159 |
percentages = list(range(0, 110, 10))
|
| 160 |
all_data = []
|
| 161 |
-
skipped = []
|
| 162 |
|
| 163 |
for method in selected_methods:
|
| 164 |
constraint = st.session_state.constraints.get(method) or default_constraint()
|
| 165 |
cap_type = constraint.get("cap_type", "percent")
|
| 166 |
-
current_cap =
|
| 167 |
-
"cap_value", 100 if cap_type == "percent" else 0.0
|
| 168 |
-
)
|
| 169 |
-
|
| 170 |
-
if current_cap <= 0:
|
| 171 |
-
skipped.append(method)
|
| 172 |
-
continue
|
| 173 |
|
| 174 |
for pct in percentages:
|
| 175 |
perturbed_constraints = copy.deepcopy(st.session_state.constraints)
|
|
@@ -198,9 +226,6 @@ def panel_method_sensitivity(baseline_removed):
|
|
| 198 |
f"CO₂ removed ({CO2_UNIT})": result["total_removed"] if success else 0,
|
| 199 |
})
|
| 200 |
|
| 201 |
-
for m in skipped:
|
| 202 |
-
st.warning(f"Method **{m}** has a cap of 0 — set a value first to include it.", icon=":material/warning:")
|
| 203 |
-
|
| 204 |
if not all_data:
|
| 205 |
return
|
| 206 |
|
|
@@ -229,8 +254,8 @@ def render_tab():
|
|
| 229 |
|
| 230 |
Computes a baseline optimisation from session state, then lets the user
|
| 231 |
sweep one or more resources (or method caps) over a range and plots how
|
| 232 |
-
total CO₂ removed changes.
|
| 233 |
-
|
| 234 |
|
| 235 |
Returns:
|
| 236 |
None
|
|
@@ -247,14 +272,6 @@ def render_tab():
|
|
| 247 |
unsafe_allow_html=True,
|
| 248 |
)
|
| 249 |
|
| 250 |
-
if (
|
| 251 |
-
"resource_caps" not in st.session_state
|
| 252 |
-
or "constraints" not in st.session_state
|
| 253 |
-
or "costs" not in st.session_state
|
| 254 |
-
):
|
| 255 |
-
st.warning("Please complete the setup in previous tabs before running sensitivity analysis.", icon=":material/warning:")
|
| 256 |
-
return
|
| 257 |
-
|
| 258 |
baseline_removed, all_resources, resource_to_unit = compute_baseline()
|
| 259 |
|
| 260 |
tab1, tab2 = st.tabs(["🔁 Resource sensitivity", "🔁 Method sensitivity"])
|
|
|
|
| 27 |
)
|
| 28 |
baseline_removed = baseline_result["total_removed"] if baseline_success else 0
|
| 29 |
|
| 30 |
+
# Only offer resources that actually have a quantity set — sweeping a
|
| 31 |
+
# resource currently at 0 always gives 0 regardless of the % slider
|
| 32 |
+
# (any percentage of a 0 base cap is still 0), so it's never useful to
|
| 33 |
+
# compare.
|
| 34 |
+
custom_resource_names = [
|
| 35 |
+
b["name"] for b in st.session_state.get("custom_resources", [])
|
| 36 |
+
if float(b.get("amount", 0)) > 0
|
| 37 |
+
]
|
| 38 |
+
all_resources = [
|
| 39 |
+
r for r in resource_order
|
| 40 |
+
if r not in hidden_resource_names and st.session_state.resource_caps.get(r, 0) > 0
|
| 41 |
+
] + custom_resource_names
|
| 42 |
|
| 43 |
resource_to_unit = get_resource_to_unit()
|
| 44 |
for b in st.session_state.get("custom_resources", []):
|
|
|
|
| 131 |
|
| 132 |
Sweeps each selected method's cap from 0 % to 100 % of its current value
|
| 133 |
in 10 % steps, re-solves the LP at each point, and plots total CO₂ removed.
|
| 134 |
+
Methods whose current cap is already 0 are excluded from the selector
|
| 135 |
+
entirely (sweeping "X% of 0" is always 0, never useful to compare).
|
| 136 |
|
| 137 |
Args:
|
| 138 |
baseline_removed (float): total CO₂ removed at current inputs.
|
|
|
|
| 149 |
# independent variants (e.g. "Enhanced weathering - Other mineral: Ganite")
|
| 150 |
# by run_expanded_optimization — offer those variants as selectable
|
| 151 |
# entries too, alongside the standard method names, so their own cap can
|
| 152 |
+
# be swept just like any other method. An "Other"-tagged method with NO
|
| 153 |
+
# batch attached has no variant AND can never produce any result on its
|
| 154 |
+
# own (its only resource is the hidden "Other X" pool, which is never
|
| 155 |
+
# directly settable) — skip it entirely rather than offer a sweep that
|
| 156 |
+
# would always show a flat 0 line.
|
| 157 |
+
#
|
| 158 |
+
# Same reasoning as the resource panel: a method whose current cap is
|
| 159 |
+
# already 0 would sweep to "X% of 0", always 0 regardless of the slider —
|
| 160 |
+
# never useful to compare — so it's excluded from the list up front
|
| 161 |
+
# instead of being selectable and only warned about afterwards. Same for
|
| 162 |
+
# a method that is simply inactive (missing resources, manually toggled
|
| 163 |
+
# off in Tab 2...): sweeping its cap_value doesn't touch "active", so the
|
| 164 |
+
# perturbed run stays forced to 0 regardless — just as pointless to offer.
|
| 165 |
+
def _current_cap(method_name):
|
| 166 |
+
constraint = st.session_state.constraints.get(method_name) or default_constraint()
|
| 167 |
+
cap_type = constraint.get("cap_type", "percent")
|
| 168 |
+
return constraint.get("cap_value", 100 if cap_type == "percent" else 0.0)
|
| 169 |
+
|
| 170 |
+
def _is_sweepable(method_name):
|
| 171 |
+
constraint = st.session_state.constraints.get(method_name) or default_constraint()
|
| 172 |
+
return constraint.get("active", True) and _current_cap(method_name) > 0
|
| 173 |
+
|
| 174 |
custom_resources = st.session_state.get("custom_resources", [])
|
| 175 |
method_options = []
|
| 176 |
for m in method_order:
|
| 177 |
variants = get_other_method_variants(m, custom_resources)
|
| 178 |
if variants:
|
| 179 |
+
method_options.extend(
|
| 180 |
+
make_variant_name(m, b["name"]) for b in variants
|
| 181 |
+
if _is_sweepable(make_variant_name(m, b["name"]))
|
| 182 |
+
)
|
| 183 |
+
elif not is_other_method(m) and _is_sweepable(m):
|
| 184 |
method_options.append(m)
|
| 185 |
|
| 186 |
selected_methods = st.multiselect(
|
|
|
|
| 193 |
|
| 194 |
percentages = list(range(0, 110, 10))
|
| 195 |
all_data = []
|
|
|
|
| 196 |
|
| 197 |
for method in selected_methods:
|
| 198 |
constraint = st.session_state.constraints.get(method) or default_constraint()
|
| 199 |
cap_type = constraint.get("cap_type", "percent")
|
| 200 |
+
current_cap = _current_cap(method)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
|
| 202 |
for pct in percentages:
|
| 203 |
perturbed_constraints = copy.deepcopy(st.session_state.constraints)
|
|
|
|
| 226 |
f"CO₂ removed ({CO2_UNIT})": result["total_removed"] if success else 0,
|
| 227 |
})
|
| 228 |
|
|
|
|
|
|
|
|
|
|
| 229 |
if not all_data:
|
| 230 |
return
|
| 231 |
|
|
|
|
| 254 |
|
| 255 |
Computes a baseline optimisation from session state, then lets the user
|
| 256 |
sweep one or more resources (or method caps) over a range and plots how
|
| 257 |
+
total CO₂ removed changes. resource_caps, constraints, and costs are
|
| 258 |
+
guaranteed present by app.py's shared init, which runs before any tab.
|
| 259 |
|
| 260 |
Returns:
|
| 261 |
None
|
|
|
|
| 272 |
unsafe_allow_html=True,
|
| 273 |
)
|
| 274 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
baseline_removed, all_resources, resource_to_unit = compute_baseline()
|
| 276 |
|
| 277 |
tab1, tab2 = st.tabs(["🔁 Resource sensitivity", "🔁 Method sensitivity"])
|