Skip to content

perf: tighten indicator big-Ms to natural per-step bounds#88

Open
andig wants to merge 2 commits into
mainfrom
model/tight-big-m
Open

perf: tighten indicator big-Ms to natural per-step bounds#88
andig wants to merge 2 commits into
mainfrom
model/tight-big-m

Conversation

@andig

@andig andig commented Jul 15, 2026

Copy link
Copy Markdown
Member

Full-suite benchmark (bundled CBC, single-thread, app-faithful build, median of 3)

case main nodes/iters/wall PR nodes/iters/wall
009 0 / 160 / 0.08s 0 / 121 / 0.09s
012 0 / 104 / 0.08s 6 / 101 / 0.10s
013 0 / 3 / 0.05s 0 / 12 / 0.05s
016 2 / 1256 / 0.18s 0 / 148 / 0.09s
017 4 / 97 / 0.09s 0 / 98 / 0.06s
018 8 / 546 / 0.09s 2 / 266 / 0.09s
020 86 / 5233 / 1.02s 18 / 1442 / 0.62s
021 0 / 15 / 0.14s 10 / 220 / 0.38s
others (010/011/014/015/019) all 0 nodes, ≤0.05s unchanged
TOTAL 1.93s 1.70s

020 (the pathological case) drops 4.8× in nodes and 3.6× in iterations; 016/017/018 improve. One regression: 021 goes 0→10 nodes (0.14→0.38s) — the tighter relaxation lands CBC's dives on a different plateau vertex there; total suite is still a net win. All 13 Optimal with unchanged full objectives.

Every indicator constraint used the global M=1e6 while the largest
per-step energy in any test case is a few kWh — 150-400x looser than
needed, which makes the LP relaxation nearly vacuous around the
binaries: a fractional z_p_demand of ~0.003 satisfies the charge-demand
disjunction while paying neither the charge cost nor the penalty, and
export/import direction indicators carry no real information at the
root.

Replace each M with the smallest constant that keeps the inactive
branch vacuous: p_demand and s_max for the demand disjunction, the
per-step charge/discharge caps (the variables' own upper bounds) for
z_c/z_cd and the grid-coupling rows, solar-plus-discharge-capacity and
demand-plus-charge-capacity for the flow-direction indicators (falling
back to M only when a grid limit's unbounded excess variable enters the
balance), and the limit threshold itself for z_imp_lim/z_exp_lim. The
integer feasible set is unchanged; the relaxation is strictly tighter.

CBC on case 020: 271 nodes / 2.0s -> 16 nodes / 0.67s; full pulp
objectives on all 13 golden cases match the loose model to ~1e-6
relative (verified side by side). Cases 013/019 land on a different
point of the same full-objective optimal plateau (full objective equal
to 5.5e-7, but the reported penalty-free objective_value decomposition
differs by ~0.7%), so those two expected values are updated; the
underlying optimum is identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@andig

andig commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

@ekkea found this during model optimization when porting CBC to Go.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant