def city_swap(self, route_id1, route_id2, pick_random=False): """city swap in two route""" if route_id1 == route_id2: print("[ERROR] Route IDs must be different for city_swap: {} {}". format(route_id1, route_id2)) route1, route2 = self.schedule.get_multiple([route_id1, route_id2]) old_distance = route1.total_distance + route2.total_distance min_dist = None neighbor = None feasible_neighbors = [] delta_distance = None # go through each store in route 1 except HQ for idx1, store1 in enumerate(route1.path[1:len(route1.path) - 1]): new_path1 = route1.path[:] # go through each store in route 2 except HQ for idx2, store2 in enumerate(route2.path[1:len(route2.path) - 1]): new_path2 = route2.path[:] # swap city between two routes new_path1[idx1 + 1], new_path2[idx2 + 1] = store2, store1 # check if new routes are feasible # add 0.01 as minimum distance gained to prevent numerical problem (float problem) new_route1 = Route() if new_route1.traverse(new_path1, self.maps): new_route2 = Route() if new_route2.traverse(new_path2, self.maps): new_distance = new_route1.total_distance + new_route2.total_distance if pick_random: feasible_neighbors.append( (old_distance - new_distance, (new_route1, new_route2))) # if pick_random is False, # consider the best out of the feasible alternatives in terms of distance. elif (min_dist is None or (new_route1.total_distance + new_route2.total_distance < min_dist)): min_dist = new_distance neighbor = (new_route1, new_route2) if pick_random and len(feasible_neighbors) > 0: delta_distance, neighbor = feasible_neighbors[random.randint( 0, len(feasible_neighbors) - 1)] elif not pick_random and min_dist is not None: delta_distance = old_distance - min_dist return (delta_distance, neighbor)
def single_route_exchange(self, route_id, pick_random=False): """2-edge exchange within a route""" route = self.schedule.get(route_id) # no possible exchange can be made # if none or only 1 store is visited if len(route.path) < 4: return (None, None) old_distance = route.total_distance neighbor = None feasible_neighbors = [] delta_distance = None # go through the stores in the path for i in range(1, len(route.path)): # edges cannot be adjacent to each other for j in range(i + 2, len(route.path)): # reverse the path in the middle candidate = route.path[0:i] + route.path[ i:j][::-1] + route.path[j:len(route.path)] # check if the route is equal to the original route, # a reversed order path is onsidered equal. if not route.is_equal(candidate): new_route = Route() # check if new route is feasible # add 0.01 as minimum distance gained to prevent numerical problem (float problem) if new_route.traverse(candidate, self.maps): if pick_random: feasible_neighbors.append( (new_route.total_distance, (new_route, ))) elif abs(old_distance - new_route.total_distance) > 0.01 and ( neighbor is None or new_route.total_distance < neighbor[0].total_distance): # put inside iterable for consistency neighbor = (new_route, ) if pick_random and len(feasible_neighbors) > 0: delta_distance, neighbor = feasible_neighbors[random.randint( 0, len(feasible_neighbors) - 1)] elif not pick_random and neighbor is not None: delta_distance = old_distance - neighbor[0].total_distance return (delta_distance, neighbor)
def optimize_all(self, schedule): # call optimize() for each route for uid in schedule.get_all_route_ids(): curr_route = schedule.get(uid) solution = self.optimize(curr_route) # rotate the route new_path = self.make_cycle(solution.tour) new_route = Route() # make sure the new route is feasible if new_route.traverse(new_path, self.maps): schedule.replace(uid, new_route) # if it is not feasible, use unoptimised route else: print("[INFO] route is no longer valid after ACO: {}".format(new_route))
def two_route_exchange(self, route_id1, route_id2, pick_random=False): """2-edge exchange between 2 routes""" if route_id1 == route_id2: print( "[ERROR] Route IDs must be different for two_route_exchange: {} {}" .format(route_id1, route_id2)) min_dist = None neighbor = None feasible_neighbors = [] delta_distance = None route1, route2 = self.schedule.get_multiple((route_id1, route_id2)) old_distance = route1.total_distance + route2.total_distance # go through every possible split of each route for split1 in route1.splits: for split2 in route2.splits: # for every 2-edge exchange, there are 2 alternatives # each alternative consists of possibly 2 routes. # e.g. we want to exchange: # route1 = [ABC][DEF], route2 = [GHI][JKL] candidates = ( # alternative 1: [ABC][JKL], [GHI][DEF] (split1[0] + split2[1], split2[0] + split1[1]), # alternative 2: [ABC][IHG], [LKJ][DEF] (split1[0] + split2[0][::-1], split1[1][::-1] + split2[1])) # check feasibility and find minimum for candidate in candidates: new_route1 = Route() # check if the first candidate route is equal with either route 1 or 2 is_exist1 = route1.is_equal( candidate[0]) or route2.is_equal(candidate[0]) # check if the first candidate route is feasible if not is_exist1 and new_route1.traverse( candidate[0], self.maps): new_route2 = Route() # check if the second candidate route is equal with either route 1 or 2 is_exist2 = route1.is_equal( candidate[1]) or route2.is_equal(candidate[1]) # check if the second route is feasible if not is_exist2 and new_route2.traverse( candidate[1], self.maps): new_distance = new_route1.total_distance + new_route2.total_distance if pick_random: feasible_neighbors.append( (new_distance - old_distance, (new_route1, new_route2))) # if pick_random is False, # consider the best out of the feasible alternatives # in terms of distance. elif (min_dist is None or (new_route1.total_distance + new_route2.total_distance < min_dist)): min_dist = new_distance neighbor = (new_route1, new_route2) # if is_valid2 and (min_dist is None or ( # new_route1.total_distance + new_route2.total_distance < min_dist)): # min_dist = new_route1.total_distance + new_route2.total_distance # min_neighbor = (new_route1, new_route2) if pick_random and len(feasible_neighbors) > 0: delta_distance, neighbor = feasible_neighbors[random.randint( 0, len(feasible_neighbors) - 1)] elif not pick_random and min_dist is not None: delta_distance = old_distance - min_dist return (delta_distance, neighbor)