我正在寻找一种简单的方法来创建受锥形区域约束的随机单位向量。原点始终是[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代码生成频率分布(在评论中)。 我想知道如何获得矩形或正态分布。
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
答案 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图:
这是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)))
图:
这告诉我们“随机向量的数量除以直方图箱边缘之间的球形段的表面积”。这是统一的!
(N.B。如果对原始代码生成的向量进行归一化直方图,使用拒绝抽样,则同样成立:标准化直方图是均匀的。与此相比,拒绝抽样是昂贵的。)
(注意2:注意在球体上挑选随机点的天真方式 - 首先生成方位角/仰角,然后将这些球面坐标转换为笛卡尔坐标 - 并不好,因为它在极点附近聚集点:{{ 3}},Mathworld,example。在整个球体上拾取点的一种方法是从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坐标