Метод золотої спіралі
Ви сказали, що не можете змусити метод золотої спіралі працювати, і це прикро, тому що це дійсно, дуже добре. Я хотів би дати вам повне розуміння цього, щоб, можливо, ви зрозуміли, як уберегти це від того, щоб не бути "згубленими".
Отже, ось швидкий, невипадковий спосіб створити ґрати, що є приблизно правильним; як обговорювалося вище, жодна решітка не буде ідеальною, але це може бути досить добре. Його порівнюють з іншими методами, наприклад, на BendWavy.org, але він просто приємний і гарний вигляд, а також гарантія рівного відстані в ліміті.
Буквар: спіраль соняшнику на одиничному диску
Щоб зрозуміти цей алгоритм, спершу запрошую вас переглянути 2D алгоритм спіралі соняшнику. Це ґрунтується на тому, що найбільше ірраціональне число - це відношення золота, (1 + sqrt(5))/2
і якщо один випромінює точки підходом «стоїть у центрі, повертаєш золоте співвідношення цілих витків, а потім випромінюєш іншу точку в цьому напрямку», природно будується спіраль, яка, переходячи до все більшої кількості очок, все ж відмовляється мати чітко визначені «бруски», на які розташовуються точки. (Примітка 1.)
Алгоритм рівномірного інтервалу на диску:
from numpy import pi, cos, sin, sqrt, arange
import matplotlib.pyplot as pp
num_pts = 100
indices = arange(0, num_pts, dtype=float) + 0.5
r = sqrt(indices/num_pts)
theta = pi * (1 + 5**0.5) * indices
pp.scatter(r*cos(theta), r*sin(theta))
pp.show()
і це дає результати, схожі на (n = 100 і n = 1000):
Розміщення точок радіально
Ключова дивна річ - формула r = sqrt(indices / num_pts)
; як я прийшов до того? (Примітка 2.)
Ну, я тут використовую квадратний корінь, тому що я хочу, щоб вони мали рівний простір між диском. Це те саме, що говорити, що в межах великого N я хочу, щоб невелика область R ∈ ( r , r + d r ), Θ ∈ ( θ , θ + d θ ) містила ряд точок, пропорційних її площі, що є r d r d θ . Тепер, якщо ми робимо вигляд, що мова йде про випадкову величину, це має прямолінійну інтерпретацію, що говорить про те, що щільність спільної ймовірності для ( R , Θ ) просто crдля деякої постійної с . Нормалізація на одиничному диску тоді приводить c = 1 / π.
Тепер дозвольте ввести хитрість. Це випливає з теорії ймовірностей, де вона відома як вибірка зворотного CDF : припустимо, ви хотіли генерувати випадкову величину з щільністю ймовірності f ( z ) і у вас є випадкова величина U ~ Уніформа (0, 1), як і виходить з random()
у більшості мов програмування. Як це зробити?
- По-перше, перетворіть свою щільність на функцію кумулятивного розподілу або CDF, яку ми будемо називати F ( z ). CDF, пам’ятайте, монотонно зростає від 0 до 1 з похідною f ( z ).
- Потім обчисліть обернену функцію CDF F -1 ( z ).
- Ви побачите, що Z = F -1 ( U ) розподілено відповідно до цільової щільності. (Примітка 3).
Тепер спіральний трюк із золотим співвідношенням розміщує точки у добре рівномірному шарі для θ, тому давайте інтегруємо це; для одиничного диска нам залишається F ( r ) = r 2 . Отже, обернена функція - F -1 ( u ) = u 1/2 , і тому ми би генерували випадкові точки на диску в полярних координатах з r = sqrt(random()); theta = 2 * pi * random()
.
Тепер замість випадкового відбору цієї зворотної функції ми рівномірно відбираємо її, і приємна річ у рівномірному відборі полягає в тому, що наші результати про те, як точки розподіляються в межах великого N, будуть вести себе так, як ніби ми його випадково відібрали. Це поєднання - хитрість. Замість того , щоб random()
використовувати (arange(0, num_pts, dtype=float) + 0.5)/num_pts
, так що, скажімо, якщо ми хочемо відібрати 10 балів, вони є r = 0.05, 0.15, 0.25, ... 0.95
. Ми рівномірно відбираємо r, щоб отримати рівний простір, і використовуємо приріст соняшника, щоб уникнути жахливих «брусків» точок у виході.
Зараз робимо соняшник на кулі
Зміни, які нам потрібно внести, щоб поставити точку сфери з точками, лише пов'язані з вимиканням полярних координат для сферичних координат. Радіальна координата, звичайно, не входить до цього, тому що ми знаходимося в одиничній сфері. Щоб зробити тут дещо більш послідовним, навіть якщо я був підготовлений як фізик, я буду використовувати координати математиків, де 0 ≤ φ ≤ π - широта, що сходить від полюса, а 0 ≤ θ ≤ 2π - довгота. Тож відмінність зверху полягає в тому, що ми в основному замінюємо змінну r на φ .
Наш елемент області, який був r d r d θ , тепер стає не набагато складнішим гріхом ( φ ) d φ d θ . Отже, наша щільність суглоба для рівномірного проміжку - sin ( φ ) / 4π. Інтегруючи θ , знаходимо f ( φ ) = sin ( φ ) / 2, таким чином, F ( φ ) = (1 - cos ( φ )) / 2. Інвертуючи це, ми можемо побачити, що рівномірна випадкова величина виглядатиме як acos (1 - 2 u ), але ми вибірково вибираємо рівномірно, а не випадково, тому використовуємо φ k = acos (1 - 2 ( k+ 0,5) / N ). А решта алгоритму просто проектує це на координати x, y і z:
from numpy import pi, cos, sin, arccos, arange
import mpl_toolkits.mplot3d
import matplotlib.pyplot as pp
num_pts = 1000
indices = arange(0, num_pts, dtype=float) + 0.5
phi = arccos(1 - 2*indices/num_pts)
theta = pi * (1 + 5**0.5) * indices
x, y, z = cos(theta) * sin(phi), sin(theta) * sin(phi), cos(phi);
pp.figure().add_subplot(111, projection='3d').scatter(x, y, z);
pp.show()
Знову для n = 100 і n = 1000 результати виглядають так:
Подальші дослідження
Я хотів вигукувати блог Мартіна Робертса. Зауважте, що вище я створив зміщення своїх індексів, додавши 0,5 до кожного індексу. Це мене просто візуально приваблювало, але виявляється, що вибір компенсації має велике значення і не є постійним протягом інтервалу і може означати отримання на 8% кращої точності в упаковці при правильному виборі . Також повинен бути спосіб отримати його послідовність R 2 для покриття сфери, і було б цікаво побачити, чи це також призвело до хорошого рівного покриття, можливо, як є, але, можливо, його потрібно, скажімо, взяти лише у половини одиничний квадрат вирізати по діагоналі або так і розтягнути навколо, щоб отримати коло.
Примітки
Ці "бари" утворені раціональним наближенням до числа, а найкращі раціональні наближення до числа виходять із його тривалого вираження дробу, z + 1/(n_1 + 1/(n_2 + 1/(n_3 + ...)))
де z
це ціле число і n_1, n_2, n_3, ...
є або кінцевою, або нескінченною послідовністю натуральних чисел:
def continued_fraction(r):
while r != 0:
n = floor(r)
yield n
r = 1/(r - n)
Оскільки частина дробу 1/(...)
завжди знаходиться між нулем і одиницею, велике ціле число у триваючому дробі забезпечує особливо гарне раціональне наближення: "одна, поділена на щось між 100 і 101", краще, ніж "одна, поділена на щось між 1 і 2." Тому найбільш ірраціональним числом є те, яке є 1 + 1/(1 + 1/(1 + ...))
і не має особливо гарних раціональних наближень; можна вирішити φ = 1 + 1 / φ шляхом множення на φ, щоб отримати формулу золотого відношення.
Для людей, які не так знайомі з NumPy - всі функції "векторизовані", так що sqrt(array)
це те саме, що можуть писати інші мови map(sqrt, array)
. Отже, це sqrt
додаток по компонентам . Те ж стосується поділу на скаляр або додавання зі скалярами - вони застосовуються до всіх компонентів паралельно.
Доказ простий, коли ви знаєте, що це результат. Якщо ви запитаєте, яка ймовірність, що z < Z < z + d z , це те саме, що запитати, яка ймовірність, що z < F -1 ( U ) < z + d z , застосуйте F до всіх трьох виразів, зазначивши, що це монотонно зростаюча функція, отже, F ( z ) < U < F ( z + d z ), розгорніть праву частину, щоб знайти F ( z ) + f( z ) d z , а оскільки U рівномірна, ця ймовірність є просто f ( z ) d z, як було обіцяно.