diff --git a/TP_2_3/exo3.py b/TP_2_3/exo3.py new file mode 100644 index 0000000000000000000000000000000000000000..ac8198d78040442b8e53943e0abda423730701cd --- /dev/null +++ b/TP_2_3/exo3.py @@ -0,0 +1,348 @@ +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 distribuée +class DistributedAllocation(TaskAllocation): + def __init__(self, robotliste, zoneliste): + super().__init__(robotliste, zoneliste) + # Dictionnaire pour stocker les préférences de chaque robot + self.preferences = {} + # Dictionnaire pour stocker le type de préférence de chaque robot + self.robot_types = {} + + # Assigner aléatoirement un type de préférence à chaque robot + types = ["utilitariste", "egalitariste", "elitiste", "egoiste"] + for robot in robotliste: + self.robot_types[robot] = random.choice(types) + + def generate_all_allocations(self): + """ + Génère toutes les allocations possibles où chaque robot a au moins une zone + et le nombre de zones est égal au nombre de robots. + Pour simplifier, on suppose que chaque robot reçoit exactement une zone. + """ + # Pour simplifier, on ne considère que les allocations où chaque robot a exactement une zone + # et on suppose qu'il y a autant de zones que de robots + if len(self.robotliste) != len(self.zoneliste): + print("Pour simplifier, on suppose qu'il y a autant de zones que de robots.") + # On prend les n premières zones où n est le nombre de robots + zones_to_use = self.zoneliste[:len(self.robotliste)] + else: + zones_to_use = self.zoneliste + + # Générer toutes les permutations possibles des zones + all_permutations = list(itertools.permutations(zones_to_use)) + + # Créer les allocations correspondantes + all_allocations = [] + for perm in all_permutations: + allocation = {} + for i, robot in enumerate(self.robotliste): + allocation[robot] = [perm[i]] + all_allocations.append(allocation) + + return all_allocations + + def calculate_preference(self, robot, allocation): + """ + Calcule la préférence d'un robot pour une allocation donnée + selon son type de préférence. + """ + robot_type = self.robot_types[robot] + + if robot_type == "utilitariste": + # Préfère l'allocation qui maximise l'utilité totale + total_utility = 0 + for r, zones in allocation.items(): + for zone in zones: + total_utility += self.dictio[(r, zone)] + return total_utility + + elif robot_type == "egalitariste": + # Préfère l'allocation qui maximise l'utilité minimale + utilities = [] + for r, zones in allocation.items(): + robot_utility = sum(self.dictio[(r, zone)] for zone in zones) + utilities.append(robot_utility) + return min(utilities) if utilities else 0 + + elif robot_type == "elitiste": + # Préfère l'allocation qui maximise l'utilité maximale + utilities = [] + for r, zones in allocation.items(): + robot_utility = sum(self.dictio[(r, zone)] for zone in zones) + utilities.append(robot_utility) + return max(utilities) if utilities else 0 + + elif robot_type == "egoiste": + # Préfère l'allocation qui maximise sa propre utilité + if robot in allocation: + return sum(self.dictio[(robot, zone)] for zone in allocation[robot]) + return 0 + + return 0 + + def generate_preferences(self): + """ + Génère les préférences de chaque robot pour toutes les allocations possibles. + """ + all_allocations = self.generate_all_allocations() + + # Pour chaque robot, calculer sa préférence pour chaque allocation + for robot in self.robotliste: + # Calculer les scores de préférence pour chaque allocation + preferences = [] + for allocation in all_allocations: + score = self.calculate_preference(robot, allocation) + preferences.append((allocation, score)) + + # Trier les allocations par score décroissant + preferences.sort(key=lambda x: x[1], reverse=True) + + # Stocker les préférences triées + self.preferences[robot] = preferences + + def allocation(self): + """ + Implémente un algorithme distribué simple de type contract-net. + Les robots votent pour leurs allocations préférées et on élimine + progressivement les allocations les moins populaires. + """ + # Générer les préférences des robots + self.generate_preferences() + + # Obtenir toutes les allocations possibles + all_allocations = self.generate_all_allocations() + + # Si aucune allocation n'est possible, retourner une allocation vide + if not all_allocations: + return {robot: [] for robot in self.robotliste} + + # Tant qu'il reste plus d'une allocation + while len(all_allocations) > 1: + # Compter les votes pour chaque allocation + votes = {i: 0 for i in range(len(all_allocations))} + + # Chaque robot vote pour sa préférence parmi les allocations restantes + for robot in self.robotliste: + # Trouver l'allocation préférée du robot parmi celles restantes + for pref, _ in self.preferences[robot]: + if pref in all_allocations: + # Trouver l'index de cette allocation + idx = all_allocations.index(pref) + votes[idx] += 1 + break + + # Trouver l'allocation avec le moins de votes + min_votes = min(votes.values()) + min_indices = [i for i, v in votes.items() if v == min_votes] + + # En cas d'égalité, choisir aléatoirement + idx_to_remove = random.choice(min_indices) + + # Supprimer l'allocation la moins populaire + all_allocations.pop(idx_to_remove) + + # Retourner la dernière allocation restante + return all_allocations[0] + + def print_robot_types(self): + """ + Affiche le type de préférence de chaque robot. + """ + print("Types de préférence des robots:") + for robot, robot_type in self.robot_types.items(): + print(f"{robot}: {robot_type}") + + +# Test avec 3 robots et 3 zones pour l'allocation distribuée +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 (même nombre que de robots pour simplifier) + zone1 = Zone("zone1", 4) + zone2 = Zone("zone2", 6) + zone3 = Zone("zone3", 1) + + robotliste = [robot1, robot2, robot3] + zoneliste = [zone1, zone2, zone3] + + print("=== Test de l'allocation distribuée ===") + + # Création de l'allocation distribuée + dist_allocation = DistributedAllocation(robotliste, zoneliste) + + # Afficher les types de préférence des robots + dist_allocation.print_robot_types() + + # Afficher le dictionnaire d'utilités + print("\nDictionnaire d'utilités:") + for (robot, zone), utility in dist_allocation.getdictio().items(): + print(f"{robot} - {zone}: {utility}") + + # Générer les préférences + dist_allocation.generate_preferences() + + # Afficher les préférences de chaque robot + print("\nPréférences des robots:") + for robot, preferences in dist_allocation.preferences.items(): + print(f"\n{robot}:") + for i, (allocation, score) in enumerate(preferences[:3]): # Afficher les 3 premières préférences + print(f" {i+1}. Score: {score}") + for r, zones in allocation.items(): + print(f" {r}: {zones}") + + # Exécuter l'algorithme d'allocation distribuée + print("\nRésultat de l'allocation distribuée:") + result = dist_allocation.allocation() + for robot, zones in result.items(): + print(f"{robot}: {zones}") + + # Calculer le temps de nettoyage + cleaning_time = dist_allocation.calculate_cleaning_time(result) + print(f"Temps de nettoyage: {cleaning_time:.2f} heures") + + # Comparer avec l'allocation centralisée + print("\n=== Comparaison avec l'allocation centralisée ===") + + # Allocation utilitariste + task_allocation = TaskAllocation(robotliste, zoneliste) + cuf_utilitariste = CollectiveUtilityFunction(task_allocation.getdictio(), "utilitariste") + alloc_utilitariste = task_allocation.allocation(cuf_utilitariste) + + print("\nAllocation centralisée (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") \ No newline at end of file