Files
EOS/tests/test_geneticsimulation.py

549 lines
16 KiB
Python
Raw Permalink Normal View History

from unittest.mock import Mock
import numpy as np
2024-10-04 10:57:01 +02:00
import pytest
2024-10-04 11:46:52 +02:00
from akkudoktoreos.config.configabc import TimeWindow, TimeWindowSequence
2025-10-28 02:50:31 +01:00
from akkudoktoreos.devices.genetic.battery import Battery
from akkudoktoreos.devices.genetic.homeappliance import HomeAppliance
from akkudoktoreos.devices.genetic.inverter import Inverter
from akkudoktoreos.optimization.genetic.genetic import GeneticSimulation
from akkudoktoreos.optimization.genetic.geneticdevices import (
2024-12-19 14:50:19 +01:00
ElectricVehicleParameters,
2025-10-28 02:50:31 +01:00
HomeApplianceParameters,
InverterParameters,
2024-12-19 14:50:19 +01:00
SolarPanelBatteryParameters,
)
2025-10-28 02:50:31 +01:00
from akkudoktoreos.optimization.genetic.geneticparams import (
GeneticEnergyManagementParameters,
GeneticOptimizationParameters,
)
from akkudoktoreos.optimization.genetic.geneticsolution import GeneticSimulationResult
from akkudoktoreos.utils.datetimeutil import to_duration, to_time
2024-10-06 14:32:49 +02:00
2024-10-06 14:29:23 +02:00
start_hour = 1
2024-10-04 11:46:52 +02:00
2024-10-06 14:32:49 +02:00
2024-10-04 10:57:51 +02:00
# Example initialization of necessary components
2024-10-04 10:57:01 +02:00
@pytest.fixture
2025-10-28 02:50:31 +01:00
def genetic_simulation(config_eos) -> GeneticSimulation:
2025-02-12 21:35:51 +01:00
"""Fixture to create an EnergyManagement instance with given test parameters."""
# Assure configuration holds the correct values
2025-01-12 05:19:37 +01:00
config_eos.merge_settings_from_dict(
2025-01-18 14:26:34 +01:00
{"prediction": {"hours": 48}, "optimization": {"hours": 24}}
2025-01-12 05:19:37 +01:00
)
2025-01-18 14:26:34 +01:00
assert config_eos.prediction.hours == 48
2025-10-28 02:50:31 +01:00
assert config_eos.optimization.horizon_hours == 24
2024-10-04 10:57:51 +02:00
# Initialize the battery and the inverter
2024-12-19 14:50:19 +01:00
akku = Battery(
SolarPanelBatteryParameters(
2025-01-12 05:19:37 +01:00
device_id="battery1",
capacity_wh=5000,
initial_soc_percentage=80,
min_soc_percentage=10,
2025-10-28 02:50:31 +01:00
),
prediction_hours=config_eos.prediction.hours,
2024-11-15 22:27:25 +01:00
)
2024-10-04 10:57:01 +02:00
akku.reset()
2025-01-12 05:19:37 +01:00
inverter = Inverter(
InverterParameters(
device_id="inverter1", max_power_wh=10000, battery_id=akku.parameters.device_id
),
battery=akku,
2025-01-12 05:19:37 +01:00
)
2024-10-04 10:57:51 +02:00
# Flexible consumer (fixed start at slot 2 for this deterministic test)
home_appliance = HomeAppliance(
HomeApplianceParameters(
2025-01-12 05:19:37 +01:00
device_id="dishwasher1",
consumption_wh=2000,
duration_h=2,
2025-10-28 02:50:31 +01:00
time_windows=None,
2024-11-15 22:27:25 +01:00
),
optimization_hours=config_eos.optimization.horizon_hours,
prediction_hours=config_eos.prediction.hours,
2024-10-06 14:32:49 +02:00
)
home_appliance.build_load_curve([2])
2024-10-06 14:32:49 +02:00
2024-10-04 10:57:51 +02:00
# Example initialization of electric car battery
2024-12-19 14:50:19 +01:00
eauto = Battery(
ElectricVehicleParameters(
2025-01-12 05:19:37 +01:00
device_id="ev1", capacity_wh=26400, initial_soc_percentage=10, min_soc_percentage=10
2024-12-19 14:50:19 +01:00
),
prediction_hours=config_eos.prediction.hours,
2024-11-15 22:27:25 +01:00
)
2025-01-18 14:26:34 +01:00
eauto.set_charge_per_hour(np.full(config_eos.prediction.hours, 1))
2024-11-15 22:27:25 +01:00
2024-10-04 10:57:51 +02:00
# Parameters based on previous example data
2024-10-04 10:57:01 +02:00
pv_prognose_wh = [
2024-10-04 11:46:52 +02:00
0,
0,
0,
0,
0,
0,
0,
8.05,
352.91,
728.51,
930.28,
1043.25,
1106.74,
1161.69,
6018.82,
5519.07,
3969.88,
3017.96,
1943.07,
1007.17,
319.67,
7.88,
0,
0,
0,
0,
0,
0,
0,
0,
0,
5.04,
335.59,
705.32,
1121.12,
1604.79,
2157.38,
1433.25,
5718.49,
4553.96,
3027.55,
2574.46,
1720.4,
963.4,
383.3,
0,
0,
0,
2024-10-04 10:57:01 +02:00
]
2024-10-04 10:57:51 +02:00
2024-10-04 10:57:01 +02:00
strompreis_euro_pro_wh = [
2024-10-04 11:46:52 +02:00
0.0003384,
0.0003318,
0.0003284,
0.0003283,
0.0003289,
0.0003334,
0.0003290,
0.0003302,
0.0003042,
0.0002430,
0.0002280,
0.0002212,
0.0002093,
0.0001879,
0.0001838,
0.0002004,
0.0002198,
0.0002270,
0.0002997,
0.0003195,
0.0003081,
0.0002969,
0.0002921,
0.0002780,
0.0003384,
0.0003318,
0.0003284,
0.0003283,
0.0003289,
0.0003334,
0.0003290,
0.0003302,
0.0003042,
0.0002430,
0.0002280,
0.0002212,
0.0002093,
0.0001879,
0.0001838,
0.0002004,
0.0002198,
0.0002270,
0.0002997,
0.0003195,
0.0003081,
0.0002969,
0.0002921,
0.0002780,
2024-10-04 10:57:01 +02:00
]
2024-10-04 10:57:51 +02:00
2024-11-15 22:27:25 +01:00
einspeiseverguetung_euro_pro_wh = 0.00007
preis_euro_pro_wh_akku = 0.0001
2024-10-04 10:57:01 +02:00
gesamtlast = [
2024-10-04 11:46:52 +02:00
676.71,
876.19,
527.13,
468.88,
531.38,
517.95,
483.15,
472.28,
1011.68,
995.00,
1053.07,
1063.91,
1320.56,
1132.03,
1163.67,
1176.82,
1216.22,
1103.78,
1129.12,
1178.71,
1050.98,
988.56,
912.38,
704.61,
516.37,
868.05,
694.34,
608.79,
556.31,
488.89,
506.91,
804.89,
1141.98,
1056.97,
992.46,
1155.99,
827.01,
1257.98,
1232.67,
871.26,
860.88,
1158.03,
1222.72,
1221.04,
949.99,
987.01,
733.99,
592.97,
2024-10-04 10:57:01 +02:00
]
2024-10-04 10:57:51 +02:00
# Initialize the energy management system with the respective parameters
2025-10-28 02:50:31 +01:00
simulation = GeneticSimulation()
simulation.prepare(
GeneticEnergyManagementParameters(
2024-11-15 22:27:25 +01:00
pv_prognose_wh=pv_prognose_wh,
strompreis_euro_pro_wh=strompreis_euro_pro_wh,
einspeiseverguetung_euro_pro_wh=einspeiseverguetung_euro_pro_wh,
preis_euro_pro_wh_akku=preis_euro_pro_wh_akku,
gesamtlast=gesamtlast,
),
optimization_hours=config_eos.optimization.horizon_hours,
prediction_hours=config_eos.prediction.hours,
2024-12-16 15:33:00 +01:00
inverter=inverter,
ev=eauto,
home_appliances=[home_appliance],
2024-10-04 10:57:01 +02:00
)
2024-10-16 15:40:04 +02:00
2025-11-08 15:42:18 +01:00
# Init for test
assert simulation.ac_charge_hours is not None
assert simulation.dc_charge_hours is not None
assert simulation.bat_discharge_hours is not None
assert simulation.bat_grid_export_hours is not None
2025-11-08 15:42:18 +01:00
assert simulation.ev_charge_hours is not None
simulation.ac_charge_hours[start_hour] = 1.0
simulation.dc_charge_hours[start_hour] = 1.0
simulation.bat_discharge_hours[start_hour] = 1.0
simulation.ev_charge_hours[start_hour] = 1.0
2025-10-28 02:50:31 +01:00
return simulation
2024-10-04 10:57:01 +02:00
2025-10-28 02:50:31 +01:00
def test_simulation(genetic_simulation):
2025-02-12 21:35:51 +01:00
"""Test the EnergyManagement simulation method."""
2025-10-28 02:50:31 +01:00
simulation = genetic_simulation
2024-10-04 10:57:51 +02:00
# Simulate starting from hour 1 (this value can be adjusted)
2024-10-06 14:32:49 +02:00
2025-10-28 02:50:31 +01:00
result = simulation.simulate(start_hour=start_hour)
2024-10-06 14:32:49 +02:00
2024-10-22 10:22:50 +02:00
# visualisiere_ergebnisse(
2025-10-28 02:50:31 +01:00
# simulation.gesamtlast,
# simulation.pv_prognose_wh,
# simulation.strompreis_euro_pro_wh,
2024-10-22 10:22:50 +02:00
# result,
2025-10-28 02:50:31 +01:00
# simulation.akku.discharge_array+simulation.akku.charge_array,
2024-10-22 10:22:50 +02:00
# None,
2025-10-28 02:50:31 +01:00
# simulation.pv_prognose_wh,
2024-10-22 10:22:50 +02:00
# start_hour,
# 48,
# np.full(48, 0.0),
# filename="visualization_results.pdf",
# extra_data=None,
# )
2024-10-16 15:40:04 +02:00
2024-10-04 10:57:01 +02:00
# Assertions to validate results
2024-10-04 10:57:51 +02:00
assert result is not None, "Result should not be None"
assert isinstance(result, dict), "Result should be a dictionary"
assert "Last_Wh_pro_Stunde" in result, "Result should contain 'Last_Wh_pro_Stunde'"
2024-10-04 10:57:01 +02:00
"""
2024-10-04 10:57:51 +02:00
Check the result of the simulation based on expected values.
2024-10-04 10:57:01 +02:00
"""
2024-10-04 10:57:51 +02:00
# Example result returned from the simulation (used for assertions)
assert result is not None, "Result should not be None."
# Check that the result is a dictionary
assert isinstance(result, dict), "Result should be a dictionary."
2025-10-28 02:50:31 +01:00
assert GeneticSimulationResult(**result) is not None
2024-10-04 10:57:01 +02:00
2024-10-04 10:57:51 +02:00
# Check the length of the main arrays
assert (
len(result["Last_Wh_pro_Stunde"]) == 47
), "The length of 'Last_Wh_pro_Stunde' should be 48."
assert (
len(result["Netzeinspeisung_Wh_pro_Stunde"]) == 47
), "The length of 'Netzeinspeisung_Wh_pro_Stunde' should be 48."
assert (
len(result["Netzbezug_Wh_pro_Stunde"]) == 47
), "The length of 'Netzbezug_Wh_pro_Stunde' should be 48."
assert (
len(result["Kosten_Euro_pro_Stunde"]) == 47
), "The length of 'Kosten_Euro_pro_Stunde' should be 48."
assert (
len(result["akku_soc_pro_stunde"]) == 47
), "The length of 'akku_soc_pro_stunde' should be 48."
2024-10-04 10:57:01 +02:00
2024-10-04 10:57:51 +02:00
# Verify specific values in the 'Last_Wh_pro_Stunde' array
assert (
result["Last_Wh_pro_Stunde"][1] == 1527.13
), "The value at index 1 of 'Last_Wh_pro_Stunde' should be 1527.13."
assert (
result["Last_Wh_pro_Stunde"][2] == 1468.88
), "The value at index 2 of 'Last_Wh_pro_Stunde' should be 1468.88."
assert (
result["Last_Wh_pro_Stunde"][12] == 1132.03
), "The value at index 12 of 'Last_Wh_pro_Stunde' should be 1132.03."
2024-10-04 10:57:01 +02:00
2024-10-04 10:57:51 +02:00
# Verify that the value at index 0 is 'None'
# Check that 'Netzeinspeisung_Wh_pro_Stunde' and 'Netzbezug_Wh_pro_Stunde' are consistent
assert (
result["Netzbezug_Wh_pro_Stunde"][1] == 1527.13
), "The value at index 1 of 'Netzbezug_Wh_pro_Stunde' should be 1527.13."
2024-10-04 10:57:01 +02:00
2024-10-04 10:57:51 +02:00
# Verify the total balance
assert (
abs(result["Gesamtbilanz_Euro"] - 7.224316588371922) < 1e-5
), "Total balance should reflect the shared per-slot battery power limit."
2024-10-04 10:57:51 +02:00
# Check total revenue and total costs
assert (
abs(result["Gesamteinnahmen_Euro"] - 2.3247787887715) < 1e-5
), "Total revenue should respect the shared per-slot battery power limit."
assert (
abs(result["Gesamtkosten_Euro"] - 9.549095377143422) < 1e-5
), "Total costs should respect the shared per-slot battery power limit."
2024-10-04 10:57:51 +02:00
# Check the losses
assert abs(result["Gesamt_Verluste"] - 1620.0) < 1e-5, "Total losses should be 1620.0 ."
2024-10-04 10:57:51 +02:00
# Check the values in 'akku_soc_pro_stunde'
assert (
result["akku_soc_pro_stunde"][-1] == 98.0
), "The value at index -1 of 'akku_soc_pro_stunde' should be 98.0."
assert (
result["akku_soc_pro_stunde"][1] == 98.0
), "The value at index 1 of 'akku_soc_pro_stunde' should be 98.0."
2024-10-04 10:57:51 +02:00
2024-10-06 14:04:26 +02:00
# Check home appliances
assert (
sum(simulation.home_appliances[0].get_load_curve()) == 2000
), "The sum of 'simulation.home_appliances[0].get_load_curve()' should be 2000."
2024-10-06 14:29:23 +02:00
2024-10-06 14:04:26 +02:00
assert (
np.nansum(
np.where(
2024-11-26 22:28:05 +01:00
result["Home_appliance_wh_per_hour"] is None,
np.nan,
np.array(result["Home_appliance_wh_per_hour"]),
)
)
== 2000
), "The sum of 'Home_appliance_wh_per_hour' should be 2000."
2024-10-06 14:04:26 +02:00
2024-10-04 10:57:51 +02:00
print("All tests passed successfully.")
def test_ev_charging_uses_raw_input_energy_for_load_and_grid(config_eos):
config_eos.merge_settings_from_dict(
{"prediction": {"hours": 1}, "optimization": {"horizon_hours": 1}}
)
ev = Battery(
ElectricVehicleParameters(
device_id="ev1",
capacity_wh=1000,
charging_efficiency=0.8,
max_charge_power_w=100,
initial_soc_percentage=0,
min_soc_percentage=0,
),
prediction_hours=1,
)
inverter = Inverter(InverterParameters(device_id="inverter1", max_power_wh=1000.0))
simulation = GeneticSimulation()
simulation.prepare(
GeneticEnergyManagementParameters(
pv_prognose_wh=[0.0],
strompreis_euro_pro_wh=[0.001],
einspeiseverguetung_euro_pro_wh=[0.0],
preis_euro_pro_wh_akku=0.0,
gesamtlast=[0.0],
),
optimization_hours=1,
prediction_hours=1,
inverter=inverter,
ev=ev,
)
simulation.ev_charge_hours = np.array([1.0])
result = simulation.simulate(start_hour=0)
assert result["Last_Wh_pro_Stunde"][0] == pytest.approx(100.0)
assert result["Netzbezug_Wh_pro_Stunde"][0] == pytest.approx(100.0)
assert result["Kosten_Euro_pro_Stunde"][0] == pytest.approx(0.1)
assert result["Verluste_Pro_Stunde"][0] == pytest.approx(20.0)
assert ev.current_soc_percentage() == pytest.approx(8.0)
def test_direct_marketing_curtails_negative_feed_in(config_eos, monkeypatch):
config_eos.merge_settings_from_dict(
{"prediction": {"hours": 2}, "optimization": {"horizon_hours": 2}}
)
inverter = Inverter(InverterParameters(device_id="inverter1", max_power_wh=1000.0))
monkeypatch.setattr(
inverter.self_consumption_predictor,
"calculate_expected_direct_consumption",
Mock(side_effect=min),
)
simulation = GeneticSimulation()
simulation.prepare(
GeneticEnergyManagementParameters(
pv_prognose_wh=[500.0, 500.0],
strompreis_euro_pro_wh=[-0.0001, -0.0001],
einspeiseverguetung_euro_pro_wh=[-0.0001, -0.0001],
preis_euro_pro_wh_akku=0.0,
gesamtlast=[0.0, 0.0],
),
optimization_hours=config_eos.optimization.horizon_hours,
prediction_hours=config_eos.prediction.hours,
inverter=inverter,
direct_marketing_enabled=True,
)
result = simulation.simulate(start_hour=0)
assert result["Netzeinspeisung_Wh_pro_Stunde"][0] == 0.0
assert result["Einnahmen_Euro_pro_Stunde"][0] == 0.0
assert result["Verluste_Pro_Stunde"][0] == pytest.approx(500.0)
def _direct_marketing_battery_export_simulation(
config_eos,
levelized_cost_of_storage_kwh: float = 0.0,
dc_to_ac_efficiency: float = 1.0,
) -> GeneticSimulation:
config_eos.merge_settings_from_dict(
{"prediction": {"hours": 2}, "optimization": {"horizon_hours": 2}}
)
battery = Battery(
SolarPanelBatteryParameters(
device_id="battery1",
capacity_wh=1000,
initial_soc_percentage=100,
min_soc_percentage=0,
charging_efficiency=1.0,
discharging_efficiency=1.0,
levelized_cost_of_storage_kwh=levelized_cost_of_storage_kwh,
max_charge_power_w=500,
),
prediction_hours=config_eos.prediction.hours,
)
inverter = Inverter(
InverterParameters(
device_id="inverter1",
max_power_wh=500.0,
battery_id=battery.parameters.device_id,
dc_to_ac_efficiency=dc_to_ac_efficiency,
),
battery=battery,
)
simulation = GeneticSimulation()
simulation.prepare(
GeneticEnergyManagementParameters(
pv_prognose_wh=[0.0, 0.0],
strompreis_euro_pro_wh=[0.0, 0.0],
einspeiseverguetung_euro_pro_wh=[0.0002, 0.0002],
preis_euro_pro_wh_akku=0.0,
gesamtlast=[0.0, 0.0],
),
optimization_hours=config_eos.optimization.horizon_hours,
prediction_hours=config_eos.prediction.hours,
inverter=inverter,
direct_marketing_enabled=True,
)
return simulation
def test_direct_marketing_discharge_allowed_does_not_export_battery(config_eos):
simulation = _direct_marketing_battery_export_simulation(config_eos)
assert simulation.bat_discharge_hours is not None
simulation.bat_discharge_hours[0] = 1
result = simulation.simulate(start_hour=0)
assert result["Netzeinspeisung_Wh_pro_Stunde"][0] == 0.0
assert simulation.battery is not None
assert simulation.battery.current_soc_percentage() == 100.0
def test_direct_marketing_battery_grid_export_uses_separate_signal(config_eos):
simulation = _direct_marketing_battery_export_simulation(config_eos)
assert simulation.bat_grid_export_hours is not None
simulation.bat_grid_export_hours[0] = 1
result = simulation.simulate(start_hour=0)
assert result["Netzeinspeisung_Wh_pro_Stunde"][0] == pytest.approx(500.0)
assert result["Einnahmen_Euro_pro_Stunde"][0] == pytest.approx(0.1)
assert simulation.battery is not None
assert simulation.battery.current_soc_percentage() == 50.0
def test_battery_lcos_is_charged_once_on_delivered_energy(config_eos):
simulation = _direct_marketing_battery_export_simulation(
config_eos,
levelized_cost_of_storage_kwh=0.12,
dc_to_ac_efficiency=0.8,
)
assert simulation.bat_grid_export_hours is not None
simulation.bat_grid_export_hours[0] = 1
result = simulation.simulate(start_hour=0)
# The battery delivers 500 Wh DC, so LCOS is 0.5 kWh * 0.12 EUR/kWh
# = 0.06 EUR exactly once. After the 80% inverter, 400 Wh AC reaches
# the grid and earns 400 Wh * 0.0002 EUR/Wh = 0.08 EUR.
assert result["Kosten_Euro_pro_Stunde"][0] == pytest.approx(0.06)
assert result["Gesamtkosten_Euro"] == pytest.approx(0.06)
assert result["Einnahmen_Euro_pro_Stunde"][0] == pytest.approx(0.08)
assert result["Gesamtbilanz_Euro"] == pytest.approx(-0.02)