Tests for class_load_container v2 (#247)

* Tests for class_load_container v2
  - file as same name as class
  - useless numpy conversations removed
  - switched to built-in type tests
  - human readable tests included
* ruff
* removed dupicate empty list
* load_aggregator: collections.abc.Sequence

---------

Co-authored-by: Dominique Lasserre <lasserre.d@gmail.com>
This commit is contained in:
Normann 2024-12-17 22:17:29 +01:00 committed by GitHub
parent 31bd2de18b
commit 0e122a9a49
4 changed files with 86 additions and 49 deletions

View File

@ -0,0 +1,37 @@
from collections import defaultdict
from collections.abc import Sequence
class LoadAggregator:
def __init__(self, prediction_hours: int = 24) -> None:
"""Initializes the LoadAggregator object with the number of prediction hours.
:param prediction_hours: Number of hours to predict (default: 24)
"""
self.loads: defaultdict[str, list[float]] = defaultdict(
list
) # Dictionary to hold load arrays for different sources
self.prediction_hours: int = prediction_hours
def add_load(self, name: str, last_array: Sequence[float]) -> None:
"""Adds a load array for a specific source. Accepts a Sequence of floats.
:param name: Name of the load source (e.g., "Household", "Heat Pump").
:param last_array: Sequence of loads, where each entry corresponds to an hour.
:raises ValueError: If the length of last_array doesn't match the prediction hours.
"""
# Check length of the array without converting
if len(last_array) != self.prediction_hours:
raise ValueError(f"Total load inconsistent lengths in arrays: {name} {len(last_array)}")
self.loads[name] = list(last_array)
def calculate_total_load(self) -> list[float]:
"""Calculates the total load for each hour by summing up the loads from all sources.
:return: A list representing the total load for each hour.
Returns an empty list if no loads have been added.
"""
# Optimize the summation using a single loop with zip
total_load = [sum(hourly_loads) for hourly_loads in zip(*self.loads.values())]
return total_load

View File

@ -1,39 +0,0 @@
import numpy as np
class Gesamtlast:
def __init__(self, prediction_hours: int = 24):
self.lasten: dict[
str, np.ndarray
] = {} # Contains names and load arrays for different sources
self.prediction_hours = prediction_hours
def hinzufuegen(self, name: str, last_array: np.ndarray) -> None:
"""Adds an array of loads for a specific source.
:param name: Name of the load source (e.g., "Household", "Heat Pump")
:param last_array: Array of loads, where each entry corresponds to an hour
"""
if len(last_array) != self.prediction_hours:
raise ValueError(f"Total load inconsistent lengths in arrays: {name} {len(last_array)}")
self.lasten[name] = last_array
def gesamtlast_berechnen(self) -> np.ndarray:
"""Calculates the total load for each hour and returns an array of total loads.
:return: Array of total loads, where each entry corresponds to an hour
"""
if not self.lasten:
return np.ndarray(0)
# Assumption: All load arrays have the same length
stunden = len(next(iter(self.lasten.values())))
gesamtlast_array = [0] * stunden
for last_array in self.lasten.values():
gesamtlast_array = [
gesamtlast + stundenlast
for gesamtlast, stundenlast in zip(gesamtlast_array, last_array)
]
return np.array(gesamtlast_array)

View File

@ -23,7 +23,7 @@ from akkudoktoreos.optimization.genetic import (
)
# Still to be adapted
from akkudoktoreos.prediction.load_container import Gesamtlast
from akkudoktoreos.prediction.load_aggregator import LoadAggregator
from akkudoktoreos.prediction.load_corrector import LoadPredictionAdjuster
from akkudoktoreos.prediction.load_forecast import LoadForecast
from akkudoktoreos.prediction.prediction import get_prediction
@ -160,14 +160,13 @@ def fastapi_gesamtlast(request: GesamtlastRequest) -> list[float]:
future_predictions = adjuster.predict_next_hours(hours)
leistung_haushalt = future_predictions["Adjusted Pred"].to_numpy()
gesamtlast = Gesamtlast(prediction_hours=hours)
gesamtlast.hinzufuegen(
gesamtlast = LoadAggregator(prediction_hours=hours)
gesamtlast.add_load(
"Haushalt",
leistung_haushalt,
tuple(leistung_haushalt),
)
last = gesamtlast.gesamtlast_berechnen()
return last.tolist()
return gesamtlast.calculate_total_load()
@app.get("/gesamtlast_simple")
@ -183,8 +182,10 @@ def fastapi_gesamtlast_simple(year_energy: float) -> list[float]:
)[0] # Get expected household load for the date range
prediction_hours = config_eos.prediction_hours if config_eos.prediction_hours else 48
gesamtlast = Gesamtlast(prediction_hours=prediction_hours) # Create Gesamtlast instance
gesamtlast.hinzufuegen("Haushalt", leistung_haushalt) # Add household to total load calculation
gesamtlast = LoadAggregator(prediction_hours=prediction_hours) # Create Gesamtlast instance
gesamtlast.add_load(
"Haushalt", tuple(leistung_haushalt)
) # Add household to total load calculation
# ###############
# # WP (Heat Pump)
@ -192,8 +193,7 @@ def fastapi_gesamtlast_simple(year_energy: float) -> list[float]:
# leistung_wp = wp.simulate_24h(temperature_forecast) # Simulate heat pump load for 24 hours
# gesamtlast.hinzufuegen("Heatpump", leistung_wp) # Add heat pump load to total load calculation
last = gesamtlast.gesamtlast_berechnen() # Calculate total load
return last.tolist() # Return total load as JSON
return gesamtlast.calculate_total_load()
class ForecastResponse(PydanticBaseModel):

View File

@ -0,0 +1,39 @@
import pytest
from akkudoktoreos.prediction.load_aggregator import LoadAggregator
def test_initialization():
aggregator = LoadAggregator()
assert aggregator.prediction_hours == 24
assert aggregator.loads == {}
def test_add_load_valid():
aggregator = LoadAggregator(prediction_hours=3)
aggregator.add_load("Source1", [10.0, 20.0, 30.0])
assert aggregator.loads["Source1"] == [10.0, 20.0, 30.0]
def test_add_load_invalid_length():
aggregator = LoadAggregator(prediction_hours=3)
with pytest.raises(ValueError, match="Total load inconsistent lengths in arrays: Source1 2"):
aggregator.add_load("Source1", [10.0, 20.0])
def test_calculate_total_load_empty():
aggregator = LoadAggregator()
assert aggregator.calculate_total_load() == []
def test_calculate_total_load():
aggregator = LoadAggregator(prediction_hours=3)
aggregator.add_load("Source1", [10.0, 20.0, 30.0])
aggregator.add_load("Source2", [5.0, 15.0, 25.0])
assert aggregator.calculate_total_load() == [15.0, 35.0, 55.0]
def test_calculate_total_load_single_source():
aggregator = LoadAggregator(prediction_hours=3)
aggregator.add_load("Source1", [10.0, 20.0, 30.0])
assert aggregator.calculate_total_load() == [10.0, 20.0, 30.0]