mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-10-16 11:01:56 +00:00
117 lines
4.3 KiB
Python
117 lines
4.3 KiB
Python
"""The genetic configuration is frozen for the duration of an optimization run.
|
|||
|
|
|
||
|
|
A client (e.g. an energy manager that shrinks the control horizon as the known
|
||
|
|
price horizon moves on during the night) may update the configuration while a
|
||
|
|
run is in progress. Genomes and device arrays are sized when the run starts, so
|
||
|
|
a mid-run change of horizon, tail or prediction hours must not reach the final
|
||
|
|
evaluation - before the snapshot it failed with
|
||
|
|
``IndexError: index 84 is out of bounds for axis 0 with size 84``.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from akkudoktoreos.config.config import ConfigEOS
|
||
|
|
from akkudoktoreos.core.cache import CacheEnergyManagementStore
|
||
|
|
from akkudoktoreos.core.coreabc import get_ems
|
||
|
|
from akkudoktoreos.optimization.genetic.genetic import GeneticOptimization
|
||
|
|
from akkudoktoreos.optimization.genetic.geneticparams import (
|
||
|
|
GeneticOptimizationParameters,
|
||
|
|
)
|
||
|
|
from akkudoktoreos.utils.datetimeutil import to_datetime
|
||
|
|
|
||
|
|
ems_eos = get_ems(init=True) # init once
|
||
|
|
|
||
|
|
HOURS = 24
|
||
|
|
|
||
|
|
|
||
|
|
def _setup(config_eos: ConfigEOS, horizon_hours: int) -> GeneticOptimizationParameters:
|
||
|
|
config_eos.merge_settings_from_dict(
|
||
|
|
{
|
||
|
|
"prediction": {"hours": HOURS},
|
||
|
|
"optimization": {
|
||
|
|
"genetic": {
|
||
|
|
"individuals": 20,
|
||
|
|
"generations": 10,
|
||
|
|
"tail_horizon_hours": 0,
|
||
|
|
"horizon_hours": horizon_hours,
|
||
|
|
"interval_sec": 3600,
|
||
|
|
}
|
||
|
|
},
|
||
|
|
"feedintariff": {"direct_marketing_enabled": True},
|
||
|
|
"devices": {
|
||
|
|
"max_batteries": 1,
|
||
|
|
"batteries": {
|
||
|
|
"battery1": {"device_id": "battery1", "grid_export_rates": [0.5, 1.0]}
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
)
|
||
|
|
ems_eos.set_start_datetime(to_datetime().set(hour=0, minute=0))
|
||
|
|
CacheEnergyManagementStore().clear()
|
||
|
|
return GeneticOptimizationParameters.model_validate(
|
||
|
|
dict(
|
||
|
|
ems={
|
||
|
|
"pv_prognose_wh": [0.0] * HOURS,
|
||
|
|
"strompreis_euro_pro_wh": [0.0003] * HOURS,
|
||
|
|
"einspeiseverguetung_euro_pro_wh": [0.0001] * 12 + [0.0009] * 12,
|
||
|
|
"preis_euro_pro_wh_akku": 0.0,
|
||
|
|
"gesamtlast": [200.0] * HOURS,
|
||
|
|
},
|
||
|
|
pv_battery={
|
||
|
|
"device_id": "battery1",
|
||
|
|
"capacity_wh": 10000,
|
||
|
|
"initial_soc_percentage": 100,
|
||
|
|
"min_soc_percentage": 0,
|
||
|
|
"max_charge_power_w": 5000,
|
||
|
|
},
|
||
|
|
inverter={
|
||
|
|
"device_id": "inverter1",
|
||
|
|
"max_power_wh": 10000,
|
||
|
|
"battery_id": "battery1",
|
||
|
|
},
|
||
|
|
ev=None,
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize("start_horizon,changed_horizon", [(20, 24), (24, 20)])
|
||
|
|
def test_horizon_change_during_run_does_not_reach_the_run(
|
||
|
|
config_eos: ConfigEOS, start_horizon: int, changed_horizon: int
|
||
|
|
):
|
||
|
|
parameters = _setup(config_eos, start_horizon)
|
||
|
|
optimization = GeneticOptimization(fixed_seed=42)
|
||
|
|
|
||
|
|
original_optimize = optimization.optimize
|
||
|
|
|
||
|
|
def optimize_then_change_config(*args, **kwargs):
|
||
|
|
result = original_optimize(*args, **kwargs)
|
||
|
|
# A client updates the configuration while the run is still going.
|
||
|
|
config_eos.merge_settings_from_dict(
|
||
|
|
{"optimization": {"genetic": {"horizon_hours": changed_horizon}}}
|
||
|
|
)
|
||
|
|
return result
|
||
|
|
|
||
|
|
optimization.optimize = optimize_then_change_config # type: ignore[method-assign]
|
||
|
|
|
||
|
|
solution = optimization.optimize_ems(parameters=parameters, start_hour=0, ngen=2)
|
||
|
|
|
||
|
|
# The run finished on the horizon it started with ...
|
||
|
|
assert len(solution.battery_grid_export_factor) == start_horizon
|
||
|
|
# ... and the new value is live for the next run.
|
||
|
|
assert config_eos.optimization.genetic.horizon_hours == changed_horizon
|
||
|
|
assert optimization._run_genetic_cfg is None
|
||
|
|
|
||
|
|
|
||
|
|
def test_snapshot_is_released_when_the_run_fails(config_eos: ConfigEOS):
|
||
|
|
parameters = _setup(config_eos, 24)
|
||
|
|
optimization = GeneticOptimization(fixed_seed=42)
|
||
|
|
|
||
|
|
def failing_optimize(*args, **kwargs):
|
||
|
|
raise RuntimeError("boom")
|
||
|
|
|
||
|
|
optimization.optimize = failing_optimize # type: ignore[method-assign]
|
||
|
|
with pytest.raises(RuntimeError):
|
||
|
|
optimization.optimize_ems(parameters=parameters, start_hour=0, ngen=1)
|
||
|
|
assert optimization._run_genetic_cfg is None
|
||
|
|
assert optimization._run_prediction_hours is None
|