# -*- coding: iso-8859-15 -*- ## Liste des opérateurs: operators = [] ## Description de la situation initiale: initialState = [] ## Liste des états encore à explorer: openList = [] ## Heuristique d'ordonnancement pour A*: heuristicFn = [] ## Fonction vérifiant la consistence d'un noeud, càd si ses buts sont ## cohérents: consistentFn = [] ## Liste des solutions trouvées: plannerSolutions = [] ###=================================================================== ### OPERATEURS ###=================================================================== class Operator: ## Constructeur ## @param action Action effectuée ## @param preconds Liste des préconditions ## @param addList Liste ajoutée ## @param deleteList liste enlevée ## @cost cost Coût de l'opérateur. def __init__(self, action, preconds, addList, deleteList, cost): self.action=action self.preconds=preconds self.addList=addList self.deleteList=deleteList self.cost=cost ## Retourne une représentation en string des caractéristiques de l'opérateur def __str__(self): out = ("<Operator %s; Preconds=%s; Add-List=%s; Delete-List=%s; cost=%s>\n" % (self.action, self.preconds, self.addList, self.deleteList, self.cost)) return out ## Définit un nouvel opérateur d'après sa <description> et l'ajoute ## a la variable globale operators. ## @param description Liste [action preconditions add-list delete-list coût] def defOperator(description): global operators op = Operator(description[0],description[1],description[2],description[3],description[4]) operators.append(op) ###=================================================================== ### ETATS (STATES) ###=================================================================== class State: ## Constructeur ## @param goals Les buts de l'état. ## @param operators La liste des opérateurs appliqués à cet état pour parvenir a l'état final ## @param cost Le coût de cet état def __init__(self, goals, ops, cost=0): self.goals = goals self.operators = ops self.cost = cost ## Compare deux états ## @param other L'autre état à comparer def __cmp__(self, other): return self.cost - other.cost ## Retourne une représentation en string avec les caractéristiques de l'état def __str__(self): out = ("<State: Goals=%s; operators=%s; cost=%s>\n" % (self.goals, self.operators, self.cost)) return out ## Retourne True ssi l'état est une solution, càd si ses buts ## font partie de l'état initial. def isSolution(self): global initialState return set(self.goals).issubset(set(initialState)) ## Calcule le coût de l'état d'après la fonction heuristique ## heuristicFn et retourne l'état. def computeCost(self): global heuristicFn self.cost = apply(heuristicFn, [self]) return self ## Retourne tous les nouveaux états que l'on peut obtenir en ## appliquant les opérateurs à l'état . ## @param ops Les opérateurs à appliquer def applyOperators(self, ops): rs = [] for o in ops: s = self.applyOperator(o) if isinstance(s, State): rs.append(s) return rs ## Applique l'opérateur <op> a l'état s'il est applicable et ## retourne le nouvel état. ## @param op L'opérateur à appliquer def applyOperator(self, op): global consistentFn x = self.applicable(op) if x: newGoals = list(set(self.goals).union(set(op.preconds)).difference(set(x))) newOperators = self.operators[:] newOperators.append(op) newState = State(newGoals, newOperators) newState.computeCost() if apply(consistentFn, [newState]): return newState return [] ## Verifie que l'opérateur op peut s'appliquer a l'état, càd que: ## - aucun prédicat de la deleteList de op ne fait partie des ## buts de l'état. ## - au moins un but de l'état est satisfait par un prédicat de ## l'addList de op. ## Retourne la liste des buts satisfaits par op. ## @param op L'opérateur à appliquer def applicable(self,op): if len(set(self.goals).intersection(set(op.deleteList))) < 1: return set(self.goals).intersection(set(op.addList)) else: return [] ## Analyse moyens-buts; retourne le nombre de buts de l'état ## qui ne sont pas satisfaits par l'état initial initialState. def meansEnds(self): global initialState return len(set(initialState).difference(set(self.goals))) ## Retourne la somme des coûts des opérateurs appliqués a l'état ## final pour obtenir l'état. def opCost(self): if self.operators: return reduce(lambda x,y: x + y.cost, self.operators, self.cost) else: return self.cost ## Affiche la solution def printPlannerSolution(self): print("== PLANNER SOLUTION ==") i = 0 for op in self.operators: print("Action %s: %s" % (i, op.action)) i = i+1 ###=================================================================== ### INITIALISATION et CONSTRUCTION ###=================================================================== ## Initialise et construit un nouveau planificateur. ## @param initState L'état initial ## @param goals Les buts ## @param ops La description des opérateurs ## @param consistent La fonction de consistance ## @param heuristic La fonction heuristique utilisée par A* def buildPlanner(initState, goals, ops, consistent, heuristic): global operators global initialState global plannerSolutions global openList global consistentFn global heuristicFn operators = [] initialState = initState plannerSolutions = [] openList = [State(goals,[],0)] consistentFn = consistent heuristicFn = heuristic for op in ops: defOperator(op) return openList[0] ###=================================================================== ### RECHERCHE ###=================================================================== ## Recherche une solution en explorant au plus <max-steps> états. ## @param maxSteps Le nombre maximal de pas def findPlan(maxSteps=1000): global plannerSolutions sol, steps = AStarSearch(maxSteps) if isinstance(sol,State): print("FOUND A SOLUTION IN %s STEPS\n" % (steps)) plannerSolutions = [sol] + plannerSolutions sol.printPlannerSolution() return sol elif steps < 0: print("NO MORE SOLUTIONS (open list exhausted)\n") else: print("NO SOLUTION FOUND IN %s STEPS\n" % (steps)) ## Algorithme de recherche A*. Effectue au maximum <max-steps> pas ## de recherche. N'utilise pas de closed-list. ## Retourne 2 valeurs: ## - la solution trouvée ou [] si aucune solution n'a été ## trouvée. ## - le nombre de pas de recherche effectués. Si openList a ## été "vidée" (càd il n'y a plus de solutions), cette ## deuxieme valeur sera -1. ## @param maxSteps Le nombre maximal de pas def AStarSearch(maxsteps): global openList step = 0 while len(openList) > 0: s = openList[0] openList = openList[1:] if s.isSolution(): return s, step openList = openList + s.applyOperators(operators) openList.sort() step = step + 1 if step == maxsteps: return [], step return [], -1 ###=================================================================== ### UTILITAIRES ###=================================================================== ## Affiche toutes les solutions trouvées par le planificateur def printPlannerSolutions(): global plannerSolutions print(">>> FOUND %s PLANS <<<" % (len(plannerSolutions))) for sol in plannerSolutions.revers(): sol.printPlannerSolution()