Skip to content

Instantly share code, notes, and snippets.

@Mizux
Last active July 4, 2024 14:14
Show Gist options
  • Select an option

  • Save Mizux/36a87040af87eb9c5324d49224f709ac to your computer and use it in GitHub Desktop.

Select an option

Save Mizux/36a87040af87eb9c5324d49224f709ac to your computer and use it in GitHub Desktop.

Revisions

  1. Mizux revised this gist Jul 4, 2024. 2 changed files with 0 additions and 0 deletions.
    File renamed without changes.
    File renamed without changes.
  2. Mizux created this gist Jul 4, 2024.
    277 changes: 277 additions & 0 deletions cvrptw_pd.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,277 @@
    #!/usr/bin/env python3

    """Development In Progress
    CVRPTW.
    or-tools == 9.9.3963
    python == 3.11.7
    Number of Regular Nodes = 16,
    Depot Nodes = 1
    Number of Nodes Duplicated = 16(Same as Number of Regular Nodes)
    Total Nodes = 16+16+1 = 33
    Demnds = 0 for Depot, 1 for Regular Nodes and -1 for Duplicate Nodes.
    Time Windwos = Applied Business Logic for Regular Nodes and Whole Day for Duplicate Nodes.
    pickup_dropoff = Regular Node Index, len(data)+Regular Node Index
    """

    import numpy as np
    from ortools.routing import enums_pb2
    from ortools.routing import pywraprouting

    FirstSolutionStrategy = enums_pb2.FirstSolutionStrategy
    LocalSearchMetaheuristic = enums_pb2.LocalSearchMetaheuristic
    RoutingSearchStatus = enums_pb2.RoutingSearchStatus


    def create_data_model():
    data = {}
    num_depots = 1
    patient_len = 16
    vehicle_capacity = 3
    data["num_Locations"] = 2 * patient_len + num_depots

    # fmt:off
    # data['demands'] = [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3]
    # for the Demands Tried adding Vehicle_Capacity(-3) instead of -1, Vehicle Didnt Visit Any Nodes. But adding -1 yielded result
    data['demands']=[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]

    data['time_windows']=[(0, 86400), (23400, 25200), (23400, 25200), (23400, 25200), (23400, 25200), (23400, 25200), (28800, 28800), (28800, 28800), (32400, 32400), (32400, 32400), (36000, 36000), (36000, 36000), (36000, 36000), (39600, 39600), (39600, 39600), (43200, 43200), (43200, 43200), (0, 86400), (0, 86400), (0, 86400), (0, 86400), (0, 86400), (0, 86400), (0, 86400), (0, 86400), (0, 86400), (0, 86400), (0, 86400), (0, 86400), (0, 86400), (0, 86400), (0, 86400), (0, 86400)]
    data['num_vehicles']= 40
    data['vehicle_capacity'] = [vehicle_capacity for _ in range(data['num_vehicles'])]
    data['depot']=0
    data['pickup_dropoff']=[(1, 17), (2, 18), (3, 19), (4, 20), (5, 21), (6, 22), (7, 23), (8, 24), (9, 25), (10, 26), (11, 27), (12, 28), (13, 29), (14, 30), (15, 31), (16, 32)]
    data['duplicate_nodes']=patient_len+1 # INdex of Start of Duplicate Node
    data['distance_matrix'] = [
    [ 0 ,18432 , 8219 , 7741 ,21036 ,11099 ,28697 ,21912 ,31279 ,39777 ,35803 , 8667 ,71688 ,12592 ,11929 ,21985 ,18432 ],
    [17866 , 0 ,10701 ,16151 ,21228 ,29756 ,25445 ,22105 ,31472 ,36525 ,48423 ,10499 ,68436 ,14823 ,14225 ,12439 , 0 ],
    [ 8084 ,10986 , 0 , 6369 ,13032 ,19974 ,21474 ,13909 ,23623 ,32555 ,38641 , 1221 ,64465 , 4799 , 4140 ,14762 ,10986 ],
    [ 7268 ,16304 , 6090 , 0 ,18907 ,19158 ,26568 ,19783 ,31148 ,37649 ,32296 , 6538 ,69559 ,10463 , 9800 ,19856 ,16304 ],
    [19622 ,21970 ,12496 ,17908 , 0 ,31512 ,13404 , 4291 ,13905 ,26053 ,37574 ,11758 ,56084 , 8945 , 9289 ,18362 ,21970 ],
    [11208 ,30040 ,19827 ,19349 ,32644 , 0 ,40305 ,33520 ,42887 ,51385 ,47721 ,20275 ,83296 ,24200 ,23536 ,33593 ,30040 ],
    [28383 ,26104 ,21364 ,26669 ,15291 ,40273 , 0 ,13247 ,15930 ,14162 ,46324 ,20465 ,46072 ,21785 ,22129 ,15111 ,26104 ],
    [20790 ,23138 ,13664 ,19075 , 4208 ,32680 ,11417 , 0 ,10501 ,24831 ,37917 ,12926 ,54862 ,10113 ,10457 ,20970 ,23138 ],
    [31108 ,33154 ,23981 ,29142 ,14263 ,42997 ,17630 ,11016 , 0 ,24328 ,31712 ,23243 ,50310 ,19040 ,19699 ,28100 ,33154 ],
    [39053 ,36774 ,32034 ,37339 ,26278 ,50943 ,12623 ,24234 ,25334 , 0 ,69610 ,31135 ,32389 ,36012 ,35353 ,25781 ,36774 ],
    [37147 ,49357 ,39144 ,31154 ,39309 ,45936 ,47054 ,38162 ,32604 ,57812 , 0 ,39592 ,87181 ,43516 ,42853 ,52910 ,49357 ],
    [ 8344 ,10812 , 1179 , 6629 ,12035 ,20234 ,20257 ,12911 ,22229 ,31337 ,38901 , 0 ,63248 , 5003 , 4344 ,13545 ,10812 ],
    [70605 ,68325 ,63585 ,68890 ,54839 ,82495 ,43646 ,52795 ,49929 ,26387 ,80816 ,62687 , 0 ,61333 ,61677 ,57333 ,68325 ],
    [12370 ,14944 , 4734 ,10655 , 8988 ,24259 ,24531 , 9864 ,18076 ,35611 ,42926 , 4653 ,62123 , 0 , 658 ,17819 ,14944 ],
    [11711 ,14285 , 4075 , 9996 , 9332 ,23600 ,23872 ,10208 ,18734 ,34952 ,42267 , 3994 ,62467 , 658 , 0 ,17160 ,14285 ],
    [21792 ,12856 ,14773 ,20077 ,19004 ,33682 ,15385 ,19880 ,26223 ,26465 ,52349 ,13874 ,58376 ,18750 ,18091 , 0 ,12856 ],
    [17866 , 0 ,10701 ,16151 ,21228 ,29756 ,25445 ,22105 ,31472 ,36525 ,48423 ,10499 ,68436 ,14823 ,14225 ,12439 , 0 ],
    ]

    data['duration_matrix']=[
    [ 0 , 1468 , 564 , 528 , 1535 , 864 , 1797 , 1630 , 2442 , 2552 , 1737 , 614 , 3806 , 916 , 870 , 1501 , 1468 ],
    [1391 , 0 , 939 , 1229 , 1685 , 2157 , 1949 , 1781 , 2592 , 2704 , 2610 , 921 , 3958 , 1194 , 1167 , 1167 , 0 ],
    [ 612 , 1026 , 0 , 450 , 1165 , 1378 , 1475 , 1260 , 2017 , 2230 , 1832 , 173 , 3485 , 445 , 388 , 1180 , 1026 ],
    [ 515 , 1306 , 402 , 0 , 1373 , 1282 , 1635 , 1468 , 2238 , 2390 , 1506 , 452 , 3644 , 755 , 708 , 1339 , 1306 ],
    [1488 , 1699 , 1077 , 1327 , 0 , 2255 , 1216 , 450 , 1297 , 1998 , 2616 , 968 , 2941 , 895 , 929 , 1470 , 1699 ],
    [ 801 , 2204 , 1301 , 1265 , 2272 , 0 , 2533 , 2367 , 3178 , 3288 , 2511 , 1351 , 4542 , 1653 , 1607 , 2238 , 2204 ],
    [1805 , 1968 , 1438 , 1644 , 1069 , 2572 , 0 , 958 , 1318 , 1113 , 2858 , 1318 , 2368 , 1572 , 1607 , 1051 , 1968 ],
    [1620 , 1831 , 1208 , 1458 , 445 , 2386 , 1062 , 0 , 1060 , 1879 , 2512 , 1100 , 2821 , 1026 , 1061 , 1557 , 1831 ],
    [2427 , 2680 , 2015 , 2189 , 1345 , 3194 , 1433 , 1097 , 0 , 2052 , 2065 , 1907 , 2937 , 1802 , 1860 , 2218 , 2680 ],
    [2549 , 2712 , 2182 , 2387 , 1980 , 3315 , 1130 , 1869 , 1986 , 0 , 3769 , 2062 , 1751 , 2421 , 2363 , 1794 , 2712 ],
    [1906 , 2784 , 1880 , 1494 , 2655 , 2516 , 3025 , 2534 , 2047 , 3862 , 0 , 1930 , 4808 , 2233 , 2186 , 2817 , 2784 ],
    [ 617 , 989 , 165 , 456 , 1059 , 1384 , 1308 , 1154 , 1937 , 2063 , 1837 , 0 , 3317 , 451 , 393 , 1012 , 989 ],
    [3862 , 4024 , 3495 , 3700 , 2895 , 4628 , 2432 , 2784 , 2858 , 1643 , 4725 , 3375 , 0 , 3397 , 3432 , 3107 , 4024 ],
    [ 913 , 1297 , 428 , 751 , 922 , 1679 , 1684 , 1017 , 1752 , 2439 , 2132 , 431 , 3435 , 0 , 57 , 1388 , 1297 ],
    [ 855 , 1240 , 371 , 693 , 957 , 1622 , 1626 , 1052 , 1810 , 2381 , 2075 , 374 , 3470 , 57 , 0 , 1331 , 1240 ],
    [1488 , 1267 , 1120 , 1326 , 1529 , 2254 , 1121 , 1624 , 2266 , 1876 , 2707 , 1000 , 3130 , 1360 , 1302 , 0 , 1267 ],
    [1391 , 0 , 939 , 1229 , 1685 , 2157 , 1949 , 1781 , 2592 , 2704 , 2610 , 921 , 3958 , 1194 , 1167 , 1167 , 0 ],
    ]
    # fmt:off
    assert data['num_Locations']==len(data['time_windows'])
    assert len(data['distance_matrix'])*2-1 == data['num_Locations']
    assert len(data['duration_matrix'])*2-1 == data['num_Locations']
    assert data['num_Locations']==len(data['demands'])
    return data


    def print_solution(manager, routing, solution):
    """Prints solution on console."""
    status = routing.status()
    print(f"Status: {RoutingSearchStatus.Value.Name(status)}")
    if (
    status != RoutingSearchStatus.ROUTING_OPTIMAL
    and status != RoutingSearchStatus.ROUTING_SUCCESS
    ):
    print("No solution found!")
    return
    print(f"Objective: {solution.ObjectiveValue()}")
    # Display dropped nodes.
    dropped_nodes = "Dropped nodes:"
    for node in range(routing.Size()):
    if routing.IsStart(node) or routing.IsEnd(node):
    continue
    if solution.Value(routing.NextVar(node)) == node:
    dropped_nodes += f" {manager.IndexToNode(node)}"
    print(dropped_nodes)
    # Display routes.
    time_dimension = routing.GetDimensionOrDie("Time")
    capacity_dimension = routing.GetDimensionOrDie("Capacity")
    total_distance = 0
    total_time = 0
    total_load = 0
    for vehicle_id in range(manager.GetNumberOfVehicles()):
    nodes_visited = [] # To Keep Track of Node Visited by Vehicle
    index = routing.Start(vehicle_id)
    plan_output = f"Route for vehicle {vehicle_id}:\n"
    route_distance = 0
    nodes_visited.append(manager.IndexToNode(index))
    while not routing.IsEnd(index):
    time_var = time_dimension.CumulVar(index)
    capacity_var = capacity_dimension.CumulVar(index)
    plan_output += (
    f"Node_{manager.IndexToNode(index)}"
    f" {route_distance}m"
    f" TW:[{time_var.Min()},{time_var.Max()}]"
    f" Time({solution.Min(time_var)},{solution.Max(time_var)})"
    f" Load({solution.Value(capacity_var)}/{capacity_var.Max()})"
    " -> "
    )
    previous_index = index
    index = solution.Value(routing.NextVar(index))
    nodes_visited.append(manager.IndexToNode(index))
    route_distance += routing.GetArcCostForVehicle(
    previous_index, index, vehicle_id
    )
    if len(nodes_visited) > 2: # Condition does not prints vehicels that are not assigned
    time_var = time_dimension.CumulVar(index)
    capacity_var = capacity_dimension.CumulVar(index)
    plan_output += (
    f"Node_{manager.IndexToNode(index)}"
    f" {route_distance}m"
    f" Time({solution.Min(time_var)},{solution.Max(time_var)})"
    f" Load({solution.Value(capacity_var)}/{capacity_var.Max()})"
    "\n"
    )
    plan_output += f"Distance of the route: {route_distance}m\n"
    plan_output += f"Time of the route: {solution.Min(time_var)}min\n"
    plan_output += f"Load of the route: {solution.Value(capacity_var)}\n"
    print(plan_output)
    total_distance += route_distance
    total_time += solution.Min(time_var)
    total_load += solution.Value(capacity_var)
    print(nodes_visited)
    print(f"Total distance of all routes: {total_distance}m")
    print(f"Total time of all routes: {total_time}min")
    print(f"Total load of all routes: {total_load}")
    # print(f"Total Distance of all routes: {total_distance}m")


    def main():
    data = create_data_model()
    manager = pywraprouting.RoutingIndexManager(
    data["num_Locations"], data["num_vehicles"], data["depot"]
    )

    routing = pywraprouting.RoutingModel(manager)

    def distance_callback(from_index, to_index):
    from_node = manager.IndexToNode(from_index)
    to_node = manager.IndexToNode(to_index)
    if from_node in range(data["duplicate_nodes"], data["num_Locations"]):
    from_node = 0
    if to_node in range(data["duplicate_nodes"], data["num_Locations"]):
    to_node = 0
    return data["distance_matrix"][from_node][to_node]

    data_callback_index = routing.RegisterTransitCallback(distance_callback)
    routing.SetArcCostEvaluatorOfAllVehicles(data_callback_index)

    def demand_callback(from_index):
    from_node = manager.IndexToNode(from_index)
    return data["demands"][from_node]

    demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback)

    capacity = "Capacity"
    routing.AddDimensionWithVehicleCapacity(
    demand_callback_index, 0, data["vehicle_capacity"], True, capacity
    )
    capacity_dimension = routing.GetDimensionOrDie(capacity)

    def time_callback(from_index, to_index):
    from_node = manager.IndexToNode(from_index)
    to_node = manager.IndexToNode(to_index)
    if from_node in range(data["duplicate_nodes"], data["num_Locations"]):
    from_node = 0
    if to_node in range(data["duplicate_nodes"], data["num_Locations"]):
    to_node = 0
    return data["duration_matrix"][from_node][to_node]

    time_callback_index = routing.RegisterTransitCallback(time_callback)

    time_dim = "Time"
    routing.AddDimension(
    time_callback_index,
    300_000,
    100_000_000_000, # Some Big Number so that Model should not limit itself.
    False,
    time_dim,
    )
    time_dimension = routing.GetDimensionOrDie(time_dim)

    for location_idx, time_window in enumerate(data["time_windows"]):
    if location_idx == 0:
    continue
    index = manager.NodeToIndex(location_idx)

    time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])
    routing.AddToAssignment(time_dimension.SlackVar(index))

    for vehicle_id in range(data["num_vehicles"]):
    index = routing.Start(vehicle_id)
    time_dimension.CumulVar(index).SetRange(
    data["time_windows"][0][0], data["time_windows"][0][1]
    )
    routing.AddToAssignment(time_dimension.SlackVar(index))
    for i in range(data["num_vehicles"]):
    routing.AddVariableMinimizedByFinalizer(
    time_dimension.CumulVar(routing.Start(i))
    )
    routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.End(i)))

    for request in data["pickup_dropoff"]:
    pickup_index = manager.NodeToIndex(request[0])
    delivery_index = manager.NodeToIndex(request[1])

    routing.solver().Add(
    routing.VehicleVar(pickup_index) == routing.VehicleVar(delivery_index)
    )
    routing.solver().Add(
    time_dimension.CumulVar(pickup_index)
    <= time_dimension.CumulVar(delivery_index)
    )

    # Not Sure about whether this is right or wrong. Added Here Baesd Upon the issue #685
    min_dur = time_callback(pickup_index, delivery_index)
    max_dur = int(1.3 * min_dur)
    dur_expr = time_dimension.CumulVar(delivery_index) - time_dimension.CumulVar(
    index
    )
    routing.solver().Add(dur_expr <= max_dur)
    # Consraint End-----------

    routing.AddPickupAndDelivery(pickup_index, delivery_index)
    routing.AddDisjunction([pickup_index, delivery_index], 20_000, 2)

    search_parameters = pywraprouting.DefaultRoutingSearchParameters()
    search_parameters.first_solution_strategy = (
    FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION
    )
    search_parameters.local_search_metaheuristic = (
    LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH
    )
    search_parameters.time_limit.FromSeconds(3)
    # search_parameters.log_search = True

    solution = routing.SolveWithParameters(search_parameters)
    print_solution(manager, routing, solution)


    if __name__ == "__main__":
    main()
    175 changes: 175 additions & 0 deletions diff
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,175 @@
    ```diff
    %diff -u --color main.py cvrptw_pd.py
    --- main.py 2024-07-04 16:11:07.524848863 +0200
    +++ cvrptw_pd.py 2024-07-04 16:10:16.518365568 +0200
    @@ -17,8 +17,12 @@
    """

    import numpy as np
    -from ortools.constraint_solver import routing_enums_pb2
    -from ortools.constraint_solver import pywrapcp
    +from ortools.routing import enums_pb2
    +from ortools.routing import pywraprouting
    +
    +FirstSolutionStrategy = enums_pb2.FirstSolutionStrategy
    +LocalSearchMetaheuristic = enums_pb2.LocalSearchMetaheuristic
    +RoutingSearchStatus = enums_pb2.RoutingSearchStatus


    def create_data_model():
    @@ -86,47 +90,89 @@
    return data


    -def print_solution(data, manager, routing, solution):
    +def print_solution(manager, routing, solution):
    """Prints solution on console."""
    + status = routing.status()
    + print(f"Status: {RoutingSearchStatus.Value.Name(status)}")
    + if (
    + status != RoutingSearchStatus.ROUTING_OPTIMAL
    + and status != RoutingSearchStatus.ROUTING_SUCCESS
    + ):
    + print("No solution found!")
    + return
    print(f"Objective: {solution.ObjectiveValue()}")
    + # Display dropped nodes.
    + dropped_nodes = "Dropped nodes:"
    + for node in range(routing.Size()):
    + if routing.IsStart(node) or routing.IsEnd(node):
    + continue
    + if solution.Value(routing.NextVar(node)) == node:
    + dropped_nodes += f" {manager.IndexToNode(node)}"
    + print(dropped_nodes)
    + # Display routes.
    + time_dimension = routing.GetDimensionOrDie("Time")
    + capacity_dimension = routing.GetDimensionOrDie("Capacity")
    total_distance = 0
    - for vehicle_id in range(data["num_vehicles"]):
    - nodes_visited = [] # To Keep Track of Vehicles Visited by Vehicle
    + total_time = 0
    + total_load = 0
    + for vehicle_id in range(manager.GetNumberOfVehicles()):
    + nodes_visited = [] # To Keep Track of Node Visited by Vehicle
    index = routing.Start(vehicle_id)
    plan_output = f"Route for vehicle {vehicle_id}:\n"
    route_distance = 0
    nodes_visited.append(manager.IndexToNode(index))
    while not routing.IsEnd(index):
    - node = manager.IndexToNode(index)
    - plan_output += f" {node} -> "
    + time_var = time_dimension.CumulVar(index)
    + capacity_var = capacity_dimension.CumulVar(index)
    + plan_output += (
    + f"Node_{manager.IndexToNode(index)}"
    + f" {route_distance}m"
    + f" TW:[{time_var.Min()},{time_var.Max()}]"
    + f" Time({solution.Min(time_var)},{solution.Max(time_var)})"
    + f" Load({solution.Value(capacity_var)}/{capacity_var.Max()})"
    + " -> "
    + )
    previous_index = index
    index = solution.Value(routing.NextVar(index))
    nodes_visited.append(manager.IndexToNode(index))
    route_distance += routing.GetArcCostForVehicle(
    previous_index, index, vehicle_id
    )
    - if (
    - len(nodes_visited) > 2
    - ): # Condition does not prints vehicels that are not assigned
    - plan_output += f"{manager.IndexToNode(index)}\n"
    + if len(nodes_visited) > 2: # Condition does not prints vehicels that are not assigned
    + time_var = time_dimension.CumulVar(index)
    + capacity_var = capacity_dimension.CumulVar(index)
    + plan_output += (
    + f"Node_{manager.IndexToNode(index)}"
    + f" {route_distance}m"
    + f" Time({solution.Min(time_var)},{solution.Max(time_var)})"
    + f" Load({solution.Value(capacity_var)}/{capacity_var.Max()})"
    + "\n"
    + )
    plan_output += f"Distance of the route: {route_distance}m\n"
    + plan_output += f"Time of the route: {solution.Min(time_var)}min\n"
    + plan_output += f"Load of the route: {solution.Value(capacity_var)}\n"
    print(plan_output)
    total_distance += route_distance
    + total_time += solution.Min(time_var)
    + total_load += solution.Value(capacity_var)
    print(nodes_visited)
    + print(f"Total distance of all routes: {total_distance}m")
    + print(f"Total time of all routes: {total_time}min")
    + print(f"Total load of all routes: {total_load}")
    # print(f"Total Distance of all routes: {total_distance}m")


    def main():
    data = create_data_model()
    - manager = pywrapcp.RoutingIndexManager(
    + manager = pywraprouting.RoutingIndexManager(
    data["num_Locations"], data["num_vehicles"], data["depot"]
    )

    - routing = pywrapcp.RoutingModel(manager)
    + routing = pywraprouting.RoutingModel(manager)

    def distance_callback(from_index, to_index):
    from_node = manager.IndexToNode(from_index)
    - to_node = manager.IndexToNode(to_node)
    + to_node = manager.IndexToNode(to_index)
    if from_node in range(data["duplicate_nodes"], data["num_Locations"]):
    from_node = 0
    if to_node in range(data["duplicate_nodes"], data["num_Locations"]):
    @@ -148,9 +194,6 @@
    )
    capacity_dimension = routing.GetDimensionOrDie(capacity)

    - for node in range(data["num_Locations"]):
    - routing.AddDisjunction([manager.NodeToIndex(node)], 100)
    -
    def time_callback(from_index, to_index):
    from_node = manager.IndexToNode(from_index)
    to_node = manager.IndexToNode(to_index)
    @@ -165,8 +208,8 @@
    time_dim = "Time"
    routing.AddDimension(
    time_callback_index,
    - 300,
    - 100000000000, # Some Big Number so that Model should not limit itself.
    + 300_000,
    + 100_000_000_000, # Some Big Number so that Model should not limit itself.
    False,
    time_dim,
    )
    @@ -214,24 +257,20 @@
    # Consraint End-----------

    routing.AddPickupAndDelivery(pickup_index, delivery_index)
    - search_parameters = pywrapcp.DefaultRoutingSearchParameters()
    + routing.AddDisjunction([pickup_index, delivery_index], 20_000, 2)
    +
    + search_parameters = pywraprouting.DefaultRoutingSearchParameters()
    search_parameters.first_solution_strategy = (
    - routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION
    + FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION
    )
    search_parameters.local_search_metaheuristic = (
    - routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH
    + LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH
    )
    - search_parameters.time_limit.seconds = 30
    + search_parameters.time_limit.FromSeconds(3)
    # search_parameters.log_search = True

    solution = routing.SolveWithParameters(search_parameters)
    -
    - if solution:
    - print("Solution Found")
    - print_solution(data, manager, routing, solution)
    -
    - else:
    - print("NO SOLUTION")
    + print_solution(manager, routing, solution)


    if __name__ == "__main__":
    ```
    23 changes: 23 additions & 0 deletions output
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,23 @@
    ```sh
    python /tmp/cvrptw_pd.py
    Status: ROUTING_SUCCESS
    Objective: 268314
    Dropped nodes: 1 5 6 8 9 10 12 15 16 17 21 22 24 25 26 28 31 32
    Route for vehicle 0:
    Node_0 0m TW:[0,86400] Time(0,0) Load(0/0) -> Node_3 7741m TW:[23400,25200] Time(23400,24121) Load(0/3) -> Node_19 15009m TW:[0,86400] Time(23915,24636) Load(1/3) -> Node_2 23228m TW:[23400,25200] Time(24479,25200) Load(0/3) -> Node_11 24449m TW:[36000,36000] Time(36000,36000) Load(1/3) -> Node_14 28793m TW:[39600,39600] Time(39600,39600) Load(2/3) -> Node_18 40504m TW:[0,86400] Time(40455,40455) Load(3/3) -> Node_27 40504m TW:[0,86400] Time(40455,40455) Load(2/3) -> Node_30 40504m TW:[0,86400] Time(40455,40455) Load(1/3) -> Node_0 40504m Time(40455,40455) Load(0/3)
    Distance of the route: 40504m
    Time of the route: 40455min
    Load of the route: 0

    [0, 3, 19, 2, 11, 14, 18, 27, 30, 0]
    Route for vehicle 3:
    Node_0 0m TW:[0,86400] Time(0,0) Load(0/0) -> Node_4 21036m TW:[23400,25200] Time(23400,25200) Load(0/3) -> Node_7 25327m TW:[28800,28800] Time(28800,28800) Load(1/3) -> Node_13 35440m TW:[39600,39600] Time(39600,39600) Load(2/3) -> Node_23 47810m TW:[0,86400] Time(40513,40513) Load(3/3) -> Node_20 47810m TW:[0,86400] Time(40513,40513) Load(2/3) -> Node_29 47810m TW:[0,86400] Time(40513,40513) Load(1/3) -> Node_0 47810m Time(40513,40513) Load(0/3)
    Distance of the route: 47810m
    Time of the route: 40513min
    Load of the route: 0

    [0, 4, 7, 13, 23, 20, 29, 0]
    Total distance of all routes: 88314m
    Total time of all routes: 80968min
    Total load of all routes: 0
    ```