fix: optimization fail after restart (#1007)
Some checks failed
Bump Version / Bump Version Workflow (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
docker-build / platform-excludes (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
Run Pytest on Pull Request / test (push) Has been cancelled
docker-build / build (push) Has been cancelled
docker-build / merge (push) Has been cancelled
Close stale pull requests/issues / Find Stale issues and PRs (push) Has been cancelled

Fix documentation for the loadforecast_power_w key.

Fix documentation to explain the usage of import file/ JSON string to
primarily initialise prediction data.

Fix code scanning alert no. 6: URL redirection from remote source

Enable to automatically save the configuration to the configuration file
by default, which is a widespread user expectation.

Make the genetic parameters non optional for better pydantic compliance.

Update:
- bump pytest to 9.0.3
- bump pillow to 12.2.0
- bump platformdirs to 4.9.6
- bump typespyyaml to 6.0.12.20260408
- bump tzfpy to 1.2.0
- bump pydantic to 2.13.0
- bump types-requests to 2.33.0.20260408

Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
Co-authored-by: Normann <github@koldrack.com>
This commit is contained in:
Bobby Noelte
2026-04-15 08:48:56 +02:00
committed by GitHub
parent 29cc3d7f3b
commit 6f28022ed0
19 changed files with 717 additions and 671 deletions

View File

@@ -200,24 +200,15 @@ class GeneticOptimizationParameters(
)
cls.config.optimization.interval = 3600
# Check genetic algorithm definitions
if cls.config.optimization.genetic is None:
logger.info(
"Genetic optimization configuration not configured - defaulting to demo config."
)
cls.config.optimization.genetic = {
"individuals": 300,
"generations": 400,
"seed": None,
"penalties": {
"ev_soc_miss": 10,
},
}
if cls.config.optimization.genetic.individuals is None:
logger.info("Genetic individuals unknown - defaulting to 300.")
cls.config.optimization.genetic.individuals = 300
if cls.config.optimization.genetic.generations is None:
logger.info("Genetic generations unknown - defaulting to 400.")
cls.config.optimization.genetic.generations = 400
if "ev_soc_miss" not in cls.config.optimization.genetic.penalties:
logger.info("Genetic penalties unknown - defaulting to ev_soc_miss = 10.")
cls.config.optimization.genetic.penalties["ev_soc_miss"] = 10
# Get start solution from last run
start_solution = None

View File

@@ -1,6 +1,6 @@
from typing import Optional, Union
from pydantic import Field, computed_field, model_validator
from pydantic import Field, computed_field
from akkudoktoreos.config.configabc import SettingsBaseModel
from akkudoktoreos.core.coreabc import get_ems
@@ -18,7 +18,7 @@ class GeneticCommonSettings(SettingsBaseModel):
default=300,
ge=10,
json_schema_extra={
"description": "Number of individuals (solutions) to generate for the (initial) generation [>= 10]. Defaults to 300.",
"description": "Number of individuals (solutions) in the population [>= 10]. Defaults to 300.",
"examples": [300],
},
)
@@ -27,7 +27,7 @@ class GeneticCommonSettings(SettingsBaseModel):
default=400,
ge=10,
json_schema_extra={
"description": "Number of generations to evaluate the optimal solution [>= 10]. Defaults to 400.",
"description": "Number of generations to evolve [>= 10]. Defaults to 400.",
"examples": [400],
},
)
@@ -36,21 +36,21 @@ class GeneticCommonSettings(SettingsBaseModel):
default=None,
ge=0,
json_schema_extra={
"description": "Fixed seed for genetic algorithm. Defaults to 'None' which means random seed.",
"examples": [None],
"description": "Random seed for reproducibility. None = random.",
"examples": [None, 42],
},
)
# --- Penalties (existing) -------------------------------------------------
penalties: dict[str, Union[float, int, str]] = Field(
default_factory=lambda: {
"ev_soc_miss": 10,
"ac_charge_break_even": 1.0,
},
json_schema_extra={
"description": "A dictionary of penalty function parameters consisting of a penalty function parameter name and the associated value.",
"examples": [
{"ev_soc_miss": 10},
],
"description": "Penalty parameters used in fitness evaluation.",
"examples": [{"ev_soc_miss": 10}],
},
)
@@ -58,7 +58,7 @@ class GeneticCommonSettings(SettingsBaseModel):
class OptimizationCommonSettings(SettingsBaseModel):
"""General Optimization Configuration."""
horizon_hours: Optional[int] = Field(
horizon_hours: int = Field(
default=24,
ge=0,
json_schema_extra={
@@ -67,23 +67,26 @@ class OptimizationCommonSettings(SettingsBaseModel):
},
)
interval: Optional[int] = Field(
interval: int = Field(
default=3600,
ge=15 * 60,
le=60 * 60,
json_schema_extra={
"description": "The optimization interval [sec].",
"description": "The optimization interval [sec]. Defaults to 3600 seconds (1 hour)",
"examples": [60 * 60, 15 * 60],
},
)
algorithm: Optional[str] = Field(
algorithm: str = Field(
default="GENETIC",
json_schema_extra={"description": "The optimization algorithm.", "examples": ["GENETIC"]},
json_schema_extra={
"description": "The optimization algorithm. Defaults to GENETIC",
"examples": ["GENETIC"],
},
)
genetic: Optional[GeneticCommonSettings] = Field(
default=None,
genetic: GeneticCommonSettings = Field(
default_factory=GeneticCommonSettings,
json_schema_extra={
"description": "Genetic optimization algorithm configuration.",
"examples": [{"individuals": 400, "seed": None, "penalties": {"ev_soc_miss": 10}}],
@@ -109,14 +112,14 @@ class OptimizationCommonSettings(SettingsBaseModel):
key_list = df.columns.tolist()
return sorted(set(key_list))
# Validators
@model_validator(mode="after")
def _enforce_algorithm_configuration(self) -> "OptimizationCommonSettings":
"""Ensure algorithm default configuration is set."""
if self.algorithm is not None:
if self.algorithm.lower() == "genetic" and self.genetic is None:
self.genetic = GeneticCommonSettings()
return self
@computed_field # type: ignore[prop-decorator]
@property
def horizon(self) -> int:
"""Number of optimization steps."""
if self.interval is None or self.interval == 0 or self.horizon_hours is None:
return 0
num_steps = int(float(self.horizon_hours * 3600) / self.interval)
return num_steps
class OptimizationSolution(PydanticBaseModel):