def test_2opt_one_crossed(self): route = [0, 1, 6, 2, 7, 0] initial_f = objf(route, self.D) sol, delta_f = do_2opt_move(route, self.D, LSOPT.BEST_ACCEPT) do_2opt_f = objf(sol, self.D) self.assertEqual( sol, [0, 1, 2, 6, 7, 0], "chose invalid move, initial %f, optimized %f" % (initial_f, do_2opt_f)) self.assertEqual( initial_f + delta_f, do_2opt_f, "The delta based and recalculated objective functions differ")
def inspect_heuristic(routes, D, C, d, L): improvement_found = False for rd in routes: if rd.is_empty(): continue delta = 0.0 while delta is not None: new_route, delta = do_2opt_move(rd.route, D, FAS) if delta is not None: rd.route = new_route rd.cost += delta improvement_found = True return improvement_found
def test_2opt_two_crossed(self): route = [0, 1, 6, 3, 4, 5, 2, 7, 0] required_moves = 2 for i in range(required_moves): #print "op %d:"%i+1 #print "initial f =",f(route,self.D) opt_route, opt_f = do_2opt_move(route, self.D, LSOPT.BEST_ACCEPT) #print "2-opt'd f =",f(opt_route,self.D) if opt_route is None: return route else: route = opt_route self.assertEqual(i + 1, required_moves) self.assertEqual(opt_route, [0, 1, 2, 3, 4, 5, 6, 7, 0])
def solve_tsp_ropt(D, selected_idxs, do_shuffle=False, do2opt=True, do3opt=True): # r-Opt (r \in {2,3} ) endp = selected_idxs[0] if do_shuffle: shuffled_idxs = list(selected_idxs[1:]) shuffle(shuffled_idxs) new_route = [endp] + shuffled_idxs + [endp] elif selected_idxs[-1] != endp: new_route = selected_idxs + [endp] else: new_route = selected_idxs new_route_cost = objf(new_route, D) # make first 2-optimal if do2opt: improved = True while improved: improved = False improved_route, delta = do_2opt_move(new_route, D, 1) if improved_route is not None: new_route = improved_route new_route_cost += delta improved = True # then 3-optimal (do not waste time on "easy" 2-opt # operations if the route has already been made 2-optimal if do3opt: improved = True while improved: improved = False improved_route, delta = do_3opt_move(new_route, D, 1) if improved_route is not None: new_route = improved_route new_route_cost += delta improved = True return new_route, new_route_cost
def test_2opt_none_crossed(self): route = [0, 1, 2, 3, 0] sol, delta_f = do_2opt_move(route, self.D) self.assertEqual( sol, None, "Route was already 2-optimal, improvements are not possible")
def test_empty_route(self): self.assertEqual(do_2opt_move([], self.D), (None, None)) self.assertEqual(do_2opt_move([0, 0], self.D), (None, None))
def _try_insert_2opt_and_update(insertion, rd, D, L, minimize_K): inserted = rd.route.insert(insertion.customer, insertion.before_node) #print("inserting", insertion, "resulting to", list(rd.route)) ## keep the route 2-optimal # unfortunately we cannot do local_search.py do_2opt_move on the reoute, # because it is an dllist. However, as the route is kept 2-optimal, it # is probable that not all search attempts lead to updating the route, # so convert and udpate only as needed. route_2opt_improved = list(rd.route) total_delta = insertion.cost_delta applied_2opt_moves = False while True: updated_route, delta = do_2opt_move(route_2opt_improved, D, strategy=LSOPT.BEST_ACCEPT) if delta is not None: applied_2opt_moves = True total_delta += delta route_2opt_improved = updated_route else: break # do not accept insertions/moves that make the solution worse! if not minimize_K: # Compared to a solution where the customer is served individually insertion_cost = total_delta - D[0, insertion.customer] - D[ insertion.customer, 0] if insertion_cost > 0: if __debug__: log(DEBUG - 2, ("Rejecting insertion of n%d " % insertion.customer) + ("as it is expected make solution worse.")) rd.route.remove(inserted) # reverse the change return False, None if L and rd.cost + total_delta - S_EPS > L: # The L constraint cannot be satisfied, even with 2-opt rd.route.remove(inserted) # reverse the change return False, None rd.cost += total_delta rd.used_capacity += insertion.demand_delta if applied_2opt_moves: # this is very ineffective, but as the dllist nodes and next/prev # are read only, and the llist module does not offer a sequence # reversing functionality (!) we have no choice but scrap the entire # insertion queue and calculate the insertions all over for the # updated route. :( # #TODO: an improvement would be to use an another doubly-linked-list # implementaiton that allows manipulation of the nodes / list del rd.potential_insertions[:] updated_dllist = dllist(route_2opt_improved) new_edges = [] prev_node = updated_dllist.first current_node = prev_node.next while prev_node != updated_dllist.last: new_edges.append((prev_node, current_node)) prev_node = current_node current_node = prev_node.next if __debug__: log( DEBUG, "Applying a 2-opt moves, which transforms " + " %s to %s with an %.2f improvement" % (str(list(rd.route)), str(route_2opt_improved), total_delta - insertion.cost_delta)) rd.route = updated_dllist return True, new_edges else: new_edges = [(insertion.after_node, inserted), (inserted, insertion.before_node)] return True, new_edges