Refuse a schedule whose binaries came back off the integers - #136
Merged
Conversation
The second stage decides whether to keep its result by reading the variables back, and a solve that ran out of clock before it found an integer solution leaves the relaxation in them. That point scores better on the preferences than any real schedule can, because it is one the model forbids: the binaries land between 0 and 1 and every rule they gate stops holding. So the tie break kept it, overwrote the first stage schedule, and the response went out with the charge power gate ignored in a third of its slots and a status of Feasible. Reported in #127, where an EV with c_min=4140 charged at a fifth of that in 14 of 192 slots, z_c sitting between 0.12 and 0.36. It reproduces on the split path whenever the preference stage finds nothing before its slice runs out. Integrality is now checked where the other two acceptance conditions already are, for the reason stated there: a solver that reports the wrong status must not be able to spend money, and it must not be able to hand back a schedule the model does not allow either. A second check sits right before the result is built, standing in for every stage above deciding correctly. A schedule that breaks the model is worse than no schedule, since it looks like an answer and the caller charges a battery by it, so a fractional point is reported as no schedule at all rather than returned. One pass over the integer variables against a solve measured in seconds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Member
Author
|
@ekkea on top of the 2-stage solve. Merging since that hasn't hit master yet. |
andig
added a commit
that referenced
this pull request
Aug 8, 2026
The second stage decides whether to keep its result by reading the variables back, and a solve that ran out of clock before it found an integer solution leaves the relaxation in them. That point scores better on the preferences than any real schedule can, because it is one the model forbids: the binaries land between 0 and 1 and every rule they gate stops holding. So the tie break kept it, overwrote the first stage schedule, and the response went out with the charge power gate ignored in a third of its slots and a status of Feasible. Reported in #127, where an EV with c_min=4140 charged at a fifth of that in 14 of 192 slots, z_c sitting between 0.12 and 0.36. It reproduces on the split path whenever the preference stage finds nothing before its slice runs out. Integrality is now checked where the other two acceptance conditions already are, for the reason stated there: a solver that reports the wrong status must not be able to spend money, and it must not be able to hand back a schedule the model does not allow either. A second check sits right before the result is built, standing in for every stage above deciding correctly. A schedule that breaks the model is worse than no schedule, since it looks like an answer and the caller charges a battery by it, so a fractional point is reported as no schedule at all rather than returned. One pass over the integer variables against a solve measured in seconds. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Fixes the defect behind #127: an EV with
c_min=4140charging at a fifth of that in 14 of 192 slots,z_csitting between 0.12 and 0.36, reported asFeasible.What happens
_solve_preferencesdecides whether to keep its result by reading the variables back. A solve that runs out of clock before it finds an integer solution leaves the relaxation in them, and pulp reads that like any other result. It scores better on the preferences than any real schedule can — because it is one the model forbids — soimprovedis true, the first stage schedule is discarded, andproblem.statusis then forced to Optimal.Reproduces on the split path at the default probe budget:
What changes
The acceptance test. Integrality joins the two conditions already checked there, for the reason the comment beside them gives: a solver that reports the wrong status must not be able to spend money, and it must not be able to hand back a schedule the model does not allow either. The stage now ends as
Not Solved, kept the first stageand the first stage schedule survives.A check before the result is built. Standing in for every stage above deciding correctly. A schedule that breaks the model is worse than no schedule — it looks like an answer and the caller charges a battery by it — so a fractional point is reported as no schedule rather than returned. One pass over the integer variables against a solve measured in seconds. It logs when it fires; a silent fallback is how this stayed invisible.
Note for anyone reading the check: pulp stores a binary as an integer bounded to 0 and 1, so
LpBinarynever survives on a variable andLpIntegeris what the filter has to match. Getting that wrong makes the guard pass vacuously.Tests
tests/test_fractional_solution.py, three cases, no captured request and no dependence on a timing race:Not Solvedwith no scheduleFull suite passes (115).
Base
Branched off
perf/two-stage-solverather thanmain, since that is where_solve_preferenceslives. #135 picks it up on its next merge. Worth deploying independently of the perf work — production currently runs the rollup, where this is reachable.Related: the probe change measured alongside this hides the bug by keeping most requests on the joint path. It does not remove it, and this fix stands on its own.