在圆内均匀生成一个随机点

301

我需要在半径为R的圆内生成一个均匀随机点。

我意识到,仅通过在区间[0 ... 2π)内选择一个均匀随机角度,并在区间(0 ... R)内选择一个均匀随机半径,我会得到更多指向圆心的点,因为对于给定半径的两个点而言,较小半径的点之间的距离会比较大半径的点之间的距离更近。

我在这篇博客文章中找到了相关内容,但我不理解他的推理。我想这是正确的,但我真的很想知道他从哪里得到 (2/R2r并如何得出最终解决方案。


更新:在发布这个问题7年后,我仍然没有得到关于平方根算法背后数学原理的满意答复。所以我花了一天时间自己写了一个答案。我的回答链接


21
拒绝采样的缺点真的很严重吗?所需尝试次数的期望值为4/π≈1.27,需要尝试超过k次的概率为(1-π/4)^k。对于[k=20](http://www.wolframalpha.com/input/?i=%281-%CF%80/4%29%5E20),这个概率约为.00000000000004,而对于k=50,它的数量级是10^{-34}。你可以在任何时候接受这些赔率;你会做得很好。 - ShreevatsaR
3
实际上,拒绝抽样确实提供了终止的保证。你的算法永远不会无限循环的可能性是无限小的(确切地说是零)。 - Jared Nielsen
3
依我看,拒绝采样方法的缺陷重要性与能够避免使用该方法而采用其他抽样方法的易用程度成正比。在这种情况下,由于无需使用拒绝采样即可轻松实现抽样,所以该缺陷变得尤为重要。 - spex
6
实际上,拒绝采样技术更快,因为它避免了需要进行超越函数计算的步骤。 - pjs
5
拒绝抽样的失败率为27%(4 / pi-1),所需随机数比btilly多27%但比sigfpe少15%。所有实验的平均值和标准偏差完全相同(保留3个有效数字)。这证实了pjs和其他人的评论,即除非生成随机数的成本非常高,否则拒绝抽样可能是最佳方法。 - Peter Davidson
显示剩余4条评论
23个回答

4

以下内容或许能帮助对算法速度有兴趣的人选择合适的算法:最快的方法可能是拒绝采样。

只需在单位正方形内生成一个点并拒绝它,直到它在圆内。例如(伪代码),

def sample(r=1):
    while True:
        x = random(-1, 1)
        y = random(-1, 1)
        if x*x + y*y <= 1:
            return (x, y) * r

虽然有时可能会运行一次或两次以上(并且它不是常数时间或适合并行执行),但它比使用复杂公式如sincos要快得多。


1
我认为这个建议并没有被提出,因为它并没有回答问题。 - aioobe
你说得没错,但这个问题是从“圆内随机点”搜索中出现的 - 考虑到该问题的普及程度,它可能对其他人有用。 - Max
1
没错,我只是在回答“我不知道为什么这还没有被建议”的部分。顺便说一下,如果你关注执行速度,可以省略对 sqrt(...) 的调用。(当且仅当 x <= 1 时,sqrt(x) 将是 <= 1 - aioobe
是的,你两个方面都是正确的。实际上,我有一瞬间怀疑自己,因为 Wolfram Alpha 说它们不相等(当时已经很晚了)。 - Max

4
我不确定这是否比Sqrt更快的方法:
R = max(random(), random())
theta = random() * 2 * PI

然后:
x = centerX + r * cos(theta)
y = centerY + r * sin(theta)

为什么它有效? < p > < code > sqrt(random()) 和 < code > max(random(), random()) 的PDF是相同的。 < / p >

时间比较:

对我来说真奇怪!至少在我提供的代码中,这并不比sqrt更快。 enter image description here

以下是这些函数:

def f1(n):
    for i in range(n):
        x, y = random.random(), random.random()
        x = x if x > y else y
def f2(n):
    for i in range(n):
        x = math.sqrt(random.random())

这是绘图代码。
import random
import timeit
import numpy as np
import pandas as pd
import seaborn as sns
import math

l = 1000
repeat = 10

x_values = list(range(1, l))
y_values_func1 = []
y_values_func2 = []

for n in range(1, l):
  if not n % 100: print(n)
  y1, y2 = 0, 0
  y_values_func1.append(timeit.timeit(lambda: f1(n), number=repeat)/repeat)
  y_values_func2.append(timeit.timeit(lambda: f2(n), number=repeat)/repeat)



data = pd.DataFrame({
    'Input Size': x_values * 2,
    'Execution Time': y_values_func1 + y_values_func2,
    'Function': ['Max'] * len(x_values) + ['Sqrt'] * len(x_values)
})

# Use Seaborn to create the regression plots without scatter points
sns.lmplot(x='Input Size', y='Execution Time', hue='Function', data=data, scatter=False)

1
这个会更有说服力如果有一些时间数据。(我天真地期望sqrt比额外的random调用要快,但是我还没有运行过时间测试。)顺便问一下,为什么你把这个答案发了两次?你想删除之前的版本吗? - Mark Dickinson
1
@MarkDickinson 已更新。 - Peyman
1
使用math.sqrt,而不是np.sqrt!(除非你想将sqrt应用于NumPy数组,当然,在这种情况下请使用np.sqrt。但这不是发生在这里的情况。) - Mark Dickinson
1
几个评论/问题:
  1. 你能展示一下你的计时代码吗?
  2. 为什么你要为max_f做这么多次赋值?使用x = max(random.random(), random.random())会更高效吧?
  3. 我认为速度应该很大程度上取决于所使用的伪随机数生成器(PRNG)。例如,xoshiro/xoroshiro类的生成器与Random的MT19937相比在性能上非常出色,并且在统计上与之相匹配甚至超越。你可以使用numpy的新位生成器架构来测试PRNG的影响。
  4. Python很流行,但你的发现可能不适用于其他编程语言。
- pjs
1
@pjs 1) 已添加。2) 不会的。Python的max函数不针对两个变量进行优化。3, 4) 是的,你是对的。这在很大程度上取决于伪随机数生成器(PRNG)。 - Peyman
显示剩余2条评论

3

Java语言解决方案及示例代码(2000分)

public void getRandomPointInCircle() {
    double t = 2 * Math.PI * Math.random();
    double r = Math.sqrt(Math.random());
    double x = r * Math.cos(t);
    double y = r * Math.sin(t);
    System.out.println(x);
    System.out.println(y);
}

分布式2000点

基于之前解决方案https://dev59.com/wG025IYBdhLWcg3wtobX#5838055,来自@sigfpe的


2

首先,我们生成一个 cdf[x],它是指距离圆心距离为 x 的点的概率。假设圆的半径为 R。

显然,如果 x 是零,则 cdf[0] = 0

显然,如果 x 是 R,则 cdf[R] = 1

显然,如果 x = r,则 cdf[r] = (Pi r^2)/(Pi R^2)

这是因为圆上每个“小区域”被选中的概率相同,因此概率与相关区域的面积成比例。给定距离圆心距离为 x 的区域的面积为 Pi r^2

所以 cdf[x] = x^2/R^2,因为 Pi 相互抵消了

我们有 cdf[x]=x^2/R^2,其中 x 范围从 0 到 R

因此,我们解出 x

R^2 cdf[x] = x^2

x = R Sqrt[ cdf[x] ]

我们现在可以用0到1之间的随机数来替换cdf。
x = R Sqrt[ RandomReal[{0,1}] ]

最后
r = R Sqrt[  RandomReal[{0,1}] ];
theta = 360 deg * RandomReal[{0,1}];
{r,theta}

我们获得了极坐标 {0.601168 R,311.915度}。

我们现在可以用0到1之间的随机数来替换cdf。为什么可以这样做? - Amit Portnoy

2
我曾经使用过这种方法: 这可能完全没有优化(例如,它使用点的数组,因此对于大圆形来说不可用),但足以产生足够随机的分布。如果您希望这样做,可以跳过矩阵的创建直接绘制。该方法是将圆形内所有落在矩形内的点随机化。
bool[,] getMatrix(System.Drawing.Rectangle r) {
    bool[,] matrix = new bool[r.Width, r.Height];
    return matrix;
}

void fillMatrix(ref bool[,] matrix, Vector center) {
    double radius = center.X;
    Random r = new Random();
    for (int y = 0; y < matrix.GetLength(0); y++) {
        for (int x = 0; x < matrix.GetLength(1); x++)
        {
            double distance = (center - new Vector(x, y)).Length;
            if (distance < radius) {
                matrix[x, y] = r.NextDouble() > 0.5;
            }
        }
    }

}

private void drawMatrix(Vector centerPoint, double radius, bool[,] matrix) {
    var g = this.CreateGraphics();

    Bitmap pixel = new Bitmap(1,1);
    pixel.SetPixel(0, 0, Color.Black);

    for (int y = 0; y < matrix.GetLength(0); y++)
    {
        for (int x = 0; x < matrix.GetLength(1); x++)
        {
            if (matrix[x, y]) {
                g.DrawImage(pixel, new PointF((float)(centerPoint.X - radius + x), (float)(centerPoint.Y - radius + y)));
            }
        }
    }

    g.Dispose();
}

private void button1_Click(object sender, EventArgs e)
{
    System.Drawing.Rectangle r = new System.Drawing.Rectangle(100,100,200,200);
    double radius = r.Width / 2;
    Vector center = new Vector(r.Left + radius, r.Top + radius);
    Vector normalizedCenter = new Vector(radius, radius);
    bool[,] matrix = getMatrix(r);
    fillMatrix(ref matrix, normalizedCenter);
    drawMatrix(center, radius, matrix);
}

enter image description here


5
分布并不会有“足够随机”的说法。对于给定的随机定义,它们要么是随机的,要么不是随机的。您的回答有些含糊不清:您没有评论您的代码,也没有解释您是如何得出结论的。含糊的回答很难理解,也更难被信任。 - Richard

1
您也可以运用直觉。
一个圆的面积是pi*r^2。
当r=1时,
这给我们一个面积为pi。假设我们有某种函数f,它会在圆内均匀分布N=10个点。这里的比率是10/pi。
现在我们将面积和点数翻倍,
当r=2且N=20时,
这给出了一个面积为4pi,比率现在为20/4pi或10/2pi。比率随着半径的增加而变得越来越小,因为它的增长是二次的,而N则按线性比例缩放。
为了解决这个问题,我们可以说:
x = r^2
sqrt(x) = r

如果您想这样生成极坐标下的向量
length = random_0_1();
angle = random_0_2pi();

更多的点会落在中心附近。
length = sqrt(random_0_1());
angle = random_0_2pi();

length 不再是均匀分布的,但向量现在将是均匀分布的。


1
在圆形中的面积元素是dA=rdr*dphi。额外的因子r破坏了您随机选择r和phi的想法。虽然phi是平坦分布的,但r不是,而是平坦分布在1/r上(即您更有可能击中边界而不是“靶心”)。
因此,要生成均匀分布在圆上的点,请从平坦分布中选择phi,并从1/r分布中选择r。
或者使用Mehrdad提出的蒙特卡罗方法。
编辑
要在1/r中平坦地选择随机r,可以从区间[1/R,无穷大]中选择随机x并计算r=1/x。然后,r在1/r中平坦分布。
要计算随机phi,请从区间[0,1]中选择随机x并计算phi=2*pi*x。

我应该如何从“1/r分布”中准确地选择r - aioobe

0

这是一个有趣的问题。
在轴原点距离增加时,点被选中的概率降低的原理在上面已经解释了多次。我们通过取U [0,1]的根来考虑这一点。 以下是Python 3中正数r的通用解决方案。

import numpy
import math
import matplotlib.pyplot as plt

def sq_point_in_circle(r):
    """
    Generate a random point in an r radius circle 
    centered around the start of the axis
    """

    t = 2*math.pi*numpy.random.uniform()
    R = (numpy.random.uniform(0,1) ** 0.5) * r

    return(R*math.cos(t), R*math.sin(t))

R = 200 # Radius
N = 1000 # Samples

points = numpy.array([sq_point_in_circle(R) for i in range(N)])
plt.scatter(points[:, 0], points[:,1])

enter image description here


0

半径和“附近”该半径的点数之间存在线性关系,因此他需要使用一个半径分布,使得在半径r附近的数据点数量与r成比例。


0

我仍然不确定确切的“(2/R2)×r”是什么,但显而易见的是,在给定单位“dr”中需要分布的点数,即r的增加将与r2成比例,而不是r。

检查这种方式...在某个角度θ和r(0.1r到0.2r之间)之间的点数,即r的一部分和r(0.6r到0.7r之间)之间的点数将相等,如果您使用标准生成,则差异仅为两个间隔之间的0.1r。但是,由于在点之间覆盖的区域(从0.6r到0.7r)要比在0.1r到0.2r之间覆盖的区域大得多,因此相等数量的点将稀疏地分布在更大的区域中,我认为您已经知道了这一点。因此,生成随机点的函数必须不是线性的,而是二次的(因为需要在给定单位'dr'中分布所需的点数与r²成比例,而不是r),因此在这种情况下,它将是二次的倒数,因为我们有(0.1r)的增量在两个间隔中都必须是某个函数的平方,以便它可以作为点的线性生成的种子值(因为之后,该种子在sin和cos函数中被线性使用),因此我们知道,dr必须是二次值,并且为了使这个种子二次,我们需要从r的平方根而不是r本身开始,我希望这能让它更清晰一些。


你是第一个在这里引用毕达哥拉斯定理的人。如果你能加上一两个图表来支持你的解释,我会很高兴。现在我很难跟上 :-( - aioobe
@aioobe 我已经尝试重新表述答案,如果您需要,我可以添加图表 :) - cheesefest
我知道为什么不能线性地展开它。我在这里不明白的是与毕达哥拉斯或sin/cos的联系。也许图表可以帮助我理解这个问题。 - aioobe
毕达哥拉斯是我的错误,请忘记它,但希望您理解函数的二次性质,确切的(2/R2)×r需要证明,我无法提供任何证明。 - cheesefest

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接