【问题标题】:Generate a random sample of points distributed on the surface of a unit sphere生成分布在单位球面上的点的随机样本
【发布时间】:2016-03-02 19:24:14
【问题描述】:

我正在尝试使用 numpy 在球体表面上生成随机点。我已经查看了解释均匀分布here 的帖子。但是,需要有关如何仅在球体表面上生成点的想法。我有坐标(x,y,z)和每个球体的半径。

我对这个级别的数学不是很精通,并试图理解蒙特卡洛模拟。

任何帮助将不胜感激。

谢谢, 帕林

【问题讨论】:

    标签: python numpy geometry random-sample uniform-distribution


    【解决方案1】:

    基于the last approach on this page,您可以简单地生成一个由三个标准正态分布的独立样本组成的向量,然后对向量进行归一化,使其大小为1:

    import numpy as np
    
    def sample_spherical(npoints, ndim=3):
        vec = np.random.randn(ndim, npoints)
        vec /= np.linalg.norm(vec, axis=0)
        return vec
    

    例如:

    from matplotlib import pyplot as plt
    from mpl_toolkits.mplot3d import axes3d
    
    phi = np.linspace(0, np.pi, 20)
    theta = np.linspace(0, 2 * np.pi, 40)
    x = np.outer(np.sin(theta), np.cos(phi))
    y = np.outer(np.sin(theta), np.sin(phi))
    z = np.outer(np.cos(theta), np.ones_like(phi))
    
    xi, yi, zi = sample_spherical(100)
    
    fig, ax = plt.subplots(1, 1, subplot_kw={'projection':'3d', 'aspect':'equal'})
    ax.plot_wireframe(x, y, z, color='k', rstride=1, cstride=1)
    ax.scatter(xi, yi, zi, s=100, c='r', zorder=10)
    

    同样的方法也可以推广到在单位圆 (ndim=2) 或更高维单位超球面上的均匀分布点。

    【讨论】:

    • 这似乎有一个角落过密度,因为它正在规范化 ndim 立方体而不是 ndim 球体。我认为可以通过在函数中应用选择来修复过密度,以便在将单位球体之外的点标准化为球体之前不计算它们。我使用了一个可能不是 Pythonic 的函数来为 ndim=3 执行此操作。不过,您也许可以想出一种更快的方法来做到这一点。 def inside(x, y, z): r = np.sqrt(x**2 + y**2 + z**2) if r <= 1: return True else: return False
    • 如果原始点在立方体内均匀采样,那将是一个问题,但我是从正态分布中采样的。
    • @ali_m 这是我最终实现的解决方案。 position_list = [] for _ in range(num_pts): vec = np.random.normal(0, 1, 3) # select three random points vec /= np.linalg.norm(vec) # normalize vector vec *= radius # lengthen vector to desired radius position_list.append(list(vec))这看起来对吗?
    • 为什么取正态分布而不是均匀分布?
    • @zabop 请参阅我回答的前两个 cmets。将立方体内的均匀分布投影到球体表面会产生不均匀的密度,因为立方体的“角”中有更多的点。正态分布不会出现这个问题,因为它是球对称的。
    【解决方案2】:

    球体表面上的点可以使用两个球坐标表示,thetaphi0 < theta < 2pi0 < phi < pi

    转换公式为笛卡尔x, y, z坐标:

    x = r * cos(theta) * sin(phi)
    y = r * sin(theta) * sin(phi)
    z = r * cos(phi)
    

    其中r 是球体的半径。

    因此程序可以在thetaphi 的范围内以均匀分布随机抽样,并从中生成笛卡尔坐标。

    但随后这些点在球体的两极上分布得更密集。为了使点在球面上均匀分布,phi 需要选择为phi = acos(a),其中-1 < a < 1 是在均匀分布上选择的。

    对于 Numpy 代码,它与 Sampling uniformly distributed random points inside a spherical volume 中的代码相同,只是变量 radius 具有固定值。

    【讨论】:

    • Theta 和 phi 通常被称为相反的方式,theta 是极角,phi 是方位角:) 也可以generate 3 independent normals 并对结果向量进行归一化。
    【解决方案3】:

    另一种方式,取决于硬件可能更快。

    选择a, b, c 为三个随机数,每个在-1 和1 之间

    计算r2 = a^2 + b^2 + c^2

    如果 r2 > 1.0(=该点不在球体中)或 r2 a, b, c

    否则,您将获得随机点(相对于球心):

    ir = R / sqrt(r2)
    x = a * ir
    y = b * ir
    z = c * ir
    

    【讨论】:

    • 使用统一坐标不会在球体上得到均匀分布:想象一个具有均匀随机点的立方体投影到球体上。这是不对的,你会在角落里有太多的点。 Use normally distributed coordinates instead.
    • 我在角落里没有太多的点,因为我拒绝了 r2 > 1.0 的点。
    • 嗯...是的,对不起,我忽略了那部分。虽然我不确定它是否更快,因为你必须拒绝很多点,但你是对的。请编辑您的帖子,以便我可以删除我的反对票:)
    • 它通常比那些三角函数快得多。我只拒绝 1.0 - 4/3*π / 8 = 48% 的点(加上中心附近的一些非常小的体积,以避免在投影到球体表面时除以零)。
    • 是的,原点周围的小部分影响不大。我在考虑生成 3 个正态分布变量的版本,但老实说,我不知道其中涉及的计算工作是什么:) 无论如何,你的解决方案绝对是正确的和新的。在我之前的评论中,我只是说你应该做一些琐碎的编辑,这样我的反对票就会解锁。
    【解决方案4】:

    在与@Soonts 进行一些讨论后,我对答案中使用的三种方法的性能感到好奇:一种生成随机角度,一种使用正态分布坐标,另一种拒绝均匀分布的点。

    这是我尝试的比较:

    import numpy as np
    
    def sample_trig(npoints):
        theta = 2*np.pi*np.random.rand(npoints)
        phi = np.arccos(2*np.random.rand(npoints)-1)
        x = np.cos(theta) * np.sin(phi)
        y = np.sin(theta) * np.sin(phi)
        z = np.cos(phi)
        return np.array([x,y,z])
    
    def sample_normals(npoints):
        vec = np.random.randn(3, npoints)
        vec /= np.linalg.norm(vec, axis=0)
        return vec
    
    def sample_reject(npoints):
        vec = np.zeros((3,npoints))
        abc = 2*np.random.rand(3,npoints)-1
        norms = np.linalg.norm(abc,axis=0) 
        mymask = norms<=1
        abc = abc[:,mymask]/norms[mymask]
        k = abc.shape[1]
        vec[:,0:k] = abc
        while k<npoints:
           abc = 2*np.random.rand(3)-1
           norm = np.linalg.norm(abc)
           if 1e-5 <= norm <= 1:  
               vec[:,k] = abc/norm
               k = k+1
        return vec
    

    那么1000分

    In [449]: timeit sample_trig(1000)
    1000 loops, best of 3: 236 µs per loop
    
    In [450]: timeit sample_normals(1000)
    10000 loops, best of 3: 172 µs per loop
    
    In [451]: timeit sample_reject(1000)
    100 loops, best of 3: 13.7 ms per loop
    

    请注意,在基于拒绝的实现中,我首先生成了npoints 样本并丢弃了不好的样本,并且我只使用了一个循环来生成其余的点。似乎是直接逐步拒绝需要更长的时间。我还删除了除零检查,以便与sample_normals 案例进行更清晰的比较。


    从这两种直接方法中删除矢量化会使它们进入同一个球场:

    def sample_trig_loop(npoints):
        x = np.zeros(npoints)
        y = np.zeros(npoints)
        z = np.zeros(npoints)
        for k in range(npoints):
            theta = 2*np.pi*np.random.rand()
            phi = np.arccos(2*np.random.rand()-1)
            x[k] = np.cos(theta) * np.sin(phi)
            y[k] = np.sin(theta) * np.sin(phi)
            z[k] = np.cos(phi)
        return np.array([x,y,z])
    
    def sample_normals_loop(npoints):
        vec = np.zeros((3,npoints))
        for k in range(npoints):
          tvec = np.random.randn(3)
          vec[:,k] = tvec/np.linalg.norm(tvec)
        return vec
    
    In [464]: timeit sample_trig(1000)
    1000 loops, best of 3: 236 µs per loop
    
    In [465]: timeit sample_normals(1000)
    10000 loops, best of 3: 173 µs per loop
    
    In [466]: timeit sample_reject(1000)
    100 loops, best of 3: 14 ms per loop
    
    In [467]: timeit sample_trig_loop(1000)
    100 loops, best of 3: 7.92 ms per loop
    
    In [468]: timeit sample_normals_loop(1000)
    100 loops, best of 3: 10.9 ms per loop
    

    【讨论】:

      【解决方案5】:

      (经过编辑以反映来自 cmets 的更正)

      我在 2004 年研究了几个常数时间方法来解决这个问题。

      假设您在球坐标中工作,其中theta 是围绕垂直轴的角度(例如经度),phi 是从赤道升起的角度(例如纬度), 然后要在赤道以北的半球上获得均匀分布的随机点,请执行以下操作:

      1. 选择theta = rand(0, 360)。
      2. 选择phi = 90 * (1 - sqrt(rand(0, 1)))。

      要在球体而不是半球上获得点,然后在 50% 的时间内简单地否定 phi

      出于好奇,类似的方法适用于在单位磁盘上生成均匀分布的点:

      1. 选择theta = rand(0, 360)。
      2. 选择radius = sqrt(rand(0, 1))。

      我没有证据证明这些方法的正确性, 但在过去十年左右的时间里,我使用它们取得了很大的成功,并且确信它们的正确性。

      各种方法的一些说明(从 2004 年开始)是 here,包括在立方体表面上选择点并将它们归一化到球体上的方法的可视化。

      【讨论】:

      • 我不确定我是否理解你的方法。如果我在纸上计算它,(半)球上的概率密度似乎与上述方法不一致。更糟糕的是,如果我尝试重现您的计算,那么this is what I get:两极点太多,赤道点太少(与纸上的结果相同)。代码:figure; N=5000; theta=2*pi*rand(1,N); phi=pi/2*[sqrt(rand(1,N/2)), -sqrt(rand(1,N/2))]; plot3(cos(phi).*cos(theta),cos(phi).*sin(theta),sin(phi),'.'); axis equal; axis vis3d
      • @AndrasDeak 嗯,是的,这看起来不对。 rand(1, N)rand(1, N/2) 返回什么?这种方法肯定假设 sqrt 内的值是 [0, 1] 范围内的均匀分布。
      • 对不起,忘了这是一个numpy线程,上面是matlab...rand(1,N)rand(1,N/2)产生长度为NN/2的向量(分别),每个元素统一[0, 1]。 IE。与numpy.random.rand(1,N) 等相同。
      • @AndrasDeak - 我欠你的。我能够重现您的结果。我的公式有误; Phi 应选择为90 * (1 - sqrt(rand(0, 1))。我怀疑错误是由于我不正确的球面->笛卡尔映射造成的。我已经实现了一个 WebGL 演示(使用您的映射公式)here
      • 我相信我解开了这个谜团:)如果您以弧度为单位测量 phi,则您使用的概率密度不均匀,但与 (1-2/pi*phi)/cos(phi) 成正比。虽然这不是恒定的,it's pretty close。所以你近似均匀分布。通过比较半球上生成的大样本中的mean(z),您可以看到它实际上并不均匀。在真正统一的情况下(如正态分布坐标),您会得到0.50,但使用您的代码会得到0.46。足够接近以至于在视觉上不明显,但统一:)
      猜你喜欢
      • 2020-05-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-22
      • 2023-03-19
      • 2011-02-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多