使用非连接图形或工具Python的车辆路径问题

时间:2017-11-16 00:20:30

标签: python or-tools

我正在使用这个python库或工具:https://developers.google.com/optimization/routing/tsp/vehicle_routing (代码可以在这里找到。)

问题在于,当您运行解决方案时,它会为您提供一条覆盖所有节点的路径。但是我的项目需要对节点之间的路径进行约束。例如,如果您在节点{3}上,则可能无法前往节点{18}。或者换句话说,如果您在节点{5},则只能前往节点{1,12,14}。我不确定如何将此约束添加到当前代码示例中。

请允许我进一步解释......

如果我们要查看此图表: enter image description here

您可以在此处看到此图表的代表: https://www.datacamp.com/community/tutorials/networkx-python-graph-tutorial

显然,在此问题中,您无法从其他节点前往某些节点。我在谷歌或工具示例中使用此图表中的数据来获取车辆路径问题解决方案。

这是我的代码:

import math
from ortools.constraint_solver import pywrapcp
from ortools.constraint_solver import routing_enums_pb2
import pandas as pd
from sklearn import preprocessing

def distance(x1, y1, x2, y2):
    dist = ((x1 - x2)**2 + (y1 - y2)**2)**(1/2)

    return dist
class CreateDistanceCallback(object):
  """Create callback to calculate distances between points."""

  def __init__(self, locations):
    """Initialize distance array."""
    size = len(locations)
    self.matrix = {}

    for from_node in range(size):
      self.matrix[from_node] = {}
      for to_node in range(size):
        x1 = locations[from_node][0]
        y1 = locations[from_node][1]
        x2 = locations[to_node][0]
        y2 = locations[to_node][1]
        self.matrix[from_node][to_node] = distance(x1, y1, x2, y2)

  def Distance(self, from_node, to_node):
    return int(self.matrix[from_node][to_node])

# Demand callback
class CreateDemandCallback(object):
  """Create callback to get demands at each location."""

  def __init__(self, demands):
    self.matrix = demands

  def Demand(self, from_node, to_node):
    return self.matrix[from_node]

def main():
  # Create the data.
  data = create_data_array()
  locations = data[0]
  demands = data[1]
  num_locations = len(locations)
  depot = 0    # The depot is the start and end point of each route.
  num_vehicles = 1

  # Create routing model.
  if num_locations > 0:
    routing = pywrapcp.RoutingModel(num_locations, num_vehicles, depot)
    search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()

    # Callback to the distance function.
    dist_between_locations = CreateDistanceCallback(locations)
    dist_callback = dist_between_locations.Distance
    routing.SetArcCostEvaluatorOfAllVehicles(dist_callback)

    # Put a callback to the demands.
    demands_at_locations = CreateDemandCallback(demands)
    demands_callback = demands_at_locations.Demand

    # Add a dimension for demand.
    slack_max = 0
    vehicle_capacity = 1500
    fix_start_cumul_to_zero = True
    demand = "Demand"
    routing.AddDimension(demands_callback, slack_max, vehicle_capacity,
                         fix_start_cumul_to_zero, demand)

    # Solve, displays a solution if any.
    assignment = routing.SolveWithParameters(search_parameters)
    if assignment:
      # Display solution.
      # Solution cost.
      print("Total distance of all routes: " + str(assignment.ObjectiveValue()) + "\n")

      for vehicle_nbr in range(num_vehicles):
        index = routing.Start(vehicle_nbr)
        index_next = assignment.Value(routing.NextVar(index))
        route = ''
        route_dist = 0
        route_demand = 0

        while not routing.IsEnd(index_next):
          node_index = routing.IndexToNode(index)
          node_index_next = routing.IndexToNode(index_next)
          route += str(node_index) + " -> "
          # Add the distance to the next node.
          route_dist += dist_callback(node_index, node_index_next)
          # Add demand.
          route_demand += demands[node_index_next]
          index = index_next
          index_next = assignment.Value(routing.NextVar(index))

        node_index = routing.IndexToNode(index)
        node_index_next = routing.IndexToNode(index_next)
        route += str(node_index) + " -> " + str(node_index_next)
        route_dist += dist_callback(node_index, node_index_next)
        print("Route for vehicle " + str(vehicle_nbr) + ":\n\n" + route + "\n")
        print("Distance of route " + str(vehicle_nbr) + ": " + str(route_dist))
        print("Demand met by vehicle " + str(vehicle_nbr) + ": " + str(route_demand) + "\n")
    else:
      print('No solution found.')
  else:
    print('Specify an instance greater than 0.')

def create_data_array():

  nodelist = pd.read_csv('https://gist.githubusercontent.com/brooksandrew/f989e10af17fb4c85b11409fea47895b/raw/a3a8da0fa5b094f1ca9d82e1642b384889ae16e8/nodelist_sleeping_giant.csv')

  locations = [[e.X, e.Y] for e in nodelist.itertuples()]
  demands = [1] + [1] + [1] * 75

  data = [locations, demands]
  return data

if __name__ == '__main__':
  main()

这会输出一个解决方案:

Total distance of all routes: 12900

Route for vehicle 0:

0 -> 56 -> 40 -> 53 -> 63 -> 55 -> 14 -> 15 -> 12 -> 26 -> 34 -> 69 -> 36 -> 1 -> 64 -> 27 -> 48 -> 70 -> 47 -> 13 -> 10 -> 61 -> 45 -> 42 -> 60 -> 9 -> 8 -> 21 -> 43 -> 44 -> 3 -> 18 -> 58 -> 38 -> 28 -> 49 -> 32 -> 35 -> 50 -> 74 -> 46 -> 54 -> 76 -> 71 -> 65 -> 29 -> 16 -> 17 -> 22 -> 59 -> 7 -> 24 -> 31 -> 37 -> 67 -> 73 -> 41 -> 52 -> 75 -> 72 -> 20 -> 2 -> 39 -> 57 -> 23 -> 66 -> 5 -> 6 -> 30 -> 33 -> 68 -> 19 -> 25 -> 4 -> 11 -> 62 -> 51 -> 0

Distance of route 0: 12900
Demand met by vehicle 0: 76

因此,您可以看到我们正在不能在两者之间旅行的节点之间旅行。

3 个答案:

答案 0 :(得分:2)

我认为Capacitized Vehicular Routing Problem依赖于一个完整的图表,你在这里没有。没问题 - 我们可以用原始图形替换原始图形,其中任意两个节点之间的距离是原始图形上它们之间的最短距离。这很容易(O(n^3)) - 不那么容易)由Floyd-Warshall计算,我将在networkx中进行计算,因为我对它更熟悉。如果可用,请替换为ortools版本。现在CreateDistanceCallback似乎是

class CreateDistanceCallback(object):
  '''Create callback to calculate distances between points.'''

  def __init__(self, G):
      '''Calculate shortest paths using Floyd-Warshall'''
      self.paths = nx.floyd_warshall(G)

  def Distance(self, from_node, to_node):
      return self.paths[from_node][to_node]

需要nx.Graph()个对象,因此请将<{1}}替换为

create_data_graph()

def create_data_graph(): edgelist = pd.read_csv('https://gist.githubusercontent.com/brooksandrew/e570c38bcc72a8d102422f2af836513b/raw/89c76b2563dbc0e88384719a35cba0dfc04cd522/edgelist_sleeping_giant.csv') nodelist = pd.read_csv('https://gist.githubusercontent.com/brooksandrew/f989e10af17fb4c85b11409fea47895b/raw/a3a8da0fa5b094f1ca9d82e1642b384889ae16e8/nodelist_sleeping_giant.csv') node_dict = dict(zip(nodelist['id'], list(range(nodelist.shape[0])))) G = nx.Graph() for i, elrow in edgelist.iterrows(): G.add_edge(node_dict[elrow.node1], node_dict[elrow.node2], weight=elrow.distance) locations = [[e.X, e.Y] for e in nodelist.itertuples()] demands = [1] + [1] + [1] * 75 return G, locations, demands 中的第一行现在已根据新输入数据进行调整

main()

并用

替换 # Create the data. G, locations, demands = create_data_graph() 对象的实例化
CreateDistanceCallback

当我跑步时,我得到输出:

    dist_between_locations = CreateDistanceCallback(G)

你能检查一下吗?

要按要求打印“扩展路径”,请重写Total distance of all routes: 2 Route for vehicle 0: 0 -> 68 -> 75 -> 73 -> 76 -> 74 -> 71 -> 1 -> 8 -> 3 -> 9 -> 10 -> 12 -> 13 -> 14 -> 16 -> 15 -> 18 -> 21 -> 17 -> 22 -> 19 -> 20 -> 2 -> 4 -> 5 -> 6 -> 11 -> 72 -> 67 -> 69 -> 70 -> 65 -> 64 -> 63 -> 61 -> 60 -> 58 -> 50 -> 55 -> 59 -> 62 -> 66 -> 52 -> 39 -> 37 -> 56 -> 51 -> 57 -> 25 -> 33 -> 41 -> 31 -> 36 -> 54 -> 49 -> 48 -> 53 -> 47 -> 46 -> 45 -> 44 -> 43 -> 42 -> 35 -> 38 -> 34 -> 32 -> 29 -> 28 -> 27 -> 26 -> 24 -> 40 -> 7 -> 30 -> 23 -> 0 Distance of route 0: 49.440000000000005 Demand met by vehicle 0: 76 的最后一部分:

main()

答案 1 :(得分:0)

(乍一看)这看起来像一些已经实现的专用解算器的包装器。也许改变内部结构并不容易。

但是一个简单的解决方法:只需更改distance-callback,这样就可以为禁止边缘提供非常高的成本。

这种方法需要调整,并不完美。但这是一种解决方法。

如果这是有道理的,取决于你的任务,看起来非常非正式地描述。

答案 2 :(得分:0)

我想为Charles Pehlivanian提出另一种解决方案。要禁止某些节点对之间的转换,您可以尝试使它们过于昂贵,以使它们出现在赋值中。首先计算访问所有站点的最昂贵路线的上限。例如,所有边缘之间的最大距离是停靠次数的时间:

const sessionPaths = ['/path1', '/path2', ...];
app.use(sessionPaths, session(sessionOptions));
app.use(sessionPaths, passport.initialize());
app.use(sessionPaths, passport.session());
app.use(sessionPaths, csrf());

然后将矩阵中禁止转换的值设置为该值:

penalty = len(locations) * max(distance(x,y) for x,y in edges)