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
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
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')
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')
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')
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)