Files
EOS/docs/_generated/configoptimization.md
T
Andreas 1c10ab83ad feat(optimization): concave terminal value for the energy left in the battery
The energy still stored when the horizon ends keeps its worth: it replaces
grid imports that are paid for afterwards. Crediting that with a single
price per kWh cannot describe it, because the value is not linear in the
amount stored. The first kWh replaces the most expensive hour that PV
cannot cover, the next one the second most expensive, and once every such
hour is served, further energy replaces nothing.

A scalar has to pick one slope for all of it. High enough for the first kWh
means hoarding a full battery; low enough for the last kWh means running it
empty by the end of the horizon - which is exactly what the previous default
of 0 EUR/kWh did.

terminal_value_mode = AUTO (the new default) builds the curve instead. There
is no forecast beyond the horizon, so its trailing window stands in for the
day that follows: residual load max(load - PV, 0) per slot, priced at its
import price, sorted and accumulated. LCOS is subtracted from every marginal
value so stored energy is not credited twice, and the tail beyond the
residual load is only credited when direct marketing allows an export. The
curve is built once per run; the search only interpolates on it.

The solution reports what a run used as terminal_value, curve included, so
the shape can be inspected instead of guessed. FIXED restores the previous
scalar behaviour.

In a 48 h scenario with two cheap slots at the end, AUTO keeps the battery
at 50 % and credits 3.85 EUR where FIXED with 0 EUR/kWh drains it to empty.
The stored optimization results move accordingly - the objective changed.
2026-09-04 10:37:48 +02:00

4.9 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.
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 optimization horizon. AUTO derives a concave value curve from the trailing horizon window and needs no configuration; 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 horizon window the AUTO terminal value 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": {
           "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": {
           "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
               }
           }
       }
   }