考虑到彼此之间的距离,将节点放置在圆上的算法

时间:2009-08-28 09:56:01

标签: algorithm

我有以下问题。我有脑区和它们之间的相关性。大脑区域我知道距离。现在,我们预计相关性与大脑区域之间的距离呈负相关。所以当我们增加距离相关性时,下降到零。期望是这是1 / D ^ 2。

我希望可视化我的相关矩阵以检查异常情况。我已经有一些其他的实现,如Taiyun's correlation matrix visualization和一个简单的2D散点图,其中1 / D ^ 2曲线为蓝线。

接下来我希望有一些基于correlation circles的内容。

我为大脑区域创建了一个Node类。所以我的大脑区域是节点。 我模仿与Edges的相关性。我的边缘有一个sourceNode和一个destinationNode,还有一个相关和距离,所以我可以将它们耦合到正确的节点。表查找需要距离和相关性(backIDouping到regionID和regionName等)。

现在我想要的是将所有节点放在一个圆上,以便彼此距离较小的节点靠近放置,远离彼此的节点放置得更远。这样,强边缘(厚的)彼此接近。当你有一个非常强大的边缘越过圆圈时,它很尴尬,眼睛很容易发现它。 当然我寻求最佳,正如下面指出的那样,一个真正的答案并不令人兴奋。

我一直在搜索谷歌,但由于我不知道搜索什么,我找不到任何结果。我怀疑有一个标准算法的名称,但我不知道。这种算法的链接也可以。

到目前为止我想出的是将圆上的节点排列成所有距离的SUM最小的方式。但是为此,我需要制作一种点系统,使得彼此靠近并彼此靠近放置的区域例如获得一些+点和点彼此靠近但彼此远离的位置得到一些下行点。现在优化点算法并获得最高结果。

关于此事的任何提示?我的数学不是很好;)。我目前正在使用圈子,节点,权重进行Google搜索..

注意

如果您有任何其他明智的想法可视化矩阵,请务必告诉我,或在此处发表评论:)。

3 个答案:

答案 0 :(得分:3)

您描述的一般问题没有解决方案,因为您尝试将地图从2D曲面制作到保留所有距离的1D线,这是不可能的。如果某个特定区域要与其他区域进行比较,则可以将所有其他区域放在一个圆圈周围,使其距离与该区域的距离相匹配(但这些其他区域之间的距离将会失真)。 p>

但是你可以做得比在接近距离时随机做得更好。这是一种方法:第一步是做多个随机安排,然后选择其中最好的。下一个改进是通过以小步长移动区域直到它们达到局部最小值,然后选择这些局部最小值中的最佳值来优化这些布置中的每一个与一些成本函数。结果显示在下面的图中,Python代码更进一步。

alt text

import pylab as px
import numpy as nx
import numpy.random as rand
rand.seed(1)
rt2 = nx.sqrt(2)

N = 10 # number of brain regions

# make brain region locations r=1
regions = []
s = 2.
while len(regions)<N:
    p = 2*s*rand.rand(2)-s
    if nx.sqrt(px.dot(p,p))<s:
        regions.append(p)
regions = nx.array(regions)

#px.figure()
px.subplot(2,2,1)
for i in range(len(regions)):
    px.text(regions[i,0], regions[i,1], `i`, fontsize=15)
px.xlim(-1.1*s, 1.1*s)
px.ylim(-1.1*s, 1.1*s)
px.title("inital positions")

# precalc distance matrix for future comparisons
dm = nx.zeros((N,N), dtype=nx.float)
for i in range(N):
    for j in range(N):
        dm[i,j] = nx.sqrt(nx.sum((regions[i,:]-regions[j,:])**2))

def randomize_on_circle(n):
    """return array of n random angles"""
    return 2*nx.pi*rand.rand(n)

def cost_fcn(d_target, d_actual): # cost for distances not matching
    return abs(d_target-d_actual)

def calc_cost(angles):
    """calc cost for the given arrangement    """
    c = 0.
    for i in range(N-1):
        for j in range(i, N):
            # sqrt(...) is distance between two pts on a circle (I think)
            c += cost_fcn(dm[j, i], rt2*nx.sqrt(1-nx.cos(angles[i]-angles[j])))
    return c

def optimize_step(a, shift=2*nx.pi/360):
    """try shifting all points a bit cw and ccw, and return the most beneficial"""
    max_benefit, ref_cost = None, None
    best_i, best_shift = None, None
    for imove in range(N): # loop through the regions and try moving each one
        cost0 = calc_cost(a)
        for da in (shift, -shift):
            a_temp = nx.array(a)
            a_temp[imove] += da
            cost = calc_cost(a_temp)
            benefit = cost0 - cost  # benefit if moving lowers the cost
            if max_benefit is None or benefit > max_benefit:
                max_benefit, best_i, best_shift, ref_cost = benefit, imove, da, cost
    return max_benefit, best_i, best_shift, ref_cost       

lowest_cost, best_angles = None, None
cost_initials, cost_plateaus = [], []
for i in range(30):  # loop though 20 randomized placements on the circle
    angles = randomize_on_circle(N)
    costs = []
    benefits = []
    # optimize each original arrangement by shifting placements one-by-one in small steps
    count_benefits_neg = 0
    count_total, max_total = 0, 2000
    while count_benefits_neg < 10: # better to do a variable step size
        b, i, s, c = optimize_step(angles)
        angles[i] += s
        costs.append(c)
        benefits.append(b)
        if b < 0:
            count_benefits_neg += 1
        count_total += 1
        if count_total > max_total:
            print count_total, b, costs[-20:], benefits[-20]
            raise "not finding an equilibrium"
    if lowest_cost is None or c < lowest_cost:
        lowest_cost = c
        best_angles = nx.array(angles)
        cost_graph = costs[:]
        benefit_graph = nx.array(benefits)
    cost_plateaus.append(c)
    cost_initials.append(costs[0])

px.subplot(2, 2, 2)
px.plot(cost_graph, 'o') # make sure the cost is leveling off
px.title("cost evoloution of best")
px.subplot(2, 2, 3)
px.plot(cost_initials, 'o')
px.plot(cost_plateaus, 'd')
px.title("initial and final costs")

px.subplot(2, 2, 4)
for i in range(len(best_angles)):
    px.text(nx.cos(best_angles[i]), nx.sin(best_angles[i]), `i`, fontsize=15)
px.xlim(-1.2, 1.2)
px.ylim(-1.2, 1.2)
px.title("positioned on circle")

px.show()

有趣的是,这似乎导致了远远不够的东西,并且近乎有事情,但是中档订单搞砸了,所以也许这会做你想要的? (这也说明了从2D到1D的基本问题。例如,在圆圈上,4想要离9更远,但是如果不接近其他数字就不能这样做,而在2D中则可以只是走到一边。)

您可能希望修改cost_fnc,其中指定使圆上的点的距离与2D排列的距离不匹配的惩罚。改变这种方式可以增加大错误(比如说四边形)的成本,或者强调大距离的成本(比如d_target*(abs(d_actual-d_target))等)可能有所帮助。

此外,相对于2D数据的大小更改圆的大小将改变这种相当大的外观,并且您可能想要比数据小一些,就像我在这里所做的那样,这将更多地围绕圆圈扩展点。 (这里的圆圈有R = 1,所以只需适当地调整数据。)另请注意,这将使成本的定量评估不具有意义,因为最好的安排永远不会达到非常低的成本,因为一些地区永远不会与2D数据一样远。

运行多个随机启动的关键是,不断变化的安排可能会陷入局部最小值。这种技术似乎很有用:沉降有助于缩小距离并降低成本(情节#3,蓝点=初始随机,钻石=局部最小),它有助于一些初始安排比其他更多,所以尝试是好的多个初始安排。此外,由于其中一些似乎已达到15左右,因此可以确信这种安排可能具有代表性。

答案 1 :(得分:2)

我建议您使用energy minimization算法将节点放置在圆上,以最小化方形(圆上距离 - 大脑距离)等方式。然后在描述时加粗边缘,可视化应该完整。

答案 2 :(得分:1)

GraphViz可能有一些算法链接。或者,您可以将数据转换为GraphViz接受的格式并通过该格式运行。