ランダムな方向のベクトルを生成する方法とコード例です。
以下のページの情報を参考に作成しました。
ランダムな方向のベクトルを生成する方法とコード例です。
以下のページの情報を参考に作成しました。
平面ベクトルの場合、偏角$\theta$を一様乱数で選んで直交座標に変換する。 \begin{align} \begin{bmatrix} X \\ Y \end{bmatrix} = \begin{bmatrix} \cos \varTheta \\ \sin \varTheta \end{bmatrix} \end{align}
/* theta ~ U(-pi, pi) */ let theta = Math.PI * (2.0 * Math.random() - 1.0); let x = Math.cos(theta); let y = Math.sin(theta);
3次元ベクトルの場合には、意外なことに周辺分布が一様分布である。そこで、z成分と方位角$\varphi$を一様乱数で選んで、z成分を固定したときのベクトルのxy平面への正射影の長さが$\sqrt{1 - Z^2}$と表されることから、次のように変換する。 \begin{align} \begin{bmatrix} X \\ Y \\ Z \end{bmatrix} = \begin{bmatrix} \sqrt{1 - Z^2} \cos \varPhi \\ \sqrt{1 - Z^2} \sin \varPhi \\ Z \end{bmatrix} \end{align}
/* z ~ U(-1, 1) */ /* phi ~ U(-pi, pi) */ let z = 2.0 * Math.random() - 1.0; let phi = Math.PI * (2.0 * Math.random() - 1.0); let x = Math.sqrt(1.0 - z * z) * Math.cos(phi); let y = Math.sqrt(1.0 - z * z) * Math.sin(phi);
下図のようにどの方向のベクトルも偏りなく実現する。
次のよくない例では、極角$\theta$と方位角$\varphi$を一様乱数で選んで直交座標に変換している。
/* BAD EXAMPLE! */ /* theta ~ U(0, pi) */ /* phi ~ U(-pi, pi) */ let theta = Math.PI * Math.random(); let phi = Math.PI * (2.0 * Math.random() - 1.0); let x = Math.sin(theta) * Math.cos(phi); let y = Math.sin(theta) * Math.sin(phi); let z = Math.cos(theta);
下図のように(0, 0, 1)と(0, 0, −1)に近い方向のベクトルに偏ってしまう。
平面ベクトルの場合、棄却サンプリングで単位円板内の一様乱数を作り、第二段階で正規化する。
/* (u, v) ~ U_[unit-disk] */ let u, v, rr; do { u = 2.0 * Math.random() - 1.0; v = 2.0 * Math.random() - 1.0; rr = u * u + v * v; } while (rr >= 1.0 || rr == 0.0); let s = 1.0 / Math.sqrt(rr); let x = s * u; let y = s * v;
3次元ベクトルの場合も平面ベクトルの場合と同様である。
/* (u, v, w) ~ U_[unit-ball] */ let u, v, w, rr; do { u = 2.0 * Math.random() - 1.0; v = 2.0 * Math.random() - 1.0; w = 2.0 * Math.random() - 1.0; rr = u * u + v * v + w * w; } while (rr >= 1.0 || rr == 0.0); let s = 1.0 / Math.sqrt(rr); let x = s * u; let y = s * v; let z = s * w;
次のよくない例では、棄却を省略して立方体内の一様乱数をそのまま正規化している。
/* BAD EXAMPLE! */ /* u ~ U(-1, 1) */ /* v ~ U(-1, 1) */ /* w ~ U(-1, 1) */ let u = 2.0 * Math.random() - 1.0; let v = 2.0 * Math.random() - 1.0; let w = 2.0 * Math.random() - 1.0; let s = 1.0 / Math.sqrt(u * u + v * v + w * w); let x = s * u; let y = s * v; let z = s * w;
下図のように立方体の頂点に近い方向のベクトルに偏ってしまう。
棄却・正規化法では次元の呪いが発生する。$n$次元での採択率は$\frac{\pi^{n/2}}{\Gamma(n/2 + 1)} \cdot \frac{1}{2^n}$となり1,2急速に悪化する。繰り返しの期待回数(採択率の逆数)は、平面の場合に約1.3回、4次元で約3.2回、8次元で約63回、16次元では約28万回(非実用的)となる。
3次元ベクトルの場合、標準3変量正規乱数を正規化する。次の例では、零ベクトル(ごく低確率)を除外して正規化している。
/* (u, v, w) ~ N(0, I_3) */ let u, v, w, rr; do { u = rnorm(0.0, 1.0); v = rnorm(0.0, 1.0); w = rnorm(0.0, 1.0); rr = u * u + v * v + w * w; } while (rr == 0.0); let s = 1.0 / Math.sqrt(rr); let x = s * u; let y = s * v; let z = s * w;
座標変換法と棄却・正規化法が効率的。どちらが速いかは、三角関数のパフォーマンスと乱数生成のパフォーマンスの兼ね合いによる。正規乱数法はオーバーヘッドが大きい。
正規乱数法が効率的。ただし、5次元程度までなら棄却・正規化法も選択肢に入る。(特に正規乱数生成が低速な場合。)