Skip to content

perf: stop on a relative gap as well as an absolute one - #122

Draft
andig wants to merge 2 commits into
perf/two-stage-solvefrom
perf/stop-on-a-relative-gap
Draft

perf: stop on a relative gap as well as an absolute one#122
andig wants to merge 2 commits into
perf/two-stage-solvefrom
perf/stop-on-a-relative-gap

Conversation

@andig

@andig andig commented Jul 27, 2026

Copy link
Copy Markdown
Member

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_REL states 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:

type share of the captured set slow on #115 slow with the relative gap
charge_before_export / flat 38.2% 33% 8%
attenuate_grid_peaks / flat 22.3% 0% 0%
attenuate_grid_peaks / varying 19.6% 8% 8%
charge_before_export / varying 16.9% 33% 8%
none / flat 2.6% 17% 0%
none / varying 0.5% 25% 25%

Weighted 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_export with a flat tariff, which is both the largest type at 38 percent of the set and the one #115 helps least.

  • The gap bounds what it can cost by construction, and the measurement agrees: against perf: decide strategy ties in a second solve, not with a weight #115 the worst any request gives up is 0.146 percent of its cost objective, none exceeds the 0.2 percent the gap allows, and the median is unchanged. No request gets slower by more than 0.18 seconds.
  • gap_abs is multiplied by OBJECTIVE_SCALE on the way to CBC because the model is scaled. gap_rel is a ratio, so it goes over untouched. tests/test_gap_settings.py pins both, including that an unset gap arrives as None rather than as a zero that would quietly demand proven optimality.
  • test_objective_scaling has 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

  • A relative gap is a share of the whole objective, penalties included. A request that is missing a charge goal carries a penalty term orders above its energy cost, so two per mille of that objective is a larger sum of real money than two per mille of a normal bill. No captured request hit this, and the stored infeasible cases are too fast for the gap to bind, so it is untested rather than ruled out. Bounding the relative gap against cost_objective instead of the full objective would close it.

andig added 2 commits July 26, 2026 22:45
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
andig marked this pull request as draft July 27, 2026 08:42
@andig
andig force-pushed the perf/two-stage-solve branch 2 times, most recently from b94b353 to 1c53e73 Compare July 28, 2026 06:33
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