球体上的随机点


31

挑战

编写一个程序或函数,该程序或函数不输入任何内容,并在理论上均匀的随机方向上输出长度为的向量。1

这等效于所描述的球面上的随机点

x2+y2+z2=1

导致这样的分布

半径为1的球体上点的随机分布。

输出量

从理论上均匀的随机分布中得出三个浮点数,方程满足精度极限。x2+y2+z2=1

挑战备注

  • 理论上,随机分布必须是均匀的。也就是说,如果将伪随机数生成器替换为数中的真实RNG ,则会导致球体上点的均匀随机分布。
  • 从均匀分布中生成三个随机数并将它们归一化是无效的:将会对三维空间的各个角产生偏差。
  • 同样,从均匀分布中生成两个随机数并将其用作球面坐标也是无效的:向球体的极点会产生偏差。
  • 适当的均匀性可以通过算法实现,这些算法包括但不限于:
    • 根据附近的正态(高斯)分布生成三个随机数,和并将其标准化。 xyz0
    • 根据范围内的均匀分布生成三个随机数,和xyz(1,1)。通过l = 计算向量的长度l=x2+y2+z2。然后,如果l>1,则拒绝该向量并生成一组新的数字。否则,如果l1,标准化载体并返回结果。
    • 生成两个随机数ij均匀的范围内分布(0,1)并将其转换为球面坐标,像这样:
      θ=2×π×iϕ=cos1(2×j1)
      因此可以通过 x计算xyz
      x=cos(θ)×sin(ϕ)y=sin(θ)×sin(ϕ)z=cos(ϕ)
  • 在您的答案中提供您正在使用的算法的简短描述。
  • MathWorld上阅读有关球体点拾取的更多信息。

输出示例

[ 0.72422852 -0.58643067  0.36275628]
[-0.79158628 -0.17595886  0.58517488]
[-0.16428481 -0.90804027  0.38532243]
[ 0.61238768  0.75123833 -0.24621596]
[-0.81111161 -0.46269121  0.35779156]

一般说明


可以在[-1,1]中均匀选择3个实部,然后拒绝它们(并重复),如果它们的平方和不为1可以吗?
Grimmy

6
@肮脏的我喜欢那个漏洞。不,这是不允许的,因为理论上任何输出的机会都是零。
吉特

@Grimy的建议是否与您提到的第二个示例实现类似?从理论上讲,该解决方案也不会产生任何产出的机会
Saswat Padhi

2
@SaswatPadhi不,那有可能pi/6 ≈ 0.5236产生输出。这是单位面积立方体中所刻的球体面积
Luis

1
@LuisMendo我明白了,对。如您所述,在这种情况下,概率约为0.5。对于Grimy的建议,约为0。
Saswat Padhi

Answers:



24

R,23个字节

x=rnorm(3)
x/(x%*%x)^.5

在线尝试!

生成的3层的实现N(0,1)分布和归一化所得的矢量。

1000个实现的图解:

在此处输入图片说明


2
您能否证明3轴正态分布导致球体上均匀分布?(我看不到)
Jeffrey支持Monica

4
@Jeffrey在概率/统计上是众所周知的;但对于2D的证明(其整齐地延伸至3个维度)近似为:让和独立的。然后f Xx = K e 1X,YN(0,1)fYy=Ke1fX(x)=Ke12x2,所以通过独立性fXYxy=K2e1fY(y)=Ke12y2其中ž=Xÿ,所以很明显的分布Ž仅取决于幅度Ž,因此方向是均匀分布的。fXY(x,y)=K2e12(x2+y2)=fZ(z)=K2e12z2z=(x,y)zz
朱塞佩

1
因此,正态分布为我们提供了均匀分布的点周围的圈子,和幅度将确保点位于
朱塞佩·

23

x86-64机器代码-63 62 55 49字节

6A 4F                push        4Fh  
68 00 00 80 3F       push        3F800000h  
C4 E2 79 18 4C 24 05 vbroadcastss xmm1,dword ptr [rsp+5]  
rand:
0F C7 F0             rdrand      eax  
73 FB                jnc         rand  
66 0F 6E C0          movd        xmm0,eax  
greaterThanOne:
66 0F 38 DC C0       aesenc      xmm0,xmm0  
0F 5B C0             cvtdq2ps    xmm0,xmm0  
0F 5E C1             divps       xmm0,xmm1  
C4 E3 79 40 D0 7F    vdpps       xmm2,xmm0,xmm0,7Fh  
0F 2F 14 24          comiss      xmm2,dword ptr [rsp]  
75 E9                jne         greaterThanOne
58                   pop         rax  
58                   pop         rax  
C3                   ret  

使用第二种算法,经过修改。返回[x, y, z, 0]xmm0 中的向量。

说明:

push 4Fh
push 3f800000h

将1和2 ^ 31的值作为浮点值压入堆栈。由于符号扩展,数据重叠,节省了几个字节。

vbroadcastss xmm1,dword ptr [rsp+5] 将2 ^ 31的值加载到xmm1的4个位置。

rdrand      eax  
jnc         rand  
movd        xmm0,eax

生成随机的32位整数并将其加载到xmm0的底部。

aesenc      xmm0,xmm0  
cvtdq2ps    xmm0,xmm0  
divps       xmm0,xmm1 

生成一个随机的32位整数,将其转换为浮点数(有符号),然后除以2 ^ 31以得到-1和1之间的数字。

vdpps xmm2,xmm0,xmm0,7Fh单独使用点乘积来添加下方3个浮点的平方,从而遮盖顶部浮点。这给出了长度

comiss      xmm2,dword ptr [rsp]  
jne          rand+9h (07FF7A1DE1C9Eh)

将长度的平方与1比较,如果不等于1,则拒绝该值。如果长度的平方为1,则长度也为1。这意味着向量已经被规范化并保存平方根和除法。

pop         rax  
pop         rax 

恢复堆栈。

ret 返回值xmm0

在线试用


7
+1 aesenc用于产生128个“随机”位非常漂亮。
DocMax

13

Python 2,86个字节

from random import*;R=random
z=R()*2-1
a=(1-z*z)**.5*1j**(4*R())
print a.real,a.imag,z

在线尝试!

从-1到1均匀地生成z坐标。然后在radius的圆上均匀地采样x和y坐标(1-z*z)**.5

球面分布在z坐标上(因此在每个坐标上)均是因子均匀的,这可能并不明显。这是尺寸3的特殊之处。请参见证明球体水平切片的表面积与其高度成正比的证明。尽管赤道附近的切片具有较大的半径,但极点附近的切片的标题更多地向内,并且事实证明这两个效果完全抵消。

为了在该圆上生成一个随机角度,我们将虚数单位提高1j到0到4之间的均匀随机幂,这使我们免于需要三角函数pi或e,而其中的任何一个都需要输入。然后,我们提取真实的虚部。如果我们可以为两个坐标输出复数,则最后一行可能只是print a,z


86字节

from random import*
a,b,c=map(gauss,[0]*3,[1]*3)
R=(a*a+b*b+c*c)**.5
print a/R,b/R,c/R

在线尝试!

生成三个法线并缩放结果。


带有numpy的Python 2,57个字节

from numpy import*
a=random.randn(3)
print a/sum(a*a)**.5

在线尝试!

sum(a*a)**.5比短linalg.norm(a)。我们也可以做dot(a,a)与相同的长度sum(a*a)。在Python 3中,可以简化为a@a使用new运算符@


1
我喜欢你的第一种方法。我很难理解如果z不改变均匀分布下如何避免偏向赤道的问题。
吉特

2
@Jitse球面分布在每个坐标上的因子均一。这是尺寸3的特殊之处。例如,参见证明球体切片的表面积与其高度成正比的证明。关于直觉偏向赤道的直觉,请注意,虽然赤道附近的切片具有较大的半径,但极点附近的切片的标题更多地向内,这提供了更大的面积,事实证明这两个效果完全抵消。
xnor

非常好!感谢您的澄清和参考。
吉特

@Jitse谢谢,我将其添加到身体中。我意识到虽然我只是采样了正数z,然后将其修复了几个字节。
xnor

1
@Jitse的确,球体的表面积等于封闭圆柱体的侧面表面积!
尼尔

13

八度40 33 22字节

我们采样形成3d标准正态分布,并对向量进行归一化:

(x=randn(1,3))/norm(x)

在线尝试!


对于只有八度(即不MATLAB),你可以保存一个字节这样
汤姆·卡彭特

1
@TomCarpenter谢谢!在这种情况下,因为它只是一个表达式,我们甚至可以省略disp:)
瑕疵的

10

Unity C#,34个字节

f=>UnityEngine.Random.onUnitSphere

Unity具有单位球面随机值的内置函数,因此我认为应该发布它。


好的使用内置+1,您可以提交一个简短一些的函数f=>Random.onUnitSphere
LiefdeWen

@LiefdeWen我知道lambda,只是不确定(就Code Golf的有效性而言)是否足够,因为它没有声明f'Type'。var仅在方法内部有效,使用System.Func<Vector3>时间更长。
Draco18s

1
在codegolf中,返回一个函数是完全可以的,并且您也不必计数声明,这意味着您可以使用动态参数来进行偷偷摸摸的事情。您也不计算最后一个分号。但是,您确实要计算添加的所有using语句。因此您的字节数需要包含使用。但这f=>Random.onUnitSphere是一个完全有效的意见书
LiefdeWen

@LiefdeWen是的,我只是不确定声明的处理方式,也不真正愿意“搜索meta”。
Draco18s

f=>UnityEngine.Random.onUnitSphere为您节省using
Orace

6

MATL,10字节

1&3Xrt2&|/

在线尝试!

说明

这使用了挑战中描述的第一种方法。

1&3Xr  % Generate a 1×3 vector of i.i.d standard Gaussian variables
t      % Duplicate
2&|    % Compute the 2-norm
/      % Divide, element-wise. Implicitly display

6

红宝石34 50 49字节

->{[z=rand*2-1]+((1-z*z)**0.5*1i**(rand*4)).rect}

在线尝试!

返回3个数字组成的数组[z,y,x]

xy是通过将i(-1的平方根)提高到0到4之间的随机乘方来生成的。此复数需要z根据毕达哥拉斯定理根据值适当缩放:(x**2 + y**2) + z**2 = 1.

z坐标(其首先产生)只是之间-1和1虽然没有立即明显,DA / DZ用于通过球体的切片是恒定的均匀分布的数目(并且等于同一半径的圆作为周边整个领域。)。

这显然是由阿基米德发现的,他用一种非微积分的方式对其进行了描述,并且被称为阿基米德Hat-B​​ox定理。参见https://brilliant.org/wiki/surface-area-sphere/

有关xnor答案的评论的另一参考。一个简短的URL,描述了一个简单的公式:http//mathworld.wolfram.com/Zone.html


@Jitse我忘记了在z的高值时缩小x和y的比例。有效地,这些点定义了一个圆柱体。现在已修复,但花费很多字节!如果输出可以用复数表示,我可以节省一些[z, x+yi],除非您说没问题,否则我将保留原样。
圣河

看起来不错!我真的很喜欢这种方法。为了保持一致,所需的输出为三个浮点数,因此我建议这样保留它。
吉特

为什么不使用z*z代替z**2
价值墨水

@ValueInk是的,谢谢,我意识到我错过了z*z。我已经编辑了。我可以做的另一件事是替换rand*4z*99x*9E9(有效地将可能的值限制为球体上非常细的螺旋),但是我认为这会降低随机性的质量。
圣河上的水准

4

05AB1E23 22 字节

[тε5°x<Ýs/<Ω}DnOtDî#}/

实现第二种算法。

在线尝试获得更多随机输出

说明:

[0,1)0.000010.00000000159

[            # Start an infinite loop:
 тε          #  Push 100, and map (basically, create a list with 3 values):
   5°        #   Push 100,000 (10**5)
     x       #   Double it to 200,000 (without popping)
      <      #   Decrease it by 1 to 199,999
       Ý     #   Create a list in the range [0, 199,999]
        s/   #   Swap to get 100,000 again, and divide each value in the list by this
          <  #   And then decrease by 1 to change the range [0,2) to [-1,1)
           Ω #   And pop and push a random value from this list
  }          #  After the map, we have our three random values
   D         #   Duplicate this list
    n        #   Square each inner value
     O       #   Take the sum of these squares
      t      #   Take the square-root of that
       D     #   Duplicate that as well
        î    #   Ceil it, and if it's now exactly 1:
         #   #    Stop the infinite loop
}/           # After the infinite loop: normalize by dividing
             # (after which the result is output implicitly)

1
l<1l1lx0<x1l<0.51

@Jitse Ok,在我的Java和05AB1E答案中都实现了规范化。我希望现在一切都正确。
凯文·克鲁伊森

v1v==1v<10<x1l1

4

TI-BASIC,15字节*

:randNorm(0,1,3
:Ans/√(sum(Ans²

使用算法“生成3个正态分布的值并将该向量归一化”。

以表达式结尾的程序会在程序终止后自动在主屏幕上显示结果,因此结果会真正显示出来,而不仅仅是生成和填充。

*:randNorm(两个字节的令牌,其余的是一个字节的令牌。我已经计算了初始的(不可避免的):,如果没有的话,它将是14个字节。另存为一个带有一个字母的名称的程序,它将占用24个字节的内存,其中包括9个字节的文件系统开销。


3

JavaScript(ES7), 77 76  75字节

使用实现第三种算法sin(ϕ)=sin(cos1(z))=1z2

with(Math)f=_=>[z=2*(r=random)()-1,cos(t=2*PI*r(q=(1-z*z)**.5))*q,sin(t)*q]

在线尝试!

已评论

with(Math)                       // use Math
f = _ =>                         //
  [ z = 2 * (r = random)() - 1,  // z = 2 * j - 1
    cos(                         //
      t =                        // θ =
        2 * PI *                 //   2 * π * i
        r(q = (1 - z * z) ** .5) // q = sin(ɸ) = sin(arccos(z)) = √(1 - z²)
                                 // NB: it is safe to compute q here because
                                 //     Math.random ignores its parameter(s)
    ) * q,                       // x = cos(θ) * sin(ɸ)
    sin(t) * q                   // y = sin(θ) * sin(ɸ)
  ]                              //

JavaScript(ES6),79个字节

实现第二算法。

f=_=>(n=Math.hypot(...v=[0,0,0].map(_=>Math.random()*2-1)))>1?f():v.map(x=>x/n)

在线尝试!

已评论

f = _ =>                         // f is a recursive function taking no parameter
  ( n = Math.hypot(...           // n is the Euclidean norm of
      v =                        // the vector v consisting of:
        [0, 0, 0].map(_ =>       //
          Math.random() * 2 - 1  //   3 uniform random values in [-1, 1]
        )                        //
  )) > 1 ?                       // if n is greater than 1:
    f()                          //   try again until it's not
  :                              // else:
    v.map(x => x / n)            //   return the normalized vector

3

处理 26个字节

完整程序

print(PVector.random3D());

这是实现https://github.com/processing/processing/blob/master/core/src/processing/core/PVector.java

  static public PVector random3D(PVector target, PApplet parent) {
    float angle;
    float vz;
    if (parent == null) {
      angle = (float) (Math.random()*Math.PI*2);
      vz    = (float) (Math.random()*2-1);
    } else {
      angle = parent.random(PConstants.TWO_PI);
      vz    = parent.random(-1,1);
    }
    float vx = (float) (Math.sqrt(1-vz*vz)*Math.cos(angle));
    float vy = (float) (Math.sqrt(1-vz*vz)*Math.sin(angle));
    if (target == null) {
      target = new PVector(vx, vy, vz);
      //target.normalize(); // Should be unnecessary
    } else {
      target.set(vx,vy,vz);
    }
    return target;
  }

2
您可能想更清楚地知道实现不是字节数的一部分。一读时我就错过了它,然后又做了一遍。
圣河河畔

我喜欢该实现使用与我基本相同的方法,但是
Level River St

2

Python 2,86个字节

from random import*
x,y,z=map(gauss,[0]*3,[1]*3);l=(x*x+y*y+z*z)**.5
print x/l,y/l,z/l

在线尝试!

实现第一个算法。


Python 2中107个 103字节

from random import*
l=2
while l>1:x,y,z=map(uniform,[-1]*3,[1]*3);l=(x*x+y*y+z*z)**.5
print x/l,y/l,z/l

在线尝试!

实现第二种算法。


2
@RobinRyder此实现拒绝初始长度大于1的向量,该向量在质询中指定为有效。
吉特

@Jitse对,对不起。我误读了代码。
罗宾·赖德

2

Haskell中125个 123 119 118字节

import System.Random
f=mapM(\_->randomRIO(-1,1))"lol">>= \a->last$f:[pure$(/n)<$>a|n<-[sqrt.sum$map(^2)a::Double],n<1]

在线尝试!

进行三个制服随机抽样和拒绝抽样。


看来您的随机数来自分布(0,1)而不是(-1,1),因此仅覆盖了球体的1/8。
吉特

@Jitse gotcha,感谢您的注意。
Angs

2

JavaScript,95个字节

f=(a=[x,y,z]=[0,0,0].map(e=>Math.random()*2-1))=>(s=Math.sqrt(x*x+y*y+z*z))>1?f():a.map(e=>e/s)

并不需要输入a


哇,我完全错过了。固定。
鸣子

2

朱莉娅1.0,24字节

x=randn(3)
x/hypot(x...)

在线尝试!

绘制一个3个值的向量,该向量是从0周围的正态分布中提取的,标准差为1。然后将其标准化。


randn()通过几次快速测试,似乎并没有限制在所需范围内。另外,这不包括检查是否应hypot()返回value 的检查>1
毛茸茸的

3
@Shaggy它似乎是randn从标准正态分布而不是均匀(0,1)进行模拟的,因此此方法与R相同。
朱塞佩

@Giuseppe是的,完全是!
user3263164

@Giuseppe,我想我可能对这个挑战背后的数学没有正确的了解,但是,如果我正确地理解了你,你就是说,如果任何浮点数超出了[-1,1)它们的范围,然后将它们除以斜边,可以>1抵消?这使我想知道解决方案中的三元数是否必要……
粗野的

@Shaggy不,正态/高斯分布具有制服所没有的某些属性(特别是旋转不变性),请参阅此注释,例如
Giuseppe

2

MathGolf21 19 18 字节

{╘3Ƀ∞(ß_²Σ√_1>}▲/

第二算法的实现。

在线尝试或同时查看更多输出

说明:

{              }▲   # Do-while true by popping the value:
                   #  Discard everything on the stack to clean up previous iterations
  3É                #  Loop 3 times, executing the following three operations:
    ƒ               #   Push a random value in the range [0,1]
                   #   Double it to make the range [0,2]
      (             #   Decrease it by 1 to make the range [-1,1]
       ß            #  Wrap these three values into a list
        _           #  Duplicate the list of random values
         ²          #  Square each value in the list
          Σ         #  Sum them
                   #  And take the square-root of that
            _       #  Duplicate it as well
             1>     #  And check if it's larger than 1
                 /  # After the do-while, divide to normalize
                    # (after which the entire stack joined together is output implicitly,
                    #  which is why we need the `╘` to cleanup after every iteration)

2

爪哇8(@Arnauld的改性第三算法),131个 126 119 111 109字节

v->{double k=2*M.random()-1,t=M.sqrt(1-k*k),r[]={k,M.cos(k=2*M.PI*M.random())*t,M.sin(k)*t};return r;}

@Arnauld的JavaScript答案端口,确保对其进行投票!
-2个字节,感谢@OlivierGrégoire

这实现为:

k=N[1,1)
t=1k2
u=2π×(N[0,1))
x,y,z={k,cos(u)×t,sin(u)×t}

在线尝试。

先前的第3种算法实现(131 126 119字节):

Math M;v->{double k=2*M.random()-1,t=2*M.PI*M.random();return k+","+M.cos(t)*M.sin(k=M.acos(k))+","+M.sin(t)*M.sin(k);}

实施为:

k=N[1,1)
t=2π×(N[0,1))
x,y,z={k,cos(t)×sin(arccos(k)),sin(t)×sin(arccos(k))}

在线尝试。

说明:

Math M;                         // Math on class-level to use for static calls to save bytes
v->{                            // Method with empty unused parameter & double-array return
  double k=2*M.random()-1,      //  Get a random value in the range [-1,1)
         t=M.sqrt(1-k*k),       //  Calculate the square-root of 1-k^2
    r[]={                       //  Create the result-array, containing:
         k,                     //   X: the random value `k`
         M.cos(k=2*M.PI         //   Y: first change `k` to TAU (2*PI)
                     *M.random()//       multiplied by a random [0,1) value
                )               //      Take the cosine of that
                 *t,            //      and multiply it by `t`
         M.sin(k)               //   Z: Also take the sine of the new `k` (TAU * random)
                  *t};          //      And multiply it by `t` as well
  return r;}                    //  Return this array as result

爪哇8(第二算法),153个 143字节

v->{double x=2,y=2,z=2,l;for(;(l=Math.sqrt(x*x+y*y+z*z))>1;y=m(),z=m())x=m();return x/l+","+y/l+","+z/l;};double m(){return Math.random()*2-1;}

在线尝试。

第二种算法:

v->{                              // Method with empty unused parameter & String return-type
  double x=2,y=2,z=2,l;           //  Start results a,b,c all at 2
  for(;(l=Math.sqrt(x*x+y*y+z*z)) //  Loop as long as the hypotenuse of x,y,z
       >1;                        //  is larger than 1
    y=m(),z=m())x=m();            //   Calculate a new x, y, and z
  return x/l+","+y/l+","+z/l;}    //  And return the normalized x,y,z as result
double m(){                       // Separated method to reduce bytes, which will:
  return Math.random()*2-1;}      //  Return a random value in the range [-1,1)

使用sqrt(1-k*k)Java实际上比在JS中节省更多的字节。:)
Arnauld

@Arnauld是的。您的方法不是使用3x M.sin,1x M.cos和1x M.acos,而是使用2x M.sin和1x M.sqrt,这是额外节省的字节主要来自的地方。:)
Kevin Cruijssen

108个字节使用修改后的第二个算法,其中我仅允许s == 1(而不是s <= 1,然后进行归一化)的值。它有时会给出答案,但由于超时而不会给出答案。编辑:糟糕,我忘记了Math.sqrt的结果
OlivierGrégoire

实际上,不,不需要sqrt,因为sqrt(1)== 1。所以我坚持我的高尔夫建议。
OlivierGrégoire

1
109个字节(您可以使用字符串输出代替,double[]因为它不会更改字节数。)
OlivierGrégoire

1

Japt,20 字节

Arnauld实施第二种算法的端口。

MhV=3ÆMrJ1
>1?ß:V®/U

测试一下

MhV=3ÆMrJ1
Mh             :Get the hypotenuse of
  V=           :  Assign to V
    3Æ         :  Map the range [0,3)
      Mr       :    Random float
        J1     :    In range [-1,1)
>1?ß:V®/U      :Assign result to U
>1?            :If U is greater than 1
   ß           :  Run the programme again
    :V®/U      :Else map V, dividing all elements by U

1

Pyth,24个字节

W<1Ks^R2JmtO2.0 3;cR@K2J

在线尝试!

使用算法2

W                         # while 
 <1                       #   1 < 
   Ks                     #       K := sum(
     ^R2                  #               map(lambda x:x**2,
        Jm      3         #                    J := map(                            , range(3))
          tO2.0           #                             lambda x: random(0, 2.0) - 1           )):
                 ;        #   pass
                   R   J  # [return] map(lambda x:            , J)
                  c @K2   #                        x / sqrt(K)

1

OCaml110 99 95字节

(fun f a c s->let t,p=f 4.*.a 0.,a(f 2.-.1.)in[c t*.s p;s t*.s p;c p])Random.float acos cos sin

ijlet ... infun()

在线尝试


原始解决方案:

Random.(let a,c,s,i,j=acos,cos,sin,float 4.,float 2. in let t,p=i*.(a 0.),a (j-.1.) in[c t*.s p;s t*.s p;c p])

首先我定义:

a=arccos,  c=cos,  s=siniunif(0,4),  junif(0,2)

OCaml的Random.float功能包括边界。然后,

t=ia(0)=iπ2,  p=a(j1)

ϕ=pθ=tij


1
我对这种语言不太熟悉,但是好像您使用之间的随机浮点数0并将其1直接用作球面坐标。如挑战性注释3和4所示,这是不正确的,因为您最终会偏向球体的两极。您可以通过应用备注4所示的方法解决此
Jitse

谢谢!完全错过了。修复了错误并更新了我的答案
Saswat Padhi

1
看起来不错!非常好的第一答案!
吉特

谢谢:)我能够将其减少到100个字节以下!
Saswat Padhi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.