From 967be9e750221ab2ab783f95df79bb26d290a45e Mon Sep 17 00:00:00 2001 From: Martial Simon Date: Mon, 15 Sep 2025 01:07:58 +0200 Subject: add: added projects --- ero1/src/demo/ask_variable.py | 26 +++++ ero1/src/demo/demo_cost.py | 41 +++++++ ero1/src/demo/demo_exec.py | 229 +++++++++++++++++++++++++++++++++++++ ero1/src/demo/demo_final_report.py | 110 ++++++++++++++++++ ero1/src/demo/demo_stats.py | 64 +++++++++++ ero1/src/demo/print_demo.py | 20 ++++ 6 files changed, 490 insertions(+) create mode 100644 ero1/src/demo/ask_variable.py create mode 100644 ero1/src/demo/demo_cost.py create mode 100644 ero1/src/demo/demo_exec.py create mode 100644 ero1/src/demo/demo_final_report.py create mode 100644 ero1/src/demo/demo_stats.py create mode 100644 ero1/src/demo/print_demo.py (limited to 'ero1/src/demo') diff --git a/ero1/src/demo/ask_variable.py b/ero1/src/demo/ask_variable.py new file mode 100644 index 0000000..f2f9330 --- /dev/null +++ b/ero1/src/demo/ask_variable.py @@ -0,0 +1,26 @@ +from src.demo.print_demo import print_demo +import os + +def ask_variable(question, answers): + """ + Pose une question avec des réponses multiples et attend une réponse de l'utilisateur. + Parameters: + question : La question à afficher + answers : Liste de tuples contenant (réponse, valeur) + """ + print("") + print_demo(f"{question}") + for i, (answer_text, _) in enumerate(answers, 1): + print_demo(f"{i}. {answer_text}", 1) + + while True: + try: + choice = input("\n⟩ Entrez le numéro de votre choix ou 'stop' pour arrêter : ") + if choice == "stop": + raise KeyboardInterrupt + choice = int(choice) + if 1 <= choice <= len(answers): + return answers[choice - 1][1] + print_demo(f"Insérez un nombre entre 1 et {len(answers)}", 1) + except ValueError: + print_demo(f"Insérez un nombre entre 1 et {len(answers)}", 1) diff --git a/ero1/src/demo/demo_cost.py b/ero1/src/demo/demo_cost.py new file mode 100644 index 0000000..573e367 --- /dev/null +++ b/ero1/src/demo/demo_cost.py @@ -0,0 +1,41 @@ +import math + +def get_edge_length(graph, u, v): + try: + return graph[u][v][0]['length'] + except KeyError: + try: + return graph[v][u][0]['length'] + except KeyError: + return 0 + +def format_time(time_seconds): + seconds = math.ceil(time_seconds % 60) + minutes = math.floor(time_seconds / 60) + if minutes == 0: + return f"{seconds} secondes" + return f"{minutes} minute(s) et {seconds} secondes" + +def export_drone(graph, time_exec, parcours): + total_dist = 0 + time_exec = time_exec / 60 + total_dist += math.ceil(sum(get_edge_length(graph, u, v) for u, v in parcours)) + + return { + "dist":total_dist, + "time":{ + "format":format_time(time_exec), + "value":time_exec + } + } + +def export_deneigeuse(graph, time_exec, parcours): + total_dist = math.ceil(sum(get_edge_length(graph, u, v) for u, v in parcours)) + + return { + "dist":total_dist, + "time":{ + "format":format_time(time_exec), + "value":time_exec + } + } \ No newline at end of file diff --git a/ero1/src/demo/demo_exec.py b/ero1/src/demo/demo_exec.py new file mode 100644 index 0000000..1242ff5 --- /dev/null +++ b/ero1/src/demo/demo_exec.py @@ -0,0 +1,229 @@ +# DEMO IMPORT +from src.demo.print_demo import print_demo +from src.demo.ask_variable import ask_variable +from src.helper.color_suburbs import display_suburbs_with_colors +from src.demo.demo_cost import export_deneigeuse, export_drone, get_edge_length +from src.demo.demo_stats import stats_deneigeuses, stats_drone +from src.demo.demo_final_report import * # nsm, flemme de trier + +# IMPORT FOR GENERATION +from src.generation.graph_generation import generate_graph +from src.generation.suburb_snowplow_generation import generate_quartiers_from_path + +# IMPORT FOR DRONE: +from src.drone.postier_chinoisV2 import final_path, postier_chinois_process_v2, connect_circuits +from src.drone.postier_chinoisV1 import postier_chinois_process_v1 +from src.drone.postier_chinoisV3 import postier_chinois_process_v3 + +# IMPORT FOR DENEIGEUSE: +from src.deneigeuses.hierholzer_v1 import process_hierholzer_v1 +from src.deneigeuses.hierholzer_v2 import process_hierholzer_v2 +from src.deneigeuses.directed_cpp import oriented_cpt +from src.deneigeuses.drone_path import drone_path +from src.helper.partitions import partition +from src.deneigeuses.hangar_to_deneigeuse import path_hangar_to_deneigeuse, path_deneigeuse_to_hangar + +# IMPORT FOR YAML & KML +from src.helper.export_to_kml import export_to_kml +from src.helper.export_import_yaml import load_paths_from_yaml +import os +import time + + +def demo_exec(place, drone, deneigeuse, debug): + """ + Fonction principale pour le démarrage de la démo. + Parameters: + place : Liste des arrondissements à parcourir. + drone : Type de drone à utiliser. + deneigeuse : Type de déneigeuse à utiliser. + debug : Mode debug activé ou non. + """ + + # ---------------------- INITIALISATION --------------------------- + + os.system('cls' if os.name == 'nt' else 'clear') + print_demo("Démarrage du programme sur :", time=True) + for lieu in place: + print_demo(lieu, indent=1) + print("") + + # ---------------------- GENERATION DU GRAPHE --------------------------- + + print_demo("Génération du graph de Montréal en cours...", time=True) + graph = generate_graph("Montréal, Québec, Canada", debug_mode=debug) + print_demo("Graph généré avec succès.", indent=1) + + # ---------------------- PARCOURS - DRONE --------------------------- + + start_time_drone = time.time() + if drone == "postier_chinoisV1": + print_demo("Parcours du drone sur Montréal avec la version 1.", time=True) + parcours_drone, finalPath = postier_chinois_process_v1(graph, debug) + + elif drone == "postier_chinoisV2": + print_demo("Parcours du drone sur Montréal avec la version 2.", time=True) + parcours_drone, finalPath = postier_chinois_process_v2(graph, debug) + + elif drone == "postier_chinoisV3": + print_demo("Parcours du drone sur Montréal avec la version 3.", time=True) + parcours_drone, finalPath = postier_chinois_process_v3(graph, debug) + else: + print_demo(f"Récupération du parcours de Montréal sur le fichier : {drone}", time=True) + parcours_drone = load_paths_from_yaml(graph, drone) + finalPath = final_path(graph, parcours_drone) + + end_time_drone = time.time() + print_demo("Parcours du drone effectué avec succès.", indent=1) + + # ---------------------- AFFICHAGE DU GRAPHE --------------------------- + + Q_display = "Voulez-vous afficher le graph du parcours du drone ?" + A_display = [("Oui", True), ("Non", False)] + display = ask_variable(Q_display, A_display) + if display: + connections = connect_circuits(graph, parcours_drone) + display_suburbs_with_colors(graph, parcours_drone, connections) + + # ---------------------- RECUPERATION DU PARCOURS DU DRONE --------------------------- + + parcours_drone_arrondissements = [] + for elt in place: + if elt in parcours_drone: + parcours_drone_arrondissements.extend(parcours_drone[elt]) + else: + raise ValueError(f"{elt} introuvable dans le parcours du drone.") + + # ---------------------- CALCUL DES COUTS - DRONE --------------------------- + + cost_drone = export_drone( + graph, end_time_drone-start_time_drone, finalPath) + stat_drone = stats_drone(cost_drone) + + # ---------------------- CALCUL DU NOMBRE OPTIMAL DE DÉNEIGEUSES --------------------------- + + if deneigeuse == "naive": + print_demo("/!\\ L'algorithme de déneigeuse choisi ne dispose pas de la possibilité d'avoir plusieurs déneigeuses.", time=True) + number_of_deneigeuses = [1] + else: + optimal_number_deneigeuses = max(1, round((sum(get_edge_length( + graph, u, v) for u, v in parcours_drone_arrondissements)) / 200000)) + + Q_deneigeuse_number = "Avec quel nombre de déneigeuses voulez-vous comparer les coûts ?" + A_deneigeuse_number = [ + ("1 déneigeuse", 1), + (f"{optimal_number_deneigeuses} déneigeuses - Version Optimale", optimal_number_deneigeuses), + (f"{optimal_number_deneigeuses * 2} déneigeuses", optimal_number_deneigeuses * 2), (f"{optimal_number_deneigeuses * 3} déneigeuses", optimal_number_deneigeuses * 3), + ("Faire toutes les partitions et comparer les coûts", 0)] + number_reply = ask_variable(Q_deneigeuse_number, A_deneigeuse_number) + if number_reply == 0: + number_of_deneigeuses = [optimal_number_deneigeuses, 1, + optimal_number_deneigeuses * 2, optimal_number_deneigeuses * 3] + else: + number_of_deneigeuses = [number_reply] + print("") + + # ---------------------- GENERATION DU GRAPHE DE PARTITIONS --------------------------- + + G_partition = generate_quartiers_from_path( + parcours_drone, graph, suburb_list=place, debug_mode=debug) + + # ---------------------- PARCOURS - DÉNEIGEUSES --------------------------- + + if deneigeuse == "hierholzer_v1" or deneigeuse == "hierholzer_v2": + print_demo("Parcours de la déneigeuse à partir de l'algorithme de Hierholzer", time=True) + elif deneigeuse == "naive": + print_demo("Parcours de la déneigeuse à partir de l'implémentation naïve", time=True) + else: + print_demo("Parcours de la déneigeuse à partir de l'algorithme du Postier Chinois Orienté", time=True) + + cost_deneigeuses = {} + parcours_deneigeuses = {} + for i in number_of_deneigeuses: + cost_deneigeuses[i] = [] + parcours_deneigeuses[i] = [] + + if i == 1 and (deneigeuse == "naive" or "hierholzer" in deneigeuse): + temp = [] + for arrondissement in place: + temp.extend(parcours_drone[arrondissement]) + partitions = [temp] + else: + partitions = partition(G_partition, i, debug_mode=debug) + + for part in partitions: + start_time_deneigeuse = time.time() + drone_edges = [(u, v) for u, v in part.edges()] if i != 1 else part + + if "hierholzer" in deneigeuse: + if deneigeuse == "hierholzer_v2": + parcours_deneigeuse = process_hierholzer_v2( + graph, drone_edges, debug) + else: + parcours_deneigeuse = process_hierholzer_v1( + graph, drone_edges, debug) + + start_node = parcours_deneigeuse[0][0] + parcours_HangarDeneigeuse = path_hangar_to_deneigeuse( + graph, start_node) + end_node = parcours_deneigeuse[-1][1] + parcours_DeneigeuseHangar = path_deneigeuse_to_hangar( + graph, end_node) + + parcours_deneigeuse = parcours_HangarDeneigeuse + parcours_deneigeuse + parcours_DeneigeuseHangar + if deneigeuse == "oriented_cpt": + parcours_deneigeuse = oriented_cpt(part, graph, debug) + + start_node = parcours_deneigeuse[0][0] + parcours_HangarDeneigeuse = path_hangar_to_deneigeuse(graph, start_node) + end_node = parcours_deneigeuse[-1][1] + parcours_DeneigeuseHangar = path_deneigeuse_to_hangar(graph, end_node) + + parcours_deneigeuse = parcours_HangarDeneigeuse + parcours_deneigeuse + parcours_DeneigeuseHangar + if deneigeuse == "naive": + parcours_deneigeuse = drone_path(drone_edges, graph) + + start_node = parcours_deneigeuse[0][0] + parcours_HangarDeneigeuse = path_hangar_to_deneigeuse(graph, start_node) + end_node = parcours_deneigeuse[-1][1] + parcours_DeneigeuseHangar = path_deneigeuse_to_hangar(graph, end_node) + + parcours_deneigeuse = parcours_HangarDeneigeuse + parcours_deneigeuse + parcours_DeneigeuseHangar + + parcours_deneigeuses[i].append(parcours_deneigeuse) + end_time_deneigeuse = time.time() + + cost_deneigeuse = export_deneigeuse( + graph, end_time_deneigeuse-start_time_deneigeuse, parcours_deneigeuse) + cost_deneigeuses[i].append(cost_deneigeuse) + + stat_déneigeuses = {} + for i in number_of_deneigeuses: + stat_déneigeuses[i] = stats_deneigeuses(cost_deneigeuses[i]) + + print_demo("Parcours de la déneigeuse effectué avec succès.", indent=1) + + # ---------------------- CALCUL DES STATS - DÉNEIGEUSES --------------------------- + + os.system('cls' if os.name == 'nt' else 'clear') + + print_recap_stat_drone(stat_drone, 0) + + print("") + + print_all_stat(stat_déneigeuses, 0) + + print("") + print("--------------------") + print("") + + # ---------------------- EXPORTATION DU PARCOURS EN KML --------------------------- + + print_demo( + "Exportation des parcours des déneigeuses avec le parcours du drone au format KML.") + for i in parcours_deneigeuses: + print_demo( + f"{i} déneigeuses - Fichier KML : temp/parcours_{i}_deneigeuses.kml", indent=1) + export_to_kml(parcours_deneigeuses[i], finalPath, graph, debug, name=f"parcours_{i}_deneigeuses") + + return True diff --git a/ero1/src/demo/demo_final_report.py b/ero1/src/demo/demo_final_report.py new file mode 100644 index 0000000..59badda --- /dev/null +++ b/ero1/src/demo/demo_final_report.py @@ -0,0 +1,110 @@ +from src.demo.print_demo import print_demo +from src.demo.ask_variable import ask_variable +from src.helper.color_suburbs import display_suburbs_with_colors +from src.demo.demo_cost import export_deneigeuse, export_drone, get_edge_length +from src.demo.demo_stats import stats_deneigeuses, stats_drone + +# IMPORT FOR GENERATION +from src.generation.graph_generation import generate_graph +from src.generation.suburb_snowplow_generation import generate_quartiers_from_path + +# IMPORT FOR DRONE: +from src.drone.postier_chinoisV2 import final_path, postier_chinois_process_v2, connect_circuits +from src.drone.postier_chinoisV1 import postier_chinois_process_v1 +from src.drone.postier_chinoisV3 import postier_chinois_process_v3 + +# IMPORT FOR DENEIGEUSE: +from src.helper.partitions import partition +from src.deneigeuses.hangar_to_deneigeuse import path_hangar_to_deneigeuse, path_deneigeuse_to_hangar + +# IMPORT FOR YAML & KML +from src.helper.export_to_kml import export_to_kml +from src.helper.export_import_yaml import load_paths_from_yaml +import os +import time + + +def line(): + print("----------------------------------") + + +def print_cost_from_list(lst, base_indent=0): + """ + affiche la distance et le temps approximatif de chaque déneigeuses + """ + + # print_demo(f"Récap avec {len(lst)} déneigeuses:", base_indent) + print("") + print_demo("Récapitulatif des déneigeuses:", base_indent) + + for i, o in enumerate(lst): + print_demo(f"Déneigeuse numéro {i+1} :", base_indent + 1) + print_demo(f"Distance = {(o['dist']/1000):.2f} km", base_indent + 2) + print_demo(f"Durée approximative = {o['time']['format']}", base_indent + 2) + + +def print_recap_stat_target(stat_deneigeuses, nb_deneigeuse, most_opti=False, base_indent=0): + """ + Affiche dans le terminal sous un format organisé les statistiques + avec nb_deneigeuse déneigueses + """ + if (nb_deneigeuse not in stat_deneigeuses): + raise ValueError(f'Données pour {nb_deneigeuse} non calculées') + + data = stat_deneigeuses[nb_deneigeuse] + + if most_opti: + print_demo(f"Récapitulatif global (solution opti): {data['number']} déneigeuses", base_indent) + else: + print_demo(f"Récapitulatif global: {data['number']} déneigeuses", base_indent) + + # print_demo(f"nombre de déneigeuses = {data['number']}", base_indent + 1) + + print_demo("Distance parcourue par les déneigeuses :", base_indent + 1) + print_demo(f"Maximale = {data['dist']['max']:.2f} km", base_indent + 2) + print_demo(f"Minimale = {data['dist']['min']:.2f} km", base_indent + 2) + print_demo(f"Totale = {data['dist']['total']:.2f} km", base_indent + 2) + print_demo(f"Moyenne = {data['dist']['avg']:.2f} km", base_indent + 2) + + print_demo("Temps réel de parcours pour les déneigeuses:", base_indent + 1) + print_demo("Type 1 : ", base_indent+2) + print_demo(f"Maximale = {data['time']['real']['t1']['max']:.2f} heures", base_indent + 3) + print_demo(f"Moyenne = {data['time']['real']['t1']['avg']:.2f} heures", base_indent + 3) + print_demo("Type 2 : ", base_indent+2) + print_demo(f"Maximale = {data['time']['real']['t2']['max']:.2f} heures", base_indent + 3) + print_demo(f"Moyenne = {data['time']['real']['t2']['avg']:.2f} heures", base_indent + 3) + + print_demo("Coût total :", base_indent + 1) + print_demo(f"Type 1 = {data['cost']['t1']:.2f} euros", base_indent + 2) + print_demo(f"Type 2 = {data['cost']['t2']:.2f} euros", base_indent + 2) + + print_cost_from_list(data['list'], base_indent) + + +def print_recap_stat_drone(stat_drone, base_indent=0): + print("##################") + print("# Recap du drone #") + print("##################") + # dist en km plus bas print_demo(f"distance parcourue par le drone: {stat_drone["dist"]}", base_indent+1) + print_demo("Temps écoulé :", base_indent) + print_demo(f"Durée de l'exécution du programme : {stat_drone['time']['format']}", base_indent+1) + print_demo(f"Durée réelle du parcours :", base_indent+1) + print_demo(f"{stat_drone['time']['real']:.2f} minutes", base_indent+2) + print_demo(f"{(stat_drone['time']['real'] / 60):.2f} heures", base_indent+2) + print_demo(f"Distance parcourue : {stat_drone['distKM']:.2f} km", base_indent) + print_demo(f"Coût : {stat_drone['cost']:.2f} euros", base_indent) + + +def print_all_stat(stat_list, base_indent): + print("#########################") + print("# Recap des déneigeuses #") + print("#########################") + # line() + + best = True + for elem in stat_list: + print("") + line() + print("") + print_recap_stat_target(stat_list, elem, best, base_indent) + best = False diff --git a/ero1/src/demo/demo_stats.py b/ero1/src/demo/demo_stats.py new file mode 100644 index 0000000..d8dc78c --- /dev/null +++ b/ero1/src/demo/demo_stats.py @@ -0,0 +1,64 @@ +import math +import parameters + +def stats_deneigeuses(all_costs): + ''' + Calcule les statistiques des déneigeuses. + Parameters: + all_costs : Liste des coûts des déneigeuses. + Returns: + Un dictionnaire contenant les statistiques des déneigeuses. + ''' + result = { + "list":all_costs + } + result["number"] = len(all_costs) + result["dist"] = { + "max": max(d["dist"] for d in all_costs) / 1000, + "min": min(d["dist"] for d in all_costs) / 1000, + "total": sum(d["dist"] for d in all_costs) / 1000 + } + result["dist"]["avg"] = result["dist"]["total"] / result["number"] + result["time"] = {} + result["time"]["real"] = {"t1": {}, "t2": {}} + result["time"]["real"]["t1"]["avg"] = result["dist"]["avg"] / parameters.TYPE_I_SPEED + result["time"]["real"]["t2"]["avg"] = result["dist"]["avg"] / parameters.TYPE_II_SPEED + result["time"]["real"]["t1"]["max"] = result["dist"]["max"] / parameters.TYPE_I_SPEED + result["time"]["real"]["t2"]["max"] = result["dist"]["max"] / parameters.TYPE_II_SPEED + + + def coutHoraireTypeI(heures): + return heures * parameters.TYPE_I_COST_HOUR_UNDER_8 if heures <= 8 else 8 * parameters.TYPE_I_COST_HOUR_UNDER_8 + (heures - 8) * parameters.TYPE_I_COST_HOUR_ABOVE_8 + + def coutHoraireTypeII(heures): + return heures * parameters.TYPE_II_COST_HOUR_UNDER_8 if heures <= 8 else 8 * parameters.TYPE_II_COST_HOUR_UNDER_8 + (heures - 8) * parameters.TYPE_II_COST_HOUR_ABOVE_8 + + coutTotalTypeI = ( + parameters.TYPE_I_COST_FIXED * (result["number"]) + + parameters.TYPE_I_COST_KILO * result["dist"]["total"] + + coutHoraireTypeI(result["time"]["real"]["t1"]["avg"] * result["number"])) + + + coutTotalTypeII = ( + parameters.TYPE_II_COST_FIXED * result["number"] + + parameters.TYPE_II_COST_KILO * result["dist"]["total"] + + coutHoraireTypeII(result["time"]["real"]["t2"]["avg"] * result["number"]) + ) + result["cost"] = { + "t1":coutTotalTypeI, + "t2":coutTotalTypeII + } + return result + +def stats_drone(cost): + ''' + Calcule les statistiques du drone. + Parameters: + cost : Dictionnaire contenant les coûts du drone. + Returns: + Un dictionnaire contenant les statistiques du drone. + ''' + cost["distKM"] = cost["dist"] / 1000 + cost["time"]["real"] = (cost["distKM"] / parameters.SUPER_DRONE_SPEED) * 60 + cost["cost"] = parameters.SUPER_DRONE_COST_FIXED + parameters.SUPER_DRONE_COST_KILO * (cost["dist"] / 1000) + return cost \ No newline at end of file diff --git a/ero1/src/demo/print_demo.py b/ero1/src/demo/print_demo.py new file mode 100644 index 0000000..f44d98b --- /dev/null +++ b/ero1/src/demo/print_demo.py @@ -0,0 +1,20 @@ +from datetime import datetime + + +def print_demo(message, indent=0, time=False): + """ + Affiche dans la console un message de démo formaté. + Parameters: + message : Le message à afficher. + indent : Le nombre d'espaces à ajouter devant le message. + time : Si True, affiche le timestamp. + """ + # Récupérer le timestamp actuel en format string + str_time = datetime.now().strftime('%H:%M:%S.%f')[:-3] + time_in_str= f"[{str_time}] " if time else "" + space_in_str= " " * indent + + if indent == 0: + print(f"{time_in_str}» {message}") + else: + print(f"{space_in_str} {time_in_str}› {message}") -- cgit v1.2.3