diff --git a/src/akkudoktoreos/devices/inverter.py b/src/akkudoktoreos/devices/inverter.py index ca572ea..eb0bf30 100644 --- a/src/akkudoktoreos/devices/inverter.py +++ b/src/akkudoktoreos/devices/inverter.py @@ -1,6 +1,9 @@ from pydantic import BaseModel, Field from akkudoktoreos.devices.battery import PVAkku +from akkudoktoreos.prediction.self_consumption_probability import ( + self_consumption_probability_interpolator, +) class WechselrichterParameters(BaseModel): @@ -8,11 +11,17 @@ class WechselrichterParameters(BaseModel): class Wechselrichter: - def __init__(self, parameters: WechselrichterParameters, akku: PVAkku): + def __init__( + self, + parameters: WechselrichterParameters, + akku: PVAkku, + self_consumption_predictor: self_consumption_probability_interpolator, + ): self.max_leistung_wh = ( parameters.max_leistung_wh # Maximum power that the inverter can handle ) self.akku = akku # Connection to a battery object + self.self_consumption_predictor = self_consumption_predictor def energie_verarbeiten( self, erzeugung: float, verbrauch: float, hour: int @@ -21,7 +30,7 @@ class Wechselrichter: netzeinspeisung = 0.0 # Grid feed-in netzbezug = 0.0 # Grid draw eigenverbrauch = 0.0 # Self-consumption - + aus_akku = 0.0 if erzeugung >= verbrauch: if verbrauch > self.max_leistung_wh: # If consumption exceeds maximum inverter power @@ -30,10 +39,14 @@ class Wechselrichter: netzbezug = -restleistung_nach_verbrauch # Negative indicates feeding into the grid eigenverbrauch = self.max_leistung_wh else: + scr = self.self_consumption_predictor.calculate_self_consumption( + verbrauch, erzeugung + ) + # Remaining power after consumption - restleistung_nach_verbrauch = (erzeugung - verbrauch) * 0.95 # EVQ + restleistung_nach_verbrauch = (erzeugung - verbrauch) * scr # EVQ # Remaining load Self Consumption not perfect - restlast_evq = (erzeugung - verbrauch) * (1.0 - 0.95) + restlast_evq = (erzeugung - verbrauch) * (1.0 - scr) if restlast_evq > 0: # Akku muss den Restverbrauch decken diff --git a/src/akkudoktoreos/optimization/genetic.py b/src/akkudoktoreos/optimization/genetic.py index 96f01d3..3544eda 100644 --- a/src/akkudoktoreos/optimization/genetic.py +++ b/src/akkudoktoreos/optimization/genetic.py @@ -5,6 +5,7 @@ import numpy as np from deap import algorithms, base, creator, tools from pydantic import BaseModel, Field, field_validator, model_validator from typing_extensions import Self +from pathlib import Path from akkudoktoreos.config import AppConfig from akkudoktoreos.devices.battery import ( @@ -13,6 +14,9 @@ from akkudoktoreos.devices.battery import ( PVAkku, PVAkkuParameters, ) +from akkudoktoreos.prediction.self_consumption_probability import ( + self_consumption_probability_interpolator, +) from akkudoktoreos.devices.generic import HomeAppliance, HomeApplianceParameters from akkudoktoreos.devices.inverter import Wechselrichter, WechselrichterParameters from akkudoktoreos.prediction.ems import ( @@ -142,7 +146,7 @@ class optimization_problem: # AC states ac_mask = (discharge_hours_bin_np >= 2 * len_ac) & (discharge_hours_bin_np < 3 * len_ac) - ac_indices = discharge_hours_bin_np[ac_mask] - 2 * len_ac + ac_indices = (discharge_hours_bin_np[ac_mask] - 2 * len_ac).astype(int) # DC states (if enabled) if self.optimize_dc_charge: @@ -350,10 +354,10 @@ class optimization_problem: worst_case: bool, ) -> Tuple[float]: """Evaluate the fitness of an individual solution based on the simulation results.""" - try: - o = self.evaluate_inner(individual, ems, start_hour) - except Exception as e: - return (100000.0,) # Return a high penalty in case of an exception + # try: + o = self.evaluate_inner(individual, ems, start_hour) + # except Exception as e: + # return (100000.0,) # Return a high penalty in case of an exception gesamtbilanz = o["Gesamtbilanz_Euro"] * (-1.0 if worst_case else 1.0) @@ -443,13 +447,18 @@ class optimization_problem: parameters: OptimizationParameters, start_hour: int, worst_case: bool = False, - ngen: int = 600, + ngen: int = 200, ) -> OptimizeResponse: """Perform EMS (Energy Management System) optimization and visualize results.""" einspeiseverguetung_euro_pro_wh = np.full( self.prediction_hours, parameters.ems.einspeiseverguetung_euro_pro_wh ) + # 1h Load to Sub 1h Load Distribution -> SelfConsumptionRate + sc = self_consumption_probability_interpolator( + Path(__file__).parent.resolve() / ".." / "data" / "regular_grid_interpolator.pkl" + ) + # Initialize PV and EV batteries akku = PVAkku( parameters.pv_akku, @@ -481,7 +490,11 @@ class optimization_problem: ) # Initialize the inverter and energy management system - wr = Wechselrichter(parameters.wechselrichter, akku) + wr = Wechselrichter( + parameters.wechselrichter, + akku, + self_consumption_predictor=sc, + ) ems = EnergieManagementSystem( self._config.eos, parameters.ems, diff --git a/src/akkudoktoreos/prediction/self_consumption_probability.py b/src/akkudoktoreos/prediction/self_consumption_probability.py index ac789ce..1865986 100644 --- a/src/akkudoktoreos/prediction/self_consumption_probability.py +++ b/src/akkudoktoreos/prediction/self_consumption_probability.py @@ -10,9 +10,11 @@ class self_consumption_probability_interpolator: def __init__(self, filepath: str | Path): self.filepath = filepath self.interpolator = None + print("OPEN") # Load the RegularGridInterpolator - with open("regular_grid_interpolator.pkl", "rb") as file: - interpolator = pickle.load(self.filepath) + with open(self.filepath, "rb") as file: + print("OPENED") + self.interpolator = pickle.load(file) def calculate_self_consumption(self, load_1h_power: float, pv_power: float) -> float: """Calculate the PV self-consumption rate using RegularGridInterpolator. @@ -29,11 +31,13 @@ class self_consumption_probability_interpolator: # Get probabilities for all partial loads points = np.array([np.full_like(partial_loads, load_1h_power), partial_loads]).T - probabilities = interpolator(points) + if self.interpolator == None: + return -1.0 + probabilities = self.interpolator(points) probabilities = probabilities / probabilities.sum() - for i, w in enumerate(partial_loads): - print(w, ": ", probabilities[i]) - print(probabilities.sum()) + # for i, w in enumerate(partial_loads): + # print(w, ": ", probabilities[i]) + # print(probabilities.sum()) # Ensure probabilities are within [0, 1] probabilities = np.clip(probabilities, 0, 1) diff --git a/src/akkudoktoreos/server/fastapi_server.py b/src/akkudoktoreos/server/fastapi_server.py index c3add8a..1019b2a 100755 --- a/src/akkudoktoreos/server/fastapi_server.py +++ b/src/akkudoktoreos/server/fastapi_server.py @@ -48,7 +48,7 @@ app = FastAPI( working_dir = get_working_dir() # copy config to working directory. Make this a CLI option later config = load_config(working_dir, True) -opt_class = optimization_problem(config) +opt_class = optimization_problem(config, verbose=False) server_dir = Path(__file__).parent.resolve() @@ -196,7 +196,7 @@ def fastapi_optimize( ] = None, ) -> OptimizeResponse: if start_hour is None: - start_hour = datetime.now().hour + start_hour = 10 # datetime.now().hour # Perform optimization simulation result = opt_class.optimierung_ems(parameters=parameters, start_hour=start_hour)