OR-tools求解器使用介绍(二)

简介: OR-tools求解器使用介绍(二)

接上期OR-tools 最核心的功能是解决车辆路径问题。其提供了车辆路径问题的不同场景的建模, 并提供了多种可供选择的元启发式算法,本文介绍OR-tools解决车辆路径问题的方法。

3.车辆路径问题

旅行商问题(TSP)

1)导入包,传入距离矩阵数据。

from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
def create_data_model():
    data = {}
    data['distance_matrix'] = [
        [0, 2451, 713, 1018, 1631, 1374, 2408, 213, 2571, 875, 1420, 2145, 1972],
        [2451, 0, 1745, 1524, 831, 1240, 959, 2596, 403, 1589, 1374, 357, 579],
        [713, 1745, 0, 355, 920, 803, 1737, 851, 1858, 262, 940, 1453, 1260],
        [1018, 1524, 355, 0, 700, 862, 1395, 1123, 1584, 466, 1056, 1280, 987],
        [1631, 831, 920, 700, 0, 663, 1021, 1769, 949, 796, 879, 586, 371],
        [1374, 1240, 803, 862, 663, 0, 1681, 1551, 1765, 547, 225, 887, 999],
        [2408, 959, 1737, 1395, 1021, 1681, 0, 2493, 678, 1724, 1891, 1114, 701],
        [213, 2596, 851, 1123, 1769, 1551, 2493, 0, 2699, 1038, 1605, 2300, 2099],
        [2571, 403, 1858, 1584, 949, 1765, 678, 2699, 0, 1744, 1645, 653, 600],
        [875, 1589, 262, 466, 796, 547, 1724, 1038, 1744, 0, 679, 1272, 1162],
        [1420, 1374, 940, 1056, 879, 225, 1891, 1605, 1645, 679, 0, 1017, 1200],
        [2145, 357, 1453, 1280, 586, 887, 1114, 2300, 653, 1272, 1017, 0, 504],
        [1972, 579, 1260, 987, 371, 999, 701, 2099, 600, 1162, 1200, 504, 0],]  
    data['num_vehicles'] = 1 #车辆数,旅行商问题1辆车
    data['depot'] = 0  #起点
    return data

2)创建路由模型,创建距离回调函数。

#创建路由模型
data = create_data_model()
manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),
                                      data['num_vehicles'], data['depot'])
routing = pywrapcp.RoutingModel(manager)
#创建距离回调函数
def distance_callback(from_index, to_index):
   """Returns the distance between the two nodes."""
   # Convert from routing variable Index to distance matrix NodeIndex.
   from_node = manager.IndexToNode(from_index)
   to_node = manager.IndexToNode(to_index)
   return data['distance_matrix'][from_node][to_node]
transit_callback_index = routing.RegisterTransitCallback(distance_callback)#注册回调函数,方便后面求解

回调函数搭建,以后很快调用距离矩阵的数据,方便后面搭建模型搜寻最优解。

3)设置目标函数(总距离)

routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)

4)设置搜索算法 设置第一个搜索策略,TSP问题仅设置一个搜索策略即可,复杂的vrp问题,需要设置第二个搜索方案。

search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = (
    routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)

可选择的第一种搜索方案有以下几种

对于复杂问题,需要新增第二种搜索方案(元启发式算法),其选项也有很多。

其可供选择的元启发式算法有:引导式搜索、禁忌搜索、模拟退火等。其推荐选择引导式搜索(GLS),我测试过多种案例,GLS效果确实最好。另外可以设置搜索使用的算子。常用的two_opt、relocate、cross等都能在OR-tools底层代码文档中找到,默认选择了这些算子,但我们也可以自己调整使用不同的算子。

search_parameters.local_search_operators.use_two_opt = (pywrapcp.BOOL_TRUE)
search_parameters.local_search_operators.use_relocate = (pywrapcp.BOOL_TRUE)
search_parameters.local_search_operators.use_cross = (pywrapcp.BOOL_TRUE)

5)完整代码

"""Simple Travelling Salesperson Problem (TSP) between cities."""
from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
def create_data_model():
    """Stores the data for the problem."""
    data = {}
    data['distance_matrix'] = [
        [0, 2451, 713, 1018, 1631, 1374, 2408, 213, 2571, 875, 1420, 2145, 1972],
        [2451, 0, 1745, 1524, 831, 1240, 959, 2596, 403, 1589, 1374, 357, 579],
        [713, 1745, 0, 355, 920, 803, 1737, 851, 1858, 262, 940, 1453, 1260],
        [1018, 1524, 355, 0, 700, 862, 1395, 1123, 1584, 466, 1056, 1280, 987],
        [1631, 831, 920, 700, 0, 663, 1021, 1769, 949, 796, 879, 586, 371],
        [1374, 1240, 803, 862, 663, 0, 1681, 1551, 1765, 547, 225, 887, 999],
        [2408, 959, 1737, 1395, 1021, 1681, 0, 2493, 678, 1724, 1891, 1114, 701],
        [213, 2596, 851, 1123, 1769, 1551, 2493, 0, 2699, 1038, 1605, 2300, 2099],
        [2571, 403, 1858, 1584, 949, 1765, 678, 2699, 0, 1744, 1645, 653, 600],
        [875, 1589, 262, 466, 796, 547, 1724, 1038, 1744, 0, 679, 1272, 1162],
        [1420, 1374, 940, 1056, 879, 225, 1891, 1605, 1645, 679, 0, 1017, 1200],
        [2145, 357, 1453, 1280, 586, 887, 1114, 2300, 653, 1272, 1017, 0, 504],
        [1972, 579, 1260, 987, 371, 999, 701, 2099, 600, 1162, 1200, 504, 0],
    ]  # yapf: disable
    data['num_vehicles'] = 1
    data['depot'] = 0
    return data
def print_solution(manager, routing, solution):
    """Prints solution on console."""
    print('Objective: {} miles'.format(solution.ObjectiveValue()))
    index = routing.Start(0)
    plan_output = 'Route for vehicle 0:\n'
    route_distance = 0
    while not routing.IsEnd(index):
        plan_output += ' {} ->'.format(manager.IndexToNode(index))
        previous_index = index
        index = solution.Value(routing.NextVar(index))
        route_distance += routing.GetArcCostForVehicle(previous_index, index, 0)
    plan_output += ' {}\n'.format(manager.IndexToNode(index))
    print(plan_output)
    plan_output += 'Route distance: {}miles\n'.format(route_distance)
def main():
    """Entry point of the program."""
    # Instantiate the data problem.
    data = create_data_model()
    # Create the routing index manager.
    manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),
                                           data['num_vehicles'], data['depot'])
    # Create Routing Model.
    routing = pywrapcp.RoutingModel(manager)
    def distance_callback(from_index, to_index):
        """Returns the distance between the two nodes."""
        # Convert from routing variable Index to distance matrix NodeIndex.
        from_node = manager.IndexToNode(from_index)
        to_node = manager.IndexToNode(to_index)
        return data['distance_matrix'][from_node][to_node]
    transit_callback_index = routing.RegisterTransitCallback(distance_callback)
    # Define cost of each arc.
    routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
    # Setting first solution heuristic.
    search_parameters = pywrapcp.DefaultRoutingSearchParameters()
    search_parameters.first_solution_strategy = (
        routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
    # Solve the problem.
    solution = routing.SolveWithParameters(search_parameters)
    # Print solution on console.
    if solution:
        print_solution(manager, routing, solution)
if __name__ == '__main__':
    main()

简单车辆路径问题(vrp)

本问题是增加多辆车配送。与上一问题不同之处是主要是增加了多辆车。并对车辆行驶距离做了限制。

dimension_name = 'Distance'
routing.AddDimension(
    transit_callback_index,
    0,  # 是否可以等待(有时间窗的时候有用)
    3000,  # 车辆最大行驶距离
    True,  # 从0开始计算。
    dimension_name)
distance_dimension = routing.GetDimensionOrDie(dimension_name)
distance_dimension.SetGlobalSpanCostCoefficient(100)

完整代码:

"""Simple Vehicles Routing Problem (VRP).
   This is a sample using the routing library python wrapper to solve a VRP
   problem.
   A description of the problem can be found here:
   http://en.wikipedia.org/wiki/Vehicle_routing_problem.
   Distances are in meters.
"""
from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
def create_data_model():
    """Stores the data for the problem."""
    data = {}
    data['distance_matrix'] = [
        [
            0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,
            468, 776, 662
        ],
        [
            548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,
            1016, 868, 1210
        ],
        [
            776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,
            1130, 788, 1552, 754
        ],
        [
            696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,
            1164, 560, 1358
        ],
        [
            582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,
            1050, 674, 1244
        ],
        [
            274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,
            514, 1050, 708
        ],
        [
            502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,
            514, 1278, 480
        ],
        [
            194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,
            662, 742, 856
        ],
        [
            308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,
            320, 1084, 514
        ],
        [
            194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,
            274, 810, 468
        ],
        [
            536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,
            730, 388, 1152, 354
        ],
        [
            502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,
            308, 650, 274, 844
        ],
        [
            388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,
            536, 388, 730
        ],
        [
            354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,
            342, 422, 536
        ],
        [
            468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,
            342, 0, 764, 194
        ],
        [
            776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,
            388, 422, 764, 0, 798
        ],
        [
            662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,
            536, 194, 798, 0
        ],
    ]
    data['num_vehicles'] = 4
    data['depot'] = 0
    return data
def print_solution(data, manager, routing, solution):
    """Prints solution on console."""
    print(f'Objective: {solution.ObjectiveValue()}')
    max_route_distance = 0
    for vehicle_id in range(data['num_vehicles']):
        index = routing.Start(vehicle_id)
        plan_output = 'Route for vehicle {}:\n'.format(vehicle_id)
        route_distance = 0
        while not routing.IsEnd(index):
            plan_output += ' {} -> '.format(manager.IndexToNode(index))
            previous_index = index
            index = solution.Value(routing.NextVar(index))
            route_distance += routing.GetArcCostForVehicle(
                previous_index, index, vehicle_id)
        plan_output += '{}\n'.format(manager.IndexToNode(index))
        plan_output += 'Distance of the route: {}m\n'.format(route_distance)
        print(plan_output)
        max_route_distance = max(route_distance, max_route_distance)
    print('Maximum of the route distances: {}m'.format(max_route_distance))
def main():
    """Entry point of the program."""
    # Instantiate the data problem.
    data = create_data_model()
    # Create the routing index manager.
    manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),
                                           data['num_vehicles'], data['depot'])
    # Create Routing Model.
    routing = pywrapcp.RoutingModel(manager)
    # Create and register a transit callback.
    def distance_callback(from_index, to_index):
        """Returns the distance between the two nodes."""
        # Convert from routing variable Index to distance matrix NodeIndex.
        from_node = manager.IndexToNode(from_index)
        to_node = manager.IndexToNode(to_index)
        return data['distance_matrix'][from_node][to_node]
    transit_callback_index = routing.RegisterTransitCallback(distance_callback)
    # Define cost of each arc.
    routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
    # Add Distance constraint.
    dimension_name = 'Distance'
    routing.AddDimension(
        transit_callback_index,
        0,  # no slack
        3000,  # vehicle maximum travel distance
        True,  # start cumul to zero
        dimension_name)
    distance_dimension = routing.GetDimensionOrDie(dimension_name)
    distance_dimension.SetGlobalSpanCostCoefficient(100)
    # Setting first solution heuristic.
    search_parameters = pywrapcp.DefaultRoutingSearchParameters()
    search_parameters.first_solution_strategy = (
        routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
    # Solve the problem.
    solution = routing.SolveWithParameters(search_parameters)
    # Print solution on console.
    if solution:
        print_solution(data, manager, routing, solution)
    else:
        print('No solution found !')
if __name__ == '__main__':
    main()

车辆路径问题复杂场景

以上只是很基础的建模框架,实际业务场景要复杂的多,OR-tools官方文档并没有完全提供解决复杂场景的所有方法,需要深入了解OR-tools建模原理,底层函数逻辑。也可以去OR-tools官方小组学习各个国家的人使用OR-tools遇到新场景新问题如何解决(小组活跃度很高)。在这里,举几个我在实际应用中的遇到的几个典型的场景的代码实现过程:

1)改变目标函数

目标函数一般不能只是按距离来衡量。大部分时候目标函数是成本,因为企业的目的最终是降低成本。因此需要将目标函数设定为:车辆使用成本和车辆行驶成本之和。

routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator_index)
 # 以下是后面增加的少用车的约束,变量暂时重新设置方便后续区分.会增加在狐函数中,已把后续输出距离调整过来
Vehicle = namedtuple('Vehicle', ['index', 'capacity', 'cost'])
idxs = [i for i in range(num4)]
capacities = [i * 10000 for i in [cnum4] * num4]  # 车辆容量
costs = [1000 * cost_tz] * num4
pppp = [Vehicle(idx, capacity, cost) for idx, capacity, cost in zip(idxs, capacities, costs)]
for veh in pppp:
    routing.SetFixedCostOfVehicle(veh.cost, int(veh.index))

以上代码在目标函数中增加了车辆费用。

2)设置最长行驶时间、禁止访问时间

用solver.AddConstraint函数来设置禁止访问时间。

def add_time_window_constraints(routing, manager, data, time_evaluator_index):
    time = 'Time'
    horizon = 60
    routing.AddDimension(
        time_evaluator_index,
        horizon * 10,  # 允许等待的时间
        horizon * maxhour,  # 每辆车的最大行驶时间
        False,  # 按出发点的时间窗开始累积,如果True是按0开始计算。
        time)
    time_dimension = routing.GetDimensionOrDie(time)
    # 给每个位置增加时间窗约束
    ######增加禁止送货时间,
    solver = routing.solver()
    for i in range(1, len(df2['网点名称'])):
        solver.AddConstraint(
            solver.NotMemberCt(
                time_dimension.CumulVar(i),
                [time_forbid(im[1])[0]],
                [time_forbid(im[1])[1]]
            )
        )

3)加上定制化更精确的卸货时间

def create_time_evaluator(data):
  # 编写时间回调函数.
  def service_time(data, node):
      if data['demands'][node] < 0.001 * 10000:
          return int(0)
      elif data['demands'][node] < 0.02 * 10000:  #
          return int(numberChosen13.get())  # int(10)
      else:
          return int(numberChosen14.get())  # int(30)
  def travel_time(data, from_node, to_node):
      # 计算两店之间的时间
      travel_time = data['mtime'][from_node][to_node]
      # print(travel_time)
      return travel_time
  _total_time = {}
  # 加上服务时间,生成时间矩阵
  for from_node in range(data['num_locations']):
      _total_time[from_node] = {}
      for to_node in range(data['num_locations']):
          if from_node == to_node:
              _total_time[from_node][to_node] = 0
          else:
              _total_time[from_node][to_node] = int( \
                  service_time(data, from_node) + travel_time( \
                      data, from_node, to_node))
  def time_evaluator(manager, from_node, to_node):
      # 从时间矩阵中,获取两个店的时间
      return _total_time[manager.IndexToNode(from_node)][manager.IndexToNode(
          to_node)]
  return time_evaluator

以上几个是经常用到的场景,实际上现实业务还有很多复杂的场景。OR-tools解决车辆路径问题要求输入的是整数,我们可以先放大数据,输出结果时候缩小数据,对优化的结果无影响。

目录
相关文章
|
人工智能 算法 决策智能
OR-tools求解器使用介绍(一)
OR-tools求解器使用介绍(一)
809 0
|
决策智能
Or-tools调用求解器介绍(三)
Or-tools调用求解器介绍(三)
335 0
|
7月前
|
达摩院 开发者 容器
「达摩院MindOpt」优化形状切割问题(MILP)
在制造业,高效地利用材料不仅是节约成本的重要环节,也是可持续发展的关键因素。无论是在金属加工、家具制造还是纺织品生产中,原材料的有效利用都直接影响了整体效率和环境影响。
「达摩院MindOpt」优化形状切割问题(MILP)
|
7月前
|
人工智能 自然语言处理 达摩院
MindOpt 云上建模求解平台:多求解器协同优化
数学规划是一种数学优化方法,主要是寻找变量的取值在特定的约束情况下,使我们的决策目标得到一个最大或者最小值的决策。
|
2月前
|
机器学习/深度学习 算法 数据可视化
如果你的PyTorch优化器效果欠佳,试试这4种深度学习中的高级优化技术吧
在深度学习领域,优化器的选择对模型性能至关重要。尽管PyTorch中的标准优化器如SGD、Adam和AdamW被广泛应用,但在某些复杂优化问题中,这些方法未必是最优选择。本文介绍了四种高级优化技术:序列最小二乘规划(SLSQP)、粒子群优化(PSO)、协方差矩阵自适应进化策略(CMA-ES)和模拟退火(SA)。这些方法具备无梯度优化、仅需前向传播及全局优化能力等优点,尤其适合非可微操作和参数数量较少的情况。通过实验对比发现,对于特定问题,非传统优化方法可能比标准梯度下降算法表现更好。文章详细描述了这些优化技术的实现过程及结果分析,并提出了未来的研究方向。
29 1
|
5月前
|
人工智能 算法 调度
优化问题之如何选择合适的优化求解器
优化问题之如何选择合适的优化求解器
|
5月前
|
调度 决策智能
优化问题之优化求解器有哪些主要的评估特性
优化问题之优化求解器有哪些主要的评估特性
|
达摩院 调度
使用达摩院MindOpt优化交通调度_最大化通行量—线性规划问题
在数学规划中,网络流问题是指一类基于网络模型的流量分配问题。网络流问题的目标是在网络中分配资源,使得网络的流量满足一定的限制条件,并且使得某些目标函数最小或最大化。网络流问题通常涉及一个有向图,图中每个节点表示一个资源,每条边表示资源之间的关系。边上有一个容量值,表示该边上最多可以流动的资源数量。流量从源节点开始流出,经过一系列中间节点,最终到达汇节点。在这个过程中,需要遵守一定的流量守恒和容量限制条件。
|
7月前
|
存储 达摩院 调度
「达摩院MindOpt」优化FlowShop流水线作业排班问题
在企业在面临大量多样化的生产任务时,如何合理地安排流水线作业以提高生产效率及确保交货期成为了一个重要的问题。
「达摩院MindOpt」优化FlowShop流水线作业排班问题
MindOpt V1.0优化种植计划问题,新的建模方法
种植计划是指农业生产中针对不同农作物的种植时间、面积和种植方式等方面的规划安排。根据具体情况进行合理的规划和安排,以实现农作物的高产、优质和可持续发展。
MindOpt V1.0优化种植计划问题,新的建模方法