【问题标题】:Create random unit vector inside a defined conical region在定义的圆锥区域内创建随机单位向量
【发布时间】:2016-08-17 12:57:26
【问题描述】:

我正在寻找一种简单的方法来创建受圆锥区域约束的随机单位向量。原点始终是 [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

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

来自下面的测试代码的结果图像

使用Ahmed Fasih 代码的结果频率分布(以 cmets 为单位)。 我想知道如何获得矩形或正态分布。

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

测试代码:

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

【问题讨论】:

  • 您提供的代码并不能真正说明您的问题。因此,如果我正确地陈述了您的问题,请告诉我:您有一个 defined 锥形区域(已知:origindirection出发角),你想生成一个随机向量(相同的origin),其方向包含在圆锥域中?
  • 是的,完全正确。我已经更新了代码示例。
  • 余弦角的分布有什么要求吗?我可能认为角度应该是统一的,但是您的代码生成的向量相对于coneDir 的角度非常倾斜。
  • 您能否更新描述您希望结果具有哪些统计属性的问题?有很多选择,这个问题很有趣。
  • 如果您想要得到的角度正常均匀分布,请以此为起点。生成代表您的角度的n 随机数(根据您想要的分布)。每个数字(角度)将围绕您的圆锥中心线定义一个 。然后在每个圆圈上生成一个随机点(或更多,但每个圆圈使用相同数量的点)。获得的所有点都是你的向量(原点为 0),你的分布应该得到尊重。

标签: matlab vector linear-algebra


【解决方案1】:

这是解决方案。它基于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')

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

这是 120° 球冠:


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

生成此代码的代码:

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)))

图:

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

(注意,如果您使用拒绝采样对原始代码生成的向量执行此归一化直方图,则同样适用:归一化直方图是统一的。只是与此相比,拒绝采样成本较高。)

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

【讨论】:

  • 哦,嘿!好的!非常感谢!
  • 不错。 +1 很好地解释了为什么生成方位角/仰角会将点集中在两极。
  • 添加了一个错误修复(参见历史记录),因为最初如果您询问coneDir = [0; 0; 1](北极),代码会中断并返回所有NaNs。如果coneDir 非常靠近北极,可能仍然存在数值问题,因此请避免这些情况。代码是公共领域。
  • @Pedro77 同样,Math.StackExchange math.stackexchange.com/a/44691/81266 上的这个答案对在整个球体上采样的算法有非常清晰的描述。我在问题(math.stackexchange.com/a/205589/81266)中提到的另一个答案告诉您如何使第一个算法适应上限(而不是整个球体),以及如何将其旋转到coneDir,但不可否认它有点棘手我的那部分代码肯定需要更多的 cmets。请稍候。
  • @Pedro77 我做了一个东西:bl.ocks.org/fasiha/2bbfc20ef882d76e27f17df31950d156
【解决方案2】:

最好使用球坐标,并转换成笛卡尔坐标:

coneDirtheta = rand(1) * 2 * pi;
coneDirphi = rand(1) * pi;
coneAngle = 45;
N = 1000;
%perfom transformation preventing concentration of points around the pole
rpolar = acos(cos(coneAngle/2*pi/180) + (1-cos(coneAngle/2*pi/180)) * rand(N, 1));
thetapolar = rand(N,1) * 2 * pi;
x0 = rpolar .*  cos(thetapolar);
y0 = rpolar .* sin(thetapolar);

theta = coneDirtheta + x0;
phi = coneDirphi + y0;
r = rand(N, 1);
x = r .* cos(theta) .* sin(phi);
y = r .* sin(theta) .* sin(phi);
z = r .* cos(phi);
scatter3(x,y,z)

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

编辑: 由于圆锥体与球体的交点首先形成一个圆,因此我们在极坐标中半径为 (45 / 2) 的圆内创建随机点,正如@Ahmed Fasih 所评论的那样,为了防止点集中在极点附近,我们应该首先转换这个随机点,然后将极坐标转换为笛卡尔二维坐标以形成 x0 和 y0

我们可以使用 x0 和 y0 作为球坐标的 phi 和 theta 角,并添加 coneDirtheta 和 coneDirphi 作为这些坐标的偏移量。 然后将球面坐标转换为笛卡尔 3D 坐标

【讨论】:

  • 您能解释一下您的解决方案吗?得到的向量不会形成一个圆锥体,它就像一个“矩形圆锥体”约束区域。
  • @Pedro77 答案已更新为“圆锥形”,我添加了一些解释。
  • 我认为这个解决方案有同样的问题,试图通过统一生成方位角和仰角(球坐标的角度)在球体上生成随机点:参见mathworld.wolfram.com/SpherePointPicking.html 的第一段——如果你使用这种技术,你的解决方案会在两极附近聚集起来。
  • @Ahmed Fasih 有趣!谢谢,我编辑了答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-23
  • 1970-01-01
相关资源
最近更新 更多