mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-10-29 09:11:57 +00:00
fix(genetic0): keep parameter arrays aligned with the start hour on DST days (#1455)
This commit is contained in:
@@ -304,7 +304,15 @@ class Genetic0OptimizationParameters(
|
|||||||
# Add forecast and device data
|
# Add forecast and device data
|
||||||
interval = to_duration(cls.config.optimization.genetic0.interval_sec)
|
interval = to_duration(cls.config.optimization.genetic0.interval_sec)
|
||||||
power_to_energy_per_interval_factor = cls.config.optimization.genetic0.interval_sec / 3600
|
power_to_energy_per_interval_factor = cls.config.optimization.genetic0.interval_sec / 3600
|
||||||
parameter_start_datetime = ems.start_datetime.set(hour=0, second=0, microsecond=0)
|
# GENETIC0 indexes the parameter arrays by wall-clock hour (the simulation
|
||||||
|
# starts at ``ems.start_datetime.hour``), while the arrays themselves are
|
||||||
|
# sampled in elapsed time. On a DST transition day local midnight is 23 or
|
||||||
|
# 25 elapsed hours away from the same wall-clock hour, so step back
|
||||||
|
# ``hour`` elapsed hours instead of setting the hour to midnight. This keeps
|
||||||
|
# array index ``ems.start_datetime.hour`` aligned with the start slot.
|
||||||
|
parameter_start_datetime = ems.start_datetime.set(second=0, microsecond=0).subtract(
|
||||||
|
hours=ems.start_datetime.hour
|
||||||
|
)
|
||||||
parameter_end_datetime = parameter_start_datetime.add(hours=cls.config.prediction.hours)
|
parameter_end_datetime = parameter_start_datetime.add(hours=cls.config.prediction.hours)
|
||||||
max_retries = 10
|
max_retries = 10
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
"""GENETIC0 parameter arrays must stay aligned with the start hour on DST days."""
|
||||||
|
|
||||||
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
import pendulum
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from akkudoktoreos.core.coreabc import get_ems
|
||||||
|
from akkudoktoreos.optimization.genetic0.genetic0params import (
|
||||||
|
Genetic0OptimizationParameters,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"start",
|
||||||
|
[
|
||||||
|
# Normal day: 23 elapsed hours since local midnight.
|
||||||
|
pendulum.datetime(2026, 10, 24, 23, tz="Europe/Berlin"),
|
||||||
|
# Fall back (CEST -> CET): 24 elapsed hours since local midnight.
|
||||||
|
pendulum.datetime(2026, 10, 25, 23, tz="Europe/Berlin"),
|
||||||
|
# Spring forward (CET -> CEST): 22 elapsed hours since local midnight.
|
||||||
|
pendulum.datetime(2026, 3, 29, 23, tz="Europe/Berlin"),
|
||||||
|
],
|
||||||
|
ids=["normal", "fall-back", "spring-forward"],
|
||||||
|
)
|
||||||
|
async def test_parameter_arrays_start_hour_aligned_on_dst_days(config_eos, start):
|
||||||
|
config_eos.merge_settings_from_dict(
|
||||||
|
{
|
||||||
|
"general": {"timezone": "Europe/Berlin"},
|
||||||
|
"prediction": {"hours": 48},
|
||||||
|
"optimization": {"genetic0": {"horizon_hours": 24}},
|
||||||
|
"feedintariff": {"provider": "FeedInTariffDvhubOnline"},
|
||||||
|
"elecfee": {"provider": None},
|
||||||
|
"devices": {
|
||||||
|
"max_batteries": 0,
|
||||||
|
"max_electric_vehicles": 0,
|
||||||
|
"max_inverters": 0,
|
||||||
|
"max_home_appliances": 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
get_ems().set_start_datetime(start)
|
||||||
|
ems_start = get_ems().start_datetime
|
||||||
|
assert ems_start.hour == 23
|
||||||
|
|
||||||
|
# The tariff is only published from the start slot on, as a live
|
||||||
|
# provider would do late in the evening.
|
||||||
|
prices = pd.Series(
|
||||||
|
np.full(24 * 4, 0.0001),
|
||||||
|
index=pd.date_range(ems_start, periods=24 * 4, freq="15min"),
|
||||||
|
)
|
||||||
|
array_starts = []
|
||||||
|
|
||||||
|
async def read_array(**kwargs):
|
||||||
|
array_starts.append(kwargs["start_datetime"])
|
||||||
|
return np.full(48, 1.0)
|
||||||
|
|
||||||
|
prediction = Mock(
|
||||||
|
update_data=AsyncMock(),
|
||||||
|
key_to_array=AsyncMock(side_effect=read_array),
|
||||||
|
key_to_raw_series=AsyncMock(return_value=prices),
|
||||||
|
)
|
||||||
|
with patch.object(Genetic0OptimizationParameters, "prediction", prediction):
|
||||||
|
parameters = await Genetic0OptimizationParameters.prepare()
|
||||||
|
|
||||||
|
assert parameters is not None
|
||||||
|
# Index ``start.hour`` of every array is the start slot.
|
||||||
|
assert array_starts
|
||||||
|
for array_start in array_starts:
|
||||||
|
assert array_start.add(hours=ems_start.hour) == ems_start
|
||||||
|
tariffs = parameters.ems.feed_in_tariff_per_wh
|
||||||
|
assert isinstance(tariffs, list)
|
||||||
|
assert np.isfinite(tariffs[ems_start.hour : ems_start.hour + 24]).all()
|
||||||
Reference in New Issue
Block a user