import numpy as np import random import yaml # Sezione di print: import pandas as pd import plotly.graph_objects as go ################################################################################################################################################### def print_result(perc_econd,n_devices,total_energy_shared,total_incentive,total_energy_consumption,total_energy_cost,perc_error,*param): print("\nGA OPT\n") print("Percentuale condivisione: {:.2f} %".format(float(perc_econd)*100)) print("Percentuale condivisione per singolo dispositivo: {:.2f} %".format(float(perc_econd/n_devices)*100)) print(f'Total energy sherred: {total_energy_shared}') print(f'Incentivo totale: {total_incentive}') print(f'Total energy consumption: {total_energy_consumption}') print(f'Costo totale: {total_energy_cost}') print("Optimization in percentuale su consumo: {:.2f}%".format(float((perc_error)*100))) print('popolazione {}, generazione: {}, mutazione: {}'.format(*param)) ################################################################################################################################################### def stampa_result(dataframe, immission_profile, num_intervals, day, show_plot = True): # # print per tutti i dispositiv: df = pd.DataFrame({ 'immission_profile': immission_profile, }, index=np.arange(num_intervals)) # # Create figure: x = pd.date_range(start='2018-01-01 00:00+00:00', freq='15min', inclusive='left', periods=97) fig = go.Figure() # tutte queste sono cumulate, quindi stacked area for column in dataframe.columns: fig.add_trace(go.Scatter( x = x, y = dataframe[column], mode='lines', name = column, line_shape = 'hv', textposition='top center',stackgroup='one')) # Inserisco il profilo di immissione: fig.add_trace(go.Scatter( x = x, y = df['immission_profile'], mode='lines', name = 'immission_profile', line_shape = 'hv', textposition='top center')) fig.update_yaxes(title_text = "Power (kW)") # verificare se è effettivamente la potenza: fig.update_xaxes(title_text = 'Tempo (HH:MM)', dtick = 1000*60*30, range = [x[0], x[96]]) fig.update_layout(title= f'Day {day}', xaxis_tickformat = '%H:%M', plot_bgcolor='white', legend = dict(orientation='h',yanchor="top", y = -0.4, xanchor="left", x=0.01)) #barmode='stack') # si setta la modalità di rappresentazione del grafico a barre, in questo caso avranno una visualizzazione di tipo "stack", ovvero impilate le une sulle altre if show_plot: fig.show() ######################################################################################################### config = yaml.safe_load(open("config.yml", 'r')) path = config['forldername_graphs_DSM_optimizer'] title = "DSM optimization day " + str(day) fig.write_html(path + title + ".html") fig.write_image(path + title + ".png", width=1000, height=1100/13.2*5, scale = 4) ######################################################################################################### # print delle tipologie d'utente: # fig = go.Figure() # Definizione dei raggruppamenti basati sulle stringhe specifiche # user_types = ['single', 'coppia', 'famiglia_3p', 'famiglia_4p', 'famiglia_mag4p'] # Dizionario per memorizzare i risultati delle somme per ogni gruppo # group_sums = {} # Iterazione sui tipi di utenti per raggruppare e sommare le colonne # for user_type in user_types: # # Selezione delle colonne che contengono la stringa specifica # columns_to_group = [col for col in dataframe.columns if user_type in col] # # Somma delle colonne selezionate # df_grouped_sum = dataframe[columns_to_group].sum(axis=1) # # Memorizzazione del risultato nel dizionario # group_sums[user_type] = df_grouped_sum # Creazione di un nuovo DataFrame dai risultati delle somme # df_grouped_sums = pd.DataFrame(group_sums) # # tutte queste sono cumulate, quindi stacked area # for column in df_grouped_sums.columns: # fig.add_trace(go.Scatter( # x = x, y = df_grouped_sums[column], mode='lines', # name = column, # line_shape = 'hv', #'spline', # textposition='top center',stackgroup='one')) # # Inserisco il profilo di immissione: # fig.add_trace(go.Scatter( # x = x, y = df['immission_profile'], mode='lines', # name = 'immission_profile', # line_shape = 'hv', #'spline', # textposition='top center')) # fig.update_yaxes(title_text = "Power (kW)") # verificare se è effettivamente la potenza: # fig.update_xaxes(title_text = 'Tempo (HH:MM)', dtick = 1000*60*30, range = [x[0], x[96]]) # fig.update_layout(title= 'GA Optimization 2', xaxis_tickformat = '%H:%M', plot_bgcolor='white', legend = dict(orientation='h',yanchor="top", y = -0.4, xanchor="left", x=0.01)) #barmode='stack') # si setta la modalità di rappresentazione del grafico a barre, in questo caso avranno una visualizzazione di tipo "stack", ovvero impilate le une sulle altre # #fig.show() # Print con aggregati: # fig = go.Figure() # df = pd.DataFrame({ # 'autonsumption_profile': autoconsumption_profile, # 'pv_profile': pv_profile # }, index=np.arange(num_intervals)) # # Inserisco i profili di produzione: # fig.add_trace(go.Scatter( # x = x, y = df['pv_profile'], mode='lines', # name = 'pv_profile', # line_shape = 'hv', #'spline', # textposition='top center')) # fig.add_trace(go.Scatter( # x = x, y = df['autonsumption_profile'], mode='lines', # name = 'autonsumption_profile', # line_shape = 'hv', #'spline', # textposition='top center',stackgroup='one')) # # Inserisco il profilo dei dispositivi: # for column in df_grouped_sums.columns: # fig.add_trace(go.Scatter( # x = x, y = df_grouped_sums[column], mode='lines', # name = column, # line_shape = 'hv', #'spline', # textposition='top center',stackgroup='one')) # fig.update_yaxes(title_text = "Power (kW)") # verificare se è effettivamente la potenza: # fig.update_xaxes(title_text = 'Tempo (HH:MM)', dtick = 1000*60*30, range = [x[0], x[96]]) # fig.update_layout(title= 'GA Optimization 2', xaxis_tickformat = '%H:%M', plot_bgcolor='white', legend = dict(orientation='h',yanchor="top", y = -0.4, xanchor="left", x=0.01)) #barmode='stack') # si setta la modalità di rappresentazione del grafico a barre, in questo caso avranno una visualizzazione di tipo "stack", ovvero impilate le une sulle altre # fig.update_layout(autosize=False,width=1400,height=800) # html_str = fig.to_html(full_html=False, include_plotlyjs='cdn') # #fig.show() ################################################################################################################################################### def stampa_best_solution_gen(fig, best_solution_gen,params, n_device): # create the dataframe: df = pd.DataFrame({ 'fitness_values': best_solution_gen, }, index=np.arange(len(best_solution_gen))) x = df.index fig.add_trace(go.Scatter( x = x, y = df['fitness_values'], mode='lines', name = 'ft_p_{}_g_{}_m_{}'.format(*params), line_shape = 'hv', textposition='top center')) fig.update_yaxes(title_text = "fitness_values") fig.update_xaxes(title_text = 'Number of Gen') fig.update_layout(title= f'GA Optimization Device for {n_device} devices', plot_bgcolor='white', legend = dict(orientation='h',yanchor="top", y = -0.4, xanchor="left", x=0.01)) #barmode='stack') # si setta la modalità di rappresentazione del grafico a barre, in questo caso avranno una visualizzazione di tipo "stack", ovvero impilate le une sulle altre ################################################################################################################################################### def intervallo_funzionamento(start,end,n): giorno = np.zeros(n,int) for i in range(start,end): giorno[i]=1 return giorno ################################################################################################################################################### # Funzione per generare un profilo di consumo casuale di lunghezza specificata def generate_random_consumption_profile(length): return np.random.rand(length) ################################################################################################################################################### def first_nonzero_index(arr): """funzione per trovare l'indice del primo elemento non nullo di un array Args: arr (array): array di numpy Returns: int: indice del primo elemento non nullo, 0 se l'array è vuoto """ nonzero_indices = np.nonzero(arr)[0] return nonzero_indices[0] if nonzero_indices.size > 0 else 0 ################################################################################################################################################### # Function to calculate total energy cost for a device on/off schedule # Precedente # def calculate_device_cost(device_schedule, immission_profile, max_power_contract, energy_cost_per_hour): # total_energy_cost = 0 # total_energy_consumption = 0 # total_energy_shared = 0 # incentivo = 0.11 # total_cost = 0 # num_intervals = len(device_schedule[0]) # for t in range(num_intervals): # total_power = sum(device[t] for device in device_schedule) # for device in device_schedule: # # Verifica del vincolo di potenza contrattuale massima # if device[t] > max_power_contract: # return float('inf') # Penalità per violazione del vincolo # total_energy_cost = total_power*energy_cost_per_hour[t] # total_energy_shared = min(total_power, immission_profile[t])*incentivo # # # Se supero energia immessa: # # if total_power > immission_profile[t]: # # total_energy_cost += total_power # # Calcolo del costo considerando l'immissione nel profilo # total_cost += total_energy_cost - total_energy_shared # return total_cost ################################################################################################################################################### # Post rev. NO check # def calculate_device_cost(device_schedule, immission_profile, energy_cost_per_hour): # device_schedule = np.array(device_schedule) # total_power = device_schedule.sum(axis=0) # total_energy_cost = total_power * np.array(energy_cost_per_hour) # total_energy_shared = np.minimum(total_power, immission_profile) * 0.11 # total_cost = total_energy_cost.sum() - total_energy_shared.sum() # return total_cost ################################################################################################################################################### def calculate_device_cost(device_schedule, immission_profile, energy_cost_per_hour): # device_schedule = np.asarray(device_schedule) # total_power = device_schedule.sum(axis=0) total_power = np.sum(device_schedule, axis=0) total_energy_cost = np.dot(total_power, energy_cost_per_hour) total_energy_shared = np.dot(np.minimum(total_power, immission_profile), 0.11) total_cost = total_energy_cost.sum() - total_energy_shared.sum() return total_cost # Objective function to calculate total energy cost for all solutions # def objective_function(solutions): # total_costs = [] # for solution in solutions: # total_cost = np.sum(calculate_device_cost(solution, consumption_profiles)) # total_costs.append(total_cost) # return total_costs ################################################################################################################################################### # Funzione per generare una soluzione casuale rispettando il profilo di consumo di ciascun dispositivo # PRE rev. def generate_random_solution(num_intervals, consumption_profiles, allowed_intervals, possible_starts): solution = [] for profile in consumption_profiles: # Seleziona casualmente un punto di inizio nel profilo di consumo # Integro ora il vincolo sugli istanti di tempo permessi: start_index = random.choice(possible_starts) # start_index = np.random.randint(0, num_intervals - len(profile)) # Costruisci il programma di utilizzo del dispositivo utilizzando il profilo di consumo device_schedule = [0] * num_intervals device_schedule[start_index:start_index + len(profile)] = profile solution.append(device_schedule) return solution ################################################################################################################################################### # def generate_random_solution(num_intervals, consumption_profiles, allowed_intervals, possible_starts): # solution = np.zeros((len(consumption_profiles), num_intervals)) # profile_lengths = np.array([len(profile) for profile in consumption_profiles]) # for i, profile in enumerate(consumption_profiles): # start_index = np.random.choice(possible_starts) # solution[i, start_index:start_index + profile_lengths[i]] = profile # return solution ################################################################################################################################################### # #Mutation function to randomly modify an existing solution def mutate_solution(solution, mutation_rate, consumption_profiles, allowed_intervals, num_intervals, possible_starts): for i, device_schedule in enumerate(solution): if np.random.rand() < mutation_rate: # Seleziona casualmente un nuovo punto di inizio per il profilo di consumo # Integro ora il vincolo sugli istanti di tempo permessi: profile = consumption_profiles[i] new_start_index = random.choice(possible_starts) #new_start_index = np.random.randint(0, len(device_schedule) - len(profile)) # Aggiorna il programma di utilizzo del dispositivo new_device_schedule = [0] * len(device_schedule) new_device_schedule[new_start_index:new_start_index + len(profile)] = profile solution[i] = new_device_schedule return solution ################################################################################################################################################### # def mutate_solution(solution, mutation_rate, consumption_profiles, allowed_intervals, num_intervals, possible_starts): # profile_lengths = np.array([len(profile) for profile in consumption_profiles]) # for i, device_schedule in enumerate(solution): # if np.random.rand() < mutation_rate: # profile = consumption_profiles[i] # new_start_index = np.random.choice(possible_starts) # solution[i, :] = 0 # solution[i, new_start_index:new_start_index + profile_lengths[i]] = profile # return solution ################################################################################################################################################### # PRE #Algoritmo genetico def genetic_algorithm(num_intervals, population_size, generations, mutation_rate, consumption_profiles,immission_profile, max_power_contract, energy_cost_per_hour, allowed_intervals): # calcolo possibili istanti attivazione: for profile in consumption_profiles: possible_starts = [i for i in allowed_intervals if i + len(profile)-1 <= allowed_intervals[-1]] #ultimo valore di allowed intervals possible_starts = sorted(possible_starts, key=lambda x: -sum(immission_profile[x:x + len(profile)])) # Inizializza la popolazione iniziale di soluzioni casuali population = [generate_random_solution(num_intervals, consumption_profiles, allowed_intervals, possible_starts) for _ in range(population_size)] # per analisi, eliminare poi: best_solution_gen_value =[] # Esegui le iterazioni per un numero fisso di generazioni for generation in range(generations): # Calcola il valore di fitness per ogni soluzione nella popolazione # fitness_values = objective_function(population, consumption_profiles) fitness_values = [np.sum(calculate_device_cost(solution, immission_profile, energy_cost_per_hour)) for solution in population] # Seleziona i genitori in base al loro valore di fitness parents = [population[i] for i in np.argsort(fitness_values)[:int(population_size/2)]] # Genera nuovi individui attraverso incrocio (crossover) children = [] for _ in range(population_size - len(parents)): parent1, parent2 = random.sample(parents, 2) crossover_point = np.random.randint(num_intervals) child = parent1[:crossover_point] + parent2[crossover_point:] #child = np.concatenate((parent1[:, :crossover_point], parent2[:, crossover_point:]), axis=1) children.append(child) # Aggiorna la popolazione con nuovi individui generati attraverso crossover e mutazione population = parents + children population = [mutate_solution(solution, mutation_rate, consumption_profiles, allowed_intervals, num_intervals, possible_starts) for solution in population] # Elitismo: conserva la migliore soluzione best_solution = min(population, key=lambda sol: calculate_device_cost(sol, immission_profile, energy_cost_per_hour)) population[0] = best_solution # Introduci diversità ogni 10 generazioni if generation % 5 == 0: for i in range(len(population)): if np.random.rand() < 0.1: population[i] = generate_random_solution(num_intervals, consumption_profiles,allowed_intervals, possible_starts) # Per analisi ---> da eliminare poi best_solution_gen_value.append(fitness_values[np.argmin(fitness_values)]) # Calcola il valore di fitness per ogni soluzione nella popolazione finale fitness_values = [np.sum(calculate_device_cost(solution, immission_profile, energy_cost_per_hour)) for solution in population] # Trova e restituisci la migliore soluzione best_solution_index = np.argmin(fitness_values) best_solution = population[best_solution_index] best_cost = fitness_values[best_solution_index] return best_solution, best_cost, best_solution_gen_value ################################################################################################################################################### # POST using Concurrency: too slow # from concurrent.futures import ProcessPoolExecutor # def calculate_fitness(solution, immission_profile, energy_cost_per_hour): # return calculate_device_cost(solution, immission_profile, energy_cost_per_hour) ################################################################################################################################################### # def fitness_wrapper(args): # solution, immission_profile, energy_cost_per_hour = args # return calculate_fitness(solution, immission_profile, energy_cost_per_hour) ################################################################################################################################################### # # # # POST rev No Check # def genetic_algorithm(num_intervals, population_size, generations, mutation_rate, consumption_profiles, immission_profile, max_power_contract, energy_cost_per_hour, allowed_intervals): # population = [generate_random_solution(num_intervals, consumption_profiles, allowed_intervals, immission_profile) for _ in range(population_size)] # best_solution_gen_value = [] # with ProcessPoolExecutor() as executor: # for generation in range(generations): # fitness_values = list(executor.map(fitness_wrapper, [(sol, immission_profile, energy_cost_per_hour) for sol in population])) # parents = [population[i] for i in np.argsort(fitness_values)[:int(population_size / 2)]] # children = [] # for _ in range(population_size - len(parents)): # parent1, parent2 = random.sample(parents, 2) # crossover_point = np.random.randint(num_intervals) # if len(parent1) != len(parent2): # parent1, parent2 = np.broadcast_to(parent1, (len(parent2), len(parent2[0]))), np.broadcast_to(parent2, (len(parent1), len(parent1[0]))) # child = np.concatenate((parent1[:crossover_point], parent2[crossover_point:]), axis=0) # children.append(child) # population = parents + children # population = parents + children # population = [mutate_solution(solution, mutation_rate, consumption_profiles, allowed_intervals, num_intervals, immission_profile) for solution in population] # best_solution = min(population, key=lambda sol: fitness_wrapper((sol, immission_profile, energy_cost_per_hour))) # population[0] = best_solution # if generation % 5 == 0: # for i in range(len(population)): # if np.random.rand() < 0.1: # population[i] = generate_random_solution(num_intervals, consumption_profiles, allowed_intervals, immission_profile) # best_solution_gen_value.append(min(fitness_values)) # fitness_values = [fitness_wrapper((sol, immission_profile, energy_cost_per_hour)) for sol in population] # best_solution_index = np.argmin(fitness_values) # best_solution = population[best_solution_index] # best_cost = fitness_values[best_solution_index] # return best_solution, best_cost, best_solution_gen_value ################################################################################################################################################### ################################################################################################################################################### ################################################################################################################################################### ###################################################################################################################################################