Example #1
0
    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
Example #2
0
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