在定义的锥形区域内创建随机单位向量

时间:2016-08-17 12:57:27

标签: matlab vector linear-algebra

我正在寻找一种简单的方法来创建受锥形区域约束的随机单位向量。原点始终是[0,0,0]。

到目前为止我的解决方案:

function v = GetRandomVectorInsideCone(coneDir,coneAngleDegree)

coneDir = normc(coneDir);

ang = coneAngleDegree + 1;
while ang > coneAngleDegree
    v = [randn(1); randn(1); randn(1)];
    v = v + coneDir;
    v = normc(v);
    ang = atan2(norm(cross(v,coneDir)), dot(v,coneDir))*180/pi;
end

我的代码循环,直到随机生成的单位向量在定义的圆锥内。 有更好的方法吗?

测试代码的生成图像 Resultant vectors plot

使用Ahmed Fasih代码生成频率分布(在评论中)。 我想知道如何获得矩形或正态分布。

c = [1;1;1]; angs = arrayfun(@(i) subspace(c, GetRandomVectorInsideCone(c, 30)), 1:1e5) * 180/pi; figure(); hist(angs, 50);

Freq angular distribution

测试代码:

clearvars; clc; close all;

coneDir = [randn(1); randn(1); randn(1)];
coneDir = [0 0 1]';
coneDir = normc(coneDir);
coneAngle = 45;
N = 1000;
vAngles = zeros(N,1);
vs = zeros(3,N);
for i=1:N
    vs(:,i) = GetRandomVectorInsideCone(coneDir,coneAngle);
    vAngles(i) = subspace(vs(:,i),coneDir)*180/pi;
end
maxAngle = max(vAngles);
minAngle = min(vAngles);
meanAngle = mean(vAngles);
AngleStd = std(vAngles);

fprintf('v angle\n');
fprintf('Direction: [%.3f %.3f %.3f]^T. Angle: %.2fº\n',coneDir,coneAngle);
fprintf('Min: %.2fº. Max: %.2fº\n',minAngle,maxAngle);
fprintf('Mean: %.2fº\n',meanAngle);
fprintf('Standard Dev: %.2fº\n',AngleStd);

%% Plot
figure;
grid on;
rotate3d on;
axis equal;
axis vis3d;
axis tight;
hold on;
xlabel('X'); ylabel('Y'); zlabel('Z');

% Plot all vectors
p1 = [0 0 0]';
for i=1:N
    p2 = vs(:,i);
    plot3ex(p1,p2);
end

% Trying to plot the limiting cone, but no success here :(
% k = [0 1];
% [X,Y,Z] = cylinder([0 1 0]');
% testsubject = surf(X,Y,Z); 
% set(testsubject,'FaceAlpha',0.5)

% N = 50;
% r = linspace(0, 1, N);
% [X,Y,Z] = cylinder(r, N);
% 
% h = surf(X, Y, Z);
% 
% rotate(h, [1 1 0], 90);

plot3ex.m:

function p = plot3ex(varargin)

% Plots a line from each p1 to each p2.
% Inputs:
%   p1 3xN
%   p2 3xN
%   args plot3 configuration string
%   NOTE: p1 and p2 number of points can range from 1 to N
%   but if the number of points are different, one must be 1!
% PVB 2016

p1 = varargin{1};
p2 = varargin{2};
extraArgs = varargin(3:end);

N1 = size(p1,2);
N2 = size(p2,2);
N = N1;

if N1 == 1 && N2 > 1
    N = N2;
elseif N1 > 1 && N2 == 1
    N = N1
elseif N1 ~= N2
    error('if size(p1,2) ~= size(p1,2): size(p1,2) and/or size(p1,2) must be 1 !');
end

for i=1:N
    i1 = i;
    i2 = i;

    if i > N1
        i1 = N1;
    end
    if i > N2
        i2 = N2;
    end

    x = [p1(1,i1) p2(1,i2)];
    y = [p1(2,i1) p2(2,i2)];
    z = [p1(3,i1) p2(3,i2)];
    p = plot3(x,y,z,extraArgs{:});
end

2 个答案:

答案 0 :(得分:9)

这是解决方案。它基于https://math.stackexchange.com/a/205589/81266的精彩答案。我在Mathworld上了解到a spherical cap这是一个带有平面的3球的切割后,我通过谷歌搜索“球冠上的随机点”找到了这个答案。

这是功能:

function r = randSphericalCap(coneAngleDegree, coneDir, N, RNG)

if ~exist('coneDir', 'var') || isempty(coneDir)
  coneDir = [0;0;1];
end

if ~exist('N', 'var') || isempty(N)
  N = 1;
end

if ~exist('RNG', 'var') || isempty(RNG)
  RNG = RandStream.getGlobalStream();
end

coneAngle = coneAngleDegree * pi/180;

% Generate points on the spherical cap around the north pole [1].
% [1] See https://math.stackexchange.com/a/205589/81266
z = RNG.rand(1, N) * (1 - cos(coneAngle)) + cos(coneAngle);
phi = RNG.rand(1, N) * 2 * pi;
x = sqrt(1-z.^2).*cos(phi);
y = sqrt(1-z.^2).*sin(phi);

% If the spherical cap is centered around the north pole, we're done.
if all(coneDir(:) == [0;0;1])
  r = [x; y; z];
  return;
end

% Find the rotation axis `u` and rotation angle `rot` [1]
u = normc(cross([0;0;1], normc(coneDir)));
rot = acos(dot(normc(coneDir), [0;0;1]));

% Convert rotation axis and angle to 3x3 rotation matrix [2]
% [2] See https://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle
crossMatrix = @(x,y,z) [0 -z y; z 0 -x; -y x 0];
R = cos(rot) * eye(3) + sin(rot) * crossMatrix(u(1), u(2), u(3)) + (1-cos(rot))*(u * u');

% Rotate [x; y; z] from north pole to `coneDir`.
r = R * [x; y; z];

end

function y = normc(x)
y = bsxfun(@rdivide, x, sqrt(sum(x.^2)));
end

此代码只是实现joriki’s answer on math.stackexchange,填写了joriki省略的所有细节。

这是一个显示如何使用它的脚本。

clearvars

coneDir = [1;1;1];
coneAngleDegree = 30;
N = 1e4;

sol = randSphericalCap(coneAngleDegree, coneDir, N);
figure;plot3(sol(1,:), sol(2,:), sol(3,:), 'b.', 0,0,0,'rx');
grid
xlabel('x'); ylabel('y'); zlabel('z')
legend('random points','origin','location','best')
title('Final random points on spherical cap')

以下是以[1; 1; 1]向量为中心的30°球形帽的10'000点的3D图:

30° spherical cap

这是120°球冠:

120° spherical cap

现在,如果您查看coneDir = [1;1;1]处这些随机点之间角度的直方图,您会看到分布偏斜。这是分布:

Histogram of angles between coneDir and vectors on 120° cap

生成此代码的代码:

normc = @(x) bsxfun(@rdivide, x, sqrt(sum(x.^2)));
mysubspace = @(a,b) real(acos(sum(bsxfun(@times, normc(a), normc(b)))));

angs = arrayfun(@(i) mysubspace(coneDir, sol(:,i)), 1:N) * 180/pi;

nBins = 16;
[n, edges] = histcounts(angs, nBins);
centers = diff(edges(1:2))*[0:(length(n)-1)] + mean(edges(1:2));

figure('color','white');
bar(centers, n);
xlabel('Angle (degrees)')
ylabel('Frequency')
title(sprintf('Histogram of angles between coneDir and random points: %d deg', coneAngleDegree))

嗯,这是有道理的!如果你从coneDir周围的120°球冠生成点,当然 ,1°顶盖的样本将很少,而10°和11°之间的条带上限将有更多的积分。因此,我们要做的是将给定角度的点数归一化球面的表面积 角度。

这是一个函数,它为我们提供了半径为R的球冠的表面积和弧度为theta的角度(Mathworld’s spherical cap文章中的等式16):

rThetaToH = @(R, theta) R * (1 - cos(theta));
rThetaToS = @(R, theta) 2 * pi * R * rThetaToH(R, theta);

然后,我们可以通过bin边缘球形帽的表面积差异来标准化每个bin(上面n)的直方图计数:

figure('color','white');
bar(centers, n ./ diff(rThetaToS(1, edges * pi/180)))

图:

Normalized histogram

这告诉我们“随机向量的数量除以直方图箱边缘之间的球形段的表面积”。这是统一的!

(N.B。如果对原始代码生成的向量进行归一化直方图,使用拒绝抽样,则同样成立:标准化直方图是均匀的。与此相比,拒绝抽样是昂贵的。)

(注意2:注意在球体上挑选随机点的天真方式 - 首先生成方位角/仰角,然后将这些球面坐标转换为笛卡尔坐标 - 并不好,因为它在极点附近聚集点:{{ 3}},Mathworldexample。在整个球体上拾取点的一种方法是从3D正态分布中采样:这样你就不会在极点附近聚集。所以我相信你原来的技术是完全合适的,在球体上给你提供漂亮,均匀分布的点,没有任何聚束。上面描述的算法也做“正确的事情”,应该避免聚束。仔细评估任何提出的算法,以确保聚束近极点问题是避免的。)

答案 1 :(得分:0)

最好使用球面坐标并将其转换为笛卡尔坐标:

<div ng-include="'goodbye.html'" ></div>

如果所有点的长度都应为1,则r = 1(N,1);

编辑: 由于锥体与球体的交点首先形成一个圆圈,我们在一个圆内创建随机点,在极坐标中使用(45/2)raduis,并且@Ahmed Fasih评论为防止极点附近的点集中,我们应首先变换这个随机点,然后将极坐标转换为笛卡尔2D坐标,形成x0和y0

我们可以使用x0和y0作为phi&amp;球面坐标的角度和添加锥体的数值。 coneDirphi作为这些坐标的偏移量。 然后将球形转换为笛卡尔3D坐标