【问题标题】:OR-tools routing optimization node compatibilityOR-tools 路由优化节点兼容性
【发布时间】:2021-05-22 09:28:46
【问题描述】:

我正在尝试解决容量路由问题,其中我有一组需要不同数量和不同类型项目的节点。
另外我想允许节点丢弃,因为所有节点都带有一种物品可能仍然超过车辆容量,因此会导致没有解决方案。
然而,最终应该为所有节点提供服务,因此我使用迭代方法,将每个项目类型视为单独的路由问题。
但我想知道是否可以使用析取或类似的东西来解决“全局”路由问题。任何有关这是否可能的帮助表示赞赏。

Example:
Node 1 - item A - demand 10
Node 2 - item A - demand 10
Node 3 - item A - demand 12
Node 4 - item B - demand 10
Node 5 - item B - demand 10

vehicle I - capacity 20
vehicle II - capacity 10

我的方法:
首先解决项目 A:车辆 I 服务于节点 1 和 2,节点 3 被丢弃,保存丢弃的节点以供以后迭代
然后求解 B 项:车辆 I 服务于节点 4 和 5,车辆 II 空闲 求解剩余节点 3:车辆 I 服务于节点 3

编辑 我调整了我的方法以适应@mizux 的答案。代码下方:

EDIT2 修复了第一个循环中的需求回调函数仍会引用 product_index 变量并因此返回错误需求的错误。使用functools.partial修复。

import functools
from ortools.constraint_solver import pywrapcp, routing_enums_pb2

class CVRP():
    def __init__(self, data):
        # assert all(data['demands'] < max(data['vehicle_capacities'])) # if any demand exceeds cap no solution possible
        self.data = data

        self.vehicle_names_internal = [f'{i}:{j}' for j in data['products'] for i in data['vehicle_names']]
        self.manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']), len(self.vehicle_names_internal), data['depot'])
        self.routing = pywrapcp.RoutingModel(self.manager)

        transit_callback_id = self.routing.RegisterTransitCallback(self._dist_callback)      
        self.routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_id)
        
        # set up dimension for each product type for vehicle capacity constraint
        for product_index, product in enumerate(data['products']):
            dem_product_callback = functools.partial(self._dem_callback_generic, product_index=product_index)
            dem_callback_id = self.routing.RegisterUnaryTransitCallback(dem_product_callback)
            vehicle_product_capacity = [0 for i in range(len(self.vehicle_names_internal))]
            vehicle_product_capacity[product_index*data['num_vehicles']:product_index*data['num_vehicles']+data['num_vehicles']] = data['vehicle_capacities']
            print(product_index, product)
            print(self.vehicle_names_internal)
            print(vehicle_product_capacity)
            self.routing.AddDimensionWithVehicleCapacity(
                dem_callback_id,
                0,
                vehicle_product_capacity,
                True,
                f'capacity_{product}',
                )

        # disjunction (allow node drops)
        penalty = int(self.data['distance_matrix'].sum()+1) # penalty needs to be higher than total travel distance in order to only drop locations if not other feasible solution
        for field_pos_idx_arr in self.data['disjunctions']: 
            self.routing.AddDisjunction([self.manager.NodeToIndex(i) for i in field_pos_idx_arr], penalty)

        
    def _dist_callback(self, i, j):
        return self.data['distance_matrix'][self.manager.IndexToNode(i)][self.manager.IndexToNode(j)]
    
    def _dem_callback_generic(self, i, product_index):
        node = self.manager.IndexToNode(i)
        if node == self.data['depot']:
            return 0
        else:
            return self.data['demands'][node, product_index]

    def solve(self, verbose=False):    
        search_parameters = pywrapcp.DefaultRoutingSearchParameters()
        search_parameters.first_solution_strategy = (
            routing_enums_pb2.FirstSolutionStrategy.AUTOMATIC)
        search_parameters.local_search_metaheuristic = (
            routing_enums_pb2.LocalSearchMetaheuristic.AUTOMATIC)
        search_parameters.time_limit.FromSeconds(30)

        self.solution = self.routing.SolveWithParameters(search_parameters)

【问题讨论】:

  • 在您的示例中,总需求超过了车辆的总容量。您不会找到服务于所有节点的解决方案。如果可以多程派车,建议复制现有车辆增加车辆数量。您能否添加有关您期望的“析取”的更多信息,例如每辆车只有一种物品?
  • 我知道,这就是为什么我允许删除节点(有惩罚)并为剩余的未访问节点设置一个新问题。关于分离:每辆车只能装载一种类型的物品,并且不能有一条路线需要为不同的物品提供服务。但是我不知道如何用代码来准确表示。

标签: python optimization or-tools vehicle-routing


【解决方案1】:
  1. 您应该创建两个容量维度,每个类型一个, 您可以在每个位置增加相关维度。

  2. 您可以为每种物品类型复制您的车辆,即:

    • v0,车辆 1 类型 A:容量 A:20,容量 B:0
    • v1,车辆 1 类型 B,容量 A:0,容量 B:20
    • v2,车辆 2 类型 A:容量 A:10,容量 B:0
    • v3,车辆 2 类型 B,容量 A:0,容量 B:10

    注意:您可以复制它以允许多次旅行

  3. 您可以创建一个“门”节点以仅允许一种车辆配置。 例如只允许 v0 或 v1 做一些访问

    v0_start = routing.Start(0)
    v0_end = routing.End(0)
    v1_start = routing.Start(1)
    v1_end = routing.End(1)
    gate_index = manager.NodeToIndex(gate_index)
    routing.NextVar(v0_start).setValues[gate_index, v0_end]
    routing.NextVar(v1_start).setValues[gate_index, v1_end]
    

    由于节点只能被访问一次,v0和v1中的一辆车可以通过门节点,而另一辆车别无选择,只能去末端节点,即空路线,您可以在后处理分配时删除。

  4. 如果车辆 II 比车辆 I 更便宜,您还可以将车辆 FixedCost 添加到激励求解器以使用车辆 II 等...

  5. 将每个位置添加到析取中,以便求解器在需要时删除它们

    location_index = manager.NodeToIndex(location_id)
    routing.AddDisjunction(
      [location_index], # locations
      penalty,
      max_cardinality=1 # you can omit it since it is already 1 by default
    )
    

【讨论】:

  • 我假设“门”位置可以是任意的,例如和仓库一模一样?
  • 是的,通常你有depot-&gt;gate: 0gate-&gt;any == depot-&gt;any,所以在显示解决方案时你可以移除门并显示depot-&gt;Next(gate)
  • 我调整了我对您的回答的方法。对于每种产品类型,我有一个维度,并且对于需要不同产品类型的节点,将需求回调返回值设置为 0。然而,现在求解器返回一条路线,该路线为所有节点提供错误的产品,因此负载为 0。我是否需要将他们对不合适产品的需求设置为无穷大?
  • 您应该提供使用数据的要点,以便我们对其进行调试,您也可以通过 or-tools 邮件列表或我们的 discord(github 的 README.md 上的聊天徽章)联系我们项目)
  • 由于我在 EDIT2 中提到的错误修复,它现在可以工作了
猜你喜欢
  • 1970-01-01
  • 2022-06-22
  • 1970-01-01
  • 2014-08-27
  • 1970-01-01
  • 2020-03-18
  • 1970-01-01
  • 2016-01-31
  • 1970-01-01
相关资源
最近更新 更多