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解决车辆路径问题要求输入的是整数,我们可以先放大数据,输出结果时候缩小数据,对优化的结果无影响。

相关文章
|
7月前
|
人工智能 算法 决策智能
OR-tools求解器使用介绍(一)
OR-tools求解器使用介绍(一)
|
7月前
|
决策智能
Or-tools调用求解器介绍(三)
Or-tools调用求解器介绍(三)
|
2天前
|
达摩院 开发者 容器
「达摩院MindOpt」优化形状切割问题(MILP)
在制造业,高效地利用材料不仅是节约成本的重要环节,也是可持续发展的关键因素。无论是在金属加工、家具制造还是纺织品生产中,原材料的有效利用都直接影响了整体效率和环境影响。
「达摩院MindOpt」优化形状切割问题(MILP)
|
2天前
|
人工智能 自然语言处理 达摩院
MindOpt 云上建模求解平台:多求解器协同优化
数学规划是一种数学优化方法,主要是寻找变量的取值在特定的约束情况下,使我们的决策目标得到一个最大或者最小值的决策。
|
2天前
|
存储 达摩院 调度
「达摩院MindOpt」优化FlowShop流水线作业排班问题
在企业在面临大量多样化的生产任务时,如何合理地安排流水线作业以提高生产效率及确保交货期成为了一个重要的问题。
「达摩院MindOpt」优化FlowShop流水线作业排班问题
|
10月前
|
达摩院 调度
使用达摩院MindOpt优化交通调度_最大化通行量—线性规划问题
在数学规划中,网络流问题是指一类基于网络模型的流量分配问题。网络流问题的目标是在网络中分配资源,使得网络的流量满足一定的限制条件,并且使得某些目标函数最小或最大化。网络流问题通常涉及一个有向图,图中每个节点表示一个资源,每条边表示资源之间的关系。边上有一个容量值,表示该边上最多可以流动的资源数量。流量从源节点开始流出,经过一系列中间节点,最终到达汇节点。在这个过程中,需要遵守一定的流量守恒和容量限制条件。
|
6月前
|
API Python
MindOpt V1.0优化种植计划问题,新的建模方法
种植计划是指农业生产中针对不同农作物的种植时间、面积和种植方式等方面的规划安排。根据具体情况进行合理的规划和安排,以实现农作物的高产、优质和可持续发展。
MindOpt V1.0优化种植计划问题,新的建模方法
|
10月前
|
达摩院 供应链 JavaScript
网络流:优化仓储物流调度问题-达摩院MindOpt
仓储物流调度是指在物流供应链中,对仓储和运输(运输路线、成本)进行协调和安排的过程。主要包含物流计划、运输调度、运发管理、库存管理等重要环节。随着网络、电商行业的迅速发展,仓储物流调度对于企业来说也非常重要,优秀的调度方案可以帮助降低库存成本、物流配送的效率、成本等等等,从而给企业带来降本增效。
网络流:优化仓储物流调度问题-达摩院MindOpt
|
10月前
|
数据可视化
MindOpt优化如何分散化风险并实现收益与风险最优配比问题
资产配置,投资组合是指通过分散投资资金的方式来规避投资过程中的风险。在实际的投资过程中,如何决定投资哪些产品来实现收益最大化和风险最小化是一个关键的问题。
MindOpt优化如何分散化风险并实现收益与风险最优配比问题
|
12月前
|
机器学习/深度学习 人工智能 达摩院
MindOpt——优化虚拟电厂智能调度问题(二)
智慧楼宇调度,是在保证社区负荷需求的情况下,通过储能设备的指令控制,以用电经济性、环保性和对电网稳定性为综合目标的一种调度场景。
MindOpt——优化虚拟电厂智能调度问题(二)