基于蒙特卡洛模拟求解城市货运配送路径(python+arcgis)

本文介绍了如何使用蒙特卡洛模拟方法对城市路网数据进行处理,包括arcgis中的数据预处理、Python中利用networkx构建复杂网络以及最短路径计算。重点讲解了如何从路网数据到创建网络模型,以评估配送中心与配送点的最优路径方案。
摘要由CSDN通过智能技术生成

1、前言


本文主要介绍基于城市路网数据进行借用蒙特卡洛模拟思想进行实验,各个步骤的处理方式并不唯一,达到各环节所需要完成的任务即可。实验操作为本人摸索总结出来的,操作方式仅作参考,并不为最优方法,但为可行方式。不严谨的地方请见谅,欢迎批评与讨论。

2、路网数据预处理


该环节主要在arcgis软件中进行。由于蒙特卡罗模拟的循环过程将采用python进行,所以arcgis在此处仅起到数据预处理作用。通常我们获得的路网数据如图1所示,只是单纯的线要素。首先需要将该线要素构建网络数据集,具体方法可以参考以下链接中的教程:

[1] https://zhuanlan.zhihu.com/p/688360165

[2] arcgis--数据库构建网络数据集_arcgis新建网络数据集-CSDN博客

[3] GIS学习 建立网络数据集_哔哩哔哩_bilibili5

其中比较重要的点是需要保证路网数据的连通性,arcgis提供了拓扑检查工具,对于存在问题的路段可以人工修复。如果路网不连通,则后续实验难以进行。

图1

创建网络数据集后,我们将得到图2中的数据,将该数据导入arcgis后将如图3所示。此时各个节点为路网中各线段的端点,这也是后续实验中进行路径求解的起点与终点的来源。每个的端点需要设置ID,这个ID具有唯一性。创建ID的过程可以在点要素的属性表中通过添加字段完成。

图2
图3

此时路网数据集的道路的线要素数据表如图4所示,每条道路数据并不含有两端端点的ID,故需要进行Coverage操作,具体过程如图5-6所示,最终将得到图7的文件。(导出的文件名要设置短一些,不要像图7那样数字开头,可以用中文)

图4
图5
图6
图7

此时“arc”文件中将含有每条道路两端的端点的ID,“node”文件中将含有各个端点的ID信息。将两个文件的属性表导出为excel文件,分别为“节点信息”与“道路信息”。

3、基于python构建复杂网络


该环节需要基于python的networkx库进行。实验中所用到的excel文件内容如图8所示。Python中的部分操作内容可以参考如下链接:

[4] https://zhuanlan.zhihu.com/p/164470586

图8

通过以下代码可以构建复杂网络,节点为道路端点,连边为道路。

#节点经纬度字典
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、结语


基于蒙特卡洛模拟的思想进行实验的目的主要为了比较不同配送模式下对于配送距离/配送效率的影响,由于没有实际数据的支持,所以选择这种模拟的方式对配送模式进行考察。本文代码并不完整,主要为进行相关工作提供一些思路上的参考,不同任务需要结合具体场景进行建模。

  • 16
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CREATE TABLE IF NOT EXISTS `mk_international_location` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `pid` int(10) unsigned DEFAULT '0' COMMENT '父id/上级id', `path` varchar(255) DEFAULT '' COMMENT '路径', `level` int(10) unsigned DEFAULT '0' COMMENT '层级', `name` varchar(255) DEFAULT '' COMMENT '中文名称', `name_en` varchar(255) DEFAULT '' COMMENT '英文名称', `name_pinyin` varchar(255) DEFAULT '' COMMENT '中文拼音', `code` varchar(50) DEFAULT '' COMMENT '地区代码', `zip_code` varchar(50) DEFAULT '' COMMENT '邮政编码', `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '状态值 0无效 1有效', `manager_id` int(10) unsigned DEFAULT '0' COMMENT '操作管理员id', `manager_username` varchar(30) DEFAULT '' COMMENT '操作员账户名', `deleted_at` timestamp NULL DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, `lat` varchar(255) DEFAULT NULL COMMENT '百度.纬度', `lng` varchar(255) DEFAULT NULL COMMENT '百度.经度', PRIMARY KEY (`id`), KEY `international_location_pid_index` (`pid`), KEY `international_location_manager_id_index` (`manager_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=4170 ; -- -- 转存表中的数据 `mk_international_location` -- INSERT INTO `mk_international_location` (`id`, `pid`, `path`, `level`, `name`, `name_en`, `name_pinyin`, `code`, `zip_code`, `status`, `manager_id`, `manager_username`, `deleted_at`, `created_at`, `updated_at`, `lat`, `lng`) VALUES (1, 0, ',1,', 1, '亚洲', 'Asia', 'yazhou', '', '', 1, 0, '', NULL, '2019-01-25 02:51:17', NULL, NULL, NULL), (2, 0, ',2,', 1, '欧洲', 'Europe', 'ouzhou', '', '', 1, 0, '', NULL, '2019-01-25 02:51:17', NULL, NULL, NULL);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值