Files
EOS/tests/test_geneticoptimize.py
Bobby Noelte 976a2c8405
Some checks failed
Bump Version / Bump Version Workflow (push) Has been cancelled
docker-build / platform-excludes (push) Has been cancelled
docker-build / build (push) Has been cancelled
docker-build / merge (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
Run Pytest on Pull Request / test (push) Has been cancelled
chore: automate development version and release generation (#772)
This change introduces a GitHub Action to automate release creation, including
proper tagging and automatic addition of a development marker to the version.

A hash is also appended to development versions to make their state easier to
distinguish.

Tests and release documentation have been updated to reflect the revised
release workflow. Several files now retrieve the current version dynamically.

The test --full-run option has been rename to --finalize to make
clear it is to do commit finalization testing.

Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
2025-11-20 00:10:19 +01:00

151 lines
5.1 KiB
Python

import json
from pathlib import Path
from typing import Any
from unittest.mock import patch
import pytest
from akkudoktoreos.config.config import ConfigEOS
from akkudoktoreos.core.cache import CacheEnergyManagementStore
from akkudoktoreos.core.ems import get_ems
from akkudoktoreos.optimization.genetic.genetic import GeneticOptimization
from akkudoktoreos.optimization.genetic.geneticparams import (
GeneticOptimizationParameters,
)
from akkudoktoreos.optimization.genetic.geneticsolution import GeneticSolution
from akkudoktoreos.utils.datetimeutil import to_datetime
from akkudoktoreos.utils.visualize import (
prepare_visualize, # Import the new prepare_visualize
)
ems_eos = get_ems()
DIR_TESTDATA = Path(__file__).parent / "testdata"
def compare_dict(actual: dict[str, Any], expected: dict[str, Any]):
assert set(actual) == set(expected)
for key, value in expected.items():
if isinstance(value, dict):
assert isinstance(actual[key], dict)
compare_dict(actual[key], value)
elif isinstance(value, list):
assert isinstance(actual[key], list)
assert actual[key] == pytest.approx(value)
else:
assert actual[key] == pytest.approx(value)
@pytest.mark.parametrize(
"fn_in, fn_out, ngen",
[
("optimize_input_1.json", "optimize_result_1.json", 3),
("optimize_input_2.json", "optimize_result_2.json", 3),
("optimize_input_2.json", "optimize_result_2_full.json", 400),
],
)
def test_optimize(
fn_in: str,
fn_out: str,
ngen: int,
config_eos: ConfigEOS,
is_finalize: bool,
):
"""Test optimierung_ems."""
# Test parameters
fixed_start_hour = 10
fixed_seed = 42
# Assure configuration holds the correct values
config_eos.merge_settings_from_dict(
{
"prediction": {
"hours": 48
},
"optimization": {
"horizon_hours": 48,
"genetic": {
"individuals": 300,
"generations": 10,
"penalties": {
"ev_soc_miss": 10
}
}
},
"devices": {
"max_electric_vehicles": 1,
"electric_vehicles": [
{
"charge_rates": [0.0, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0],
}
],
}
}
)
# Load input and output data
file = DIR_TESTDATA / fn_in
with file.open("r") as f_in:
input_data = GeneticOptimizationParameters(**json.load(f_in))
file = DIR_TESTDATA / fn_out
# In case a new test case is added, we don't want to fail here, so the new output is written
# to disk before
try:
with file.open("r") as f_out:
expected_data = json.load(f_out)
expected_result = GeneticSolution(**expected_data)
except FileNotFoundError:
pass
# Fake energy management run start datetime
ems_eos.set_start_datetime(to_datetime().set(hour=fixed_start_hour))
# Throw away any cached results of the last energy management run.
CacheEnergyManagementStore().clear()
genetic_optimization = GeneticOptimization(fixed_seed=fixed_seed)
# Activate with pytest --finalize
if ngen > 10 and not is_finalize:
pytest.skip()
visualize_filename = str((DIR_TESTDATA / f"new_{fn_out}").with_suffix(".pdf"))
with patch(
"akkudoktoreos.utils.visualize.prepare_visualize",
side_effect=lambda parameters, results, *args, **kwargs: prepare_visualize(
parameters, results, filename=visualize_filename, **kwargs
),
) as prepare_visualize_patch:
# Call the optimization function
genetic_solution = genetic_optimization.optimierung_ems(
parameters=input_data, start_hour=fixed_start_hour, ngen=ngen
)
# The function creates a visualization result PDF as a side-effect.
prepare_visualize_patch.assert_called_once()
assert Path(visualize_filename).exists()
# Write test output to file, so we can take it as new data on intended change
TESTDATA_FILE = DIR_TESTDATA / f"new_{fn_out}"
with TESTDATA_FILE.open("w", encoding="utf-8", newline="\n") as f_out:
f_out.write(genetic_solution.model_dump_json(indent=4, exclude_unset=True))
assert genetic_solution.result.Gesamtbilanz_Euro == pytest.approx(
expected_result.result.Gesamtbilanz_Euro
)
# Assert that the output contains all expected entries.
# This does not assert that the optimization always gives the same result!
# Reproducibility and mathematical accuracy should be tested on the level of individual components.
compare_dict(genetic_solution.model_dump(), expected_result.model_dump())
# Check the correct generic optimization solution is created
optimization_solution = genetic_solution.optimization_solution()
# @TODO
# Check the correct generic energy management plan is created
plan = genetic_solution.energy_management_plan()
# @TODO