def pick_one(self): index = 0 r = random() while r > 0: r -= self.paths[index].prob index += 1 new_path = Path(self.paths[index - 1].nodes) new_path.normalize_score = self.paths[index - 1].normalize_score new_path.prob = self.paths[index - 1].prob return new_path
def testConvertDHCtoUHC(): from uhc import uhc from dhc import dhc from graph import Path instances = [ '', 'a,a', 'a,b', 'a,b b,a', 'a,b b,c c,a', 'a,b b,c a,c', 'a,b b,c c,d', 'a,b b,c c,d d,a', 'a,b b,c c,d a,d', 'a,b b,c c,d a,d d,b c,a', ] for instance in instances: convertedInstance = convertDHCtoUHC(instance) instanceSolution = dhc(instance) convertedInstanceSolution = uhc(convertedInstance) revertedSolution = revertSolution(convertedInstanceSolution) utils.tprint(instance, 'maps to', convertedInstance,\ ' solutions were: ', instanceSolution, ';', convertedInstanceSolution) utils.tprint('revertedSolution', revertedSolution) if revertedSolution == 'no': assert instanceSolution == 'no' else: g = Graph(instance, weighted=False) path = Path.fromString(revertedSolution) # print('g', g, 'path', path) assert g.isHamiltonCycle(path) or g.isHamiltonCycle(path.reverse())
def testTspDirK(): testvals = [ (';1', 'no'), ('a,a,3;1', 3), ('a,a,3 ; 1 ', 3), ('a,a,3;2', 'no'), ('a,b,4;1', 'no'), ('a,b,4;2', 'no'), ('a,b,4;3', 'no'), ('a,b,4 b,a,9;1', 13), ('a,b,4 b,a,9;2', 13), ('a,b,4 b,a,9;3', 'no'), ('a,b,4 b,a,9 b,c,6 c,a,10;2', 13), ('a,b,4 b,a,9 b,c,6 c,a,10;3', 20), ('a,b,4 b,a,9 b,c,6 c,a,1;2', 11), ('a,b,4 b,a,9 b,c,6 c,a,10;4', 'no'), ('a,b,4 b,a,9 b,c,6 c,a,10 a,c,2 c,b,3;3', 14), ('a,b,4 b,a,9 b,c,6 c,a,10 a,c,2 c,b,3;4', 'no'), ] for (inString, solution) in testvals: val = tspDirK(inString) utils.tprint(inString.strip(), ':', val) if solution == 'no': assert val == solution else: (graphString, K) = inString.split(';') graph = Graph(graphString, directed=True) K = int(K) cycle = Path.fromString(val) dist = graph.cycleLength(cycle) assert graph.isCycle(cycle) assert len(cycle) >= K assert dist == solution
def choose_path_for_ant(self, pheromone_influence, heuristic_influence): chosen_edges = [] node = self.graph.start while not (node == self.graph.end): possible_edges = self.graph.edges_adjacent_to_node[node][:] if (chosen_edges): possible_edges.remove(chosen_edges[-1]) # Calculate the probabilities. # If another ant in current iteration has already been in this node, having come # through the same edge, then the probabilities are already calculated. if (chosen_edges and node in self.edge_choice_probabilities and chosen_edges[-1] in self.edge_choice_probabilities[node] ): probabilities = self.edge_choice_probabilities[node][chosen_edges[-1]] if (self.verbosity == 2): for i in range (len(possible_edges)): print (possible_edges[i], "[prob:", "{:.3f}".format(probabilities[i]), end="], ") else: sum = 0 for edge in possible_edges: sum += self.pheromone_array[edge]**pheromone_influence sum += (1/self.graph.edges_lenght[edge])**heuristic_influence probabilities = [] for edge in possible_edges: probabilities.append((self.pheromone_array[edge]**pheromone_influence \ + (1/self.graph.edges_lenght[edge])**heuristic_influence) \ / sum) if (self.verbosity == 2): print (edge, "[prob:", "{:.3f}".format(probabilities[-1]), end="], ") # Store the result for future ants. if (chosen_edges): if (not node in self.edge_choice_probabilities): self.edge_choice_probabilities[node] = {} self.edge_choice_probabilities[node][chosen_edges[-1]] = probabilities # Draw the next edge. drawn_edge = random.choices(possible_edges, probabilities, k=1)[0] if (self.verbosity == 2): print("drawn edge: ", drawn_edge) chosen_edges.append(drawn_edge) if (node == drawn_edge[0]): node = drawn_edge[1] else: assert (node == drawn_edge[1]) node = drawn_edge[0] assert (chosen_edges) lenght = 0 for edge in chosen_edges: lenght += self.graph.edges_lenght[edge] if (self.verbosity >= 1): if (self.verbosity == 2): print() print ("Chosen path: ", chosen_edges, "\nLenght: ", lenght, end="\n\n") return Path(chosen_edges, lenght)
def tspDir(inString): graph = Graph(inString) firstNode = graph.chooseNode() if firstNode == None: # graph contains no vertices return '' # this is a positive instance -- we return the empty cycle (cycle, distance) = shortestCycleWithPrefix(graph, Path([firstNode]), 0) if cycle != None: return str(cycle) else: return 'no'
def find_best_route(graph, start, end): """ Find the best route through the grid between the given start node and the given end node. """ if start == end: best_route_found = True else: best_route_found = False graph.nodes[start].distance = 0 reachable_unprocessed_nodes = set([graph.nodes[start]]) while not best_route_found: # 1. loop over the reachable but unprocessed nodes. Find the one with the # least distance from the start (X). # 2. For each node Y connected to X, take d(start,X) + d(X,Y) and see if it # is less than d(start, Y). If so, update node Y with a new distance and # a new previous node (X). # 3. If the end has been reached, iterate over all items in the reachable # but unprocessed nodes. If d(start,end) <= d(start,node) for all of these # nodes, then we have found the best route. # Find the unprocessed node closest to the start closest_distance = None for node in reachable_unprocessed_nodes: if closest_distance is None or node.distance < closest_distance: closest_node = node closest_distance = node.distance assert closest_node.processed == False, \ "Processed node is in list of unprocessed nodes" if closest_node.node_id == end: best_route_found = True # Process this node if not best_route_found: reachable_unprocessed_nodes.remove(closest_node) closest_node.processed = True for (node_id, distance) in closest_node.connections: node = graph.nodes[node_id] if node.distance is None or closest_node.distance + distance < node.distance: node.distance = closest_node.distance + distance reachable_unprocessed_nodes.add(node) node.previous_node = closest_node # Create the path of the best route. current_node = graph.nodes[end] best_route = [current_node] if start != end: while current_node.previous_node is not None: current_node = current_node.previous_node best_route.insert(0, current_node) return Path(best_route)
def tspPath(inString): graphStr, source, dest = [x.strip() for x in inString.split(";")] graph = Graph(graphStr, weighted=True, directed=False) # If there are two vertices, our conversion won't work correctly, # so treat this as a special case. if len(graph) == 2: if graph.containsEdge(Edge([source, dest])): return str(Path([source, dest])) else: return 'no' # add an overwhelmingly negative edge from source to dest, thus # forcing that to be part of a shortest Ham cycle. sumOfWeights = graph.sumEdgeWeights() fakeEdge = Edge([source, dest]) if graph.containsEdge(fakeEdge) == True: graph.removeEdge(fakeEdge) graph.addEdge(fakeEdge, -sumOfWeights) tspSoln = tsp(str(graph)) # print('graph', graph) # print('tspSoln', tspSoln) if tspSoln == 'no': return 'no' # convert from string to Path object tspSoln = Path.fromString(tspSoln) rotatedSoln = tspSoln.rotateToFront(source) if rotatedSoln[-1] != dest: # Probably oriented the wrong way (or else fakeEdge wasn't # used -- see below). Reverse then rotate source to front # again rotatedSoln = rotatedSoln.reverse().rotateToFront(source) # If dest still isn't at the end of the cycle, then the orginal graph didn't # have a Ham path from source to dest (but it did have a Ham # cycle -- a cycle that did *not* include fakeEdge). if rotatedSoln[-1] != dest: return 'no' else: return str(rotatedSoln)
def testShortestCycleWithPrefix(): graph = Graph(''' a,b,5 b,a,3 b,c,6 c,a,7 c,d,8 d,a,9 a,c,1 d,b,2 ''', directed=True) path = Path(['a']) val = shortestCycleWithPrefix(graph, path, 0) cycle, dist = val utils.tprint(val) assert graph.isHamiltonCycle(cycle) assert dist == 14
def bruteForceSearch(digraph, start, end, maxTotalDist, maxDistOutdoors): """ Finds the shortest path from start to end using brute-force approach. The total distance travelled on the path must not exceed maxTotalDist, and the distance spent outdoor on this path must not exceed maxDisOutdoors. Parameters: digraph: instance of class Digraph or its subclass start, end: start & end building numbers (strings) maxTotalDist : maximum total distance on a path (integer) maxDistOutdoors: maximum distance spent outdoors on a path (integer) Assumes: start and end are numbers for existing buildings in graph Returns: The shortest-path from start to end, represented by a list of building numbers (in strings), [n_1, n_2, ..., n_k], where there exists an edge from n_i to n_(i+1) in digraph, for all 1 <= i < k. If there exists no path that satisfies maxTotalDist and maxDistOutdoors constraints, then raises a ValueError. """ stack = [Path([SmartNode(start)], 0, 0)] valid_paths = [] while stack: path = stack.pop(-1) for edge in digraph.childrenOf(path.get_current_position()): if not path.is_node_visited(edge.getDestination()): new_path = path.clone() new_path.add_edge(edge) # "...consider first finding all valid paths that satisfy # the max distance outdoors constraint,..." if new_path.distance_outdoors <= maxDistOutdoors: if new_path.is_end_reached(end): valid_paths.append(new_path) else: stack.append(new_path) # "... and then going through those paths and returning the shortest, # rather than trying to fulfill both constraints at once..." valid_paths = filter(lambda path: path.total_distance <= maxTotalDist, valid_paths) # min will raise a ValueError if an empty sequence is passed to it shortest = min(valid_paths, key=lambda x: x.total_distance) return shortest.get_node_list()
def verifyTspD(I, S, H): if S == 'no': return 'unsure' # extract G,L from I, and convert to correct data types etc. (G, L) = I.split(';') G = Graph(G, directed=False) L = int(L) # split the hint string into a list of vertices, which will # form a Hamilton cycle of length at most L, if the hint is correct cycle = Path.fromString(H) # verify the hint is a Hamilton cycle, and has length at most L if G.isHamiltonCycle(cycle) and \ G.cycleLength(cycle) <= L: return 'correct' else: return 'unsure'
def _pathN2G(self, neopath): path = Path() n0 = self._nodeN2G(neopath.start_node) for rel in neopath: relation = self._relN2G(rel) path.relations.append(relation) for n in neopath.nodes: path.nodes.append(self._nodeN2G(n)) for i in range(len(path.nodes)): path.elements.append(path.nodes[i]) if path.relations and i < len(path.relations): path.elements.append(path.relations[i]) return path
def testTsp(): testvals = [ ('a,b,5', 'no', None), ('a,b,5 b,c,6 c,d,8 d,a,9 a,c,1 d,b,2', 'a,b,d,c', 16), ('a,b,5 b,c,6 c,d,8 d,a,9 a,c,1 d,b,200', 'a,b,c,d', 28), ('a,b,5 b,c,6 d,a,9 a,c,1', 'no', None), ] for (inString, solution, length) in testvals: val = tsp(inString) utils.tprint(inString, ':', val) if solution == 'no': assert val == solution else: g = Graph(inString, weighted=True, directed=False) p = Path.fromString(val) assert g.isHamiltonCycle(p) assert g.cycleLength(p) == length
def tspDirK(inString): (graphString, K) = inString.split(';') K = int(K) # treat K=0 as a special case. Since the empty cycle satisfies # this, we return a positive solution which is the empty string. if K==0: return '' graph = Graph(graphString) bestCycle = None; bestDistance = None for firstNode in graph: (cycle, distance) = shortestKCycleWithPrefix(graph, K, Path([firstNode]), 0) if cycle != None: if bestDistance == None or distance < bestDistance: (bestCycle, bestDistance) = (cycle, distance) if bestCycle != None: return str(bestCycle) else: return 'no'
def testUhc(): testVals = [ ('', True), ('a,b', False), ('a,b b,c', False), ('a,b b,c c,a', True), ('a,b b,c c,a a,d', False), ('a,b b,c c,a a,d b,d', True), ('aB,aA aB,aC aA,aC', True), ] for (inString, hasUhc) in testVals: result = uhc(inString) utils.tprint(inString, ':', result) if not hasUhc: assert result == 'no' else: g = Graph(inString, weighted=False, directed=False) path = Path.fromString(result) assert g.isHamiltonCycle(path)
def directedDFS(digraph, start, end, maxTotalDist, maxDistOutdoors): """ Finds the shortest path from start to end using directed depth-first. search approach. The total distance travelled on the path must not exceed maxTotalDist, and the distance spent outdoor on this path must not exceed maxDisOutdoors. Parameters: digraph: instance of class Digraph or its subclass start, end: start & end building numbers (strings) maxTotalDist : maximum total distance on a path (integer) maxDistOutdoors: maximum distance spent outdoors on a path (integer) Assumes: start and end are numbers for existing buildings in graph Returns: The shortest-path from start to end, represented by a list of building numbers (in strings), [n_1, n_2, ..., n_k], where there exists an edge from n_i to n_(i+1) in digraph, for all 1 <= i < k. If there exists no path that satisfies maxTotalDist and maxDistOutdoors constraints, then raises a ValueError. """ stack = [Path([SmartNode(start)], 0, 0)] while stack: path = stack.pop(-1) for edge in digraph.childrenOf(path.get_current_position()): if not path.is_node_visited(edge.getDestination()): new_path = path.clone() new_path.add_edge(edge) if (new_path.distance_outdoors <= maxDistOutdoors and new_path.total_distance <= maxTotalDist): if new_path.is_end_reached(end): return new_path.get_node_list() else: stack.append(new_path) raise ValueError()
def testTspPath(): testvals = [ ('a,b,5;a;b', 5), ('a,b,5 b,c,6 c,d,8 d,a,9 a,c,1 d,b,2;a;b', 11), ('a,b,5 b,c,6 c,d,8 d,a,9 a,c,1 d,b,2 c,e,10 d,e,20;a;b', 33), ('a,b,5 b,c,6 d,a,9 a,c,1 d,b,2;a;b', 'no'), ('a,b,5 b,c,6 d,a,9 a,c,1;a;b', 'no'), ] for (inString, solution) in testvals: val = tspPath(inString) utils.tprint(inString.strip(), ':', val) if solution == 'no': assert val == solution else: graphStr, source, dest = [x.strip() for x in inString.split(";")] graph = Graph(graphStr, directed=False) path = Path.fromString(val) dist = graph.pathLength(path) assert graph.isHamiltonPath(path) assert dist == solution
def perform_algorithm(self, iterations, population_size, evaporation_factor, pheromone_influence, heuristic_influence): self.initialize_pheromone_array() self.edge_choice_probabilities = {} shortest_path = Path([], float("inf")) for i in range(iterations): if (self.verbosity >= 1): print ("=== Iteration ", i, end="\n\n") self.edge_choice_probabilities.clear() for j in range(population_size): if (self.verbosity >= 1): print ("Ant ", j) path = self.choose_path_for_ant(pheromone_influence, heuristic_influence) self.drop_pheromone_on_path(path) if (path.lenght < shortest_path.lenght): shortest_path = path self.update_pheromone_array(evaporation_factor, shortest_path) if (i % self.state_displaying_period == 0): self.printer.display_state(self.pheromone_array, i) self.printer.display_state(self.pheromone_array, iterations)
def verifyTspDPolytime(I, S, H): # reject excessively long solutions and hints if len(S) > len(I) or len(H) > len(I): return 'unsure' # The remainder of the program is identical to verifyTspD.py # ... if S == 'no': return 'unsure' # extract G,L from I, and convert to correct data types etc. (G, L) = I.split(';') G = Graph(G, directed=False) L = int(L) # split the hint string into a list of vertices, which will # form a Hamilton cycle of length at most L, if the hint is correct cycle = Path.fromString(H) # verify the hint is a Hamilton cycle, and has length at most L if G.isHamiltonCycle(cycle) and \ G.cycleLength(cycle) <= L: return 'correct' else: return 'unsure'
def testTspDir(): testvals = [ ('aC,aB,1 aA,aB,1 aC,aA,1 aB,aA,1 aB,aC,1 aA,aC,1', 3), ('', 0), ('a,a,3', 3), ('a,b,4', 'no'), ('a,b,4 b,a,9', 13), ('a,b,4 b,a,9 b,c,6 c,a,10', 20), ('a,b,4 b,a,9 b,c,6 c,a,10 a,c,2 c,b,3', 14), ('a,b,4 b,a,9 b,c,6 c,a,10 a,c,2 c,b,300', 20), ('a,b,4 b,a,9 b,c,6 c,a,10 a,c,2 c,b,3 c,d,1', 'no'), ] for (inString, solution) in testvals: val = tspDir(inString) utils.tprint(inString.strip(), ':', val) if solution == 'no': assert val == solution else: graph = Graph(inString, directed=True) cycle = Path.fromString(val) dist = graph.cycleLength(cycle) assert graph.isHamiltonCycle(cycle) assert dist == solution
def testHalfUhc(): testvals = [ ('', 'yes'), ('a,b', 'no'), ('a,a', 'yes'), ('a,b b,c', 'no'), ('a,b b,c c,a', 'yes'), ('a,b b,c c,a d,e d,f', 'yes'), ('a,b b,c c,a d,e f,g', 'no'), ('a,b b,c c,a a,d', 'yes'), ('a,b b,c c,a a,d b,d', 'yes'), ('a,b b,c c,d d,e e,f f,g g,h h,i i,j h,a', 'yes'), ('a,b b,c c,d d,e e,f f,g g,h h,i i,j d,a', 'no'), ] for (inString, solution) in testvals: val = halfUhc(inString) utils.tprint(inString, ':', val) if solution == 'no': assert val == solution else: g = Graph(inString, weighted=False, directed=False) path = Path.fromString(val) assert g.isCycle(path) assert g.cycleLength(path) >= len(g) / 2
def generate_initial_population(self): for i in range(self.popsize): new_path = Path(self.graph.get_random_path(self.start, self.end)) self.paths.append(new_path)