CarlyneB commited on
Commit
7a80c0b
·
1 Parent(s): 8bd0ff2

[UX] British spelling: rename optimization → optimisation throughout code (conflict resolve)

Browse files
app.py CHANGED
@@ -6,7 +6,7 @@ import pathlib
6
  import streamlit as st
7
  from streamlit_option_menu import option_menu
8
  from tabs import (tab0_welcome, tab1_inputs, tab2_constraints,
9
- tab3_coefficients, tab4_optimization, tab5_sensitivity,
10
  tab6_scenarios)
11
  from utils.data_utils import load_slider_data, strip_unit_suffix
12
  from utils.state_utils import init_custom_resources
@@ -207,7 +207,7 @@ elif selected == "Method constraints":
207
  elif selected == "Resource coefficients":
208
  tab3_coefficients.render_tab(cost_sliders)
209
  elif selected == "Portfolio generation":
210
- tab4_optimization.render_tab()
211
  elif selected == "Sensitivity analysis":
212
  tab5_sensitivity.render_tab()
213
  elif selected == "Scenario comparison":
 
6
  import streamlit as st
7
  from streamlit_option_menu import option_menu
8
  from tabs import (tab0_welcome, tab1_inputs, tab2_constraints,
9
+ tab3_coefficients, tab4_optimisation, tab5_sensitivity,
10
  tab6_scenarios)
11
  from utils.data_utils import load_slider_data, strip_unit_suffix
12
  from utils.state_utils import init_custom_resources
 
207
  elif selected == "Resource coefficients":
208
  tab3_coefficients.render_tab(cost_sliders)
209
  elif selected == "Portfolio generation":
210
+ tab4_optimisation.render_tab()
211
  elif selected == "Sensitivity analysis":
212
  tab5_sensitivity.render_tab()
213
  elif selected == "Scenario comparison":
assets/styles.css CHANGED
@@ -404,8 +404,8 @@ h1.custom-title {
404
  font-style: italic;
405
  }
406
 
407
- /* Run Optimization — CTA principal */
408
- .st-key-btn-run-optimization button {
409
  border: none !important;
410
  border-radius: 0px;
411
  background: var(--green-dark) !important;
@@ -416,17 +416,17 @@ h1.custom-title {
416
  transition: background 0.25s ease, transform 0.15s ease;
417
  box-shadow: 4px 4px 0px var(--green);
418
  }
419
- .st-key-btn-run-optimization button:hover {
420
  background: #01740f !important;
421
  color: var(--off-white) !important;
422
  transform: translate(-2px, -2px);
423
  box-shadow: 6px 6px 0px var(--green);
424
  }
425
- .st-key-btn-run-optimization button:active {
426
  transform: translate(0px, 0px);
427
  box-shadow: 2px 2px 0px var(--green);
428
  }
429
- .st-key-btn-run-optimization button p {
430
  font-weight: 700 !important;
431
  font-size: clamp(15px, 1vw, 20px) !important;
432
  letter-spacing: 0.12em !important;
 
404
  font-style: italic;
405
  }
406
 
407
+ /* Run optimisation — CTA principal */
408
+ .st-key-btn-run-optimisation button {
409
  border: none !important;
410
  border-radius: 0px;
411
  background: var(--green-dark) !important;
 
416
  transition: background 0.25s ease, transform 0.15s ease;
417
  box-shadow: 4px 4px 0px var(--green);
418
  }
419
+ .st-key-btn-run-optimisation button:hover {
420
  background: #01740f !important;
421
  color: var(--off-white) !important;
422
  transform: translate(-2px, -2px);
423
  box-shadow: 6px 6px 0px var(--green);
424
  }
425
+ .st-key-btn-run-optimisation button:active {
426
  transform: translate(0px, 0px);
427
  box-shadow: 2px 2px 0px var(--green);
428
  }
429
+ .st-key-btn-run-optimisation button p {
430
  font-weight: 700 !important;
431
  font-size: clamp(15px, 1vw, 20px) !important;
432
  letter-spacing: 0.12em !important;
data/update.py CHANGED
@@ -19,7 +19,7 @@ scripts = [
19
  ]
20
 
21
  print("=" * 60)
22
- print("CRRA Optimization Tool — data update")
23
  print("=" * 60)
24
 
25
  for label, script in scripts:
 
19
  ]
20
 
21
  print("=" * 60)
22
+ print("CRRA Optimisation Tool — data update")
23
  print("=" * 60)
24
 
25
  for label, script in scripts:
{optimization → optimisation}/__init__.py RENAMED
File without changes
optimization/optimization.py → optimisation/optimisation.py RENAMED
@@ -26,7 +26,7 @@ def build_custom_batch_structures(method_costs, resource_caps, custom_resources)
26
 
27
  Mutates method_costs (adds fallback median coefficients) and resource_caps
28
  (adds batch caps that are not yet present). Both dicts are already copies
29
- when this function is called from run_optimization.
30
 
31
  Args:
32
  method_costs (dict): {method: {resource: coefficient}} — mutated in place.
@@ -531,7 +531,7 @@ def resolve_pooling_tiebreak(
531
  """Re-solve the LP with each method's output pinned, preferring the more
532
  efficient resource within each standard/custom pooling pair or family pool.
533
 
534
- The main LP (run_optimization) maximises total CO2 removed only — nothing
535
  in its objective distinguishes a standard resource from a custom batch
536
  that substitutes for it (or from a same-family alternative resource), so
537
  when several could satisfy the same demand the solver picks an arbitrary
@@ -650,7 +650,7 @@ def compute_standalone_potential(method, resource_caps, method_costs, custom_res
650
  usage pairs once every other method is excluded).
651
  enabled_methods (dict | None): {resource: {method: bool}}.
652
  resource_to_group (dict | None): {resource: family group name} — see
653
- build_family_pools. Must match what the outer run_optimization
654
  call uses, so the method's standalone potential already reflects
655
  OR-availability across its own same-family resources.
656
 
@@ -659,7 +659,7 @@ def compute_standalone_potential(method, resource_caps, method_costs, custom_res
659
  produce anything on its own).
660
  """
661
  _UNCAPPED = 1e15
662
- success, result = run_optimization(
663
  resource_caps=resource_caps,
664
  method_constraints={method: {"active": True, "cap_type": "absolute", "cap_value": _UNCAPPED}},
665
  method_costs={method: dict(method_costs.get(method, {}))},
@@ -670,7 +670,7 @@ def compute_standalone_potential(method, resource_caps, method_costs, custom_res
670
  return result["total_removed"] if success else 0.0
671
 
672
 
673
- def run_optimization(
674
  resource_caps,
675
  method_constraints,
676
  method_costs,
 
26
 
27
  Mutates method_costs (adds fallback median coefficients) and resource_caps
28
  (adds batch caps that are not yet present). Both dicts are already copies
29
+ when this function is called from run_optimisation.
30
 
31
  Args:
32
  method_costs (dict): {method: {resource: coefficient}} — mutated in place.
 
531
  """Re-solve the LP with each method's output pinned, preferring the more
532
  efficient resource within each standard/custom pooling pair or family pool.
533
 
534
+ The main LP (run_optimisation) maximises total CO2 removed only — nothing
535
  in its objective distinguishes a standard resource from a custom batch
536
  that substitutes for it (or from a same-family alternative resource), so
537
  when several could satisfy the same demand the solver picks an arbitrary
 
650
  usage pairs once every other method is excluded).
651
  enabled_methods (dict | None): {resource: {method: bool}}.
652
  resource_to_group (dict | None): {resource: family group name} — see
653
+ build_family_pools. Must match what the outer run_optimisation
654
  call uses, so the method's standalone potential already reflects
655
  OR-availability across its own same-family resources.
656
 
 
659
  produce anything on its own).
660
  """
661
  _UNCAPPED = 1e15
662
+ success, result = run_optimisation(
663
  resource_caps=resource_caps,
664
  method_constraints={method: {"active": True, "cap_type": "absolute", "cap_value": _UNCAPPED}},
665
  method_costs={method: dict(method_costs.get(method, {}))},
 
670
  return result["total_removed"] if success else 0.0
671
 
672
 
673
+ def run_optimisation(
674
  resource_caps,
675
  method_constraints,
676
  method_costs,
optimization/__pycache__/__init__.cpython-311.pyc DELETED
Binary file (204 Bytes)
 
optimization/__pycache__/optimization.cpython-311.pyc DELETED
Binary file (4.08 kB)
 
tabs/tab1_inputs.py CHANGED
@@ -312,7 +312,7 @@ def dialog_add_custom_resource(cost_sliders, edit_index: int | None = None, pres
312
  if edit_index is not None:
313
  # Clean up stale costs[method][old_name] entries left behind by
314
  # this edit — otherwise tab4's missing-resource check wrongly
315
- # deactivates that method on the next optimization run. Two
316
  # triggers: (a) a method that was previously assigned is no
317
  # longer checked, or (b) the batch itself was renamed, which
318
  # orphans the old name even for methods still checked (the new
 
312
  if edit_index is not None:
313
  # Clean up stale costs[method][old_name] entries left behind by
314
  # this edit — otherwise tab4's missing-resource check wrongly
315
+ # deactivates that method on the next optimisation run. Two
316
  # triggers: (a) a method that was previously assigned is no
317
  # longer checked, or (b) the batch itself was renamed, which
318
  # orphans the old name even for methods still checked (the new
tabs/tab2_constraints.py CHANGED
@@ -58,7 +58,7 @@ def get_all_missing_required(m, cost_sliders_norm):
58
 
59
  Family pooling: when two or more of the method's resources share the same
60
  resource family (e.g. "Arable land" and "Other land" are both "Land" — see
61
- build_family_pools in optimization.py), they are OR-substitutable rather
62
  than all-required. A resource in such a family is only reported as missing
63
  if every member of that family is unavailable; a family member currently
64
  enabled as a secondary resource (its slider toggled on in Tab 3) also
@@ -159,7 +159,7 @@ def _render_resource_status(m, cost_sliders_norm):
159
 
160
  Family pooling: when 2+ of the method's required resources share the same
161
  resource family (e.g. "Forest biomass" / "Other biomass" are both
162
- "Biomass" — see build_family_pools in optimization.py), only one of them
163
  is actually needed. A family member that isn't set is shown as ✅
164
  "optional" (not a blocking ❌) once a sibling already satisfies the
165
  family, since adding it is a bonus, not a requirement.
 
58
 
59
  Family pooling: when two or more of the method's resources share the same
60
  resource family (e.g. "Arable land" and "Other land" are both "Land" — see
61
+ build_family_pools in optimisation.py), they are OR-substitutable rather
62
  than all-required. A resource in such a family is only reported as missing
63
  if every member of that family is unavailable; a family member currently
64
  enabled as a secondary resource (its slider toggled on in Tab 3) also
 
159
 
160
  Family pooling: when 2+ of the method's required resources share the same
161
  resource family (e.g. "Forest biomass" / "Other biomass" are both
162
+ "Biomass" — see build_family_pools in optimisation.py), only one of them
163
  is actually needed. A family member that isn't set is shown as ✅
164
  "optional" (not a blocking ❌) once a sibling already satisfies the
165
  family, since adding it is a bonus, not a requirement.
tabs/{tab4_optimization.py → tab4_optimisation.py} RENAMED
@@ -9,7 +9,7 @@ import pandas as pd
9
  import streamlit as st
10
  import plotly.express as px
11
  from config.config import CRRA_COLORS, PLOTLY_COLOR_SEQUENCE, PLOTLY_HEATMAP_SCALE, resource_order, resource_unit_map, resource_groups, secondary_resources, CO2_UNIT, method_order, section_intros
12
- from optimization.optimization import run_optimization
13
  from utils.ui_helpers import apply_chart_style, register_chart_data, show_bar
14
  from utils.data_utils import (is_valid_scenario, is_complete_scenario, load_inputs_from_excel,
15
  neutralize_zero_coefficient_methods, strip_unit_suffix,
@@ -20,7 +20,7 @@ from utils.state_utils import clear_widget_keys
20
  from config.config import hidden_resource_names as _hidden
21
 
22
  def panel_run_from_tabs():
23
- """Left sub-panel: run optimization from manually filled tabs."""
24
  st.markdown("**Option 1 - From current tab values**", unsafe_allow_html=True)
25
  st.markdown('<p class="caption">Set resource availability and method constraints in the tabs above, then run.</p>', unsafe_allow_html=True)
26
  st.divider()
@@ -88,7 +88,7 @@ def parse_zip_scenario(uploaded) -> dict | None:
88
 
89
  The detailed outputs (resource_usage, resource_used_total, resource_remaining)
90
  are NOT trusted from allocations.csv alone — they are
91
- recomputed by re-running the optimizer on the reloaded inputs. This guarantees
92
  the reloaded scenario is always internally consistent (and lets results pages
93
  render without missing keys), instead of relying on whatever was exported.
94
  """
@@ -200,7 +200,7 @@ def parse_zip_scenario(uploaded) -> dict | None:
200
 
201
  # Recompute detailed outputs from the reloaded inputs so the scenario
202
  # is internally consistent (rather than trusting allocations.csv alone).
203
- success, result = run_optimization(
204
  resource_caps=resource_caps,
205
  method_constraints=constraints,
206
  method_costs=costs,
@@ -255,7 +255,7 @@ def panel_load_from_json_or_csv():
255
 
256
  if "scenario_loaded_file" in st.session_state:
257
  st.success(
258
- f"Scenario **{st.session_state['scenario_loaded_file']}** loaded :\n\nAll tabs updated - Optimization results displayed.",
259
  icon="✅"
260
  )
261
  uploaded_json = st.file_uploader(
@@ -275,7 +275,7 @@ def panel_load_from_json_or_csv():
275
  st.error("Invalid scenario format.")
276
  return
277
  if not is_complete_scenario(data):
278
- success, result = run_optimization(
279
  resource_caps=data["resource_caps"],
280
  method_constraints=data["method_constraints"],
281
  method_costs=data["method_costs"],
@@ -302,7 +302,7 @@ def panel_load_from_json_or_csv():
302
 
303
  filename_stem = uploaded_json.name.rsplit(".", 1)[0]
304
 
305
- if st.button("↩️ Reload scenario and run optimization", width='stretch'):
306
  st.session_state.setdefault("scenario_name_suggestion", filename_stem)
307
  st.session_state.pop("latest_result", None)
308
  st.session_state.pop("excel_loaded_file", None)
@@ -346,7 +346,7 @@ def run_and_store_result():
346
 
347
  Reads resource_caps, constraints, costs, custom_resources, and enabled_methods
348
  from session state. Applies enabled_methods overrides to a temporary cost copy,
349
- neutralises zero-coefficient methods, runs run_optimization, then writes the
350
  full result dict to st.session_state.latest_result.
351
 
352
  Shows a Streamlit warning/error if inputs are invalid or optimisation fails.
@@ -503,7 +503,7 @@ def run_and_store_result():
503
  if group_cap_is_zero and batch_group in applied_costs[method_name]:
504
  applied_costs[method_name][batch_group] = 0.0
505
 
506
- success, result = run_optimization(
507
  resource_caps=resource_caps_for_lp,
508
  method_constraints=safe_constraints,
509
  method_costs=applied_costs,
@@ -513,7 +513,7 @@ def run_and_store_result():
513
  )
514
 
515
  if not success:
516
- st.error("❌ Optimization failed")
517
  if "message" in result:
518
  st.error(result["message"])
519
  return
@@ -529,7 +529,7 @@ def run_and_store_result():
529
  total_removed = result["total_removed"]
530
 
531
  if total_removed <= 0:
532
- st.warning(f"❌ Optimization removed 0 {CO2_UNIT}. Relax constraints or increase resources.", icon=":material/warning:")
533
  return
534
 
535
  total_cost = sum(
@@ -586,7 +586,7 @@ def show_metrics(latest):
586
  """Render top-level KPI metrics and an optional custom-resource summary expander.
587
 
588
  Args:
589
- latest (dict): result dict from run_optimization with keys summary, allocations,
590
  custom_resources.
591
 
592
  Returns:
@@ -1131,7 +1131,7 @@ def export_inputs_only():
1131
 
1132
  # Coefficients — a disabled (method, resource) pair is always exported
1133
  # as 0, regardless of whether the user ever opened it in tab3. This
1134
- # keeps the export consistent with what the optimizer actually uses.
1135
  ws3 = wb.create_sheet("coefficients")
1136
  ws3.append(["method", "resource", "value", "secondary"])
1137
  for m, resources in st.session_state.costs.items():
@@ -1333,7 +1333,7 @@ def render_tab():
1333
  st.markdown(
1334
  f"""
1335
  <div class="text_with_border">
1336
- <p>{section_intros.get("tab4_optimization", "")}</p>
1337
  </div>
1338
  """,
1339
  unsafe_allow_html=True
@@ -1358,7 +1358,7 @@ def render_tab():
1358
 
1359
  with tab_run:
1360
  st.markdown("""
1361
- <p class="crra-sub-heading">Run optimization</p>""",
1362
  unsafe_allow_html=True,
1363
  )
1364
  col_inputs, col_scenario = st.columns(2, gap="large")
@@ -1368,7 +1368,7 @@ def render_tab():
1368
  <p class="crra-sub-sub-heading">From inputs</p>""",
1369
  unsafe_allow_html=True,
1370
  )
1371
- run_clicked = st.button("Run Optimization", icon=":material/play_arrow:", key="btn-run-optimization", type="primary")
1372
  run_msg_area = st.container()
1373
  panel_run_from_tabs()
1374
  panel_load_from_excel()
@@ -1384,7 +1384,7 @@ def render_tab():
1384
  with run_msg_area:
1385
  run_and_store_result()
1386
 
1387
- no_results = '<p class="caption">Run the optimization first to see results here.</p>'
1388
 
1389
  latest = st.session_state.get("latest_result")
1390
  if not latest:
@@ -1418,7 +1418,7 @@ def render_tab():
1418
 
1419
  with tab_results:
1420
  show_metrics(latest)
1421
- st.markdown('<p class="crra-sub-heading">Optimization Summary</p>', unsafe_allow_html=True)
1422
  st.dataframe(df_display, width='stretch')
1423
  st.markdown('<p class="crra-sub-heading">Method allocation breakdown</p>', unsafe_allow_html=True)
1424
  st.markdown(
@@ -1455,6 +1455,6 @@ def render_tab():
1455
  with tab_reset:
1456
  reset_scenario("reset")
1457
 
1458
- if st.button("Sensitivity analysis →", key="btn-nav-next-optimization", type="primary", help="Explore how resource or method changes affect your portfolio."):
1459
  st.session_state["_navigate_to"] = "Sensitivity analysis"
1460
  st.rerun()
 
9
  import streamlit as st
10
  import plotly.express as px
11
  from config.config import CRRA_COLORS, PLOTLY_COLOR_SEQUENCE, PLOTLY_HEATMAP_SCALE, resource_order, resource_unit_map, resource_groups, secondary_resources, CO2_UNIT, method_order, section_intros
12
+ from optimisation.optimisation import run_optimisation
13
  from utils.ui_helpers import apply_chart_style, register_chart_data, show_bar
14
  from utils.data_utils import (is_valid_scenario, is_complete_scenario, load_inputs_from_excel,
15
  neutralize_zero_coefficient_methods, strip_unit_suffix,
 
20
  from config.config import hidden_resource_names as _hidden
21
 
22
  def panel_run_from_tabs():
23
+ """Left sub-panel: run optimisation from manually filled tabs."""
24
  st.markdown("**Option 1 - From current tab values**", unsafe_allow_html=True)
25
  st.markdown('<p class="caption">Set resource availability and method constraints in the tabs above, then run.</p>', unsafe_allow_html=True)
26
  st.divider()
 
88
 
89
  The detailed outputs (resource_usage, resource_used_total, resource_remaining)
90
  are NOT trusted from allocations.csv alone — they are
91
+ recomputed by re-running the optimiser on the reloaded inputs. This guarantees
92
  the reloaded scenario is always internally consistent (and lets results pages
93
  render without missing keys), instead of relying on whatever was exported.
94
  """
 
200
 
201
  # Recompute detailed outputs from the reloaded inputs so the scenario
202
  # is internally consistent (rather than trusting allocations.csv alone).
203
+ success, result = run_optimisation(
204
  resource_caps=resource_caps,
205
  method_constraints=constraints,
206
  method_costs=costs,
 
255
 
256
  if "scenario_loaded_file" in st.session_state:
257
  st.success(
258
+ f"Scenario **{st.session_state['scenario_loaded_file']}** loaded :\n\nAll tabs updated - optimisation results displayed.",
259
  icon="✅"
260
  )
261
  uploaded_json = st.file_uploader(
 
275
  st.error("Invalid scenario format.")
276
  return
277
  if not is_complete_scenario(data):
278
+ success, result = run_optimisation(
279
  resource_caps=data["resource_caps"],
280
  method_constraints=data["method_constraints"],
281
  method_costs=data["method_costs"],
 
302
 
303
  filename_stem = uploaded_json.name.rsplit(".", 1)[0]
304
 
305
+ if st.button("↩️ Reload scenario and run optimisation", width='stretch'):
306
  st.session_state.setdefault("scenario_name_suggestion", filename_stem)
307
  st.session_state.pop("latest_result", None)
308
  st.session_state.pop("excel_loaded_file", None)
 
346
 
347
  Reads resource_caps, constraints, costs, custom_resources, and enabled_methods
348
  from session state. Applies enabled_methods overrides to a temporary cost copy,
349
+ neutralises zero-coefficient methods, runs run_optimisation, then writes the
350
  full result dict to st.session_state.latest_result.
351
 
352
  Shows a Streamlit warning/error if inputs are invalid or optimisation fails.
 
503
  if group_cap_is_zero and batch_group in applied_costs[method_name]:
504
  applied_costs[method_name][batch_group] = 0.0
505
 
506
+ success, result = run_optimisation(
507
  resource_caps=resource_caps_for_lp,
508
  method_constraints=safe_constraints,
509
  method_costs=applied_costs,
 
513
  )
514
 
515
  if not success:
516
+ st.error("❌ optimisation failed")
517
  if "message" in result:
518
  st.error(result["message"])
519
  return
 
529
  total_removed = result["total_removed"]
530
 
531
  if total_removed <= 0:
532
+ st.warning(f"❌ optimisation removed 0 {CO2_UNIT}. Relax constraints or increase resources.", icon=":material/warning:")
533
  return
534
 
535
  total_cost = sum(
 
586
  """Render top-level KPI metrics and an optional custom-resource summary expander.
587
 
588
  Args:
589
+ latest (dict): result dict from run_optimisation with keys summary, allocations,
590
  custom_resources.
591
 
592
  Returns:
 
1131
 
1132
  # Coefficients — a disabled (method, resource) pair is always exported
1133
  # as 0, regardless of whether the user ever opened it in tab3. This
1134
+ # keeps the export consistent with what the optimiser actually uses.
1135
  ws3 = wb.create_sheet("coefficients")
1136
  ws3.append(["method", "resource", "value", "secondary"])
1137
  for m, resources in st.session_state.costs.items():
 
1333
  st.markdown(
1334
  f"""
1335
  <div class="text_with_border">
1336
+ <p>{section_intros.get("tab4_optimisation", "")}</p>
1337
  </div>
1338
  """,
1339
  unsafe_allow_html=True
 
1358
 
1359
  with tab_run:
1360
  st.markdown("""
1361
+ <p class="crra-sub-heading">Run optimisation</p>""",
1362
  unsafe_allow_html=True,
1363
  )
1364
  col_inputs, col_scenario = st.columns(2, gap="large")
 
1368
  <p class="crra-sub-sub-heading">From inputs</p>""",
1369
  unsafe_allow_html=True,
1370
  )
1371
+ run_clicked = st.button("Run optimisation", icon=":material/play_arrow:", key="btn-run-optimisation", type="primary")
1372
  run_msg_area = st.container()
1373
  panel_run_from_tabs()
1374
  panel_load_from_excel()
 
1384
  with run_msg_area:
1385
  run_and_store_result()
1386
 
1387
+ no_results = '<p class="caption">Run the optimisation first to see results here.</p>'
1388
 
1389
  latest = st.session_state.get("latest_result")
1390
  if not latest:
 
1418
 
1419
  with tab_results:
1420
  show_metrics(latest)
1421
+ st.markdown('<p class="crra-sub-heading">optimisation Summary</p>', unsafe_allow_html=True)
1422
  st.dataframe(df_display, width='stretch')
1423
  st.markdown('<p class="crra-sub-heading">Method allocation breakdown</p>', unsafe_allow_html=True)
1424
  st.markdown(
 
1455
  with tab_reset:
1456
  reset_scenario("reset")
1457
 
1458
+ if st.button("Sensitivity analysis →", key="btn-nav-next-optimisation", type="primary", help="Explore how resource or method changes affect your portfolio."):
1459
  st.session_state["_navigate_to"] = "Sensitivity analysis"
1460
  st.rerun()
tabs/tab5_sensitivity.py CHANGED
@@ -3,7 +3,7 @@ import pandas as pd
3
  import streamlit as st
4
  import plotly.express as px
5
  from config.config import CO2_UNIT, hidden_resource_names, method_order, resource_order, resource_unit_map, section_intros
6
- from utils.data_utils import (get_resource_to_unit, get_resource_to_group, run_expanded_optimization,
7
  is_other_method, get_other_method_variants, make_variant_name,
8
  default_constraint)
9
  from utils.ui_helpers import register_chart_data, show_bar
@@ -18,7 +18,7 @@ def compute_baseline():
18
  all_resources – standard resources + custom batch names.
19
  resource_to_unit – {resource: unit} including custom batches.
20
  """
21
- baseline_success, baseline_result = run_expanded_optimization(
22
  resource_caps=st.session_state.resource_caps,
23
  method_constraints=st.session_state.constraints,
24
  costs=st.session_state.costs,
@@ -98,7 +98,7 @@ def panel_resource_sensitivity(baseline_removed, all_resources, resource_to_unit
98
  actual_amount = base_cap * pct / 100
99
  perturbed_caps[resource] = actual_amount
100
 
101
- success, result = run_expanded_optimization(
102
  resource_caps=perturbed_caps,
103
  method_constraints=st.session_state.constraints,
104
  costs=st.session_state.costs,
@@ -162,7 +162,7 @@ def panel_method_sensitivity(baseline_removed):
162
 
163
  # "Other"-tagged methods with custom batches attached are expanded into
164
  # independent variants (e.g. "Enhanced weathering - Other mineral: Ganite")
165
- # by run_expanded_optimization — offer those variants as selectable
166
  # entries too, alongside the standard method names, so their own cap can
167
  # be swept just like any other method. An "Other"-tagged method with NO
168
  # batch attached has no variant AND can never produce any result on its
@@ -226,7 +226,7 @@ def panel_method_sensitivity(baseline_removed):
226
  perturbed_constraints[method]["cap_value"] = actual_cap
227
  cap_label = f"{actual_cap:.2f} {CO2_UNIT}/yr"
228
 
229
- success, result = run_expanded_optimization(
230
  resource_caps=st.session_state.resource_caps,
231
  method_constraints=perturbed_constraints,
232
  costs=st.session_state.costs,
@@ -267,7 +267,7 @@ def panel_method_sensitivity(baseline_removed):
267
  def show_bottleneck_analysis():
268
  """Render the resource sensitivity table (CDR gain per +1 unit of each resource).
269
 
270
- Reads the latest optimization result from session state. Shows nothing if no
271
  result is available yet.
272
 
273
  Returns:
 
3
  import streamlit as st
4
  import plotly.express as px
5
  from config.config import CO2_UNIT, hidden_resource_names, method_order, resource_order, resource_unit_map, section_intros
6
+ from utils.data_utils import (get_resource_to_unit, get_resource_to_group, run_expanded_optimisation,
7
  is_other_method, get_other_method_variants, make_variant_name,
8
  default_constraint)
9
  from utils.ui_helpers import register_chart_data, show_bar
 
18
  all_resources – standard resources + custom batch names.
19
  resource_to_unit – {resource: unit} including custom batches.
20
  """
21
+ baseline_success, baseline_result = run_expanded_optimisation(
22
  resource_caps=st.session_state.resource_caps,
23
  method_constraints=st.session_state.constraints,
24
  costs=st.session_state.costs,
 
98
  actual_amount = base_cap * pct / 100
99
  perturbed_caps[resource] = actual_amount
100
 
101
+ success, result = run_expanded_optimisation(
102
  resource_caps=perturbed_caps,
103
  method_constraints=st.session_state.constraints,
104
  costs=st.session_state.costs,
 
162
 
163
  # "Other"-tagged methods with custom batches attached are expanded into
164
  # independent variants (e.g. "Enhanced weathering - Other mineral: Ganite")
165
+ # by run_expanded_optimisation — offer those variants as selectable
166
  # entries too, alongside the standard method names, so their own cap can
167
  # be swept just like any other method. An "Other"-tagged method with NO
168
  # batch attached has no variant AND can never produce any result on its
 
226
  perturbed_constraints[method]["cap_value"] = actual_cap
227
  cap_label = f"{actual_cap:.2f} {CO2_UNIT}/yr"
228
 
229
+ success, result = run_expanded_optimisation(
230
  resource_caps=st.session_state.resource_caps,
231
  method_constraints=perturbed_constraints,
232
  costs=st.session_state.costs,
 
267
  def show_bottleneck_analysis():
268
  """Render the resource sensitivity table (CDR gain per +1 unit of each resource).
269
 
270
+ Reads the latest optimisation result from session state. Shows nothing if no
271
  result is available yet.
272
 
273
  Returns:
tabs/tab6_scenarios.py CHANGED
@@ -4,7 +4,7 @@ import plotly.express as px
4
  import streamlit as st
5
  from config.config import CO2_UNIT, hidden_resource_names
6
  from utils.data_utils import is_valid_scenario
7
- from tabs.tab4_optimization import parse_zip_scenario
8
  from utils.ui_helpers import apply_chart_style, register_chart_data, show_bar
9
 
10
 
 
4
  import streamlit as st
5
  from config.config import CO2_UNIT, hidden_resource_names
6
  from utils.data_utils import is_valid_scenario
7
+ from tabs.tab4_optimisation import parse_zip_scenario
8
  from utils.ui_helpers import apply_chart_style, register_chart_data, show_bar
9
 
10
 
utils/data_utils.py CHANGED
@@ -9,7 +9,7 @@ from config.config import (
9
  resource_groups, method_groups, secondary_resources, CO2_UNIT,
10
  hidden_resource_names,
11
  )
12
- from optimization.optimization import run_optimization
13
 
14
  def get_resource_to_group() -> dict[str, str]:
15
  """Map each resource name to its resource group.
@@ -245,10 +245,10 @@ def make_variant_name(method_name: str, batch_name: str) -> str:
245
  return f"{method_name}: {batch_name}"
246
 
247
 
248
- def run_expanded_optimization(resource_caps, method_constraints, costs, custom_resources, enabled_methods):
249
  """Apply the full Tab 4 preprocessing pipeline, then run the LP.
250
 
251
- This is the same pipeline as tab4_optimization.run_and_store_result: apply
252
  enabled_methods overrides, inject custom-batch coefficients, neutralise
253
  all-zero methods, deactivate methods missing a required resource, and —
254
  crucially — expand any "Other"-tagged method into one independent method
@@ -258,7 +258,7 @@ def run_expanded_optimization(resource_caps, method_constraints, costs, custom_r
258
 
259
  Any caller that needs LP results consistent with what Tab 4 actually shows
260
  (e.g. Tab 5's sensitivity sweeps) should use this instead of calling
261
- run_optimization() directly with raw session-state costs — otherwise a
262
  custom resource attached to an "Other" method would be silently computed
263
  through the wrong mechanism and never show up as its own entry.
264
 
@@ -273,7 +273,7 @@ def run_expanded_optimization(resource_caps, method_constraints, costs, custom_r
273
  enabled_methods (dict): {resource: {method: bool}}.
274
 
275
  Returns:
276
- tuple[bool, dict]: same shape as optimization.run_optimization()'s
277
  return value — (True, result_dict) or (False, {"message": str}).
278
  """
279
  applied_costs = {m: dict(v) for m, v in costs.items()}
@@ -359,7 +359,7 @@ def run_expanded_optimization(resource_caps, method_constraints, costs, custom_r
359
  if group_cap_is_zero and batch_group in applied_costs[method_name]:
360
  applied_costs[method_name][batch_group] = 0.0
361
 
362
- return run_optimization(
363
  resource_caps=resource_caps_for_lp,
364
  method_constraints=safe_constraints,
365
  method_costs=applied_costs,
 
9
  resource_groups, method_groups, secondary_resources, CO2_UNIT,
10
  hidden_resource_names,
11
  )
12
+ from optimisation.optimisation import run_optimisation
13
 
14
  def get_resource_to_group() -> dict[str, str]:
15
  """Map each resource name to its resource group.
 
245
  return f"{method_name}: {batch_name}"
246
 
247
 
248
+ def run_expanded_optimisation(resource_caps, method_constraints, costs, custom_resources, enabled_methods):
249
  """Apply the full Tab 4 preprocessing pipeline, then run the LP.
250
 
251
+ This is the same pipeline as tab4_optimisation.run_and_store_result: apply
252
  enabled_methods overrides, inject custom-batch coefficients, neutralise
253
  all-zero methods, deactivate methods missing a required resource, and —
254
  crucially — expand any "Other"-tagged method into one independent method
 
258
 
259
  Any caller that needs LP results consistent with what Tab 4 actually shows
260
  (e.g. Tab 5's sensitivity sweeps) should use this instead of calling
261
+ run_optimisation() directly with raw session-state costs — otherwise a
262
  custom resource attached to an "Other" method would be silently computed
263
  through the wrong mechanism and never show up as its own entry.
264
 
 
273
  enabled_methods (dict): {resource: {method: bool}}.
274
 
275
  Returns:
276
+ tuple[bool, dict]: same shape as optimisation.run_optimisation()'s
277
  return value — (True, result_dict) or (False, {"message": str}).
278
  """
279
  applied_costs = {m: dict(v) for m, v in costs.items()}
 
359
  if group_cap_is_zero and batch_group in applied_costs[method_name]:
360
  applied_costs[method_name][batch_group] = 0.0
361
 
362
+ return run_optimisation(
363
  resource_caps=resource_caps_for_lp,
364
  method_constraints=safe_constraints,
365
  method_costs=applied_costs,