关于距离优化点网格(python)

时间:2018-03-27 13:22:42

标签: python optimization grid distance

我正面临着一个问题,我陷入了微积分和约束之间......

  1. 简单案例
  2. 我有一个点网格,有点均匀地分布在3D网格上(第一张图片)。

    注意:你没有看到它,但有一个垂直于网格的第3轴 - 有125个点(每边5 x 5 x 5)

    3D grid with dots distributed ~ equally

    我的目标是,通过噪声统计给出一定距离(我们将在这种情况下取​​0.03),用这些条件填充网格:

    • 随机生成并通过距离条件测试发送,该测试将决定是否保留它
    • 距离条件:如果一个点与另一个点的距离为0.03 +/- 10%,则可以将其放入网格中,如果没有,则将其移除。

    以下是填充网格的示例:

    Badly filled grid (still in 3D)

    问题是,这里有很多我想删除的空白区域。

    有关详细信息,此处是从一个点到最近邻居的所有距离的图表:

    Histogram of distances from dots their closest neighbour

    红色矩形是我希望所有蓝色条形堆叠的地方,在这种情况下介于0.03 - 10%和0.03 + 10%之间。

    主要的问题是,因为它是随机生成的,所以我必须生成很多点,希望即使只有一个点符合约束。这是一个相对“简单的案例”,因为之后,我将不得不将其应用于:

    1. 硬案例

      • 不均匀分布超过10轴的基础
      • 最多1000个以上的点数
    2. 我不是python中的一个破解,我只知道基础知识(numpy,matplotlib.pyplot),并且对图表,数组,循环非常熟悉,但是我的代码没有针对那些微积分的时间进行优化任务类型:

      • 生成一个点
      • 它与点之间的距离是针对每隔一个点计算的(我不知道还有什么办法可以防止扫描整个列表..)
      • 如果一个点更接近前一个点,则它变为“最接近的一个”
      • 如果与“最近的一个”的距离在约束中,则不会将其删除。

      如果有人有一个简单的想法或一些建议,那就太好了,我也希望我做得很好解释(我试图保留最重要的东西,并尝试用适当的英文写作:))。

      感谢阅读,

      森。

      编辑:此处简化代码,因为它可能有助于更轻松地回答! 我直接添加了非均匀分布的网格(A,B)。

      import numpy as np
      import matplotlib.pyplot as plt
      plt.close("all")
      
      A = np.random.rand(100)
      B = np.random.rand(100)
      
      plt.figure()
      plt.plot(A, B,".")
      plt.grid()
      
      Nfillers = 10000
      SNRD = 0.03
      
      for i in range(Nfillers):
          a, b = np.random.rand(2)
          A = np.hstack((A, np.atleast_1d(a)))
          B = np.hstack((B, np.atleast_1d(b)))
          DCN = 1E+308 # Distance Closest Neighbor
          for j in range(len(A)): # len(A) is updated with each iteration
              D1 = A[j] - a
              D2 = B[j] - a
              distance = np.sqrt(D1**2 + D2**2)
              if distance != 0:
                  if distance < DCN:
                      DCN = distance
          if DCN < 0.9*SNRD or DCN > 1.1*SNRD:
              A = np.delete(A, len(A)-1)
              B = np.delete(B, len(B)-1)
      
          print i
      
      plt.figure()
      plt.plot(A, B, ".")
      plt.grid()
      

      编辑2:更快的代码:

      import numpy as np
      import matplotlib.pyplot as plt
      
      A = np.random.rand(100)
      B = np.random.rand(100)
      
      plt.figure()
      plt.plot(A, B,".")
      plt.grid()
      
      Nfillers = 100000
      SNRD = 0.03
      
      for i in range(Nfillers):
          a, b = np.random.rand(2)
          DCN = np.min(np.sqrt((A - a)**2 + (B - b)**2))
          if DCN > 0.9*SNRD and DCN < 1.1*SNRD:
              A = np.hstack((A, np.atleast_1d(a)))
              B = np.hstack((B, np.atleast_1d(b)))
      
          print(i)
      
      plt.figure()
      plt.plot(A, B, ".")
      plt.grid()
      

1 个答案:

答案 0 :(得分:0)

你没有包含任何代码,但这里有一个关于如何加速这些问题的大致想法(计算许多点之间的距离)。

将空间划分为大致与所需距离d大小相当的块。这可以是块号到点列表E.g的映射。 blocks[i,j,k] = list_of_points。然后,当您生成新点时,您只需要检查相邻的块。类似的东西:

new_point = random_point()
i, j, k = get_block_indices(new_point)

for ai in (i-1, i, i+1):
    for aj in (j-1, j, j+1):
        for ak in (k-1, k, k+1):
            for other_point in blocks[ai, aj, ak]:
                # check whether other_point is too close
                found_close_other_point = check(new_point, other_point, d)
                if found_close_other_point:
                    return  # start again

# only reach if we haven't found a close point
try:
    blocks[i, j, k].append(new_point)
except KeyError:
    # create new block
    blocks[i, j, k] = [new_point]

在您想要存储的块数和需要检查的邻居数之间需要进行权衡。一些额外的要点:

  • 您不希望存储每个块,而只是在块不存在时处理(KeyError)为空。在最糟糕的情况下,如果你懒得这样做,你将需要尽可能多的积分。

  • 您可以使用itertools.product来避免嵌套for循环。

  • 如果您玩的是块的大小,您还可以引入额外的跳过条件,例如:如果您知道某个区块中的点数可能超过n,那么您只需要检查len(block[i, j, k]) > n

  • 纯python实现可能不是最快的,您可以使用http://numba.pydata.org/来查看速度,这对于这些类型的数字问题非常容易和快速,同时仍然像python一样阅读。

编辑:实施。

import itertools
import numpy as np
from math import ceil
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D


def compute_min_distances(ps):
    """Compute the minimum distances between all points ``ps``,
    with shape (num_points, num_dims).
    """
    ndim = ps.shape[-1]
    p1, p2 = ps.reshape(-1, 1, ndim), ps.reshape(1, -1, ndim)
    dd = np.sum((p1 - p2)**2, -1)**0.5
    # want to ignore distance to self
    np.fill_diagonal(dd, np.inf)
    return np.min(dd, axis=0)


def get_block_indices(p, B):
    """Given B blocks, get the indices of ``p``.
    """
    return [int(c * B) for c in p]


def init_blocks(ps, B):
    """Set up the initial block mapping.
    """
    blocks = {}
    for p in ps:
        i, j, k = get_block_indices(p, B)
        try:
            blocks[i, j, k].append(p)
        except KeyError:
            blocks[i, j, k] = [p]
    return blocks


def distance(p1, p2):
    """Distance bewtween two points.
    """
    return sum((a - b)**2 for a, b in zip(p1, p2))**0.5


def gen_close_points(p0s, D=0.03, N=1000):
    """Takes ``p0s`` and generates ``N`` new points with each of 
    which has a closest neighbour +- 10% of ``D``.
    """
    # number of blocks required so that only neigbours matter
    B = ceil(1 / D)

    # mapping of blocks to points
    blocks = init_blocks(p0s, B)

    n = 0
    while n < N:
        new_p = np.random.rand(3)
        i, j, k = get_block_indices(new_p, B)

        neighbour_inds = [(b - 1, b, b + 1) for b in (i, j, k)]

        def gen_neighbours():
            for ai, aj, ak in itertools.product(*neighbour_inds):
                try:
                    # try yielding each point
                    yield from blocks[ai, aj, ak]
                except KeyError:
                    # skip if empty
                    continue

        ds = (distance(new_p, p) for p in gen_neighbours())
        min_d = min(ds, default=0)

        if 0.9 * D < min_d < 1.1 * D:
            try:
                blocks[i, j, k].append(new_p)
            except KeyError:
                blocks[i, j, k] = [new_p]
            n += 1

    # combine into array of all points
    return np.concatenate(tuple(itertools.chain(blocks.values())))

让我们用一些初始点给它一个旋转:

p0s = np.random.rand(100, 3)
d0s = compute_min_distances(p0s)
plt.hist(d);

enter image description here

现在让我们生成10000多个点,每个点都指定最近的距离:

ps = gen_close_points(p0s, 0.03, 10000)

检查距离:

plt.hist(compute_min_distances(ps));

enter image description here

看起来不错!最后让我们一起绘制旧点和新点:

fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(111, projection='3d')
ax.scatter3D(ps[:, 0], ps[:, 1], ps[:, 2], alpha=0.1)
ax.scatter3D(p0s[:, 0], p0s[:, 1], p0s[:, 2])

enter image description here

目前假设数据是3维的范围(0,1),但应该是推广它的一个不错的起点。