Repository navigation
Replies: 9 comments 4 replies
|
Two things from adjacent work, in case they save you a round. On Ask 2, On the adapter. You build Nothing above argues against the shape, the price-coordinated split is a good fit for the evcc case, and 0.09% worst case against the MILP on EMHASS's own models is a strong number. |
|
I Ameet, thanks for opening this and for your contribution. I still need to take the time to review the proposed PR. Our primary guiding principle here is safety for the existing user base. Because this is an opt-in feature, we need absolute certainty that it introduces zero regressions or side effects for users sticking with the default LesIT brought up excellent technical hurdles that align exactly with this safety goal. I think we can proceed with the federated backend proposal by checking that a strictly side-effect-free dry_run implementation and isolating adapter logic to prevent merge conflicts with concurrent updates. |
|
Thanks Les — yes, this is directly related to the query/scenario work I was exploring in #931. The main immediate value in this proposal is definitely the dry_run, independently of whether we ever adopt the federated backend. I run EMHASS as a rolling 5-minute MPC and use its current plan/last-run state as part of the production evidence/control chain. We also do quite a lot of counterfactual work around forecasts, battery modelling, terminal SOC, tariffs and other controller settings. A native way to submit the exact same EMHASS optimisation request and get the resulting plan back without replacing the authoritative production plan would remove a fair amount of special-purpose shadow/replay machinery. I agree with the distinction Les makes though: for that use case, “does not write files or publish entities” is necessary but not quite sufficient. I would want dry_run to leave the production optimisation context unchanged as well — in particular no mutation/eviction of the live OptimizationCache, no change to the warm-start state used by the next production solve, and no change to /api/v1/plan, /api/v1/last-run, continual-publish sources or other persistent state. In other words, ideally: same request + dry_run -> real EMHASS solve + returned opt_res, but no observable change to the next normal production solve other than CPU time That would make it genuinely useful as a scenario/query primitive, including for an external coordinator. For my installation the coordinated backend itself is a later question because I use EMHASS’s hybrid-inverter model and consume P_hybrid_inverter downstream for the Sigenergy control path, while the current federated scope falls back on hybrid configurations. If that scope expands later, I think preserving the existing first-action/output semantics (P_grid, P_batt, P_hybrid_inverter, SOC etc.) will matter at least as much as horizon-level objective parity for MPC users. I would strongly support dry_run as a standalone capability if splitting the PR makes that easier to review/land. The cache/process-state isolation Les identified is the critical thing I would want down before relying on it against a live EMHASS instance. |
|
Thanks @LesIT1 and @MMicieli and @davidusb-geek . I believe the file side was already covered on the draft PR: a dry run against a live instance changes nothing under /data except the action log. The OptimizationCache wasn't, thanks for reporting this bug. On a hit, the dry run reconfigured and re-solved the live object itself. As of b122c34, a dry run builds a private Optimization and never reads or writes the cache, so the next live solve sees the same object, key, configuration and warm-start values. |
|
Thanks @ameetdesh — I had another look through the latest changes after your response and the additional hardening in The I did notice a few remaining things that may be worth considering as the RFC develops. None of these are intended as a redesign of the proposal, just observations from trying to think through how an external consumer could safely depend on the interface. The generated OpenAPI currently appears to describe I also wondered whether The other area I think may become important is distinguishing a usable coordinated plan from a proven optimiser status. Related to that, some lightweight backend provenance could be useful. For example, knowing that For I also had a look at Overall, I think the latest changes answer the main concerns I raised around Thanks again for taking the earlier feedback on board. |
|
@MMicieli regarding the earlier fallback to cvxpy for hybrid inverter case, this use case is now supported on 0.2.6 (thanks for pointing this gap). Dantzig-Wolfe backend now supports hybrid inverter limits as well as any number of group limits (e.g. for circuit breakers). The side effect is that the prices are now defined at each sub-meter, and thus reflect local price for violating that limit, similar to the locational marginal price. Please see home-energy-optimizer 0.2.6 and section "A four-DER house". I also created a single html wasm based UI to visualize this here.
|
|
I've completed an off-box qualification of the dry_run implementation in PR #1158, including additional adversarial testing beyond the existing upstream suite. Overall, the results are encouraging. The private Optimization instance, cache isolation and preservation of the existing plan/last-run state appear to provide a sound foundation for a native, non-persisting optimisation interface. I also appreciate the subsequent improvements to the coordinated backend, particularly hybrid-inverter support, local marginal pricing, backend provenance and the clearer distinction between feasible and proven-optimal solutions. Independent validation Testing was performed in an isolated WSL2 environment against PR #1158 at 2f1bf5f.
Cold-start live and dry-run plans matched across naive-mpc-optim, dayahead-optim and perfect-optim. The tested optimisation paths also preserved OptimizationCache, result files, /api/v1/plan and /api/v1/last-run, including across the exercised failure and restart scenarios. Three findings may be worth considering before finalising the dry_run contract. 1. Concurrent requests can interfere with HTTP success/failure reporting The web action handler currently uses check_file_log() to inspect a shared action log when determining the request's final HTTP status. With a healthy request overlapping a failing optimisation, the healthy request incorrectly returned a non-200 response in approximately 17–20 of 40 trials, depending on the failure scenario. The reverse also occurred: a failing request was occasionally reported as successful. Importantly, the same behaviour reproduced with ordinary live/live requests, confirming this is a pre-existing request-isolation issue rather than something introduced by dry_run. Nevertheless, a future scenario evaluator or external coordinator could exercise infeasible trial inputs frequently, making this interaction more visible. Would it make sense to derive each request's HTTP outcome directly from its own execution result, rather than from errors in a shared log? This could be addressed independently of the coordinated backend. 2. Forecast-cache writes occur outside the optimisation persistence guards The optimisation-result and warm-start isolation tests passed, but input acquisition has separate persistence paths. I demonstrated that Forecast.get_weather_forecast() can update cached-open-meteo-forecast-b.json when invoked with an input context marked dry_run=True, using mocked HTTP responses. This was a focused forecast-function test, not proof that every normal dry-run HTTP request refreshes this cache. Other input-cache paths have been identified through source inspection but not all executed end-to-end. The relevant question is whether dry_run promises:
For reproducible counterfactual analysis, the stronger contract would be preferable, particularly where hypothetical queries share an EMHASS instance with production optimisation. Would you consider either enforcing this boundary during input acquisition or documenting the narrower guarantee explicitly? 3. dry_run on unsupported actions The current documentation correctly describes dry_run as an optimisation-action parameter. However, publish-data still performs publication when supplied dry_run=true. My isolated test observed nine post_data calls with and without the flag. This is not a failure of the supported optimisation dry-run paths. It is an API misuse risk: a caller could reasonably assume that an explicitly supplied dry_run=true prevents side effects. One possible improvement would be to reject dry_run=true on actions that do not support it, rather than silently ignoring the parameter. Suggested direction dry_run as a standalone EMHASS capability, independently of whether an installation adopts Dantzig–Wolfe coordination. For rolling five-minute MPC use case, the main benefit is being able to submit a hypothetical optimisation and retrieve its plan without affecting the next ordinary solve. A native dry_run interface, combined with externally retained input provenance and appropriately matched economic scoring, should also support useful forecast-value and counterfactual experiments without requiring a separate optimiser or a new native regret-calculation framework. My overall assessment is positive: the core optimisation-state isolation appears robust under the tested scenarios. The remaining questions primarily concern request-level error isolation, the precise scope of persistent input-state protection, and explicit API behaviour for unsupported actions. These tests were entirely off-box, with no production EMHASS, Home Assistant or physical control interaction. The coordinated backend's remote-participant paths and real Home Assistant input path were not independently exercised. |
|
+1 for splitting dry_run into its own PR. I'd use it straight away. My setup is a rolling naive-mpc-optim every 15 minutes driving a home battery, with the plan read back from /api/v1/plan. The grid import cap is enforced outside EMHASS by my own controller, so what I want from dry_run is shadow evaluation: the live payload with maximum_power_from_grid as a per-step list that leaves room for an EV charge, to see whether the plan still reaches soc_target and what the cap costs, before deciding whether to pass the cap into EMHASS at all. Right now the only clean way to do that is a second instance kept in step by hand. On MMicieli's three findings, from where I sit: Finding 3 matters most to me. Today an unknown runtime key is just ignored, so a client that sends dry_run: true to a release without the feature gets a normal solve that replaces the live plan. A line in the docs saying which release first honours it would let callers gate on version. Rejecting dry_run on actions that don't support it is the same idea on the new side: a caller should never get a silent publish after asking for a dry run. Finding 2 doesn't bite me, since PV, load and price all go in as lists, but I'd still like the guarantee written down. Optimisation-state isolation with the forecast caches called out as not covered is fine, as long as it's explicit. Finding 1 I avoid by serialising every EMHASS call on my side, and the dry-run response carries optim_status on each row, so I don't have to trust the HTTP code. It's still a real bug for anyone running a coordinator against a shared instance. The wrapper's False already maps to a 400; what leaks is the scan of the shared action log for ERROR lines afterwards. If nobody's on it I'm happy to put up a separate PR that scopes error capture to the request and reads optim_status from its own result. |
|
Another +1 for splitting The use case is concrete. The registry sidecar from RFC 0001 folds the active demands into one Two of the points already raised matter to that consumer in particular, and I would put them in the contract rather than the docs alone: a release that does not know the flag must not run a live solve when asked for a dry one (so a caller can gate on a version or capability signal, as LesIT1 said), and actions that cannot honour the flag should reject it rather than ignore it (MMicieli's finding 3). The narrower isolation guarantee, optimisation state only with forecast caches called out as not covered, is fine for this use if it is written down. The revised RFC 0001 states this dependency and the relation to this proposal explicitly: https://github.com/OptimalNothing90/emhass-contributions/blob/main/rfcs/0001-shared-plan-registry.md. |

Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
An opt-in EMHASS backend, off by default, that plans each device as its own subproblem, coordinates the plans by prices at the meter, and splits the saving among the devices. Nothing changes for anyone who leaves it off.
Why
Homes now run several optimisers side by side: EMHASS for batteries and loads, evcc for the car, vendor apps for heat pumps. Each plans against the tariff alone.
Planning each device as its own subproblem, coordinated at the meter, answers all five.
What
All three parts below are in one draft PR, #1158, to review as one piece or split as you prefer.
Ask 1: an opt-in coordinator backend.
optimization_backend:cvxpy(default, today's MILP),dantzig_wolfeoradmm.participants(optional): groups of devices, each solved byemhass(its own model, the default) orhome_energy_optimizer.perform_optimization. The coordinator, the interface and the settlement come fromhome-energy-optimizerthroughpip install emhass[federated](MIT, needs only numpy).opt_rescolumns, plusfed_meter_price,fed_lower_boundandfed_gap. Anything not yet supported falls back to the default solver, with one log line.Ask 2:
dry_run. A runtime flag on the optimisation actions: solve at the given prices, return the plan in the response, and write nothing. An outside coordinator can then include EMHASS as one participant without overwriting the live plan.How it works. Each participant answers one query: its cheapest plan when its energy is priced at the coordinator's meter price. It returns the plan, its private cost (everything except the bill: discomfort, wear, a missed goal, less revenue earned elsewhere) and its state. The interface is a versioned JSON Schema (query, answer), so EMHASS's models, other solvers and remote agents look alike to the coordinator. Dantzig–Wolfe mixes the proposed plans in a small LP that holds the meter, updates the prices, and returns a lower bound (theory).
flowchart TB subgraph A["1. All in EMHASS"] CA["Coordinator<br/>(EMHASS backend)"] --> A1["Battery<br/>EMHASS model"] CA --> A2["Deferrable loads<br/>EMHASS model"] end subgraph B["2. Mixed solvers"] CB["Coordinator<br/>(EMHASS backend)"] --> B1["Battery<br/>DP solver"] CB --> B2["Deferrable loads<br/>EMHASS model"] end subgraph C["3. EMHASS as one participant"] CC["Outside coordinator"] --> C1["EMHASS<br/>(all its devices, dry_run)"] CC --> C2["EV<br/>evcc"] CC --> C3["Heat pump<br/>vendor solver"] endRevenue sharing. Each participant is first reimbursed its change in private cost. The rest is split between solar and the participants by an Owen value, then among the participants by Aumann–Shapley. This needs one extra plan, and came within 0.46 currency units of the exact Shapley value on six test cases. Each player's share comes back as an
opt_rescolumn,fed_share_<player>.Evidence. On EMHASS's own device models, the coordinated plans are very close to its MILP. Across 25 runs the cost differs by 0.016% on average and by at most 0.09%, with the MILP run at
lp_solver_mip_rel_gap: 0.The coordinated plan takes 0.2–1.6 s, including the extra plan the shares need, against 0.1–0.6 s for the MILP.
scripts/federated_benchmark.pyin the PR reproduces these runs. A battery with ramp limits or stress costs must be answered by EMHASS as a black box, which is weaker: 94–100% of the MILP's savings, in 1.2–14 s. Throughdayahead-optimand publish, both backends write the same files and entities.Compatibility. Off by default. Parameters and columns are only added, there is no new required dependency, no EV or evcc code in core (#789), and no restructuring of
optimization.py. The adapter builds EMHASS'sOptimizationthrough its constructor, which is not a public contract, so the backend's tests should run in one CI job.Plan. After your feedback, the draft PR is finished or split as you prefer. Later, if wanted, the coordinator, the interface and the settlement can move into EMHASS as composable modules (step 2).
Questions for the maintainers
optimization_backendandparticipantsthe right names, withcvxpy,dantzig_wolfeandadmmas values?opt_rescolumns, or a table beside/api/v1/plan?admmcan use EMHASS's models too?All reactions