在单位圆和单位正方形之间高效生成点

机器算法验证 可能性 采样 蒙特卡洛 随机生成
2022-02-05 20:08:02

我想从此处定义的蓝色区域生成样本:

在此处输入图像描述

天真的解决方案是在单位平方中使用拒绝采样,但这仅提供 (~21.4%) 的效率。1π/4

有什么方法可以更有效地采样吗?

3个回答

我提出了以下解决方案,它应该比 @cardinal、@whuber 和 @stephan-kolassa 迄今为止的其他解决方案更简单、更高效和/或计算成本更低。

它涉及以下简单步骤:

1) 绘制两个标准均匀样本:

u1Unif(0,1)u2Unif(0,1).

应用以下剪切变换 (右下三角中的点反映到左上三角,它们将是“un-反映”在 2b): min{u1,u2},max{u1,u2}

[xy]=[11]+[2212210][min{u1,u2}max{u1,u2}].

2b)如果xyu1>u2

3)如果在单位圆内(接受率应该在72%左右),即:

x2+y2<1.

该算法背后的直觉如图所示。 在此处输入图像描述

步骤 2a 和 2b 可以合并为一个步骤:

2) 应用剪切变换并交换

x=1+22min(u1,u2)u2y=1+22min(u1,u2)u1

以下代码实现了上述算法(并使用@whuber 的代码对其进行了测试)。

n.sim <- 1e6
x.time <- system.time({
    # Draw two standard uniform samples
    u_1 <- runif(n.sim)
    u_2 <- runif(n.sim)
    # Apply shear transformation and swap
    tmp <- 1 + sqrt(2)/2 * pmin(u_1, u_2)
    x <- tmp - u_2
    y <- tmp - u_1
    # Reject if inside circle
    accept <- x^2 + y^2 > 1
    x <- x[accept]
    y <- y[accept]
    n <- length(x)
})
message(signif(x.time[3] * 1e6/n, 2), " seconds per million points.")
#
# Plot the result to confirm.
#
plot(c(0,1), c(0,1), type="n", bty="n", asp=1, xlab="x", ylab="y")
rect(-1, -1, 1, 1, col="White", border="#00000040")
m <- sample.int(n, min(n, 1e4))
points(x[m],y[m], pch=19, cex=1/2, col="#0000e010")

一些快速测试会产生以下结果。

算法https://stats.stackexchange.com/a/258349最佳 3:每百万分 0.33 秒。

这个算法。最佳 3:每百万分 0.18 秒。

每秒200万点会做吗?

分布是对称的:我们只需要计算出整个圆的八分之一的分布,然后将其复制到其他八分圆周围。在极坐标中,随机位置在值的累积分布之间的面积给出延伸到的圆弧因此它与(r,θ)Θ(X,Y)θ(0,0),(1,0),(1,tanθ)(1,0)(cosθ,sinθ)

FΘ(θ)=Pr(Θθ)12tan(θ)θ2,

它的密度从何而来

fΘ(θ)=ddθFΘ(θ)tan2(θ).

我们可以使用拒绝方法(效率为)从这个密度中采样。8/π254.6479%

径向坐标之间成正比这可以通过 CDF 的简单反演进行采样。Rrdrr=1r=secθ

如果我们生成独立样本,则转换回笛卡尔坐标对这个八分圆进行采样。因为样本是独立的,所以随机交换坐标会根据需要从第一象限产生一个独立的随机样本。(随机交换只需要生成一个二项式变量来确定要交换多少个实现。)(ri,θi)(xi,yi)

的每个这样的实现平均需要一个统一变量(对于)加上乘以两个统一变量(对于)和少量(快速)计算。这是每个点的变量(当然,有两个坐标)。完整的详细信息在下面的代码示例中。这个数字绘制了超过 50 万个点中的 10,000 个。(X,Y)R1/(8π2)Θ4/(π4)4.66

数字

这是R生成此模拟并对其进行计时的代码。

n.sim <- 1e6
x.time <- system.time({
  # Generate trial angles `theta`
  theta <- sqrt(runif(n.sim)) * pi/4
  # Rejection step.
  theta <- theta[runif(n.sim) * 4 * theta <= pi * tan(theta)^2]
  # Generate radial coordinates `r`.
  n <- length(theta)
  r <- sqrt(1 + runif(n) * tan(theta)^2)
  # Convert to Cartesian coordinates.
  # (The products will generate a full circle)
  x <- r * cos(theta) #* c(1,1,-1,-1)
  y <- r * sin(theta) #* c(1,-1,1,-1)
  # Swap approximately half the coordinates.
  k <- rbinom(1, n, 1/2)
  if (k > 0) {
    z <- y[1:k]
    y[1:k] <- x[1:k]
    x[1:k] <- z
  }
})
message(signif(x.time[3] * 1e6/n, 2), " seconds per million points.")
#
# Plot the result to confirm.
#
plot(c(0,1), c(0,1), type="n", bty="n", asp=1, xlab="x", ylab="y")
rect(-1, -1, 1, 1, col="White", border="#00000040")
m <- sample.int(n, min(n, 1e4))
points(x[m],y[m], pch=19, cex=1/2, col="#0000e010")

好吧,可以更高效地完成,但我当然希望你不是在寻找更快的.

这个想法是首先对上方的垂直蓝色切片的长度成比例:xx

f(x)=11x2.

Wolfram 帮助您整合

0xf(y)dy=12x1x2+x12arcsinx.

所以累积分布函数将是这个表达式,缩放到积分到 1(即除以)。F01f(y)dy

现在,要生成您的值,请选择一个随机数,均匀分布在之间。然后找到使得也就是说,我们需要反转 CDF(逆变换采样)。这可以做到,但并不容易。也不快。xt01xF(x)=t

最后,给定之间均匀分布的随机xy1x21

下面是R代码。请注意,我在值的网格中预先评估 CDF,即使这样,这也需要几分钟。x

如果您投入一些思考,您可能可以大大加快 CDF 反转的速度。再一次,思考很痛苦。我个人会选择拒绝抽样,它更快且更不容易出错,除非我有很好的理由不这样做。

epsilon <- 1e-6
xx <- seq(0,1,by=epsilon)
x.cdf <- function(x) x-(x*sqrt(1-x^2)+asin(x))/2
xx.cdf <- x.cdf(xx)/x.cdf(1)

nn <- 1e4
rr <- matrix(nrow=nn,ncol=2)
set.seed(1)
pb <- winProgressBar(max=nn)
for ( ii in 1:nn ) {
    setWinProgressBar(pb,ii,paste(ii,"of",nn))
    x <- max(xx[xx.cdf<runif(1)])
    y <- runif(1,sqrt(1-x^2),1)
    rr[ii,] <- c(x,y)
}
close(pb)

plot(rr,pch=19,cex=.3,xlab="",ylab="")

随机数