1、前言
本文主要介绍基于城市路网数据进行借用蒙特卡洛模拟思想进行实验,各个步骤的处理方式并不唯一,达到各环节所需要完成的任务即可。实验操作为本人摸索总结出来的,操作方式仅作参考,并不为最优方法,但为可行方式。不严谨的地方请见谅,欢迎批评与讨论。
2、路网数据预处理
该环节主要在arcgis软件中进行。由于蒙特卡罗模拟的循环过程将采用python进行,所以arcgis在此处仅起到数据预处理作用。通常我们获得的路网数据如图1所示,只是单纯的线要素。首先需要将该线要素构建网络数据集,具体方法可以参考以下链接中的教程:
[1] https://zhuanlan.zhihu.com/p/688360165
其中比较重要的点是需要保证路网数据的连通性,arcgis提供了拓扑检查工具,对于存在问题的路段可以人工修复。如果路网不连通,则后续实验难以进行。
创建网络数据集后,我们将得到图2中的数据,将该数据导入arcgis后将如图3所示。此时各个节点为路网中各线段的端点,这也是后续实验中进行路径求解的起点与终点的来源。每个的端点需要设置ID,这个ID具有唯一性。创建ID的过程可以在点要素的属性表中通过添加字段完成。
此时路网数据集的道路的线要素数据表如图4所示,每条道路数据并不含有两端端点的ID,故需要进行Coverage操作,具体过程如图5-6所示,最终将得到图7的文件。(导出的文件名要设置短一些,不要像图7那样数字开头,可以用中文)
此时“arc”文件中将含有每条道路两端的端点的ID,“node”文件中将含有各个端点的ID信息。将两个文件的属性表导出为excel文件,分别为“节点信息”与“道路信息”。
3、基于python构建复杂网络
该环节需要基于python的networkx库进行。实验中所用到的excel文件内容如图8所示。Python中的部分操作内容可以参考如下链接:
[4] https://zhuanlan.zhihu.com/p/164470586
通过以下代码可以构建复杂网络,节点为道路端点,连边为道路。
#节点经纬度字典
dict_loc=dict(zip(df_node['节点ID'],list(zip(df_node['经度'],df_node['纬度']))))
# 创建空的交通网络
G = nx.Graph()
# 添加节点到交通网络
for index, row in df_node.iterrows():
node_id = row['节点ID']
longitude = row['经度']
latitude = row['纬度']
G.add_node(node_id, pos=(longitude, latitude))
# 添加边到交通网络
for index, row in df_edge.iterrows():
road_id = row['道路ID']
start_node = row['节点1']
end_node = row['节点2']
road_length = row['长度']
G.add_edge(start_node, end_node, road_id=road_id, length=road_length)
4、最短路径计算
以下代码以节点“19814”与节点“30073”为例展示了最短路径的计算方式,批量计算可以通过For循环实现。另外,由于实际计算中所需的POI(如配送中心)并不一定是道路的端点,此时可以通过一些判断准则,选取合适的道路端点代表POI(如选取距离POI最近的端点)。
# 计算最短路径
start_node = 19814
end_node = 30073
shortest_path = nx.shortest_path(G, source=start_node, target=end_node, weight='length')
shortest_distance = nx.shortest_path_length(G, source=start_node, target=end_node, weight='length')
# 打印最短路径和最短距离
print(f"最短路径: {shortest_path}")
print(f"最短距离: {shortest_distance}")
5、蒙特卡洛模拟
此处主要参考蒙特卡洛模拟的思想,进行多次随机实验。如存在配送中心A与100个配送点,每次配送需要运送5个配送点,需要计算最短的配送距离。所以每次模型都要从100个配送点中随机抽取5个点。假设5个配送点分别为a、b、c、d、e,前往各个配送点的配送顺序将决定整体的配送距离。以下代码展示了利用穷举法进行相关实验所构建的相关函数。
#计算节点之间的最短路径
def cal_path(start,end):
distance = nx.shortest_path_length(G, source=start, target=end, weight='length')
return distance
#蒙特卡洛模拟
def monte_carlo_sim(data,num_samples,num_sim):
list_sample=[]
for _ in range(num_sim):
sample = random.sample(data, num_samples)
sample_key = tuple(sorted(sample))
list_sample.append(sample_key)
return list_sample
#生成列表元素的全排列
def all_permutations(lst):
# 使用permutations函数生成所有排列
perm_list = list(permutations(lst))
return perm_list
#列表最小元素位置
def min_position(lst):
min_value = min(lst)
min_position = lst.index(min_value)
return min_position
#对于每条配送路径选取最优路径
#穷举法
def select_path_all(start,list_data):
#蒙特卡洛模拟每次抽取的一个送货目的地列表进行全排列
list_choose=all_permutations(list_data)
list_result=[]
for i in list_choose:
#首先计算起点到第一个点的距离
length_all=cal_path(start,i[0])
#计算剩余节点间的距离
for j in range(0,len(i)-1):
distance=cal_path(i[j],i[j+1])
length_all=length_all+distance
#返程
length_end= cal_path(i[-1],start)
length_all=length_all+length_end
list_result.append(length_all)
index=min_position(list_result)
list_out=list(list_choose[index])
list_out.insert(0,start)
list_out.append(start)
return list_out,list_result
穷举法存在计算时间较长的问题,可以结合一些智能算法进行优化求解,如遗传算法等。主要针对“select_path_all”函数代码进行改进。以下代码使用精英策略的遗传算法进行求解。
#对于每条配送路径选取最优路径
#遗传算法
def calculate_total_distance(graph, path):
total_distance = 0
#计算某条路径的总长度
for i in range(len(path) - 1):
total_distance += graph[path[i]][path[i+1]]['weight']
return total_distance
def generate_random_route(graph, start):
nodes = list(graph.nodes())
nodes.remove(start)
random.shuffle(nodes)
return [start] + nodes + [start]
def crossover(parent1, parent2):
crossover_point = random.randint(1, len(parent1) - 2)
child = parent1[:crossover_point]
for node in parent2:
if node not in child:
child.append(node)
child.append(child[0])
return child
def mutate(route):
mutation_point1 = random.randint(1, len(route) - 2)
mutation_point2 = random.randint(1, len(route) - 2)
route[mutation_point1], route[mutation_point2] = route[mutation_point2], route[mutation_point1]
def genetic_algorithm(graph, start, population_size, generations):
#随机生成路径,含起始点,生成种群大小
population = [generate_random_route(graph, start) for _ in range(population_size)]
for _ in range(generations):
population = sorted(population, key=lambda x: calculate_total_distance(graph, x))
new_population = [population[0]] # Elitism: keep the best route from the previous generation
while len(new_population) < population_size:
parent1, parent2 = random.sample(population, 2)
child = crossover(parent1, parent2)
if random.random() < 0.1: # Mutation rate
mutate(child)
new_population.append(child)
population = new_population
return population[0], calculate_total_distance(graph, population[0])
def pair_elements(lst):
#生成节点之间两两的组合,用于创建网络
return list(combinations(lst, 2))
def GA_sim(list_path,start,population_size,generations):
#构建路径图
G_cal=nx.Graph()
nodes=start+list_path
pairs = pair_elements(nodes)
for i in pairs:
length_edge=cal_path(i[0],i[1])
#weight与函数遗传算法中计算距离保持一致
G_cal.add_edge(i[0],i[1],weight=length_edge)
optimal,result=genetic_algorithm(G_cal, start[0], population_size, generations)
return optimal,result
下面代码为路径求解及文件导出过程,最后将得到各起点(start/配送中心)到多种终点(end/配送点)组合的配送距离结果。
#循环求解配送中心到各个配送点组合的最短距离
for i in list_start:
df_out=pd.DataFrame([])
list_out=[]
# for i in list_choose_path:
list_monte_path=monte_carlo_sim(list_end,10,1000)
for j in tqdm(list_monte_path, desc="Processing paths"):
_,result=GA_sim(list(j),start,1000,100)
list_out.append(result)
file_name=str(i)+'.csv'
df_out.to_csv(file_name)
6、结语
基于蒙特卡洛模拟的思想进行实验的目的主要为了比较不同配送模式下对于配送距离/配送效率的影响,由于没有实际数据的支持,所以选择这种模拟的方式对配送模式进行考察。本文代码并不完整,主要为进行相关工作提供一些思路上的参考,不同任务需要结合具体场景进行建模。