perf: stop on a relative gap as well as an absolute one - #122
Draft
andig wants to merge 2 commits into
Draft
Conversation
The cost neutral tie breakers, not the physics and not the horizon, drive the requests that exhaust OPTIMIZER_TIME_LIMIT. On a flat tariff the cost optimum is a plateau of equally priced schedules, and the strategy terms are far too small to decide between them, so the search walks the plateau instead. OPTIMIZER_GAP_ABS, one cent, stops the search once the remaining gap is worth less than a cent. OPTIMIZER_STRATEGY_WEIGHT, 3, lifts the strategy terms far enough above the cost terms to actually decide the tie. Neither is enough alone: the gap collapses the plateau walk but leaves the tie to wherever the search stopped, the weight decides the tie but still searches. To support the weight, the objective is assembled as cost_objective plus preference_objective, with a test asserting the split stays exhaustive.
A cent is the wrong unit once the bill is large. Requests whose objective runs to tens of euro reach the schedule they will return within the first second and then spend the whole time limit proving it, sitting a few cents from their bound. On one captured request CBC has its final solution at node 0 and is still 1.36 cents away after 3000 nodes, just outside OPTIMIZER_GAP_ABS. OPTIMIZER_GAP_REL states the same rule as a share of the objective, two per mille by default. The two gaps stop the search on whichever is reached first, so the absolute one still governs small bills and the relative one governs large ones. Of eleven captured requests that neither the objective scaling nor the strategy weight rescues, eight drop under a second. The rest are a separate problem.
andig
marked this pull request as draft
July 27, 2026 08:42
andig
force-pushed
the
perf/two-stage-solve
branch
2 times, most recently
from
July 28, 2026 06:33
b94b353 to
1c53e73
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #115, which it needs for the objective split and the absolute gap. Follows the classes #115 leaves behind.
A cent is the wrong unit once the bill is large. On a captured request whose objective runs to €24.6, CBC has the schedule it will return at node 0 and is still 13,603 scaled units from its bound after 3,000 nodes, having improved the incumbent exactly once in between. That is 1.36 cents, just outside
OPTIMIZER_GAP_ABS, so the search spends the entire time limit proving a schedule it found in the first second.OPTIMIZER_GAP_RELstates the same rule as a share of the objective, two per mille by default. The two stop the search on whichever is reached first, so the absolute gap still governs small bills and the relative one governs large ones.Measured on the 822 slow requests captured after #104 reached production, typed by charging strategy and tariff shape, twelve sampled per type:
charge_before_export/ flatattenuate_grid_peaks/ flatattenuate_grid_peaks/ varyingcharge_before_export/ varyingnone/ flatnone/ varyingWeighted by how common each type is, the share that still exceeds the 10 second production limit goes from 20.6 percent to 6.3 percent. Across the 64 sampled requests that is 12 slow down to 4, and 292.9 seconds of solving down to 123.4. It bites hardest on
charge_before_exportwith a flat tariff, which is both the largest type at 38 percent of the set and the one #115 helps least.gap_absis multiplied byOBJECTIVE_SCALEon the way to CBC because the model is scaled.gap_relis a ratio, so it goes over untouched.tests/test_gap_settings.pypins both, including that an unset gap arrives asNonerather than as a zero that would quietly demand proven optimality.test_objective_scalinghas to switch off both gaps now, not just the absolute one. Either of them lets a run stop short by far more than the difference that test compares, which would turn it into a measurement of where the search happened to stop.Two things this does not settle.
The stored cases cannot justify the default. Every one of them solves in under a second, so the gap never binds and they give up nothing at any value from 0.05 to 0.5 percent. They confirm the change is safe on the shapes they cover; they say nothing about where to set it. The number comes from the captured requests, where 0.2 percent is the first value that collapses the stall: 0.1 percent leaves 6 of the 11 hardest still slow, 0.2 percent leaves 3.
Four of the 64 remain slow, and they are a different problem. Three sit at the cap with the gap on and off, including one under
none, where there are no tie breakers to weight and no plateau to collapse, so whatever holds them is in the base model rather than the preferences. The fourth is a 475 step horizon that is slow for its size. Worth a reproducer and its own change rather than another tolerance.TODO
cost_objectiveinstead of the full objective would close it.