diff --git a/src/akkudoktoreos/class_akku.py b/src/akkudoktoreos/class_akku.py index 5c190dd..1e33870 100644 --- a/src/akkudoktoreos/class_akku.py +++ b/src/akkudoktoreos/class_akku.py @@ -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, 1) + self.charge_array = np.full(self.hours, 0) # Charge and discharge efficiency self.lade_effizienz = lade_effizienz self.entlade_effizienz = entlade_effizienz @@ -88,7 +88,7 @@ class PVAkku: # 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 + self.discharge_array[conflict_mask] = 0 def ladezustand_in_prozent(self): diff --git a/src/akkudoktoreos/class_ems.py b/src/akkudoktoreos/class_ems.py index 9a983e7..4ee2943 100644 --- a/src/akkudoktoreos/class_ems.py +++ b/src/akkudoktoreos/class_ems.py @@ -92,9 +92,7 @@ class EnergieManagementSystem: # AC PV Battery Charge if self.akku.charge_array[stunde] > 0.0: - #soc_pre = self.akku.ladezustand_in_prozent() geladene_menge, verluste_wh = self.akku.energie_laden(None,stunde) - #print(self.akku.charge_array[stunde], " ",geladene_menge," ",soc_pre," ",self.akku.ladezustand_in_prozent()) verbrauch += geladene_menge verluste_wh_pro_stunde[stunde_since_now] += verluste_wh diff --git a/src/akkudoktoreos/class_optimize.py b/src/akkudoktoreos/class_optimize.py index d100d92..bd33fbe 100644 --- a/src/akkudoktoreos/class_optimize.py +++ b/src/akkudoktoreos/class_optimize.py @@ -59,6 +59,43 @@ class optimization_problem: discharge = np.where(discharge_hours_bin > 0, discharge_hours_bin, 0) 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] + ) + + if self.optimize_ev: + # Mutate the EV charging indices + ev_charge_part = individual[self.prediction_hours : self.prediction_hours * 2] + ev_charge_part_mutated, = self.toolbox.mutate_ev_charge_index(ev_charge_part) + ev_charge_part_mutated[self.prediction_hours - self.fixed_eauto_hours :] = [0] * self.fixed_eauto_hours + individual[self.prediction_hours : self.prediction_hours * 2] = ev_charge_part_mutated + + # Mutate the appliance start hour if present + if self.opti_param["haushaltsgeraete"] > 0: + appliance_part = [individual[-1]] + appliance_part_mutated, = self.toolbox.mutate_hour(appliance_part) + 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 + individual_components = [self.toolbox.attr_discharge_state() for _ in range(self.prediction_hours)] + + # Add EV charge index values if optimize_ev is True + if self.optimize_ev: + individual_components += [self.toolbox.attr_ev_charge_index() for _ in range(self.prediction_hours)] + + # Add the start time of the household appliance if it's being optimized + if self.opti_param["haushaltsgeraete"] > 0: + individual_components += [self.toolbox.attr_int()] + + return creator.Individual(individual_components) def split_individual( self, individual: List[float] @@ -100,43 +137,10 @@ class optimization_problem: 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) - # Function to create an individual based on the conditions - def create_individual(): - # Start with discharge states for the individual - individual_components = [self.toolbox.attr_discharge_state() for _ in range(self.prediction_hours)] - # Add EV charge index values if optimize_ev is True - if self.optimize_ev: - individual_components += [self.toolbox.attr_ev_charge_index() for _ in range(self.prediction_hours)] - - # Add the start time of the household appliance if it's being optimized - if self.opti_param["haushaltsgeraete"] > 0: - individual_components += [self.toolbox.attr_int()] - - return creator.Individual(individual_components) # Register individual creation function - self.toolbox.register("individual", create_individual) - - - # # Register individual creation method based on household appliance parameter - # if opti_param["haushaltsgeraete"] > 0: - # self.toolbox.register( - # "individual", - # lambda: creator.Individual( - # [self.toolbox.attr_discharge_state() for _ in range(self.prediction_hours)] - # + [self.toolbox.attr_ev_charge_index() for _ in range(self.prediction_hours)] - # + [self.toolbox.attr_int()] - # ), - # ) - # else: - # self.toolbox.register( - # "individual", - # lambda: creator.Individual( - # [self.toolbox.attr_discharge_state() for _ in range(self.prediction_hours)] - # + [self.toolbox.attr_ev_charge_index() for _ in range(self.prediction_hours)] - # ), - # ) + self.toolbox.register("individual", self.create_individual) # Register population, mating, mutation, and selection functions self.toolbox.register("population", tools.initRepeat, list, self.toolbox.individual) @@ -150,32 +154,8 @@ class optimization_problem: # - Start hour mutation for household devices self.toolbox.register("mutate_hour", tools.mutUniformInt, low=start_hour, up=23, indpb=0.1) - # Custom mutation function that applies type-specific mutations - def mutate(individual): - # Mutate the discharge state genes (-1, 0, 1) - individual[:self.prediction_hours], = self.toolbox.mutate_discharge( - individual[:self.prediction_hours] - ) - - if self.optimize_ev: - # Mutate the EV charging indices - ev_charge_part = individual[self.prediction_hours : self.prediction_hours * 2] - ev_charge_part_mutated, = self.toolbox.mutate_ev_charge_index(ev_charge_part) - ev_charge_part_mutated[self.prediction_hours - self.fixed_eauto_hours :] = [0] * self.fixed_eauto_hours - individual[self.prediction_hours : self.prediction_hours * 2] = ev_charge_part_mutated - - # Mutate the appliance start hour if present - if self.opti_param["haushaltsgeraete"] > 0: - appliance_part = [individual[-1]] - appliance_part_mutated, = self.toolbox.mutate_hour(appliance_part) - individual[-1] = appliance_part_mutated[0] - - return (individual,) - - # Register custom mutation function - self.toolbox.register("mutate", mutate) - + self.toolbox.register("mutate", self.mutate) self.toolbox.register("select", tools.selTournament, tournsize=3) @@ -198,13 +178,13 @@ class optimization_problem: ems.set_akku_discharge_hours(discharge) ems.set_akku_charge_hours(charge) - #print(charge) - - eautocharge_hours_float = [ - possible_ev_charge_currents[i] for i in eautocharge_hours_index - ] + if self.optimize_ev: + eautocharge_hours_float = [ + possible_ev_charge_currents[i] for i in eautocharge_hours_index + ] ems.set_eauto_charge_hours(eautocharge_hours_float) + return ems.simuliere(start_hour) def evaluate( @@ -226,7 +206,6 @@ class optimization_problem: gesamtbilanz = o["Gesamtbilanz_Euro"] * (-1.0 if worst_case else 1.0) discharge_hours_bin, eautocharge_hours_float, _ = self.split_individual(individual) - #max_ladeleistung = np.max(possible_ev_charge_currents) # Small Penalty for not discharging gesamtbilanz += sum( diff --git a/src/akkudoktoreos/visualize.py b/src/akkudoktoreos/visualize.py index a97a6a3..411e24f 100644 --- a/src/akkudoktoreos/visualize.py +++ b/src/akkudoktoreos/visualize.py @@ -160,39 +160,39 @@ def visualisiere_ergebnisse( plt.grid(True, which="both", axis="x") # Grid for every hour ax1 = plt.subplot(3, 2, 3) - # Plot für die discharge_hours-Werte + # Plot charge and discharge values for hour, value in enumerate(discharge_hours): - # Festlegen der Farbe und des Labels basierend auf dem Wert - if value > 0: # Positive Werte (Entladung) + # Determine color and label based on the value + if value > 0: # Positive values (discharge) color = "red" - label = "Discharge" if hour == 0 else "" # Label nur beim ersten Eintrag hinzufügen - elif value < 0: # Negative Werte (Ladung) + label = "Discharge" if hour == 0 else "" + elif value < 0: # Negative values (charge) color = "blue" label = "Charge" if hour == 0 else "" - else: - continue # Überspringe 0-Werte - # Erstellen der Farbbereiche mit `axvspan` + else: + continue # Skip zero values + + # Create colored areas with `axvspan` ax1.axvspan( - hour, # Start der Stunde - hour + 1, # Ende der Stunde - ymin=0, # Untere Grenze - ymax=abs(value), # Obere Grenze: abs(value), um die Höhe richtig darzustellen + 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 ) - # Annotieren der Werte in der Mitte des Farbbereichs - ax1.text( - hour + 0.5, # In der Mitte des Bereichs - abs(value) / 2, # In der Mitte der Höhe - f'{value:.2f}', # Wert mit zwei Dezimalstellen - ha='center', - va='center', - fontsize=8, - color='black' - ) + + # 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.grid(True) + pdf.savefig() # Save the current figure state to the PDF