mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-10-15 10:31:57 +00:00
feat: adapt configuration for multi optimization algorithms
Decouple configuration from optimization algorithm parameters. Add to_[algorithm]_param() methods to the configuration that derive optimization algorithm specific parameters from the configuration. Add x-scope tags to the configuration options that describe for which specific algorithms the configuration option is for. The whole device settings are restructured. There are now general settings for the device classes with the afore mentioned to_[algorithm]_param() methods. The general device settings got their own directory `devices/settings`. By this the parameter class also does not have to be a pydantic model which can be used for future optimization/ simulations speed up. Also the parameter class for a device is now part of the device module. This better decouples and also is the natural place for parameters of a device. Besides this feature there are also fixes and improvements: * feat: extend home appliance time window settings and simulation Home appliance can now be configured for multiple runs with per-cycle allowed time windows. The number of remaining cycles to plan is determined at runtime by reading the ``cycles_completed_measurement_key`` from the measurement store. * feat: specialiced CycleTimeWindowSequence for time window sequences Sequence of time windows associated to cycles. This model specializes ``ValueTimeWindowSequence`` so that the ``value`` field of each ``ValueTimeWindow`` encodes the **cycle index** (0-based integer) the window belongs to. Typical use: an appliance that must run ``n`` times per day, each run constrained to a distinct time window. Assign ``value=0`` to windows for the first cycle, ``value=1`` for the second, and so on. Multiple windows may share the same cycle index (their allowed regions are unioned). Windows with ``value=None`` are silently ignored by all cycle-aware methods. * fix: Make test_configmigrate also regard the _ANY_SENTENIEL in key values * chore: Make devices configurations a map instead of a list This makes config paths stable regardless of declaration order and lets each device settings class build its own config path from ``self.device_id`` without needing an external index. Tests are adapted likewise. Devices configurations are automatically migrated from lists to maps. * chore: rename levelized_cost_of_storage_kwh to levelized_cost_of_storage_amt kwh This better fits in the naming scheme and also makes clear the costs are money. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
This commit is contained in:
@@ -1102,12 +1102,12 @@ class GeneticOptimization(OptimizationBase):
|
||||
self.ev_possible_charge_values = parameters.ev.charge_rates
|
||||
elif (
|
||||
self.config.devices.electric_vehicles
|
||||
and self.config.devices.electric_vehicles[0]
|
||||
and self.config.devices.electric_vehicles[0].charge_rates is not None
|
||||
and len(self.config.devices.electric_vehicles) > 0
|
||||
and list(self.config.devices.electric_vehicles.values())[0].charge_rates is not None
|
||||
):
|
||||
self.ev_possible_charge_values = self.config.devices.electric_vehicles[
|
||||
0
|
||||
].charge_rates
|
||||
self.ev_possible_charge_values = list(
|
||||
self.config.devices.electric_vehicles.values()
|
||||
)[0].charge_rates
|
||||
else:
|
||||
warning_msg = "No charge rates provided for electric vehicle - using default."
|
||||
logger.warning(warning_msg)
|
||||
@@ -1136,11 +1136,11 @@ class GeneticOptimization(OptimizationBase):
|
||||
] or [1.0]
|
||||
elif (
|
||||
self.config.devices.batteries
|
||||
and self.config.devices.batteries[0]
|
||||
and self.config.devices.batteries[0].charge_rates
|
||||
and len(self.config.devices.batteries) > 0
|
||||
and list(self.config.devices.batteries.values())[0].charge_rates
|
||||
):
|
||||
self.bat_possible_charge_values = [
|
||||
r for r in self.config.devices.batteries[0].charge_rates if r > 0.0
|
||||
r for r in list(self.config.devices.batteries.values())[0].charge_rates if r > 0.0
|
||||
] or [1.0]
|
||||
else:
|
||||
self.bat_possible_charge_values = [1.0]
|
||||
|
||||
@@ -4,7 +4,6 @@ from typing import Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from akkudoktoreos.config.configabc import TimeWindowSequence
|
||||
from akkudoktoreos.optimization.genetic.geneticabc import GeneticParametersBaseModel
|
||||
|
||||
|
||||
@@ -18,179 +17,3 @@ class DeviceParameters(GeneticParametersBaseModel):
|
||||
"examples": [None],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def max_charging_power_field(description: Optional[str] = None) -> float:
|
||||
if description is None:
|
||||
description = "Maximum charging power in watts."
|
||||
return Field(default=5000, gt=0, json_schema_extra={"description": description})
|
||||
|
||||
|
||||
def initial_soc_percentage_field(description: str) -> int:
|
||||
return Field(
|
||||
default=0, ge=0, le=100, json_schema_extra={"description": description, "examples": [42]}
|
||||
)
|
||||
|
||||
|
||||
def discharging_efficiency_field(default_value: float) -> float:
|
||||
return Field(
|
||||
default=default_value,
|
||||
gt=0,
|
||||
le=1,
|
||||
json_schema_extra={
|
||||
"description": "A float representing the discharge efficiency of the battery."
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class BaseBatteryParameters(DeviceParameters):
|
||||
"""Battery Device Simulation Configuration."""
|
||||
|
||||
device_id: str = Field(
|
||||
json_schema_extra={"description": "ID of battery", "examples": ["battery1"]}
|
||||
)
|
||||
capacity_wh: int = Field(
|
||||
gt=0,
|
||||
json_schema_extra={
|
||||
"description": "An integer representing the capacity of the battery in watt-hours.",
|
||||
"examples": [8000],
|
||||
},
|
||||
)
|
||||
charging_efficiency: float = Field(
|
||||
default=0.88,
|
||||
gt=0,
|
||||
le=1,
|
||||
json_schema_extra={
|
||||
"description": "A float representing the charging efficiency of the battery."
|
||||
},
|
||||
)
|
||||
discharging_efficiency: float = discharging_efficiency_field(0.88)
|
||||
max_charge_power_w: Optional[float] = max_charging_power_field()
|
||||
initial_soc_percentage: int = initial_soc_percentage_field(
|
||||
"An integer representing the state of charge of the battery at the **start** of the current hour (not the current state)."
|
||||
)
|
||||
min_soc_percentage: int = Field(
|
||||
default=0,
|
||||
ge=0,
|
||||
le=100,
|
||||
json_schema_extra={
|
||||
"description": "An integer representing the minimum state of charge (SOC) of the battery in percentage.",
|
||||
"examples": [10],
|
||||
},
|
||||
)
|
||||
max_soc_percentage: int = Field(
|
||||
default=100,
|
||||
ge=0,
|
||||
le=100,
|
||||
json_schema_extra={
|
||||
"description": "An integer representing the maximum state of charge (SOC) of the battery in percentage."
|
||||
},
|
||||
)
|
||||
charge_rates: Optional[list[float]] = Field(
|
||||
default=None,
|
||||
json_schema_extra={
|
||||
"description": "Charge rates as factor of maximum charging power [0.00 ... 1.00]. None denotes all charge rates are available.",
|
||||
"examples": [[0.0, 0.25, 0.5, 0.75, 1.0], None],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class SolarPanelBatteryParameters(BaseBatteryParameters):
|
||||
"""PV battery device simulation configuration."""
|
||||
|
||||
max_charge_power_w: Optional[float] = max_charging_power_field()
|
||||
|
||||
|
||||
class ElectricVehicleParameters(BaseBatteryParameters):
|
||||
"""Battery Electric Vehicle Device Simulation Configuration."""
|
||||
|
||||
device_id: str = Field(
|
||||
json_schema_extra={"description": "ID of electric vehicle", "examples": ["ev1"]}
|
||||
)
|
||||
discharging_efficiency: float = discharging_efficiency_field(1.0)
|
||||
initial_soc_percentage: int = initial_soc_percentage_field(
|
||||
"An integer representing the current state of charge (SOC) of the battery in percentage."
|
||||
)
|
||||
|
||||
|
||||
class HomeApplianceParameters(DeviceParameters):
|
||||
"""Home Appliance Device Simulation Configuration."""
|
||||
|
||||
device_id: str = Field(
|
||||
json_schema_extra={"description": "ID of home appliance", "examples": ["dishwasher"]}
|
||||
)
|
||||
consumption_wh: int = Field(
|
||||
gt=0,
|
||||
json_schema_extra={
|
||||
"description": "An integer representing the energy consumption of a household device in watt-hours.",
|
||||
"examples": [2000],
|
||||
},
|
||||
)
|
||||
duration_h: int = Field(
|
||||
gt=0,
|
||||
json_schema_extra={
|
||||
"description": "An integer representing the usage duration of a household device in hours.",
|
||||
"examples": [3],
|
||||
},
|
||||
)
|
||||
time_windows: Optional[TimeWindowSequence] = Field(
|
||||
default=None,
|
||||
json_schema_extra={
|
||||
"description": "List of allowed time windows. Defaults to optimization general time window.",
|
||||
"examples": [
|
||||
[
|
||||
{"start_time": "10:00", "duration": "3 hours"},
|
||||
],
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class InverterParameters(DeviceParameters):
|
||||
"""Inverter Device Simulation Configuration."""
|
||||
|
||||
device_id: str = Field(
|
||||
json_schema_extra={"description": "ID of inverter", "examples": ["inverter1"]}
|
||||
)
|
||||
max_power_wh: float = Field(gt=0, json_schema_extra={"examples": [10000]})
|
||||
battery_id: Optional[str] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "ID of battery", "examples": [None, "battery1"]},
|
||||
)
|
||||
ac_to_dc_efficiency: float = Field(
|
||||
default=1.0,
|
||||
ge=0,
|
||||
le=1,
|
||||
json_schema_extra={
|
||||
"description": (
|
||||
"Efficiency of AC to DC conversion (for AC/grid charging of battery). "
|
||||
"Set to 0 to disable AC charging via inverter. "
|
||||
"Default 1.0 for backward compatibility (no additional inverter loss)."
|
||||
),
|
||||
"examples": [0.95, 1.0, 0.0],
|
||||
},
|
||||
)
|
||||
dc_to_ac_efficiency: float = Field(
|
||||
default=1.0,
|
||||
gt=0,
|
||||
le=1,
|
||||
json_schema_extra={
|
||||
"description": (
|
||||
"Efficiency of DC to AC conversion (for battery discharging to AC load/grid). "
|
||||
"Default 1.0 for backward compatibility (no additional inverter loss)."
|
||||
),
|
||||
"examples": [0.95, 1.0],
|
||||
},
|
||||
)
|
||||
max_ac_charge_power_w: Optional[float] = Field(
|
||||
default=None,
|
||||
ge=0,
|
||||
json_schema_extra={
|
||||
"description": (
|
||||
"Maximum AC charging power in watts. "
|
||||
"None means no additional limit (battery's own max_charge_power_w applies). "
|
||||
"Set to 0 to disable AC charging."
|
||||
),
|
||||
"examples": [None, 0, 5000],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -27,13 +27,13 @@ from akkudoktoreos.core.coreabc import (
|
||||
PredictionMixin,
|
||||
get_ems,
|
||||
)
|
||||
from akkudoktoreos.optimization.genetic.geneticabc import GeneticParametersBaseModel
|
||||
from akkudoktoreos.optimization.genetic.geneticdevices import (
|
||||
from akkudoktoreos.devices.genetic.battery import (
|
||||
ElectricVehicleParameters,
|
||||
HomeApplianceParameters,
|
||||
InverterParameters,
|
||||
SolarPanelBatteryParameters,
|
||||
)
|
||||
from akkudoktoreos.devices.genetic.homeappliance import HomeApplianceParameters
|
||||
from akkudoktoreos.devices.genetic.inverter import InverterParameters
|
||||
from akkudoktoreos.optimization.genetic.geneticabc import GeneticParametersBaseModel
|
||||
from akkudoktoreos.utils.datetimeutil import to_duration
|
||||
|
||||
# Do not import directly from akkudoktoreos.core.coreabc
|
||||
@@ -269,6 +269,11 @@ class GeneticOptimizationParameters(
|
||||
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
|
||||
# Setup some basic providers if not set
|
||||
if not cls.config.weather.provider:
|
||||
cls.config.weather.provider = "OpenMeteo"
|
||||
if not cls.config.load.provider:
|
||||
cls.config.load.provider = "LoadAkkudoktor"
|
||||
|
||||
# Get start solution from last run
|
||||
start_solution = None
|
||||
@@ -557,36 +562,38 @@ class GeneticOptimizationParameters(
|
||||
else:
|
||||
if cls.config.devices.batteries is None:
|
||||
logger.info("No battery device data available - defaulting to demo data.")
|
||||
cls.config.devices.batteries = [{"device_id": "battery1", "capacity_wh": 8000}]
|
||||
cls.config.devices.batteries = {
|
||||
"battery1": {
|
||||
"device_id": "battery1",
|
||||
"capacity_wh": 8000,
|
||||
},
|
||||
}
|
||||
try:
|
||||
battery_config = cls.config.devices.batteries[0]
|
||||
battery_params = SolarPanelBatteryParameters(
|
||||
device_id=battery_config.device_id,
|
||||
capacity_wh=battery_config.capacity_wh,
|
||||
charging_efficiency=battery_config.charging_efficiency,
|
||||
discharging_efficiency=battery_config.discharging_efficiency,
|
||||
max_charge_power_w=battery_config.max_charge_power_w,
|
||||
min_soc_percentage=battery_config.min_soc_percentage,
|
||||
max_soc_percentage=battery_config.max_soc_percentage,
|
||||
charge_rates=battery_config.charge_rates,
|
||||
)
|
||||
# Take first battery
|
||||
battery_config = list(cls.config.devices.batteries.values())[0]
|
||||
battery_params = battery_config.to_genetic_pv_bat_param()
|
||||
except Exception as e:
|
||||
logger.info(
|
||||
"No battery device data available - defaulting to demo data. Parameter preparation attempt {}: {}",
|
||||
attempt,
|
||||
e,
|
||||
)
|
||||
cls.config.devices.batteries = [{"device_id": "battery1", "capacity_wh": 8000}]
|
||||
cls.config.devices.batteries = {
|
||||
"battery1": {
|
||||
"device_id": "battery1",
|
||||
"capacity_wh": 8000,
|
||||
},
|
||||
}
|
||||
# Retry
|
||||
continue
|
||||
# Levelized cost of ownership
|
||||
if battery_config.levelized_cost_of_storage_kwh is None:
|
||||
if battery_config.levelized_cost_of_storage_amt_kwh is None:
|
||||
logger.info(
|
||||
"No battery device LCOS data available - defaulting to 0 [amount/kWh]. Parameter preparation attempt {}.",
|
||||
attempt,
|
||||
)
|
||||
battery_config.levelized_cost_of_storage_kwh = 0
|
||||
battery_lcos_kwh = battery_config.levelized_cost_of_storage_kwh
|
||||
battery_config.levelized_cost_of_storage_amt_kwh = 0
|
||||
battery_lcos_kwh = battery_config.levelized_cost_of_storage_amt_kwh
|
||||
# Initial SOC
|
||||
try:
|
||||
initial_soc_factor = await cls.measurement.key_to_value(
|
||||
@@ -623,26 +630,18 @@ class GeneticOptimizationParameters(
|
||||
"No electric vehicle device data available - defaulting to demo data."
|
||||
)
|
||||
cls.config.devices.max_electric_vehicles = 1
|
||||
cls.config.devices.electric_vehicles = [
|
||||
{
|
||||
"device_id": "ev11",
|
||||
cls.config.devices.electric_vehicles = {
|
||||
"ev1": {
|
||||
"device_id": "ev1",
|
||||
"capacity_wh": 50000,
|
||||
"charge_rates": [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
|
||||
"min_soc_percentage": 70,
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
try:
|
||||
electric_vehicle_config = cls.config.devices.electric_vehicles[0]
|
||||
electric_vehicle_params = ElectricVehicleParameters(
|
||||
device_id=electric_vehicle_config.device_id,
|
||||
capacity_wh=electric_vehicle_config.capacity_wh,
|
||||
charging_efficiency=electric_vehicle_config.charging_efficiency,
|
||||
discharging_efficiency=electric_vehicle_config.discharging_efficiency,
|
||||
charge_rates=electric_vehicle_config.charge_rates,
|
||||
max_charge_power_w=electric_vehicle_config.max_charge_power_w,
|
||||
min_soc_percentage=electric_vehicle_config.min_soc_percentage,
|
||||
max_soc_percentage=electric_vehicle_config.max_soc_percentage,
|
||||
)
|
||||
# Take first electric_vehicle
|
||||
electric_vehicle_config = list(cls.config.devices.electric_vehicles.values())[0]
|
||||
electric_vehicle_params = electric_vehicle_config.to_genetic_ev_bat_param()
|
||||
except Exception as e:
|
||||
logger.info(
|
||||
"No electric_vehicle device data available - defaulting to demo data. Parameter preparation attempt {}: {}",
|
||||
@@ -650,14 +649,14 @@ class GeneticOptimizationParameters(
|
||||
e,
|
||||
)
|
||||
cls.config.devices.max_electric_vehicles = 1
|
||||
cls.config.devices.electric_vehicles = [
|
||||
{
|
||||
"device_id": "ev12",
|
||||
cls.config.devices.electric_vehicles = {
|
||||
"ev1": {
|
||||
"device_id": "ev1",
|
||||
"capacity_wh": 50000,
|
||||
"charge_rates": [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
|
||||
"min_soc_percentage": 70,
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
# Retry
|
||||
continue
|
||||
# Initial SOC
|
||||
@@ -693,36 +692,30 @@ class GeneticOptimizationParameters(
|
||||
else:
|
||||
if cls.config.devices.inverters is None:
|
||||
logger.info("No inverter device data available - defaulting to demo data.")
|
||||
cls.config.devices.inverters = [
|
||||
{
|
||||
cls.config.devices.inverters = {
|
||||
"inverter1": {
|
||||
"device_id": "inverter1",
|
||||
"max_power_w": 10000,
|
||||
"battery_id": battery_config.device_id,
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
try:
|
||||
inverter_config = cls.config.devices.inverters[0]
|
||||
inverter_params = InverterParameters(
|
||||
device_id=inverter_config.device_id,
|
||||
max_power_wh=inverter_config.max_power_w,
|
||||
battery_id=inverter_config.battery_id,
|
||||
ac_to_dc_efficiency=inverter_config.ac_to_dc_efficiency,
|
||||
dc_to_ac_efficiency=inverter_config.dc_to_ac_efficiency,
|
||||
max_ac_charge_power_w=inverter_config.max_ac_charge_power_w,
|
||||
)
|
||||
# Take first inverter
|
||||
inverter_config = list(cls.config.devices.inverters.values())[0]
|
||||
inverter_params = inverter_config.to_genetic_param()
|
||||
except Exception as e:
|
||||
logger.info(
|
||||
"No inverter device data available - defaulting to demo data. Parameter preparation attempt {}: {}",
|
||||
attempt,
|
||||
e,
|
||||
)
|
||||
cls.config.devices.inverters = [
|
||||
{
|
||||
cls.config.devices.inverters = {
|
||||
"inverter1": {
|
||||
"device_id": "inverter1",
|
||||
"max_power_w": 10000,
|
||||
"battery_id": battery_config.device_id,
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
# Retry
|
||||
continue
|
||||
|
||||
@@ -739,12 +732,12 @@ class GeneticOptimizationParameters(
|
||||
logger.info(
|
||||
"No home appliance device data available - defaulting to demo data."
|
||||
)
|
||||
cls.config.devices.home_appliances = [
|
||||
{
|
||||
cls.config.devices.home_appliances = {
|
||||
"dishwasher1": {
|
||||
"device_id": "dishwasher1",
|
||||
"consumption_wh": 2000,
|
||||
"duration_h": 3.0,
|
||||
"time_windows": {
|
||||
"cycle_time_windows": {
|
||||
"windows": [
|
||||
{
|
||||
"start_time": "08:00",
|
||||
@@ -756,30 +749,26 @@ class GeneticOptimizationParameters(
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
try:
|
||||
home_appliance_config = cls.config.devices.home_appliances[0]
|
||||
home_appliance_params = HomeApplianceParameters(
|
||||
device_id=home_appliance_config.device_id,
|
||||
consumption_wh=home_appliance_config.consumption_wh,
|
||||
duration_h=home_appliance_config.duration_h,
|
||||
time_windows=home_appliance_config.time_windows,
|
||||
)
|
||||
# Take first appliance
|
||||
home_appliance_config = list(cls.config.devices.home_appliances.values())[0]
|
||||
home_appliance_params = home_appliance_config.to_genetic_param()
|
||||
except Exception as e:
|
||||
logger.info(
|
||||
"No home appliance device data available - defaulting to demo data. Parameter preparation attempt {}: {}",
|
||||
attempt,
|
||||
e,
|
||||
)
|
||||
cls.config.devices.home_appliances = [
|
||||
{
|
||||
cls.config.devices.home_appliances = {
|
||||
"dishwasher1": {
|
||||
"device_id": "dishwasher1",
|
||||
"consumption_wh": 2000,
|
||||
"duration_h": 3.0,
|
||||
"time_windows": None,
|
||||
}
|
||||
]
|
||||
"cycle_time_windows": None,
|
||||
},
|
||||
}
|
||||
# Retry
|
||||
continue
|
||||
|
||||
|
||||
@@ -350,21 +350,21 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
|
||||
def _battery_device_id(self) -> str:
|
||||
"""Get battery device id."""
|
||||
try:
|
||||
return self.config.devices.batteries[0].device_id
|
||||
return list(self.config.devices.batteries.values())[0].device_id
|
||||
except Exception:
|
||||
return "battery1"
|
||||
|
||||
def _ev_device_id(self) -> str:
|
||||
"""Get electric vehicle device id."""
|
||||
try:
|
||||
return self.config.devices.electric_vehicles[0].device_id
|
||||
return self.config.devices.electric_vehicles.values()[0].device_id
|
||||
except Exception:
|
||||
return "ev1"
|
||||
|
||||
def _homeappliance_device_id(self) -> str:
|
||||
"""Get home appliance device id."""
|
||||
try:
|
||||
return self.config.devices.home_appliances[0].device_id
|
||||
return self.config.devices.home_appliances.values()[0].device_id
|
||||
except Exception:
|
||||
return "homeappliance1"
|
||||
|
||||
@@ -453,11 +453,11 @@ class GeneticSolution(ConfigMixin, GeneticParametersBaseModel):
|
||||
(the inverter curtails automatically, but this makes intent clear).
|
||||
- Discharge: blocked when SOC is at or below min SOC.
|
||||
"""
|
||||
bat_list = self.config.devices.batteries
|
||||
if not bat_list:
|
||||
bat_dict = self.config.devices.batteries
|
||||
if bat_dict is None or len(bat_dict) <= 0:
|
||||
return ac_charge, dc_charge, discharge_allowed
|
||||
|
||||
bat = bat_list[0]
|
||||
bat = list(bat_dict.values())[0]
|
||||
min_soc = float(bat.min_soc_percentage)
|
||||
max_soc = float(bat.max_soc_percentage)
|
||||
capacity_wh = float(bat.capacity_wh)
|
||||
|
||||
@@ -1105,12 +1105,12 @@ class Genetic0Optimization(OptimizationBase):
|
||||
self.ev_possible_charge_values = parameters.ev.charge_rates
|
||||
elif (
|
||||
self.config.devices.electric_vehicles
|
||||
and self.config.devices.electric_vehicles[0]
|
||||
and self.config.devices.electric_vehicles[0].charge_rates is not None
|
||||
and len(self.config.devices.electric_vehicles) > 0
|
||||
and list(self.config.devices.electric_vehicles.values())[0].charge_rates is not None
|
||||
):
|
||||
self.ev_possible_charge_values = self.config.devices.electric_vehicles[
|
||||
0
|
||||
].charge_rates
|
||||
self.ev_possible_charge_values = list(
|
||||
self.config.devices.electric_vehicles.values()
|
||||
)[0].charge_rates
|
||||
else:
|
||||
warning_msg = "No charge rates provided for electric vehicle - using default."
|
||||
logger.warning(warning_msg)
|
||||
@@ -1139,11 +1139,11 @@ class Genetic0Optimization(OptimizationBase):
|
||||
] or [1.0]
|
||||
elif (
|
||||
self.config.devices.batteries
|
||||
and self.config.devices.batteries[0]
|
||||
and self.config.devices.batteries[0].charge_rates
|
||||
and len(self.config.devices.batteries) > 0
|
||||
and list(self.config.devices.batteries.values())[0].charge_rates
|
||||
):
|
||||
self.bat_possible_charge_values = [
|
||||
r for r in self.config.devices.batteries[0].charge_rates if r > 0.0
|
||||
r for r in list(self.config.devices.batteries.values())[0].charge_rates if r > 0.0
|
||||
] or [1.0]
|
||||
else:
|
||||
self.bat_possible_charge_values = [1.0]
|
||||
|
||||
@@ -4,7 +4,6 @@ from typing import Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from akkudoktoreos.config.configabc import TimeWindowSequence
|
||||
from akkudoktoreos.optimization.genetic0.genetic0abc import Genetic0ParametersBaseModel
|
||||
|
||||
|
||||
@@ -18,179 +17,3 @@ class Genetic0DeviceParameters(Genetic0ParametersBaseModel):
|
||||
"examples": [None],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def max_charging_power_field(description: Optional[str] = None) -> float:
|
||||
if description is None:
|
||||
description = "Maximum charging power in watts."
|
||||
return Field(default=5000, gt=0, json_schema_extra={"description": description})
|
||||
|
||||
|
||||
def initial_soc_percentage_field(description: str) -> int:
|
||||
return Field(
|
||||
default=0, ge=0, le=100, json_schema_extra={"description": description, "examples": [42]}
|
||||
)
|
||||
|
||||
|
||||
def discharging_efficiency_field(default_value: float) -> float:
|
||||
return Field(
|
||||
default=default_value,
|
||||
gt=0,
|
||||
le=1,
|
||||
json_schema_extra={
|
||||
"description": "A float representing the discharge efficiency of the battery."
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class Genetic0BaseBatteryParameters(Genetic0DeviceParameters):
|
||||
"""Battery Device Simulation Configuration."""
|
||||
|
||||
device_id: str = Field(
|
||||
json_schema_extra={"description": "ID of battery", "examples": ["battery1"]}
|
||||
)
|
||||
capacity_wh: int = Field(
|
||||
gt=0,
|
||||
json_schema_extra={
|
||||
"description": "An integer representing the capacity of the battery in watt-hours.",
|
||||
"examples": [8000],
|
||||
},
|
||||
)
|
||||
charging_efficiency: float = Field(
|
||||
default=0.88,
|
||||
gt=0,
|
||||
le=1,
|
||||
json_schema_extra={
|
||||
"description": "A float representing the charging efficiency of the battery."
|
||||
},
|
||||
)
|
||||
discharging_efficiency: float = discharging_efficiency_field(0.88)
|
||||
max_charge_power_w: Optional[float] = max_charging_power_field()
|
||||
initial_soc_percentage: int = initial_soc_percentage_field(
|
||||
"An integer representing the state of charge of the battery at the **start** of the current hour (not the current state)."
|
||||
)
|
||||
min_soc_percentage: int = Field(
|
||||
default=0,
|
||||
ge=0,
|
||||
le=100,
|
||||
json_schema_extra={
|
||||
"description": "An integer representing the minimum state of charge (SOC) of the battery in percentage.",
|
||||
"examples": [10],
|
||||
},
|
||||
)
|
||||
max_soc_percentage: int = Field(
|
||||
default=100,
|
||||
ge=0,
|
||||
le=100,
|
||||
json_schema_extra={
|
||||
"description": "An integer representing the maximum state of charge (SOC) of the battery in percentage."
|
||||
},
|
||||
)
|
||||
charge_rates: Optional[list[float]] = Field(
|
||||
default=None,
|
||||
json_schema_extra={
|
||||
"description": "Charge rates as factor of maximum charging power [0.00 ... 1.00]. None denotes all charge rates are available.",
|
||||
"examples": [[0.0, 0.25, 0.5, 0.75, 1.0], None],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class Genetic0SolarPanelBatteryParameters(Genetic0BaseBatteryParameters):
|
||||
"""PV battery device simulation configuration."""
|
||||
|
||||
max_charge_power_w: Optional[float] = max_charging_power_field()
|
||||
|
||||
|
||||
class Genetic0ElectricVehicleParameters(Genetic0BaseBatteryParameters):
|
||||
"""Battery Electric Vehicle Device Simulation Configuration."""
|
||||
|
||||
device_id: str = Field(
|
||||
json_schema_extra={"description": "ID of electric vehicle", "examples": ["ev1"]}
|
||||
)
|
||||
discharging_efficiency: float = discharging_efficiency_field(1.0)
|
||||
initial_soc_percentage: int = initial_soc_percentage_field(
|
||||
"An integer representing the current state of charge (SOC) of the battery in percentage."
|
||||
)
|
||||
|
||||
|
||||
class Genetic0HomeApplianceParameters(Genetic0DeviceParameters):
|
||||
"""Home Appliance Device Simulation Configuration."""
|
||||
|
||||
device_id: str = Field(
|
||||
json_schema_extra={"description": "ID of home appliance", "examples": ["dishwasher"]}
|
||||
)
|
||||
consumption_wh: int = Field(
|
||||
gt=0,
|
||||
json_schema_extra={
|
||||
"description": "An integer representing the energy consumption of a household device in watt-hours.",
|
||||
"examples": [2000],
|
||||
},
|
||||
)
|
||||
duration_h: int = Field(
|
||||
gt=0,
|
||||
json_schema_extra={
|
||||
"description": "An integer representing the usage duration of a household device in hours.",
|
||||
"examples": [3],
|
||||
},
|
||||
)
|
||||
time_windows: Optional[TimeWindowSequence] = Field(
|
||||
default=None,
|
||||
json_schema_extra={
|
||||
"description": "List of allowed time windows. Defaults to optimization general time window.",
|
||||
"examples": [
|
||||
[
|
||||
{"start_time": "10:00", "duration": "3 hours"},
|
||||
],
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class Genetic0InverterParameters(Genetic0DeviceParameters):
|
||||
"""Inverter Device Simulation Configuration."""
|
||||
|
||||
device_id: str = Field(
|
||||
json_schema_extra={"description": "ID of inverter", "examples": ["inverter1"]}
|
||||
)
|
||||
max_power_wh: float = Field(gt=0, json_schema_extra={"examples": [10000]})
|
||||
battery_id: Optional[str] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "ID of battery", "examples": [None, "battery1"]},
|
||||
)
|
||||
ac_to_dc_efficiency: float = Field(
|
||||
default=1.0,
|
||||
ge=0,
|
||||
le=1,
|
||||
json_schema_extra={
|
||||
"description": (
|
||||
"Efficiency of AC to DC conversion (for AC/grid charging of battery). "
|
||||
"Set to 0 to disable AC charging via inverter. "
|
||||
"Default 1.0 for backward compatibility (no additional inverter loss)."
|
||||
),
|
||||
"examples": [0.95, 1.0, 0.0],
|
||||
},
|
||||
)
|
||||
dc_to_ac_efficiency: float = Field(
|
||||
default=1.0,
|
||||
gt=0,
|
||||
le=1,
|
||||
json_schema_extra={
|
||||
"description": (
|
||||
"Efficiency of DC to AC conversion (for battery discharging to AC load/grid). "
|
||||
"Default 1.0 for backward compatibility (no additional inverter loss)."
|
||||
),
|
||||
"examples": [0.95, 1.0],
|
||||
},
|
||||
)
|
||||
max_ac_charge_power_w: Optional[float] = Field(
|
||||
default=None,
|
||||
ge=0,
|
||||
json_schema_extra={
|
||||
"description": (
|
||||
"Maximum AC charging power in watts. "
|
||||
"None means no additional limit (battery's own max_charge_power_w applies). "
|
||||
"Set to 0 to disable AC charging."
|
||||
),
|
||||
"examples": [None, 0, 5000],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -27,13 +27,15 @@ from akkudoktoreos.core.coreabc import (
|
||||
PredictionMixin,
|
||||
get_ems,
|
||||
)
|
||||
from akkudoktoreos.optimization.genetic0.genetic0abc import Genetic0ParametersBaseModel
|
||||
from akkudoktoreos.optimization.genetic0.genetic0devices import (
|
||||
from akkudoktoreos.devices.genetic0.genetic0battery import (
|
||||
Genetic0ElectricVehicleParameters,
|
||||
Genetic0HomeApplianceParameters,
|
||||
Genetic0InverterParameters,
|
||||
Genetic0SolarPanelBatteryParameters,
|
||||
)
|
||||
from akkudoktoreos.devices.genetic0.genetic0homeappliance import (
|
||||
Genetic0HomeApplianceParameters,
|
||||
)
|
||||
from akkudoktoreos.devices.genetic0.genetic0inverter import Genetic0InverterParameters
|
||||
from akkudoktoreos.optimization.genetic0.genetic0abc import Genetic0ParametersBaseModel
|
||||
from akkudoktoreos.utils.datetimeutil import to_duration
|
||||
|
||||
# Do not import directly from akkudoktoreos.core.coreabc
|
||||
@@ -260,6 +262,11 @@ class Genetic0OptimizationParameters(
|
||||
if "ev_soc_miss" not in cls.config.optimization.genetic0.penalties:
|
||||
logger.info("Genetic penalties unknown - defaulting to ev_soc_miss = 10.")
|
||||
cls.config.optimization.genetic0.penalties["ev_soc_miss"] = 10
|
||||
# Setup some basic providers if not set
|
||||
if not cls.config.weather.provider:
|
||||
cls.config.weather.provider = "OpenMeteo"
|
||||
if not cls.config.load.provider:
|
||||
cls.config.load.provider = "LoadAkkudoktor"
|
||||
|
||||
# Get start solution from last run
|
||||
start_solution = None
|
||||
@@ -548,36 +555,38 @@ class Genetic0OptimizationParameters(
|
||||
else:
|
||||
if cls.config.devices.batteries is None:
|
||||
logger.info("No battery device data available - defaulting to demo data.")
|
||||
cls.config.devices.batteries = [{"device_id": "battery1", "capacity_wh": 8000}]
|
||||
cls.config.devices.batteries = {
|
||||
"battery1": {
|
||||
"device_id": "battery1",
|
||||
"capacity_wh": 8000,
|
||||
},
|
||||
}
|
||||
try:
|
||||
battery_config = cls.config.devices.batteries[0]
|
||||
battery_params = Genetic0SolarPanelBatteryParameters(
|
||||
device_id=battery_config.device_id,
|
||||
capacity_wh=battery_config.capacity_wh,
|
||||
charging_efficiency=battery_config.charging_efficiency,
|
||||
discharging_efficiency=battery_config.discharging_efficiency,
|
||||
max_charge_power_w=battery_config.max_charge_power_w,
|
||||
min_soc_percentage=battery_config.min_soc_percentage,
|
||||
max_soc_percentage=battery_config.max_soc_percentage,
|
||||
charge_rates=battery_config.charge_rates,
|
||||
)
|
||||
# Take first battery
|
||||
battery_config = list(cls.config.devices.batteries.values())[0]
|
||||
battery_params = battery_config.to_genetic0_pv_bat_param()
|
||||
except Exception as e:
|
||||
logger.info(
|
||||
"No battery device data available - defaulting to demo data. Parameter preparation attempt {}: {}",
|
||||
attempt,
|
||||
e,
|
||||
)
|
||||
cls.config.devices.batteries = [{"device_id": "battery1", "capacity_wh": 8000}]
|
||||
cls.config.devices.batteries = {
|
||||
"battery1": {
|
||||
"device_id": "battery1",
|
||||
"capacity_wh": 8000,
|
||||
},
|
||||
}
|
||||
# Retry
|
||||
continue
|
||||
# Levelized cost of ownership
|
||||
if battery_config.levelized_cost_of_storage_kwh is None:
|
||||
if battery_config.levelized_cost_of_storage_amt_kwh is None:
|
||||
logger.info(
|
||||
"No battery device LCOS data available - defaulting to 0 [amount/kWh]. Parameter preparation attempt {}.",
|
||||
attempt,
|
||||
)
|
||||
battery_config.levelized_cost_of_storage_kwh = 0
|
||||
battery_lcos_kwh = battery_config.levelized_cost_of_storage_kwh
|
||||
battery_config.levelized_cost_of_storage_amt_kwh = 0
|
||||
battery_lcos_kwh = battery_config.levelized_cost_of_storage_amt_kwh
|
||||
# Initial SOC
|
||||
try:
|
||||
initial_soc_factor = await cls.measurement.key_to_value(
|
||||
@@ -614,26 +623,18 @@ class Genetic0OptimizationParameters(
|
||||
"No electric vehicle device data available - defaulting to demo data."
|
||||
)
|
||||
cls.config.devices.max_electric_vehicles = 1
|
||||
cls.config.devices.electric_vehicles = [
|
||||
{
|
||||
"device_id": "ev11",
|
||||
cls.config.devices.electric_vehicles = {
|
||||
"ev1": {
|
||||
"device_id": "ev1",
|
||||
"capacity_wh": 50000,
|
||||
"charge_rates": [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
|
||||
"min_soc_percentage": 70,
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
try:
|
||||
electric_vehicle_config = cls.config.devices.electric_vehicles[0]
|
||||
electric_vehicle_params = Genetic0ElectricVehicleParameters(
|
||||
device_id=electric_vehicle_config.device_id,
|
||||
capacity_wh=electric_vehicle_config.capacity_wh,
|
||||
charging_efficiency=electric_vehicle_config.charging_efficiency,
|
||||
discharging_efficiency=electric_vehicle_config.discharging_efficiency,
|
||||
charge_rates=electric_vehicle_config.charge_rates,
|
||||
max_charge_power_w=electric_vehicle_config.max_charge_power_w,
|
||||
min_soc_percentage=electric_vehicle_config.min_soc_percentage,
|
||||
max_soc_percentage=electric_vehicle_config.max_soc_percentage,
|
||||
)
|
||||
# Take first electric_vehicle
|
||||
electric_vehicle_config = list(cls.config.devices.electric_vehicles.values())[0]
|
||||
electric_vehicle_params = electric_vehicle_config.to_genetic0_ev_bat_param()
|
||||
except Exception as e:
|
||||
logger.info(
|
||||
"No electric_vehicle device data available - defaulting to demo data. Parameter preparation attempt {}: {}",
|
||||
@@ -641,14 +642,14 @@ class Genetic0OptimizationParameters(
|
||||
e,
|
||||
)
|
||||
cls.config.devices.max_electric_vehicles = 1
|
||||
cls.config.devices.electric_vehicles = [
|
||||
{
|
||||
"device_id": "ev12",
|
||||
cls.config.devices.electric_vehicles = {
|
||||
"ev1": {
|
||||
"device_id": "ev1",
|
||||
"capacity_wh": 50000,
|
||||
"charge_rates": [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
|
||||
"min_soc_percentage": 70,
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
# Retry
|
||||
continue
|
||||
# Initial SOC
|
||||
@@ -684,36 +685,30 @@ class Genetic0OptimizationParameters(
|
||||
else:
|
||||
if cls.config.devices.inverters is None:
|
||||
logger.info("No inverter device data available - defaulting to demo data.")
|
||||
cls.config.devices.inverters = [
|
||||
{
|
||||
cls.config.devices.inverters = {
|
||||
"inverter1": {
|
||||
"device_id": "inverter1",
|
||||
"max_power_w": 10000,
|
||||
"battery_id": battery_config.device_id,
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
try:
|
||||
inverter_config = cls.config.devices.inverters[0]
|
||||
inverter_params = Genetic0InverterParameters(
|
||||
device_id=inverter_config.device_id,
|
||||
max_power_wh=inverter_config.max_power_w,
|
||||
battery_id=inverter_config.battery_id,
|
||||
ac_to_dc_efficiency=inverter_config.ac_to_dc_efficiency,
|
||||
dc_to_ac_efficiency=inverter_config.dc_to_ac_efficiency,
|
||||
max_ac_charge_power_w=inverter_config.max_ac_charge_power_w,
|
||||
)
|
||||
# Take first inverter
|
||||
inverter_config = list(cls.config.devices.inverters.values())[0]
|
||||
inverter_params = inverter_config.to_genetic0_param()
|
||||
except Exception as e:
|
||||
logger.info(
|
||||
"No inverter device data available - defaulting to demo data. Parameter preparation attempt {}: {}",
|
||||
attempt,
|
||||
e,
|
||||
)
|
||||
cls.config.devices.inverters = [
|
||||
{
|
||||
cls.config.devices.inverters = {
|
||||
"inverter1": {
|
||||
"device_id": "inverter1",
|
||||
"max_power_w": 10000,
|
||||
"battery_id": battery_config.device_id,
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
# Retry
|
||||
continue
|
||||
|
||||
@@ -730,12 +725,12 @@ class Genetic0OptimizationParameters(
|
||||
logger.info(
|
||||
"No home appliance device data available - defaulting to demo data."
|
||||
)
|
||||
cls.config.devices.home_appliances = [
|
||||
{
|
||||
cls.config.devices.home_appliances = {
|
||||
"dishwasher1": {
|
||||
"device_id": "dishwasher1",
|
||||
"consumption_wh": 2000,
|
||||
"duration_h": 3.0,
|
||||
"time_windows": {
|
||||
"cycle_time_windows": {
|
||||
"windows": [
|
||||
{
|
||||
"start_time": "08:00",
|
||||
@@ -747,30 +742,26 @@ class Genetic0OptimizationParameters(
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
try:
|
||||
home_appliance_config = cls.config.devices.home_appliances[0]
|
||||
home_appliance_params = Genetic0HomeApplianceParameters(
|
||||
device_id=home_appliance_config.device_id,
|
||||
consumption_wh=home_appliance_config.consumption_wh,
|
||||
duration_h=home_appliance_config.duration_h,
|
||||
time_windows=home_appliance_config.time_windows,
|
||||
)
|
||||
# Take first appliance
|
||||
home_appliance_config = list(cls.config.devices.home_appliances.values())[0]
|
||||
home_appliance_params = home_appliance_config.to_genetic0_param()
|
||||
except Exception as e:
|
||||
logger.info(
|
||||
"No home appliance device data available - defaulting to demo data. Parameter preparation attempt {}: {}",
|
||||
attempt,
|
||||
e,
|
||||
)
|
||||
cls.config.devices.home_appliances = [
|
||||
{
|
||||
cls.config.devices.home_appliances = {
|
||||
"dishwasher1": {
|
||||
"device_id": "dishwasher1",
|
||||
"consumption_wh": 2000,
|
||||
"duration_h": 3.0,
|
||||
"time_windows": None,
|
||||
}
|
||||
]
|
||||
"cycle_time_windows": None,
|
||||
},
|
||||
}
|
||||
# Retry
|
||||
continue
|
||||
|
||||
|
||||
@@ -427,21 +427,21 @@ class Genetic0Solution(ConfigMixin, Genetic0ParametersBaseModel):
|
||||
def _battery_device_id(self) -> str:
|
||||
"""Get battery device id."""
|
||||
try:
|
||||
return self.config.devices.batteries[0].device_id
|
||||
return list(self.config.devices.batteries.values())[0].device_id
|
||||
except Exception:
|
||||
return "battery1"
|
||||
|
||||
def _ev_device_id(self) -> str:
|
||||
"""Get electric vehicle device id."""
|
||||
try:
|
||||
return self.config.devices.electric_vehicles[0].device_id
|
||||
return self.config.devices.electric_vehicles.values()[0].device_id
|
||||
except Exception:
|
||||
return "ev1"
|
||||
|
||||
def _homeappliance_device_id(self) -> str:
|
||||
"""Get home appliance device id."""
|
||||
try:
|
||||
return self.config.devices.home_appliances[0].device_id
|
||||
return self.config.devices.home_appliances.values()[0].device_id
|
||||
except Exception:
|
||||
return "homeappliance1"
|
||||
|
||||
@@ -530,11 +530,11 @@ class Genetic0Solution(ConfigMixin, Genetic0ParametersBaseModel):
|
||||
(the inverter curtails automatically, but this makes intent clear).
|
||||
- Discharge: blocked when SOC is at or below min SOC.
|
||||
"""
|
||||
bat_list = self.config.devices.batteries
|
||||
if not bat_list:
|
||||
bat_dict = self.config.devices.batteries
|
||||
if bat_dict is None or len(bat_dict) <= 0:
|
||||
return ac_charge, dc_charge, discharge_allowed
|
||||
|
||||
bat = bat_list[0]
|
||||
bat = list(bat_dict.values())[0]
|
||||
min_soc = float(bat.min_soc_percentage)
|
||||
max_soc = float(bat.max_soc_percentage)
|
||||
capacity_wh = float(bat.capacity_wh)
|
||||
|
||||
Reference in New Issue
Block a user