Выборка равномерно распределенных случайных точек внутри сферического объема
Я надеюсь, что смогу сгенерировать случайный однородный образец местоположений частиц, которые попадают в сферический объем.
Изображение ниже (любезно предоставлено http://nojhan.free.fr/metah/) показывает, что я ищу. Это кусочек сферы, показывающий равномерное распределение точек:
Вот что я сейчас получаю:
Вы можете видеть, что есть центр точек в центре из-за преобразования между сферическими и декартовыми координатами.
Код, который я использую:
def new_positions_spherical_coordinates(self):
radius = numpy.random.uniform(0.0,1.0, (self.number_of_particles,1))
theta = numpy.random.uniform(0.,1.,(self.number_of_particles,1))*pi
phi = numpy.arccos(1-2*numpy.random.uniform(0.0,1.,(self.number_of_particles,1)))
x = radius * numpy.sin( theta ) * numpy.cos( phi )
y = radius * numpy.sin( theta ) * numpy.sin( phi )
z = radius * numpy.cos( theta )
return (x,y,z)
Ниже приведен некоторый код MATLAB, который предположительно создает однородную сферическую выборку, которая похожа на уравнение, заданное http://nojhan.free.fr/metah. Я просто не могу расшифровать это или понять, что они сделали.
function X = randsphere(m,n,r)
% This function returns an m by n array, X, in which
% each of the m rows has the n Cartesian coordinates
% of a random point uniformly-distributed over the
% interior of an n-dimensional hypersphere with
% radius r and center at the origin. The function
% 'randn' is initially used to generate m sets of n
% random variables with independent multivariate
% normal distribution, with mean 0 and variance 1.
% Then the incomplete gamma function, 'gammainc',
% is used to map these points radially to fit in the
% hypersphere of finite radius r with a uniform % spatial distribution.
% Roger Stafford - 12/23/05
X = randn(m,n);
s2 = sum(X.^2,2);
X = X.*repmat(r*(gammainc(s2/2,n/2).^(1/n))./sqrt(s2),1,n);
Я был бы очень признателен за любые предложения по созданию действительно однородного образца из сферического тома в Python.
Кажется, есть много примеров, показывающих, как делать выборки из однородной сферической оболочки, но это, кажется, легче, чем простая проблема. Проблема связана с масштабированием - в радиусе 0,1 должно быть меньше частиц, чем в радиусе 1,0, чтобы получить однородный образец из объема сферы.
Редактировать: Исправлено и убрано то, что я обычно просил, и имел в виду униформу
10 ответов
Хотя я предпочитаю метод отбрасывания для сфер, для полноты я предлагаю точное решение.
В сферических координатах, используя преимущество правила выборки:
phi = random(0,2pi)
costheta = random(-1,1)
u = random(0,1)
theta = arccos( costheta )
r = R * cuberoot( u )
теперь у вас есть (r, theta, phi)
группа, которая может быть преобразована в (x, y, z)
обычным способом
x = r * sin( theta) * cos( phi )
y = r * sin( theta) * sin( phi )
z = r * cos( theta )
Существует блестящий способ генерировать равномерно точки на сфере в n-мерном пространстве, и вы указали это в своем вопросе (я имею в виду код MATLAB).
Почему это работает? Ответ таков: посмотрим на плотность вероятности n-мерного нормального распределения. Равен (до постоянного)
exp (-x_1 * x_1 / 2) * exp (-x_2 * x_2 / 2)... = exp (-r * r / 2), поэтому это не зависит от направления, только от расстояния! Это означает, что после нормализации вектора полученная плотность распределения будет постоянной по всей сфере.
Этот метод должен быть определенно предпочтительным из-за его простоты, универсальности и эффективности (и красоты). Код, который генерирует 1000 событий на сфере в трех измерениях:
size = 1000
n = 3 # or any positive integer
x = numpy.random.normal(size=(size, n))
x /= numpy.linalg.norm(x, axis=1)[:, numpy.newaxis]
Кстати, хорошая ссылка для просмотра: http://www-alg.ist.hokudai.ac.jp/~jan/randsphere.pdf
Что касается равномерного распределения внутри сферы, вместо нормализации вектора вы должны умножить vercor на некоторое f(r): f(r)*r распределено с плотностью, пропорциональной r^n на [0,1], что было сделано в коде вы разместили
Создайте набор точек, равномерно распределенных внутри куба, а затем отбросьте те, чье расстояние от центра превышает радиус желаемой сферы.
Я согласен с Alleo. Я перевел ваш код Matlab на Python, и он может очень быстро генерировать тысячи точек (доли секунды в моем компьютере для 2D и 3D). Я даже запускал его для 5D гиперсфер. Я нашел ваш код настолько полезным, что я применяю его в исследовании. Тим МакДжилтон, кого я должен добавить в качестве ссылки?
import numpy as np
from scipy.special import gammainc
from matplotlib import pyplot as plt
def sample(center,radius,n_per_sphere):
r = radius
ndim = center.size
x = np.random.normal(size=(n_per_sphere, ndim))
ssq = np.sum(x**2,axis=1)
fr = r*gammainc(ndim/2,ssq/2)**(1/ndim)/np.sqrt(ssq)
frtiled = np.tile(fr.reshape(n_per_sphere,1),(1,ndim))
p = center + np.multiply(x,frtiled)
return p
fig1 = plt.figure(1)
ax1 = fig1.gca()
center = np.array([0,0])
radius = 1
p = sample(center,radius,10000)
ax1.scatter(p[:,0],p[:,1],s=0.5)
ax1.add_artist(plt.Circle(center,radius,fill=False,color='0.5'))
ax1.set_xlim(-1.5,1.5)
ax1.set_ylim(-1.5,1.5)
ax1.set_aspect('equal')
Нормированный гауссовский трехмерный вектор равномерно распределен по сфере, см. http://mathworld.wolfram.com/SpherePointPicking.html
Например:
N = 1000
v = numpy.random.uniform(size=(3,N))
vn = v / numpy.sqrt(numpy.sum(v**2, 0))
Будет ли это достаточно равномерным для ваших целей?
In []: p= 2* rand(3, 1e4)- 1
In []: p= p[:, sum(p* p, 0)** .5<= 1]
In []: p.shape
Out[]: (3, 5216)
Ломтик этого
In []: plot(p[0], p[2], '.')
похоже:
Функция для однородной выборки из гиперсферы
Функция ниже равномерно выбирает точки из гиперсферы:
import numpy as np
def sample(center, radius, n_samples, seed=None):
# initial values
d = center.shape[0]
# sample n_samples points in d dimensions from a standard normal distribution
rng = np.random.default_rng(seed)
samples = rng.normal(size=(n_samples, d))
# make the samples lie on the surface of the unit hypersphere
normalize_radii = np.linalg.norm(samples, axis=1)[:, np.newaxis]
samples /= normalize_radii
# make the samples lie inside the hypersphere with the correct density
uniform_points = rng.uniform(size=n_samples)[:, np.newaxis]
new_radii = np.power(uniform_points, 1/d)
samples *= new_radii
# scale the points to have the correct radius and center
samples = samples * radius + center
return samples
Этот код работает следующим образом.
Во-первых, сначала генерируется
Во-вторых, он вычисляет радиус каждой точки данных с помощью . (Примечание:
В-третьих, он создает новый радиус для каждой точки данных, генерируя
В-четвертых, этот код масштабирует все точки на поверхности единичной гиперсферы по этому новому радиусу, равномерно распределяя эти точки по объему гиперсферы. Затем он сдвигает эти точки, чтобы получить желаемое
Функция Производительность
Мой код намного быстрее, чем код @Daniel, который опирается на
def time_it():
# initial values
d = 20
center, radius = np.full(d, 2), 3
n_samples_list = np.logspace(3, 6, num=10).astype(int)
runtime_my_code, runtime_daniel_code = [], []
for n_samples in n_samples_list:
# time my code
start = time.perf_counter()
sample_hypersphere_uniformly(center, radius, n_samples)
end = time.perf_counter()
duration = end - start
runtime_my_code.append(duration)
# time Daniel's code
start = time.perf_counter()
daniels_code(center, radius, n_samples)
end = time.perf_counter()
duration = end - start
runtime_daniel_code.append(duration)
# plot the results
fig, ax = plt.subplots()
ax.scatter(n_samples_list, runtime_my_code)
ax.scatter(n_samples_list, runtime_daniel_code)
ax.plot(n_samples_list, runtime_my_code, label='my code')
ax.plot(n_samples_list, runtime_daniel_code, label='daniel\'s code')
ax.set(xlabel='n_samples',
ylabel='runtime (s)', title='runtime VS n_samples')
plt.legend()
fig.savefig('runtime_vs_n_samples.png')
Эта диаграмма показывает, что мой код намного эффективнее, чем у Дэниела.
Другие источники
Ознакомьтесь с этим полезным руководством по выборке из гиперсферы здесь. Это ясно объясняет, почему эта математика работает.
import random
R = 2
def sample_circle(center):
a = random.random() * 2 * np.pi
r = R * np.sqrt(random.random())
x = center[0]+ (r * np.cos(a))
y = center[1] + (r * np.sin(a))
return x,y
ps = np.array([sample_circle((0,0)) for i in range(100)])
plt.plot(ps[:,0],ps[:,1],'.')
plt.xlim(-3,3)
plt.ylim(-3,3)
plt.show()
import numpy as np
import matplotlib.pyplot as plt
r= 30.*np.sqrt(np.random.rand(1000))
#r= 30.*np.random.rand(1000)
phi = 2. * np.pi * np.random.rand(1000)
x = r * np.cos(phi)
y = r * np.sin(phi)
plt.figure()
plt.plot(x,y,'.')
plt.show()
это то, что вы хотите
Вы можете просто генерировать случайные точки в сферических координатах (при условии, что вы работаете в 3D): S(r, θ, φ), где r ∈ [0, R), θ ∈ [0, π ], φ ∈ [0, 2π), где R - радиус вашей сферы. Это также позволит вам напрямую контролировать, сколько очков генерируется (т.е. вам не нужно сбрасывать какие-либо очки).
Чтобы компенсировать потерю плотности с радиусом, вы должны сгенерировать радиальную координату, следуя распределению по степенному закону (объяснение того, как это сделать, см. В ответе dmckee).
Если вашему коду требуются (x,y,z) (т.е. декартовы) координаты, вы должны просто преобразовать случайно сгенерированные точки в сферические в декартовые координаты, как описано здесь.