Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/study_cases/basic_pv.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This scenario adds a 5 kWp PV installation to the previous tutorial. No battery
| Optimization modes | perfect-optim (backtest), dayahead-optim |
| Cost function | profit |

This matches the default configuration shipped in `config_defaults.json` — no parameter changes required.
To enable PV in EMHASS, set `set_use_pv: true` (default is `false`) and configure your PV plant via `solar_forecast_kwp` (for the `solar.forecast` method) or one of the other `weather_forecast_method` options. The two deferrable loads use the default `nominal_power_of_deferrable_loads: [3000.0, 750.0]` from `config_defaults.json`.

## Perfect optimization (7-day historical backtest)

Expand Down
2 changes: 1 addition & 1 deletion docs/study_cases/dhw_walkthrough.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ rest_command:
{},
{
"thermal_config": {
"heating_rate": 4.0,
"heating_rate": 5.0,
"cooling_constant": 0.02,
"start_temperature": {{ states('sensor.dhw_tank_temperature') | float }},
"sense": "heat",
Expand Down
19 changes: 14 additions & 5 deletions docs/study_cases/good_practices.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,24 @@ Source: `optimization.py` constraints `min_energy = battery_minimum_state_of_cha

If your downstream automation or display does need a different convention (e.g. percentage of *usable* range), apply the transform yourself in a HA template. Don't assume EMHASS already did it.

## 4. `soc_init` and `soc_final` defaults are forgiving
## 4. `soc_init` and `soc_final` runtime semantics

For day-ahead optimization, setting `soc_final` to a desired end-of-day SOC (e.g. 0.6) ensures you don't end the day empty. EMHASS uses `battery_target_state_of_charge` (default 0.6) as the fallback when neither `soc_init` nor `soc_final` is passed.
The two SOC parameters behave differently across optimization actions, so it pays to know exactly what EMHASS does with each.

For rolling MPC, the often-cited concern is that a fixed `soc_final` reserves capacity at the trailing edge of every horizon and biases the optimizer toward conservative mid-day behavior. This is real, but the EMHASS code already handles the common case: when you pass only `soc_init` at runtime, EMHASS auto-sets `soc_final = soc_init`. So **passing only `soc_init`** in your MPC payload is the standard rolling-MPC recipe.
**`naive-mpc-optim`** reads `soc_init` and `soc_final` from `runtimeparams` independently. If one is missing, EMHASS substitutes `battery_target_state_of_charge` (default `0.6`) for that value alone; it does **not** mirror the passed value onto the missing one. So passing only `soc_init = 0.45` yields `soc_init = 0.45, soc_final = 0.6`, which still imposes a terminal-SOC constraint at every solve. Always pass both explicitly.

Pass `soc_final` explicitly only when you have a hard end-of-horizon target (e.g. "must be at 60% by tomorrow 06:00 to absorb morning PV"). In that case you may also want to extend the horizon so the constraint sits at the actual deadline, not 24 h after the current re-run.
**`dayahead-optim`** and **`perfect-optim`** do not read `soc_init` or `soc_final` from `runtimeparams` at all. Both fall back to `battery_target_state_of_charge`. Use day-ahead when that single value is the right answer for both ends of the horizon, and switch to MPC when you need runtime control of either.

A common new-user trap is the opposite: starting with very low actual SOC where `soc_init` is below `battery_minimum_state_of_charge`. The optimization becomes infeasible because the initial state already violates a constraint. See Section 5 below (item 4) for the full triage and [discussion #359](https://github.com/davidusb-geek/emhass/discussions/359) for the canonical thread.
### Practical recipes for rolling MPC

Two patterns work well in production. Both pass *both* values explicitly per MPC call, just with different `soc_final`:

- **`soc_final = soc_init`** (pass current measured SOC for both). The trailing-edge constraint becomes neutral: the battery's end-of-horizon SOC is allowed to land wherever it started, so the optimizer is free to use it inside the horizon. Good for systems with no hard end-of-day target.
- **`soc_final = 0`** (or `battery_minimum_state_of_charge` if you prefer to stay above the floor). With a 48-step (24 h) rolling horizon and re-runs every 30 min, the deadline at step 48 is always 24 h ahead. Each run replaces the schedule before that deadline ever arrives, so the trailing target is never actually reached. In practice the optimizer behaves the same as the neutral-edge case, just expressed differently. Useful when your runtime layer wants a single static `soc_final` value rather than tracking the live sensor.

If you do have a real end-of-horizon deadline (for example "must be at 60% before tomorrow 06:00 to absorb morning PV"), pass that target as `soc_final` and extend `prediction_horizon` so the deadline sits at the actual point in time, not at the trailing edge of a fixed 24 h window.

A common new-user trap is starting with very low actual SOC where `soc_init` is below `battery_minimum_state_of_charge`. The optimization becomes infeasible because the initial state already violates a constraint. See Section 5 below (item 4) for the full triage and [discussion #359](https://github.com/davidusb-geek/emhass/discussions/359) for the canonical thread.

## 5. `optim_status: Infeasible` triage order

Expand Down
2 changes: 1 addition & 1 deletion docs/study_cases/mpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ In addition to the parameters from [Basic — PV + Battery](basic_pv_battery.md)
For the full list of runtime keys, see [Passing data](../passing_data.md).

```{note}
`soc_init` and `soc_final` defaults: EMHASS already defaults `soc_final` to `soc_init` (and vice versa) when only one is passed at runtime; if neither is passed, both fall back to `battery_target_state_of_charge` from the static config. So for typical rolling MPC, passing only `soc_init` is sufficient: the optimizer will not impose a terminal-SOC bias inside the horizon. Pass `soc_final` explicitly only when you have a hard end-of-horizon target (e.g. ensure 60% before tomorrow morning).
`soc_init` and `soc_final` are read from `runtimeparams` independently. If one is omitted, EMHASS substitutes `battery_target_state_of_charge` (default `0.6`) for that single value; it does **not** mirror the passed value onto the missing one. Always pass both explicitly. Two production-tested rolling-MPC patterns: `soc_final = soc_init` (current SOC for both, neutral trailing edge) or `soc_final = 0` (with a 24 h horizon re-run every 30 min, the deadline is always 24 h away and never reached, so this behaves the same in practice). For a hard end-of-horizon target, pass that value and extend `prediction_horizon` so the deadline sits at the real point in time.
```

## Run
Expand Down
3 changes: 2 additions & 1 deletion docs/study_cases/reference_configs.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ The most common modern setup: ~150 m² home, 8–12 kWp PV, 10–15 kWh battery,
set_use_pv: true
solar_forecast_kwp: 10
optimization_time_step: 30
prediction_horizon: 48 # used by MPC
# Note: prediction_horizon is a runtime parameter (passed in the MPC payload),
# not a static config field. With this 30-min step a 24 h horizon is 48 timesteps.

set_use_battery: true
battery_nominal_energy_capacity: 12000 # Wh (= 12 kWh)
Expand Down
Loading