mirror of
				https://github.com/Akkudoktor-EOS/EOS.git
				synced 2025-11-04 00:36:21 +00:00 
			
		
		
		
	48 Stunden Predcition & Optimierung
Ein paar Zeitfunktionen korrigiert (24h / 48h) Strompreis Cache stündlich leeren Strompreis bei nur 24h Daten, wird verdoppelt (Prognose fehlt noch)
This commit is contained in:
		@@ -13,6 +13,10 @@ Dieses Projekt bietet eine umfassende Lösung zur Simulation und Optimierung ein
 | 
			
		||||
- `Simulation:` Lastverteilung 1h Werte -> Minuten (Tabelle) 
 | 
			
		||||
- `Dynamische Lasten:` z.B. eine Spülmaschine, welche gesteuert werdeb jabb,
 | 
			
		||||
- `Simulation:` AC Chargen möglich
 | 
			
		||||
- `Optimierung:` E-Auto Akku voll = in der 0/1 Liste keine Möglichkeit mehr auf 1 (aktuell ist der Optimierung das egalm ändert ja nichts) Optimierungsparameter reduzieren
 | 
			
		||||
- `Backend:` Visual Cleaner (z.B. E-Auto Akku = 100%, dann sollte die Lademöglichkeit auf 0 stehen. Zumindest bei der Ausgabe sollte das "sauber" sein)
 | 
			
		||||
- `Backend:` Cache regelmäßig leeren können (API)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Installation
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ import os
 | 
			
		||||
app = Flask(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
opt_class = optimization_problem(prediction_hours=24, strafe=10)
 | 
			
		||||
opt_class = optimization_problem(prediction_hours=48, strafe=10)
 | 
			
		||||
soc_predictor = BatterySocPredictor.load_model('battery_model.pkl')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -69,7 +69,7 @@ def flask_optimize():
 | 
			
		||||
                return jsonify({"error": f"Fehlender Parameter: {p}"}), 400
 | 
			
		||||
 | 
			
		||||
        # Simulation durchführen
 | 
			
		||||
        ergebnis = opt_class.optimierung_ems(parameter=parameter, start_hour=datetime.now().hour)
 | 
			
		||||
        ergebnis = opt_class.optimierung_ems(parameter=parameter, start_hour=datetime.now().hour) # , startdate = datetime.now().date() - timedelta(days = 1)
 | 
			
		||||
        
 | 
			
		||||
        return jsonify(ergebnis)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -100,31 +100,6 @@ class EnergieManagementSystem:
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            
 | 
			
		||||
            # Mehr erzeugt als verbraucht
 | 
			
		||||
            # if erzeugung > verbrauch:
 | 
			
		||||
                # überschuss = erzeugung - verbrauch
 | 
			
		||||
                # #geladene_energie = min(überschuss, self.akku.kapazitaet_wh - self.akku.soc_wh)
 | 
			
		||||
                # geladene_energie, verluste_laden_akku = self.akku.energie_laden(überschuss, stunde)
 | 
			
		||||
                # verluste_wh_pro_stunde[-1] += verluste_laden_akku
 | 
			
		||||
                # #print("verluste_laden_akku:",verluste_laden_akku)
 | 
			
		||||
                # netzeinspeisung_wh_pro_stunde.append(überschuss - geladene_energie-verluste_laden_akku)
 | 
			
		||||
                # eigenverbrauch_wh_pro_stunde.append(verbrauch)
 | 
			
		||||
                # stündliche_einnahmen_euro = (überschuss - geladene_energie-verluste_laden_akku) * self.einspeiseverguetung_euro_pro_wh[stunde] 
 | 
			
		||||
                # #print(überschuss," ", geladene_energie," ",verluste_laden_akku)
 | 
			
		||||
                # netzbezug_wh_pro_stunde.append(0.0)
 | 
			
		||||
            # # Noch Netzbezug nötig
 | 
			
		||||
            # else:
 | 
			
		||||
                # netzeinspeisung_wh_pro_stunde.append(0.0)
 | 
			
		||||
                # benötigte_energie = verbrauch - erzeugung
 | 
			
		||||
                # aus_akku, akku_entladeverluste = self.akku.energie_abgeben(benötigte_energie, stunde)
 | 
			
		||||
                # verluste_wh_pro_stunde[-1] += akku_entladeverluste
 | 
			
		||||
                # #print("akku_entladeverluste:",akku_entladeverluste)
 | 
			
		||||
                
 | 
			
		||||
                # stündlicher_netzbezug_wh = benötigte_energie - aus_akku
 | 
			
		||||
                # netzbezug_wh_pro_stunde.append(stündlicher_netzbezug_wh)
 | 
			
		||||
                # eigenverbrauch_wh_pro_stunde.append(erzeugung+aus_akku)
 | 
			
		||||
                # stündliche_kosten_euro = stündlicher_netzbezug_wh * strompreis 
 | 
			
		||||
            
 | 
			
		||||
            if self.eauto:
 | 
			
		||||
                eauto_soc_pro_stunde.append(eauto_soc)
 | 
			
		||||
            
 | 
			
		||||
 
 | 
			
		||||
@@ -67,12 +67,19 @@ class LoadForecast:
 | 
			
		||||
 | 
			
		||||
        # Beachten, dass bei Schaltjahren der Tag des Jahres angepasst werden muss
 | 
			
		||||
        stats_for_range = self.data_year_energy[start_day_of_year:end_day_of_year]  # -1 da die Indizierung bei 0 beginnt
 | 
			
		||||
        # print(start_day_of_year,"-",end_day_of_year)
 | 
			
		||||
        # print(stats_for_range.shape)
 | 
			
		||||
        stats_for_range =stats_for_range.swapaxes(1, 0)
 | 
			
		||||
 | 
			
		||||
        stats_for_range = stats_for_range.reshape(stats_for_range.shape[0],-1)
 | 
			
		||||
        # print(stats_for_range.shape)
 | 
			
		||||
        # print(stats_for_range)        
 | 
			
		||||
        # print()
 | 
			
		||||
        # print(stats_for_range)
 | 
			
		||||
        # print(start_day_of_year, " ",end_day_of_year)
 | 
			
		||||
        # Hier kannst du entscheiden, wie du die Daten über den Zeitraum aggregieren möchtest
 | 
			
		||||
        # Zum Beispiel könntest du Mittelwerte, Summen oder andere Statistiken über diesen Zeitraum berechnen
 | 
			
		||||
        return stats_for_range[0]
 | 
			
		||||
        return stats_for_range
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -70,7 +70,7 @@ class optimization_problem:
 | 
			
		||||
        self.toolbox.register("individual", create_individual)#tools.initCycle, creator.Individual, (self.toolbox.attr_bool,self.toolbox.attr_bool), n=self.prediction_hours+1)
 | 
			
		||||
        self.toolbox.register("population", tools.initRepeat, list, self.toolbox.individual)
 | 
			
		||||
        self.toolbox.register("mate", tools.cxTwoPoint)
 | 
			
		||||
        self.toolbox.register("mutate", tools.mutFlipBit, indpb=0.05)
 | 
			
		||||
        self.toolbox.register("mutate", tools.mutFlipBit, indpb=0.1)
 | 
			
		||||
        self.toolbox.register("select", tools.selTournament, tournsize=3)    
 | 
			
		||||
        
 | 
			
		||||
    def evaluate_inner(self,individual, ems,start_hour):
 | 
			
		||||
@@ -131,7 +131,7 @@ class optimization_problem:
 | 
			
		||||
 | 
			
		||||
    # Genetischer Algorithmus
 | 
			
		||||
    def optimize(self,start_solution=None):
 | 
			
		||||
        population = self.toolbox.population(n=1000)
 | 
			
		||||
        population = self.toolbox.population(n=400)
 | 
			
		||||
        hof = tools.HallOfFame(1)
 | 
			
		||||
        
 | 
			
		||||
        stats = tools.Statistics(lambda ind: ind.fitness.values)
 | 
			
		||||
@@ -145,7 +145,7 @@ class optimization_problem:
 | 
			
		||||
                population.insert(0, creator.Individual(start_solution))     
 | 
			
		||||
        
 | 
			
		||||
        #algorithms.eaMuPlusLambda(population, self.toolbox, 100, 200, cxpb=0.2, mutpb=0.2, ngen=500,             stats=stats, halloffame=hof, verbose=True)
 | 
			
		||||
        algorithms.eaSimple(population, self.toolbox, cxpb=0.2, mutpb=0.2, ngen=200,             stats=stats, halloffame=hof, verbose=True)
 | 
			
		||||
        algorithms.eaSimple(population, self.toolbox, cxpb=0.1, mutpb=0.1, ngen=400,             stats=stats, halloffame=hof, verbose=True)
 | 
			
		||||
        
 | 
			
		||||
        member = {"bilanz":[],"verluste":[],"nebenbedingung":[]}
 | 
			
		||||
        for ind in population:
 | 
			
		||||
@@ -159,14 +159,19 @@ class optimization_problem:
 | 
			
		||||
        return hof[0], member
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def optimierung_ems(self,parameter=None, start_hour=None,worst_case=False):
 | 
			
		||||
    def optimierung_ems(self,parameter=None, start_hour=None,worst_case=False, startdate=None):
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        ############
 | 
			
		||||
        # Parameter 
 | 
			
		||||
        ############
 | 
			
		||||
        date = (datetime.now().date() + timedelta(hours = self.prediction_hours)).strftime("%Y-%m-%d")
 | 
			
		||||
        date_now = datetime.now().strftime("%Y-%m-%d")
 | 
			
		||||
        if startdate == None:
 | 
			
		||||
                date = (datetime.now().date() + timedelta(hours = self.prediction_hours)).strftime("%Y-%m-%d")
 | 
			
		||||
                date_now = datetime.now().strftime("%Y-%m-%d")
 | 
			
		||||
        else:
 | 
			
		||||
                date = (startdate + timedelta(hours = self.prediction_hours)).strftime("%Y-%m-%d")
 | 
			
		||||
                date_now = startdate.strftime("%Y-%m-%d")
 | 
			
		||||
        #print("Start_date:",date_now)
 | 
			
		||||
        
 | 
			
		||||
        akku_size = parameter['pv_akku_cap'] # Wh
 | 
			
		||||
        year_energy = parameter['year_energy'] #2000*1000 #Wh
 | 
			
		||||
@@ -187,7 +192,7 @@ class optimization_problem:
 | 
			
		||||
        eauto.set_charge_per_hour(laden_moeglich)
 | 
			
		||||
        min_soc_eauto = parameter['eauto_min_soc']
 | 
			
		||||
        start_params = parameter['start_solution']
 | 
			
		||||
        gesamtlast = Gesamtlast()
 | 
			
		||||
        gesamtlast = Gesamtlast(prediction_hours=self.prediction_hours)
 | 
			
		||||
        
 | 
			
		||||
        ###############
 | 
			
		||||
        # spuelmaschine
 | 
			
		||||
@@ -205,7 +210,12 @@ class optimization_problem:
 | 
			
		||||
        ###############
 | 
			
		||||
        lf = LoadForecast(filepath=r'load_profiles.npz', year_energy=year_energy)
 | 
			
		||||
        #leistung_haushalt = lf.get_daily_stats(date)[0,...]  # Datum anpassen
 | 
			
		||||
        leistung_haushalt = lf.get_stats_for_date_range(date_now,date)[0,...].flatten()
 | 
			
		||||
        
 | 
			
		||||
        leistung_haushalt = lf.get_stats_for_date_range(date_now,date)[0] # Nur Erwartungswert!
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        gesamtlast.hinzufuegen("Haushalt", leistung_haushalt)
 | 
			
		||||
 | 
			
		||||
        ###############
 | 
			
		||||
@@ -229,8 +239,11 @@ class optimization_problem:
 | 
			
		||||
        ###############
 | 
			
		||||
        filepath = os.path.join (r'test_data', r'strompreise_akkudokAPI.json')  # Pfad zur JSON-Datei anpassen
 | 
			
		||||
        #price_forecast = HourlyElectricityPriceForecast(source=filepath)
 | 
			
		||||
        price_forecast = HourlyElectricityPriceForecast(source="https://api.akkudoktor.net/prices?start="+date_now+"&end="+date+"")
 | 
			
		||||
        price_forecast = HourlyElectricityPriceForecast(source="https://api.akkudoktor.net/prices?start="+date_now+"&end="+date+"", prediction_hours=self.prediction_hours)
 | 
			
		||||
        specific_date_prices = price_forecast.get_price_for_daterange(date_now,date)
 | 
			
		||||
        #print(price_forecast)
 | 
			
		||||
        print(specific_date_prices)
 | 
			
		||||
        #print("https://api.akkudoktor.net/prices?start="+date_now+"&end="+date)
 | 
			
		||||
 | 
			
		||||
        ###############
 | 
			
		||||
        # WP
 | 
			
		||||
 
 | 
			
		||||
@@ -14,20 +14,34 @@ utc_time = utc_time.replace(tzinfo=pytz.utc)
 | 
			
		||||
local_time = utc_time.astimezone(pytz.timezone('Europe/Berlin'))
 | 
			
		||||
print(local_time)
 | 
			
		||||
 | 
			
		||||
def repeat_to_shape(array, target_shape):
 | 
			
		||||
    # Prüfen , ob das Array in die Zielgröße passt
 | 
			
		||||
    if len(target_shape) != array.ndim:
 | 
			
		||||
        raise ValueError("Array and target shape must have the same number of dimensions")
 | 
			
		||||
 | 
			
		||||
    # die Anzahl der Wiederholungen pro Dimension
 | 
			
		||||
    repeats = tuple(target_shape[i] // array.shape[i] for i in range(array.ndim))
 | 
			
		||||
 | 
			
		||||
    #  np.tile, um das Array zu erweitern
 | 
			
		||||
    expanded_array = np.tile(array, repeats)
 | 
			
		||||
    return expanded_array
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HourlyElectricityPriceForecast:
 | 
			
		||||
    def __init__(self, source, cache_dir='cache', abgaben=0.00019):
 | 
			
		||||
    def __init__(self, source, cache_dir='cache', abgaben=0.000, prediction_hours=24): #228
 | 
			
		||||
        self.cache_dir = cache_dir
 | 
			
		||||
        if not os.path.exists(self.cache_dir):
 | 
			
		||||
            os.makedirs(self.cache_dir)
 | 
			
		||||
        self.cache_time_file = os.path.join(self.cache_dir, 'cache_timestamp.txt')
 | 
			
		||||
        self.prices = self.load_data(source)
 | 
			
		||||
        self.abgaben = abgaben
 | 
			
		||||
        self.prediction_hours = prediction_hours
 | 
			
		||||
    
 | 
			
		||||
    def load_data(self, source):
 | 
			
		||||
        cache_filename = self.get_cache_filename(source)
 | 
			
		||||
        if source.startswith('http'):
 | 
			
		||||
            cache_filename = self.get_cache_filename(source)
 | 
			
		||||
            if os.path.exists(cache_filename):
 | 
			
		||||
            if os.path.exists(cache_filename) and not self.is_cache_expired():
 | 
			
		||||
                print("Lade Daten aus dem Cache...")
 | 
			
		||||
                with open(cache_filename, 'r') as file:
 | 
			
		||||
                    data = json.load(file)
 | 
			
		||||
@@ -38,6 +52,7 @@ class HourlyElectricityPriceForecast:
 | 
			
		||||
                    data = response.json()
 | 
			
		||||
                    with open(cache_filename, 'w') as file:
 | 
			
		||||
                        json.dump(data, file)
 | 
			
		||||
                    self.update_cache_timestamp()
 | 
			
		||||
                else:
 | 
			
		||||
                    raise Exception(f"Fehler beim Abrufen der Daten: {response.status_code}")
 | 
			
		||||
        else:
 | 
			
		||||
@@ -50,6 +65,18 @@ class HourlyElectricityPriceForecast:
 | 
			
		||||
        hex_dig = hash_object.hexdigest()
 | 
			
		||||
        return os.path.join(self.cache_dir, f"cache_{hex_dig}.json")
 | 
			
		||||
    
 | 
			
		||||
    def is_cache_expired(self):
 | 
			
		||||
        if not os.path.exists(self.cache_time_file):
 | 
			
		||||
            return True
 | 
			
		||||
        with open(self.cache_time_file, 'r') as file:
 | 
			
		||||
            timestamp_str = file.read()
 | 
			
		||||
            last_cache_time = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S')
 | 
			
		||||
        return datetime.now() - last_cache_time > timedelta(hours=1)
 | 
			
		||||
    
 | 
			
		||||
    def update_cache_timestamp(self):
 | 
			
		||||
        with open(self.cache_time_file, 'w') as file:
 | 
			
		||||
            file.write(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    def get_price_for_date(self, date_str):
 | 
			
		||||
@@ -68,13 +95,17 @@ class HourlyElectricityPriceForecast:
 | 
			
		||||
        
 | 
			
		||||
        # Extrahieren aller Preise für das spezifizierte Datum
 | 
			
		||||
        date_prices = [entry["marketpriceEurocentPerKWh"]+self.abgaben for entry in self.prices if date_str in entry['end']]
 | 
			
		||||
        print("getPRice:",len(date_prices))
 | 
			
		||||
        
 | 
			
		||||
        # Hinzufügen des letzten Preises des vorherigen Tages am Anfang der Liste
 | 
			
		||||
        date_prices.insert(0, last_price_of_previous_day)
 | 
			
		||||
        if len(date_prices) == 23:
 | 
			
		||||
                date_prices.insert(0, last_price_of_previous_day)
 | 
			
		||||
 | 
			
		||||
        return np.array(date_prices)/(1000.0*100.0) + self.abgaben
 | 
			
		||||
    
 | 
			
		||||
    def get_price_for_daterange(self, start_date_str, end_date_str):
 | 
			
		||||
        print(start_date_str)
 | 
			
		||||
        print(end_date_str)
 | 
			
		||||
        """Gibt alle Preise zwischen dem Start- und Enddatum zurück."""
 | 
			
		||||
        start_date_utc = datetime.strptime(start_date_str, "%Y-%m-%d").replace(tzinfo=pytz.utc)
 | 
			
		||||
        end_date_utc = datetime.strptime(end_date_str, "%Y-%m-%d").replace(tzinfo=pytz.utc)
 | 
			
		||||
@@ -84,13 +115,16 @@ class HourlyElectricityPriceForecast:
 | 
			
		||||
        
 | 
			
		||||
        price_list = []
 | 
			
		||||
 | 
			
		||||
        while start_date <= end_date:
 | 
			
		||||
        while start_date < end_date:
 | 
			
		||||
            date_str = start_date.strftime("%Y-%m-%d")
 | 
			
		||||
            daily_prices = self.get_price_for_date(date_str)
 | 
			
		||||
            #print(len(self.get_price_for_date(date_str)))
 | 
			
		||||
            print(date_str," ",daily_prices)
 | 
			
		||||
            print(len(self.get_price_for_date(date_str)))
 | 
			
		||||
            
 | 
			
		||||
            if daily_prices.size ==24:
 | 
			
		||||
                price_list.extend(daily_prices)
 | 
			
		||||
            start_date += timedelta(days=1)
 | 
			
		||||
        
 | 
			
		||||
        return np.array(price_list)
 | 
			
		||||
        price_list = repeat_to_shape(np.array(price_list),(self.prediction_hours,))
 | 
			
		||||
        
 | 
			
		||||
        return price_list
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user