python - 为什么这种模拟退火算法应用于TSP不收敛?

标签 python simulated-annealing operations-research

作为练习,我必须找到此旅行商问题的最佳解决方案,唯一的区别是销售员可能不会访问第一个坐标两次。给出的最佳值应该在 1200 左右。我不明白为什么这不会收敛。另外一个重要的注意事项是使用曼哈顿距离度量而不是欧几里得距离。

def shuffle_coords(coords):
     x0 = coords[0]
     coords = shuffle(coords[1:])
     return np.insert(coords,0,x0,axis = 0)

def distance(x,y):
    return abs(x[1] - y[1]) + abs(x[0] - y[0])

这里的坐标是随机打乱的。

def shuffle(array):
    df = pd.DataFrame(array)
    return df.sample(len(array)).to_numpy() 


def path_distance(path):
    dist     = []
    for i in range(1,len(path)):
        dist.append(distance(path[i],path[i-1]))
    return np.sum(dist)

这里初始化了模拟退火算法。

def SA_distance(path, T_0,T_min, alpha):
    T = T_0
    dist = path_distance(path)
    
    while T >  T_min:
        new_path = gen_subtour(path)
        diffF = path_distance(new_path) - dist
        if diffF < 0:
            path = new_path
            dist = path_distance(path)
 
        elif np.exp(-(diffF/T)) > random.uniform(0,1):
            path = new_path
            dist = path_distance(path)
      
        T = T * alpha
        print(dist,T)
    return dist,path

此处生成随机子路线,同时保持 x0 不变。

def gen_subtour(path):
    subset = shuffle(np.delete(path,0,axis =0))
    subset = shuffle(path)
    if random.uniform(0,1) < 0.5:
        subset = np.flipud(subset)
    else:
        j = random.randint(1,(len(subset)-1))
        p = subset[j-1]
        q = subset[j]
        subset = np.delete(subset,[j-1,j],axis = 0)
        subset = np.insert(subset,0,p,axis = 0)
        subset = np.insert(subset,len(subset),q,axis = 0)
 
    return np.insert(subset,0,path[0],axis = 0)

def main():
    T_0     = 12
    T_min   = 10**-9
    alpha   =  0.999
    coords = np.array([[375, 375],[161, 190], [186, 169],[185, 124],
                       [122, 104],[109, 258], [55, 153] ,[120, 49],
                       [39, 85]  ,[59, 250] , [17, 310] ,[179, 265],
                       [184, 198]])
    path , distance = SA_distance(coords,T_0,T_min,alpha)

最佳答案

我已经测试过了,我的第一直觉是 gen_subtour() 运行不正常,可能是它改变了路线,步数太多。

我会尝试不同的版本,看看效果如何。 SA 方案似乎运作良好,我认为错误在于提案。

无论如何,这里有一些代码希望能帮助您更好地进行测试。

我使用 pdist 预先计算曼哈顿距离,即;

import numpy as np
from scipy.spatial.distance import pdist, cdist, squareform

coords = np.array([[375, 375],[161, 190], [186, 169],[185, 124],
                   [122, 104],[109, 258], [55, 153] ,[120, 49],
                   [39, 85]  ,[59, 250] , [17, 310] ,[179, 265],
                   [184, 198]])

Y = pdist(coords, 'cityblock')

distance_matrix = squareform(Y)
nodes_count = coords.shape[0]

并定义开始于

def random_start():
    """
        Random start, returns a state
    """
    a = np.arange(0,nodes_count)
    np.random.shuffle(a)
    return a

目标函数,这里有我们不回原点的版本;

def objective_function( route ):
    # uncomment when testing new/modify neighbors
    # assert check_all_nodes_visited(route)

    return np.sum( distance_matrix[route[1:],route[:-1]] )

我这里有 3 种建议,基于一条路线;

def random_swap( route ):
    """
        Random Swap - a Naive neighbour function

        Will only work for small instances of the problem
    """
    route_copy = route.copy()

    random_indici = np.random.choice( route , 2, replace = False)
    route_copy[ random_indici[0] ] = route[ random_indici[1] ]
    route_copy[ random_indici[1] ] = route[ random_indici[0] ]

    return route_copy

def vertex_insert( route, nodes=1 ):
    """
        Vertex Insert Neighbour, inspired by

        http://www.sciencedirect.com/science/article/pii/S1568494611000573
    """
    route_copy = route.copy()
    random_indici = np.random.choice( route , 2, replace = False)
    index_of_point_to_reroute = random_indici[0]
    value_of_point_to_reroute = route[ random_indici[0] ]
    index_of_new_place = random_indici[1]
    route_copy = np.delete(route_copy, index_of_point_to_reroute)
    route_copy = np.insert(route_copy, index_of_new_place, values=value_of_point_to_reroute)
    return route_copy

def block_reverse( route, nodes=1 ):
    """
        Block Reverse Neighbour, inspired by

        http://www.sciencedirect.com/science/article/pii/S1568494611000573

        Note that this is a random 2-opt operation.
    """
    route_copy = route.copy()
    random_indici = np.random.choice( route , 2, replace = False)
    index_of_cut_left = np.min(random_indici)
    index_of_cut_right = np.max(random_indici)
    route_copy[ index_of_cut_left:index_of_cut_right ] = np.flip(route_copy[ index_of_cut_left:index_of_cut_right ])

    return route_copy

您可以选择在 SA 之后进行 2-opt 轮次,以确保没有交叉。

def swap_for_2opt( route, i, k):
    """
        Helper for 2-opt search
    """
    route_copy = route.copy()
    index_of_cut_left = i
    index_of_cut_right = k
    route_copy[ index_of_cut_left:index_of_cut_right ] = np.flip(route_copy[ index_of_cut_left:index_of_cut_right ])

    return route_copy

def local_search_2opt( route ):
    """
        Local Optimum with 2-opt

        https://en.wikipedia.org/wiki/2-opt

    """
    steps_since_improved = 0
    still_improving = True

    route = route.copy()

    while still_improving :
        for i in range( route.size - 1 ):
            for k in np.arange( i + 1, route.size ):
                alt_route = swap_for_2opt(route, i, k)

                if objective_function(alt_route) < objective_function(route):
                    route = alt_route.copy()
                    steps_since_improved = 0

            steps_since_improved += 1

            if steps_since_improved > route.size + 1:
                still_improving = False
                break

    return route

并使用 frigidum 进行 SA

import frigidum


local_opt = frigidum.sa(random_start=random_start,
           objective_function=objective_function,
           neighbours=[random_swap, vertex_insert, block_reverse],
           copy_state=frigidum.annealing.naked,
           T_start=10**5,
           alpha=.95,
           T_stop=0.001,
           repeats=10**2,
           post_annealing = local_search_2opt)

返回的路由几乎总是 1145。

我已经在 frigidum 上发布了一般提示和技巧主页。

关于python - 为什么这种模拟退火算法应用于TSP不收敛?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65890955/

相关文章:

python - scipy 最小化 SLSQP - 'Singular matrix C in LSQ subproblem'

optimization - 强化学习与运筹学

Python Popen 输出到 C 程序,fget 在循环中读取相同的 stdin

python - Python 列表中的 Pandas bool 运算

python - Python 3 中小数到 2 位的钱

algorithm - 模拟退火中T代表什么?

python - 获取包含python中给定键的字典的名称

java - 是否有人知道或具有贪婪可满足性 (GSAT) 和模拟退火可满足性 (SA-SAT) java 算法?

c++ - 模拟退火算法

algorithm - 产业划分问题