mirror of
				https://github.com/Akkudoktor-EOS/EOS.git
				synced 2025-10-30 22:36:21 +00:00 
			
		
		
		
	Inverter v2 (#245)
* inverter class rewritten second try * cleanup * inverter section of decives.py translation * open api fix * fix openapi v2 * renamed the class itself * ruff fix * Update genetic.py * cleanup * reverted indent
This commit is contained in:
		| @@ -10,7 +10,7 @@ from akkudoktoreos.core.coreabc import ConfigMixin, PredictionMixin, SingletonMi | ||||
| from akkudoktoreos.core.pydantic import PydanticBaseModel | ||||
| from akkudoktoreos.devices.battery import PVAkku | ||||
| from akkudoktoreos.devices.generic import HomeAppliance | ||||
| from akkudoktoreos.devices.inverter import Wechselrichter | ||||
| from akkudoktoreos.devices.inverter import Inverter | ||||
| from akkudoktoreos.utils.datetimeutil import to_datetime | ||||
| from akkudoktoreos.utils.logutil import get_logger | ||||
| from akkudoktoreos.utils.utils import NumpyEncoder | ||||
| @@ -155,7 +155,7 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda | ||||
|     akku: Optional[PVAkku] = Field(default=None, description="TBD.") | ||||
|     eauto: Optional[PVAkku] = Field(default=None, description="TBD.") | ||||
|     home_appliance: Optional[HomeAppliance] = Field(default=None, description="TBD.") | ||||
|     wechselrichter: Optional[Wechselrichter] = Field(default=None, description="TBD.") | ||||
|     inverter: Optional[Inverter] = Field(default=None, description="TBD.") | ||||
|  | ||||
|     # ------------------------- | ||||
|     # TODO: Move to devices | ||||
| @@ -170,7 +170,7 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda | ||||
|         parameters: EnergieManagementSystemParameters, | ||||
|         eauto: Optional[PVAkku] = None, | ||||
|         home_appliance: Optional[HomeAppliance] = None, | ||||
|         wechselrichter: Optional[Wechselrichter] = None, | ||||
|         inverter: Optional[Inverter] = None, | ||||
|     ) -> None: | ||||
|         self.gesamtlast = np.array(parameters.gesamtlast, float) | ||||
|         self.pv_prognose_wh = np.array(parameters.pv_prognose_wh, float) | ||||
| @@ -180,13 +180,13 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda | ||||
|             if isinstance(parameters.einspeiseverguetung_euro_pro_wh, list) | ||||
|             else np.full(len(self.gesamtlast), parameters.einspeiseverguetung_euro_pro_wh, float) | ||||
|         ) | ||||
|         if wechselrichter is not None: | ||||
|             self.akku = wechselrichter.akku | ||||
|         if inverter is not None: | ||||
|             self.akku = inverter.akku | ||||
|         else: | ||||
|             self.akku = None | ||||
|         self.eauto = eauto | ||||
|         self.home_appliance = home_appliance | ||||
|         self.wechselrichter = wechselrichter | ||||
|         self.inverter = inverter | ||||
|         self.ac_charge_hours = np.full(self.config.prediction_hours, 0.0) | ||||
|         self.dc_charge_hours = np.full(self.config.prediction_hours, 1.0) | ||||
|         self.ev_charge_hours = np.full(self.config.prediction_hours, 0.0) | ||||
| @@ -354,10 +354,10 @@ class EnergieManagementSystem(SingletonMixin, ConfigMixin, PredictionMixin, Pyda | ||||
|             netzeinspeisung, netzbezug, verluste, eigenverbrauch = (0.0, 0.0, 0.0, 0.0) | ||||
|             if self.akku: | ||||
|                 self.akku.set_charge_allowed_for_hour(self.dc_charge_hours[stunde], stunde) | ||||
|             if self.wechselrichter: | ||||
|             if self.inverter: | ||||
|                 erzeugung = self.pv_prognose_wh[stunde] | ||||
|                 netzeinspeisung, netzbezug, verluste, eigenverbrauch = ( | ||||
|                     self.wechselrichter.energie_verarbeiten(erzeugung, verbrauch, stunde) | ||||
|                 netzeinspeisung, netzbezug, verluste, eigenverbrauch = self.inverter.process_energy( | ||||
|                     erzeugung, verbrauch, stunde | ||||
|                 ) | ||||
|  | ||||
|             # AC PV Battery Charge | ||||
|   | ||||
| @@ -9,7 +9,7 @@ from akkudoktoreos.core.coreabc import SingletonMixin | ||||
| from akkudoktoreos.devices.battery import PVAkku | ||||
| from akkudoktoreos.devices.devicesabc import DevicesBase | ||||
| from akkudoktoreos.devices.generic import HomeAppliance | ||||
| from akkudoktoreos.devices.inverter import Wechselrichter | ||||
| from akkudoktoreos.devices.inverter import Inverter | ||||
| from akkudoktoreos.utils.datetimeutil import to_duration | ||||
| from akkudoktoreos.utils.logutil import get_logger | ||||
|  | ||||
| @@ -111,10 +111,10 @@ class Devices(SingletonMixin, DevicesBase): | ||||
|     kosten_euro_pro_stunde: Optional[NDArray[Shape["*"], float]] = Field( | ||||
|         default=None, description="The costs in euros per hour." | ||||
|     ) | ||||
|     netzbezug_wh_pro_stunde: Optional[NDArray[Shape["*"], float]] = Field( | ||||
|     grid_import_wh_pro_stunde: Optional[NDArray[Shape["*"], float]] = Field( | ||||
|         default=None, description="The grid energy drawn in watt-hours per hour." | ||||
|     ) | ||||
|     netzeinspeisung_wh_pro_stunde: Optional[NDArray[Shape["*"], float]] = Field( | ||||
|     grid_export_wh_pro_stunde: Optional[NDArray[Shape["*"], float]] = Field( | ||||
|         default=None, description="The energy fed into the grid in watt-hours per hour." | ||||
|     ) | ||||
|     verluste_wh_pro_stunde: Optional[NDArray[Shape["*"], float]] = Field( | ||||
| @@ -162,9 +162,7 @@ class Devices(SingletonMixin, DevicesBase): | ||||
|     akku: ClassVar[PVAkku] = PVAkku(provider_id="GenericBattery") | ||||
|     eauto: ClassVar[PVAkku] = PVAkku(provider_id="GenericBEV") | ||||
|     home_appliance: ClassVar[HomeAppliance] = HomeAppliance(provider_id="GenericDishWasher") | ||||
|     wechselrichter: ClassVar[Wechselrichter] = Wechselrichter( | ||||
|         akku=akku, provider_id="GenericInverter" | ||||
|     ) | ||||
|     inverter: ClassVar[Inverter] = Inverter(akku=akku, provider_id="GenericInverter") | ||||
|  | ||||
|     def update_data(self) -> None: | ||||
|         """Update device simulation data.""" | ||||
| @@ -172,12 +170,12 @@ class Devices(SingletonMixin, DevicesBase): | ||||
|         self.akku.setup() | ||||
|         self.eauto.setup() | ||||
|         self.home_appliance.setup() | ||||
|         self.wechselrichter.setup() | ||||
|         self.inverter.setup() | ||||
|  | ||||
|         # Pre-allocate arrays for the results, optimized for speed | ||||
|         self.last_wh_pro_stunde = np.full((self.total_hours), np.nan) | ||||
|         self.netzeinspeisung_wh_pro_stunde = np.full((self.total_hours), np.nan) | ||||
|         self.netzbezug_wh_pro_stunde = np.full((self.total_hours), np.nan) | ||||
|         self.grid_export_wh_pro_stunde = np.full((self.total_hours), np.nan) | ||||
|         self.grid_import_wh_pro_stunde = np.full((self.total_hours), np.nan) | ||||
|         self.kosten_euro_pro_stunde = np.full((self.total_hours), np.nan) | ||||
|         self.einnahmen_euro_pro_stunde = np.full((self.total_hours), np.nan) | ||||
|         self.akku_soc_pro_stunde = np.full((self.total_hours), np.nan) | ||||
| @@ -219,60 +217,60 @@ class Devices(SingletonMixin, DevicesBase): | ||||
|         einspeiseverguetung_euro_pro_wh_arr = np.full((self.total_hours), 0.078) | ||||
|  | ||||
|         for stunde_since_now in range(0, self.total_hours): | ||||
|             stunde = self.start_datetime.hour + stunde_since_now | ||||
|             hour = self.start_datetime.hour + stunde_since_now | ||||
|  | ||||
|             # Accumulate loads and PV generation | ||||
|             verbrauch = load_total_mean[stunde_since_now] | ||||
|             consumption = load_total_mean[stunde_since_now] | ||||
|             self.verluste_wh_pro_stunde[stunde_since_now] = 0.0 | ||||
|  | ||||
|             # Home appliances | ||||
|             if self.home_appliance: | ||||
|                 ha_load = self.home_appliance.get_load_for_hour(stunde) | ||||
|                 verbrauch += ha_load | ||||
|                 ha_load = self.home_appliance.get_load_for_hour(hour) | ||||
|                 consumption += ha_load | ||||
|                 self.home_appliance_wh_per_hour[stunde_since_now] = ha_load | ||||
|  | ||||
|             # E-Auto handling | ||||
|             if self.eauto: | ||||
|                 if self.ev_charge_hours[stunde] > 0: | ||||
|                 if self.ev_charge_hours[hour] > 0: | ||||
|                     geladene_menge_eauto, verluste_eauto = self.eauto.energie_laden( | ||||
|                         None, stunde, relative_power=self.ev_charge_hours[stunde] | ||||
|                         None, hour, relative_power=self.ev_charge_hours[hour] | ||||
|                     ) | ||||
|                     verbrauch += geladene_menge_eauto | ||||
|                     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() | ||||
|  | ||||
|             # Process inverter logic | ||||
|             netzeinspeisung, netzbezug, verluste, eigenverbrauch = (0.0, 0.0, 0.0, 0.0) | ||||
|             grid_export, grid_import, losses, self_consumption = (0.0, 0.0, 0.0, 0.0) | ||||
|             if self.akku: | ||||
|                 self.akku.set_charge_allowed_for_hour(self.dc_charge_hours[stunde], stunde) | ||||
|             if self.wechselrichter: | ||||
|                 erzeugung = pvforecast_ac_power[stunde] | ||||
|                 netzeinspeisung, netzbezug, verluste, eigenverbrauch = ( | ||||
|                     self.wechselrichter.energie_verarbeiten(erzeugung, verbrauch, stunde) | ||||
|                 self.akku.set_charge_allowed_for_hour(self.dc_charge_hours[hour], hour) | ||||
|             if self.inverter: | ||||
|                 generation = pvforecast_ac_power[hour] | ||||
|                 grid_export, grid_import, losses, self_consumption = self.inverter.process_energy( | ||||
|                     generation, consumption, hour | ||||
|                 ) | ||||
|  | ||||
|             # AC PV Battery Charge | ||||
|             if self.akku and self.ac_charge_hours[stunde] > 0.0: | ||||
|                 self.akku.set_charge_allowed_for_hour(1, stunde) | ||||
|             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( | ||||
|                     None, stunde, relative_power=self.ac_charge_hours[stunde] | ||||
|                     None, hour, relative_power=self.ac_charge_hours[hour] | ||||
|                 ) | ||||
|                 # print(stunde, " ", geladene_menge, " ",self.ac_charge_hours[stunde]," ",self.akku.ladezustand_in_prozent()) | ||||
|                 verbrauch += geladene_menge | ||||
|                 netzbezug += geladene_menge | ||||
|                 consumption += geladene_menge | ||||
|                 grid_import += geladene_menge | ||||
|                 self.verluste_wh_pro_stunde[stunde_since_now] += verluste_wh | ||||
|  | ||||
|             self.netzeinspeisung_wh_pro_stunde[stunde_since_now] = netzeinspeisung | ||||
|             self.netzbezug_wh_pro_stunde[stunde_since_now] = netzbezug | ||||
|             self.verluste_wh_pro_stunde[stunde_since_now] += verluste | ||||
|             self.last_wh_pro_stunde[stunde_since_now] = verbrauch | ||||
|             self.grid_export_wh_pro_stunde[stunde_since_now] = grid_export | ||||
|             self.grid_import_wh_pro_stunde[stunde_since_now] = grid_import | ||||
|             self.verluste_wh_pro_stunde[stunde_since_now] += losses | ||||
|             self.last_wh_pro_stunde[stunde_since_now] = consumption | ||||
|  | ||||
|             # Financial calculations | ||||
|             self.kosten_euro_pro_stunde[stunde_since_now] = ( | ||||
|                 netzbezug * self.strompreis_euro_pro_wh[stunde] | ||||
|                 grid_import * self.strompreis_euro_pro_wh[hour] | ||||
|             ) | ||||
|             self.einnahmen_euro_pro_stunde[stunde_since_now] = ( | ||||
|                 netzeinspeisung * self.einspeiseverguetung_euro_pro_wh_arr[stunde] | ||||
|                 grid_export * self.einspeiseverguetung_euro_pro_wh_arr[hour] | ||||
|             ) | ||||
|  | ||||
|             # Akku SOC tracking | ||||
| @@ -285,8 +283,8 @@ class Devices(SingletonMixin, DevicesBase): | ||||
|         """Provides devices simulation output as a dictionary.""" | ||||
|         out: Dict[str, Optional[Union[np.ndarray, float]]] = { | ||||
|             "Last_Wh_pro_Stunde": self.last_wh_pro_stunde, | ||||
|             "Netzeinspeisung_Wh_pro_Stunde": self.netzeinspeisung_wh_pro_stunde, | ||||
|             "Netzbezug_Wh_pro_Stunde": self.netzbezug_wh_pro_stunde, | ||||
|             "grid_export_Wh_pro_Stunde": self.grid_export_wh_pro_stunde, | ||||
|             "grid_import_Wh_pro_Stunde": self.grid_import_wh_pro_stunde, | ||||
|             "Kosten_Euro_pro_Stunde": self.kosten_euro_pro_stunde, | ||||
|             "akku_soc_pro_stunde": self.akku_soc_pro_stunde, | ||||
|             "Einnahmen_Euro_pro_Stunde": self.einnahmen_euro_pro_stunde, | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| from typing import Optional | ||||
| from typing import Optional, Tuple | ||||
|  | ||||
| from pydantic import BaseModel, Field | ||||
|  | ||||
| @@ -9,14 +9,14 @@ from akkudoktoreos.utils.logutil import get_logger | ||||
| logger = get_logger(__name__) | ||||
|  | ||||
|  | ||||
| class WechselrichterParameters(BaseModel): | ||||
|     max_leistung_wh: float = Field(default=10000, gt=0) | ||||
| class InverterParameters(BaseModel): | ||||
|     max_power_wh: float = Field(default=10000, gt=0) | ||||
|  | ||||
|  | ||||
| class Wechselrichter(DeviceBase): | ||||
| class Inverter(DeviceBase): | ||||
|     def __init__( | ||||
|         self, | ||||
|         parameters: Optional[WechselrichterParameters] = None, | ||||
|         parameters: Optional[InverterParameters] = None, | ||||
|         akku: Optional[PVAkku] = None, | ||||
|         provider_id: Optional[str] = None, | ||||
|     ): | ||||
| @@ -45,69 +45,55 @@ class Wechselrichter(DeviceBase): | ||||
|             return | ||||
|         if self.provider_id is not None: | ||||
|             # Setup by configuration | ||||
|             self.max_leistung_wh = getattr(self.config, f"{self.prefix}_power_max") | ||||
|             self.max_power_wh = getattr(self.config, f"{self.prefix}_power_max") | ||||
|         elif self.parameters is not None: | ||||
|             # Setup by parameters | ||||
|             self.max_leistung_wh = ( | ||||
|                 self.parameters.max_leistung_wh  # Maximum power that the inverter can handle | ||||
|             self.max_power_wh = ( | ||||
|                 self.parameters.max_power_wh  # Maximum power that the inverter can handle | ||||
|             ) | ||||
|         else: | ||||
|             error_msg = "Parameters and provider ID missing. Can't instantiate." | ||||
|             logger.error(error_msg) | ||||
|             raise ValueError(error_msg) | ||||
|  | ||||
|     def energie_verarbeiten( | ||||
|         self, erzeugung: float, verbrauch: float, hour: int | ||||
|     ) -> tuple[float, float, float, float]: | ||||
|         verluste = 0.0  # Losses during processing | ||||
|         netzeinspeisung = 0.0  # Grid feed-in | ||||
|         netzbezug = 0.0  # Grid draw | ||||
|         eigenverbrauch = 0.0  # Self-consumption | ||||
|     def process_energy( | ||||
|         self, generation: float, consumption: float, hour: int | ||||
|     ) -> Tuple[float, float, float, float]: | ||||
|         losses = 0.0 | ||||
|         grid_export = 0.0 | ||||
|         grid_import = 0.0 | ||||
|         self_consumption = 0.0 | ||||
|  | ||||
|         if erzeugung >= verbrauch: | ||||
|             if verbrauch > self.max_leistung_wh: | ||||
|                 # If consumption exceeds maximum inverter power | ||||
|                 verluste += erzeugung - self.max_leistung_wh | ||||
|                 restleistung_nach_verbrauch = self.max_leistung_wh - verbrauch | ||||
|                 netzbezug = -restleistung_nach_verbrauch  # Negative indicates feeding into the grid | ||||
|                 eigenverbrauch = self.max_leistung_wh | ||||
|             else: | ||||
|                 # Remaining power after consumption | ||||
|                 restleistung_nach_verbrauch = erzeugung - verbrauch | ||||
|         if generation >= consumption: | ||||
|             # Case 1: Sufficient or excess generation | ||||
|             actual_consumption = min(consumption, self.max_power_wh) | ||||
|             remaining_energy = generation - actual_consumption | ||||
|  | ||||
|                 # Load battery with excess energy | ||||
|                 geladene_energie, verluste_laden_akku = self.akku.energie_laden( | ||||
|                     restleistung_nach_verbrauch, hour | ||||
|                 ) | ||||
|                 rest_überschuss = restleistung_nach_verbrauch - ( | ||||
|                     geladene_energie + verluste_laden_akku | ||||
|                 ) | ||||
|             # Charge battery with excess energy | ||||
|             charged_energy, charging_losses = self.akku.energie_laden(remaining_energy, hour) | ||||
|             losses += charging_losses | ||||
|  | ||||
|                 # Feed-in to the grid based on remaining capacity | ||||
|                 if rest_überschuss > self.max_leistung_wh - verbrauch: | ||||
|                     netzeinspeisung = self.max_leistung_wh - verbrauch | ||||
|                     verluste += rest_überschuss - netzeinspeisung | ||||
|                 else: | ||||
|                     netzeinspeisung = rest_überschuss | ||||
|             # Calculate remaining surplus after battery charge | ||||
|             remaining_surplus = remaining_energy - (charged_energy + charging_losses) | ||||
|             grid_export = min(remaining_surplus, self.max_power_wh - actual_consumption) | ||||
|  | ||||
|                 verluste += verluste_laden_akku | ||||
|                 eigenverbrauch = verbrauch  # Self-consumption is equal to the load | ||||
|             # If any remaining surplus can't be fed to the grid, count as losses | ||||
|             losses += max(remaining_surplus - grid_export, 0) | ||||
|             self_consumption = actual_consumption | ||||
|  | ||||
|         else: | ||||
|             benötigte_energie = verbrauch - erzeugung  # Energy needed from external sources | ||||
|             max_akku_leistung = self.akku.max_ladeleistung_w  # Maximum battery discharge power | ||||
|             # Case 2: Insufficient generation, cover shortfall | ||||
|             shortfall = consumption - generation | ||||
|             available_ac_power = max(self.max_power_wh - generation, 0) | ||||
|  | ||||
|             # Calculate remaining AC power available | ||||
|             rest_ac_leistung = max(self.max_leistung_wh - erzeugung, 0) | ||||
|             # Discharge battery to cover shortfall, if possible | ||||
|             battery_discharge, discharge_losses = self.akku.energie_abgeben( | ||||
|                 min(shortfall, available_ac_power), hour | ||||
|             ) | ||||
|             losses += discharge_losses | ||||
|  | ||||
|             # Discharge energy from the battery based on need | ||||
|             if benötigte_energie < rest_ac_leistung: | ||||
|                 aus_akku, akku_entladeverluste = self.akku.energie_abgeben(benötigte_energie, hour) | ||||
|             else: | ||||
|                 aus_akku, akku_entladeverluste = self.akku.energie_abgeben(rest_ac_leistung, hour) | ||||
|             # Draw remaining required power from the grid (discharge_losses are already substraved in the battery) | ||||
|             grid_import = shortfall - battery_discharge | ||||
|             self_consumption = generation + battery_discharge | ||||
|  | ||||
|             verluste += akku_entladeverluste  # Include losses from battery discharge | ||||
|             netzbezug = benötigte_energie - aus_akku  # Energy drawn from the grid | ||||
|             eigenverbrauch = erzeugung + aus_akku  # Total self-consumption | ||||
|  | ||||
|         return netzeinspeisung, netzbezug, verluste, eigenverbrauch | ||||
|         return grid_export, grid_import, losses, self_consumption | ||||
|   | ||||
| @@ -19,7 +19,7 @@ from akkudoktoreos.devices.battery import ( | ||||
|     PVAkkuParameters, | ||||
| ) | ||||
| from akkudoktoreos.devices.generic import HomeAppliance, HomeApplianceParameters | ||||
| from akkudoktoreos.devices.inverter import Wechselrichter, WechselrichterParameters | ||||
| from akkudoktoreos.devices.inverter import Inverter, InverterParameters | ||||
| from akkudoktoreos.utils.utils import NumpyEncoder | ||||
| from akkudoktoreos.visualize import visualisiere_ergebnisse | ||||
|  | ||||
| @@ -27,7 +27,7 @@ from akkudoktoreos.visualize import visualisiere_ergebnisse | ||||
| class OptimizationParameters(BaseModel): | ||||
|     ems: EnergieManagementSystemParameters | ||||
|     pv_akku: PVAkkuParameters | ||||
|     wechselrichter: WechselrichterParameters = WechselrichterParameters() | ||||
|     inverter: InverterParameters = InverterParameters() | ||||
|     eauto: Optional[EAutoParameters] | ||||
|     dishwasher: Optional[HomeApplianceParameters] = None | ||||
|     temperature_forecast: Optional[list[float]] = Field( | ||||
| @@ -488,10 +488,9 @@ class optimization_problem(ConfigMixin, DevicesMixin, EnergyManagementSystemMixi | ||||
|         ) | ||||
|  | ||||
|         # Initialize the inverter and energy management system | ||||
|         wr = Wechselrichter(parameters.wechselrichter, akku) | ||||
|         self.ems.set_parameters( | ||||
|             parameters.ems, | ||||
|             wechselrichter=wr, | ||||
|             inverter=Inverter(parameters.inverter, akku), | ||||
|             eauto=eauto, | ||||
|             home_appliance=dishwasher, | ||||
|         ) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user