CarlyneB commited on
Commit
4198f14
·
1 Parent(s): be0befa

[FIX] Lower usage epsilon to 1e-14 and raise on unknown cap_type in LP builder

Browse files
Files changed (1) hide show
  1. optimization/optimization.py +25 -16
optimization/optimization.py CHANGED
@@ -250,14 +250,19 @@ def build_constraint_matrix(
250
  A_rows.append(row)
251
  b_rows.append(cap_value)
252
  constraint_labels.append(("method_cap", m))
253
- else:
254
- cap_ratio = cap_value / 100.0
255
  row = np.zeros(num_lp_vars)
256
  row[:num_methods] -= cap_ratio
257
  row[j] += 1.0
258
  A_rows.append(row)
259
  b_rows.append(0.0)
260
  constraint_labels.append(("method_cap", m))
 
 
 
 
 
261
 
262
  A_ub = np.array(A_rows) if A_rows else np.empty((0, num_lp_vars))
263
  b_ub = np.array(b_rows)
@@ -296,13 +301,14 @@ def extract_resource_usage(
296
  all_resource_names = standard_resources + list(custom_batch_names)
297
  resource_usage = {r: {} for r in all_resource_names}
298
 
299
- # 1e-9 is a near-zero threshold: LP solvers return tiny floating-point residuals
300
- # (e.g. 0.000000001) instead of exact 0. Values below this threshold are treated
301
- # as zero so they don't appear as spurious resource usage in the output.
 
302
  for r in resources_no_batches:
303
  for j, m in enumerate(active_methods):
304
  used = method_costs.get(m, {}).get(r, 0.0) * method_outputs[j]
305
- if used > 1e-9:
306
  resource_usage[r][m] = float(used)
307
 
308
  for r in resources_with_batches:
@@ -314,7 +320,7 @@ def extract_resource_usage(
314
  if (m, batch_name) in custom_pair_to_index
315
  )
316
  used = c_s_m * (method_outputs[j] - z_sum)
317
- if used > 1e-9:
318
  resource_usage[r][m] = float(used)
319
 
320
  for batch_name, specs in custom_batch_specs.items():
@@ -330,10 +336,10 @@ def extract_resource_usage(
330
  if (m, batch_name) in custom_pair_to_index
331
  else 0.0
332
  )
333
- if used > 1e-9:
334
  resource_usage[batch_name][m] = float(used)
335
 
336
- _EPS = 1e-9
337
  resource_used_total = {
338
  r: round(sum(v.values()), 10) for r, v in resource_usage.items()
339
  }
@@ -489,13 +495,16 @@ def run_optimization(
489
  resources_with_batches = [r for r in standard_resources if r in group_to_custom_batches]
490
  resources_no_batches = [r for r in standard_resources if r not in group_to_custom_batches]
491
 
492
- A_ub, b_ub, constraint_labels = build_constraint_matrix(
493
- active_methods, method_to_index, num_lp_vars, num_methods,
494
- resource_caps, method_costs, method_constraints,
495
- custom_batch_specs, group_to_custom_batches,
496
- resources_no_batches, resources_with_batches,
497
- custom_usage_pairs, custom_pair_to_index,
498
- )
 
 
 
499
 
500
  # linprog minimises by convention, so we negate the objective to maximise Σ y_m.
501
  objective_coefficients = np.zeros(num_lp_vars)
 
250
  A_rows.append(row)
251
  b_rows.append(cap_value)
252
  constraint_labels.append(("method_cap", m))
253
+ elif cap_type == "percent":
254
+ cap_ratio = cap_value / 100
255
  row = np.zeros(num_lp_vars)
256
  row[:num_methods] -= cap_ratio
257
  row[j] += 1.0
258
  A_rows.append(row)
259
  b_rows.append(0.0)
260
  constraint_labels.append(("method_cap", m))
261
+ else:
262
+ raise ValueError(
263
+ f"Unknown cap_type '{cap_type}' for method '{m}'. "
264
+ "Expected 'percent' or 'absolute'."
265
+ )
266
 
267
  A_ub = np.array(A_rows) if A_rows else np.empty((0, num_lp_vars))
268
  b_ub = np.array(b_rows)
 
301
  all_resource_names = standard_resources + list(custom_batch_names)
302
  resource_usage = {r: {} for r in all_resource_names}
303
 
304
+ # LP solvers (HiGHS) return floating-point residuals on the order of 1e-14 to
305
+ # 1e-12, so 1e-14 safely filters out numerical noise while preserving
306
+ # legitimately tiny usage values (e.g. Non-arable land coefficient = 1e-11).
307
+ _USAGE_EPS = 1e-14
308
  for r in resources_no_batches:
309
  for j, m in enumerate(active_methods):
310
  used = method_costs.get(m, {}).get(r, 0.0) * method_outputs[j]
311
+ if used > _USAGE_EPS:
312
  resource_usage[r][m] = float(used)
313
 
314
  for r in resources_with_batches:
 
320
  if (m, batch_name) in custom_pair_to_index
321
  )
322
  used = c_s_m * (method_outputs[j] - z_sum)
323
+ if used > _USAGE_EPS:
324
  resource_usage[r][m] = float(used)
325
 
326
  for batch_name, specs in custom_batch_specs.items():
 
336
  if (m, batch_name) in custom_pair_to_index
337
  else 0.0
338
  )
339
+ if used > _USAGE_EPS:
340
  resource_usage[batch_name][m] = float(used)
341
 
342
+ _EPS = 1e-14
343
  resource_used_total = {
344
  r: round(sum(v.values()), 10) for r, v in resource_usage.items()
345
  }
 
495
  resources_with_batches = [r for r in standard_resources if r in group_to_custom_batches]
496
  resources_no_batches = [r for r in standard_resources if r not in group_to_custom_batches]
497
 
498
+ try:
499
+ A_ub, b_ub, constraint_labels = build_constraint_matrix(
500
+ active_methods, method_to_index, num_lp_vars, num_methods,
501
+ resource_caps, method_costs, method_constraints,
502
+ custom_batch_specs, group_to_custom_batches,
503
+ resources_no_batches, resources_with_batches,
504
+ custom_usage_pairs, custom_pair_to_index,
505
+ )
506
+ except ValueError as e:
507
+ return False, {"message": str(e)}
508
 
509
  # linprog minimises by convention, so we negate the objective to maximise Σ y_m.
510
  objective_coefficients = np.zeros(num_lp_vars)