Python 生成分布在单位球体表面上的点的随机样本

Python 生成分布在单位球体表面上的点的随机样本,python,numpy,geometry,random-sample,uniform-distribution,Python,Numpy,Geometry,Random Sample,Uniform Distribution,我正在尝试使用numpy在球体表面生成随机点。我已经审阅了解释统一分配的帖子。但是,您需要了解如何仅在球体表面上生成点。我有坐标(x,y,z)和每个球体的半径 我不太精通这一级别的数学,并试图理解蒙特卡罗模拟 任何帮助都将不胜感激 谢谢, 球面上的点可以用两个球坐标表示,theta和phi,其中0

我正在尝试使用numpy在球体表面生成随机点。我已经审阅了解释统一分配的帖子。但是,您需要了解如何仅在球体表面上生成点。我有坐标(x,y,z)和每个球体的半径

我不太精通这一级别的数学,并试图理解蒙特卡罗模拟

任何帮助都将不胜感激

谢谢,
球面上的点可以用两个球坐标表示,
theta
phi
,其中
0
0

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

x = r * cos(theta) * sin(phi)
y = r * sin(theta) * sin(phi)
z = r * cos(phi)
其中
r
是球体的半径

因此,该程序可以在其范围内以均匀分布随机采样θ
和φ
,并由此生成笛卡尔坐标

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


对于Numpy代码,除了变量
radius
有一个固定值外,它与中的相同。

另一种方式是,根据硬件的不同,速度可能会快得多

选择
a、b、c
为三个分别介于-1和1之间的随机数

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

如果r2>1.0(=该点不在球体中)或r2<0.00001(=该点太靠近中心,我们将在投影到球体表面时进行零除),则丢弃这些值,然后选择另一组随机
a、b、c

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

基于,您只需从三个标准正态分布生成一个由独立样本组成的向量,然后对向量进行规格化,使其大小为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
)或高维单位超球面的表面上拾取均匀分布的点。

在与@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
请注意,在基于拒绝的实现中,我首先生成了
npoints
样本并丢弃了不好的样本,而我只使用了一个循环来生成其余的点。似乎情况是,直接逐步拒绝需要更长的时间。我还删除了除法为零的检查,以便与
sample\u 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
(编辑以反映评论中的更正)

2004年,我研究了一些解决这个问题的固定时间方法

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

  • 选择θ=rand(0360)
  • 选择
    phi
    =90*(1-sqrt(rand(0,1)))
  • 要在球体而不是半球上获取点,只需在50%的时间内对φ求反即可

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

  • 选择θ=rand(0360)
  • 选择
    radius
    =sqrt(rand(0,1))
  • 我没有证据证明这些方法的正确性, 但我在过去十年左右的时间里成功地使用了它们,我确信它们是正确的


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

    θ和φ通常被称为另一种方法,使用θ表示极角,φ表示方位角:)也可以对结果向量进行分析和规格化。使用统一坐标不会在球体上提供均匀分布:想象一个立方体,具有均匀随机点,投影到球体上。那是不对的,你在角落里会有太多的分数。我没有太多的角点,因为我拒绝r2>1.0的点。嗯。。。是的,对不起,我忽略了那部分。虽然我不确定它是否更快,因为你不得不拒绝很多分数,但你是对的。请编辑你的帖子,这样我就可以删除我的反对票:)它通常比那些三角函数快得多。我只拒绝1.0-4/3*π/8=48%的点(加上靠近中心的一些非常小的体积,以避免投影到球体表面时被零除)。是的,原点周围的小部分影响不大。我在考虑生成3个正态分布变量的版本,但老实说,我不知道其中涉及的计算工作量:)无论如何,你的解决方案绝对是正确的和新的。在我之前的评论中,我的意思是你应该做一些琐碎的编辑,这样我的否决票就可以解锁了。我不确定我是否理解你的方法。如果我在纸上计算的话,(半球)上的概率密度似乎与上述方法不一致。更糟糕的是,如果我试图重现你的计算,那么:极点的点太多,极点的点太少
    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
    
    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