from __future__ import nested_scopes import string from utils import * from copy import * """USAGE: python search.py 'search_algorithm' 'problem#' ('core_algorithm') search_algorithm ... (breadthfirst|depthfirst|uniformcost|iterativedeepening|bestfirst|beamsearch|astar|idastar|all) problem# ... (1-8) see source code for problem definitions core_algorithm (optional) ... (treesearch|graphsearch|all) -- default value is treesearch, core_algorithm parameter is used only in conjunction with breadthfirst, depthfirst, uniformcost, bestfirst, and astar EXAMPLES: python search.py breadthfirst 3 treesearch to run breathfirst search algorithm on problem 3 using tree search python search.py all 3 to run all the defined search algorithms on problem 3 using tree search python search.py all 3 all to run all the defined search algorithms on problem 3 using both tree and graph search """ # # Note: to use the global variables X1 and X2, initialize X1 and X2 before you call any functions accessing it, and then # include this line in any function that changes the values of X1 and X2: # global X1,X2 # #========================== class Node: def __init__ (self,state,statename="",parent=None,depth=0,gval=0,hval=None): self.state = state if statename: self.statename = statename else: self.statename = state self.parent = parent self.depth = depth self.gval = gval self.hval = hval def printNode(self): print("State: ", self.statename, " Gval=",self.gval, end="") print(" Hval=",self.hval,"depth=",self.depth, end="") if self.parent: print(" Parent state:", self.parent.statename) else: print("root node") def __str__(self): strings = ["(depth:", ", gval:",", hval:",", fval:"] nums = [str(self.depth),str(self.gval),\ str(h(self)),str(self.gval + h(self))] s = " " for i in range(4): s = s + strings[i] if int(nums[i]) < 10: s = s + " " if int(nums[i]) < 100: s = s + " " s = s + nums[i] s = s + ")" return self.statename + s def makeNodes (n, succStates): return [Node(s,parent=n,depth=1+n.depth,\ gval=n.gval+edgecost(n.state,s))\ for s in succStates] #====================================================== # # The problems are hand-coded into this file using dictionaries to # allow you to focus on the algorithms themselves without having to # implement a real problem. When using the code for real problems, you # will not use such dictionaries, and will use appropriate input. # #****************************************************** #****************************************************** # Problem 0 # problemPrintString0 = "Problem 0. Very simple example" startState0 = 'a' succDict0 = {'a':['b','c'],'b':['d'],'d':[],'e':[],'c':['e']} edgeDict0 = {('a','b'):1,('a','c'):1,('b','d'):1,('c','e'):1} hDict0 = {'a':1,'b':1,'c':1,'d':1,'e':1} goalDict0 = {} #****************************************************** #****************************************************** # Problem 1 # This heuristic is not admissible problemPrintString1 = "Problem 1. This heuristic is not admissible" startState1 = 'a' succDict1 = {'a':['b','c'],'b':['d','e','f','a'],'d':['j'],\ 'e':['k'],'f':['m'],'j':[],'k':['a'],'m':[],'c':['a','g','h'],\ 'g':['c','r','s'],'h':['c','n','o'],'r':[],'s':[],'n':[],'o':['h']} edgeDict1 = {('a','b'):18,('b','a'):10,('a','c'):10,('c','a'):10,\ ('b','d'):17,('g','c'):10,('h','c'):10,\ ('b','e'):12,('b','f'):14,('c','g'):20,\ ('c','h'):14,('d','j'):15,('e','k'):14,\ ('f','m'):18,('g','r'):21,('g','s'):16,\ ('h','n'):30,('h','o'):17,('o','h'):44,('k','a'):55} hDict1 = {'a':43,'b':34,'c':37,'d':10,'e':12,'f':14,\ 'g':18,'h':8,'j':0,'k':6,'m':28,'r':0,'s':6,'n':0,'o':10} goalDict1 = {'j':1,'r':1,'n':1} ##======== ## Problem 2 ## This is the same problem as problem 1, but this heuristic is admissible problemPrintString2 = "Problem 2: This is the same problem as problem 1, but this heuristic is admissible" startState2 = 'a' succDict2 = {'a':['b','c'],'b':['d','e','f','a'],'d':['j'],\ 'e':['k'],'f':['m'],'j':[],'k':['a'],'m':[],'c':['a','g','h'],\ 'g':['c','r','s'],'h':['c','n','o'],'r':[],'s':[],'n':[],'o':['h']} edgeDict2 = {('a','b'):18,('b','a'):10,('a','c'):10,('c','a'):10,\ ('b','d'):17,('g','c'):10,('h','c'):10,\ ('b','e'):12,('b','f'):14,('c','g'):20,\ ('c','h'):14,('d','j'):15,('e','k'):14,\ ('f','m'):18,('g','r'):21,('g','s'):16,\ ('h','n'):30,('h','o'):17,('o','h'):44,('k','a'):55} hDict2 = {'a':42,'b':30,'c':34,'d':10,'e':80,'f':56,\ 'g':18,'h':22,'j':0,'k':60,'m':58,'r':0,'s':100,'n':0,'o':54} goalDict2 = {'j':1,'r':1,'n':1} ##****************************************************** ## Problem 3 ## This is the same problem as problem 2, but this heuristic is less ## informed. Specifically, these h-vals are div 2 the ones in Problem 2. problemPrintString3 = "Problem 3: This is the same problem as problem, but this heuristic" problemPrintString3 = problemPrintString3 + "is less\ninformed. Specifically, these h-vals are div 2" problemPrintString3 = problemPrintString3 + "the ones in Problem 2" startState3 = 'a' succDict3 = {'a':['b','c'],'b':['d','e','f','a'],'d':['j'],\ 'e':['k'],'f':['m'],'j':[],'k':['a'],'m':[],'c':['a','g','h'],\ 'g':['c','r','s'],'h':['c','n','o'],'r':[],'s':[],'n':[],'o':['h']} edgeDict3 = {('a','b'):18,('b','a'):10,('a','c'):10,('c','a'):10,\ ('b','d'):17,('g','c'):10,('h','c'):10,\ ('b','e'):12,('b','f'):14,('c','g'):20,\ ('c','h'):14,('d','j'):15,('e','k'):14,\ ('f','m'):18,('g','r'):21,('g','s'):16,\ ('h','n'):30,('h','o'):17,('o','h'):44,('k','a'):55} hDict3 = {'a':21,'b':15,'c':17,'d':5,'e':40,'f':28,\ 'g':9,'h':11,'j':0,'k':30,'m':29,'r':0,'s':50,'n':0,'o':27} goalDict3 = {'j':1,'r':1,'n':1} ##****************************************************** ## Problem 4 ## good one for showing how graphsearch works # problemPrintString4 = "Problem 4. Good one for showing how graphsearch words." startState4 = 'a' succDict4 = {'a':['b','c'],'b':['d'],'c':['d','g'],\ 'd':['e'],'e':['f'],'f':[],'g':['d']} edgeDict4 = {('a','b'):10,('a','c'):5,('b','d'):10,('c','d'):5,('c','g'):50,\ ('g','d'):50,('d','e'):1,('e','f'):1} hDict4 = {'a':200,'b':50,'d':75,'e':80,'f':85,'c':90,'g':95} goalDict4 = {} #============================================================= startState5 = 'a' problemPrintString5 = "Problem 5." succDict5 = {'a':['b','c','d','e','f','g','h','j','k','l'],\ 'b':['d','e','f','a','h','i','j','k'],\ 'c':['a','b','c','d'],\ 'd':['e','j'],\ 'e':['f','k'],'f':['g'],'g':['h','j','c','d','e','f'],\ 'h':['i','j','k'],'i':['j','k','l','m'],\ 'j':[],'k':['a'],'l':[],'m':[]} edgeDict5 = {} for k in succDict5.keys(): for v in succDict5[k]: edgeDict5[(k,v)] = 1 edgeDict5[(v,k)] = 1 hDict5 = {'a':42,'b':30,'c':34,'d':10,'e':80,'f':56,\ 'g':18,'h':22,'i':4,'j':10,'k':60,'l':20,'m':0} goalDict5 = {'m':1} #========= problemPrintString6 = "Problem 6. Romania example in text." startState6 = "arad" succDict6 = {'buch': ['pit', 'faga', 'urz', 'giu'], 'cra': ['dob', 'rim', 'pit'], 'hir': ['efo', 'urz'], 'tim': ['arad', 'lug'], 'dob': ['cra', 'meh'], 'giu': ['buch'], 'efo': ['hir'], 'meh': ['dob', 'lug'], 'nea': ['ias'], 'zer': ['arad', 'ora'], 'sib': ['arad', 'ora', 'faga', 'rim'], 'rim': ['sib', 'pit', 'cra'], 'ias': ['nea', 'vas'], 'arad': ['zer', 'sib', 'tim'], 'lug': ['meh', 'tim'], 'vas': ['urz', 'ias'], 'faga': ['sib', 'buch'], 'pit': ['rim', 'cra', 'buch'], 'urz': ['buch', 'hir', 'vas'], 'ora': ['zer', 'sib']} edgeDict6 = {('buch', 'pit'): 101, ('cra', 'rim'): 146, ('hir', 'urz'): 98, ('arad', 'tim'): 118, ('sib', 'faga'): 99, ('rim', 'sib'): 80, ('cra', 'pit'): 138, ('lug', 'meh'): 70, ('sib', 'ora'): 151, ('meh', 'lug'): 70, ('rim', 'pit'): 97, ('efo', 'hir'): 86, ('vas', 'urz'): 142, ('tim', 'arad'): 118, ('urz', 'buch'): 85, ('arad', 'zer'): 75, ('nea', 'ias'): 87, ('pit', 'cra'): 138, ('ias', 'nea'): 87, ('zer', 'ora'): 71, ('faga', 'sib'): 99, ('arad', 'sib'): 140, ('ias', 'vas'): 92, ('sib', 'arad'): 140, ('pit', 'buch'): 101, ('rim', 'cra'): 146, ('meh', 'dob'): 75, ('hir', 'efo'): 86, ('pit', 'rim'): 97, ('dob', 'meh'): 75, ('ora', 'sib'): 151, ('sib', 'rim'): 80, ('vas', 'ias'): 92, ('buch', 'urz'): 85, ('buch', 'faga'): 211, ('faga', 'buch'): 211, ('giu', 'buch'): 90, ('zer', 'arad'): 75, ('urz', 'hir'): 98, ('lug', 'tim'): 111, ('dob', 'cra'): 120, ('buch', 'giu'): 90, ('ora', 'zer'): 71, ('urz', 'vas'): 142, ('cra', 'dob'): 120, ('tim', 'lug'): 111} hDict6 = {'buch': 0, 'sib': 253, 'dob': 242, 'giu': 77, 'lug': 244, 'rim': 193, 'hir': 151, 'nea': 234, 'zer': 374, 'tim': 329, 'meh': 241, 'cra': 160, 'urz': 80, 'arad': 366, 'efo': 161, 'vas': 199, 'ora': 380, 'pit': 100, 'ias': 226, 'faga': 176} goalDict6 = {'buch': 1} #===== problemPrintString7 = "Problem 7. Good for illustrating iterative deepening search." succDict7 = {'a':['b','c'],'b':['a'],'c':['d'],'d':['e'],'e':['f','g'],'f':[],'g':[]} goalDict7 = {'e':1} startState7 = 'a' edgeDict7 = {('a','b'):1,('b','a'):1,('a','c'):1,('c','d'):1,('d','e'):1,('e','f'):1,('e','g'):1} hDict7 = {'a':0,'b':0,'c':0,'d':0,'e':0,'f':0,'g':0} #****************************************************** #****************************************************** # Problem 8 # problemPrintString8 = "Problem 8. The problem from the first quiz" startState8 = 'a' succDict8 = {'a':['c','b'],'b':['a'],'c':['d'],'d':['g'],'g':[]} edgeDict8 = {('a','b'):1,('a','c'):1,('b','a'):1,('c','d'):1,('d','g'):1} hDict8 = {'a':1,'b':1,'c':1,'d':1,'g':1} goalDict8 = {'g':1} #****************************************************** #****************************************************** # Problem 81 - same as Problem 8 but the order of the successors of node a is reversed. # problemPrintString81 = "Problem 8. The problem from the first quiz" startState81 = 'a' succDict81 = {'a':['b','c'],'b':['a'],'c':['d'],'d':['g'],'g':[]} edgeDict81 = {('a','b'):1,('a','c'):1,('b','a'):1,('c','d'):1,('d','g'):1} hDict81 = {'a':1,'b':1,'c':1,'d':1,'g':1} goalDict81 = {'g':1} # Problem 9 # problemPrintString9 = "Problem 9." startState9 = 'a' succDict9 = {'a':['b','c'],'b':['a'],'c':['d'],'d':['g'],'g':[]} edgeDict9 = {('a','b'):10,('a','c'):5,('b','a'):2,('c','d'):11,('d','g'):9} hDict9 = {'a':2,'b':3,'c':1,'d':1,'g':0} goalDict9 = {'g':1} #======== Printing functions for this skeleton version that uses the #======== above dictionaries def printPathToNode(node): if node: node.printNode() if node.parent: printPathToNode(node.parent) def printGraph(root): """ Print the graph defined by the dictionaries defining the empty problem. Cycles are handled, but the printed graph in that case may not be intuitive. I suggest you draw the graph defined by succDict on paper and refer to your drawing to understand the output of the algorithms""" printed = {root:1} print(root) printGraphHelp(printed,root,succDict[root],' ') def printGraphHelp(printed,parent,kids,space): for kid in kids: if kid in printed: print(space,parent,"-->",kid,"edgeCost=",edgeDict[(parent,kid)],"**Edge to state already printed**") else: printed[kid] = 1 print(space + parent + "-->" + kid + " edgeCost=" + str(edgeDict[(parent,kid)]) + " h(" + kid + ")=" + str(hDict[kid])) printGraphHelp(printed,kid,succDict[kid],space + ' ') def printFringe (q): print("FRINGE:") if isinstance(q, list): f = [] for i in range(0,len(q)): x = q.pop() print(x) f.append(x) for i in range(0,len(f)): x = f.pop() q.append(x) else: f = FIFOQueue() for i in range(0,len(q)): x = q.pop() print(x) f.append(x) for i in range(0,len(f)): x = f.pop() q.append(x) #====== Functions defining the problem. def h(node): if not node.hval: node.hval = hDict[node.state] return node.hval def successors(node): return succDict[node.state] def goalp(node): return (node.state in goalDict) def edgecost (state1, state2): return edgeDict[(state1,state2)] #======= The core search functions and auxiliaries def treesearch(start,fringe): """Search through the successors of a problem to find a goal. start -- the start state fringe -- an empty queue Doesn't worry about repeated paths to a state """ fringe.append(Node(start)) while len(fringe) > 0: current = fringe.pop() if goalp(current): return current if current.depth >= INFINITY: print("** REACHED INFINITY; giving up") return [] if printVerbose: print("CURRENT: ", end="") print(current) fringe.extend(makeNodes(current,successors(current))) if printVerbose: printFringe(fringe) return [] def graphsearch(start,fringe): """Search through the successors of a problem to find a goal. start -- the start state fringe -- an empty queue If a new path to a state is found, save the shortest-length path""" expanded = {} fringe.append(Node(start)) while len(fringe) > 0: current = fringe.pop() if current.depth >= INFINITY: print("** REACHED INFINITY; giving up") return [] # The next if statement is just for printing if printVerbose and current.state in expanded: if expanded[current.state].gval <= current.gval: print("**New path to",current.statename,"from",current.parent.statename,\ "is NOT better than the old path to",current.statename) else: print("**New path to",current.statename,"from",current.parent.statename,\ "IS better than the old path to",current.statename) if not (current.state in expanded and\ expanded[current.state].gval <= current.gval): expanded[current.state] = current if goalp(current): return current if printVerbose: print("CURRENT: ", end="") print(current) fringe.extend(makeNodes(current,successors(current))) if printVerbose: printFringe(fringe) return [] def depthfirst(start,coreAlgorithm=treesearch): """Search the deepest nodes in the search tree first. """ return coreAlgorithm (start,Stack()) def breadthfirst(start,coreAlgorithm=treesearch): """Search the shallowest nodes in the search tree first. """ return coreAlgorithm (start,FIFOQueue()) def uniformcost(start,coreAlgorithm=treesearch): """Search the nodes with the lowest path cost first. """ return coreAlgorithm (start,\ PriorityQueue(lambda a,b:a.gval < b.gval)) def bestfirst(start,coreAlgorithm=treesearch): """Search the nodes with the lowest h scores first. Called greedy search in Russell and Norvig 1st edition, and greedy best-first search in the 2nd edition. """ return coreAlgorithm (start,\ PriorityQueue(lambda a,b: h(a) < h(b))) def Astar(start,coreAlgorithm=treesearch): """Search the nodes with the lowest f=h+g scores first.""" return coreAlgorithm(start,\ PriorityQueue(lambda a,b: h(a) + a.gval < h(b) + b.gval)) def iterativeDeepening(start): result = [] depthlim = 1 startnode = Node(start) while not result and depthlim < INFINITY: result = depthLimSearch([startnode],depthlim) depthlim = depthlim + 1 if depthlim == INFINITY: print("** REACHED INFINITY; giving up") return result def depthLimSearch(fringe,depthlim): if printVerbose: print("" ) print("**Starting at root with depthLim=%d" %(depthlim)) print("" ) while fringe: current = fringe[0] fringe = fringe[1:] if goalp (current): return current if printVerbose: print("CURRENT: ", end="") print(current) if current.depth <= depthlim: fringe = makeNodes(current,successors(current)) + fringe if printVerbose: printFringe(fringe) return [] def IDAstar(start): result = [] startNode = Node(start) fLim = h(startNode) while not result and fLim < FINFINITY: if printVerbose: print("" ) print("**Starting at root with fLim=%d" %(fLim)) print("" ) result, fLim = fLimSearch([startNode],fLim) if fLim == FINFINITY: print("** REACHED INFINITY; giving up") return result def fLimSearch(fringe,fLim): nextF = FINFINITY while fringe: current = fringe[0] fringe = fringe[1:] currentF = current.gval + h(current) if currentF <= fLim: if goalp (current): return (current, currentF) if printVerbose: print("CURRENT: ", end="") print(current) succNodes = makeNodes(current,successors(current)) for s in succNodes: fVal = s.gval + h(s) if fVal > fLim and fVal < nextF: nextF = fVal fringe = succNodes + fringe return ([],nextF) def beamsearch(start,beamwidth): def insertByH (item,lst): i = 0 while i < len(lst) and h(lst[i]) < h(item): i = i + 1 return lst[:i] + [item] + lst[i:] fringe = [Node(start)] while len(fringe) > 0: current = fringe[0] fringe = fringe[1:] if goalp(current): return current if current.depth >= INFINITY: print("** REACHED INFINITY; giving up") return [] if printVerbose: print("LEN(FRINGE):",len(fringe),"CURRENT: ", end="") print(current) newnodes = makeNodes(current,successors(current)) for s in newnodes: fringe = insertByH(s, fringe) fringe = fringe[:beamwidth] return [] def runAlgorithm(start, search_algo, core_algo): print("") print("======================================================") print("strategy:",search_algo) print("core algorithm:",core_algo) if core_algo=='treesearch': core_algo=treesearch else: core_algo=graphsearch if search_algo=='breadthfirst': result = breadthfirst(start,core_algo) elif search_algo=='depthfirst': result = depthfirst(start,core_algo) elif search_algo=='uniformcost': result = uniformcost(start,core_algo) elif search_algo=='bestfirst': result = bestfirst(start,core_algo) elif search_algo=='astar': result = Astar(start,core_algo) elif search_algo=='iterativedeepening': result = iterativeDeepening(start) elif search_algo=='beamsearch': result = beamsearch(start,5) elif search_algo=='idastar': result = IDAstar(start) if result: print("====Following is the (reverse) Path to the Goal===>") printPathToNode(result) else: print("No goal found") #==== Main program. Change this as appropriate for your application. if __name__ == '__main__': usage = """USAGE: python search.py 'search_algorithm' 'problem#' ('core_algorithm') search_algorithm ... (breadthfirst|depthfirst|uniformcost|iterativedeepening|bestfirst|beamsearch|astar|idastar|all) problem# ... (1-8) see source code for problem definitions core_algorithm (optional) ... (treesearch|graphsearch|all) -- default value is treesearch, core_algorithm parameter is used only in conjunction with breadthfirst, depthfirst, uniformcost, bestfirst, and astar EXAMPLES: python search.py breadthfirst 3 treesearch to run breathfirst search algorithm on problem 3 using tree search python search.py all 3 to run all the defined search algorithms on problem 3 using tree search python search.py all 3 all to run all the defined search algorithms on problem 3 using both tree and graph search """ # Checking input arguments print(sys.argv) if len(sys.argv)==4: search_algorithm = sys.argv[1] problem = sys.argv[2] core_algorithm = sys.argv[3] elif len(sys.argv)==3: search_algorithm = sys.argv[1] problem = sys.argv[2] core_algorithm = 'treesearch' else: print(usage) sys.exit(-1) if not problem.isdigit(): print(usage) sys.exit(-1) avv_core_algorithms=['treesearch','graphsearch','all'] avv_search_algorithms=['breadthfirst','depthfirst','uniformcost','iterativedeepening','bestfirst','beamsearch','astar','idastar','all'] if not core_algorithm in avv_core_algorithms or not search_algorithm in avv_search_algorithms: print(usage) sys.exit(-1) avv_core_algorithms.remove('all') avv_search_algorithms.remove('all') printVerbose = 1 INFINITY = 30 # depth infinity FINFINITY = 5000 # f-limit infinity # numRunsEachAlg = 100 # Set the problem to Problem sys.argv[3]. # exec(string) executes the command represented by the string. # For example, exec("x = 5") sets variable x to 5. exec("problemPrintString = problemPrintString" + problem) exec("startState = startState" + problem) exec("succDict = succDict" + problem) exec("edgeDict = edgeDict" + problem) exec("hDict = hDict" + problem) exec("goalDict = goalDict" + problem) print("==== Start State: ====") print(problemPrintString) printGraph(startState) print("") print("==== Calls to Search Algorithms ====") print("") if search_algorithm == 'all': search_algorithm = avv_search_algorithms else: search_algorithm = [search_algorithm] if core_algorithm == 'all': core_algorithm = avv_core_algorithms else: core_algorithm = [core_algorithm] for s in search_algorithm: for c in core_algorithm: runAlgorithm(startState,s,c) """ CHANGELOG (09/7/2013: modified by charmgil) - The scope of the main function has been explicitly specified (by 'if __name__ == '__main__':') (08/30/2013: ported by charmgil) - 'print' has been replaced with 'print()' - 'expanded.has_key(cur.state)' on line#269,308,352,359 has been removed, since the function is deprecated - line#528 typo: breathfirst -> breadthfirst """