def test_create_time_matrix(): matrix = reader.load_matrix_from_csv('test/data/matrix.csv') hours_matrix = reader.travel_time(60,matrix) assert (hours_matrix.loc[0,0] == 0) assert (hours_matrix.loc[0,1] == math.floor(21.15)) minutes_matrix = reader.travel_time(1,matrix) assert (minutes_matrix.loc[0,0] == 0) assert (minutes_matrix.loc[0,1] == 1269.0) # a mile a minute
def test_time_callback(): pickup_time = 20 dropoff_time = 10 horizon = 10000 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, pickup_time=pickup_time, dropoff_time=dropoff_time) max_distance = m.max().max() m_m = reader.travel_time(1, m) max_time = m_m.max().max() # test fixing travel time matrix into model space m_m = d.generate_solver_space_matrix(m_m) assert m_m.ndim == 2 # did I get it right? the time from # node 0 to node 1 is depot to node 5, is 930 assert m_m.loc[0, 1] == m.loc[0, d.get_map_node(1)] # with horizon of 10000, only one OD pair is feasible assert len(m_m.index) == 3 # 2 nodes, 1 depot assert len(m_m.loc[0]) == 3 demand_callback = E.create_demand_callback(m.index, d) manager = MockManager() assert demand_callback(manager, 0) == 0 assert demand_callback(manager, 1) == 1 assert demand_callback(manager, 2) == -1 assert demand_callback(manager, 3) == 0 # Distance tests, no pickup or dropoff times dist_callback = E.create_dist_callback(m_m, d) assert dist_callback(manager, 0, 0) == 0 # depot to depot is zero # Again that first line in demand is mapnode 7 to mapnode 9 # so node 0 to node 1 is depot to node 7, is 930 assert dist_callback(manager, 0, 1) == m.loc[0, d.get_map_node(1)] assert dist_callback(manager, 1, 2) == m.loc[d.get_map_node(1), d.get_map_node(2)] assert dist_callback(manager, 2, 0) == m.loc[d.get_map_node(2), 0] # everything else is forbidden assert dist_callback(manager, 0, 2) > max_distance # can't skip pickup assert dist_callback(manager, 2, 1) > max_distance # can't go backwards assert dist_callback(manager, 1, 0) > max_distance # can't skip dropoff # Time tests add pickup and delivery time # simple time callback with original minutes matrix time_callback = E.create_time_callback2(m_m, d) assert time_callback(manager, 0, 0) == 0 # note that pickup time at node, dropoff time at node is transit # time across node, so is only added in when node is origin assert time_callback(manager, 0, 1) == m.loc[0, d.get_map_node(1)] assert time_callback( manager, 1, 2) == m.loc[d.get_map_node(1), d.get_map_node(2)] + pickup_time assert time_callback(manager, 2, 0) == m.loc[d.get_map_node(2), 0] + dropoff_time # everything else is forbidden assert time_callback(manager, 0, 2) > max_time # can't skip pickup assert time_callback(manager, 2, 1) > max_time # can't go backwards assert time_callback(manager, 1, 0) > max_time # can't skip dropoff # more extensive time callback with break nodes too m_m_more = d.insert_nodes_for_breaks(m_m) print('len m_m_more is ', len(m_m_more)) assert len(m_m_more) > len(m_m) time_callback = E.create_time_callback2(m_m_more, d) # the following are same as above, should not have changed assert time_callback(manager, 0, 0) == 0 assert time_callback(manager, 0, 1) == m.loc[0, d.get_map_node(1)] assert time_callback( manager, 1, 2) == m.loc[d.get_map_node(1), d.get_map_node(2)] + pickup_time assert time_callback(manager, 2, 0) == m.loc[d.get_map_node(2), 0] + dropoff_time assert time_callback(manager, 0, 2) > max_time # can't skip pickup assert time_callback(manager, 2, 1) > max_time # can't go backwards assert time_callback(manager, 1, 0) > max_time # can't skip dropoff # now test new nodes, 3+ assert len(m_m_more.index) == 14 # test long break from 0 to 1 assert time_callback(manager, 0, 3) == 660 # short break assert time_callback(manager, 0, 4) == 480 # short break happens assert time_callback(manager, 4, 3) == (660 - 480) + 30 # long break happens assert time_callback(manager, 3, 1) == (m.loc[0, d.get_map_node(1)] - 660) + 600 # can't go assert time_callback(manager, 3, 4) > max_time # test the drive callback accumulator drive_callback = partial( E.create_drive_callback(m_m_more, d, 11 * 60, 10 * 60), manager) # drive callback is just the drive time, but gets reset at long breaks bn11 = d.get_break_node(3) assert bn11.drive_time_restore() == -660 assert drive_callback(0, 0) == 0 # no pickup, no dropoff time added to drive dimension assert drive_callback(0, 1) == m.loc[0, d.get_map_node(1)] assert drive_callback(1, 2) == m.loc[d.get_map_node(1), d.get_map_node(2)] assert drive_callback(2, 0) == m.loc[d.get_map_node(2), 0] assert drive_callback(0, 2) > max_time # can't skip pickup assert drive_callback(2, 1) > max_time # can't go backwards assert drive_callback(1, 0) > max_time # can't skip dropoff # now test break nodes # test long break from 0 to 1 assert drive_callback(0, 3) == 660 # just drive, haven't taken break # short break assert drive_callback(0, 4) == 480 # just drive, haven't taken break # short break happens, no impact assert drive_callback(4, 3) == (660 - 480) # no 30 minute break added # long break happens assert drive_callback( 3, 1) == (m.loc[0, d.get_map_node(1)] - 660) + bn11.drive_time_restore() # can't go assert drive_callback(3, 4) > max_time # now short break callback short_callback = partial( E.create_short_break_callback(m_m_more, d, 8 * 60, 30), manager) # drive callback is just the drive time, but gets reset at long breaks bn8 = d.get_break_node(4) assert bn8.drive_time_restore() == -480 assert short_callback(0, 0) == 0 # no pickup, no dropoff time added to drive dimension assert short_callback(0, 1) == m.loc[0, d.get_map_node(1)] assert short_callback(1, 2) == m.loc[d.get_map_node(1), d.get_map_node(2)] assert short_callback(2, 0) == m.loc[d.get_map_node(2), 0] assert short_callback(0, 2) > max_time # can't skip pickup assert short_callback(2, 1) > max_time # can't go backwards assert short_callback(1, 0) > max_time # can't skip dropoff # now test break nodes # test long break from 0 to 1 assert short_callback(0, 3) == 660 # just drive, haven't taken break # short break assert short_callback(0, 4) == 480 # just drive, haven't taken break # short break happens, triggers restore of 480 assert short_callback(4, 3) == (660 - 480) + bn8.drive_time_restore() # long break happens. Okay, the source code is lame, but it also restores 660 - 480 assert short_callback( 3, 1) == (m.loc[0, d.get_map_node(1)] - 660) + (bn11.drive_time_restore() - bn8.drive_time_restore()) # can't go assert short_callback(3, 4) > max_time
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')