Files
EOS/docs/_generated/configoptimization.md
T
Andreas a2f4ef6f54 feat(optimization): split the control horizon from the forecast tail
The optimizer treated the end of `optimization.horizon_hours` as the end of the
world: energy left in the battery there was worth a single configured price per
kWh, so it either dumped the battery into the last hours or hoarded it,
depending on that one number.

The horizon is now two spans. `horizon_hours` still receives every control
command. The new `optimization.tail_horizon_hours` (default 48 h) is a pure
lookahead that never produces a command. In AUTO terminal-value mode a
deterministic dynamic program solves that tail backwards on a 101-point SoC
grid using the production battery and inverter models - SoC bounds, power caps,
conversion losses, configured charge and export rates, direct-marketing
permission and LCOS on delivered DC energy - and the existing AUTO proxy
supplies the continuation value at the tail end. Genetic fitness reads the
resulting curve. `tail_horizon_hours: 0` restores the plain proxy at the control
end, FIXED is unchanged.

The forecast budget is reported, never enforced by refusal: a tail that does not
fit is shortened to what the forecast covers and reported as
`effective_tail_hours`, and a control horizon that does not fit is warned about
at configuration time and rejected by the optimizer at run time, which knows
which series ran out. `prediction.hours` defaults to 72 so the new defaults fit
out of the box; existing shorter configurations keep starting.

Control arrays and warm-start genomes now begin at the run timestamp rather than
midnight, flagged by `controls_start_at_now` so the adapters still read older
solutions. `forecast_interval_seconds` declares the resolution of shortened
native quarter-hour inputs.

Required forecasts are no longer silently replaced by demo providers. A missing
PV, price, load, feed-in or weather forecast used to rewrite the configured
provider and retry, so a run could quietly optimize against invented data.
Missing values now stay missing, and provider values are held only within their
own source interval instead of being extended indefinitely.

Also fixes a config update that could leave EOS half-updated: the merged
candidate is validated before the singleton is reinitialized.

Four provider tests that hard-coded the old 48 h prediction default are rewritten
to derive their expectations from the configured horizon.
2026-09-09 07:56:38 +02:00

5.2 KiB

General Optimization Configuration

:::{table} optimization :widths: 10 20 10 5 5 30 :align: left

Name Environment Variable Type Read-Only Default Description
algorithm EOS_OPTIMIZATION__ALGORITHM str rw GENETIC The optimization algorithm. Defaults to GENETIC
genetic EOS_OPTIMIZATION__GENETIC GeneticCommonSettings rw required Genetic optimization algorithm configuration.
horizon int ro N/A Number of optimization steps.
horizon_hours EOS_OPTIMIZATION__HORIZON_HOURS int rw 24 The general time window within which the energy optimization goal shall be achieved [h]. Defaults to 24 hours.
interval EOS_OPTIMIZATION__INTERVAL int rw 3600 The optimization interval (slot length) [sec]. The genetic optimizer supports 3600 (1 hour) and 900 (15 min); other values fall back to 3600. Defaults to 3600 seconds (1 hour).
keys list[str] ro N/A The keys of the solution.
tail_horizon_hours EOS_OPTIMIZATION__TAIL_HORIZON_HOURS int rw 48 Forecast lookahead after the control horizon [h]. No tail commands are issued. Set 0 to disable.
terminal_value_euro_per_kwh EOS_OPTIMIZATION__TERMINAL_VALUE_EURO_PER_KWH float rw 0.0 Value assigned to usable battery energy remaining at the end of the optimization horizon [EUR/kWh]. This terminal value is independent of the battery LCOS. Only used with terminal_value_mode = FIXED. Defaults to 0 EUR/kWh.
terminal_value_mode EOS_OPTIMIZATION__TERMINAL_VALUE_MODE <enum 'TerminalValueMode'> rw AUTO How to value the energy left in the battery at the end of the control horizon. AUTO solves the forecast tail with an AUTO continuation proxy at its end (or only the proxy if tail is zero); FIXED uses 'terminal_value_euro_per_kwh'. Defaults to AUTO.
terminal_value_window_hours EOS_OPTIMIZATION__TERMINAL_VALUE_WINDOW_HOURS int rw 24 Length of the trailing window at the effective tail end the AUTO continuation curve is derived from [h]. One day covers a full load and PV cycle. Defaults to 24 hours.
visualize_pdf EOS_OPTIMIZATION__VISUALIZE_PDF bool rw True Generate the PDF visualization after each optimization run. Disable for headless setups (e.g. Node-RED integration) to save several seconds per run. Defaults to True.
:::

Example Input

   {
       "optimization": {
           "tail_horizon_hours": 48,
           "horizon_hours": 24,
           "interval": 3600,
           "algorithm": "GENETIC",
           "visualize_pdf": true,
           "terminal_value_mode": "AUTO",
           "terminal_value_euro_per_kwh": 0.0,
           "terminal_value_window_hours": 24,
           "genetic": {
               "individuals": 400,
               "generations": 400,
               "seed": null,
               "penalties": {
                   "ev_soc_miss": 10
               }
           }
       }
   }

Example Output

   {
       "optimization": {
           "tail_horizon_hours": 48,
           "horizon_hours": 24,
           "interval": 3600,
           "algorithm": "GENETIC",
           "visualize_pdf": true,
           "terminal_value_mode": "AUTO",
           "terminal_value_euro_per_kwh": 0.0,
           "terminal_value_window_hours": 24,
           "genetic": {
               "individuals": 400,
               "generations": 400,
               "seed": null,
               "penalties": {
                   "ev_soc_miss": 10
               }
           },
           "keys": [],
           "horizon": 24
       }
   }

General Genetic Optimization Algorithm Configuration

:::{table} optimization::genetic :widths: 10 10 5 5 30 :align: left

Name Type Read-Only Default Description
generations Optional[int] rw 400 Number of generations to evolve [>= 10]. Defaults to 400.
individuals Optional[int] rw 300 Number of individuals (solutions) in the population [>= 10]. Defaults to 300.
penalties dict[str, Union[float, int, str]] rw required Penalty parameters used in fitness evaluation.
seed Optional[int] rw None Random seed for reproducibility. None = random.
:::

Example Input/Output

   {
       "optimization": {
           "genetic": {
               "individuals": 300,
               "generations": 400,
               "seed": null,
               "penalties": {
                   "ev_soc_miss": 10
               }
           }
       }
   }