#!/usr/bin/env python import sys import math import random from collections import Counter import shortpath def LOG( *args ): """Print something on stderr and flush""" for msg in args: sys.stderr.write( str(msg) ) sys.stderr.write(" ") sys.stderr.flush() def LOGN( *args ): """Print something on stdeer, with a trailing new line, and flush""" LOG( *args ) LOG("\n") def tour(lst): # consecutive pairs in lst + last-to-first element for a,b in zip(lst, lst[1:] + [lst[0]]): yield (a,b) def euclidian_distance( ci, cj, graph = None): return math.sqrt( float(ci[0] - cj[0])**2 + float(ci[1] - cj[1])**2 ) def graph_distance( ci, cj, graph ): p,c = shortpath.astar( graph, ci, cj ) return c def cost( permutation, cost_func, cities ): dist = 0 for ci,cj in tour(permutation): dist += cost_func( ci, cj, cities ) return dist def look( cities, last, exclude, pheromones, w_heuristic, w_history, cost_func = graph_distance ): choices = [] # gather informations about possible moves for current in cities: if current in exclude: # This is faster than "if current not in exclude" continue c = {"city" : current} c["history"] = pheromones[last][current] ** w_history c["distance"] = cost_func( last, current, cities ) c["heuristic"] = (1.0 / c["distance"]) ** w_heuristic c["proba"] = c["history"] * c["heuristic"] choices.append(c) return choices def proba_choose( choices ): s = float(sum( c["proba"] for c in choices )) if s == 0.0: return random.choice(choices)["city"] v = random.random() for i,c in enumerate(choices): v -= c["proba"] / s if v <= 0.0: return c["city"] return c[-1]["city"] def greedy_choose( choices ): c = max( choices, key = lambda c : c["proba"] ) return c["city"] def walk( cities, pheromones, w_heuristic, w_history, c_greedy, cost_func = graph_distance ): assert( len(cities) > 0 ) # permutations are indices # randomly draw the first city index permutation = [ random.choice( cities.keys() ) ] # then choose the next ones to build the permutation while len(permutation) < len(cities): choices = look( cities, permutation[-1], permutation, pheromones, w_heuristic, w_history, cost_func ) do_greedy = ( random.random() <= c_greedy ) if do_greedy: next_city = greedy_choose( choices ) else: next_city = proba_choose( choices ) permutation.append( next_city ) # assert no duplicates assert( max(Counter(permutation).values()) == 1 ) return permutation def initialize_pheromones_whole( cities, init_value ): rows = {} for i in cities: cols = {} for j in cities: cols[j] = init_value rows[i] = cols return rows def update_global_whole( pheromones, candidate, graph, decay ): for i,j in tour(candidate["permutation"]): value = ((1.0 - decay) * pheromones[i][j]) + (decay * (1.0/candidate["cost"])) pheromones[i][j] = value pheromones[j][i] = value def update_local_whole( pheromones, candidate, graph, w_pheromone, init_pheromone ): for i,j in tour(candidate["permutation"]): value = ((1.0 - w_pheromone) * pheromones[i][j]) + (w_pheromone * init_pheromone) pheromones[i][j] = value pheromones[j][i] = value def initialize_pheromones_neighbors( cities, init_value ): rows = {} for i in cities: cols = {} for j in cities: # set an init value for neighbors only if j in cities[i]: cols[j] = init_value else: # else, there should be no edge cols[j] = 0 rows[i] = cols return rows def update_global_neighbors( pheromones, candidate, graph, decay ): for ci,cj in tour(candidate["permutation"]): # subpath between ci and cj p,c = path.astar( graph, ci, cj ) # deposit pheromones on each edges of the subpath for i,j in zip(p,p[1:]): value = ((1.0 - decay) * pheromones[i][j]) + (decay * (1.0/candidate["cost"])) pheromones[i][j] = value pheromones[j][i] = value def update_local_neighbors( pheromones, candidate, graph, w_pheromone, init_pheromone ): for ci,cj in tour(candidate["permutation"]): p,c = path.astar( graph, ci, cj ) for i,j in zip(p,p[1:]): value = ((1.0 - w_pheromone) * pheromones[i][j]) + (w_pheromone * init_pheromone) pheromones[i][j] = value pheromones[j][i] = value def search( cities, max_iterations, nb_ants, decay, w_heuristic, w_pheromone, w_history, c_greedy, cost_func = graph_distance ): # like random.shuffle(cities) but on a copy best = { "permutation" : sorted( cities, key=lambda i: random.random()) } best["cost"] = cost( best["permutation"], cost_func, cities ) init_pheromone = 1.0 / float(len(cities)) * best["cost"] pheromones = initialize_pheromones_whole( cities, init_pheromone ) for i in range(max_iterations): LOG( i ) solutions = [] for j in range(nb_ants): LOG( "." ) candidate = {} candidate["permutation"] = walk( cities, pheromones, w_heuristic, w_history, c_greedy, cost_func ) candidate["cost"] = cost( candidate["permutation"], cost_func, cities ) if candidate["cost"] < best["cost"]: best = candidate update_local_whole( pheromones, candidate, cities, w_pheromone, init_pheromone ) update_global_whole( pheromones, best, cities, decay ) LOGN( best["cost"] ) return best,pheromones if __name__ == "__main__": max_it = 40 num_ants = 10 decay = 0.1 w_heur = 2.5 w_local_phero = 0.1 c_greed = 0.9 w_history = 1.0 print """Graph TSP: -1 0 2 : x 1 o o-----o | | | 0 o--o-----o | | | | -2 o--o-----o : y """ G = { ( 0, 0) : [(-1, 0),( 0, 1),( 2, 0),( 0,-2)], ( 0, 1) : [( 0, 0),( 2, 1)], ( 0,-2) : [( 0, 0),( 2,-2),(-1,-2)], (-1, 0) : [(-1, 1),( 0, 0)], (-1, 1) : [(-1, 0)], (-1,-2) : [( 0,-2)], ( 2, 0) : [( 2, 1),( 2,-2),( 0, 0)], ( 2, 1) : [( 0, 1),( 2, 0)], ( 2,-2) : [( 2, 0),( 0,-2)], } best,phero = search( G, max_it, num_ants, decay, w_heur, w_local_phero, w_history, c_greed, cost_func = graph_distance ) print best["cost"], best["permutation"]