Nポイント(おそらく20未満)の球の周りの位置を漠然と広げてくれるアルゴリズムが必要です。 「完璧」は必要ありませんが、必要なのはそれらが1つにまとめられないようにすることだけです。
私が出くわした他のいくつかの質問スレッドは、ランダム化された均一分布について話しました。これは、私が心配しないレベルの複雑さを追加します。これはばかげた質問であることをおizeびしますが、私は本当に一生懸命に見えて、まだ不足していることを示したかったのです。
だから、私が探しているのは、球体またはデカルト座標で返される単位球の周りにN個の点を均等に分配する単純な擬似コードです。少しのランダム化(星の周りの惑星を考えて、まんべんなく広がっているが、ゆとりの余地がある)でさえ分散できるならさらに良い。
このサンプルコードnode[k]
は、k番目のノードです。 Nポイントの配列を生成しており、node[k]
はk番目(0〜N-1)です。それがあなたを混乱させるすべてであるならば、うまくいけばあなたは今それを使うことができます。
(つまり、k
は、コードフラグメントが開始する前に定義され、ポイントのリストを含むサイズNの配列です)。
代わりに、ここで他の答えに基づいて(そしてPythonを使用して):
> cat ll.py
from math import asin
nx = 4; ny = 5
for x in range(nx):
lon = 360 * ((x+0.5) / nx)
for y in range(ny):
midpt = (y+0.5) / ny
lat = 180 * asin(2*((y+0.5)/ny-0.5))
print lon,lat
> python2.7 ll.py
45.0 -166.91313924
45.0 -74.0730322921
45.0 0.0
45.0 74.0730322921
45.0 166.91313924
135.0 -166.91313924
135.0 -74.0730322921
135.0 0.0
135.0 74.0730322921
135.0 166.91313924
225.0 -166.91313924
225.0 -74.0730322921
225.0 0.0
225.0 74.0730322921
225.0 166.91313924
315.0 -166.91313924
315.0 -74.0730322921
315.0 0.0
315.0 74.0730322921
315.0 166.91313924
それをプロットすると、各ポイントがほぼ同じ合計areaの空間に位置するように、垂直方向の間隔が極の近くで大きくなることがわかります。 (極の近くでは、「水平方向」にスペースが少なくなるため、「垂直方向」により多くなります)。
これは、すべてのポイントが近隣とほぼ同じ距離にあることとは異なります(リンクが話していると思います)、それはあなたが望むものに十分であり、単に均一な緯度/経度グリッドを作成することで向上します。
これにはフィボナッチ球アルゴリズムが最適です。それは速く、一目で人間の目を簡単にだます結果をもたらします。 処理が完了した例を見ることができます これは、ポイントが追加されると時間とともに結果を表示します。 これは別の優れたインタラクティブな例です @gmanによって作成されました。そして、簡単なランダム化オプションを備えた簡単なpythonバージョンがあります:
import math, random
def fibonacci_sphere(samples=1,randomize=True):
rnd = 1.
if randomize:
rnd = random.random() * samples
points = []
offset = 2./samples
increment = math.pi * (3. - math.sqrt(5.));
for i in range(samples):
y = ((i * offset) - 1) + (offset / 2);
r = math.sqrt(1 - pow(y,2))
phi = ((i + rnd) % samples) * increment
x = math.cos(phi) * r
z = math.sin(phi) * r
points.append([x,y,z])
return points
1000個のサンプルがこれを提供します。
これは球上のパッキングポイントと呼ばれ、一般的な完全な解決策はありません。ただし、不完全なソリューションはたくさんあります。最も人気のある3つは次のとおりです。
n
よりはるかに多く)を均一に選択し、点を拒否します球の外側。残りのポイントをベクトルとして扱い、それらを正規化します。これらは「サンプル」です-何らかの方法(ランダム、貪欲など)を使用してn
を選択します。lotこの問題に関する詳細情報を見つけることができます here
ゴールデンスパイラル法を使用することはできないと言いましたが、それは本当に素晴らしいことです。これを完全に理解して、多分あなたがこれを「束ねられ」ないようにする方法を理解できるようにしたいと思います。
そこで、ここでは、ほぼ正しいラティスを作成するための高速で非ランダムな方法を示します。上記で説明したように、完全な格子はありませんが、これは「十分」です。他の方法と比較されます BendWavy.org にありますが、見た目がきれいで、制限内の均等な間隔についても保証されています。
このアルゴリズムを理解するために、最初に2Dひまわりスパイラルアルゴリズムをご覧ください。これは、最も不合理な数が黄金比(1 + sqrt(5))/2
であり、アプローチによってポイントを放出する場合、「中央に立って、全体のターンの黄金比を回し、その方向に別のポイントを放出する」という事実に基づいています。 「自然にらせんを構築します。これは、ポイントの数が増えるにつれて、ポイントが並ぶ明確な「バー」を持つことを拒否します。(注1)
ディスク上の等間隔のアルゴリズムは、
from numpy import pi, cos, sin, sqrt, arange
import matplotlib.pyplot as pp
num_pts = 100
indices = arange(0, num_pts, dtype=float) + 0.5
r = sqrt(indices/num_pts)
theta = pi * (1 + 5**0.5) * indices
pp.scatter(r*cos(theta), r*sin(theta))
pp.show()
そして、次のような結果を生成します(n = 100およびn = 1000):
重要な奇妙なことは、式r = sqrt(indices / num_pts)
;です。どうやってそこに来たの? (注2.)
さて、ここでは平方根を使用しています。これは、これらが球の周りに均等な面積の間隔を持つようにするためです。それは、大きな制限の中でN小さな領域が必要だと言っているのと同じですR∈(r、r + d r )、Θε(θ、θ + d θ)で、その面積に比例する多数の点を含む、つまりr d r d θ。ここでランダム変数について話しているふりをすると、(R、Θ)の結合確率密度はちょうどcrいくつかの定数c。単位ディスクの正規化は、c = 1 /πを強制します。
次に、トリックを紹介します。 逆CDFのサンプリング として知られる確率理論に由来します:を生成したい場合確率密度のランダム変数f(z)およびランダム変数U〜Uniform(0、1)があります。ほとんどのプログラミング言語でrandom()
から出てくるようです。これどうやってやるの?
黄金比のスパイラルトリックは、θに対してポイントをきれいに均等なパターンで配置するので、それを統合しましょう。単位円については、F(r)= r2。したがって、逆関数はF-1(u)= u1/2、したがって、r = sqrt(random()); theta = 2 * pi * random()
を使用して、極座標で球上のランダムポイントを生成します。
今、ランダムこの逆関数をサンプリングする代わりに、私たちは均一にサンプリングしますサンプリングします。均一なサンプリングの良いところは、大きな限界でポイントがどのように広がるかについての結果ですNランダムにサンプリングしたかのように動作します。この組み合わせがトリックです。 random()
の代わりに(arange(0, num_pts, dtype=float) + 0.5)/num_pts
を使用します。したがって、たとえば、10ポイントをサンプリングしたい場合、それらはr = 0.05, 0.15, 0.25, ... 0.95
です。 rを均一にサンプリングして、等面積の間隔を取得し、ひまわりの増分を使用して、出力内のポイントのひどい「バー」を回避します。
球に点で点を付けるために必要な変更は、球座標の極座標を切り替えるだけです。もちろん、半径方向の座標は、単位球上にあるため、これには入りません。ここで物事の一貫性を保つために、物理学者として訓練されていても、0≤φ≤πが極から降りてくる緯度であり、0≤θ≤である数学者の座標を使用します2πは経度です。上記との違いは、基本的に変数rをφに置き換えていることです。
r d r d θであった面積要素は、今ではそれほど複雑ではないsin(φ)d φ dθ。したがって、等間隔のジョイント密度は、sin(φ)/4πです。 θを積分すると、f(φ)= sin(φ)/ 2であるため、F(φ)= (1-cos(φ))/ 2。これを逆にすると、一様なランダム変数はacos(1-2 u)のように見えることがわかりますが、ランダムではなく一様にサンプリングするため、代わりにφを使用しますk = acos(1 − 2(k + 0.5)/N)。そして、アルゴリズムの残りの部分は、これをx、y、およびz座標に投影しているだけです。
from numpy import pi, cos, sin, arccos, arange
import mpl_toolkits.mplot3d
import matplotlib.pyplot as pp
num_pts = 1000
indices = arange(0, num_pts, dtype=float) + 0.5
phi = arccos(1 - 2*indices/num_pts)
theta = pi * (1 + 5**0.5) * indices
x, y, z = cos(theta) * sin(phi), sin(theta) * sin(phi), cos(phi);
pp.figure().add_subplot(111, projection='3d').scatter(x, y, z);
pp.show()
再びn = 100およびn = 1000の場合、結果は次のようになります。
これらの「バー」は、数値に対する有理近似によって形成され、数値に対する最良の有理近似は、その連続分数式、z + 1/(n_1 + 1/(n_2 + 1/(n_3 + ...)))
から得られます。z
は整数で、n_1, n_2, n_3, ...
は有限または無限正の整数のシーケンス:
def continued_fraction(r):
while r != 0:
n = floor(r)
yield n
r = 1/(r - n)
小数部1/(...)
は常に0から1の間であるため、連続する小数の大きな整数は、「1を100から101で割ったもの」が「1で2で割ったもの」よりも優れた合理的な近似を可能にします。 」したがって、最も無理な数は1 + 1/(1 + 1/(1 + ...))
であり、特に優れた有理数近似はありません。 φで乗算して黄金比の式を取得することにより、φ = 1 + 1 /φを解くことができます。
NumPyにあまり慣れていない人のために-すべての関数は「ベクトル化」されているため、sqrt(array)
は他の言語がmap(sqrt, array)
を記述するものと同じです。したがって、これはコンポーネントごとのsqrt
アプリケーションです。同じことは、スカラーによる除算またはスカラーによる加算にも当てはまります。これらはすべてのコンポーネントに並列に適用されます。
これが結果であることがわかったら、証明は簡単です。 z <Z <z + d zである確率を尋ねると、これはz <である確率を尋ねるのと同じです。 F-1(U)<z + d z、単調増加関数であることに注意して、3つの式すべてにFを適用するため、F(z)<U <F(z + d z)、右側を展開してF(z)+ f(z)d z、およびUは均一なので、この確率はちょうどf (z)d z約束どおり。
探しているものは球形被覆と呼ばれます。球面被覆問題は非常に難しく、少数の点を除いて解は不明です。確かに知られていることの1つは、球上のn個のポイントが与えられた場合、d = (4-csc^2(\pi n/6(n-2)))^(1/2)
またはそれより近い距離の2つのポイントが常に存在することです。
球に均一に分布する点を生成する確率論的な方法が必要な場合、それは簡単です。ガウス分布により空間に均一に点を生成します(Javaに組み込まれ、他の言語のコードを見つけるのは難しくありません)。 3次元空間では、次のようなものが必要です
Random r = new Random();
double[] p = { r.nextGaussian(), r.nextGaussian(), r.nextGaussian() };
次に、原点からの距離を正規化することにより、ポイントを球に投影します
double norm = Math.sqrt( (p[0])^2 + (p[1])^2 + (p[2])^2 );
double[] sphereRandomPoint = { p[0]/norm, p[1]/norm, p[2]/norm };
N次元のガウス分布は球対称であるため、球への投影は均一です。
もちろん、均一に生成されたポイントのコレクション内の任意の2つのポイント間の距離が下に制限されるという保証はないため、拒否を使用して、このような条件を強制することができます。おそらく、コレクション全体を生成してから、必要に応じてコレクション全体を拒否します。 (または、「早期拒否」を使用して、これまでに生成したコレクション全体を拒否します。一部のポイントを保持せず、他のポイントを削除しないでください。)上記のd
の式を使用します。それ以下のポイントのセットを拒否するポイント間の最小距離を決定します。 2つの距離を選択してnを計算する必要があり、拒否の確率はスラックに依存します。方法を言うのは難しいので、シミュレーションを実行して、関連する統計の感触をつかんでください。
この答えは、 この答え で概説されているのと同じ「理論」に基づいています。
私はこの答えを次のように追加しています:
-「均一性」に適合する他のオプションはどれも「スポットオン」を必要としません(または明らかに明らかにそうではありません)。 (元のaskで特に求められている分布のような分布のような行動を惑星に与えるために、一様に作成されたk個の点の有限リストからランダムに拒否します(ランダムにkアイテムのインデックスカウントを返します)。)
-最も近い他の実装では、「角度軸」によって「N」を決定するように強制されましたが、両方のangular軸値(低カウントで「Nの1つの値」 of Nは、何が重要か、何が重要でないかを知るのが非常に難しい(たとえば、「5」ポイントが欲しい-楽しんでください))
-さらに、画像なしで他のオプションを区別する方法を「グロッキング」するのは非常に難しいので、このオプションがどのように見えるか(以下)と、それに伴うすぐに実行可能な実装です。
Nが20の場合:
、次に80のN:
すぐに実行できるpython3コードを次に示します。エミュレーションは同じソースです: " http://web.archive.org/web/20120421191837/http://www.cgafaq.info/wiki/Evenly_distributed_points_on_sphere "他の人が見つけました。 (「メイン」として実行されたときに起動する、私が含めたプロットは、次から取得されます: http://www.scipy.org/Cookbook/Matplotlib/mplot3D )
from math import cos, sin, pi, sqrt
def GetPointsEquiAngularlyDistancedOnSphere(numberOfPoints=45):
""" each point you get will be of form 'x, y, z'; in cartesian coordinates
eg. the 'l2 distance' from the origion [0., 0., 0.] for each point will be 1.0
------------
converted from: http://web.archive.org/web/20120421191837/http://www.cgafaq.info/wiki/Evenly_distributed_points_on_sphere )
"""
dlong = pi*(3.0-sqrt(5.0)) # ~2.39996323
dz = 2.0/numberOfPoints
long = 0.0
z = 1.0 - dz/2.0
ptsOnSphere =[]
for k in range( 0, numberOfPoints):
r = sqrt(1.0-z*z)
ptNew = (cos(long)*r, sin(long)*r, z)
ptsOnSphere.append( ptNew )
z = z - dz
long = long + dlong
return ptsOnSphere
if __== '__main__':
ptsOnSphere = GetPointsEquiAngularlyDistancedOnSphere( 80)
#toggle True/False to print them
if( True ):
for pt in ptsOnSphere: print( pt)
#toggle True/False to plot them
if(True):
from numpy import *
import pylab as p
import mpl_toolkits.mplot3d.axes3d as p3
fig=p.figure()
ax = p3.Axes3D(fig)
x_s=[];y_s=[]; z_s=[]
for pt in ptsOnSphere:
x_s.append( pt[0]); y_s.append( pt[1]); z_s.append( pt[2])
ax.scatter3D( array( x_s), array( y_s), array( z_s) )
ax.set_xlabel('X'); ax.set_ylabel('Y'); ax.set_zlabel('Z')
p.show()
#end
低カウント(2、5、7、13などのN)でテストされ、「いい」と思われる
試してください:
function sphere ( N:float,k:int):Vector3 {
var inc = Mathf.PI * (3 - Mathf.Sqrt(5));
var off = 2 / N;
var y = k * off - 1 + (off / 2);
var r = Mathf.Sqrt(1 - y*y);
var phi = k * inc;
return Vector3((Mathf.Cos(phi)*r), y, Mathf.Sin(phi)*r);
};
上記の関数は、合計Nループおよびkループの現在の反復でループで実行する必要があります。
これは、ヒマワリの種のパターンに基づいています。ただし、ヒマワリの種は半分のドームに、そして再び球体に湾曲しています。
カメラはすべてのポイントから同じ距離にあるため、カメラを球の内側半分に置いて3dではなく2dに見えることを除いて、次の写真があります。 http://3.bp.blogspot.com/-9lbPHLccQHA/USXf88_bvVI/AAAAAAAAADY/j7qhQsSZsA8/s640/sphere.jpg
Healpixは密接に関連する問題を解決します(等しい面積のピクセルで球体をピクセル化する):
http://healpix.sourceforge.net/
おそらくやり過ぎかもしれませんが、それを見た後、他のニースのプロパティがあなたにとって興味深いものであることに気付くでしょう。点群を出力する関数以上のものです。
私は再びそれを見つけようとしてここに着陸しました。 「healpix」という名前は正確に球体を呼び起こさない...
または... 20点を配置するには、20面体の面の中心を計算します。 12ポイントで、20面体の頂点を見つけます。 30ポイントの場合、20面体のエッジの中間点。四面体、立方体、十二面体、八面体でも同じことができます。1組の点は頂点上にあり、もう1組は面の中心にあり、もう1組はエッジの中心にあります。ただし、混在させることはできません。
edit:これは、OPが尋ねることを意図した質問には答えません。
無限小と組み合わせた確率の乗算規則を使用します。これにより、2行のコードで目的の結果が得られます。
longitude: φ = uniform([0,2pi))
azimuth: θ = -arcsin(1 - 2*uniform([0,1]))
(次の座標系で定義:)
通常、言語には均一な乱数プリミティブがあります。たとえば、pythonでは、random.random()
を使用して、[0,1)
の範囲の数値を返すことができます。この数値にkを掛けて、[0,k)
の範囲の乱数を取得できます。したがって、Pythonでは、uniform([0,2pi))
はrandom.random()*2*math.pi
を意味します。
証明
これで、θを均一に割り当てることはできません。球面くさびの表面積に比例する確率を割り当てたいと思います(この図のθは実際にはφです):
赤道でのangular変位dφは、dφ* rの変位になります。その変位は、任意の方位角θで何になりますか?さて、z軸からの半径はr*sin(θ)
であるため、くさびと交差する「緯度」の弧長はdφ * r*sin(θ)
です。したがって、S極からN極までのスライスの面積を積分することにより、そこからサンプリングする面積の 累積分布 を計算します。
(where = dφ*r
)
次に、CDFの逆を取得して、それからサンプリングを試みます。 http://en.wikipedia.org/wiki/Inverse_transform_sampling
最初に、ほとんどのCDFを最大値で除算して正規化します。これには、dφとrをキャンセルするという副作用があります。
azimuthalCDF: cumProb = (sin(θ)+1)/2 from -pi/2 to pi/2
inverseCDF: θ = -sin^(-1)(1 - 2*cumProb)
副<文>この[前述の事実の]結果として、それ故に、従って、だから◆【同】consequently; therefore <文>このような方法で、このようにして、こんなふうに、上に述べたように◆【同】in this manner <文>そのような程度まで<文> AひいてはB◆【用法】A and thus B <文>例えば◆【同】for example; as an example:
let x by a random float in range [0,1]
θ = -arcsin(1-2*x)
少数のポイントを使用すると、シミュレーションを実行できます。
from random import random,randint
r = 10
n = 20
best_closest_d = 0
best_points = []
points = [(r,0,0) for i in range(n)]
for simulation in range(10000):
x = random()*r
y = random()*r
z = r-(x**2+y**2)**0.5
if randint(0,1):
x = -x
if randint(0,1):
y = -y
if randint(0,1):
z = -z
closest_dist = (2*r)**2
closest_index = None
for i in range(n):
for j in range(n):
if i==j:
continue
p1,p2 = points[i],points[j]
x1,y1,z1 = p1
x2,y2,z2 = p2
d = (x1-x2)**2+(y1-y2)**2+(z1-z2)**2
if d < closest_dist:
closest_dist = d
closest_index = i
if simulation % 100 == 0:
print simulation,closest_dist
if closest_dist > best_closest_d:
best_closest_d = closest_dist
best_points = points[:]
points[closest_index]=(x,y,z)
print best_points
>>> best_points
[(9.921692138442777, -9.930808529773849, 4.037839326088124),
(5.141893371460546, 1.7274947332807744, -4.575674650522637),
(-4.917695758662436, -1.090127967097737, -4.9629263893193745),
(3.6164803265540666, 7.004158551438312, -2.1172868271109184),
(-9.550655088997003, -9.580386054762917, 3.5277052594769422),
(-0.062238110294250415, 6.803105171979587, 3.1966101417463655),
(-9.600996012203195, 9.488067284474834, -3.498242301168819),
(-8.601522086624803, 4.519484132245867, -0.2834204048792728),
(-1.1198210500791472, -2.2916581379035694, 7.44937337008726),
(7.981831370440529, 8.539378431788634, 1.6889099589074377),
(0.513546008372332, -2.974333486904779, -6.981657873262494),
(-4.13615438946178, -6.707488383678717, 2.1197605651446807),
(2.2859494919024326, -8.14336582650039, 1.5418694699275672),
(-7.241410895247996, 9.907335206038226, 2.271647103735541),
(-9.433349952523232, -7.999106443463781, -2.3682575660694347),
(3.704772125650199, 1.0526567864085812, 6.148581714099761),
(-3.5710511242327048, 5.512552040316693, -3.4318468250897647),
(-7.483466337225052, -1.506434920354559, 2.36641535124918),
(7.73363824231576, -8.460241422163824, -1.4623228616326003),
(10, 0, 0)]
N
の2つの最大要素を取ります。N==20
の場合、2つの最大要素は{5,4}
、またはより一般的には{a,b}
です。計算する
dlat = 180/(a+1)
dlong = 360/(b+1})
最初のポイントを{90-dlat/2,(dlong/2)-180}
に、2番目のポイントを{90-dlat/2,(3*dlong/2)-180}
に、3番目のポイントを{90-dlat/2,(5*dlong/2)-180}
に置き、一度世界を旅して{75,150}
{90-3*dlat/2,(dlong/2)-180}
の隣に移動したとき。
明らかに、+ /-をN/SまたはE/Wに変換するための通常の規則を使用して、これを球面の表面で度単位で処理しています。そして明らかにこれは完全に非ランダムな分布を与えますが、それは均一であり、ポイントは一緒にまとめられません。
ある程度のランダム性を追加するには、2つの正規分布(平均0および必要に応じて{dlat/3、dlong/3}のstd dev)を生成し、それらを均一に分布したポイントに追加します。
# create uniform spiral grid
numOfPoints = varargin[0]
vxyz = zeros((numOfPoints,3),dtype=float)
sq0 = 0.00033333333**2
sq2 = 0.9999998**2
sumsq = 2*sq0 + sq2
vxyz[numOfPoints -1] = array([(sqrt(sq0/sumsq)),
(sqrt(sq0/sumsq)),
(-sqrt(sq2/sumsq))])
vxyz[0] = -vxyz[numOfPoints -1]
phi2 = sqrt(5)*0.5 + 2.5
rootCnt = sqrt(numOfPoints)
prevLongitude = 0
for index in arange(1, (numOfPoints -1), 1, dtype=float):
zInc = (2*index)/(numOfPoints) -1
radius = sqrt(1-zInc**2)
longitude = phi2/(rootCnt*radius)
longitude = longitude + prevLongitude
while (longitude > 2*pi):
longitude = longitude - 2*pi
prevLongitude = longitude
if (longitude > pi):
longitude = longitude - 2*pi
latitude = arccos(zInc) - pi/2
vxyz[index] = array([ (cos(latitude) * cos(longitude)) ,
(cos(latitude) * sin(longitude)),
sin(latitude)])