summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ant.py45
-rw-r--r--main.py250
-rw-r--r--vrptw_base.py46
3 files changed, 293 insertions, 48 deletions
diff --git a/ant.py b/ant.py
index 3f2bdd2..2294377 100644
--- a/ant.py
+++ b/ant.py
@@ -8,7 +8,7 @@ class Ant:
def __init__(self, graph: VrptwGraph, start_index=0):
super()
self.graph = graph
- self.current_index = 0
+ self.current_index = start_index
self.vehicle_load = 0
self.vehicle_travel_time = 0
self.travel_path = [start_index]
@@ -110,7 +110,7 @@ class Ant:
current_ind = next_ind
return distance
- def try_insert_on_path(self, node_id):
+ def try_insert_on_path(self, node_id, what_to_do_list: list):
"""
尝试性地将node_id插入当前的travel_path中
插入的位置不能违反载重,时间,行驶距离的限制
@@ -124,6 +124,12 @@ class Ant:
path = copy.deepcopy(self.travel_path)
for insert_index in range(len(path)):
+
+ if len(what_to_do_list) > 0:
+ info = what_to_do_list[0]
+ if info.is_to_stop():
+ return
+
if self.graph.nodes[path[insert_index]].is_depot:
continue
@@ -144,6 +150,12 @@ class Ant:
# 如果可以到node_id,则要保证vehicle可以行驶回到depot
for next_ind in path[insert_index:]:
+
+ if len(what_to_do_list) > 0:
+ info = what_to_do_list[0]
+ if info.is_to_stop():
+ return
+
if check_ant.check_condition(next_ind):
check_ant.move_to_next_index(next_ind)
if self.graph.nodes[next_ind].is_depot:
@@ -162,7 +174,14 @@ class Ant:
best_ind = feasible_insert_index[int(min_insert_ind)]
return best_ind
- def insertion_procedure(self):
+ def get_path_without_duplicated_depot(self):
+ path = copy.deepcopy(self.travel_path)
+ for i in range(len(path)):
+ if self.graph.nodes[i].is_depot:
+ path[i] = 0
+ return path
+
+ def insertion_procedure(self, what_to_do_list: list):
"""
为每个未访问的结点尝试性地找到一个合适的位置,插入到当前的travel_path
插入的位置不能违反载重,时间,行驶距离的限制
@@ -181,14 +200,20 @@ class Ant:
ind_to_visit = ind_to_visit[sorted_ind]
for node_id in ind_to_visit:
- best_insert_index = self.try_insert_on_path(node_id)
+
+ if len(what_to_do_list) > 0:
+ info = what_to_do_list[0]
+ if info.is_to_stop():
+ return
+
+ best_insert_index = self.try_insert_on_path(node_id, what_to_do_list)
if best_insert_index is not None:
self.travel_path.insert(best_insert_index, node_id)
self.index_to_visit.remove(node_id)
self.total_travel_distance = self.cal_total_travel_distance(self.travel_path)
- def local_search_procedure(self):
+ def local_search_procedure(self, what_to_do_list):
"""
对当前的已经访问完graph中所有节点的travel_path使用cross进行局部搜索
:return:
@@ -205,6 +230,11 @@ class Ant:
for i in range(1, len(depot_ind)):
for j in range(i+1, len(depot_ind)):
+ if len(what_to_do_list) > 0:
+ info = what_to_do_list[0]
+ if info.is_to_stop():
+ return
+
# 随机在两段route,各随机选择一段customer id,交换这两段customer id
start_a = random.randint(depot_ind[i-1]+1, depot_ind[i]-1)
end_a = random.randint(depot_ind[i-1]+1, depot_ind[i]-1)
@@ -245,6 +275,5 @@ class Ant:
min_distance = new_path_travel_distance[min_distance_ind]
if min_distance < self.total_travel_distance:
- return new_path[int(min_distance_ind)]
- else:
- return None \ No newline at end of file
+ self.travel_path = new_path[int(min_distance_ind)]
+ self.index_to_visit.clear()
diff --git a/main.py b/main.py
index 07cc98f..30d8a8d 100644
--- a/main.py
+++ b/main.py
@@ -1,8 +1,12 @@
import numpy as np
import random
from vprtw_aco_figure import VrptwAcoFigure
-from vrptw_base import VrptwGraph
+from vrptw_base import VrptwGraph, VrptwMessage
from ant import Ant
+from threading import Thread
+from queue import Queue
+from concurrent.futures import ThreadPoolExecutor
+import copy
class VrptwAco:
@@ -27,6 +31,7 @@ class VrptwAco:
# best path
self.best_path_distance = None
self.best_path = None
+ self.best_vehicle_num = self.graph.nnh_travel_path.count(0)-1
self.whether_or_not_to_show_figure = False
@@ -34,9 +39,9 @@ class VrptwAco:
# figure
self.figure = VrptwAcoFigure(self.graph)
- def run(self):
+ def basic_aco(self):
"""
- 运行蚁群优化算法
+ 最基本的蚁群算法
:return:
"""
# 最大迭代次数
@@ -101,10 +106,11 @@ class VrptwAco:
next_index = index_to_visit[max_prob_index]
else:
# 使用轮盘赌算法
- next_index = self.stochastic_accept(index_to_visit, transition_prob)
+ next_index = VrptwAco.stochastic_accept(index_to_visit, transition_prob)
return next_index
- def stochastic_accept(self, index_to_visit, transition_prob):
+ @staticmethod
+ def stochastic_accept(index_to_visit, transition_prob):
"""
轮盘赌
:param index_to_visit: a list of N index (list or tuple)
@@ -125,11 +131,19 @@ class VrptwAco:
if random.random() <= norm_transition_prob[ind]:
return index_to_visit[ind]
- def new_active_ant(self, ant: Ant, local_search: bool, IN):
-
+ @staticmethod
+ def new_active_ant(ant: Ant, local_search: bool, IN: np.numarray, q0: float, what_to_do_list: list):
+ print('[new_active_ant]: start, start_index %d' % ant.travel_path[0])
# 计算从当前位置可以达到的下一个位置
next_index_meet_constrains = ant.cal_next_index_meet_constrains()
+
while len(next_index_meet_constrains) > 0:
+
+ if len(what_to_do_list) > 0:
+ info = what_to_do_list[0]
+ if info.is_to_stop():
+ return
+
index_num = len(next_index_meet_constrains)
ready_time = np.zeros(index_num)
due_time = np.zeros(index_num)
@@ -137,20 +151,21 @@ class VrptwAco:
ready_time[i] = ant.graph.nodes[next_index_meet_constrains[i]].ready_time
due_time[i] = ant.graph.nodes[next_index_meet_constrains[i]].due_time
- delivery_time = np.max(ant.vehicle_travel_time + ant.graph.node_dist_mat[ant.current_index][next_index_meet_constrains], ready_time)
- delat_time = delivery_time - ant.vehicle_travel_time
- distance = delat_time * (due_time - ant.vehicle_travel_time)
- distance = np.max(1.0, distance-IN)
+ delivery_time = np.array([max(i, j) for i, j in zip(ant.vehicle_travel_time + ant.graph.node_dist_mat[ant.current_index][next_index_meet_constrains], ready_time)])
+
+ delta_time = delivery_time - ant.vehicle_travel_time
+ distance = delta_time * (due_time - ant.vehicle_travel_time)
+ distance = np.array([max(1.0, j) for j in distance-IN[next_index_meet_constrains]])
closeness = 1/distance
# 按照概率选择下一个点next_index
- if np.random.rand() < ant.graph.q0:
+ if np.random.rand() < q0:
max_prob_index = np.argmax(closeness)
next_index = next_index_meet_constrains[max_prob_index]
else:
# 使用轮盘赌算法
- next_index = ant.graph.stochastic_accept(next_index_meet_constrains, closeness)
+ next_index = VrptwAco.stochastic_accept(next_index_meet_constrains, closeness)
ant.move_to_next_index(next_index)
# 更新信息素矩阵
@@ -158,26 +173,209 @@ class VrptwAco:
# 重新计算可选的下一个点
next_index_meet_constrains = ant.cal_next_index_meet_constrains()
- ant.insertion_procedure()
+ ant.insertion_procedure(what_to_do_list)
# ant.index_to_visit_empty()==True就是feasible的意思
if local_search is True and ant.index_to_visit_empty():
- new_path = ant.local_search_procedure()
- if new_path is not None:
- pass
+ ant.local_search_procedure(what_to_do_list)
- def acs_time(self, vehicle_num):
- # how to calculate init_pheromone_val
- new_graph = self.graph.construct_graph_with_duplicated_depot(vehicle_num, 1)
+ return ant.get_path_without_duplicated_depot()
+ @staticmethod
+ def acs_time(new_graph: VrptwGraph, vehicle_num, ants_num, q0, what_to_do: Queue, what_is_found: Queue):
+ print('[acs_time]: start, vehicle_num %d' % vehicle_num)
# 初始化信息素矩阵
+ global_best_path = []
+ global_best_distance = None
+ ants_pool = ThreadPoolExecutor(ants_num)
+ ants_thread = []
+ ants = []
+ ants_what_to_do_list = []
while True:
- for k in range(self.ants_num):
+ for k in range(ants_num):
+ ant = Ant(new_graph, random.randint(0, vehicle_num - 1))
+ what_to_do_list = []
+ thread = ants_pool.submit(VrptwAco.new_active_ant, ant, True, np.zeros(new_graph.node_num), q0, what_to_do_list)
+
+ ants_thread.append(thread)
+ ants.append(ant)
+ ants_what_to_do_list.append(what_to_do_list)
+
+ # 这里可以使用result方法,等待线程跑完
+ for thread in ants_thread:
+
+ while not what_to_do.empty():
+ info = what_to_do.get()
+ if info.is_to_stop():
+ print('[acs_time]: receive stop info')
+ # 顺便要让蚂蚁线程都结束
+ for what_to_do_list in ants_what_to_do_list:
+ what_to_do_list.append(VrptwMessage('stop', None, None))
+ ants_pool.shutdown()
+ return
+ else:
+ print('[acs_time]: receive global path info')
+ global_best_path, global_best_distance = info.get_path_info()
+
+ thread.result()
+
+ # 判断蚂蚁找出来的路径是否是feasible的,并且比全局的路径要好
+ for ant in ants:
+ # 如果比全局的路径要好,则要将该路径发送到macs中
+ while not what_to_do.empty():
+ info = what_to_do.get()
+ if info.is_to_stop():
+ print('[acs_time]: receive stop info')
+ # 顺便要让蚂蚁线程都结束
+ for what_to_do_list in ants_what_to_do_list:
+ what_to_do_list.append(VrptwMessage('stop', None, None))
+ ants_pool.shutdown()
+ return
+ else:
+ print('[acs_time]: receive global path info')
+ global_best_path, global_best_distance = info.get_path_info()
+
+ if ant.index_to_visit_empty() and ant.total_travel_distance < global_best_distance:
+ print('[acs_time]: found a improved feasible path, send path info to macs')
+ what_is_found.put(VrptwMessage('path_info', ant.travel_path, ant.total_travel_distance))
+
+ # 在这里执行信息素的全局更新
+ new_graph.global_update_pheromone(global_best_path, global_best_distance)
+
+ @staticmethod
+ def acs_vehicle(new_graph: VrptwGraph, vehicle_num, ants_num, q0, what_to_do: Queue, what_is_found: Queue):
+ print('[acs_vehicle]: start, vehicle_num %d' % vehicle_num)
+ global_best_path = []
+ global_best_distance = None
+
+ # 使用邻近点算法初始化path 和distance
+ current_path, current_path_distance = new_graph.nearest_neighbor_heuristic()
+
+ # 找出当前path中未访问的结点
+ current_index_to_visit = list(range(new_graph.node_num))
+ for ind in set(current_path):
+ current_index_to_visit.remove(ind)
+
+ ants_pool = ThreadPoolExecutor(ants_num)
+ ants_thread = []
+ ants = []
+ ants_what_to_do_list = []
+ IN = np.zeros(new_graph.node_num)
+ while True:
+ for k in range(ants_num):
ant = Ant(new_graph, random.randint(0, vehicle_num-1))
- self.new_active_ant(ant, True, 0)
- # if ant.index_to_visit_empty() and ant.total_travel_distance < global_travel_distance:
- # send ant.travel_path
- # pass
+ what_to_do_list = []
+ thread = ants_pool.submit(VrptwAco.new_active_ant, ant, True, IN, q0, what_to_do_list)
+
+ ants_thread.append(thread)
+ ants.append(ant)
+ ants_what_to_do_list.append(what_to_do_list)
+
+ # 这里可以使用result方法,等待线程跑完
+ for thread in ants_thread:
+
+ while not what_to_do.empty():
+ info = what_to_do.get()
+ if info.is_to_stop():
+ # 顺便要让蚂蚁线程都结束
+ print('[acs_vehicle]: receive stop info')
+ for what_to_do_list in ants_what_to_do_list:
+ what_to_do_list.append(VrptwMessage('stop', None, None))
+ ants_pool.shutdown()
+ return
+ else:
+ print('[acs_vehicle]: receive global path info')
+ global_best_path, global_best_distance = info.get_path_info()
+
+ thread.result()
+
+ for ant in ants:
+ index_to_visit = ant.index_to_visit
+ IN[index_to_visit] = IN[index_to_visit]+1
+ path = ant.get_path_without_duplicated_depot()
+
+ # 判断蚂蚁找出来的路径是否比current_path,能使用vehicle_num辆车访问到更多的结点
+ if len(index_to_visit) < len(current_index_to_visit):
+ current_path = path
+ current_index_to_visit = index_to_visit
+ current_path_distance = ant.total_travel_distance
+ # 并且将IN设置为0
+ IN = np.zeros(new_graph.node_num)
+
+ # 如果这一条路径是feasible的话,就要发到macs_vrptw中
+ if ant.index_to_visit_empty():
+ print('[acs_vehicle]: found a feasible path, send path info to macs')
+ what_is_found.put(VrptwMessage('path_info', ant.travel_path, ant.total_travel_distance))
+
+ # 更新new_graph中的信息素,global
+ new_graph.global_update_pheromone(current_path, current_path_distance)
+
+ while not what_to_do.empty():
+ info = what_to_do.get()
+ if info.is_to_stop():
+ # 顺便要让蚂蚁线程都结束
+ print('[acs_vehicle]: receive stop info')
+ for what_to_do_list in ants_what_to_do_list:
+ what_to_do_list.append(VrptwMessage('stop', None, None))
+ ants_pool.shutdown()
+ return
+ else:
+ print('[acs_vehicle]: receive global path info')
+ global_best_path, global_best_distance = info.get_path_info()
+
+ new_graph.global_update_pheromone(global_best_path, global_best_distance)
+
+ def multiple_ant_colony_system(self):
+ # 在这里需要两个队列,一个队列是macs告诉acs_time和acs_vehicle这两个线程,当前的best path是什么、告诉他们停止计算
+ # 另外的一个线程就是用来,接收他们两个计算出来的比best path还要好的feasible path
+ time_what_to_do = Queue()
+ vehicle_what_to_do = Queue()
+ what_is_found = Queue()
+
+ while True:
+ # acs_vehicle
+ graph_for_acs_vehicle = self.graph.construct_graph_with_duplicated_depot(self.best_vehicle_num-1,
+ self.graph.init_pheromone_val)
+ acs_vehicle_thread = Thread(target=self.acs_vehicle, args=(graph_for_acs_vehicle, self.best_vehicle_num-1,
+ self.ants_num, self.q0, vehicle_what_to_do,
+ what_is_found))
+
+ # acs_time
+ graph_for_acs_time = self.graph.construct_graph_with_duplicated_depot(self.best_vehicle_num,
+ self.graph.init_pheromone_val)
+ acs_time_thread = Thread(target=self.acs_time, args=(graph_for_acs_time, self.best_vehicle_num,
+ self.ants_num, self.q0, time_what_to_do, what_is_found))
+
+ # 启动acs_vehicle_thread和acs_time_thread,当他们找到feasible、且是比best path好的路径时,就会发送到macs中来
+ print('[macs]: start acs_vehicle and acs_time')
+ acs_vehicle_thread.start()
+ acs_time_thread.start()
+
+ best_vehicle_num = self.best_vehicle_num
+
+ while acs_vehicle_thread.is_alive() and acs_time_thread.is_alive():
+ path_info = what_is_found.get()
+ print('[macs]: receive found path info')
+ found_path, found_path_distance = path_info.get_path_info()
+ # 如果,这两个线程找到的路径用的车辆更少了,就停止这两个线程,开始下一轮迭代
+ # 向acs_time和acs_vehicle中发送停止信息
+
+ if found_path_distance < self.best_path_distance:
+ print('[macs]: distance of found path better than best path\'s')
+ self.best_path = found_path
+ self.best_vehicle_num = found_path.count(0) - 1
+ self.best_path_distance = 0
+
+ if found_path.count(0)-1 < best_vehicle_num:
+ print('[macs]: vehicle num of found path better than best path\'s')
+ self.best_path = found_path
+ self.best_vehicle_num = found_path.count(0)-1
+ self.best_path_distance = 0
+
+ # 停止acs_time 和 acs_vehicle 两个线程
+ print('[macs]: send stop info to acs_time and acs_vehicle')
+ time_what_to_do.put(VrptwMessage(info_type='stop', path=None, distance=None))
+ vehicle_what_to_do.put(VrptwMessage(info_type='stop', path=None, distance=None))
if __name__ == '__main__':
@@ -185,4 +383,4 @@ if __name__ == '__main__':
graph = VrptwGraph(file_path)
vrptw = VrptwAco(graph)
- vrptw.run()
+ vrptw.multiple_ant_colony_system()
diff --git a/vrptw_base.py b/vrptw_base.py
index 9413731..5837e10 100644
--- a/vrptw_base.py
+++ b/vrptw_base.py
@@ -32,7 +32,7 @@ class VrptwGraph:
self.rho = rho
# 创建信息素矩阵
- self.init_pheromone_val = self._nearest_neighbor_heuristic()
+ self.nnh_travel_path, self.init_pheromone_val = self.nearest_neighbor_heuristic()
self.init_pheromone_val = 1/(self.init_pheromone_val * self.node_num)
self.pheromone_mat = np.ones((self.node_num, self.node_num)) * self.init_pheromone_val
@@ -49,18 +49,14 @@ class VrptwGraph:
# 从新计算距离
new_graph.node_dist_mat = np.zeros((new_graph.node_num, new_graph.node_num))
for i in range(new_graph.node_num):
- if 0 <= i <= vehicle_num - 1:
- original_i = 0
- else:
- original_i = i - vehicle_num + 1
+ original_i = max(0, i - vehicle_num + 1)
+
for j in range(i + 1, new_graph.node_num):
- if 0 <= i <= vehicle_num - 1:
- original_j = 0
- else:
- original_j = j - vehicle_num + 1
+ original_j = max(0, j - vehicle_num + 1)
+ new_graph.node_dist_mat[i][j] = self.node_dist_mat[original_i][original_j]
+ new_graph.node_dist_mat[j][i] = new_graph.node_dist_mat[i][j]
- new_graph.node_dist_mat[j][i] = new_graph.node_dist_mat[i][j] = self.node_dist_mat[original_i][original_j]
- # 启发式信息
+ # 启发式信息
new_graph.heuristic_info_mat = 1 / new_graph.node_dist_mat
# 信息素
new_graph.init_pheromone_val = init_pheromone_val
@@ -88,6 +84,7 @@ class VrptwGraph:
node_dist_mat = np.zeros((node_num, node_num))
for i in range(node_num):
node_a = nodes[i]
+ node_dist_mat[i][i] = 1e-8
for j in range(i+1, node_num):
node_b = nodes[j]
node_dist_mat[i][j] = VrptwGraph.calculate_dist(node_a, node_b)
@@ -115,12 +112,13 @@ class VrptwGraph:
self.pheromone_mat[current_ind][next_ind] += self.rho/best_path_distance
current_ind = next_ind
- def _nearest_neighbor_heuristic(self):
+ def nearest_neighbor_heuristic(self):
index_to_visit = list(range(1, self.node_num))
current_index = 0
current_load = 0
current_time = 0
travel_distance = 0
+ travel_path = [0]
while len(index_to_visit) > 0:
nearest_next_index = self._cal_nearest_next_index(index_to_visit, current_index, current_load, current_time)
@@ -129,6 +127,7 @@ class VrptwGraph:
current_load = 0
current_time = 0
+ travel_path.append(0)
current_index = 0
else:
current_load += self.nodes[nearest_next_index].demand
@@ -141,9 +140,12 @@ class VrptwGraph:
index_to_visit.remove(nearest_next_index)
travel_distance += self.node_dist_mat[current_index][nearest_next_index]
-
+ travel_path.append(nearest_next_index)
current_index = nearest_next_index
- return travel_distance
+ # 最后要回到depot
+ travel_distance += self.node_dist_mat[current_index][0]
+ travel_path.append(0)
+ return travel_path, travel_distance
def _cal_nearest_next_index(self, index_to_visit, current_index, current_load, current_time):
"""
@@ -174,3 +176,19 @@ class VrptwGraph:
nearest_ind = next_index
return nearest_ind
+
+
+class VrptwMessage:
+ def __init__(self, info_type, path, distance):
+ super()
+ if info_type != 'stop' and info_type != 'path_info':
+ raise RuntimeError('info_type should be: stop or path_info')
+ self.info_type = info_type
+ self.path = copy.deepcopy(path)
+ self.distance = distance
+
+ def get_path_info(self):
+ return self.path, self.distance
+
+ def is_to_stop(self):
+ return self.info_type == 'stop'