【问题标题】:Compute the shortest path with exactly `n` nodes between two points on a meshgrid计算网格上两点之间恰好有“n”个节点的最短路径
【发布时间】:2023-04-01 06:23:01
【问题描述】:

我在网格上定义了以下 3D 表面:

%pylab inline
def muller_potential(x, y, use_numpy=False):
    """Muller potential
    Parameters
    ----------
    x : {float, np.ndarray, or theano symbolic variable}
    X coordinate. If you supply an array, x and y need to be the same shape,
    and the potential will be calculated at each (x,y pair)
    y : {float, np.ndarray, or theano symbolic variable}
    Y coordinate. If you supply an array, x and y need to be the same shape,
    and the potential will be calculated at each (x,y pair)

    Returns
    -------
    potential : {float, np.ndarray, or theano symbolic variable}
    Potential energy. Will be the same shape as the inputs, x and y.
    Reference
    ---------
    Code adapted from https://cims.nyu.edu/~eve2/ztsMueller.m
    """
    aa = [-1, -1, -6.5, 0.7]
    bb = [0, 0, 11, 0.6]
    cc = [-10, -10, -6.5, 0.7]
    AA = [-200, -100, -170, 15]
    XX = [1, 0, -0.5, -1]
    YY = [0, 0.5, 1.5, 1]
    # use symbolic algebra if you supply symbolic quantities
    exp = np.exp
    value = 0
    for j in range(0, 4):
        if use_numpy:
            value += AA[j] * numpy.exp(aa[j] * (x - XX[j])**2 + bb[j] * (x - XX[j]) * (y - YY[j]) + cc[j] * (y - YY[j])**2)
        else: # use sympy
            value += AA[j] * sympy.exp(aa[j] * (x - XX[j])**2 + bb[j] * (x - XX[j]) * (y - YY[j]) + cc[j] * (y - YY[j])**2)
    return value

这给出了以下情节:

minx=-1.5
maxx=1.2
miny=-0.2
maxy=2
ax=None
grid_width = max(maxx-minx, maxy-miny) / 50.0
xx, yy = np.mgrid[minx : maxx : grid_width, miny : maxy : grid_width]
V = muller_potential(xx, yy, use_numpy=True)
V = ma.masked_array(V, V>200)
contourf(V, 40)
colorbar();

我编写了以下代码来定义该网格上两点之间的最短路径。我在网格的两个相邻点之间使用的度量由(V[e]-V[cc])**2 给出,cc 是当前单元格,e 是相邻单元格之一。邻居被定义为完全连通性:包括对角线的所有直接邻居。

def dijkstra(V):
    mask = V.mask
    visit_mask = mask.copy() # mask visited cells
    m = numpy.ones_like(V) * numpy.inf
    connectivity = [(i,j) for i in [-1, 0, 1] for j in [-1, 0, 1] if (not (i == j == 0))]
    cc = unravel_index(V.argmin(), m.shape) # current_cell
    m[cc] = 0
    P = {}  # dictionary of predecessors 
    #while (~visit_mask).sum() > 0:
    for _ in range(V.size):
        #print cc
        neighbors = [tuple(e) for e in asarray(cc) - connectivity 
                     if e[0] > 0 and e[1] > 0 and e[0] < V.shape[0] and e[1] < V.shape[1]]
        neighbors = [ e for e in neighbors if not visit_mask[e] ]
        tentative_distance = [(V[e]-V[cc])**2 for e in neighbors]
        for i,e in enumerate(neighbors):
            d = tentative_distance[i] + m[cc]
            if d < m[e]:
                m[e] = d
                P[e] = cc
        visit_mask[cc] = True
        m_mask = ma.masked_array(m, visit_mask)
        cc = unravel_index(m_mask.argmin(), m.shape)
    return m, P

def shortestPath(start, end, P):
    Path = []
    step = end
    while 1:
        Path.append(step)
        if step == start: break
        step = P[step]
    Path.reverse()
    return asarray(Path)

D, P = dijkstra(V)
path = shortestPath(unravel_index(V.argmin(), V.shape), (40,4), P)

结果如下:

contourf(V, 40)
plot(path[:,1], path[:,0], 'r.-')

路径长度为112:

print path.shape[0]
112

我想知道是否可以计算start 和end 之间精确长度n 之间的最短路径,并为函数提供一个参数n。

备注:如果我将使用的度量从 (V[e]-V[cc])**2 更改为 V[e]-V[cc],这会增加负距离,我会得到该图,因为它按预期通过局部最小值时看起来更好:

【问题讨论】:

  • 我猜你问的是traveling purchaser problem 的变体NP-complete。因此,没有算法可以解决多项式时间。我认为各种solutions of similar problem 对你来说会很有趣。
  • @Vadim Shkaberda:是的,这是一个类似的问题。我将研究提出的各种解决方案。但是,我认为与旅行商问题 (TSP) 的一个区别是我不想访问所有节点,而只想访问 $n$ 个节点。
  • 您不能尝试为每一步添加与start/end 的最大距离的强约束,换句话说,声明一个您可以在每一步且不超过步数限制的区域。如果您要访问的节点数量与start/end之间的直接距离相比不是很大,则可以成功。显然,你应该放弃visit_mask。
  • 和 TSP 问题还是没有太大区别。如果您在start 和end 之间有直接距离d 并且您想找到s 步骤的最小解决方案并且您有8 个方向可以走每一步(除了第一个),您必须检查所有路径对于第一个(s-d)/2 步骤。所以,你需要检查(8^((s-d)/2) + k) 路径和蛮力算法是O(8^s)。

标签: python numpy shortest-path


【解决方案1】:

由于我想获得一个合理的路径来采样潜在的盆地,我编写了下面的函数。为了完整起见,我记得我写的 dijkstra 函数:

%pylab
def dijkstra(V, start):
    mask = V.mask
    visit_mask = mask.copy() # mask visited cells
    m = numpy.ones_like(V) * numpy.inf
    connectivity = [(i,j) for i in [-1, 0, 1] for j in [-1, 0, 1] if (not (i == j == 0))]
    cc = start # current_cell
    m[cc] = 0
    P = {}  # dictionary of predecessors 
    #while (~visit_mask).sum() > 0:
    for _ in range(V.size):
        #print cc
        neighbors = [tuple(e) for e in asarray(cc) - connectivity 
                     if e[0] > 0 and e[1] > 0 and e[0] < V.shape[0] and e[1] < V.shape[1]]
        neighbors = [ e for e in neighbors if not visit_mask[e] ]
        t.ntative_distance = asarray([V[e]-V[cc] for e in neighbors])
        for i,e in enumerate(neighbors):
            d = tentative_distance[i] + m[cc]
            if d < m[e]:
                m[e] = d
                P[e] = cc
        visit_mask[cc] = True
        m_mask = ma.masked_array(m, visit_mask)
        cc = unravel_index(m_mask.argmin(), m.shape)
    return m, P

start, end = unravel_index(V.argmin(), V.shape), (40,4)
D, P = dijkstra(V, start)

def shortestPath(start, end, P):
    Path = []
    step = end
    while 1:
        Path.append(step)
        if step == start: break
        step = P[step]
    Path.reverse()
    return asarray(Path)

path = shortestPath(start, end, P)

这给出了以下情节:

contourf(V, 40)
plot(path[:,1], path[:,0], 'r.-')
colorbar()

那么,extend_path 函数背后的基本思想是扩展通过在路径中取节点的邻居获得的最短路径,以最小化潜力。一个集合记录扩展过程中已经访问过的单元格。

def get_neighbors(cc, V, visited_nodes):
    connectivity = [(i,j) for i in [-1, 0, 1] for j in [-1, 0, 1] if (not (i == j == 0))]
    neighbors = [tuple(e) for e in asarray(cc) - connectivity 
                 if e[0] > 0 and e[1] > 0 and e[0] < V.shape[0] and e[1] < V.shape[1]]
    neighbors = [ e for e in neighbors if e not in visited_nodes ]
    return neighbors

def extend_path(V, path, n):
    """
    Extend the given path with n steps
    """
    path = [tuple(e) for e in path]
    visited_nodes = set()
    for _ in range(n):
        visited_nodes.update(path)
        dist_min = numpy.inf
        for i_cc, cc in enumerate(path[:-1]):
            neighbors = get_neighbors(cc, V, visited_nodes)
            next_step = path[i_cc+1]
            next_neighbors = get_neighbors(next_step, V, visited_nodes)
            join_neighbors = list(set(neighbors) & set(next_neighbors))
            if len(join_neighbors) > 0:
                tentative_distance = [ V[e] for e in join_neighbors ]
                argmin_dist = argmin(tentative_distance)
                if tentative_distance[argmin_dist] < dist_min:
                    dist_min, new_step, new_step_index  = tentative_distance[argmin_dist], join_neighbors[argmin_dist], i_cc+1
        path.insert(new_step_index, new_step)
    return path

以下是我将最短路径延长250步得到的结果:

path_ext = extend_path(V, path, 250)
print len(path), len(path_ext)
path_ext = numpy.asarray(path_ext)
contourf(V, 40)
plot(path[:,1], path[:,0], 'w.-')
plot(path_ext[:,1], path_ext[:,0], 'r.-')
colorbar()

正如预期的那样,当我增加n 时,我首先开始对更深的盆地进行采样,如下所示:

rcParams['figure.figsize'] = 14,8
for i_plot, n in enumerate(range(0,250,42)):
    path_ext = numpy.asarray(extend_path(V, path, n))
    subplot('23%d'%(i_plot+1))
    contourf(V, 40)
    plot(path_ext[:,1], path_ext[:,0], 'r.-')
    title('%d path steps'%len(path_ext))

【讨论】:

  • 如果你有一个简单的路径,你的解决方案就可以应用。如果您有另一条可能为您提供最低限度的短路径,例如70-90 步,你的算法永远找不到它。
猜你喜欢
  • 2019-04-20
  • 2017-02-03
  • 1970-01-01
  • 1970-01-01
  • 2016-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-26
相关资源
最近更新 更多