Newer
Older
ai / lab7 / lin_plan.py
@Andreas Jaggi Andreas Jaggi on 15 May 2006 7 KB Lab 7
# -*- 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()