Сгенерируйте случайную выборку точек, распределенных на поверхности единичной сферы

Я пытаюсь создать случайные точки на поверхности сферы, используя numpy. Я просмотрел сообщение, в котором объясняется равномерное распределение, здесь. Однако нужны идеи, как генерировать точки только на поверхности сферы. У меня есть координаты (x, y, z) и радиус каждой из этих сфер.

Я не очень хорошо разбираюсь в математике на этом уровне и пытаюсь разобраться в моделировании Монте-Карло.

Любая помощь будет высоко ценится.

Спасибо парин


person Parin    schedule 28.11.2015    source источник


Ответы (5)


Основываясь на последнем подходе на этой странице, вы можете просто сгенерировать вектор, состоящий из независимых выборок из трех стандартных нормальных распределений, затем нормализуйте вектор так, чтобы его величина была равна 1:

import numpy as np

def sample_spherical(npoints, ndim=3):
    vec = np.random.randn(ndim, npoints)
    vec /= np.linalg.norm(vec, axis=0)
    return vec

Например:

from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import axes3d

phi = np.linspace(0, np.pi, 20)
theta = np.linspace(0, 2 * np.pi, 40)
x = np.outer(np.sin(theta), np.cos(phi))
y = np.outer(np.sin(theta), np.sin(phi))
z = np.outer(np.cos(theta), np.ones_like(phi))

xi, yi, zi = sample_spherical(100)

fig, ax = plt.subplots(1, 1, subplot_kw={'projection':'3d', 'aspect':'equal'})
ax.plot_wireframe(x, y, z, color='k', rstride=1, cstride=1)
ax.scatter(xi, yi, zi, s=100, c='r', zorder=10)

введите описание изображения здесь

Тот же метод также распространяется на выбор равномерно распределенных точек на единичной окружности (ndim=2) или на поверхностях единичных гиперсфер более высоких измерений.

person ali_m    schedule 28.11.2015
comment
Кажется, это имеет чрезмерную плотность углов, так как это нормализует куб ndim, а не сферу ndim. Я думаю, что избыточную плотность можно исправить, применив выделение в функции, чтобы точки вне единичной сферы не учитывались перед их нормализацией к сфере. Я использовал, вероятно, не очень функцию Pythonic, чтобы сделать это для ndim = 3. Однако вы могли бы придумать более быстрый способ сделать это. def inside(x, y, z): r = np.sqrt(x**2 + y**2 + z**2) if r <= 1: return True else: return False - person Liam Plybon; 14.05.2020
comment
Это было бы проблемой, если бы исходные точки отбирались равномерно внутри куба, а вместо этого я выбираю из нормального распределения. - person ali_m; 14.05.2020
comment
@ali_m Это решение, которое я наконец реализовал. position_list = [] for _ in range(num_pts): vec = np.random.normal(0, 1, 3) # select three random points vec /= np.linalg.norm(vec) # normalize vector vec *= radius # lengthen vector to desired radius position_list.append(list(vec)) Это кажется правильным? - person alluppercase; 02.10.2020

Точки на поверхности сферы можно выразить с помощью двух сферических координат theta и phi с 0 < theta < 2pi и 0 < phi < pi.

Формула перевода в декартовы x, y, z координаты:

x = r * cos(theta) * sin(phi)
y = r * sin(theta) * sin(phi)
z = r * cos(phi)

где r - радиус сферы.

Таким образом, программа могла произвольно выбирать theta и phi в их диапазонах с равномерным распределением и генерировать из них декартовы координаты.

Но тогда точки распределяются более плотно на полюсах сферы. Чтобы точки были равномерно распределены на поверхности сферы, phi необходимо выбрать как phi = acos(a), где -1 < a < 1 выбрано для равномерного распределения.

Для кода Numpy он будет таким же, как в Выборка равномерно распределенных случайных точек внутри сферического объема , за исключением того, что переменная radius имеет фиксированное значение.

person tmlen    schedule 28.11.2015
comment
Тэта и фи обычно называются наоборот, тэта - полярный угол, а фи - азимутальный :) Также можно сгенерируйте 3 независимых нормали и нормализуйте полученный вектор. - person Andras Deak; 29.11.2015

Другой способ, который в зависимости от оборудования может быть намного быстрее.

Выберите a, b, c, чтобы получить три случайных числа от -1 до 1 каждое.

Рассчитать r2 = a^2 + b^2 + c^2

Если r2> 1.0 (= точка не в сфере) или r2 ‹0,00001 (= точка слишком близко к центру, у нас будет деление на ноль при проецировании на поверхность сферы), вы отбрасываете значения и выберите другой набор случайных a, b, c

В противном случае у вас есть случайная точка (относительно центра сферы):

ir = R / sqrt(r2)
x = a * ir
y = b * ir
z = c * ir
person Soonts    schedule 28.11.2015
comment
Использование единых координат не даст вам равномерного распределения на сфере: представьте себе куб с равномерно случайными точками, спроецированный на сферу. Это неправильно, у вас будет слишком много очков в углах. Вместо этого используйте нормально распределенные координаты. - person Andras Deak; 29.11.2015
comment
У меня не так много точек в углах, потому что я отвергаю точки, где r2 ›1.0. - person Soonts; 29.11.2015
comment
Хм ... Да, извините, я проигнорировал эту часть. Хотя я не уверен, быстрее ли это, так как приходится отбрасывать много очков, но вы правы. Пожалуйста, отредактируйте свой пост, чтобы я мог удалить свой голос против :) - person Andras Deak; 29.11.2015
comment
Обычно это намного быстрее, чем эти тригонометрические функции. Я отклоняю только 1,0 - 4/3 * π / 8 = 48% точек (плюс небольшой объем около центра, чтобы избежать деления на ноль при проецировании на поверхность сферы). - person Soonts; 29.11.2015
comment
Да, небольшая деталь вокруг начала координат не сильно влияет. Я думал о версии, в которой вы генерируете 3 нормально распределенные переменные, но, честно говоря, я не знаю, какие вычислительные усилия для этого нужны :) В любом случае, ваше решение определенно правильное и новое. В моем предыдущем комментарии я просто имел в виду, что вы должны сделать несколько тривиальных изменений, чтобы мой голос против был разблокирован. - person Andras Deak; 29.11.2015
comment
Насчет производительности - мне лень измерять, но я ожидаю, что мой метод будет примерно в 50 раз быстрее: ГСЧ быстрые, неверное предсказание ветвления обычно составляет 20 циклов, sqrt также составляет около 20 циклов, а один sin или cos составляет 200-300 циклов . - person Soonts; 29.11.2015
comment
Вы заинтересовали меня, поэтому я добавил ответ CW с некоторыми попытками синхронизации. В вашем решении меньше всего кода, поэтому, вероятно, я не реализовал его оптимально. Если хотите, можете улучшить сравнение :) - person Andras Deak; 29.11.2015

После некоторого обсуждения с @Soonts мне стало интересно узнать о производительности трех подходов, используемых в ответах: один с генерацией случайных углов, один с использованием нормально распределенных координат и один, отклоняющий равномерно распределенные точки.

Вот моя попытка сравнения:

import numpy as np

def sample_trig(npoints):
    theta = 2*np.pi*np.random.rand(npoints)
    phi = np.arccos(2*np.random.rand(npoints)-1)
    x = np.cos(theta) * np.sin(phi)
    y = np.sin(theta) * np.sin(phi)
    z = np.cos(phi)
    return np.array([x,y,z])

def sample_normals(npoints):
    vec = np.random.randn(3, npoints)
    vec /= np.linalg.norm(vec, axis=0)
    return vec

def sample_reject(npoints):
    vec = np.zeros((3,npoints))
    abc = 2*np.random.rand(3,npoints)-1
    norms = np.linalg.norm(abc,axis=0) 
    mymask = norms<=1
    abc = abc[:,mymask]/norms[mymask]
    k = abc.shape[1]
    vec[:,0:k] = abc
    while k<npoints:
       abc = 2*np.random.rand(3)-1
       norm = np.linalg.norm(abc)
       if 1e-5 <= norm <= 1:  
           vec[:,k] = abc/norm
           k = k+1
    return vec

Затем за 1000 очков

In [449]: timeit sample_trig(1000)
1000 loops, best of 3: 236 µs per loop

In [450]: timeit sample_normals(1000)
10000 loops, best of 3: 172 µs per loop

In [451]: timeit sample_reject(1000)
100 loops, best of 3: 13.7 ms per loop

Обратите внимание, что в реализации, основанной на отклонении, я сначала сгенерировал npoints выборки и выбросил плохие, а для генерации остальных точек я использовал только цикл. Казалось, что прямое пошаговое отклонение занимает больше времени. Я также удалил проверку деления на ноль, чтобы было чище сравнение с случаем sample_normals.


Удаление векторизации из двух прямых методов ставит их в один ряд:

def sample_trig_loop(npoints):
    x = np.zeros(npoints)
    y = np.zeros(npoints)
    z = np.zeros(npoints)
    for k in range(npoints):
        theta = 2*np.pi*np.random.rand()
        phi = np.arccos(2*np.random.rand()-1)
        x[k] = np.cos(theta) * np.sin(phi)
        y[k] = np.sin(theta) * np.sin(phi)
        z[k] = np.cos(phi)
    return np.array([x,y,z])

def sample_normals_loop(npoints):
    vec = np.zeros((3,npoints))
    for k in range(npoints):
      tvec = np.random.randn(3)
      vec[:,k] = tvec/np.linalg.norm(tvec)
    return vec
In [464]: timeit sample_trig(1000)
1000 loops, best of 3: 236 µs per loop

In [465]: timeit sample_normals(1000)
10000 loops, best of 3: 173 µs per loop

In [466]: timeit sample_reject(1000)
100 loops, best of 3: 14 ms per loop

In [467]: timeit sample_trig_loop(1000)
100 loops, best of 3: 7.92 ms per loop

In [468]: timeit sample_normals_loop(1000)
100 loops, best of 3: 10.9 ms per loop
person Community    schedule 29.11.2015

(отредактировано с учетом исправлений, внесенных в комментарии)

В 2004 году я исследовал несколько подходов к этой проблеме с использованием постоянного времени.

предполагая, что вы работаете в сферических координатах, где theta - угол вокруг вертикальной оси (например, долгота), а phi - угол, поднятый вверх от экватора (например, широта), тогда для получения равномерного распределения случайных точек в полушарии к северу от по экватору вы делаете это:

  1. выберите theta = rand (0, 360).
  2. выберите phi = 90 * (1 - sqrt (rand (0, 1))).

чтобы получить точки на сфере, а не на полусфере, тогда просто инвертируйте phi в 50% случаев.

для любопытных, аналогичный подход имеет место для создания равномерно распределенных точек на единичном диске:

  1. выберите theta = rand (0, 360).
  2. выберите radius = sqrt (rand (0, 1)).

У меня нет доказательств правильности этих подходов, но я использовал их с большим успехом за последнее десятилетие или около того, и я убежден в их правильности.

некоторые иллюстрации (с 2004 г.) различных подходов находятся здесь, включая визуализация подхода к выбору точек на поверхности куба и их нормализации на сфере.

person orion elenzil    schedule 01.12.2015
comment
Я не уверен, что понимаю ваш подход. Если я вычисляю это на бумаге, плотность вероятности на (полусфере) не кажется однородной с описанным выше подходом. Что еще хуже, если я попытаюсь воспроизвести ваше вычисление, то вот что я получу: слишком много точек на полюсах, слишком мало на экваторе (тот же результат, что и на бумаге). Код: figure; N=5000; theta=2*pi*rand(1,N); phi=pi/2*[sqrt(rand(1,N/2)), -sqrt(rand(1,N/2))]; plot3(cos(phi).*cos(theta),cos(phi).*sin(theta),sin(phi),'.'); axis equal; axis vis3d - person Andras Deak; 02.12.2015
comment
@AndrasDeak хм, да, похоже, это не так. что возвращают rand(1, N) и rand(1, N/2)? этот подход определенно предполагает, что значение внутри sqrt является равномерным распределением в диапазоне [0, 1]. - person orion elenzil; 02.12.2015
comment
Извините, забыл, что это numpy thread, выше был matlab ... rand(1,N) и rand(1,N/2) создают векторы длины N и N/2 (соответственно), каждый элемент одинаков на [0, 1]. Т.е. то же, что numpy.random.rand(1,N) и т. д. - person Andras Deak; 02.12.2015
comment
@AndrasDeak - Я у тебя в долгу. Мне удалось воспроизвести ваши результаты. Моя формула ошибочна; Phi следует выбрать как 90 * (1 - sqrt(rand(0, 1)). Я подозреваю, что ошибка возникла из-за неправильного сферического ›декартового отображения с моей стороны. Я реализовал демонстрацию WebGL (с использованием ваших формул сопоставления) здесь. - person orion elenzil; 03.12.2015
comment
Я считаю, что разгадал загадку :) Плотность вероятности, которую вы используете, не однородна, а пропорциональна (1-2/pi*phi)/cos(phi), если вы измеряете phi в радианах. Хотя это непостоянно, довольно близко. Итак, вы приближаете равномерное распределение. Вы можете увидеть, что это на самом деле не однородно, сравнив mean(z) из большой выборки, сгенерированной в полушарии. В действительно однородном случае (как в случае с нормально распределенными координатами) вы получите 0.50, но с вашим кодом вы получите 0.46. Достаточно близко, чтобы быть незаметным визуально, но не однородным :) - person Andras Deak; 03.12.2015
comment
Другими словами, вы используете pi/2*(1-sqrt(x)) в качестве глобального аппроксиматора для asin(1-x), который даст вам точное равномерное распределение. И снова, это довольно хорошее приближение. - person Andras Deak; 03.12.2015
comment
интересно, спасибо. Я думаю, вы здесь больше разбираетесь в математике, чем я. Я также проработаю симуляцию asin(1-x) и дам вам знать. - person orion elenzil; 03.12.2015
comment
Заранее спасибо :) Если хотите, я могу описать математику. - person Andras Deak; 03.12.2015
comment
обновили имитацию. одно небольшое примечание, x - случайная величина на [0, 1], так что asin(1-x) == asin(x), верно? - person orion elenzil; 03.12.2015
comment
Да, я думаю, вы правы, и x, и 1-x должны быть одинаковыми на [0,1]. Что касается диска: в этом случае sqrt(x) должно быть точным решением, но я это не проверял. - person Andras Deak; 03.12.2015