translation of battery.py v3 (#262)

This commit is contained in:
Normann
2024-12-19 14:50:19 +01:00
committed by GitHub
parent 0e122a9a49
commit 5f898e8aab
18 changed files with 684 additions and 648 deletions

View File

@@ -8,7 +8,7 @@ from typing_extensions import Self
from akkudoktoreos.core.coreabc import ConfigMixin, PredictionMixin, SingletonMixin
from akkudoktoreos.core.pydantic import PydanticBaseModel
from akkudoktoreos.devices.battery import PVAkku
from akkudoktoreos.devices.battery import Battery
from akkudoktoreos.devices.generic import HomeAppliance
from akkudoktoreos.devices.inverter import Inverter
from akkudoktoreos.utils.datetimeutil import to_datetime
@@ -152,8 +152,8 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda
# TODO: Move to devices
# -------------------------
akku: Optional[PVAkku] = Field(default=None, description="TBD.")
eauto: Optional[PVAkku] = Field(default=None, description="TBD.")
akku: Optional[Battery] = Field(default=None, description="TBD.")
eauto: Optional[Battery] = Field(default=None, description="TBD.")
home_appliance: Optional[HomeAppliance] = Field(default=None, description="TBD.")
inverter: Optional[Inverter] = Field(default=None, description="TBD.")
@@ -168,7 +168,7 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda
def set_parameters(
self,
parameters: EnergieManagementSystemParameters,
eauto: Optional[PVAkku] = None,
eauto: Optional[Battery] = None,
home_appliance: Optional[HomeAppliance] = None,
inverter: Optional[Inverter] = None,
) -> None:
@@ -323,9 +323,9 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda
# Set initial state
if self.akku:
akku_soc_pro_stunde[0] = self.akku.ladezustand_in_prozent()
akku_soc_pro_stunde[0] = self.akku.current_soc_percentage()
if self.eauto:
eauto_soc_pro_stunde[0] = self.eauto.ladezustand_in_prozent()
eauto_soc_pro_stunde[0] = self.eauto.current_soc_percentage()
for stunde in range(start_stunde, ende):
stunde_since_now = stunde - start_stunde
@@ -343,12 +343,12 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda
# E-Auto handling
if self.eauto:
if self.ev_charge_hours[stunde] > 0:
geladene_menge_eauto, verluste_eauto = self.eauto.energie_laden(
geladene_menge_eauto, verluste_eauto = self.eauto.charge_energy(
None, stunde, relative_power=self.ev_charge_hours[stunde]
)
verbrauch += geladene_menge_eauto
verluste_wh_pro_stunde[stunde_since_now] += verluste_eauto
eauto_soc_pro_stunde[stunde_since_now] = self.eauto.ladezustand_in_prozent()
eauto_soc_pro_stunde[stunde_since_now] = self.eauto.current_soc_percentage()
# Process inverter logic
netzeinspeisung, netzbezug, verluste, eigenverbrauch = (0.0, 0.0, 0.0, 0.0)
@@ -363,10 +363,10 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda
# AC PV Battery Charge
if self.akku and self.ac_charge_hours[stunde] > 0.0:
self.akku.set_charge_allowed_for_hour(1, stunde)
geladene_menge, verluste_wh = self.akku.energie_laden(
geladene_menge, verluste_wh = self.akku.charge_energy(
None, stunde, relative_power=self.ac_charge_hours[stunde]
)
# print(stunde, " ", geladene_menge, " ",self.ac_charge_hours[stunde]," ",self.akku.ladezustand_in_prozent())
# print(stunde, " ", geladene_menge, " ",self.ac_charge_hours[stunde]," ",self.akku.current_soc_percentage())
verbrauch += geladene_menge
verbrauch += verluste_wh
netzbezug += geladene_menge
@@ -388,7 +388,7 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda
# Akku SOC tracking
if self.akku:
akku_soc_pro_stunde[stunde_since_now] = self.akku.ladezustand_in_prozent()
akku_soc_pro_stunde[stunde_since_now] = self.akku.current_soc_percentage()
else:
akku_soc_pro_stunde[stunde_since_now] = 0.0

View File

@@ -10,345 +10,279 @@ from akkudoktoreos.utils.utils import NumpyEncoder
logger = get_logger(__name__)
def max_ladeleistung_w_field(default: Optional[float] = None) -> Optional[float]:
def max_charging_power_field(description: Optional[str] = None) -> float:
if description is None:
description = "Maximum charging power in watts."
return Field(
default=default,
default=5000,
gt=0,
description="An integer representing the charging power of the battery in watts.",
description=description,
)
def start_soc_prozent_field(description: str) -> int:
def initial_soc_percentage_field(description: str) -> int:
return Field(default=0, ge=0, le=100, description=description)
class BaseAkkuParameters(BaseModel):
kapazitaet_wh: int = Field(
class BaseBatteryParameters(BaseModel):
"""Base class for battery parameters with fields for capacity, efficiency, and state of charge."""
capacity_wh: int = Field(
gt=0, description="An integer representing the capacity of the battery in watt-hours."
)
lade_effizienz: float = Field(
charging_efficiency: float = Field(
default=0.88,
gt=0,
le=1,
description="A float representing the charging efficiency of the battery.",
)
entlade_effizienz: float = Field(default=0.88, gt=0, le=1)
max_ladeleistung_w: Optional[float] = max_ladeleistung_w_field()
start_soc_prozent: int = start_soc_prozent_field(
discharging_efficiency: float = Field(
default=0.88,
gt=0,
le=1,
description="A float representing the discharge efficiency of the battery.",
)
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_prozent: int = Field(
min_soc_percentage: int = Field(
default=0,
ge=0,
le=100,
description="An integer representing the minimum state of charge (SOC) of the battery in percentage.",
)
max_soc_prozent: int = Field(default=100, ge=0, le=100)
max_soc_percentage: int = Field(
default=100,
ge=0,
le=100,
description="An integer representing the maximum state of charge (SOC) of the battery in percentage.",
)
class PVAkkuParameters(BaseAkkuParameters):
max_ladeleistung_w: Optional[float] = max_ladeleistung_w_field(5000)
class SolarPanelBatteryParameters(BaseBatteryParameters):
max_charge_power_w: Optional[float] = max_charging_power_field()
class EAutoParameters(BaseAkkuParameters):
entlade_effizienz: float = 1.0
start_soc_prozent: int = start_soc_prozent_field(
class ElectricVehicleParameters(BaseBatteryParameters):
"""Parameters specific to an electric vehicle (EV)."""
discharging_efficiency: float = 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 EAutoResult(BaseModel):
"""This object contains information related to the electric vehicle and its charging and discharging behavior."""
class ElectricVehicleResult(BaseModel):
"""Result class containing information related to the electric vehicle's charging and discharging behavior."""
charge_array: list[float] = Field(
description="Indicates for each hour whether the EV is charging (`0` for no charging, `1` for charging)."
description="Hourly charging status (0 for no charging, 1 for charging)."
)
discharge_array: list[int] = Field(
description="Indicates for each hour whether the EV is discharging (`0` for no discharging, `1` for discharging)."
description="Hourly discharging status (0 for no discharging, 1 for discharging)."
)
entlade_effizienz: float = Field(description="The discharge efficiency as a float.")
hours: int = Field(description="Amount of hours the simulation is done for.")
kapazitaet_wh: int = Field(description="The capacity of the EVs battery in watt-hours.")
lade_effizienz: float = Field(description="The charging efficiency as a float.")
max_ladeleistung_w: int = Field(description="The maximum charging power of the EV in watts.")
discharging_efficiency: float = Field(description="The discharge efficiency as a float..")
hours: int = Field(description="Number of hours in the simulation.")
capacity_wh: int = Field(description="Capacity of the EVs battery in watt-hours.")
charging_efficiency: float = Field(description="Charging efficiency as a float..")
max_charge_power_w: int = Field(description="Maximum charging power in watts.")
soc_wh: float = Field(
description="The state of charge of the battery in watt-hours at the start of the simulation."
description="State of charge of the battery in watt-hours at the start of the simulation."
)
start_soc_prozent: int = Field(
description="The state of charge of the battery in percentage at the start of the simulation."
initial_soc_percentage: int = Field(
description="State of charge at the start of the simulation in percentage."
)
@field_validator(
"discharge_array",
"charge_array",
mode="before",
)
@field_validator("discharge_array", "charge_array", mode="before")
def convert_numpy(cls, field: Any) -> Any:
return NumpyEncoder.convert_numpy(field)[0]
class PVAkku(DeviceBase):
class Battery(DeviceBase):
"""Represents a battery device with methods to simulate energy charging and discharging."""
def __init__(
self,
parameters: Optional[BaseAkkuParameters] = None,
parameters: Optional[BaseBatteryParameters] = None,
hours: Optional[int] = 24,
provider_id: Optional[str] = None,
):
# Configuration initialisation
# Initialize configuration and parameters
self.provider_id = provider_id
self.prefix = "<invalid>"
if self.provider_id == "GenericBattery":
self.prefix = "battery"
elif self.provider_id == "GenericBEV":
self.prefix = "bev"
# Parameter initialisiation
self.parameters = parameters
if hours is None:
self.hours = self.total_hours
self.hours = self.total_hours # TODO where does that come from?
else:
self.hours = hours
self.initialised = False
# Run setup if parameters are given, otherwise setup() has to be called later when the config is initialised.
if self.parameters is not None:
self.setup()
def setup(self) -> None:
"""Sets up the battery parameters based on configuration or provided parameters."""
if self.initialised:
return
if self.provider_id is not None:
# Setup by configuration
# Battery capacity in Wh
self.kapazitaet_wh = getattr(self.config, f"{self.prefix}_capacity")
# Initial state of charge in Wh
self.start_soc_prozent = getattr(self.config, f"{self.prefix}_soc_start")
self.hours = self.total_hours
# Charge and discharge efficiency
self.lade_effizienz = getattr(self.config, f"{self.prefix}_charge_efficiency")
self.entlade_effizienz = getattr(self.config, f"{self.prefix}_discharge_efficiency")
self.max_ladeleistung_w = getattr(self.config, f"{self.prefix}_charge_power_max")
# Only assign for storage battery
if self.provider_id:
# Setup from configuration
self.capacity_wh = getattr(self.config, f"{self.prefix}_capacity")
self.initial_soc_percentage = getattr(self.config, f"{self.prefix}_initial_soc")
self.hours = self.total_hours # TODO where does that come from?
self.charging_efficiency = getattr(self.config, f"{self.prefix}_charging_efficiency")
self.discharging_efficiency = getattr(
self.config, f"{self.prefix}_discharging_efficiency"
)
self.max_charge_power_w = getattr(self.config, f"{self.prefix}_max_charging_power")
if self.provider_id == "GenericBattery":
self.min_soc_prozent = getattr(self.config, f"{self.prefix}_soc_mint")
self.min_soc_percentage = getattr(
self.config,
f"{self.prefix}_soc_min",
)
else:
self.min_soc_prozent = 0
self.max_soc_prozent = getattr(self.config, f"{self.prefix}_soc_mint")
elif self.parameters is not None:
# Setup by parameters
# Battery capacity in Wh
self.kapazitaet_wh = self.parameters.kapazitaet_wh
# Initial state of charge in Wh
self.start_soc_prozent = self.parameters.start_soc_prozent
# Charge and discharge efficiency
self.lade_effizienz = self.parameters.lade_effizienz
self.entlade_effizienz = self.parameters.entlade_effizienz
self.max_ladeleistung_w = self.parameters.max_ladeleistung_w
self.min_soc_percentage = 0
self.max_soc_percentage = getattr(
self.config,
f"{self.prefix}_soc_max",
)
elif self.parameters:
# Setup from parameters
self.capacity_wh = self.parameters.capacity_wh
self.initial_soc_percentage = self.parameters.initial_soc_percentage
self.charging_efficiency = self.parameters.charging_efficiency
self.discharging_efficiency = self.parameters.discharging_efficiency
self.max_charge_power_w = self.parameters.max_charge_power_w
# Only assign for storage battery
self.min_soc_prozent = (
self.parameters.min_soc_prozent
if isinstance(self.parameters, PVAkkuParameters)
self.min_soc_percentage = (
self.parameters.min_soc_percentage
if isinstance(self.parameters, SolarPanelBatteryParameters)
else 0
)
self.max_soc_prozent = self.parameters.max_soc_prozent
self.max_soc_percentage = self.parameters.max_soc_percentage
else:
error_msg = "Parameters and provider ID missing. Can't instantiate."
error_msg = "Parameters and provider ID are missing. Cannot instantiate."
logger.error(error_msg)
raise ValueError(error_msg)
# init
if self.max_ladeleistung_w is None:
self.max_ladeleistung_w = self.kapazitaet_wh
# Initialize state of charge
if self.max_charge_power_w is None:
self.max_charge_power_w = self.capacity_wh # TODO this should not be equal capacity_wh
self.discharge_array = np.full(self.hours, 1)
self.charge_array = np.full(self.hours, 1)
# Calculate start, min and max SoC in Wh
self.soc_wh = (self.start_soc_prozent / 100) * self.kapazitaet_wh
self.min_soc_wh = (self.min_soc_prozent / 100) * self.kapazitaet_wh
self.max_soc_wh = (self.max_soc_prozent / 100) * self.kapazitaet_wh
self.soc_wh = (self.initial_soc_percentage / 100) * self.capacity_wh
self.min_soc_wh = (self.min_soc_percentage / 100) * self.capacity_wh
self.max_soc_wh = (self.max_soc_percentage / 100) * self.capacity_wh
self.initialised = True
def to_dict(self) -> dict[str, Any]:
"""Converts the object to a dictionary representation."""
return {
"kapazitaet_wh": self.kapazitaet_wh,
"start_soc_prozent": self.start_soc_prozent,
"capacity_wh": self.capacity_wh,
"initial_soc_percentage": self.initial_soc_percentage,
"soc_wh": self.soc_wh,
"hours": self.hours,
"discharge_array": self.discharge_array,
"charge_array": self.charge_array,
"lade_effizienz": self.lade_effizienz,
"entlade_effizienz": self.entlade_effizienz,
"max_ladeleistung_w": self.max_ladeleistung_w,
"charging_efficiency": self.charging_efficiency,
"discharging_efficiency": self.discharging_efficiency,
"max_charge_power_w": self.max_charge_power_w,
}
def reset(self) -> None:
self.soc_wh = (self.start_soc_prozent / 100) * self.kapazitaet_wh
# Ensure soc_wh is within min and max limits
"""Resets the battery state to its initial values."""
self.soc_wh = (self.initial_soc_percentage / 100) * self.capacity_wh
self.soc_wh = min(max(self.soc_wh, self.min_soc_wh), self.max_soc_wh)
self.discharge_array = np.full(self.hours, 1)
self.charge_array = np.full(self.hours, 1)
def set_discharge_per_hour(self, discharge_array: np.ndarray) -> None:
assert len(discharge_array) == self.hours
"""Sets the discharge values for each hour."""
if len(discharge_array) != self.hours:
raise ValueError(f"Discharge array must have exactly {self.hours} elements.")
self.discharge_array = np.array(discharge_array)
def set_charge_per_hour(self, charge_array: np.ndarray) -> None:
assert len(charge_array) == self.hours
"""Sets the charge values for each hour."""
if len(charge_array) != self.hours:
raise ValueError(f"Charge array must have exactly {self.hours} elements.")
self.charge_array = np.array(charge_array)
def set_charge_allowed_for_hour(self, charge: float, hour: int) -> None:
assert hour < self.hours
"""Sets the charge for a specific hour."""
if hour >= self.hours:
raise ValueError(f"Hour {hour} is out of range. Must be less than {self.hours}.")
self.charge_array[hour] = charge
def ladezustand_in_prozent(self) -> float:
return (self.soc_wh / self.kapazitaet_wh) * 100
def current_soc_percentage(self) -> float:
"""Calculates the current state of charge in percentage."""
return (self.soc_wh / self.capacity_wh) * 100
def energie_abgeben(self, wh: float, hour: int) -> tuple[float, float]:
def discharge_energy(self, wh: float, hour: int) -> tuple[float, float]:
"""Discharges energy from the battery."""
if self.discharge_array[hour] == 0:
return 0.0, 0.0 # No energy discharge and no losses
return 0.0, 0.0
# Calculate the maximum energy that can be discharged considering min_soc and efficiency
max_possible_discharge_wh = (self.soc_wh - self.min_soc_wh) * self.entlade_effizienz
max_possible_discharge_wh = max(max_possible_discharge_wh, 0.0) # Ensure non-negative
max_possible_discharge_wh = (self.soc_wh - self.min_soc_wh) * self.discharging_efficiency
max_possible_discharge_wh = max(max_possible_discharge_wh, 0.0)
# Consider the maximum discharge power of the battery
max_abgebbar_wh = min(max_possible_discharge_wh, self.max_ladeleistung_w)
max_possible_discharge_wh = min(
max_possible_discharge_wh, self.max_charge_power_w
) # TODO make a new cfg variable max_discharge_power_w
# The actually discharged energy cannot exceed requested energy or maximum discharge
tatsaechlich_abgegeben_wh = min(wh, max_abgebbar_wh)
actual_discharge_wh = min(wh, max_possible_discharge_wh)
actual_withdrawal_wh = (
actual_discharge_wh / self.discharging_efficiency
if self.discharging_efficiency > 0
else 0.0
)
# Calculate the actual amount withdrawn from the battery (before efficiency loss)
if self.entlade_effizienz > 0:
tatsaechliche_entnahme_wh = tatsaechlich_abgegeben_wh / self.entlade_effizienz
else:
tatsaechliche_entnahme_wh = 0.0
# Update the state of charge considering the actual withdrawal
self.soc_wh -= tatsaechliche_entnahme_wh
# Ensure soc_wh does not go below min_soc_wh
self.soc_wh -= actual_withdrawal_wh
self.soc_wh = max(self.soc_wh, self.min_soc_wh)
# Calculate losses due to efficiency
verluste_wh = tatsaechliche_entnahme_wh - tatsaechlich_abgegeben_wh
losses_wh = actual_withdrawal_wh - actual_discharge_wh
return actual_discharge_wh, losses_wh
# Return the actually discharged energy and the losses
return tatsaechlich_abgegeben_wh, verluste_wh
def energie_laden(
def charge_energy(
self, wh: Optional[float], hour: int, relative_power: float = 0.0
) -> tuple[float, float]:
"""Charges energy into the battery."""
if hour is not None and self.charge_array[hour] == 0:
return 0.0, 0.0 # Charging not allowed in this hour
if relative_power > 0.0:
wh = self.max_ladeleistung_w * relative_power
# If no value for wh is given, use the maximum charging power
wh = wh if wh is not None else self.max_ladeleistung_w
wh = self.max_charge_power_w * relative_power
# Calculate the maximum energy that can be charged considering max_soc and efficiency
if self.lade_effizienz > 0:
max_possible_charge_wh = (self.max_soc_wh - self.soc_wh) / self.lade_effizienz
else:
max_possible_charge_wh = 0.0
max_possible_charge_wh = max(max_possible_charge_wh, 0.0) # Ensure non-negative
wh = wh if wh is not None else self.max_charge_power_w
# The actually charged energy cannot exceed requested energy, charging power, or maximum possible charge
effektive_lademenge = min(wh, max_possible_charge_wh)
max_possible_charge_wh = (
(self.max_soc_wh - self.soc_wh) / self.charging_efficiency
if self.charging_efficiency > 0
else 0.0
)
max_possible_charge_wh = max(max_possible_charge_wh, 0.0)
# Energy actually stored in the battery
geladene_menge = effektive_lademenge * self.lade_effizienz
effective_charge_wh = min(wh, max_possible_charge_wh)
charged_wh = effective_charge_wh * self.charging_efficiency
# Update soc_wh
self.soc_wh += geladene_menge
# Ensure soc_wh does not exceed max_soc_wh
self.soc_wh += charged_wh
self.soc_wh = min(self.soc_wh, self.max_soc_wh)
# Calculate losses
verluste_wh = effektive_lademenge - geladene_menge
return geladene_menge, verluste_wh
losses_wh = effective_charge_wh - charged_wh
return charged_wh, losses_wh
def aktueller_energieinhalt(self) -> float:
"""This method returns the current remaining energy considering efficiency.
It accounts for both charging and discharging efficiency.
"""
# Calculate remaining energy considering discharge efficiency
nutzbare_energie = (self.soc_wh - self.min_soc_wh) * self.entlade_effizienz
return max(nutzbare_energie, 0.0)
if __name__ == "__main__":
# Test battery discharge below min_soc
print("Test: Discharge below min_soc")
akku = PVAkku(
PVAkkuParameters(
kapazitaet_wh=10000,
start_soc_prozent=50,
min_soc_prozent=20,
max_soc_prozent=80,
),
hours=1,
)
akku.reset()
print(f"Initial SoC: {akku.ladezustand_in_prozent()}%")
# Try to discharge 5000 Wh
abgegeben_wh, verlust_wh = akku.energie_abgeben(5000, 0)
print(f"Energy discharged: {abgegeben_wh} Wh, Losses: {verlust_wh} Wh")
print(f"SoC after discharge: {akku.ladezustand_in_prozent()}%")
print(f"Expected min SoC: {akku.min_soc_prozent}%")
# Test battery charge above max_soc
print("\nTest: Charge above max_soc")
akku = PVAkku(
PVAkkuParameters(
kapazitaet_wh=10000,
start_soc_prozent=50,
min_soc_prozent=20,
max_soc_prozent=80,
),
hours=1,
)
akku.reset()
print(f"Initial SoC: {akku.ladezustand_in_prozent()}%")
# Try to charge 5000 Wh
geladen_wh, verlust_wh = akku.energie_laden(5000, 0)
print(f"Energy charged: {geladen_wh} Wh, Losses: {verlust_wh} Wh")
print(f"SoC after charge: {akku.ladezustand_in_prozent()}%")
print(f"Expected max SoC: {akku.max_soc_prozent}%")
# Test charging when battery is at max_soc
print("\nTest: Charging when at max_soc")
akku = PVAkku(
PVAkkuParameters(
kapazitaet_wh=10000,
start_soc_prozent=80,
min_soc_prozent=20,
max_soc_prozent=80,
),
hours=1,
)
akku.reset()
print(f"Initial SoC: {akku.ladezustand_in_prozent()}%")
geladen_wh, verlust_wh = akku.energie_laden(5000, 0)
print(f"Energy charged: {geladen_wh} Wh, Losses: {verlust_wh} Wh")
print(f"SoC after charge: {akku.ladezustand_in_prozent()}%")
# Test discharging when battery is at min_soc
print("\nTest: Discharging when at min_soc")
akku = PVAkku(
PVAkkuParameters(
kapazitaet_wh=10000,
start_soc_prozent=20,
min_soc_prozent=20,
max_soc_prozent=80,
),
hours=1,
)
akku.reset()
print(f"Initial SoC: {akku.ladezustand_in_prozent()}%")
abgegeben_wh, verlust_wh = akku.energie_abgeben(5000, 0)
print(f"Energy discharged: {abgegeben_wh} Wh, Losses: {verlust_wh} Wh")
print(f"SoC after discharge: {akku.ladezustand_in_prozent()}%")
def current_energy_content(self) -> float:
"""Returns the current usable energy in the battery."""
usable_energy = (self.soc_wh - self.min_soc_wh) * self.discharging_efficiency
return max(usable_energy, 0.0)

View File

@@ -6,7 +6,7 @@ from pydantic import Field, computed_field
from akkudoktoreos.config.configabc import SettingsBaseModel
from akkudoktoreos.core.coreabc import SingletonMixin
from akkudoktoreos.devices.battery import PVAkku
from akkudoktoreos.devices.battery import Battery
from akkudoktoreos.devices.devicesabc import DevicesBase
from akkudoktoreos.devices.generic import HomeAppliance
from akkudoktoreos.devices.inverter import Inverter
@@ -25,7 +25,7 @@ class DevicesCommonSettings(SettingsBaseModel):
default=None, description="Id of Battery simulation provider."
)
battery_capacity: Optional[int] = Field(default=None, description="Battery capacity [Wh].")
battery_soc_start: Optional[int] = Field(
battery_initial_soc: Optional[int] = Field(
default=None, description="Battery initial state of charge [%]."
)
battery_soc_min: Optional[int] = Field(
@@ -34,13 +34,13 @@ class DevicesCommonSettings(SettingsBaseModel):
battery_soc_max: Optional[int] = Field(
default=None, description="Battery maximum state of charge [%]."
)
battery_charge_efficiency: Optional[float] = Field(
battery_charging_efficiency: Optional[float] = Field(
default=None, description="Battery charging efficiency [%]."
)
battery_discharge_efficiency: Optional[float] = Field(
battery_discharging_efficiency: Optional[float] = Field(
default=None, description="Battery discharging efficiency [%]."
)
battery_charge_power_max: Optional[int] = Field(
battery_max_charging_power: Optional[int] = Field(
default=None, description="Battery maximum charge power [W]."
)
@@ -52,19 +52,19 @@ class DevicesCommonSettings(SettingsBaseModel):
bev_capacity: Optional[int] = Field(
default=None, description="Battery Electric Vehicle capacity [Wh]."
)
bev_soc_start: Optional[int] = Field(
bev_initial_soc: Optional[int] = Field(
default=None, description="Battery Electric Vehicle initial state of charge [%]."
)
bev_soc_max: Optional[int] = Field(
default=None, description="Battery Electric Vehicle maximum state of charge [%]."
)
bev_charge_efficiency: Optional[float] = Field(
bev_charging_efficiency: Optional[float] = Field(
default=None, description="Battery Electric Vehicle charging efficiency [%]."
)
bev_discharge_efficiency: Optional[float] = Field(
bev_discharging_efficiency: Optional[float] = Field(
default=None, description="Battery Electric Vehicle discharging efficiency [%]."
)
bev_charge_power_max: Optional[int] = Field(
bev_max_charging_power: Optional[int] = Field(
default=None, description="Battery Electric Vehicle maximum charge power [W]."
)
@@ -159,8 +159,8 @@ class Devices(SingletonMixin, DevicesBase):
# Devices
# TODO: Make devices class a container of device simulation providers.
# Device simulations to be used are then enabled in the configuration.
akku: ClassVar[PVAkku] = PVAkku(provider_id="GenericBattery")
eauto: ClassVar[PVAkku] = PVAkku(provider_id="GenericBEV")
akku: ClassVar[Battery] = Battery(provider_id="GenericBattery")
eauto: ClassVar[Battery] = Battery(provider_id="GenericBEV")
home_appliance: ClassVar[HomeAppliance] = HomeAppliance(provider_id="GenericDishWasher")
inverter: ClassVar[Inverter] = Inverter(akku=akku, provider_id="GenericInverter")
@@ -186,9 +186,9 @@ class Devices(SingletonMixin, DevicesBase):
# Set initial state
simulation_step = to_duration("1 hour")
if self.akku:
self.akku_soc_pro_stunde[0] = self.akku.ladezustand_in_prozent()
self.akku_soc_pro_stunde[0] = self.akku.current_soc_percentage()
if self.eauto:
self.eauto_soc_pro_stunde[0] = self.eauto.ladezustand_in_prozent()
self.eauto_soc_pro_stunde[0] = self.eauto.current_soc_percentage()
# Get predictions for full device simulation time range
# gesamtlast[stunde]
@@ -232,12 +232,12 @@ class Devices(SingletonMixin, DevicesBase):
# E-Auto handling
if self.eauto:
if self.ev_charge_hours[hour] > 0:
geladene_menge_eauto, verluste_eauto = self.eauto.energie_laden(
geladene_menge_eauto, verluste_eauto = self.eauto.charge_energy(
None, hour, relative_power=self.ev_charge_hours[hour]
)
consumption += geladene_menge_eauto
self.verluste_wh_pro_stunde[stunde_since_now] += verluste_eauto
self.eauto_soc_pro_stunde[stunde_since_now] = self.eauto.ladezustand_in_prozent()
self.eauto_soc_pro_stunde[stunde_since_now] = self.eauto.current_soc_percentage()
# Process inverter logic
grid_export, grid_import, losses, self_consumption = (0.0, 0.0, 0.0, 0.0)
@@ -252,10 +252,10 @@ class Devices(SingletonMixin, DevicesBase):
# AC PV Battery Charge
if self.akku and self.ac_charge_hours[hour] > 0.0:
self.akku.set_charge_allowed_for_hour(1, hour)
geladene_menge, verluste_wh = self.akku.energie_laden(
geladene_menge, verluste_wh = self.akku.charge_energy(
None, hour, relative_power=self.ac_charge_hours[hour]
)
# print(stunde, " ", geladene_menge, " ",self.ac_charge_hours[stunde]," ",self.akku.ladezustand_in_prozent())
# print(stunde, " ", geladene_menge, " ",self.ac_charge_hours[stunde]," ",self.akku.current_soc_percentage())
consumption += geladene_menge
grid_import += geladene_menge
self.verluste_wh_pro_stunde[stunde_since_now] += verluste_wh
@@ -275,7 +275,7 @@ class Devices(SingletonMixin, DevicesBase):
# Akku SOC tracking
if self.akku:
self.akku_soc_pro_stunde[stunde_since_now] = self.akku.ladezustand_in_prozent()
self.akku_soc_pro_stunde[stunde_since_now] = self.akku.current_soc_percentage()
else:
self.akku_soc_pro_stunde[stunde_since_now] = 0.0

View File

@@ -2,7 +2,7 @@ from typing import Optional, Tuple
from pydantic import BaseModel, Field
from akkudoktoreos.devices.battery import PVAkku
from akkudoktoreos.devices.battery import Battery
from akkudoktoreos.devices.devicesabc import DeviceBase
from akkudoktoreos.utils.logutil import get_logger
@@ -17,7 +17,7 @@ class Inverter(DeviceBase):
def __init__(
self,
parameters: Optional[InverterParameters] = None,
akku: Optional[PVAkku] = None,
akku: Optional[Battery] = None,
provider_id: Optional[str] = None,
):
# Configuration initialisation
@@ -70,7 +70,7 @@ class Inverter(DeviceBase):
remaining_energy = generation - actual_consumption
# Charge battery with excess energy
charged_energy, charging_losses = self.akku.energie_laden(remaining_energy, hour)
charged_energy, charging_losses = self.akku.charge_energy(remaining_energy, hour)
losses += charging_losses
# Calculate remaining surplus after battery charge
@@ -87,7 +87,7 @@ class Inverter(DeviceBase):
available_ac_power = max(self.max_power_wh - generation, 0)
# Discharge battery to cover shortfall, if possible
battery_discharge, discharge_losses = self.akku.energie_abgeben(
battery_discharge, discharge_losses = self.akku.discharge_energy(
min(shortfall, available_ac_power), hour
)
losses += discharge_losses

View File

@@ -13,10 +13,10 @@ from akkudoktoreos.core.coreabc import (
)
from akkudoktoreos.core.ems import EnergieManagementSystemParameters, SimulationResult
from akkudoktoreos.devices.battery import (
EAutoParameters,
EAutoResult,
PVAkku,
PVAkkuParameters,
Battery,
ElectricVehicleParameters,
ElectricVehicleResult,
SolarPanelBatteryParameters,
)
from akkudoktoreos.devices.generic import HomeAppliance, HomeApplianceParameters
from akkudoktoreos.devices.inverter import Inverter, InverterParameters
@@ -26,9 +26,9 @@ from akkudoktoreos.visualize import visualisiere_ergebnisse
class OptimizationParameters(BaseModel):
ems: EnergieManagementSystemParameters
pv_akku: PVAkkuParameters
pv_akku: SolarPanelBatteryParameters
inverter: InverterParameters = InverterParameters()
eauto: Optional[EAutoParameters]
eauto: Optional[ElectricVehicleParameters]
dishwasher: Optional[HomeApplianceParameters] = None
temperature_forecast: Optional[list[float]] = Field(
default=None,
@@ -68,7 +68,7 @@ class OptimizeResponse(BaseModel):
)
eautocharge_hours_float: Optional[list[float]] = Field(description="TBD")
result: SimulationResult
eauto_obj: Optional[EAutoResult]
eauto_obj: Optional[ElectricVehicleResult]
start_solution: Optional[list[float]] = Field(
default=None,
description="An array of binary values (0 or 1) representing a possible starting solution for the simulation.",
@@ -92,8 +92,8 @@ class OptimizeResponse(BaseModel):
mode="before",
)
def convert_eauto(cls, field: Any) -> Any:
if isinstance(field, PVAkku):
return EAutoResult(**field.to_dict())
if isinstance(field, Battery):
return ElectricVehicleResult(**field.to_dict())
return field
@@ -367,7 +367,7 @@ class optimization_problem(ConfigMixin, DevicesMixin, EnergyManagementSystemMixi
)
# Penalty for not meeting the minimum SOC (State of Charge) requirement
# if parameters.eauto_min_soc_prozent - ems.eauto.ladezustand_in_prozent() <= 0.0 and self.optimize_ev:
# if parameters.eauto_min_soc_prozent - ems.eauto.current_soc_percentage() <= 0.0 and self.optimize_ev:
# gesamtbilanz += sum(
# self.config.optimization_penalty for ladeleistung in eautocharge_hours_float if ladeleistung != 0.0
# )
@@ -375,7 +375,7 @@ class optimization_problem(ConfigMixin, DevicesMixin, EnergyManagementSystemMixi
individual.extra_data = ( # type: ignore[attr-defined]
o["Gesamtbilanz_Euro"],
o["Gesamt_Verluste"],
parameters.eauto.min_soc_prozent - self.ems.eauto.ladezustand_in_prozent()
parameters.eauto.min_soc_percentage - self.ems.eauto.current_soc_percentage()
if parameters.eauto and self.ems.eauto
else 0,
)
@@ -383,16 +383,16 @@ class optimization_problem(ConfigMixin, DevicesMixin, EnergyManagementSystemMixi
# Adjust total balance with battery value and penalties for unmet SOC
restwert_akku = (
self.ems.akku.aktueller_energieinhalt() * parameters.ems.preis_euro_pro_wh_akku
self.ems.akku.current_energy_content() * parameters.ems.preis_euro_pro_wh_akku
)
# print(ems.akku.aktueller_energieinhalt()," * ", parameters.ems.preis_euro_pro_wh_akku , " ", restwert_akku, " ", gesamtbilanz)
# print(ems.akku.current_energy_content()," * ", parameters.ems.preis_euro_pro_wh_akku , " ", restwert_akku, " ", gesamtbilanz)
gesamtbilanz += -restwert_akku
# print(gesamtbilanz)
if self.optimize_ev:
gesamtbilanz += max(
0,
(
parameters.eauto.min_soc_prozent - self.ems.eauto.ladezustand_in_prozent()
parameters.eauto.min_soc_percentage - self.ems.eauto.current_soc_percentage()
if parameters.eauto and self.ems.eauto
else 0
)
@@ -458,21 +458,21 @@ class optimization_problem(ConfigMixin, DevicesMixin, EnergyManagementSystemMixi
)
# Initialize PV and EV batteries
akku = PVAkku(
akku = Battery(
parameters.pv_akku,
hours=self.config.prediction_hours,
)
akku.set_charge_per_hour(np.full(self.config.prediction_hours, 1))
eauto: Optional[PVAkku] = None
eauto: Optional[Battery] = None
if parameters.eauto:
eauto = PVAkku(
eauto = Battery(
parameters.eauto,
hours=self.config.prediction_hours,
)
eauto.set_charge_per_hour(np.full(self.config.prediction_hours, 1))
self.optimize_ev = (
parameters.eauto.min_soc_prozent - parameters.eauto.start_soc_prozent >= 0
parameters.eauto.min_soc_percentage - parameters.eauto.initial_soc_percentage >= 0
)
else:
self.optimize_ev = False