Spaces:
Sleeping
Sleeping
[FIX] Model: Updating the model calculation when a method is removed from tab one
Browse files- optimization/optimization.py +59 -46
- tabs/tab1_inputs.py +24 -0
- tabs/tab3_coefficients.py +21 -20
- tabs/tab4_optimization.py +21 -2
optimization/optimization.py
CHANGED
|
@@ -43,19 +43,22 @@ def run_optimization(
|
|
| 43 |
custom_batch_info = {} # {batch_name: {"group": ref, "amount": Q}}
|
| 44 |
for batch in (custom_resources or []):
|
| 45 |
custom_batch_info[batch["name"]] = {
|
| 46 |
-
"group":
|
| 47 |
-
"amount":
|
|
|
|
| 48 |
}
|
| 49 |
custom_names = set(custom_batch_info)
|
| 50 |
ref_to_batches = {} # {standard_resource: [batch_name, ...]}
|
| 51 |
for bname, info in custom_batch_info.items():
|
| 52 |
ref_to_batches.setdefault(info["group"], []).append(bname)
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
#
|
| 57 |
-
#
|
| 58 |
-
#
|
|
|
|
|
|
|
| 59 |
for batch in (custom_resources or []):
|
| 60 |
bname = batch["name"]
|
| 61 |
for method, coefs in batch["methods"].items():
|
|
@@ -84,22 +87,22 @@ def run_optimization(
|
|
| 84 |
return False, {"message": "No active methods."}
|
| 85 |
|
| 86 |
# ── Split-variable list ───────────────────────────────────────────────────
|
| 87 |
-
# z_{m,n}
|
| 88 |
-
#
|
| 89 |
-
#
|
| 90 |
-
#
|
| 91 |
-
# Guard: if the
|
| 92 |
-
# batch is also inactive — a batch supplements an existing resource, it doesn't
|
| 93 |
-
# enable a resource type that the user hasn't allocated.
|
| 94 |
z_pairs = []
|
| 95 |
for bname, info in custom_batch_info.items():
|
| 96 |
-
ref
|
| 97 |
-
|
| 98 |
-
|
|
|
|
| 99 |
for m in methods:
|
| 100 |
-
|
|
|
|
| 101 |
c_n = method_costs.get(m, {}).get(bname, 0.0)
|
| 102 |
-
if
|
| 103 |
z_pairs.append((m, bname))
|
| 104 |
|
| 105 |
Z = len(z_pairs)
|
|
@@ -139,31 +142,27 @@ def run_optimization(
|
|
| 139 |
A_rows.append(row)
|
| 140 |
b_rows.append(resource_caps[r])
|
| 141 |
|
| 142 |
-
# (C) Custom batch capacity constraints
|
| 143 |
-
# For substitutable pairs (m, n) in z_idx → c_n[m] * z_{m,n}
|
| 144 |
-
# For non-substitutable (c_s = 0, c_n > 0) → c_n[m] * y_m (method fully
|
| 145 |
-
# relies on this batch since it has no access to the standard resource)
|
| 146 |
-
# Skip entirely when the standard resource cap is 0 (batch is inactive).
|
| 147 |
for bname, info in custom_batch_info.items():
|
| 148 |
-
|
| 149 |
-
if resource_caps.get(ref, 0.0) <= 0:
|
| 150 |
continue
|
| 151 |
row = np.zeros(N)
|
| 152 |
for j, m in enumerate(methods):
|
| 153 |
-
|
| 154 |
-
if c_n_m <= 0:
|
| 155 |
continue
|
| 156 |
-
|
| 157 |
-
if
|
| 158 |
row[z_idx[(m, bname)]] = c_n_m
|
| 159 |
-
else:
|
| 160 |
-
row[j] = c_n_m # entire y_m draws from this batch
|
| 161 |
if np.any(row != 0):
|
| 162 |
A_rows.append(row)
|
| 163 |
-
b_rows.append(
|
| 164 |
-
|
| 165 |
-
# (
|
| 166 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
coupling_done = set()
|
| 168 |
for (m, bname) in z_pairs:
|
| 169 |
ref = custom_batch_info[bname]["group"]
|
|
@@ -171,15 +170,27 @@ def run_optimization(
|
|
| 171 |
if key in coupling_done:
|
| 172 |
continue
|
| 173 |
coupling_done.add(key)
|
| 174 |
-
j
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
row = np.zeros(N)
|
| 176 |
row[j] = -1.0
|
| 177 |
-
for
|
| 178 |
-
|
| 179 |
-
row[z_idx[(m, bn)]] = 1.0
|
| 180 |
A_rows.append(row)
|
| 181 |
b_rows.append(0.0)
|
| 182 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
# (E) Method cap constraints (on y_m)
|
| 184 |
for j, m in enumerate(methods):
|
| 185 |
constraint = method_constraints.get(m, {})
|
|
@@ -242,18 +253,20 @@ def run_optimization(
|
|
| 242 |
if used > 1e-9:
|
| 243 |
resource_usage[r][m] = float(used)
|
| 244 |
|
| 245 |
-
# Custom batches
|
| 246 |
for bname, info in custom_batch_info.items():
|
| 247 |
-
ref
|
|
|
|
| 248 |
for j, m in enumerate(methods):
|
|
|
|
|
|
|
| 249 |
c_n_m = method_costs.get(m, {}).get(bname, 0.0)
|
| 250 |
if c_n_m <= 0:
|
| 251 |
continue
|
| 252 |
-
|
| 253 |
-
if c_s_m > 0 and (m, bname) in z_idx:
|
| 254 |
used = c_n_m * sol[z_idx[(m, bname)]]
|
| 255 |
else:
|
| 256 |
-
used =
|
| 257 |
if used > 1e-9:
|
| 258 |
resource_usage[bname][m] = float(used)
|
| 259 |
|
|
|
|
| 43 |
custom_batch_info = {} # {batch_name: {"group": ref, "amount": Q}}
|
| 44 |
for batch in (custom_resources or []):
|
| 45 |
custom_batch_info[batch["name"]] = {
|
| 46 |
+
"group": batch["group"],
|
| 47 |
+
"amount": float(batch["amount"]),
|
| 48 |
+
"primary_methods": set(batch.get("methods", {}).keys()),
|
| 49 |
}
|
| 50 |
custom_names = set(custom_batch_info)
|
| 51 |
ref_to_batches = {} # {standard_resource: [batch_name, ...]}
|
| 52 |
for bname, info in custom_batch_info.items():
|
| 53 |
ref_to_batches.setdefault(info["group"], []).append(bname)
|
| 54 |
+
if bname not in resource_caps:
|
| 55 |
+
resource_caps[bname] = info["amount"]
|
| 56 |
+
|
| 57 |
+
# Inject primary batch coefficients (fallback) ─────────────────────────────
|
| 58 |
+
# Tab 3 pre-init may have set the coefficient to 0; fall back to batch median.
|
| 59 |
+
# Custom pools are INDEPENDENT of the standard resource enable/disable toggle:
|
| 60 |
+
# a method can access a custom batch even if it is disabled for the standard
|
| 61 |
+
# reference resource — the D2 coupling constraint (y_m ≤ Σ z) bounds the LP.
|
| 62 |
for batch in (custom_resources or []):
|
| 63 |
bname = batch["name"]
|
| 64 |
for method, coefs in batch["methods"].items():
|
|
|
|
| 87 |
return False, {"message": "No active methods."}
|
| 88 |
|
| 89 |
# ── Split-variable list ───────────────────────────────────────────────────
|
| 90 |
+
# z_{m,n} is the CO₂ from method m drawn from custom pool n.
|
| 91 |
+
# y_m - Σ_n z_{m,n} is what method m draws from the standard pool.
|
| 92 |
+
# Custom pools are INDEPENDENT: a method can use a custom pool even when
|
| 93 |
+
# the standard resource is 0 or disabled for that method.
|
| 94 |
+
# Guard: skip batch if the batch itself has no capacity (Q_n = 0).
|
|
|
|
|
|
|
| 95 |
z_pairs = []
|
| 96 |
for bname, info in custom_batch_info.items():
|
| 97 |
+
ref = info["group"]
|
| 98 |
+
primary_methods = info["primary_methods"]
|
| 99 |
+
if resource_caps.get(bname, 0.0) <= 0:
|
| 100 |
+
continue # custom batch has no capacity → inactive
|
| 101 |
for m in methods:
|
| 102 |
+
if m not in primary_methods:
|
| 103 |
+
continue # only methods explicitly linked to this batch in the dialog
|
| 104 |
c_n = method_costs.get(m, {}).get(bname, 0.0)
|
| 105 |
+
if c_n > 0:
|
| 106 |
z_pairs.append((m, bname))
|
| 107 |
|
| 108 |
Z = len(z_pairs)
|
|
|
|
| 142 |
A_rows.append(row)
|
| 143 |
b_rows.append(resource_caps[r])
|
| 144 |
|
| 145 |
+
# (C) Custom batch capacity constraints: Σ_m c_n[m] * z_{m,n} ≤ Q_n
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
for bname, info in custom_batch_info.items():
|
| 147 |
+
if resource_caps.get(bname, 0.0) <= 0:
|
|
|
|
| 148 |
continue
|
| 149 |
row = np.zeros(N)
|
| 150 |
for j, m in enumerate(methods):
|
| 151 |
+
if (m, bname) not in z_idx:
|
|
|
|
| 152 |
continue
|
| 153 |
+
c_n_m = method_costs.get(m, {}).get(bname, 0.0)
|
| 154 |
+
if c_n_m > 0:
|
| 155 |
row[z_idx[(m, bname)]] = c_n_m
|
|
|
|
|
|
|
| 156 |
if np.any(row != 0):
|
| 157 |
A_rows.append(row)
|
| 158 |
+
b_rows.append(resource_caps.get(bname, 0.0))
|
| 159 |
+
|
| 160 |
+
# (D1) Coupling lower bound: Σ_{n: ref(n)=r} z_{m,n} ≤ y_m
|
| 161 |
+
# Keeps standard resource usage non-negative.
|
| 162 |
+
# (D2) Coupling upper bound when c_s = 0: y_m ≤ Σ z_{m,n}
|
| 163 |
+
# When a method has no access to the standard resource (disabled or coef=0),
|
| 164 |
+
# its entire output must come from custom batches — prevents y_m from being
|
| 165 |
+
# unbounded. Together with D1 this forces y_m = Σ z when c_s = 0.
|
| 166 |
coupling_done = set()
|
| 167 |
for (m, bname) in z_pairs:
|
| 168 |
ref = custom_batch_info[bname]["group"]
|
|
|
|
| 170 |
if key in coupling_done:
|
| 171 |
continue
|
| 172 |
coupling_done.add(key)
|
| 173 |
+
j = method_idx[m]
|
| 174 |
+
c_s_m = method_costs.get(m, {}).get(ref, 0.0)
|
| 175 |
+
batch_cols = [z_idx[(m, bn)] for bn in ref_to_batches.get(ref, []) if (m, bn) in z_idx]
|
| 176 |
+
|
| 177 |
+
# D1: Σ z ≤ y_m → Σ z - y_m ≤ 0
|
| 178 |
row = np.zeros(N)
|
| 179 |
row[j] = -1.0
|
| 180 |
+
for col in batch_cols:
|
| 181 |
+
row[col] = 1.0
|
|
|
|
| 182 |
A_rows.append(row)
|
| 183 |
b_rows.append(0.0)
|
| 184 |
|
| 185 |
+
# D2 (only when c_s = 0): y_m ≤ Σ z → y_m - Σ z ≤ 0
|
| 186 |
+
if c_s_m == 0:
|
| 187 |
+
row2 = np.zeros(N)
|
| 188 |
+
row2[j] = 1.0
|
| 189 |
+
for col in batch_cols:
|
| 190 |
+
row2[col] = -1.0
|
| 191 |
+
A_rows.append(row2)
|
| 192 |
+
b_rows.append(0.0)
|
| 193 |
+
|
| 194 |
# (E) Method cap constraints (on y_m)
|
| 195 |
for j, m in enumerate(methods):
|
| 196 |
constraint = method_constraints.get(m, {})
|
|
|
|
| 253 |
if used > 1e-9:
|
| 254 |
resource_usage[r][m] = float(used)
|
| 255 |
|
| 256 |
+
# Custom batches — only for methods explicitly linked in the dialog
|
| 257 |
for bname, info in custom_batch_info.items():
|
| 258 |
+
ref = info["group"]
|
| 259 |
+
primary_methods = info["primary_methods"]
|
| 260 |
for j, m in enumerate(methods):
|
| 261 |
+
if m not in primary_methods:
|
| 262 |
+
continue # ignore stale session-state coefficients
|
| 263 |
c_n_m = method_costs.get(m, {}).get(bname, 0.0)
|
| 264 |
if c_n_m <= 0:
|
| 265 |
continue
|
| 266 |
+
if (m, bname) in z_idx:
|
|
|
|
| 267 |
used = c_n_m * sol[z_idx[(m, bname)]]
|
| 268 |
else:
|
| 269 |
+
used = 0.0
|
| 270 |
if used > 1e-9:
|
| 271 |
resource_usage[bname][m] = float(used)
|
| 272 |
|
tabs/tab1_inputs.py
CHANGED
|
@@ -18,6 +18,8 @@ def init_custom_resources():
|
|
| 18 |
st.session_state.custom_resources = []
|
| 19 |
if "enabled_methods" not in st.session_state:
|
| 20 |
st.session_state.enabled_methods = {}
|
|
|
|
|
|
|
| 21 |
for batch in st.session_state.custom_resources:
|
| 22 |
try:
|
| 23 |
batch["amount"] = float(batch["amount"])
|
|
@@ -678,16 +680,38 @@ def render_tab(cost_sliders):
|
|
| 678 |
if ok:
|
| 679 |
method_active_resource_count[m] += 1
|
| 680 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 681 |
methods_with_no_active_resource = [
|
| 682 |
m for m in method_to_primary_resources
|
| 683 |
if method_active_resource_count.get(m, 0) == 0
|
| 684 |
]
|
| 685 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 686 |
newly_disabled = []
|
| 687 |
for m in methods_with_no_active_resource:
|
| 688 |
if st.session_state.constraints.get(m, {}).get("active", True):
|
| 689 |
st.session_state.constraints.setdefault(m, {"active": True, "cap_type": "percent", "cap_value": 100})
|
| 690 |
st.session_state.constraints[m]["active"] = False
|
|
|
|
| 691 |
newly_disabled.append(m)
|
| 692 |
toggle_key = f"toggle_{m}"
|
| 693 |
if toggle_key in st.session_state:
|
|
|
|
| 18 |
st.session_state.custom_resources = []
|
| 19 |
if "enabled_methods" not in st.session_state:
|
| 20 |
st.session_state.enabled_methods = {}
|
| 21 |
+
if "auto_disabled_methods" not in st.session_state:
|
| 22 |
+
st.session_state.auto_disabled_methods = set()
|
| 23 |
for batch in st.session_state.custom_resources:
|
| 24 |
try:
|
| 25 |
batch["amount"] = float(batch["amount"])
|
|
|
|
| 680 |
if ok:
|
| 681 |
method_active_resource_count[m] += 1
|
| 682 |
|
| 683 |
+
# Custom batches are INDEPENDENT of the standard resource toggle: a method
|
| 684 |
+
# linked to a custom batch with capacity counts as having an active resource,
|
| 685 |
+
# even if it is disabled for the standard reference resource.
|
| 686 |
+
for batch in st.session_state.get("custom_resources", []):
|
| 687 |
+
if float(batch.get("amount") or 0) <= 0:
|
| 688 |
+
continue
|
| 689 |
+
for m_name in batch.get("methods", {}).keys():
|
| 690 |
+
method_active_resource_count.setdefault(m_name, 0)
|
| 691 |
+
method_active_resource_count[m_name] += 1
|
| 692 |
+
|
| 693 |
methods_with_no_active_resource = [
|
| 694 |
m for m in method_to_primary_resources
|
| 695 |
if method_active_resource_count.get(m, 0) == 0
|
| 696 |
]
|
| 697 |
|
| 698 |
+
# Re-enable methods that were auto-disabled but now have at least one active resource
|
| 699 |
+
for m in list(st.session_state.auto_disabled_methods):
|
| 700 |
+
if method_active_resource_count.get(m, 0) > 0:
|
| 701 |
+
if m in st.session_state.constraints:
|
| 702 |
+
st.session_state.constraints[m]["active"] = True
|
| 703 |
+
toggle_key = f"toggle_{m}"
|
| 704 |
+
if toggle_key in st.session_state:
|
| 705 |
+
st.session_state[toggle_key] = True
|
| 706 |
+
st.session_state.auto_disabled_methods.discard(m)
|
| 707 |
+
|
| 708 |
+
# Auto-disable methods that now have no active primary resource
|
| 709 |
newly_disabled = []
|
| 710 |
for m in methods_with_no_active_resource:
|
| 711 |
if st.session_state.constraints.get(m, {}).get("active", True):
|
| 712 |
st.session_state.constraints.setdefault(m, {"active": True, "cap_type": "percent", "cap_value": 100})
|
| 713 |
st.session_state.constraints[m]["active"] = False
|
| 714 |
+
st.session_state.auto_disabled_methods.add(m)
|
| 715 |
newly_disabled.append(m)
|
| 716 |
toggle_key = f"toggle_{m}"
|
| 717 |
if toggle_key in st.session_state:
|
tabs/tab3_coefficients.py
CHANGED
|
@@ -27,24 +27,12 @@ def is_method_enabled(method, resource, enabled_methods):
|
|
| 27 |
|
| 28 |
|
| 29 |
def _enforce_disabled_coefficients(method_order_, resource_order_, custom_resources, enabled_methods):
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
for
|
| 35 |
-
|
| 36 |
-
if not disallowed:
|
| 37 |
-
continue
|
| 38 |
-
for m in disallowed:
|
| 39 |
-
if m in st.session_state.costs and r in st.session_state.costs[m]:
|
| 40 |
-
st.session_state.costs[m][r] = 0.0
|
| 41 |
-
|
| 42 |
-
for batch in custom_resources or []:
|
| 43 |
-
r = batch["name"]
|
| 44 |
-
disallowed = {m for m, ok in enabled_methods.get(r, {}).items() if not ok}
|
| 45 |
-
for m in disallowed:
|
| 46 |
-
if m in st.session_state.costs and r in st.session_state.costs[m]:
|
| 47 |
-
st.session_state.costs[m][r] = 0.0
|
| 48 |
|
| 49 |
|
| 50 |
def render_tab(cost_sliders):
|
|
@@ -142,6 +130,21 @@ def render_tab(cost_sliders):
|
|
| 142 |
sub_group = custom_resource_to_sub_group.get(custom_r)
|
| 143 |
return sub_group is not None and (method_name, sub_group) in secondary_resources
|
| 144 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
# Pre-initialize costs for custom resources so the summary table shows the
|
| 146 |
# correct value immediately, without needing to open the expander first.
|
| 147 |
# Secondary custom resources start at 0 (like standard secondary resources).
|
|
@@ -202,7 +205,6 @@ def render_tab(cost_sliders):
|
|
| 202 |
enabled = is_method_enabled(method, r, enabled_methods)
|
| 203 |
if not enabled:
|
| 204 |
st.info(f"INFO: Method disabled for **{r}** (see Resource Inputs tab)")
|
| 205 |
-
st.session_state.costs[method][r] = 0.0
|
| 206 |
continue
|
| 207 |
conf = method_sliders[r]
|
| 208 |
display_label = resource_display.get(r, r)
|
|
@@ -259,7 +261,6 @@ def render_tab(cost_sliders):
|
|
| 259 |
enabled = is_method_enabled(method, r, enabled_methods)
|
| 260 |
if not enabled:
|
| 261 |
st.info("INFO: Method disabled for this resource (see Resource Inputs tab)")
|
| 262 |
-
st.session_state.costs.setdefault(method, {})[r] = 0.0
|
| 263 |
continue
|
| 264 |
|
| 265 |
_is_cust_secondary = is_custom_secondary(method, r)
|
|
|
|
| 27 |
|
| 28 |
|
| 29 |
def _enforce_disabled_coefficients(method_order_, resource_order_, custom_resources, enabled_methods):
|
| 30 |
+
# Intentionally a no-op: disabling a method for a resource must NOT permanently
|
| 31 |
+
# zero the stored coefficient in session state, because re-enabling would then
|
| 32 |
+
# see an existing key at 0 and the pre-init would skip restoration.
|
| 33 |
+
# The LP in optimization.py already applies enabled_methods at call time
|
| 34 |
+
# (zeroing only for that run, without mutating session state).
|
| 35 |
+
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
|
| 38 |
def render_tab(cost_sliders):
|
|
|
|
| 130 |
sub_group = custom_resource_to_sub_group.get(custom_r)
|
| 131 |
return sub_group is not None and (method_name, sub_group) in secondary_resources
|
| 132 |
|
| 133 |
+
# Build reverse index: custom resource name → set of valid methods
|
| 134 |
+
valid_methods_for_custom = {}
|
| 135 |
+
for m, resources in custom_sliders_by_method.items():
|
| 136 |
+
for r in resources:
|
| 137 |
+
valid_methods_for_custom.setdefault(r, set()).add(m)
|
| 138 |
+
|
| 139 |
+
# Remove stale session state entries: a method should not carry a custom
|
| 140 |
+
# resource coefficient if it is not linked to that batch (neither as
|
| 141 |
+
# explicit primary nor as secondary auto-injection). Stale values can
|
| 142 |
+
# persist across reruns and cause the LP to create spurious z-pairs.
|
| 143 |
+
for r_name, valid in valid_methods_for_custom.items():
|
| 144 |
+
for m in list(st.session_state.get("costs", {}).keys()):
|
| 145 |
+
if r_name in st.session_state.costs.get(m, {}) and m not in valid:
|
| 146 |
+
del st.session_state.costs[m][r_name]
|
| 147 |
+
|
| 148 |
# Pre-initialize costs for custom resources so the summary table shows the
|
| 149 |
# correct value immediately, without needing to open the expander first.
|
| 150 |
# Secondary custom resources start at 0 (like standard secondary resources).
|
|
|
|
| 205 |
enabled = is_method_enabled(method, r, enabled_methods)
|
| 206 |
if not enabled:
|
| 207 |
st.info(f"INFO: Method disabled for **{r}** (see Resource Inputs tab)")
|
|
|
|
| 208 |
continue
|
| 209 |
conf = method_sliders[r]
|
| 210 |
display_label = resource_display.get(r, r)
|
|
|
|
| 261 |
enabled = is_method_enabled(method, r, enabled_methods)
|
| 262 |
if not enabled:
|
| 263 |
st.info("INFO: Method disabled for this resource (see Resource Inputs tab)")
|
|
|
|
| 264 |
continue
|
| 265 |
|
| 266 |
_is_cust_secondary = is_custom_secondary(method, r)
|
tabs/tab4_optimization.py
CHANGED
|
@@ -330,8 +330,27 @@ def run_and_store_result():
|
|
| 330 |
st.warning("⚠️ All resources are set to 0. Please set resource availability or load a scenario / input data.")
|
| 331 |
return
|
| 332 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 333 |
safe_constraints = neutralize_zero_coefficient_methods(
|
| 334 |
-
|
| 335 |
)
|
| 336 |
neutralized = [
|
| 337 |
m for m in safe_constraints
|
|
@@ -341,7 +360,7 @@ def run_and_store_result():
|
|
| 341 |
|
| 342 |
success, result = run_optimization(
|
| 343 |
resource_caps=st.session_state.resource_caps,
|
| 344 |
-
method_constraints=
|
| 345 |
method_costs=st.session_state.costs,
|
| 346 |
custom_resources=custom_resources,
|
| 347 |
enabled_methods=enabled_methods,
|
|
|
|
| 330 |
st.warning("⚠️ All resources are set to 0. Please set resource availability or load a scenario / input data.")
|
| 331 |
return
|
| 332 |
|
| 333 |
+
# Apply enabled_methods overrides to a temporary copy so that
|
| 334 |
+
# neutralize_zero_coefficient_methods sees the effective coefficients
|
| 335 |
+
# (a method fully disabled for its only resource has all-zero coefficients
|
| 336 |
+
# and must be deactivated before the LP is built).
|
| 337 |
+
costs_effective = {m: dict(v) for m, v in st.session_state.costs.items()}
|
| 338 |
+
for _m, _resources in costs_effective.items():
|
| 339 |
+
for _r in list(_resources):
|
| 340 |
+
if not enabled_methods.get(_r, {}).get(_m, True):
|
| 341 |
+
costs_effective[_m][_r] = 0.0
|
| 342 |
+
|
| 343 |
+
# Inject custom batch coefficients into costs_effective so neutralize does
|
| 344 |
+
# not wrongly deactivate a method that only has custom-batch access.
|
| 345 |
+
# Custom pools are INDEPENDENT of the standard resource enable/disable toggle.
|
| 346 |
+
for _batch in custom_resources:
|
| 347 |
+
_bname = _batch["name"]
|
| 348 |
+
for _m, _coefs in _batch.get("methods", {}).items():
|
| 349 |
+
if _m in costs_effective and costs_effective[_m].get(_bname, 0.0) == 0.0:
|
| 350 |
+
costs_effective[_m][_bname] = _coefs.get("median", 0.0)
|
| 351 |
+
|
| 352 |
safe_constraints = neutralize_zero_coefficient_methods(
|
| 353 |
+
costs_effective, st.session_state.constraints
|
| 354 |
)
|
| 355 |
neutralized = [
|
| 356 |
m for m in safe_constraints
|
|
|
|
| 360 |
|
| 361 |
success, result = run_optimization(
|
| 362 |
resource_caps=st.session_state.resource_caps,
|
| 363 |
+
method_constraints=safe_constraints,
|
| 364 |
method_costs=st.session_state.costs,
|
| 365 |
custom_resources=custom_resources,
|
| 366 |
enabled_methods=enabled_methods,
|