Я работаю над проблемой планирования, когда у меня есть N сотрудников, которые едут на автомобилях, чтобы посетить дома M клиентов с согласованными датами.
Проблема, которую я вижу, заключается в том, что общее время для каждого транспортного средства включает время ожидания от станции до момента выхода, чтобы прибыть к первой согласованной дате в первом месте для посещения. Например, если временное окно для рабочего дня транспортного средства 0 равно (8:00, 19:00), а первая дата — 3 (9:15 утра):
Route for vehicle 0:
0 Time(09:10, 09:10) -> 3 Time(09:15, 09:15) -> 7 Time(12:00, 12:00) -> 11 Time(16:35, 16:35) -> 0 Time(17:31, 17:31)
Time of the route: 571min
Мое ожидаемое время маршрута будет 501 минута, я не хочу суммировать 70 минут ожидания с 8 утра до 9:10 утра. Как я мог смириться с этим?
Заранее спасибо! Надеюсь, @Mizux может мне помочь
РЕДАКТИРОВАТЬ С ТЕКУЩИМ РЕШЕНИЕМ: отслеживая общее количество минут маршрута и только время в пути, можно сравнивать оба:
# Data
time_matrix=[
[0,350.8,556.1,272.5,401.7,319.4,306.1,521.2,569.4,502.4,722.5,742.1],
[275.4,0,243.5,200.9,404.8,436.4,139.7,279.3,256.8,189.8,409.9,468.6],
[419.6,182.4,0,338,541.9,573.5,283.9,239.4,177.2,73.6,250.2,308.9],
[115.4,245.3,442.8,0,372.4,291.5,289.8,370,446.1,388.2,608.3,614.7],
[391.3,443.9,608.6,410.2,0,292.9,527.5,539.6,615.7,586.8,782.9,747.8],
[248.9,412.8,609.2,351.8,300.1,0,415,478.5,554.6,554.6,774.7,686.7],
[207.7,115.5,320.8,268.1,462.2,378.6,0,356.6,334.1,267.1,487.2,545.9],
[419.5,220.6,199.4,304.1,449.2,507.6,322.1,0,76.1,257.6,306.3,309.9],
[478.8,241.6,135.1,379.5,524.6,583,343.1,75.4,0,208.7,242,245.6],
[346,108.8,134.9,264.4,468.3,499.9,210.3,170.7,148.2,0,309.2,367.9],
[576.8,330.9,229.1,495.2,699.1,730.7,404.2,374.4,299,230.8,0,333.9],
[681.4,444.2,277.6,586.3,706.6,765,545.7,302.8,238.6,335.4,207.8,0]
]
ttw = [(0,660),(0,660),(0,660),(60,75),\
(120,135),(90,105),(180,195),(240,255),(285,300),\
(480,495),(510,525),(510,525)]
services_duration = [0,0,0,90,45,60,30,150,45,30,60,45]
NUM_VEHICLES = 3
MIN_ORDERS_BY_VEHICLE = 3 # minimum 2 orders by vehicle
RELATIVE_START_TIME = 8 # 8AM start work day
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['time_matrix'] = time_matrix
data['time_windows'] = ttw # time windows with concerted dates
data['time_service'] = services_duration # service time for each order
data['num_vehicles'] = NUM_VEHICLES
# we start from initial routes for trying to optimize them
data['initial_routes'] = [
[3,7,11], # order vehicle 0
[4,6,10], # order vehicle 1
[5,8,9] # # order vehicle 2
]
# vehicles starts and ends from/to same location
data['starts'] = [0, 1, 2]
data['ends'] = [0, 1, 2]
data['breaks'] = [(360, 360, 120), (360, 360, 120), (360, 360, 120)]
return data
def print_solution(data, manager, routing, solution):
time_dimension = routing.GetDimensionOrDie('Time')
count_dimension = routing.GetDimensionOrDie('count')
travel_dimension = routing.GetDimensionOrDie('travel')
total_time = 0
driving_time = 0
for vehicle_id in range(data['num_vehicles']):
index = routing.Start(vehicle_id)
plan_output = 'Route for vehicle {}:\n'.format(vehicle_id)
i = 0
while not routing.IsEnd(index):
time_var = time_dimension.CumulVar(index)
travel_var = travel_dimension.CumulVar(index)
if i == 0: # store wait time until first exit moment in order can substract it from total route time later
first_wait_time_depot = solution.Min(time_var)
count_var = count_dimension.CumulVar(index)
plan_output += '{0} Time({1}, {2}) -> '.format(manager.IndexToNode(index),
"{:02d}:{:02d}".format(*divmod((RELATIVE_START_TIME*60)+solution.Min(time_var), 60)),
"{:02d}:{:02d}".format(*divmod((RELATIVE_START_TIME*60)+solution.Max(time_var), 60))
)
index = solution.Value(routing.NextVar(index))
i+=1
time_var = time_dimension.CumulVar(index)
travel_var = travel_dimension.CumulVar(index)
count_var = count_dimension.CumulVar(index)
plan_output += '{0} Time({1}, {2})\n'.format(manager.IndexToNode(index),
"{:02d}:{:02d}".format(*divmod((RELATIVE_START_TIME*60)+solution.Min(time_var), 60)),
"{:02d}:{:02d}".format(*divmod((RELATIVE_START_TIME*60)+solution.Max(time_var), 60)))
print('Orders by vehicle: {}'.format(solution.Min(count_var)-1))
plan_output += 'Time of the route (with waiting and service times): {}min\n'.format(
solution.Min(time_var)-first_wait_time_depot)
plan_output += 'Time of the route (only driving time): {}min\n'.format(
solution.Min(travel_var))
print(plan_output)
total_time += solution.Min(time_var)-first_wait_time_depot
driving_time += solution.Min(travel_var)
print('Total time of all routes: {}min'.format(total_time))
print('Total driving time of all routes: {}min'.format(driving_time))
return {'driving_time': driving_time, 'total_time':total_time}
def main():
"""Entry point of the program."""
data = create_data_model()
# Create the routing index manager.
manager = pywrapcp.RoutingIndexManager(len(data['time_matrix']),
data['num_vehicles'], data['starts'],
data['ends'])
routing = pywrapcp.RoutingModel(manager)
# Create and register a transit callback.
def time_callback(from_index, to_index):
"""Returns the travel time between the two nodes."""
# Convert from routing variable Index to time matrix NodeIndex.
from_node = manager.IndexToNode(from_index)
to_node = manager.IndexToNode(to_index)
return data['time_service'][from_node]+(int(round(data['time_matrix'][from_node][to_node]/60.)))
def travel_callback(from_index, to_index):
"""Returns the travel time between the two nodes."""
# Convert from routing variable Index to time matrix NodeIndex.
from_node = manager.IndexToNode(from_index)
to_node = manager.IndexToNode(to_index)
return int(round(data['time_matrix'][from_node][to_node]/60.))
transit_callback_index = routing.RegisterTransitCallback(time_callback)
travel_callback_index = routing.RegisterTransitCallback(travel_callback)
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
# Add Time dimension.
time = 'Time'
routing.AddDimension(
transit_callback_index,
660, # allow waiting time
660, # maximum time per vehicle
False, # Don't force start cumul to zero. Vehicles don't have to start to exactly start hour
time)
time_dimension = routing.GetDimensionOrDie(time)
# Add only travel time dimension.
routing.AddDimension(
travel_callback_index,
0, # allow waiting time
660, # maximum time per vehicle
True, # Don't force start cumul to zero. Vehicles don't have to start to exactly start hour
'travel')
travel_dimension = routing.GetDimensionOrDie('travel')
# Add time window constraints for each location except depots.
for location_idx, time_window in enumerate(data['time_windows']):
if location_idx in data['starts']:
continue # except depots
index = manager.NodeToIndex(location_idx)
time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])
# Set allowed vehicles for order
routing.SetAllowedVehiclesForIndex([], index) # for the moment, all vehicles can visit any node
# Add COUNT dimension constraint.
count_dimension_name = 'count'
routing.AddConstantDimension(
1, # increment by one every time
len(data['time_matrix'])+1, # make sure the return to depot node can be counted
True, # set count to zero
count_dimension_name)
count_dimension = routing.GetDimensionOrDie(count_dimension_name)
# enable empty route cost
# https://github.com/google/or-tools/issues/2067
# https://github.com/google/or-tools/issues/2161
for vehicle_id in range(data['num_vehicles']):
routing.ConsiderEmptyRouteCostsForVehicle(True, vehicle_id)
# Add time window constraints for each vehicle start node.
for vehicle_id in range(data['num_vehicles']):
# force depot waiting time to zero
time_dimension.SlackVar(routing.Start(vehicle_id)).SetValue(0)
index = routing.Start(vehicle_id)
time_dimension.CumulVar(index).SetRange(data['time_windows'][vehicle_id][0],
data['time_windows'][vehicle_id][1])
# https://activimetrics.com/blog/ortools/counting_dimension/
for vehicle_id in range(data['num_vehicles']):
index_end = routing.End(vehicle_id)
count_dimension.SetCumulVarSoftLowerBound(index_end,
MIN_ORDERS_BY_VEHICLE + 1,
100000)
# set break time for vehicles
node_visit_transit = {}
for n in range(routing.Size()):
if n >= len(data['time_service']):
node_visit_transit[n] = 0
else:
node_visit_transit[n] = 1
break_intervals = {}
for v in range(data['num_vehicles']):
vehicle_break = data['breaks'][v]
break_intervals[v] = [
routing.solver().FixedDurationIntervalVar(vehicle_break[0],
vehicle_break[1],
vehicle_break[2],
False,
'Break for vehicle {}'.format(v))
]
time_dimension.SetBreakIntervalsOfVehicle(
break_intervals[v], v, node_visit_transit
)
# Instantiate route start and end times to produce feasible times.
# to start the later and finish the earliest possible
for i in range(manager.GetNumberOfVehicles()):
routing.AddVariableMaximizedByFinalizer(
time_dimension.CumulVar(routing.Start(i)))
routing.AddVariableMinimizedByFinalizer(
time_dimension.CumulVar(routing.End(i)))
for i in range(manager.GetNumberOfVehicles()):
routing.AddVariableMaximizedByFinalizer(
travel_dimension.CumulVar(routing.Start(i)))
routing.AddVariableMinimizedByFinalizer(
travel_dimension.CumulVar(routing.End(i)))
initial_solution = routing.ReadAssignmentFromRoutes(data['initial_routes'], True)
print("Initial sol:\n")
ini_times = print_solution(data, manager, routing, initial_solution)
# Setting first solution heuristic.
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = (
routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
search_parameters.local_search_metaheuristic = (
routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
search_parameters.time_limit.FromSeconds(100)
solution = routing.SolveFromAssignmentWithParameters(initial_solution, search_parameters)
if solution:
print("\nSolved sol:\n")
end_times = print_solution(data, manager, routing, solution)
print("\nTotal route minutes saved: {} min".format(ini_times['total_time'] - end_times['total_time']))
print("Only driving minutes saved: {} min".format(ini_times['driving_time'] - end_times['driving_time']))
if __name__ == '__main__':
main()
Немногое:
false
, чтобы автомобиль не начинал ровно в 8 утра.time_dimension.SlackVar(routing.Start(vehicle)).SetValue(0)
РЕДАКТИРОВАТЬ:
Возможно, ваше общее время вычисления неверно:
и
1051 - 550 = 501 min
, поэтому кажется, что вычисление общего времени (продолжительности) неверно …Спасибо за ответы! У меня уже была первая точка (не заставляйте cumul start обнуляться по измерению времени), и я реализовал две другие, но общее время не меняется. — person david9ppo; 08.04.2021
Не могли бы вы поделиться кодом? Как вы подсчитали общее время? — person david9ppo; 08.04.2021
Конечно! Я только что разместил свой код. — person david9ppo; 08.04.2021
В методе print_solution я могу сохранить solution.Min (time_var) только на первой итерации цикла while. Позже я могу использовать это время для вычитания его из переменных total_time и plan_output, но я не уверен, что это лучший способ сделать это. Еще один вопрос: могу ли я отслеживать время в пути независимо от обслуживания или времени ожидания, чтобы сравнивать только время в пути между моим первоначальным решением и одним решателем? — person david9ppo; 09.04.2021
@ david9ppo Лучше всего создать еще одно измерение, предназначенное только для путешествий, то есть с обратным вызовом, возвращающим только
travel_time(from, to)
. то есть вы можете иметь любое количество измерений, которое хотите — person david9ppo; 09.04.2021Я только что отредактировал сообщение своим текущим решением, если оно кому-то поможет. Теперь я могу сравнить оба времени в пути: общее (включая время ожидания и обслуживания) и только время вождения (с новым параметром travel_dimension). — person david9ppo; 09.04.2021