or-tools ルーティングの背後にある制約プログラミングをよりよく理解するために、デポと 2 つのルートを許可するように構成された他の 4 つのノードのおもちゃの例を作成しました。
これは、車両がデポから に移動し、0
次に1
ピッキング2
またはのいずれかを選択してデポに移動し、デポに戻るという考え方です。車両は緑または赤のパスを選択します。私の実際の問題はより複雑で、複数の車両がありますが、同様の制約があります。3
4
0
この例では、コストのユークリッド距離関数を作成しました。
class Distances:
def __init__(self):
self.locations = [
[-1, 0], # source
[ 0, -1], # waypoint 1
[ 0, 1], # waypoint 2
[ 1, 0], # destination
]
def __len__(self):
return len(self.locations) + 1
def dist(self, x, y):
return int(10000 * math.sqrt(
(x[0] - y[0]) ** 2 +
(x[1] - y[1]) ** 2))
def __call__(self, i, j):
if i == 0 and j == 0:
return 0
if j == 0 or i == 0:
return 1 # very small distance between depot and non-depot, simulating 0
return self.dist(self.locations[i - 1], self.locations[j - 1])
distance = Distances()
そして、次数を制約する l0 距離関数:
# l0-distance to add order constraints
class Order:
def __call__(self, i, j):
return 0 if i == j else 1
order = Order()
次に、モデルを作成し、これを解決しようとします。
search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()
search_parameters.first_solution_strategy = (
routing_enums_pb2.FirstSolutionStrategy.ALL_UNPERFORMED)
search_parameters.local_search_metaheuristic = routing_enums_pb2.LocalSearchMetaheuristic.SIMULATED_ANNEALING
search_parameters.time_limit_ms = 3000
routing = pywrapcp.RoutingModel(len(distance), 1)
routing.SetArcCostEvaluatorOfAllVehicles(distance)
routing.SetDepot(0)
solver = routing.solver()
routing.AddDimension(order, int(1e18), int(1e18), True, "order")
# Since `ALL_UNPERFORMED` is used, each node must be allowed inactive
order_dimension = routing.GetDimensionOrDie("order")
routing.AddDisjunction([1], int(1e10))
routing.AddDisjunction([2, 3], int(1e10))
routing.AddDisjunction([4], int(1e10))
solver.AddConstraint(order_dimension.CumulVar(1) <= order_dimension.CumulVar(2))
solver.AddConstraint(order_dimension.CumulVar(1) <= order_dimension.CumulVar(3))
solver.AddConstraint(order_dimension.CumulVar(2) <= order_dimension.CumulVar(4))
solver.AddConstraint(order_dimension.CumulVar(3) <= order_dimension.CumulVar(4))
# routing.AddPickupAndDelivery(1, 2)
# routing.AddPickupAndDelivery(1, 3)
# routing.AddPickupAndDelivery(2, 4)
# routing.AddPickupAndDelivery(3, 4)
routing.CloseModelWithParameters(search_parameters)
assignment = routing.SolveWithParameters(search_parameters)
if assignment is not None:
print('cost: ' + str(assignment.ObjectiveValue()))
route = []
index = routing.Start(0)
while not routing.IsEnd(index):
route.append(routing.IndexToNode(index))
index = assignment.Value(routing.NextVar(index))
for node in route:
print(' - {:2d}'.format(node))
else:
print('nothing found')
したがって[1]
、 とは、最初のソリューションが機能[4]
することを可能にする選言であり、緑または赤のパスのいずれかを選択する必要があることを示す選言です。ALL_UNPERFORMED
[2, 3]
これらの選言により、ソルバーは解決策を見つけますが、それを追加して2
after3
と1
beforeにアクセスする必要がある場合4
、ソルバーはアクセスしない2
か3
、まったくアクセスしません。これはなぜですか?の分離ペナルティを0 -> 1 -> 2/3 -> 4 -> 0
回避して、より最適なルートをソルバーが見つけられないのはなぜですか?int(1e10)
[2,3]
編集:
それらを削除してに追加することにより、順序制約をソフトに制約しますDistance.__call__
。
if (i == 2 or j == 1) or (i == 3 or j == 1) or (i == 4 or j == 2) or (i == 4 or j == 3):
return int(1e10)
許可されていない注文にペナルティを課すには、ルートが発生し0 -> 2 -> 1 -> 4 -> 0
ます。では、 and を明示的に有効にしている場合でも、 or-tools が1
andを交換しないのはなぜだろうか。2
use_swap_active
use_relocate_neighbors
search_parameters.local_search_operators
注:失敗した理由は次のとおりです。
if (i == 2 and j == 1) or (i == 3 and j == 1) or (i == 4 and j == 2) or (i == 4 and j == 3):
return int(1e10)
結論: 検索空間は小さく、より良い解はuse_relocate_neighbors
返された解の近傍にありますが、or-tools はそれを見つけられません。なんで?
すべてのコード:
import pandas
import os.path
import numpy
import math
from ortools.constraint_solver import pywrapcp
from ortools.constraint_solver import routing_enums_pb2
class Distances:
def __init__(self):
self.locations = [
[-1, 0], # source
[ 0, -1], # waypoint 1
[ 0, 1], # waypoint 2
[ 1, 0], # destination
]
def __len__(self):
return len(self.locations) + 1
def dist(self, x, y):
return int(10000 * math.sqrt(
(x[0] - y[0]) ** 2 +
(x[1] - y[1]) ** 2))
def __call__(self, i, j):
if i == 0 and j == 0:
return 0
if j == 0 or i == 0:
return 1 # very small distance between depot and non-depot, simulating 0
return self.dist(self.locations[i - 1], self.locations[j - 1])
distance = Distances()
# l0-distance to add order constraints
class Order:
def __call__(self, i, j):
return 0 if i == j else 1
order = Order()
search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()
search_parameters.first_solution_strategy = (
routing_enums_pb2.FirstSolutionStrategy.ALL_UNPERFORMED)
search_parameters.local_search_metaheuristic = routing_enums_pb2.LocalSearchMetaheuristic.SIMULATED_ANNEALING
search_parameters.time_limit_ms = 3000
routing = pywrapcp.RoutingModel(len(distance), 1)
routing.SetArcCostEvaluatorOfAllVehicles(distance)
routing.SetDepot(0)
solver = routing.solver()
routing.AddDimension(order, int(1e18), int(1e18), True, "order")
# Since `ALL_UNPERFORMED` is used, each node must be allowed inactive
order_dimension = routing.GetDimensionOrDie("order")
routing.AddDisjunction([1], int(1e10))
routing.AddDisjunction([2, 3], int(1e10))
routing.AddDisjunction([4], int(1e10))
solver.AddConstraint(
(routing.ActiveVar(2) == 0)
or
(order_dimension.CumulVar(1) <= order_dimension.CumulVar(2))
)
solver.AddConstraint(
(routing.ActiveVar(3) == 0)
or
(order_dimension.CumulVar(1) <= order_dimension.CumulVar(3))
)
solver.AddConstraint(
(routing.ActiveVar(2) == 0)
or
(order_dimension.CumulVar(2) <= order_dimension.CumulVar(4))
)
solver.AddConstraint(
(routing.ActiveVar(3) == 0)
or
(order_dimension.CumulVar(3) <= order_dimension.CumulVar(4))
)
# routing.AddPickupAndDelivery(1, 2)
# routing.AddPickupAndDelivery(1, 3)
# routing.AddPickupAndDelivery(2, 4)
# routing.AddPickupAndDelivery(3, 4)
routing.CloseModelWithParameters(search_parameters)
assignment = routing.SolveWithParameters(search_parameters)
if assignment is not None:
print('cost: ' + str(assignment.ObjectiveValue()))
route = []
index = routing.Start(0)
while not routing.IsEnd(index):
route.append(routing.IndexToNode(index))
index = assignment.Value(routing.NextVar(index))
for node in route:
print(' - {:2d}'.format(node))
else:
print('nothing found')