@Plams
2018-06-03T13:14:58.000000Z
字数 10964
阅读 791
首先采用原始未优化的蚁群算法,求解eil51.tsp问题。
eil51.tsp 含有51个点,最优解总长度426。
一个较好的结果, 约为最优解的105%。
可以看出,仍有交叉的路径,并非最优解。
配合2-opt的local search。
一个比较好的解答, 不超过最优解的100.7%。
Shortest path found:
[ 4 37 10 31 0 21 1 15 49 8 29 33 20 28 19 34 35 2 27 30 7 25 6 42
23 22 47 5 26 50 45 11 46 3 17 13 24 12 40 39 18 41 43 16 36 14 44 32
38 9 48 4]
Minimum distance found = 428.981647
使用邻域搜索的一个附带好处是 可以大幅度减少蚂蚁数,同时不会严重影响求解质量
接下来尝试使用2-OPT_ACO来求解一个规模稍大的问题 tsp225 (3919)
Shortest path found:
[ 22 21 20 19 202 18 17 16 15 14 13 12 11 10 9 8 39 40
7 6 5 4 2 0 199 197 3 196 194 42 41 43 45 193 217 192
44 47 195 191 190 223 198 132 204 189 224 206 48 49 50 56 55 54
51 52 53 57 58 1 46 188 26 187 116 186 185 184 183 119 121 120
174 118 117 114 112 113 222 115 59 60 61 62 63 111 110 109 64 65
66 67 68 69 70 71 72 73 75 74 97 98 99 100 101 102 103 219
104 105 106 107 108 123 122 170 172 181 180 179 178 177 176 175 173 171
169 168 167 124 211 213 149 150 151 148 147 146 145 152 153 154 155 156
143 144 142 200 141 138 137 139 140 158 159 160 161 162 136 135 182 134
157 212 133 214 163 164 165 166 125 126 127 128 131 221 129 210 130 85
84 81 82 83 209 86 87 88 89 90 91 92 93 220 80 79 94 208
95 96 77 78 76 201 29 28 27 203 25 33 32 34 205 216 218 215
30 31 38 37 36 35 24 207 23 22]
Minimum distance found = 4028.625948
main.py
import math
from aco import ACO, Graph
from plot import plot
def distance(city1: dict, city2: dict):
return math.sqrt((city1['x'] - city2['x']) ** 2 + (city1['y'] - city2['y']) ** 2)
def main():
cities = []
points = []
with open('eil51.txt') as f:
for line in f.readlines():
city = line.split(' ')
cities.append(dict(index=int(city[0]), x=int(city[1]), y=int(city[2])))
points.append((int(city[1]), int(city[2])))
cost_matrix = []
rank = len(cities)
for i in range(rank):
row = []
for j in range(rank):
row.append(distance(cities[i], cities[j]))
cost_matrix.append(row)
aco = ACO(10, 2000, 1.0, 10.0, 0.5, 10, 2)
graph = Graph(cost_matrix, rank)
path, cost = aco.solve(graph)
print('cost: {}, path: {}'.format(cost, path))
plot(points, path)
if __name__ == '__main__':
for _ in range(20):
main()
aco.py
import random
class Graph(object):
def __init__(self, cost_matrix: list, rank: int):
"""
:param cost_matrix:
:param rank: rank of the cost matrix
"""
self.matrix = cost_matrix
self.rank = rank
# noinspection PyUnusedLocal
self.pheromone = [[1 / (rank * rank) for j in range(rank)] for i in range(rank)]
class ACO(object):
def __init__(self, ant_count: int, generations: int, alpha: float, beta: float, rho: float, q: int,
strategy: int):
"""
:param ant_count:
:param generations:
:param alpha: relative importance of pheromone
:param beta: relative importance of heuristic information
:param rho: pheromone residual coefficient
:param q: pheromone intensity
:param strategy: pheromone update strategy. 0 - ant-cycle, 1 - ant-quality, 2 - ant-density
"""
self.Q = q
self.rho = rho
self.beta = beta
self.alpha = alpha
self.ant_count = ant_count
self.generations = generations
self.update_strategy = strategy
def _update_pheromone(self, graph: Graph, ants: list):
for i, row in enumerate(graph.pheromone):
for j, col in enumerate(row):
graph.pheromone[i][j] *= self.rho
for ant in ants:
graph.pheromone[i][j] += ant.pheromone_delta[i][j]
# noinspection PyProtectedMember
def solve(self, graph: Graph):
"""
:param graph:
"""
best_cost = float('inf')
best_solution = []
for gen in range(self.generations):
# noinspection PyUnusedLocal
ants = [_Ant(self, graph) for i in range(self.ant_count)]
for ant in ants:
for i in range(graph.rank - 1):
ant._select_next()
ant.total_cost += graph.matrix[ant.tabu[-1]][ant.tabu[0]]
if ant.total_cost < best_cost:
best_cost = ant.total_cost
best_solution = [] + ant.tabu
# update pheromone
ant._update_pheromone_delta()
self._update_pheromone(graph, ants)
# print('generation #{}, best cost: {}, path: {}'.format(gen, best_cost, best_solution))
return best_solution, best_cost
class _Ant(object):
def __init__(self, aco: ACO, graph: Graph):
self.colony = aco
self.graph = graph
self.total_cost = 0.0
self.tabu = [] # tabu list
self.pheromone_delta = [] # the local increase of pheromone
self.allowed = [i for i in range(graph.rank)] # nodes which are allowed for the next selection
self.eta = [[0 if i == j else 1 / graph.matrix[i][j] for j in range(graph.rank)] for i in
range(graph.rank)] # heuristic information
start = random.randint(0, graph.rank - 1) # start from any node
self.tabu.append(start)
self.current = start
self.allowed.remove(start)
def _select_next(self):
denominator = 0
for i in self.allowed:
denominator += self.graph.pheromone[self.current][i] ** self.colony.alpha * self.eta[self.current][
i] ** self.colony.beta
# noinspection PyUnusedLocal
probabilities = [0 for i in range(self.graph.rank)] # probabilities for moving to a node in the next step
for i in range(self.graph.rank):
try:
self.allowed.index(i) # test if allowed list contains i
probabilities[i] = self.graph.pheromone[self.current][i] ** self.colony.alpha * \
self.eta[self.current][i] ** self.colony.beta / denominator
except ValueError:
pass # do nothing
# select next node by probability roulette
selected = 0
rand = random.random()
for i, probability in enumerate(probabilities):
rand -= probability
if rand <= 0:
selected = i
break
self.allowed.remove(selected)
self.tabu.append(selected)
self.total_cost += self.graph.matrix[self.current][selected]
self.current = selected
# noinspection PyUnusedLocal
def _update_pheromone_delta(self):
self.pheromone_delta = [[0 for j in range(self.graph.rank)] for i in range(self.graph.rank)]
for _ in range(1, len(self.tabu)):
i = self.tabu[_ - 1]
j = self.tabu[_]
if self.colony.update_strategy == 1: # ant-quality system
self.pheromone_delta[i][j] = self.colony.Q
elif self.colony.update_strategy == 2: # ant-density system
# noinspection PyTypeChecker
self.pheromone_delta[i][j] = self.colony.Q / self.graph.matrix[i][j]
else: # ant-cycle system
self.pheromone_delta[i][j] = self.colony.Q / self.total_cost
plot.py
import operator
import matplotlib.pyplot as plt
def plot(points, path: list):
x = []
y = []
for point in points:
x.append(point[0])
y.append(point[1])
# noinspection PyUnusedLocal
y = list(map(operator.sub, [max(y) for i in range(len(points))], y))
plt.plot(x, y, 'co')
for _ in range(1, len(path)):
i = path[_ - 1]
j = path[_]
# noinspection PyUnresolvedReferences
plt.arrow(x[i], y[i], x[j] - x[i], y[j] - y[i])
# noinspection PyTypeChecker
plt.xlim(0, max(x) * 1.1)
# noinspection PyTypeChecker
plt.ylim(0, max(y) * 1.1)
plt.show()
aco_2opt.py
from math import sqrt
import argparse
import os
import numpy as np
from matplotlib import pyplot as plt
import time
#Global variables. Still not sure what these should be
SCENT_MIN = 0.1
ALPHA = 1
BETA = 2
EVAP_COEFF = 0.01
class Ant(object):
def __init__(self, node):
self.node = node
self.path = np.array([node])
self.visited = {}
class Node(object):
def __init__(self, id, x, y):
self.id = int(id) - 1 #added -1
self.x = float(x)
self.y = float(y)
def build_graph(filename):
with open (filename, "r") as myfile:
data = myfile.readlines()
node_list = []
length = len(data)
print "Number of cities = %d" % length
for i in xrange(length):
line = data[i].split()
node = Node(line[0], line[2], line[1])
node_list.append(node)
distance = np.empty([length, length], dtype=float)
heuristic = np.empty([length, length], dtype=float)
scent = np.empty([length, length], dtype=float)
scent.fill(SCENT_MIN)
(distance, heuristic) = init_distances(node_list, distance)
return (node_list, distance, heuristic, scent, length)
def init_distances(node_list, distances):
for node1 in node_list:
for node2 in node_list:
dist = sqrt((node1.x - node2.x)**2 + (node1.y - node2.y)**2)
distances[node1.id][node2.id] = dist #removed -1's
np.fill_diagonal(distances, np.nan)
heuristic = 1/distances
return (distances, heuristic)
def update_probability(scent, heuristic):
probability = ((scent**ALPHA) * (heuristic**BETA)) / \
np.nansum((scent**ALPHA) * (heuristic**BETA), axis=1)[:,np.newaxis]
return probability
def update_scents(ants, scent, distance):
num_nodes = len(ants[0].path)
path_travelers = {}
for ant in ants:
for i in xrange(num_nodes - 1):
src = ant.path[i]
dst = ant.path[i + 1]
if (src,dst) in path_travelers:
path_travelers[(src,dst)] = path_travelers[(src,dst)] + 1
path_travelers[(dst,src)] = path_travelers[(dst,src)] + 1
else:
path_travelers[(src,dst)] = 1
path_travelers[(dst,src)] = 1
dist = distance[src][dst]
for key, value in path_travelers.iteritems():
src = key[0]
dst = key[1]
updated_scent = (1 - EVAP_COEFF) * scent[src][dst] + 100 * value * (1/distance[src][dst])
scent[src][dst] = max(updated_scent, SCENT_MIN)
return scent
def path_distance(path, distance):
dist = 0
num_cities = len(path)
for i in xrange(num_cities - 1):
#print "i = %d" % i
src = path[i]
dst = path[i+1]
dist = dist + distance[src][dst]
return dist
def update_paths(ants, probability):
num_nodes = probability.shape[0]
for ant in ants:
new_arr = np.copy(probability)
curr_node = ant.path[-1]
for i in xrange(num_nodes):
for node in ant.path:
new_arr[curr_node][node] = 0
row_sum = np.nansum(new_arr[curr_node])
if row_sum != 0:
new_arr[curr_node] = new_arr[curr_node] / row_sum
next_node = np.random.choice(np.arange(0, num_nodes), p = new_arr[curr_node])
ant.path = np.append(ant.path, next_node)
else:
ant.path = np.append(ant.path, ant.path[0])
return ants
def update_greedy_paths(ants, distance):
for ant in ants:
curr_node = ant.path[-1]
def place_ants_randomly(num_ants, num_cities):
ants = np.empty(num_ants, dtype=object)
for i in xrange(num_ants):
ants[i] = Ant(np.random.random_integers(0, num_cities - 1))
return ants
def shortest_path(ants, min_dist, min_path, distance):
for ant in ants:
#print ant.path
dist = 0
for i in xrange(ant.path.shape[0] - 1):
src = ant.path[i]
dst = ant.path[i + 1]
dist = dist + distance[src][dst]
if dist < min_dist:
min_path = ant.path
min_dist = dist
#print min_path
return (min_dist, min_path)
def plot(nodes, path):
locations = {}
for node in nodes:
locations[node.id] = (node.x, node.y)
x = []
y = []
nums = []
for i in path:
x.append(locations[i][0])
y.append(locations[i][1])
plt.scatter(x,y)
plt.plot(x,y)
plt.xlabel("X Position")
plt.ylabel("Y Position")
plt.title("Shortest path found")
plt.hold(True)
plt.show()
def two_opt_swap(route, i, k):
route_len = route.shape[0]
new_route = np.empty(route_len, dtype=int)
new_route[0] = route[0]
j = 1
for a in xrange(1, i):
new_route[j] = route[a]
j += 1
"""route[i] to route[k] in reverse"""
for a in reversed(xrange(i, k+1)):
new_route[j] = route[a]
j += 1
for a in xrange(k+1, route_len):
new_route[j] = route[a]
j += 1
return new_route
def two_opt_iter(curr_path, distance):
num_nodes = curr_path.shape[0]
best_distance = path_distance(curr_path, distance)
for i in xrange(1, num_nodes - 1):
for k in xrange(i + 1, num_nodes - 1):
new_route = two_opt_swap(curr_path, i, k)
new_dist = path_distance(new_route, distance)
if (new_dist < best_distance):
print new_dist
return (new_route, True)
return (curr_path, False)
def two_opt(curr_path, distance):
num_nodes = curr_path.shape[0]
improvement = True
while (improvement):
(curr_path, improvement) = two_opt_iter(curr_path, distance)
return curr_path
def local_search(ants, distance):
"""Local search on each solution"""
for ant in ants:
ant.path = two_opt(ant.path, distance)
return ants
def main():
#parse arguments
parser = argparse.ArgumentParser(description='Short sample app')
parser.add_argument('-a', action='store',dest='num_ants',default=10, type=int)
parser.add_argument('-i', action='store',dest='iterations',default=10, type=int)
parser.add_argument('-f', action='store',dest='file',type=str)
args = parser.parse_args()
#Intialize graph
num_ants = args.num_ants
iterations = args.iterations
file_path = args.file
(nodes, distance, heuristic, scent, num_cities) = build_graph(file_path)
#main loop
min_path = []
min_dist = float('inf')
for a in xrange(iterations):
ants = place_ants_randomly(num_ants, num_cities)
probability = update_probability(scent, heuristic)
ants = update_paths(ants, probability)
ants = local_search(ants, distance)
(min_dist, min_path) = shortest_path(ants, min_dist, min_path, distance)
min_dist = path_distance(min_path, distance)
scent = update_scents(ants, scent, distance)
print "Shortest path found:"
print min_path
print "Minimum distance found = %f" % min_dist
plot(nodes,min_path)
if __name__ == '__main__':
main()