├── map.p ├── cache.p ├── geoCache.p ├── directions.p ├── requirements.txt ├── plot.py ├── config.yml ├── imagesToGif.py ├── gmap-plot.py ├── README.md ├── createGeoCache.py ├── createMap.py ├── gmap-stats.py ├── LICENSE ├── createDirectionsCache.py ├── createCache.py ├── .gitignore ├── maputils.py └── index.py /map.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmhummel/Traveling-Saleman-Genetic-Algorithm/HEAD/map.p -------------------------------------------------------------------------------- /cache.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmhummel/Traveling-Saleman-Genetic-Algorithm/HEAD/cache.p -------------------------------------------------------------------------------- /geoCache.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmhummel/Traveling-Saleman-Genetic-Algorithm/HEAD/geoCache.p -------------------------------------------------------------------------------- /directions.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmhummel/Traveling-Saleman-Genetic-Algorithm/HEAD/directions.p -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | imageio==2.1.1 2 | matplotlib==2.0.0 3 | numpy==1.12.0 4 | polyline==1.3.2 5 | requests==2.12.4 -------------------------------------------------------------------------------- /plot.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import pickle 3 | import csv 4 | 5 | import maputils 6 | 7 | csvfile = open('stats.tsv', 'rb') 8 | stats = csv.reader(csvfile, delimiter='\t') 9 | route = tuple(eval(list(stats)[-1][1])) 10 | 11 | m = pickle.load(open('map.p','rb')) 12 | maputils.drawRoute(route, m) 13 | 14 | plt.show() -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | api_keys: 2 | # https://developers.google.com/maps/documentation/static-maps/ 3 | google_static_maps: YOURKEYHERE 4 | # https://developers.google.com/maps/documentation/distance-matrix/ 5 | google_distance_matrix: YOURKEYHERE 6 | # https://developers.google.com/maps/documentation/directions/ 7 | google_directions: YOURKEYHERE 8 | # https://developers.google.com/maps/documentation/geocoding/ 9 | google_geocoding: YOURKEYHERE -------------------------------------------------------------------------------- /imagesToGif.py: -------------------------------------------------------------------------------- 1 | import imageio 2 | import os 3 | 4 | images = [] 5 | filenames = os.listdir('pics') 6 | print filenames 7 | 8 | movWriter = imageio.get_writer('stats.mp4', fps=2) 9 | gifWriter = imageio.get_writer('stats.gif', fps=2) 10 | for filename in filenames: 11 | img = imageio.imread('pics/' + filename) 12 | movWriter.append_data(img) 13 | gifWriter.append_data(img) 14 | movWriter.close() 15 | gifWriter.close() 16 | # imageio.mimsave('stats.gif', images, duration=0.5) -------------------------------------------------------------------------------- /gmap-plot.py: -------------------------------------------------------------------------------- 1 | import polyline 2 | import requests 3 | import csv 4 | import pickle 5 | import shutil 6 | from subprocess import call 7 | 8 | import maputils 9 | 10 | csvfile = open('stats.tsv', 'rb') 11 | stats = csv.reader(csvfile, delimiter='\t') 12 | route = list(eval(list(stats)[-1][1])) 13 | 14 | poly = maputils.getPoly(route, 0.2) 15 | url = maputils.mapUrl(poly) 16 | 17 | filename = 'goog.png' 18 | r = requests.get(url, stream=True) 19 | if r.status_code == 200: 20 | with open(filename, 'wb') as f: 21 | r.raw.decode_content = True 22 | shutil.copyfileobj(r.raw, f) 23 | call(["open", filename]) 24 | else: 25 | print r.status_code, 'Error' -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Traveling-Saleman-Genetic-Algorithm 2 | *Solving the Traveling Salesman problem with 49 US Capitals using a genetic algorithm* 3 | 4 | ![Best solution](http://i.imgur.com/NOefVF7.png) 5 | 6 | Video of the solution evolving over time: https://www.youtube.com/watch?v=7KCLMNRRPN0 7 | 8 | This solver utilizes several Google Map APIs: 9 | * Capitals are converted into geolocations using the Geocoding API. 10 | * The Distance Matrix API is used in order to calculate driving time between each pair of capitals. 11 | * The Directions API is used to get the paths of the fastest routes between cities. 12 | * Lastly, the Static Maps API is used to draw the full solution and cities on a map and save it to disk. 13 | -------------------------------------------------------------------------------- /createGeoCache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import requests 4 | import pickle 5 | import yaml 6 | import maputils 7 | 8 | CAPITALS = maputils.CAPITALS 9 | API_KEY = maputils.config['api_keys']['google_geocoding'] 10 | BASE_URL = 'https://maps.googleapis.com/maps/api/geocode' 11 | 12 | def getCoords(place): 13 | place = place.replace(' ','+') 14 | url = BASE_URL + '/json?address=' + place + '&key=' + API_KEY 15 | r = requests.get(url) 16 | lat = r.json()['results'][0]['geometry']['location']['lat'] 17 | lon = r.json()['results'][0]['geometry']['location']['lng'] 18 | return (lat, lon) 19 | 20 | geoCache = {} 21 | for place in CAPITALS: 22 | geoCache[place] = getCoords(place) 23 | pickle.dump(geoCache, open('geoCache.p','wb'), -1) -------------------------------------------------------------------------------- /createMap.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from mpl_toolkits.basemap import Basemap 3 | import pickle 4 | 5 | # create the figure and axes instances. 6 | fig = plt.figure() 7 | ax = fig.add_axes([0.1,0.1,0.8,0.8]) 8 | # setup of basemap ('lcc' = lambert conformal conic). 9 | # use major and minor sphere radii from WGS84 ellipsoid. 10 | m = Basemap(llcrnrlon=-145.5,llcrnrlat=1.,urcrnrlon=-2.566,urcrnrlat=46.352,\ 11 | rsphere=(6378137.00,6356752.3142),\ 12 | resolution='l',area_thresh=1000.,projection='lcc',\ 13 | lat_1=50.,lon_0=-107.,ax=ax) 14 | 15 | # draw coastlines and political boundaries. 16 | m.drawcoastlines() 17 | m.drawcountries() 18 | m.drawstates() 19 | 20 | pickle.dump(m,open('map.p','wb'),-1) -------------------------------------------------------------------------------- /gmap-stats.py: -------------------------------------------------------------------------------- 1 | import polyline 2 | import requests 3 | import pickle 4 | import shutil 5 | import csv 6 | import math 7 | 8 | import maputils 9 | 10 | csvfile = open('stats.tsv', 'rb') 11 | stats = csv.reader(csvfile, delimiter='\t') 12 | routes = [ list(eval(row[1])) for row in stats ] 13 | 14 | for i in xrange(len(routes)): 15 | # for i in xrange(len(routes)-1,len(routes)): 16 | 17 | filename = 'pics/' + '{:03}'.format(i) + '.png' 18 | # route = routes[i] 19 | route = routes[i] 20 | 21 | poly = maputils.getPoly(route, 0.15) 22 | url = maputils.mapUrl(poly) 23 | 24 | r = requests.get(url, stream=True) 25 | if r.status_code == 200: 26 | with open(filename, 'wb') as f: 27 | r.raw.decode_content = True 28 | shutil.copyfileobj(r.raw, f) 29 | print len(url), len(url) - len(poly), len(poly), len(poly)/float(len(polyline.decode(poly))), filename #, routes[i] 30 | else: 31 | print len(url), len(url) - len(poly), len(poly), len(poly)/float(len(polyline.decode(poly))), r.status_code, 'ERROR' 32 | break 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jeremy Hummel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /createDirectionsCache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import requests 4 | import pickle 5 | import polyline 6 | 7 | import maputils 8 | 9 | API_KEY = maputils.config['api_keys']['google_directions'] 10 | BASE_URL = 'https://maps.googleapis.com/maps/api/directions/json?' 11 | CAPITALS = maputils.CAPITALS 12 | 13 | # directionsCache = pickle.load(open('directions.p','rb')) 14 | directionsCache = {} 15 | 16 | def getDirections(origin, destination): 17 | if (origin, destination) in directionsCache: 18 | directions = directionsCache[(origin, destination)] 19 | # print origin, destination 20 | else: 21 | url = BASE_URL + 'origin=' + origin + '&destination=' + destination + '&key=' + API_KEY 22 | print origin, destination, '\t' + url 23 | r = requests.get(url) 24 | # print url 25 | if r.json()['status'] == 'OK': 26 | poly = r.json()['routes'][0]['overview_polyline']['points'] 27 | directions = polyline.decode(poly) 28 | else: 29 | print r.json() 30 | raise Exception('Something went wrong') 31 | directionsCache[(origin, destination)] = directions 32 | directionsCache[(destination, origin)] = list(reversed(directions)) 33 | return directions 34 | 35 | for p1 in CAPITALS[:50]: 36 | # print p1 37 | for p2 in CAPITALS[:50]: 38 | if p1 != p2: 39 | getDirections(p1, p2) 40 | 41 | pickle.dump(directionsCache, open('directions.p','wb'), -1) -------------------------------------------------------------------------------- /createCache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import requests 4 | import pickle 5 | 6 | import maputils 7 | 8 | API_KEY = maputils.config['api_keys']['google_distance_matrix'] 9 | BASE_URL = 'https://maps.googleapis.com/maps/api/distancematrix' 10 | CAPITALS = maputils.CAPITALS 11 | 12 | # cachedDistDur = pickle.load(open('cache.p', 'rb')) 13 | cachedDistDur = {} 14 | 15 | def getDistDur(origin, destination): 16 | # print origin, destination 17 | key = tuple(sorted([origin, destination])) 18 | if key in cachedDistDur: 19 | # print origin, destination, 'from cache' 20 | return cachedDistDur[key] 21 | origin = origin.replace(' ','+') 22 | destination = destination.replace(' ','+') 23 | r = requests.get(BASE_URL + '/json?units=imperial&origins=' + origin + '&destinations=' + destination + '&key=' + API_KEY) 24 | if r.json()['status'] == 'OK': 25 | dist = r.json()['rows'][0]['elements'][0]['distance']['value'] 26 | time = r.json()['rows'][0]['elements'][0]['duration']['value'] 27 | result = (dist, time) 28 | cachedDistDur[key] = result 29 | print key, result 30 | # print origin, destination, result 31 | return result 32 | else: 33 | raise Exception('Over api limit!') 34 | 35 | for p1 in CAPITALS[:50]: 36 | print p1 37 | for p2 in CAPITALS[:50]: 38 | if p1 != p2: 39 | getDistDur(p1, p2) 40 | 41 | pickle.dump(cachedDistDur, open('cache.p', 'wb'), -1) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /maputils.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | import math 3 | import polyline 4 | import yaml 5 | 6 | geoCache = pickle.load(open('geoCache.p', 'rb')) 7 | directionsCache = pickle.load(open('directions.p','rb')) 8 | config = yaml.load(open("config.yml", 'r')) 9 | 10 | def getCoords(place): 11 | return geoCache[place] 12 | 13 | def getCoordString(place): 14 | return str(geoCache[place][0]) + ',' + str(geoCache[place][1]) 15 | 16 | def getDirections(origin, destination): 17 | return directionsCache[(origin, destination)] 18 | 19 | def roughDist(point1, point2): 20 | return math.sqrt((point1[0] - point2[0])**2 + (point1[1] - point2[1])**2) 21 | 22 | def perpendicularDistance(point, lineStart, lineEnd): 23 | numerator = math.fabs( (lineEnd[0]-lineStart[0])*point[1] - (lineEnd[1]-lineStart[1])*point[0] + lineEnd[1]*lineStart[0] - lineEnd[0]*lineStart[1] ) 24 | denominator = math.sqrt( (lineEnd[0]-lineStart[0])**2 + (lineEnd[1]-lineStart[1])**2 ) 25 | return numerator / denominator 26 | 27 | def DouglasPeucker(pointList, epsilon): 28 | # Find the point with the maximum distance 29 | dmax = 0 30 | index = 0 31 | end = len(pointList) 32 | for i in xrange(1, end-1): 33 | d = perpendicularDistance(pointList[i], pointList[0], pointList[-1]) 34 | if d > dmax: 35 | index = i 36 | dmax = d 37 | # If max distance is greater than epsilon, recursively simplify 38 | if dmax > epsilon: 39 | # Recursive call 40 | recResults1 = DouglasPeucker(pointList[:index+1], epsilon) 41 | recResults2 = DouglasPeucker(pointList[index:], epsilon) 42 | 43 | # Build the result list 44 | resultList = recResults1[:-1] + recResults2 45 | else: 46 | resultList = [pointList[0], pointList[-1]] 47 | # Return the result 48 | return resultList 49 | 50 | def getPoly(route, epsilon): 51 | route = [ CAPITALS[i] for i in route ] 52 | legs = zip(route, route[1:] + route[:1]) 53 | coordinates = [] 54 | for leg in legs: 55 | legCoords = DouglasPeucker(getDirections(*leg), epsilon) 56 | if leg == legs[-1]: 57 | coordinates += legCoords 58 | else: 59 | coordinates += legCoords[:-1] 60 | return polyline.encode(coordinates) 61 | 62 | def mapUrl(poly): 63 | BASE_URL = 'https://maps.googleapis.com/maps/api/staticmap?size=640x640&scale=2' 64 | API_KEY = config['api_keys']['google_static_maps'] 65 | url = BASE_URL 66 | iconUrl = 'http://i.imgur.com/n84gQUv.png' 67 | # iconUrl = 'http://i.imgur.com/cyy5Me4.png' 68 | url += '&markers=shadow:false|icon:' + iconUrl 69 | for city in CAPITALS: 70 | url += '|' + getCoordString(city) 71 | url += '&path=color:0x0000ffaa|weight:2|enc:' + poly + '&key=' + API_KEY 72 | return url 73 | 74 | def drawLeg(place1, place2, m): 75 | location1 = getCoords(place1) 76 | location2 = getCoords(place2) 77 | 78 | m.drawgreatcircle(location1[1], location1[0], location2[1], location2[0], color='#0000ff80') 79 | 80 | def drawRoute(route, m): 81 | list1 = route 82 | list2 = route[1:] + route[:1] 83 | legs = zip(list1, list2) 84 | for leg in legs: 85 | place1 = CAPITALS[leg[0]] 86 | place2 = CAPITALS[leg[1]] 87 | drawLeg(place1, place2, m) 88 | 89 | CAPITALS = [ 90 | 'Montgomery,AL', 91 | 'Juneau,AK', 92 | 'Phoenix,AZ', 93 | 'Little Rock,AR', 94 | 'Sacramento,CA', 95 | 'Denver,CO', 96 | 'Hartford,CT', 97 | 'Dover,DE', 98 | 'Tallahassee,FL', 99 | 'Atlanta,GA', 100 | 'Boise,ID', 101 | 'Springfield,IL', 102 | 'Indianapolis,IN', 103 | 'Des Moines,IA', 104 | 'Topeka,KS', 105 | 'Frankfort,KY', 106 | 'Baton Rouge,LA', 107 | 'Augusta,ME', 108 | 'Annapolis,MD', 109 | 'Boston,MA', 110 | 'Lansing,MI', 111 | 'St. Paul,MN', 112 | 'Jackson,MS', 113 | 'Jefferson City,MO', 114 | 'Helena,MT', 115 | 'Lincoln,NE', 116 | 'Carson City,NV', 117 | 'Concord,NH', 118 | 'Trenton,NJ', 119 | 'Santa Fe,NM', 120 | 'Albany,NY', 121 | 'Raleigh,NC', 122 | 'Bismarck,ND', 123 | 'Columbus,OH', 124 | 'Oklahoma City,OK', 125 | 'Salem,OR', 126 | 'Harrisburg,PA', 127 | 'Providence,RI', 128 | 'Columbia,SC', 129 | 'Pierre,SD', 130 | 'Nashville,TN', 131 | 'Austin,TX', 132 | 'Salt Lake City,UT', 133 | 'Montpelier,VT', 134 | 'Richmond,VA', 135 | 'Olympia,WA', 136 | 'Charleston,WV', 137 | 'Madison,WI', 138 | 'Cheyenne,WY', 139 | ] -------------------------------------------------------------------------------- /index.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import pickle 4 | import random 5 | import operator 6 | import time 7 | import sys 8 | 9 | import maputils 10 | 11 | USE_STOCHASTIC_SELECT = True 12 | USE_ELITISM = True 13 | 14 | CAPITALS = maputils.CAPITALS 15 | 16 | cachedDistDur = pickle.load(open('cache.p', 'r')) 17 | 18 | def getDistDur(origin, destination): 19 | # print origin, destination 20 | key = tuple(sorted([origin, destination])) 21 | if key in cachedDistDur: 22 | # print origin, destination, 'from cache' 23 | return cachedDistDur[key] 24 | else: 25 | raise Exception('Not found in cache') 26 | 27 | NUM_POP = 100 28 | NUM_SEEDS = NUM_POP 29 | 30 | def init(): 31 | seeds = [] 32 | for i in xrange(NUM_SEEDS): 33 | r = range(0, len(CAPITALS)) 34 | # r = range(0, 16) # Test using only 5 capitals to speed up, and reduce api calls 35 | random.shuffle(r) 36 | seeds.append(r) 37 | return seeds 38 | 39 | def deDup(route): 40 | idx = route.index(0) 41 | route = route[idx:] + route[:idx] 42 | if route[-1] < route[1]: 43 | return tuple(route[:1] + list(reversed(route[1:]))) 44 | return tuple(route) 45 | 46 | def calcDuration(route): 47 | route = tuple(route) 48 | # if route in cachedRouteDur: 49 | # print cachedRouteDur[route], route, 'cached' 50 | # return cachedRouteDur[route] 51 | list1 = route 52 | list2 = route[1:] + route[:1] 53 | legs = zip(list1, list2) 54 | totalDur = 0 55 | for leg in legs: 56 | point1 = CAPITALS[leg[0]] 57 | point2 = CAPITALS[leg[1]] 58 | totalDur += getDistDur(point1, point2)[1] 59 | # cachedRouteDur[route] = totalDur 60 | # print totalDur, route 61 | return totalDur 62 | 63 | def normalize(durationMap): 64 | totalInvertedDuration = 0 65 | for route, duration in durationMap.iteritems(): 66 | totalInvertedDuration += 1 / float(duration) 67 | fitnessMap = {} 68 | for route, duration in durationMap.iteritems(): 69 | fitnessMap[route] = (1 / float(duration)) / totalInvertedDuration 70 | return fitnessMap 71 | 72 | def calcAccumulatedFitness(fitnessMap): 73 | sortedFitnessList = sorted(fitnessMap.items(), key=operator.itemgetter(1), reverse=True) 74 | accumulatedFitnessList = [] 75 | accumulated = 0.0 76 | for t in sortedFitnessList: 77 | accumulatedFitnessList.append((t[0], t[1] + accumulated)) 78 | accumulated += t[1] 79 | return accumulatedFitnessList 80 | 81 | # Fitness proportionate selection 82 | def select(accumulatedFitnessMap): 83 | r = random.random() 84 | j = 0 85 | # print 'r', r 86 | for chromosome in accumulatedFitnessMap: 87 | if r < chromosome[1]: 88 | return chromosome[0] 89 | 90 | # Stochastic acceptance selection 91 | def stochasticSelect(fitnessMap): 92 | maxFitness = max(fitnessMap.values()) 93 | while True: 94 | chromosome = random.choice(fitnessMap.keys()) 95 | r = random.random() 96 | if r < fitnessMap[chromosome] / float(maxFitness): 97 | return chromosome 98 | 99 | # Partially matched crossover 100 | def crossover(mate1, mate2): 101 | # print mate1 102 | # print mate2 103 | r1 = random.randint(0, len(mate1)) 104 | r2 = random.randint(0, len(mate1)) 105 | point1 = min(r1, r2) 106 | point2 = max(r1, r2) 107 | 108 | offspring1 = list(mate2) 109 | for i in xrange(point1, point2): 110 | if offspring1[i] != mate1[i]: 111 | for j in xrange(len(offspring1)): 112 | if offspring1[j] == mate1[i]: 113 | offspring1[j] = offspring1[i] 114 | offspring1[i] = mate1[i] 115 | offspring2 = list(mate1) 116 | for i in xrange(point1, point2): 117 | if offspring2[i] != mate2[i]: 118 | for j in xrange(len(offspring2)): 119 | if offspring2[j] == mate2[i]: 120 | offspring2[j] = offspring2[i] 121 | offspring2[i] = mate2[i] 122 | # print point1, point2 123 | # print offspring 124 | return [offspring1, offspring2] 125 | 126 | CROSSOVER_RATE = 0.7 127 | # Ordered crossover 128 | def orderedCrossover(mate1, mate2): 129 | # print mate1 130 | # print mate2 131 | start = random.randint(0, len(mate1)) 132 | length = random.randint(0, len(mate1)) 133 | 134 | mate1 = mate1[start:] + mate1[:start] 135 | mate2 = mate2[start:] + mate2[:start] 136 | 137 | s = set(mate1[:length]) 138 | l = [x for x in mate2 if x not in s] 139 | offspring1 = list(mate1[:length]) + l 140 | 141 | s = set(mate2[:length]) 142 | l = [x for x in mate1 if x not in s] 143 | offspring2 = list(mate2[:length]) + l 144 | 145 | return [offspring1, offspring2] 146 | 147 | MUTATION_RATE = 0.015 148 | def mutate(chromosome): 149 | for i in xrange(len(chromosome)-1): 150 | if random.random() <= MUTATION_RATE: 151 | j = random.randint(0, len(chromosome)-1) 152 | tmp = chromosome[i] 153 | chromosome[i] = chromosome[j] 154 | chromosome[j] = tmp 155 | 156 | # Reverse subsection 157 | if random.random() <= 0.2: 158 | start = random.randint(0, len(chromosome)-1) 159 | length = random.randint(4, len(chromosome)-4) 160 | chromosome = chromosome[start:] + chromosome[:start] 161 | chromosome = list(reversed(chromosome[:length])) + chromosome[length:] 162 | 163 | ITERATIONS = 100000 164 | # MAX_DURATION = 2000000 165 | MAX_DURATION = 0 166 | 167 | def ga(): 168 | startTime = time.time() 169 | bestRoute = None 170 | bestDur = 0 171 | routes = init() 172 | stats = [] 173 | loop = 0 174 | while bestRoute is None or bestDur > MAX_DURATION: 175 | routes = [ deDup(route) for route in routes ] 176 | durationMap = {} 177 | for route in routes: 178 | durationMap[tuple(route)] = calcDuration(route) 179 | 180 | fitnessMap = normalize(durationMap) 181 | 182 | if (USE_STOCHASTIC_SELECT): 183 | shortestRoute = min(durationMap.items(), key=operator.itemgetter(1))[0] 184 | else: 185 | accumulatedFitnessList = calcAccumulatedFitness(fitnessMap) 186 | shortestRoute = accumulatedFitnessList[0][0] 187 | 188 | averageDuration = float(sum(durationMap.values())) / len(durationMap) 189 | 190 | if (bestRoute is None or durationMap[shortestRoute] < bestDur): 191 | bestRoute = shortestRoute 192 | bestDur = durationMap[shortestRoute] 193 | # stats.append(str(bestRoute) + ' ' + str(bestDur) + ' ' + str(averageDuration) + ' ' + str(loop) + ' ' + str(time.time() - startTime)) 194 | stats.append(str(bestDur) + '\t' + str(bestRoute) + '\t' + str(averageDuration) + '\t' + str(loop) + '\t' + str(time.time() - startTime)) 195 | print stats[-1] 196 | 197 | # print '\n'.join(stats) 198 | if USE_ELITISM: 199 | routes = [bestRoute] 200 | else: 201 | routes = [] 202 | for i in xrange(NUM_POP / 2): 203 | if (USE_STOCHASTIC_SELECT): 204 | mate1 = stochasticSelect(fitnessMap) 205 | mate2 = stochasticSelect(fitnessMap) 206 | else: 207 | mate1 = select(accumulatedFitnessList) 208 | mate2 = select(accumulatedFitnessList) 209 | offspring = orderedCrossover(mate1, mate2) 210 | offspring1 = offspring[0] 211 | offspring2 = offspring[1] 212 | mutate(offspring1) 213 | mutate(offspring2) 214 | routes.append(offspring1) 215 | routes.append(offspring2) 216 | loop += 1 217 | # print '\n'.join(stats) 218 | # print stats[-1] 219 | # print [CAPITALS[i] for i in bestRoute] 220 | return time.time() - startTime 221 | 222 | NUMBER_OF_TESTS = 20 223 | def test(): 224 | totalTime = 0 225 | for i in xrange(NUMBER_OF_TESTS): 226 | totalTime += ga() 227 | print totalTime / NUMBER_OF_TESTS 228 | 229 | #test() 230 | ga() --------------------------------------------------------------------------------