feat(optimizer): flatten cost-neutral grid peaks and report service shortfalls - #963
Draft
frahlg wants to merge 3 commits into
Draft
feat(optimizer): flatten cost-neutral grid peaks and report service shortfalls#963frahlg wants to merge 3 commits into
frahlg wants to merge 3 commits into
Conversation
…hortfalls After the economic solve, a preference stage keeps that cost and then minimises the horizon import and export peaks. Hard site limits stay hard; a late or failed preference solve keeps the economic schedule. The solver payload names the stage, the resulting peaks, and any remaining flex/storage/thermal shortfall. Co-authored-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
0.1 öre of cost slack was real money: flatten used it to cut profitable export and sneak a spread-blocked discharge. Bound the second solve to numerical noise, skip one-slot horizons, and reject a unique fuse-export nibble in tests. Co-authored-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Flex and thermal already fell back to CVXPY, so their service report landed. A missed storage energy target on the direct HiGHS path did not. Name that shortfall in the same solver payload, and keep the economic schedule when the preference deadline has already expired. Co-authored-by: Fredrik Ahlgren <fredrik@sourceful-labs.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.
Inspired by evcc-io/optimizer: take the two things that are worth stealing, and leave the rest.
evcc’s planner is a cloud MILP that currently only visualises a schedule. Ours is already on the dispatch path. Their useful ideas are (1) a cost-neutral second solve that flattens grid peaks among equally priced schedules, and (2) telling the caller when a goal was missed. Soft grid limits, EV-as-battery, CBC/PuLP, and cloud-by-default stay out.
Accepted text proposal
This work was requested in the agent thread: compare evcc-io/optimizer with FTW, take the useful bits, and implement them. Maintainer (Fredrik) asked to take the comparison into a build plan and then implement.
What changed
After the economic solve, a preference stage keeps that cost (plus numerical slack, not 0.1 öre) and minimises the horizon’s import and export peaks. Site fuse limits stay hard. If the extra solve is late, fractional, or fails, the economic schedule is kept. Both the direct HiGHS champion and the CVXPY fallback do this. A one-slot horizon skips the second solve (
preference_stage: single_slot) because there is no tie to break across time.The solver payload now carries
preference_stage,import_peak_w,export_peak_w, and an optionalservice_reportwith remaining flex/storage/thermal shortfall. The HiGHS champion path reports storage-target shortfall; flex and thermal still go through CVXPY (direct HiGHS is ineligible for those assets). Core unmarshals those ontoSolverInfoso/apialready exposes them onactive_solver. No UI copy in this PR.settings.flatten_peaksdefaults to true.falseturns the stage off. Handshake feature:preference_flatten_peaks. Protocol version is unchanged.Why this implementation
Putting a tiny peak weight in the same objective is what evcc had to abandon: the MIP gap swallows it. They freeze money, then break the tie. Same here, as a third stage after the existing service-then-economic split. That is the right place: service stays lexicographic, money stays lexicographic, peak shape is only a tie-break.
0.1 öre of cost slack was real money: at 50 öre/kWh it buys 2 W of peak reduction. That failed unique-export and arbitrage-spread tests. Slack is now 1e-8 öre (or 1e-12 of |cost|), which is solver noise, not a watt budget.
Boundaries and safety
model.py. This PR’smodel.pydelta is the post-economic hook only; rebase onto fix(mpc): surplus-only EV can take leftover PV while the home battery grid-charges #957 if they land together. Both also touchgo/internal/mpc/mpc.go/external_optimizer_test.go. fix(mpc): surplus-only EV can take leftover PV while the home battery grid-charges #957 has no reviews yet.Dual release
Old optimizer images omit the new fields; Core ignores unknown JSON today and understands the fields after this. Flattening only happens once the optimizer image includes this commit. Ship a new
ftw-optimizerimage with this Core.Verification
optimizer/.venv/bin/pytest -q optimizer/tests go test ./internal/mpc -count=1flatten_peaks: false(abs 1e-3 öre)single_slotand keep the economic planno_timeand does not start the second solveChecklist
model.py; this hook is post-economic only.