def __init__(self, tsp_filename, algorithm, base_dir, image_base_dir, max_distance=huge_number, coordinates_type="garbish", comment="No comment", transport_capacity=0, demand=[], nodes={}): """ Initialize a solution from a map based vrp/tsp format in tsp_filename arguments - general : tsp_filename : filename to load base_dir : base dir where tsp_filename is located arguments - constrains : max_distance - max distance a transport can do. Has a default value of one huge number (almost no limit ) algorithm : 1 - stupid 2 - closest pair """ self.my_map = VrpMaps(tsp_filename=tsp_filename, coordinates_type=coordinates_type, comment=comment, transport_capacity=transport_capacity, demand=demand, nodes=nodes) self.max_distance = max_distance self.routes = {} self.solved = False self.total_distance = 0 self.total_load = 0 self.time_to_solve = 0 self.algorithm_name = algorithm if tsp_filename != "": self.map_name = os.path.basename(os.path.splitext(tsp_filename)[0]) else: self.map_name = "None" if self.algorithm_name == "stupid": self.stupid_algorithm() elif self.algorithm_name == "closest pair": self.closest_pair() else: raise "unknown algoritm" self.heuristic = "None" self.heuristic_iterations = 0 self.heuristic_max_iterations = 0 self.base_dir = base_dir self.tsp_filename = tsp_filename self.image_base_dir = image_base_dir self.images_directory_name = "" self.update_images_directory() return None
class VrpSolutions(object): """ Class solve OVRP problems, based on F. Li et al. """ def __init__(self, tsp_filename, algorithm, base_dir, image_base_dir, max_distance=huge_number, coordinates_type="garbish", comment="No comment", transport_capacity=0, demand=[], nodes={}): """ Initialize a solution from a map based vrp/tsp format in tsp_filename arguments - general : tsp_filename : filename to load base_dir : base dir where tsp_filename is located arguments - constrains : max_distance - max distance a transport can do. Has a default value of one huge number (almost no limit ) algorithm : 1 - stupid 2 - closest pair """ self.my_map = VrpMaps(tsp_filename=tsp_filename, coordinates_type=coordinates_type, comment=comment, transport_capacity=transport_capacity, demand=demand, nodes=nodes) self.max_distance = max_distance self.routes = {} self.solved = False self.total_distance = 0 self.total_load = 0 self.time_to_solve = 0 self.algorithm_name = algorithm if tsp_filename != "": self.map_name = os.path.basename(os.path.splitext(tsp_filename)[0]) else: self.map_name = "None" if self.algorithm_name == "stupid": self.stupid_algorithm() elif self.algorithm_name == "closest pair": self.closest_pair() else: raise "unknown algoritm" self.heuristic = "None" self.heuristic_iterations = 0 self.heuristic_max_iterations = 0 self.base_dir = base_dir self.tsp_filename = tsp_filename self.image_base_dir = image_base_dir self.images_directory_name = "" self.update_images_directory() return None def update_images_directory(self): """ update images directory and create directories if necessary """ self.images_directory_name = os.path.dirname(self.tsp_filename).replace("%s/" % self.base_dir, "") self.images_directory_name = "%s/%s/%s/%s/" % (self.image_base_dir, self.heuristic, self.algorithm_name, self.images_directory_name) if not os.path.isdir(self.images_directory_name): os.makedirs(self.images_directory_name) def near_node(self, origin, node_list): """ find the nearest node of origin from a list of nodes """ min_distance = huge_number min_distance_index = -1 for cur_index in range(0, len(node_list)): distance = self.my_map.find_distances(origin, node_list[cur_index]) if distance < min_distance: min_distance = distance min_distance_index = cur_index #print "Min distance from %s with %s is %f at index %d " % \ # (origin, node_list, min_distance, min_distance_index) return node_list[min_distance_index] def closest_pair(self): """ Implements the Closest pair Algorithm """ ts_start = datetime.datetime.now() unassigned_node_list = self.my_map.bi_graph.nodes() unassigned_node_list.remove(1) #print "unassigned node_list is %s " % unassigned_node_list current_route_number = 1 current_route_points = [] current_route_load = 0 current_route_distance = 0 # totals total_load = 0 total_distance = 0 while len(unassigned_node_list) > 0: if len(current_route_points) == 0: # find closest from depot node = self.near_node(1, unassigned_node_list) else: # find closest from last in route node = self.near_node(current_route_points[-1], unassigned_node_list) #print "node to remove from unassigned is %s " % node unassigned_node_list.remove(node) old_load = current_route_load old_distance = current_route_distance current_route_points.append(node) (status, current_route_load, current_route_distance) = \ self.possible_solution(ordered_delivery_points_list=current_route_points) if status: #print "added node %s , list is %s route number is %d route load is %d distance is %f " % \ # (node, current_route_points, current_route_number, current_route_load, current_route_distance) pass else: current_route_points.pop() current_route_load = old_load current_route_distance = old_distance #print "Limit achieved at route %d with points %s, load %f distance %f" % \ # (current_route_number, current_route_points, current_route_load, current_route_distance) # update internals current_route = {'load': current_route_load, 'distance': current_route_distance, 'points': current_route_points} self.routes[current_route_number] = current_route # update totals total_load += current_route_load total_distance += current_route_distance #print "Add %s" % current_route # start new route current_route_number += 1 current_route_points = [] current_route_load = 0 current_route_distance = 0 current_route_points.append(node) (status, current_route_load, current_route_distance) = \ self.possible_solution(ordered_delivery_points_list=current_route_points) if not status: print "Impossible Solution for this problem (%s)with algorithm %s and constraint distance %d " % \ (self.map_name, self.algorithm_name, self.max_distance) solved = False break current_route = {'load': current_route_load, 'distance': current_route_distance, 'points': current_route_points} self.routes[current_route_number] = current_route ts_end = datetime.datetime.now() self.time_to_solve = (ts_end - ts_start) self.total_load = total_load + current_route_load self.total_distance = total_distance + current_route_distance self.solved = True def stupid_algorithm(self): """ really stupid algorith , just to test internal Data Structures Starts from point 2 and and to route one before limit is achieved at poin K then start and point k+1 until limit sequenctially until last point """ ts_start = datetime.datetime.now() current_route_number = 1 current_route_points = [] current_route_load = 0 current_route_distance = 0 total_load = 0 total_distance = 0 solved = True for node in self.my_map.bi_graph.nodes(): if node != 1: #old_list = current_route_points old_load = current_route_load old_distance = current_route_distance current_route_points.append(node) (status, current_route_load, current_route_distance) = \ self.possible_solution(ordered_delivery_points_list=current_route_points) if status: pass else: current_route_points.pop() current_route_load = old_load current_route_distance = old_distance # update internals current_route = {'load': current_route_load, 'distance': current_route_distance, 'points': current_route_points} self.routes[current_route_number] = current_route # update totals total_load += current_route_load total_distance += current_route_distance #print "Add %s" % current_route # start new route current_route_number += 1 current_route_points = [] current_route_points.append(node) (status, current_route_load, current_route_distance) = \ self.possible_solution(ordered_delivery_points_list=current_route_points) if not status: print "Impossible Solution for this problem (%s)with algorithm %s and constraint distance %d " \ % (self.map_name, self.algorithm_name, self.max_distance) solved = False break if solved: # check if we end without an empty route if len(current_route_points) > 0: current_route = {'load': current_route_load, 'distance': current_route_distance, 'points': current_route_points} self.routes[current_route_number] = current_route total_load += current_route_load total_distance += current_route_distance ts_end = datetime.datetime.now() self.time_to_solve = (ts_end - ts_start) self.total_load = total_load self.total_distance = total_distance self.solved = True else: self.solved = False def solution_json(self): """ return solution found in JSON format """ if self.solved: return json.dumps({'Algorithm': self.algorithm_name, 'Heuristic': self.heuristic, 'Heuristic_Iterations': self.heuristic_iterations, 'Heuristic_Max_Iterations': self.heuristic_max_iterations, 'Map': self.map_name, 'Max_Distance': self.max_distance, 'Total_Time_In_Seconds': self.time_to_solve.total_seconds(), 'Total_Distance': self.total_distance, 'Total_Load': self.total_load, 'Number_of_Routes': len(self.routes), 'Number_of_Nodes': self.my_map.number_of_nodes, 'Truck_Capacity': self.my_map.truck_capacity, 'Solved': self.solved, 'Original_Comments': self.my_map.comment, 'Routes': self.routes}) else: return json.dumps({}) def solution_xml(self): """ return solution found in XML format """ xml_line_sep = "\n" def routes_in_xml(): route_str = "<route>%s" % xml_line_sep for route in self.routes.keys(): field_name = "Number" route_str += "<%s>%s</%s>%s" % (field_name, route, field_name, xml_line_sep) field_name = "Load" route_str += "<%s>%s</%s>%s" % (field_name, self.routes[route]['load'], field_name, xml_line_sep) field_name = "Distance" route_str += "<%s>%s</%s>%s" % (field_name, self.routes[route]['distance'], field_name, xml_line_sep) route_str += "<points>%s" % xml_line_sep for i in range(0, len(self.routes[route]['points'])): field_name = "point" route_str += "<%s>%s</%s>%s" % (field_name, self.routes[route]['points'][i], field_name, xml_line_sep) route_str += "</points>%s" % xml_line_sep route_str += "</route>%s" % xml_line_sep return route_str format_str = "<solution>%s" % xml_line_sep field_name = "Algorithm" format_str += "<%s>%s</%s>%s" % (field_name, self.algorithm_name, field_name, xml_line_sep) field_name = "Heuristic" format_str += "<%s>%s</%s>%s" % (field_name, self.heuristic, field_name, xml_line_sep) field_name = 'Heuristic_Iterations' format_str += "<%s>%s</%s>%s" % (field_name, self.heuristic_iterations, field_name, xml_line_sep) field_name = 'Heuristic_Max_Iterations' format_str += "<%s>%s</%s>%s" % (field_name, self.heuristic_max_iterations, field_name, xml_line_sep) field_name = 'Map' format_str += "<%s>%s</%s>%s" % (field_name, self.map_name, field_name, xml_line_sep) field_name = 'Max_Distance' format_str += "<%s>%s</%s>%s" % (field_name, self.max_distance, field_name, xml_line_sep) field_name = 'Total_Time_In_Seconds' format_str += "<%s>%s</%s>%s" % (field_name, self.time_to_solve.total_seconds(), field_name, xml_line_sep) field_name = 'Total_Distance' format_str += "<%s>%s</%s>%s" % (field_name, self.total_distance, field_name, xml_line_sep) field_name = 'Total_Load' format_str += "<%s>%s</%s>%s" % (field_name, self.total_load, field_name, xml_line_sep) field_name = 'Number_of_Routes' format_str += "<%s>%s</%s>%s" % (field_name, len(self.routes), field_name, xml_line_sep) field_name = 'Number_of_Nodes' format_str += "<%s>%s</%s>%s" % (field_name, self.my_map.number_of_nodes, field_name, xml_line_sep) field_name = 'Truck_Capacity' format_str += "<%s>%s</%s>%s" % (field_name, self.my_map.truck_capacity, field_name, xml_line_sep) field_name = 'Solved' format_str += "<%s>%s</%s>%s" % (field_name, self.solved, field_name, xml_line_sep) field_name = 'Original_Comments' format_str += "<%s>%s</%s>%s" % (field_name, self.my_map.comment, field_name, xml_line_sep) field_name = 'Routes' format_str += "<%s>%s</%s>%s" % (field_name, routes_in_xml(), field_name, xml_line_sep) format_str += "</solution>" return format_str def debug__routes(self): """ helper to debug routes achieved """ print "Routes are:" for i in self.routes.keys(): print self.routes[i]['points'] def draw_routes(self, save, grid=False): """ Draw a map for each route found in solution """ count = 0 image_file_name = "" for i in self.routes.keys(): count += 1 if save: self.update_images_directory() image_file_name = "%s/%s_%d" % (self.images_directory_name, self.map_name, count) # developper helper if count <= huge_number: route = self.routes[i]['points'] route.insert(0, 1) cur_map = self.my_map cur_map.add_edges_from_list(route) cur_map.draw_map(title="Map for %s route_ %d\nMaximum distance per truck of %d" % (self.map_name, count, self.max_distance), save=save, image_filename=image_file_name, edge_labels=True, arrows=True, signature="route nodes: %s\n Length: %d\nLoad: %d" % (self.routes[i]['points'], self.routes[i]['distance'], self.routes[i]['load']), grid=grid) def draw_all_routes(self, save, grid=False): """ Draw a map with all routes found in solution """ if save: self.update_images_directory() image_file_name = "%s/%s" % (self.images_directory_name, self.map_name) else: image_file_name = "" all_routes = [] for i in self.routes.keys(): route = self.routes[i]['points'] route.insert(0, 1) all_routes.append(route) self.my_map.add_edges_from_list(route) self.my_map.draw_map(title="Global_Map for %s\nAlgoritm %s Heuristic %s Iterations %d" % (self.map_name, self.algorithm_name, self.heuristic, self.heuristic_iterations), save=save, image_filename=image_file_name, edge_labels=False, arrows=False, signature="No Trucks: %d Load: %d Distance: %d\nMaximum distance per truck of %d\n%s" % (len(self.routes.keys()), self.total_load, self.total_distance, self.max_distance, self.my_map.comment), grid=grid, edges=all_routes) for i in self.routes.keys(): route = self.routes[i]['points'] #delete the depot inserted self.routes[i]['points'] = self.routes[i]['points'][1:] self.my_map.del_edges_from_list(route) def update_total(self): """ Update total counters """ if self.solved: distance = 0 load = 0 for key in self.routes.keys(): distance += self.routes[key]['distance'] load += self.routes[key]['load'] self.total_distance = distance self.total_load != load def explain_solution(self): """ Textual representation of solution found @return: """ def explain_routes(): str = "Routes:\n" for route in self.routes.keys(): str += "Route #%s\tLoad:\t%d\tDistance:\t%f\n" % \ (route, self.routes[route]['load'], self.routes[route]['distance']) points, load, distance, totals = self.route_to_lists(self.routes[route]['points']) points_str = "points:\t" load_str = "load:\t" distance_str = "distance:\t" totals_str = "totals:\t" for i in range(0, len(points)): points_str += "\t%s" % points[i] load_str += "\t%d" % load[i] distance_str += "\t%f" % distance[i] totals_str += "\t%f" % totals[i] str += "%s\n" % points_str str += "%s\n" % load_str str += "%s\n" % distance_str str += "%s\n" % totals_str return str str = "SOLVED:\t%s\n" % self.solved str += "DISTANCE:\t%s\n" % self.total_distance str += "#Transports:\t%s\n" % len(self.routes) str += "Base Algoritm: %s\n" % self.algorithm_name str += "Heuristic: %s\n" % self.heuristic str += explain_routes() return str def __sanity_check__(self, debug_errors=False): """ Check if Solution internal Data Structs are consistent """ status = True if self.solved: # check 1 # All routes are possible for key in self.routes.keys(): solution_status, load, distance = self.possible_solution(self.routes[key]['points']) if not solution_status: if debug_errors: print "Impossible route found %s" % self.routes[key]['points'] status = False else: pass # check 2 # total distance and total load are the sum of routes distance and route load distance = 0 load = 0 for key in self.routes.keys(): distance += self.routes[key]['distance'] load += self.routes[key]['load'] if self.total_distance != distance or self.total_load != load: if debug_errors: print "Total Distance and load check FAIL : Total (distance %f load %f ) Sum Routes (distance %f" \ " load %f )" % (self.total_distance, self.total_load, distance, load) status = False # check 3 # no delivery points are visited more then once in the solution points_dict = {} for key in self.routes.keys(): for point in self.routes[key]['points']: try: if points_dict[point] is True: if debug_errors: print "Duplicated point %s" % point status = False except: points_dict[point] = True #print "check %s" % point # check 4 # all delivery points are in the solution node_list = self.my_map.bi_graph.nodes()[1:] for i in range(0, len(node_list)): try: if points_dict[node_list[i]] is True: pass except: if debug_errors: print "point %s is missing " % node_list[i] status = False return status def possible_solution(self, ordered_delivery_points_list): """ check if a node list solution is possibles based only on the truck capacity to the specific delivery points @rtype : object """ distance = 0 load = 0 for i in range(0, len(ordered_delivery_points_list)): if i == 0: cur_index = ordered_delivery_points_list[i] distance = self.my_map.find_distances(1, cur_index) load = self.my_map.demand[cur_index] else: cur_index = ordered_delivery_points_list[i] prev_index = ordered_delivery_points_list[i-1] distance += self.my_map.find_distances(prev_index, cur_index) load += self.my_map.demand[cur_index] if load > self.my_map.truck_capacity: return False, huge_number, huge_number if distance > self.max_distance: return False, huge_number, huge_number return True, load, distance def route_to_lists(self, ordered_delivery_points_list): """ Returns 4 list . 1 - ordered list of points (depot is the first ) 2 - Load for each point 3 - Distance from the previous point 4 - total distance so far @rtype : object """ total = 0 points = [1] load = [0] distance = [0] totals = [0] for i in range(0, len(ordered_delivery_points_list)): if i == 0: cur_index = ordered_delivery_points_list[i] points.append(ordered_delivery_points_list[i]) load.append(self.my_map.demand[cur_index]) cur_distance = self.my_map.find_distances(1, cur_index) distance.append(cur_distance) total = cur_distance totals.append(total) else: cur_index = ordered_delivery_points_list[i] prev_index = ordered_delivery_points_list[i-1] points.append(ordered_delivery_points_list[i]) load.append(self.my_map.demand[cur_index]) cur_distance = self.my_map.find_distances(prev_index, cur_index) distance.append(cur_distance) total += cur_distance totals.append(total) return points, load, distance, totals