From 7e8179bfb99b298c336aa31008f311044f0cb6f2 Mon Sep 17 00:00:00 2001 From: unknown <22013326@etu.unicaen.fr> Date: Mon, 10 Mar 2025 20:57:15 +0100 Subject: [PATCH] Exo4 OK --- TP_2_3/exo4.py | 403 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 403 insertions(+) create mode 100644 TP_2_3/exo4.py diff --git a/TP_2_3/exo4.py b/TP_2_3/exo4.py new file mode 100644 index 0000000..4da879a --- /dev/null +++ b/TP_2_3/exo4.py @@ -0,0 +1,403 @@ +import random +import itertools + +class Robot: + def __init__(self, id, capacity): + self.id = id + self.capacity = capacity + + def __repr__(self): + return f"Robot({self.id})" + + +class Zone: + def __init__(self, id, dirt): + self.id = id + self.dirt = dirt + + def __repr__(self): + return f"Zone({self.id})" + +class TaskAllocation: + def __init__(self, robotliste, zoneliste): + self.robotliste = robotliste + self.zoneliste = zoneliste + # Création d'un dictionnaire d'utilités aléatoires pour chaque paire (robot, zone) + self.dictio = {(robot, zone): random.randint(1, 10) for robot, zone in itertools.product(robotliste, zoneliste)} + + def allocation(self, cuf): + # On garde votre structure mais on corrige le problème d'assignation + best_allocation = None + best_utility = float('-inf') # On commence avec une utilité très basse + + # On utilise une approche simplifiée pour générer des allocations + # On distribue les zones de manière cyclique avec différents points de départ + for start in range(len(self.robotliste)): + current_allocation = {robot: [] for robot in self.robotliste} + for i, zone in enumerate(self.zoneliste): + robot_index = (start + i) % len(self.robotliste) + current_allocation[self.robotliste[robot_index]].append(zone) + + utility = cuf.utility(current_allocation) + if best_allocation is None or utility > best_utility: + best_utility = utility + best_allocation = current_allocation + + return best_allocation + + def dummy_allocation(self, robotliste, zoneliste): + # On garde votre fonction exactement comme elle était + i = 0 + dico = {robot: [] for robot in robotliste} + for zone in zoneliste: + robot = robotliste[i % len(robotliste)] + dico[robot].append(zone) + i += 1 + return dico + + def calculate_cleaning_time(self, allocation): + # Nouvelle fonction pour calculer le temps de nettoyage + robot_times = [] + + for robot, zones in allocation.items(): + total_dirt = sum(zone.dirt for zone in zones) + cleaning_time = total_dirt / robot.capacity + robot_times.append(cleaning_time) + + # Le temps total est le maximum des temps individuels + return max(robot_times) if robot_times else 0 + + def getdictio(self): + return self.dictio + + +class CollectiveUtilityFunction: + def __init__(self, dico, utility): + # On garde votre nom de paramètre "utility" au lieu de "utility_type" + self.utility = getattr(self, utility) + self.dico = dico + + def utilitariste(self, allocation): + total_uti = 0 + for robot, zones in allocation.items(): + for zone in zones: + total_uti += self.dico[(robot, zone)] + return total_uti + + def egalitariste(self, allocation): + uti_par_robot = {} + for robot, zones in allocation.items(): + robot_utility = 0 + for zone in zones: + robot_utility += self.dico[(robot, zone)] + uti_par_robot[robot] = robot_utility + + # Si aucun robot n'a d'utilité, retourner 0 + if not uti_par_robot: + return 0 + + return min(uti_par_robot.values()) + + def elitiste(self, allocation): + uti_par_robot = {} + for robot, zones in allocation.items(): + robot_utility = 0 + for zone in zones: + robot_utility += self.dico[(robot, zone)] + uti_par_robot[robot] = robot_utility + + # Si aucun robot n'a d'utilité, retourner 0 + if not uti_par_robot: + return 0 + + return max(uti_par_robot.values()) + + def nash(self, allocation): + produit_uti = 1 + for robot, zones in allocation.items(): + robot_uti = 0 + for zone in zones: + robot_uti += self.dico[(robot, zone)] + + # Pour éviter de multiplier par 0 + if robot_uti > 0: + produit_uti *= robot_uti + + return produit_uti + + +# Nouvelle classe pour l'allocation avec contraintes de précédence +class PrecedenceAllocation(TaskAllocation): + def __init__(self, robotliste, zoneliste, precedences=None): + super().__init__(robotliste, zoneliste) + + # Si aucune précédence n'est fournie, on en crée aléatoirement + if precedences is None: + self.precedences = self.generate_random_precedences() + else: + self.precedences = precedences + + # Vérifier la consistance des précédences + if not self.check_precedence_consistency(): + print("Attention: Les contraintes de précédence contiennent des cycles!") + + def generate_random_precedences(self): + """ + Génère aléatoirement des contraintes de précédence entre les zones. + Une contrainte (zone1, zone2) signifie que zone1 doit être nettoyée avant zone2. + """ + precedences = [] + + # Pour éviter les cycles, on crée un ordre aléatoire des zones + # et on ajoute des précédences uniquement dans cet ordre + zones_order = list(self.zoneliste) + random.shuffle(zones_order) + + # Ajouter quelques précédences aléatoires (pas trop pour garder ça simple) + num_precedences = random.randint(1, len(zones_order) - 1) + + for _ in range(num_precedences): + # Choisir deux indices aléatoires i < j pour éviter les cycles + i = random.randint(0, len(zones_order) - 2) + j = random.randint(i + 1, len(zones_order) - 1) + + # Ajouter la précédence + precedences.append((zones_order[i], zones_order[j])) + + return precedences + + def check_precedence_consistency(self): + """ + Vérifie s'il n'y a pas de cycles dans les contraintes de précédence. + Utilise un algorithme de détection de cycle dans un graphe orienté. + """ + # Construire un graphe orienté à partir des précédences + graph = {zone: [] for zone in self.zoneliste} + for zone1, zone2 in self.precedences: + graph[zone1].append(zone2) + + # Fonction pour détecter un cycle à partir d'un nœud + def has_cycle(node, visited, rec_stack): + visited[node] = True + rec_stack[node] = True + + for neighbor in graph[node]: + if not visited[neighbor]: + if has_cycle(neighbor, visited, rec_stack): + return True + elif rec_stack[neighbor]: + return True + + rec_stack[node] = False + return False + + # Vérifier pour chaque nœud s'il y a un cycle + visited = {zone: False for zone in self.zoneliste} + rec_stack = {zone: False for zone in self.zoneliste} + + for zone in self.zoneliste: + if not visited[zone]: + if has_cycle(zone, visited, rec_stack): + return False # Il y a un cycle + + return True # Pas de cycle + + def check_precedence_constraints(self, allocation): + """ + Vérifie si une allocation respecte toutes les contraintes de précédence. + Une allocation respecte les contraintes si pour chaque précédence (zone1, zone2), + soit zone1 et zone2 sont attribuées au même robot et zone1 apparaît avant zone2 dans la liste, + soit zone1 est attribuée à un robot différent de zone2. + """ + # Créer un dictionnaire qui associe chaque zone à son robot + zone_to_robot = {} + for robot, zones in allocation.items(): + for zone in zones: + zone_to_robot[zone] = robot + + # Créer un dictionnaire qui associe chaque zone à sa position dans la liste de son robot + zone_to_position = {} + for robot, zones in allocation.items(): + for i, zone in enumerate(zones): + zone_to_position[zone] = i + + # Vérifier chaque contrainte de précédence + for zone1, zone2 in self.precedences: + # Si les deux zones sont attribuées au même robot + if zone_to_robot[zone1] == zone_to_robot[zone2]: + # Vérifier que zone1 apparaît avant zone2 dans la liste + if zone_to_position[zone1] >= zone_to_position[zone2]: + return False + + return True + + def allocation(self, cuf): + """ + Calcule une allocation qui respecte les contraintes de précédence + et maximise l'utilité collective. + """ + best_allocation = None + best_utility = float('-inf') + + # Générer toutes les allocations possibles (approche simplifiée) + for start in range(len(self.robotliste)): + current_allocation = {robot: [] for robot in self.robotliste} + + # Trier les zones selon les contraintes de précédence + sorted_zones = self.topological_sort() + + # Distribuer les zones de manière cyclique + for i, zone in enumerate(sorted_zones): + robot_index = (start + i) % len(self.robotliste) + current_allocation[self.robotliste[robot_index]].append(zone) + + # Vérifier si l'allocation respecte les contraintes de précédence + if self.check_precedence_constraints(current_allocation): + utility = cuf.utility(current_allocation) + if best_allocation is None or utility > best_utility: + best_utility = utility + best_allocation = current_allocation + + return best_allocation + + def topological_sort(self): + """ + Trie les zones selon un ordre topologique respectant les contraintes de précédence. + """ + # Construire un graphe orienté à partir des précédences + graph = {zone: [] for zone in self.zoneliste} + in_degree = {zone: 0 for zone in self.zoneliste} + + for zone1, zone2 in self.precedences: + graph[zone1].append(zone2) + in_degree[zone2] += 1 + + # Trouver toutes les zones sans prédécesseur + queue = [] + for zone, degree in in_degree.items(): + if degree == 0: + queue.append(zone) + + # Tri topologique + result = [] + while queue: + zone = queue.pop(0) + result.append(zone) + + for neighbor in graph[zone]: + in_degree[neighbor] -= 1 + if in_degree[neighbor] == 0: + queue.append(neighbor) + + # Si le résultat n'a pas toutes les zones, il y a un cycle + if len(result) != len(self.zoneliste): + # Retourner une liste par défaut + return list(self.zoneliste) + + return result + + def print_precedences(self): + """ + Affiche les contraintes de précédence. + """ + print("Contraintes de précédence:") + for zone1, zone2 in self.precedences: + print(f"{zone1} doit être nettoyée avant {zone2}") + + +# Test avec l'allocation avec contraintes de précédence +if __name__ == "__main__": + # Fixer la graine aléatoire pour des résultats reproductibles + random.seed(42) + + # Création des robots + robot1 = Robot("robot1", 10) + robot2 = Robot("robot2", 6) + robot3 = Robot("robot3", 4) + + # Création des zones + zone1 = Zone("zone1", 4) + zone2 = Zone("zone2", 6) + zone3 = Zone("zone3", 1) + zone4 = Zone("zone4", 5) + zone5 = Zone("zone5", 3) + zone6 = Zone("zone6", 2) + + robotliste = [robot1, robot2, robot3] + zoneliste = [zone1, zone2, zone3, zone4, zone5, zone6] + + print("=== Test de l'allocation avec contraintes de précédence ===") + + # Création de contraintes de précédence manuelles pour l'exemple + precedences = [ + (zone1, zone3), # zone1 doit être nettoyée avant zone3 + (zone2, zone4), # zone2 doit être nettoyée avant zone4 + (zone3, zone5), # zone3 doit être nettoyée avant zone5 + (zone4, zone6) # zone4 doit être nettoyée avant zone6 + ] + + # Création de l'allocation avec précédences + prec_allocation = PrecedenceAllocation(robotliste, zoneliste, precedences) + + # Afficher les contraintes de précédence + prec_allocation.print_precedences() + + # Vérifier la consistance des précédences + if prec_allocation.check_precedence_consistency(): + print("\nLes contraintes de précédence sont consistantes (pas de cycles).") + else: + print("\nAttention: Les contraintes de précédence contiennent des cycles!") + + # Afficher le dictionnaire d'utilités + print("\nDictionnaire d'utilités:") + for (robot, zone), utility in prec_allocation.getdictio().items(): + print(f"{robot} - {zone}: {utility}") + + # Créer une allocation naïve + dummy_alloc = prec_allocation.dummy_allocation(robotliste, zoneliste) + print("\nAllocation naïve (dummy):") + for robot, zones in dummy_alloc.items(): + print(f"{robot}: {zones}") + + # Vérifier si l'allocation naïve respecte les contraintes + if prec_allocation.check_precedence_constraints(dummy_alloc): + print("L'allocation naïve respecte les contraintes de précédence.") + else: + print("L'allocation naïve ne respecte PAS les contraintes de précédence.") + + # Calculer une allocation qui respecte les contraintes + cuf_utilitariste = CollectiveUtilityFunction(prec_allocation.getdictio(), "utilitariste") + prec_alloc = prec_allocation.allocation(cuf_utilitariste) + + print("\nAllocation avec contraintes de précédence:") + for robot, zones in prec_alloc.items(): + print(f"{robot}: {zones}") + + # Vérifier si l'allocation respecte les contraintes + if prec_allocation.check_precedence_constraints(prec_alloc): + print("L'allocation respecte les contraintes de précédence.") + else: + print("L'allocation ne respecte PAS les contraintes de précédence.") + + # Calculer le temps de nettoyage + cleaning_time = prec_allocation.calculate_cleaning_time(prec_alloc) + print(f"Temps de nettoyage: {cleaning_time:.2f} heures") + + # Comparer avec l'allocation sans contraintes + print("\n=== Comparaison avec l'allocation sans contraintes ===") + + # Allocation utilitariste sans contraintes + task_allocation = TaskAllocation(robotliste, zoneliste) + alloc_utilitariste = task_allocation.allocation(cuf_utilitariste) + + print("\nAllocation sans contraintes (utilitariste):") + for robot, zones in alloc_utilitariste.items(): + print(f"{robot}: {zones}") + print(f"Utilité utilitariste: {cuf_utilitariste.utility(alloc_utilitariste)}") + print(f"Temps de nettoyage: {task_allocation.calculate_cleaning_time(alloc_utilitariste):.2f} heures") + + # Vérifier si l'allocation sans contraintes respecte les contraintes de précédence + if prec_allocation.check_precedence_constraints(alloc_utilitariste): + print("L'allocation sans contraintes respecte les contraintes de précédence (par chance).") + else: + print("L'allocation sans contraintes ne respecte PAS les contraintes de précédence.") \ No newline at end of file -- GitLab