-
-
Notifications
You must be signed in to change notification settings - Fork 6
milp optimization
The MILP solver (planner/milp_optimizer.py) finds the globally optimal battery charge/discharge schedule using scipy's HiGHS linear programming solver. It is the primary planner — heuristic candidates are generated alongside it for benchmarking and fallback, but when scipy is available the MILP solution is preferred.
flowchart TD
A[Engine passes slots with populated prices / PV / consumption]
B[Identify future active slot indices past vs fixed-zero]
C[Build per-slot data arrays: p_imp, p_exp, net_load, base_load, pv_avail]
D{EV configs provided?}
E[Rebuild net_load without pre-computed EV planned loads]
F[Keep fixed EV planned load in net_load]
G[Build objective vector c_obj: import cost, export revenue, cycle cost, conversion loss, terminal-SoC credit, SoC penalties]
H[Build equality constraints A_eq: energy balance per slot inc. EV charger load]
I[Build inequality constraints A_ub: SoC recurrence soft bounds, mutual exclusion, cycle cost auxiliary m >= ec/ed, EV cumulative SOC, EV deadline target]
J[Build variable bounds: ec, ed capped; pv fixed to actual surplus; penalties >= 0]
K[linprog method = highs, timeout = 2s]
L{Solution found?}
M[Decode solution: ec, ed, gi, ge, pv, m, penalties, EV charging]
N[Write recommendations to output slots BatteriesChargeGrid, BatteriesChargeSolar, BatteriesDischargeMode, ForceBatteriesDischarge]
O[Compute penalty violation diagnostics]
P[Return slots and diagnostics]
Q[Return None: solver failed or no future slots]
A --> B --> C --> D
D -->|Yes| E --> G
D -->|No| F --> G
G --> H --> I --> J --> K --> L
L -->|Yes| M --> N --> O --> P
L -->|No| Q
For each slot t ∈ 0…n-1 the LP variable vector x contains eight decision variables:
| Offset | Variable | Name | Description | Bounds |
|---|---|---|---|---|
0 |
ec[t] |
ec_off |
Energy charged and stored in battery this slot (kWh) | [0, max_charge_per_slot] |
n |
ed[t] |
ed_off |
Energy discharged from battery this slot (kWh) | [0, max_discharge_per_slot] |
2n |
gi[t] |
gi_off |
Grid import this slot (kWh) | [0, ∞) |
3n |
ge[t] |
ge_off |
Grid export this slot (kWh) | [0, ∞) |
4n |
pv[t] |
pv_off |
PV surplus available in slot t (kWh) |
[pv_avail[t], pv_avail[t]] (fixed) |
5n |
m[t] |
m_off |
Auxiliary variable ≥ max(ec[t], ed[t]) for cycle cost (kWh) | [0, ∞) |
6n |
s_max_pen[t] |
s_max_off |
SoC upper penalty — kWh by which state of charge exceeds usable_kwh
|
[0, ∞) |
7n |
s_min_pen[t] |
s_min_off |
SoC lower penalty — kWh by which state of charge drops below 0 | [0, ∞) |
The state of charge soc[t] is not an explicit variable — it is derived from the forward recurrence:
Penalty variables s_max_pen and s_min_pen prevent infeasibility when the initial SoC lies outside [0, usable_kwh]. Their objective coefficient is extremely high (max(p_imp) × 100), so they are only used when the initial state is physically out of bounds.
When one or more active EVs are provided, the variable vector expands to:
where E is the number of active EVs.
| Offset | Variable | Name | Description | Bounds |
|---|---|---|---|---|
8n + i·n |
evN_c[t] |
EV N DC-side charge per slot (kWh) | [0, evN.max_charge_per_slot] |
|
8n + E·n + i |
evN_pen |
EV N deadline target slack (kWh shortfall) | [0, ∞) |
The EV charger AC load entering the energy balance equation is evN_c[t] / charger_efficiency.
Plus EV deadline penalties (undiscounted — deadline is a hard commitment):
Where:
| Symbol | Description |
|---|---|
| Time discount per slot: |
|
| Grid import price (currency/kWh) | |
Grid export price (currency/kWh), clamped to 0 when below min_export_price
|
|
| Battery cycle cost per kWh: |
|
| Charge-side loss fraction: |
|
| Discharge-side loss fraction: |
|
| Terminal-SoC replacement price (currency/kWh), from the engine | |
| SoC penalty cost: |
|
| EV deadline penalty for EV v: |
For each slot
-
base_load[t]=$\max(\mathrm{net_load}[t], 0)$ — demand the grid/battery must satisfy (kWh) -
net_load[t]=avg_house_consumption[t] - solcast_pv_estimate[t](when EV co-optimisation active) -
pv_avail[t]=$\max(-\mathrm{net_load}[t], 0)$ — PV surplus fixed to thepv[t]variable bounds - EV charger efficiency re-scales DC-side charge to AC grid/PV load
SoC upper bound (soft):
SoC lower bound (soft):
Mutual exclusion — no simultaneous charge + discharge:
Cycle cost auxiliary — forcing
EV cumulative SoC upper bound (per EV v):
EV deadline target (soft, per EV v):
Where
After solving, the solution is decoded into slot recommendations:
flowchart TD
A[LP solution: ec, ed, gi, ge]
B{ec > threshold AND ed > threshold?}
C[Numerical tolerance conflict: resolve by net profit]
D{Round-trip profitable?}
E[Keep ec, zero ed]
F[Zero both]
G{ec > threshold?}
H{PV surplus available?}
I[BatteriesChargeSolar]
J[BatteriesChargeGrid]
K{ed > threshold?}
L{Grid export > 0 AND price >= min_export_price?}
M[ForceBatteriesDischarge]
N[BatteriesDischargeMode]
O[Write EV charge decisions]
P[Recompute estimated_net_consumption and estimated_cost per slot]
Q[Compute penalty violation diagnostics]
A --> B
B -->|Yes| C --> D
D -->|Yes| E --> G
D -->|No| F --> G
B -->|No| G
G -->|Yes| H
H -->|Yes| I --> O
H -->|No| J --> O
G -->|No| K
K -->|Yes| L
L -->|Yes| M --> O
L -->|No| N --> O
K -->|No| O
O --> P --> Q
| Field | Source |
|---|---|
ev_planned_load_kwh |
AC load added when base_load_includes_ev is False
|
ev_accounted_load_kwh |
AC load when already captured in house consumption |
ev_total_planned_load_kwh |
Total AC load (sum of planned + accounted) |
ev_charger_calculated_power |
Target AC power (W) for primary EV |
ev_second_charger_calculated_power |
Target AC power (W) for second EV |
- Linear relaxation: Binary charge/discharge flags are relaxed to continuous because the mutual-exclusion constraint and per-slot power caps already prevent simultaneous charge + discharge in the optimal solution.
- Deterministic inputs: All forecasts (prices, PV, load) are treated as known with certainty — no stochastic programming.
-
Cycle cost proxy: The
m[t] = max(ec[t], ed[t])formulation counts the larger of charge or discharge per slot, matching the 2× denominator in the cycle cost formula. -
Time discount: The objective uses exponential discounting with
time_discount_rate^hours_aheadto match the selector's discounted score. -
Export price clamping: Negative export prices and prices below
min_export_priceare clamped to 0 before solving, reflecting physical inverter behaviour. -
Terminal-SoC credit: Undiscounted in the objective, matching the cost function's
terminal_soc_value.
| Parameter | Value | Rationale |
|---|---|---|
| Method | highs |
scipy's HiGHS is the only supported LP method |
| Timeout | 2.0 s | Covers 192-slot (768+ variable) problems where preprocessing reaches 200-400 ms |
pv[t] bounds |
(pv_avail[t], pv_avail[t]) |
Fixed — PV surplus is not chosen by the LP |
If scipy is unavailable, usable_kwh ≤ 0, or the solver fails (crash, timeout, or non-success status), solve_milp() returns None. The engine silently drops the MILP candidate and the heuristic candidates compete as normal. Pickup is be measured via the hsem_plan_origin metric: milp when the LP succeeds, rule_based otherwise.
- Home — User-facing overview: features, FAQ, working modes, battery schedules, excess export, consumption sensors
- Battery Charging Economics — How to calculate the minimum charging price for a battery schedule
- Architecture Overview — System context, layered architecture, module map, planning pipeline
- Planner Specification — Normative — all planner invariants, rules, and constraints
- Planner Technical Guide — How the planner works with worked examples
- Cost Function Math — Complete mathematical formulation of the 8-term cost function
- Energy Accounting — Physical energy flow model, SoC simulation, efficiency math
- Candidate Generation — How candidates are generated, assumptions, partial-SoC
- MILP Optimization — Full LP formulation, variable layout, constraints, and solver pipeline
- Consumption Prediction — Weighted-average model, IQR outlier detection, spike suppression
- Safety Modes — Degraded mode, read-only gate, write-verify applier, runtime resolver
- Price Scaling — EDS price scaling, eds_share conversion factor
- Services Reference — All 4 HSEM services with examples
- Sensors Reference — Complete entity reference: all sensor, select, switch, number, and time entities
- Dashboard Setup — Step-by-step ApexCharts dashboard with full YAML, layout reference, and troubleshooting
- Config Flow Reference — Every config/options flow step and field
- EV Charge Plan Setup — EV planned load configuration guide
- EV Surplus Charging Automation — Wire your physical EV charger (go-e, Easee, Zaptec) to follow HSEM surplus recommendations
- EV Optimal Charging Template — Legacy Home Assistant template sensor for cost-optimal EV charging
- Forecast Accuracy Tracking — Forecast vs actual tracking system
- Huawei Entities — Canonical HA entity ID reference
- Troubleshooting Guide — Diagnose and fix common problems: missing data, wrong prices, write failures, battery behaviour
- Quality Checks — Static quality tools and CI configuration