mirror of
				https://github.com/Akkudoktor-EOS/EOS.git
				synced 2025-10-30 22:36:21 +00:00 
			
		
		
		
	class_ems: AC / DC Charging
class_optimize: Timing Bugs fixed class_numpy_encoder: JSON Encoder with Numpy support visualize: AC / DC / Discharge test_class_ems_2: New Test for AC / DC charging decision
This commit is contained in:
		| @@ -20,7 +20,7 @@ class PVAkku: | ||||
|         self.soc_wh = (start_soc_prozent / 100) * kapazitaet_wh | ||||
|         self.hours = hours if hours is not None else 24  # Default to 24 hours if not specified | ||||
|         self.discharge_array = np.full(self.hours, 1) | ||||
|         self.charge_array = np.full(self.hours, 0) | ||||
|         self.charge_array = np.full(self.hours, 1) | ||||
|         # Charge and discharge efficiency | ||||
|         self.lade_effizienz = lade_effizienz | ||||
|         self.entlade_effizienz = entlade_effizienz | ||||
| @@ -70,26 +70,19 @@ class PVAkku: | ||||
|         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, 0) | ||||
|         self.charge_array = np.full(self.hours, 1) | ||||
|  | ||||
|     def set_discharge_per_hour(self, discharge_array): | ||||
|         assert len(discharge_array) == self.hours | ||||
|         self.discharge_array = np.array(discharge_array) | ||||
|  | ||||
|         # Ensure no simultaneous charging and discharging in the same hour using NumPy mask | ||||
|         conflict_mask = (self.charge_array > 0) & (self.discharge_array > 0) | ||||
|         # Prioritize discharge by setting charge to 0 where both are > 0 | ||||
|         self.charge_array[conflict_mask] = 0 | ||||
|  | ||||
|     def set_charge_per_hour(self, charge_array): | ||||
|         assert len(charge_array) == self.hours | ||||
|         self.charge_array = np.array(charge_array) | ||||
|  | ||||
|         # Ensure no simultaneous charging and discharging in the same hour using NumPy mask | ||||
|         conflict_mask = (self.charge_array > 0) & (self.discharge_array > 0) | ||||
|         # Prioritize discharge by setting charge to 0 where both are > 0 | ||||
|         self.discharge_array[conflict_mask] = 0 | ||||
|  | ||||
|     def set_charge_allowed_for_hour(self, charge, hour): | ||||
|         assert hour < self.hours | ||||
|         self.charge_array[hour] = charge | ||||
|  | ||||
|     def ladezustand_in_prozent(self): | ||||
|         return (self.soc_wh / self.kapazitaet_wh) * 100 | ||||
| @@ -125,17 +118,14 @@ class PVAkku: | ||||
|         # Return the actually discharged energy and the losses | ||||
|         return tatsaechlich_abgegeben_wh, verluste_wh | ||||
|  | ||||
|     def energie_laden(self, wh, hour): | ||||
|     def energie_laden(self, wh, hour, relative_power=0.0): | ||||
|         if hour is not None and self.charge_array[hour] == 0: | ||||
|             return 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 | ||||
|  | ||||
|         # Relative to the maximum charging power (between 0 and 1) | ||||
|         relative_ladeleistung = self.charge_array[hour] | ||||
|         effektive_ladeleistung = relative_ladeleistung * self.max_ladeleistung_w | ||||
|  | ||||
|         # 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 | ||||
| @@ -144,8 +134,8 @@ class PVAkku: | ||||
|         max_possible_charge_wh = max(max_possible_charge_wh, 0.0)  # Ensure non-negative | ||||
|  | ||||
|         # The actually charged energy cannot exceed requested energy, charging power, or maximum possible charge | ||||
|         effektive_lademenge = min(wh, effektive_ladeleistung, max_possible_charge_wh) | ||||
|  | ||||
|         effektive_lademenge = min(wh,  max_possible_charge_wh) | ||||
|          | ||||
|         # Energy actually stored in the battery | ||||
|         geladene_menge = effektive_lademenge * self.lade_effizienz | ||||
|  | ||||
| @@ -153,10 +143,9 @@ class PVAkku: | ||||
|         self.soc_wh += geladene_menge | ||||
|         # Ensure soc_wh does not exceed max_soc_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 | ||||
|  | ||||
|     def aktueller_energieinhalt(self): | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| from datetime import datetime | ||||
| from typing import Dict, List, Optional, Union | ||||
|  | ||||
| from akkudoktoreos.config import * | ||||
| import numpy as np | ||||
|  | ||||
|  | ||||
| @@ -23,12 +23,17 @@ class EnergieManagementSystem: | ||||
|         self.eauto = eauto | ||||
|         self.haushaltsgeraet = haushaltsgeraet | ||||
|         self.wechselrichter = wechselrichter | ||||
|         self.ac_charge_hours = np.full(prediction_hours,0) | ||||
|         self.dc_charge_hours = np.full(prediction_hours,1) | ||||
|  | ||||
|     def set_akku_discharge_hours(self, ds: List[int]) -> None: | ||||
|         self.akku.set_discharge_per_hour(ds) | ||||
|  | ||||
|     def set_akku_charge_hours(self, ds: List[int]) -> None: | ||||
|         self.akku.set_charge_per_hour(ds) | ||||
|     def set_akku_ac_charge_hours(self, ds: np.ndarray) -> None: | ||||
|         self.ac_charge_hours = ds | ||||
|      | ||||
|     def set_akku_dc_charge_hours(self, ds: np.ndarray) -> None: | ||||
|         self.dc_charge_hours = ds | ||||
|  | ||||
|     def set_eauto_charge_hours(self, ds: List[int]) -> None: | ||||
|         self.eauto.set_charge_per_hour(ds) | ||||
| @@ -46,7 +51,12 @@ class EnergieManagementSystem: | ||||
|         return self.simuliere(start_stunde) | ||||
|  | ||||
|     def simuliere(self, start_stunde: int) -> dict: | ||||
|         # Ensure arrays have the same length | ||||
|         ''' | ||||
|         hour: | ||||
|             akku_soc_pro_stunde begin of the hour, initial hour state! | ||||
|             last_wh_pro_stunde integral of  last hour (end state) | ||||
|         ''' | ||||
|  | ||||
|         lastkurve_wh = self.gesamtlast | ||||
|         assert ( | ||||
|             len(lastkurve_wh) == len(self.pv_prognose_wh) == len(self.strompreis_euro_pro_wh) | ||||
| @@ -91,16 +101,21 @@ class EnergieManagementSystem: | ||||
|                 eauto_soc_pro_stunde[stunde_since_now] = self.eauto.ladezustand_in_prozent() | ||||
|  | ||||
|             # AC PV Battery Charge | ||||
|             if self.akku.charge_array[stunde] > 0.0: | ||||
|                 geladene_menge, verluste_wh = self.akku.energie_laden(None,stunde) | ||||
|             if self.ac_charge_hours[stunde] > 0.0: | ||||
|                 self.akku.set_charge_allowed_for_hour(self.ac_charge_hours[stunde],stunde) | ||||
|                 geladene_menge, verluste_wh = self.akku.energie_laden(None,stunde,relative_power=self.ac_charge_hours[stunde]) | ||||
|                 verbrauch += geladene_menge | ||||
|                 verluste_wh_pro_stunde[stunde_since_now] += verluste_wh                 | ||||
|              | ||||
|                 verluste_wh_pro_stunde[stunde_since_now] += verluste_wh     | ||||
|  | ||||
|             # Process inverter logic | ||||
|             erzeugung = self.pv_prognose_wh[stunde] | ||||
|             self.akku.set_charge_allowed_for_hour(self.dc_charge_hours[stunde],stunde) | ||||
|             netzeinspeisung, netzbezug, verluste, eigenverbrauch = ( | ||||
|                 self.wechselrichter.energie_verarbeiten(erzeugung, verbrauch, stunde) | ||||
|             ) | ||||
|  | ||||
|    | ||||
|  | ||||
|             netzeinspeisung_wh_pro_stunde[stunde_since_now] = netzeinspeisung | ||||
|             netzbezug_wh_pro_stunde[stunde_since_now] = netzbezug | ||||
|             verluste_wh_pro_stunde[stunde_since_now] += verluste | ||||
| @@ -136,4 +151,5 @@ class EnergieManagementSystem: | ||||
|             "Gesamt_Verluste": np.nansum(verluste_wh_pro_stunde), | ||||
|             "Haushaltsgeraet_wh_pro_stunde": haushaltsgeraet_wh_pro_stunde, | ||||
|         } | ||||
|  | ||||
|         return out | ||||
|   | ||||
							
								
								
									
										24
									
								
								src/akkudoktoreos/class_numpy_encoder.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/akkudoktoreos/class_numpy_encoder.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| import json | ||||
| import numpy as np | ||||
|  | ||||
| class NumpyEncoder(json.JSONEncoder): | ||||
|     def default(self, obj): | ||||
|         if isinstance(obj, np.ndarray): | ||||
|             return obj.tolist()  # Convert NumPy arrays to lists | ||||
|         if isinstance(obj, np.generic): | ||||
|             return obj.item()  # Convert NumPy scalars to native Python types | ||||
|         return super(NumpyEncoder, self).default(obj) | ||||
|      | ||||
|     @staticmethod | ||||
|     def dumps(data): | ||||
|         """ | ||||
|         Static method to serialize a Python object into a JSON string using NumpyEncoder. | ||||
|          | ||||
|         Args: | ||||
|             data: The Python object to serialize. | ||||
|          | ||||
|         Returns: | ||||
|             str: A JSON string representation of the object. | ||||
|         """ | ||||
|         return json.dumps(data, cls=NumpyEncoder) | ||||
|  | ||||
| @@ -12,6 +12,7 @@ from akkudoktoreos.config import possible_ev_charge_currents | ||||
| from akkudoktoreos.visualize import visualisiere_ergebnisse | ||||
|  | ||||
|  | ||||
|  | ||||
| class optimization_problem: | ||||
|     def __init__( | ||||
|         self, | ||||
| @@ -35,53 +36,104 @@ class optimization_problem: | ||||
|         if fixed_seed is not None: | ||||
|             random.seed(fixed_seed) | ||||
|  | ||||
|     def split_charge_discharge(self, discharge_hours_bin: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: | ||||
|  | ||||
|     def decode_charge_discharge(self, discharge_hours_bin: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: | ||||
|         """ | ||||
|         Split the input array `discharge_hours_bin` into two separate arrays: | ||||
|         - `charge`: Contains only the negative values from `discharge_hours_bin` (charging values). | ||||
|         - `discharge`: Contains only the positive values from `discharge_hours_bin` (discharging values). | ||||
|          | ||||
|         Decode the input array `discharge_hours_bin` into three separate arrays for AC charging, DC charging, and discharge. | ||||
|         The function maps AC and DC charging values to relative power levels (0 to 1), while the discharge remains binary (0 or 1). | ||||
|  | ||||
|         Parameters: | ||||
|         - discharge_hours_bin (np.ndarray): Input array with both positive and negative values. | ||||
|         - discharge_hours_bin (np.ndarray): Input array with integer values representing the different states. | ||||
|         The states are: | ||||
|         0: No action ("idle") | ||||
|         1: Discharge ("discharge") | ||||
|         2-6: AC charging with different power levels ("ac_charge") | ||||
|         7-8: DC charging Dissallowed/allowed ("dc_charge") | ||||
|          | ||||
|         Returns: | ||||
|         - charge (np.ndarray): Array with negative values from `discharge_hours_bin`, other values set to 0. | ||||
|         - discharge (np.ndarray): Array with positive values from `discharge_hours_bin`, other values set to 0. | ||||
|         - ac_charge (np.ndarray): Array with AC charging values as relative power (0-1), other values set to 0. | ||||
|         - dc_charge (np.ndarray): Array with DC charging values as relative power (0-1), other values set to 0. | ||||
|         - discharge (np.ndarray): Array with discharge values (1 for discharge, 0 otherwise). | ||||
|         """ | ||||
|         # Convert the input list to a NumPy array, if it's not already | ||||
|         discharge_hours_bin = np.array(discharge_hours_bin) | ||||
|          | ||||
|         # Create charge array: Keep only negative values, set the rest to 0 | ||||
|         charge = -np.where(discharge_hours_bin < 0, discharge_hours_bin, 0) | ||||
|         charge = charge / np.max(charge) | ||||
|  | ||||
|         # Create discharge array: Keep only positive values, set the rest to 0 | ||||
|         discharge = np.where(discharge_hours_bin > 0, discharge_hours_bin, 0) | ||||
|         # Create ac_charge array: Only consider values between 2 and 6 (AC charging power levels), set the rest to 0 | ||||
|         ac_charge = np.where((discharge_hours_bin >= 2) & (discharge_hours_bin <= 6), discharge_hours_bin - 1, 0) | ||||
|         ac_charge = ac_charge / 5.0  # Normalize AC charge to range between 0 and 1 | ||||
|  | ||||
|         # Create dc_charge array: 7 = Not allowed (mapped to 0), 8 = Allowed (mapped to 1) | ||||
|         dc_charge = np.where(discharge_hours_bin == 8, 1, 0) | ||||
|  | ||||
|         # Create discharge array: Only consider value 1 (Discharge), set the rest to 0 (binary output) | ||||
|         discharge = np.where(discharge_hours_bin == 1, 1, 0) | ||||
|  | ||||
|         return ac_charge, dc_charge, discharge | ||||
|  | ||||
|  | ||||
|         return charge, discharge | ||||
|      | ||||
|     # Custom mutation function that applies type-specific mutations | ||||
|     def mutate(self,individual): | ||||
|         # Mutate the discharge state genes (-1, 0, 1) | ||||
|         individual[:self.prediction_hours], = self.toolbox.mutate_discharge( | ||||
|             individual[:self.prediction_hours] | ||||
|         ) | ||||
|     def mutate(self, individual): | ||||
|         """ | ||||
|         Custom mutation function for the individual. This function mutates different parts of the individual: | ||||
|         - Mutates the discharge and charge states (AC, DC, idle) using the split_charge_discharge method. | ||||
|         - Mutates the EV charging schedule if EV optimization is enabled. | ||||
|         - Mutates appliance start times if household appliances are part of the optimization. | ||||
|  | ||||
|         Parameters: | ||||
|         - individual (list): The individual being mutated, which includes different optimization parameters. | ||||
|  | ||||
|         Returns: | ||||
|         - (tuple): The mutated individual as a tuple (required by DEAP). | ||||
|         """ | ||||
|          | ||||
|         # Step 1: Mutate the charge/discharge states (idle, discharge, AC charge, DC charge) | ||||
|         # Extract the relevant part of the individual for prediction hours, which represents the charge/discharge behavior. | ||||
|         charge_discharge_part = individual[:self.prediction_hours] | ||||
|          | ||||
|         # Apply the mutation to the charge/discharge part | ||||
|         charge_discharge_mutated, = self.toolbox.mutate_charge_discharge(charge_discharge_part) | ||||
|          | ||||
|         # Ensure that no invalid states are introduced during mutation (valid values: 0-8) | ||||
|         charge_discharge_mutated = np.clip(charge_discharge_mutated, 0, 8) | ||||
|          | ||||
|         # Use split_charge_discharge to split the mutated array into AC charge, DC charge, and discharge components | ||||
|         #ac_charge, dc_charge, discharge = self.split_charge_discharge(charge_discharge_mutated) | ||||
|          | ||||
|         # Optionally: You can process the split arrays further if needed, for example, | ||||
|         # applying additional constraints or penalties, or keeping track of charging limits. | ||||
|  | ||||
|         # Reassign the mutated values back to the individual | ||||
|         individual[:self.prediction_hours] = charge_discharge_mutated | ||||
|  | ||||
|         # Step 2: Mutate EV charging schedule if enabled | ||||
|         if self.optimize_ev: | ||||
|             # Mutate the EV charging indices | ||||
|             # Extract the relevant part for EV charging schedule | ||||
|             ev_charge_part = individual[self.prediction_hours : self.prediction_hours * 2] | ||||
|              | ||||
|             # Apply mutation on the EV charging schedule | ||||
|             ev_charge_part_mutated, = self.toolbox.mutate_ev_charge_index(ev_charge_part) | ||||
|              | ||||
|             # Ensure the EV does not charge during fixed hours (set those hours to 0) | ||||
|             ev_charge_part_mutated[self.prediction_hours - self.fixed_eauto_hours :] = [0] * self.fixed_eauto_hours | ||||
|              | ||||
|             # Reassign the mutated EV charging part back to the individual | ||||
|             individual[self.prediction_hours : self.prediction_hours * 2] = ev_charge_part_mutated | ||||
|  | ||||
|         # Mutate the appliance start hour if present | ||||
|         # Step 3: Mutate appliance start times if household appliances are part of the optimization | ||||
|         if self.opti_param["haushaltsgeraete"] > 0: | ||||
|             # Extract the appliance part (typically a single value for the start hour) | ||||
|             appliance_part = [individual[-1]] | ||||
|              | ||||
|             # Apply mutation on the appliance start hour | ||||
|             appliance_part_mutated, = self.toolbox.mutate_hour(appliance_part) | ||||
|              | ||||
|             # Reassign the mutated appliance part back to the individual | ||||
|             individual[-1] = appliance_part_mutated[0] | ||||
|  | ||||
|         return (individual,) | ||||
|  | ||||
|  | ||||
|     # Method to create an individual based on the conditions | ||||
|     def create_individual(self): | ||||
|         # Start with discharge states for the individual | ||||
| @@ -106,8 +158,14 @@ class optimization_problem: | ||||
|         2. Electric vehicle charge hours (possible_charge_values), | ||||
|         3. Dishwasher start time (integer if applicable). | ||||
|         """ | ||||
|  | ||||
|         discharge_hours_bin = individual[: self.prediction_hours] | ||||
|         eautocharge_hours_float = individual[self.prediction_hours : self.prediction_hours * 2] | ||||
|         eautocharge_hours_float = ( | ||||
|             individual[self.prediction_hours : self.prediction_hours * 2] | ||||
|             if self.optimize_ev  | ||||
|             else None | ||||
|         ) | ||||
|  | ||||
|         spuelstart_int = ( | ||||
|             individual[-1] | ||||
|             if self.opti_param and self.opti_param.get("haushaltsgeraete", 0) > 0 | ||||
| @@ -132,13 +190,12 @@ class optimization_problem: | ||||
|  | ||||
|         # Initialize toolbox with attributes and operations | ||||
|         self.toolbox = base.Toolbox() | ||||
|         self.toolbox.register("attr_discharge_state", random.randint, -5, 1) | ||||
|         self.toolbox.register("attr_discharge_state", random.randint, 0,11) | ||||
|         if self.optimize_ev: | ||||
|             self.toolbox.register("attr_ev_charge_index", random.randint, 0, len(possible_ev_charge_currents) - 1) | ||||
|         self.toolbox.register("attr_int", random.randint, start_hour, 23) | ||||
|  | ||||
|  | ||||
|  | ||||
|         # Register individual creation function | ||||
|         self.toolbox.register("individual", self.create_individual) | ||||
|  | ||||
| @@ -148,7 +205,7 @@ class optimization_problem: | ||||
|         #self.toolbox.register("mutate", tools.mutFlipBit, indpb=0.1) | ||||
|         # Register separate mutation functions for each type of value: | ||||
|         # - Discharge state mutation (-5, 0, 1) | ||||
|         self.toolbox.register("mutate_discharge", tools.mutUniformInt, low=-5, up=1, indpb=0.1) | ||||
|         self.toolbox.register("mutate_charge_discharge", tools.mutUniformInt, low=0, up=8, indpb=0.1) | ||||
|         # - Float mutation for EV charging values | ||||
|         self.toolbox.register("mutate_ev_charge_index", tools.mutUniformInt, low=0, up=len(possible_ev_charge_currents) - 1, indpb=0.1) | ||||
|         # - Start hour mutation for household devices | ||||
| @@ -173,11 +230,12 @@ class optimization_problem: | ||||
|         if self.opti_param.get("haushaltsgeraete", 0) > 0: | ||||
|             ems.set_haushaltsgeraet_start(spuelstart_int, global_start_hour=start_hour) | ||||
|  | ||||
|         charge, discharge = self.split_charge_discharge(discharge_hours_bin) | ||||
|         ac,dc,discharge = self.decode_charge_discharge(discharge_hours_bin) | ||||
|  | ||||
|  | ||||
|         ems.set_akku_discharge_hours(discharge) | ||||
|         ems.set_akku_charge_hours(charge) | ||||
|         ems.set_akku_dc_charge_hours(dc) | ||||
|         ems.set_akku_ac_charge_hours(ac) | ||||
|  | ||||
|         if self.optimize_ev: | ||||
|             eautocharge_hours_float = [ | ||||
| @@ -212,13 +270,6 @@ class optimization_problem: | ||||
|             0.01 for i in range(self.prediction_hours) if discharge_hours_bin[i] == 0.0 | ||||
|         ) | ||||
|          | ||||
|         # Penalty for charging the electric vehicle during restricted hours | ||||
|         # gesamtbilanz += sum( | ||||
|         #     self.strafe | ||||
|         #     for i in range(self.prediction_hours - self.fixed_eauto_hours, self.prediction_hours) | ||||
|         #     if eautocharge_hours_float[i] != 0.0 | ||||
|         # ) | ||||
|  | ||||
|         # Penalty for not meeting the minimum SOC (State of Charge) requirement | ||||
|         if parameter["eauto_min_soc"] - ems.eauto.ladezustand_in_prozent() <= 0.0: | ||||
|             gesamtbilanz += sum( | ||||
| @@ -266,8 +317,8 @@ class optimization_problem: | ||||
|             self.toolbox, | ||||
|             mu=100, | ||||
|             lambda_=150, | ||||
|             cxpb=0.5, | ||||
|             mutpb=0.5, | ||||
|             cxpb=0.7, | ||||
|             mutpb=0.3, | ||||
|             ngen=ngen, | ||||
|             stats=stats, | ||||
|             halloffame=hof, | ||||
| @@ -361,14 +412,18 @@ class optimization_problem: | ||||
|             start_solution | ||||
|         ) | ||||
|  | ||||
|  | ||||
|  | ||||
|         ac_charge, dc_charge, discharge = self.decode_charge_discharge(discharge_hours_bin) | ||||
|         # Visualize the results | ||||
|         visualisiere_ergebnisse( | ||||
|             parameter["gesamtlast"], | ||||
|             parameter["pv_forecast"], | ||||
|             parameter["strompreis_euro_pro_wh"], | ||||
|             o, | ||||
|             discharge_hours_bin, | ||||
|             eautocharge_hours_float, | ||||
|             ac_charge, | ||||
|             dc_charge, | ||||
|             discharge, | ||||
|             parameter["temperature_forecast"], | ||||
|             start_hour, | ||||
|             self.prediction_hours, | ||||
| @@ -395,7 +450,7 @@ class optimization_problem: | ||||
|             element_list = o[key].tolist() | ||||
|  | ||||
|             # Change the first value to None | ||||
|             element_list[0] = None | ||||
|             #element_list[0] = None | ||||
|             # Change the NaN to None (JSON) | ||||
|             element_list = [ | ||||
|                 None if isinstance(x, (int, float)) and np.isnan(x) else x for x in element_list | ||||
| @@ -406,7 +461,9 @@ class optimization_problem: | ||||
|  | ||||
|         # Return final results as a dictionary | ||||
|         return { | ||||
|             "discharge_hours_bin": discharge_hours_bin, | ||||
|             "ac_charge": ac_charge.tolist(), | ||||
|             "dc_charge":dc_charge.tolist(), | ||||
|             "discharge_allowed":discharge.tolist(), | ||||
|             "eautocharge_hours_float": eautocharge_hours_float, | ||||
|             "result": o, | ||||
|             "eauto_obj": ems.eauto.to_dict(), | ||||
|   | ||||
| @@ -18,8 +18,9 @@ def visualisiere_ergebnisse( | ||||
|     pv_forecast, | ||||
|     strompreise, | ||||
|     ergebnisse, | ||||
|     discharge_hours, | ||||
|     laden_moeglich, | ||||
|     ac,  # AC charging allowed | ||||
|     dc,  # DC charging allowed | ||||
|     discharge,  # Discharge allowed | ||||
|     temperature, | ||||
|     start_hour, | ||||
|     prediction_hours, | ||||
| @@ -58,21 +59,7 @@ def visualisiere_ergebnisse( | ||||
|         plt.grid(True) | ||||
|         plt.legend() | ||||
|  | ||||
|         # Electricity prices | ||||
|         hours_p = np.arange(0, len(strompreise)) | ||||
|         plt.subplot(3, 2, 2) | ||||
|         plt.plot( | ||||
|             hours_p, | ||||
|             strompreise, | ||||
|             label="Electricity Price (€/Wh)", | ||||
|             color="purple", | ||||
|             marker="s", | ||||
|         ) | ||||
|         plt.title("Electricity Prices") | ||||
|         plt.xlabel("Hour of the Day") | ||||
|         plt.ylabel("Price (€/Wh)") | ||||
|         plt.legend() | ||||
|         plt.grid(True) | ||||
|  | ||||
|  | ||||
|         # PV forecast | ||||
|         plt.subplot(3, 2, 3) | ||||
| @@ -122,30 +109,48 @@ def visualisiere_ergebnisse( | ||||
|  | ||||
|         # Energy flow, grid feed-in, and grid consumption | ||||
|         plt.subplot(3, 2, 1) | ||||
|         plt.plot(hours, ergebnisse["Last_Wh_pro_Stunde"], label="Load (Wh)", marker="o") | ||||
|         # Plot with transparency (alpha) and different linestyles | ||||
|         plt.plot( | ||||
|             hours, | ||||
|             ergebnisse["Haushaltsgeraet_wh_pro_stunde"], | ||||
|             label="Household Device (Wh)", | ||||
|             marker="o", | ||||
|             hours, ergebnisse["Last_Wh_pro_Stunde"], label="Load (Wh)", marker="o", linestyle="-", alpha=0.8 | ||||
|         ) | ||||
|         plt.plot( | ||||
|             hours, | ||||
|             ergebnisse["Netzeinspeisung_Wh_pro_Stunde"], | ||||
|             label="Grid Feed-in (Wh)", | ||||
|             marker="x", | ||||
|             hours, ergebnisse["Haushaltsgeraet_wh_pro_stunde"], label="Household Device (Wh)", marker="o", linestyle="--", alpha=0.8 | ||||
|         ) | ||||
|         plt.plot( | ||||
|             hours, | ||||
|             ergebnisse["Netzbezug_Wh_pro_Stunde"], | ||||
|             label="Grid Consumption (Wh)", | ||||
|             marker="^", | ||||
|             hours, ergebnisse["Netzeinspeisung_Wh_pro_Stunde"], label="Grid Feed-in (Wh)", marker="x", linestyle=":", alpha=0.8 | ||||
|         ) | ||||
|         plt.plot(hours, ergebnisse["Verluste_Pro_Stunde"], label="Losses (Wh)", marker="^") | ||||
|         plt.plot( | ||||
|             hours, ergebnisse["Netzbezug_Wh_pro_Stunde"], label="Grid Consumption (Wh)", marker="^", linestyle="-.", alpha=0.8 | ||||
|         ) | ||||
|         plt.plot( | ||||
|             hours, ergebnisse["Verluste_Pro_Stunde"], label="Losses (Wh)", marker="^", linestyle="-", alpha=0.8 | ||||
|         ) | ||||
|  | ||||
|         # Title and labels | ||||
|         plt.title("Energy Flow per Hour") | ||||
|         plt.xlabel("Hour") | ||||
|         plt.ylabel("Energy (Wh)") | ||||
|  | ||||
|         # Show legend with a higher number of columns to avoid overlap | ||||
|         plt.legend(ncol=2) | ||||
|  | ||||
|  | ||||
|  | ||||
|         # Electricity prices | ||||
|         hours_p = np.arange(0, len(strompreise)) | ||||
|         plt.subplot(3, 2, 3) | ||||
|         plt.plot( | ||||
|             hours_p, | ||||
|             strompreise, | ||||
|             label="Electricity Price (€/Wh)", | ||||
|             color="purple", | ||||
|             marker="s", | ||||
|         ) | ||||
|         plt.title("Electricity Prices") | ||||
|         plt.xlabel("Hour of the Day") | ||||
|         plt.ylabel("Price (€/Wh)") | ||||
|         plt.legend() | ||||
|         plt.grid(True) | ||||
|  | ||||
|         # State of charge for batteries | ||||
|         plt.subplot(3, 2, 2) | ||||
| @@ -159,42 +164,30 @@ def visualisiere_ergebnisse( | ||||
|         plt.legend(loc="upper left", bbox_to_anchor=(1, 1))  # Place legend outside the plot | ||||
|         plt.grid(True, which="both", axis="x")  # Grid for every hour | ||||
|  | ||||
|         ax1 = plt.subplot(3, 2, 3) | ||||
|         # Plot charge and discharge values | ||||
|         for hour, value in enumerate(discharge_hours): | ||||
|             # Determine color and label based on the value | ||||
|             if value > 0:  # Positive values (discharge) | ||||
|                 color = "red" | ||||
|                 label = "Discharge" if hour == 0 else "" | ||||
|             elif value < 0:  # Negative values (charge) | ||||
|                 color = "blue" | ||||
|                 label = "Charge" if hour == 0 else "" | ||||
|         # Plot for AC, DC charging, and Discharge status using bar charts | ||||
|         ax1 = plt.subplot(3, 2, 5) | ||||
|  | ||||
|             else: | ||||
|                 continue  # Skip zero values | ||||
|         # Plot AC charging as bars (relative values between 0 and 1) | ||||
|         plt.bar(hours, ac, width=0.4, label="AC Charging (relative)", color="blue", alpha=0.6) | ||||
|  | ||||
|             # Create colored areas with `axvspan` | ||||
|             ax1.axvspan( | ||||
|                 hour,  # Start of the hour | ||||
|                 hour + 1,  # End of the hour | ||||
|                 ymin=0,  # Lower bound | ||||
|                 ymax=abs(value) / 5 if value < 0 else value,  # Adjust height based on the value | ||||
|                 color=color, | ||||
|                 alpha=0.3, | ||||
|                 label=label | ||||
|             ) | ||||
|         # Plot DC charging as bars (relative values between 0 and 1) | ||||
|         plt.bar(hours + 0.4, dc, width=0.4, label="DC Charging (relative)", color="green", alpha=0.6) | ||||
|  | ||||
|         # Plot Discharge as bars (0 or 1, binary values) | ||||
|         plt.bar(hours, discharge, width=0.4, label="Discharge Allowed", color="red", alpha=0.6, bottom=np.maximum(ac, dc)) | ||||
|  | ||||
|         # Configure the plot | ||||
|         ax1.legend(loc="upper left") | ||||
|         ax1.set_xlim(0, prediction_hours) | ||||
|         ax1.set_xlabel("Hour") | ||||
|         ax1.set_ylabel("Charge/Discharge Level") | ||||
|         ax1.set_title("Charge and Discharge Hours Overview") | ||||
|         ax1.set_ylabel("Relative Power (0-1) / Discharge (0 or 1)") | ||||
|         ax1.set_title("AC/DC Charging and Discharge Overview") | ||||
|         ax1.grid(True) | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|         pdf.savefig()  # Save the current figure state to the PDF | ||||
|         plt.close()  # Close the current figure to free up memory | ||||
|  | ||||
| @@ -219,7 +212,6 @@ def visualisiere_ergebnisse( | ||||
|         ) | ||||
|         # Annotate costs | ||||
|         for hour, value in enumerate(costs): | ||||
|             print(hour, " ", value) | ||||
|             if value == None or np.isnan(value): | ||||
|                 value=0 | ||||
|             axs[0].annotate(f'{value:.2f}', (hour, value), textcoords="offset points", xytext=(0,5), ha='center', fontsize=8, color='red') | ||||
|   | ||||
		Reference in New Issue
	
	Block a user