Beispiel #1
0
def test_initial_routes():

    horizon = 20000
    m = reader.load_matrix_from_csv('test/data/matrix.csv')
    odpairs = reader.load_demand_from_csv('test/data/demand.csv')
    d = D.Demand(odpairs, m, horizon)
    m = d.generate_solver_space_matrix(m)
    x_m = d.insert_nodes_for_breaks(m)

    v = V.Vehicles(5, horizon)

    trip_chains_B = IR.initial_routes_2(d, v.vehicles, x_m)
    initial_routes_B = [tcv for tcv in trip_chains_B.values()]
    (assignment, routing, manager) = MR.model_run(d, x_m, v.vehicles, 10000,
                                                  None, initial_routes_B)
    assert assignment
    assert assignment.ObjectiveValue() == 43516

    for vehicle in v.vehicles:
        vehicle_id = vehicle.index
        original_chain = trip_chains_B[vehicle_id]
        index = routing.Start(vehicle_id)
        plan_output = []
        route_time = 0
        while not routing.IsEnd(index):
            # drop 0
            node = manager.IndexToNode(index)
            if node != 0:
                plan_output.append(node)
            index = assignment.Value(routing.NextVar(index))

        # print(original_chain)
        # print(plan_output)
        assert original_chain == plan_output
Beispiel #2
0
def test_vehicles():

    v = V.Vehicles(5,100)

    assert len(v.vehicles) == 5
    for idx in range(0,5):
        veh = v.vehicles[idx]
        assert veh.index == idx
        assert veh.capacity == 1
        assert veh.cost == 1000 # but this is unused at the moment
        assert veh.time_window[0] == 0
        assert veh.time_window[1] == 100 # no more default time window
        assert veh.depot_index == 0 # all vehicles are at depot 0 for now

    v2 = V.Vehicles(horizon=1000,num_vehicles=100) # test set horizon, default of 100 vehicles
    assert len(v2.vehicles) == 100
    for veh in v2.vehicles:
        assert veh.time_window[1] == 1000
Beispiel #3
0
def main():
    """Entry point of the program."""
    parser = argparse.ArgumentParser(
        description=
        'Solve assignment of truck load routing problem, give hours of service rules and a specified list of origins and destinations'
    )
    # parser.add_argument('--resume_file',type=str,dest='resumefile',
    #                     help="resume a failed solver run from this file")
    parser.add_argument('-m,--matrixfile',
                        type=str,
                        dest='matrixfile',
                        help='CSV file for travel matrix (distances)')
    parser.add_argument(
        '-d,--demandfile',
        type=str,
        dest='demand',
        help='CSV file for demand pairs (origin, dest, time windows)')
    parser.add_argument('-o,--vehicleoutput',
                        type=str,
                        dest='vehicle_output',
                        default='vehicle_output.csv',
                        help='CSV file for dumping output')
    parser.add_argument(
        '--demandoutput',
        type=str,
        dest='demand_output',
        default='demand_output.csv',
        help=
        'CSV file for dumping output for demand details (including invalid demands, etc)'
    )
    parser.add_argument(
        '--summaryoutput',
        type=str,
        dest='summary_output',
        help=
        'A file for dumping the human-readable summary output for the assignment'
    )
    parser.add_argument(
        '--speed',
        type=float,
        dest='speed',
        default=55.0,
        help=
        'Average speed, miles per hour.  Default is 55 (miles per hour).  Distance unit should match that of the matrix of distances.  The time part should be per hours'
    )
    parser.add_argument(
        '--maxtime',
        type=int,
        dest='horizon',
        default=10080,
        help='Max time in minutes.  Default is 10080 minutes, which is 7 days.'
    )

    parser.add_argument('-v,--vehicles',
                        type=int,
                        dest='numvehicles',
                        default=100,
                        help='Number of vehicles to create.  Default is 100.')
    parser.add_argument(
        '--pickup_time',
        type=int,
        dest='pickup_time',
        default=15,
        help='Pick up time in minutes.  Default is 15 minutes.')
    parser.add_argument(
        '--dropoff_time',
        type=int,
        dest='dropoff_time',
        default=15,
        help='Drop off time in minutes.  Default is 15 minutes.')

    parser.add_argument(
        '-t, --timelimit',
        type=int,
        dest='timelimit',
        default=5,
        help='Maximum run time for solver, in minutes.  Default is 5 minutes.')

    parser.add_argument(
        '--narrow_destination_timewindows',
        type=bool,
        dest='destination_time_windows',
        default=True,
        help=
        "If true, limit destination node time windows based on travel time from corresponding origin.  If false, destination nodes time windows are 0 to args.horizon.  Default true (limit the time window)."
    )

    parser.add_argument(
        '--drive_dim_start_value',
        type=int,
        dest='drive_dimension_start_value',
        default=1000,
        help=
        "Due to internal solver mechanics, the drive dimension can't go below zero (it gets truncated at zero).  So to get around this, the starting point for the drive time dimension has to be greater than zero.  The default is 1000.  Change it with this variable"
    )

    parser.add_argument('--debug',
                        type=bool,
                        dest='debug',
                        default=False,
                        help="Turn on some print statements.")

    parser.add_argument(
        '--noroutes',
        type=bool,
        dest='noroutes',
        default=False,
        help="Disable generating initial routes.  Not recommended")
    args = parser.parse_args()

    print('read in distance matrix')
    matrix = reader.load_matrix_from_csv(args.matrixfile)
    minutes_matrix = reader.travel_time(args.speed / 60, matrix)

    print('read in demand data')
    odpairs = reader.load_demand_from_csv(args.demand)
    d = D.Demand(odpairs, minutes_matrix, args.horizon)

    # convert nodes to solver space from input map space
    mm = d.generate_solver_space_matrix(minutes_matrix, args.horizon)

    # create dummy nodes for breaks
    expanded_mm = d.insert_nodes_for_breaks(mm)

    # copy new nodes to distance matrix
    expanded_m = reader.travel_time(60 / args.speed, expanded_mm)
    # print('original matrix of',len(matrix.index),'expanded to ',len(expanded_m.index))

    # vehicles:
    vehicles = V.Vehicles(args.numvehicles, args.horizon)

    # Create the routing index manager.

    # number of nodes is now given by the travel time matrix
    # probably should refactor to put time under control of
    # demand class
    num_nodes = len(expanded_mm.index)
    print('After augmenting network with break nodes, solving with ',
          num_nodes, 'nodes')
    #print(d.demand.loc[d.demand.feasible,:])
    print(d.demand.loc[:, [
        'from_node', 'to_node', 'early', 'late', 'pickup_time', 'dropoff_time',
        'round_trip', 'depot_origin', 'earliest_destination', 'feasible',
        'origin', 'destination'
    ]])

    initial_routes = None
    trip_chains = {}

    trip_chainsb = IR.initial_routes_2(d, vehicles.vehicles, expanded_mm)
    initial_routesb = [v for v in trip_chainsb.values()]
    (assB, routing, manager) = MR.model_run(d, expanded_mm, vehicles.vehicles,
                                            args.drive_dimension_start_value,
                                            None, initial_routesb,
                                            args.timelimit)
    # 1201918

    # # set up initial routes by creating a lot of little problems
    # for d_idx in d.demand.index:
    #     # depot to pickup
    #     record = d.demand.loc[d_idx]
    #     if not record.feasible:
    #         continue
    #     nodes = MR.use_nodes(record,d)
    #     (subassignment,minirouting,minimanager) = MR.model_run(d,expanded_mm,[vehicles.vehicles[0]],args.drive_dimension_start_value,nodes)
    #     trip_chains[record.origin] = MR.get_route(0,subassignment,minirouting,minimanager)
    # initial_routes = [v for v in trip_chains.values()]
    # (assA,routingA,managerA) = MR.model_run(d,expanded_mm,vehicles.vehicles,args.drive_dimension_start_value,None,initial_routes)
    # 1201918 ---  same, but slower, so don't do it

    if assB:
        assignment = assB
        ## save the assignment, (Google Protobuf format)
        #save_file_base = os.path.realpath(__file__).split('.')[0]
        #if routing.WriteAssignment(save_file_base + '_assignment.ass'):
        #    print('succesfully wrote assignment to file ' + save_file_base +
        #          '_assignment.ass')

        #print(expanded_mm)
        print('The Objective Value is {0}'.format(assignment.ObjectiveValue()))
        print('details:')

        SO.print_solution(d, expanded_m, expanded_mm, vehicles, manager,
                          routing, assignment, args.horizon,
                          args.drive_dimension_start_value, args)

        SO.csv_output(d, expanded_m, expanded_mm, vehicles, manager, routing,
                      assignment, args.horizon, args.vehicle_output)
        SO.csv_demand_output(d, expanded_m, expanded_mm, vehicles, manager,
                             routing, assignment, args.horizon,
                             args.demand_output)

    else:
        print('assignment failed')
Beispiel #4
0
def main():
    """Entry point of the program."""
    parser = argparse.ArgumentParser(
        description=
        'Solve assignment of truck load routing problem, with specified list of origins and destinations, ignoring hours of service rules'
    )
    # parser.add_argument('--resume_file',type=str,dest='resumefile',
    #                     help="resume a failed solver run from this file")
    parser.add_argument('-m,--matrixfile',
                        type=str,
                        dest='matrixfile',
                        help='CSV file for travel matrix (distances)')
    parser.add_argument(
        '-d,--demandfile',
        type=str,
        dest='demand',
        help='CSV file for demand pairs (origin, dest, time windows)')
    parser.add_argument('-o,--vehicleoutput',
                        type=str,
                        dest='vehicle_output',
                        default='vehicle_output.csv',
                        help='CSV file for dumping output')
    parser.add_argument(
        '--demandoutput',
        type=str,
        dest='demand_output',
        default='demand_output.csv',
        help=
        'CSV file for dumping output for demand details (including invalid demands, etc)'
    )
    parser.add_argument(
        '--summaryoutput',
        type=str,
        dest='summary_output',
        help=
        'A file for dumping the human-readable summary output for the assignment'
    )
    parser.add_argument(
        '--speed',
        type=float,
        dest='speed',
        default=55.0,
        help=
        'Average speed, miles per hour.  Default is 55 (miles per hour).  Distance unit should match that of the matrix of distances.  The time part should be per hours'
    )
    parser.add_argument(
        '--maxtime',
        type=int,
        dest='horizon',
        default=10080,
        help='Max time in minutes.  Default is 10080 minutes, which is 7 days.'
    )

    parser.add_argument('-v,--vehicles',
                        type=int,
                        dest='numvehicles',
                        default=100,
                        help='Number of vehicles to create.  Default is 100.')
    parser.add_argument(
        '--pickup_time',
        type=int,
        dest='pickup_time',
        default=15,
        help='Pick up time in minutes.  Default is 15 minutes.')
    parser.add_argument(
        '--dropoff_time',
        type=int,
        dest='dropoff_time',
        default=15,
        help='Drop off time in minutes.  Default is 15 minutes.')

    parser.add_argument(
        '-t, --timelimit',
        type=int,
        dest='timelimit',
        default=5,
        help='Maximum run time for solver, in minutes.  Default is 5 minutes.')

    parser.add_argument(
        '--initial_routes',
        type=bool,
        dest='initial_routes',
        default=False,
        help=
        "If true, generate initial routes.  Sometimes the solution isn't as good as letting the solver do its thing, but sometimes it is better.  In tests, with all 100 trips active it is slightly better to set initial routes, but with just 50 routes active, the solution is better without initial routes."
    )

    parser.add_argument(
        '--narrow_destination_timewindows',
        type=bool,
        dest='destination_time_windows',
        default=True,
        help=
        "If true, limit destination node time windows based on travel time from corresponding origin.  If false, destination nodes time windows are 0 to args.horizon.  Default true (limit the time window)."
    )

    parser.add_argument('--debug',
                        type=bool,
                        dest='debug',
                        default=False,
                        help="Turn on some print statements.")

    args = parser.parse_args()

    print('read in distance matrix')
    matrix = reader.load_matrix_from_csv(args.matrixfile)
    minutes_matrix = reader.travel_time(args.speed / 60, matrix)

    print('read in demand data')
    odpairs = reader.load_demand_from_csv(args.demand)
    d = D.Demand(odpairs, minutes_matrix, args.horizon, use_breaks=False)

    # convert nodes to solver space from input map space
    expanded_mm = d.generate_solver_space_matrix(minutes_matrix, args.horizon)

    # echo nodes to distance matrix
    expanded_m = reader.travel_time(60 / args.speed, expanded_mm)
    # print('original matrix of',len(matrix.index),'expanded to ',len(expanded_m.index))

    # vehicles:
    vehicles = V.Vehicles(args.numvehicles, args.horizon)

    # number of nodes is now given by the travel time matrix
    # probably should refactor to put time under control of
    # demand class
    num_nodes = len(expanded_mm.index)
    print('Solving with ', num_nodes, 'nodes')
    print(d.demand.loc[:, [
        'from_node', 'to_node', 'early', 'late', 'pickup_time', 'dropoff_time',
        'round_trip', 'depot_origin', 'earliest_destination', 'feasible',
        'origin', 'destination'
    ]])

    initial_routes = None
    trip_chains = {}
    assignment = None
    routing = None
    manager = None
    if args.initial_routes:
        trip_chains = IR.initial_routes_no_breaks(d,
                                                  vehicles.vehicles,
                                                  expanded_mm,
                                                  debug=args.debug)
        initial_routes = [v for v in trip_chains.values()]
        (assignment, routing,
         manager) = MR.model_run_nobreaks(d, expanded_mm, vehicles.vehicles,
                                          None, initial_routes, args)
    else:
        (assignment, routing,
         manager) = MR.model_run_nobreaks(d,
                                          expanded_mm,
                                          vehicles.vehicles,
                                          args=args)

    if assignment:
        ## save the assignment, (Google Protobuf format)
        #save_file_base = os.path.realpath(__file__).split('.')[0]
        #if routing.WriteAssignment(save_file_base + '_assignment.ass'):
        #    print('succesfully wrote assignment to file ' + save_file_base +
        #          '_assignment.ass')

        #print(expanded_mm)
        print('The Objective Value is {0}'.format(assignment.ObjectiveValue()))
        print('details:')

        SO.print_solution(d, expanded_m, expanded_mm, vehicles, manager,
                          routing, assignment, args.horizon, 0, args)
        SO.csv_output(d, expanded_m, expanded_mm, vehicles, manager, routing,
                      assignment, args.horizon, args.vehicle_output)
        SO.csv_demand_output(d, expanded_m, expanded_mm, vehicles, manager,
                             routing, assignment, args.horizon,
                             args.demand_output)

    else:
        print('assignment failed')
Beispiel #5
0
def main():
    num_custs = 100
    num_vehicles = 30
    num_depots = 2
    # Create a set of customer, (and depot) custs.
    customers = cu.Customers(num_custs=num_custs,
                             min_demand=1,
                             max_demand=3,
                             box_size=40,
                             min_tw=1,
                             max_tw=3,
                             num_depots=num_depots)
    print('customers created')
    # print(customers.customers)

    # Create callback fns for distances, demands, service and transit-times.
    dist_fn = customers.return_dist_callback()
    print('distance callback done')
    dem_fn = customers.return_dem_callback()
    print('demand callback done')
    serv_time_fn = customers.make_service_time_call_callback()
    transit_time_fn = customers.make_transit_time_callback()

    def tot_time_fn(a, b):
        """
        The time function we want is both transit time and service time.
        """
        st = serv_time_fn(a, b)
        tt = transit_time_fn(a, b)
        # print('from '+str(a)+' to '+str(b) + ' service_time: '+str(st) + ' transit_time: '+str(tt))
        return st + tt

    print('time callbacks done')

    # Create a list of inhomgeneous vehicle capacities as integer units.
    capacity = np.random.random_integers(3, 9, num_vehicles)

    # Create a list of inhomogenious fixed vehicle costs.
    cost = [int(100 + 2 * np.sqrt(c)) for c in capacity]

    # Create a set of vehicles, the number set by the length of capacity.
    vehicles = ve.Vehicles(capacity=capacity, cost=cost)
    print('vehicles created')

    # no need for following line, as the demandnets to zero
    # assert(customers.get_total_demand() < vehicles.get_total_capacity())

    # Set the starting nodes, and create a callback fn for the starting node.
    start_fn = vehicles.return_starting_callback(customers,
                                                 sameStartFinish=True)

    print('start function set')
    print(customers.customers)

    # Set model parameters
    model_parameters = pywrapcp.RoutingModel.DefaultModelParameters()
    print('got model parameters')

    # The solver parameters can be accessed from the model parameters. For example :
    #   model_parameters.solver_parameters.CopyFrom(
    #       pywrapcp.Solver.DefaultSolverParameters())
    #    model_parameters.solver_parameters.trace_propagation = True

    print('calling routing model')
    print('customers.number ' + str(customers.number))  # int number
    print('vehicles.number ' + str(vehicles.number))  # int number
    print('vehicles.starts ' + str(vehicles.starts))  # List of int start depot
    print('vehicles.ends ' + str(vehicles.ends))  # List of int end depot
    print('model_parameters ')
    print(model_parameters)

    # Make the routing model instance.
    routing = pywrapcp.RoutingModel(
        customers.number,  # int number
        vehicles.number,  # int number
        vehicles.starts,  # List of int start depot
        vehicles.ends,  # List of int end depot
        model_parameters)

    parameters = routing.DefaultSearchParameters()
    # Setting first solution heuristic (cheapest addition).
    parameters.first_solution_strategy = (
        routing_enums_pb2.FirstSolutionStrategy.ALL_UNPERFORMED)
    # Disabling Large Neighborhood Search, (this is the default behaviour)
    parameters.local_search_operators.use_path_lns = False
    parameters.local_search_operators.use_inactive_lns = False
    # Routing: forbids use of TSPOpt neighborhood,
    parameters.local_search_operators.use_tsp_opt = False

    parameters.time_limit_ms = 20 * 60 * 1000  # 20 minutes
    parameters.use_light_propagation = False
    # parameters.log_search = True

    # Set the cost function (distance callback) for each arc, homogeneous for
    # all vehicles.
    routing.SetArcCostEvaluatorOfAllVehicles(dist_fn)

    # Set vehicle costs for each vehicle, not homogeneous.
    for veh in vehicles.vehicles:
        routing.SetFixedCostOfVehicle(veh.cost, int(veh.index))

    # Add a dimension for vehicle capacities
    null_capacity_slack = 0
    routing.AddDimensionWithVehicleCapacity(
        dem_fn,  # demand callback
        null_capacity_slack,
        capacity,  # capacity array
        True,
        "Capacity")
    # Add a dimension for time and a limit on the total time_horizon
    routing.AddDimension(
        tot_time_fn,  # total time function callback
        customers.time_horizon,
        customers.time_horizon,
        True,  # fix start cum to zero
        "Time")

    time_dimension = routing.GetDimensionOrDie("Time")
    solver = routing.solver()
    for cust in customers.customers:
        #
        # here is where I should add pick up and delivery constraints
        #
        #
        # Need to have delivery nodes defined as well as pickup nodes
        #
        # by the way, a Customer cust is a named tuple, with members
        # index, demand, lat, lon, tw_open, tw_close.  Deliveries have
        # negative demand.  Pickups have positive demand.
        if cust.demand > 0:
            # this is a pickup node.
            cust_index = routing.NodeToIndex(cust.index)
            # fixme hack I need to add delivery index to pickups,
            # pickup index to deliveries
            deliv = customers.customers[cust.index + num_custs]
            deliv_index = routing.NodeToIndex(deliv.index)
            # print ('adding same vehicle constraint')
            solver.AddConstraint(
                routing.VehicleVar(cust_index) == routing.VehicleVar(
                    deliv_index))

            # print('adding less than, equal to constraint')
            solver.AddConstraint(
                time_dimension.CumulVar(cust_index) <= time_dimension.CumulVar(
                    deliv_index))
            routing.AddPickupAndDelivery(cust.index, deliv.index)

        # set the time window constraint for this stop (pickup or delivery)
        if cust.tw_open is not None:
            print('index: ' + str(cust.index) + ' open: ' + str(cust.tw_open) +
                  ' close: ' + str(cust.tw_close) + ' demand:' +
                  str(cust.demand))
            time_dimension.CumulVar(routing.NodeToIndex(cust.index)).SetRange(
                cust.tw_open.seconds, cust.tw_close.seconds)
    """
     To allow the dropping of orders, we add disjunctions to all the customer
    nodes. Each disjunction is a list of 1 index, which allows that customer to
    be active or not, with a penalty if not. The penalty should be larger
    than the cost of servicing that customer, or it will always be dropped!
    """
    # To add disjunctions just to the customers, make a list of non-depots.
    non_depot = set(
        [c.index for c in customers.customers if c.tw_open is not None])
    penalty = 400000000  # The cost for dropping a node from the plan.
    nodes = [routing.AddDisjunction([int(c)], penalty) for c in non_depot]

    # This is how you would implement partial routes if you already knew part
    # of a feasible solution for example:
    # partial = np.random.choice(list(non_depot), size=(4,5), replace=False)

    # routing.CloseModel()
    # partial_list = [partial[0,:].tolist(),
    #                 partial[1,:].tolist(),
    #                 partial[2,:].tolist(),
    #                 partial[3,:].tolist(),
    #                 [],[],[],[]]
    # print(routing.ApplyLocksToAllVehicles(partial_list, False))

    # Solve the problem !
    assignment = routing.SolveWithParameters(parameters)

    # The rest is all optional for saving, printing or plotting the solution.
    if assignment:
        # save the assignment, (Google Protobuf format)
        save_file_base = os.path.realpath(__file__).split('.')[0]
        if routing.WriteAssignment(save_file_base + '_assignment.ass'):
            print('succesfully wrote assignment to file ' + save_file_base +
                  '_assignment.ass')

        print('The Objective Value is {0}'.format(assignment.ObjectiveValue()))

        plan_output, dropped = vehicle_output_string(routing, assignment)
        print(plan_output)
        print('dropped nodes: ' + ', '.join(dropped))

        # you could print debug information like this:
        # print(routing.DebugOutputAssignment(assignment, 'Capacity'))

        vehicle_routes = {}
        for veh in range(vehicles.number):
            vehicle_routes[veh] = build_vehicle_route(routing, assignment,
                                                      customers, veh)

        # Plotting of the routes in matplotlib.
        fig = plt.figure()
        ax = fig.add_subplot(111)
        # Plot all the nodes as black dots.
        clon, clat = zip(*[(c.lon, c.lat) for c in customers.customers])
        ax.plot(clon, clat, 'k.')
        # plot the routes as arrows
        plot_vehicle_routes(vehicle_routes, ax, customers, vehicles)
        fig.savefig("test.png")
    else:
        print('No assignment')
Beispiel #6
0
def test_output():

    horizon = 20000
    m = reader.load_matrix_from_csv('test/data/matrix.csv')
    odpairs = reader.load_demand_from_csv('test/data/demand.csv')
    d = D.Demand(odpairs, m, horizon)
    m = d.generate_solver_space_matrix(m)

    v = V.Vehicles(5, horizon)
    # (assignment,routing,manager) = MR.model_run_nobreaks3(d,m,v)
    (assignment, routing, manager) = MR.model_run_nobreaks(d, m, v.vehicles)

    assert assignment

    out = io.StringIO()
    err = io.StringIO()
    args = MockArgs()
    with redirected(out=out, err=err):
        out.flush()
        err.flush()
        SO.print_solution(d, m, m, v, manager, routing, assignment, horizon, 0,
                          args)
        output = out.getvalue()

        expected_output = ""
        assert output == expected_output
        assert filecmp.cmp(output_file, expected_file)

    # make sure output file was created as directed
    assert os.path.exists(args.summary_output)

    # write details again, and this time there should be a _1 version of args.summary_output
    assert not os.path.exists(second_output_file)
    SO.print_solution(d, m, m, v, manager, routing, assignment, horizon, 0,
                      args)
    # created alternate named file
    assert os.path.exists(second_output_file)
    assert filecmp.cmp(output_file, second_output_file)

    # now try again without the file

    out = io.StringIO()
    err = io.StringIO()
    args.summary_output = None
    with redirected(out=out, err=err):
        out.flush()
        err.flush()
        SO.print_solution(d, m, m, v, manager, routing, assignment, horizon, 0,
                          args)
        output = out.getvalue()

        f = open(expected_file, "r", encoding="utf-8")
        expected_output = f.read()
        assert output == expected_output

    assert not os.path.exists(third_output_file)

    os.unlink(output_file)
    os.unlink(second_output_file)
    # reset args to dump output file
    args = MockArgs()

    # test when run with breaks
    x_m = d.insert_nodes_for_breaks(m)
    trip_chains = IR.initial_routes_2(d, v.vehicles, x_m)
    initial_routes = [v for v in trip_chains.values()]
    (assignment, routing, manager) = MR.model_run(d, x_m, v.vehicles, 10000,
                                                  None, initial_routes)
    SO.print_solution(d, x_m, x_m, v, manager, routing, assignment, horizon,
                      10000, args)

    assert filecmp.cmp(output_file, expected_breaks_file)
    os.unlink(output_file)