El método de la espiral dorada
Dijiste que no podías conseguir que el método de la espiral dorada funcionara y es una pena porque es realmente bueno. Me gustaría darte una comprensión completa de esto para que tal vez puedas entender cómo evitar que esto se "acumule".
Así que aquí hay una forma rápida y no aleatoria de crear una celosía que sea aproximadamente correcta; como se discutió anteriormente, ninguna celosía será perfecta, pero esto puede ser lo suficientemente bueno. Se compara con otros métodos, por ejemplo, en BendWavy.org, pero tiene un aspecto agradable y bonito, así como una garantía sobre el espaciado uniforme en el límite.
Imprimación: espirales de girasol en el disco unitario
Para comprender este algoritmo, primero lo invito a mirar el algoritmo de espiral de girasol 2D. Esto se basa en el hecho de que el número más irracional es la proporción áurea (1 + sqrt(5))/2
y si uno emite puntos por el enfoque "párese en el centro, gire una proporción áurea de giros completos, luego emita otro punto en esa dirección", uno naturalmente construye un espiral que, a medida que se llega a un número cada vez mayor de puntos, se niega, sin embargo, a tener "barras" bien definidas en las que se alinean los puntos.(Nota 1.)
El algoritmo para el espaciado uniforme en un disco es,
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()
y produce resultados que se parecen a (n = 100 yn = 1000):
Espaciar los puntos radialmente
La clave extraña es la fórmula r = sqrt(indices / num_pts)
; ¿Cómo llegué a ese? (Nota 2.)
Bueno, estoy usando la raíz cuadrada aquí porque quiero que tengan un espacio uniforme alrededor del disco. Eso es lo mismo que decir que en el límite de N grande quiero que una pequeña región R ∈ ( r , r + d r ), Θ ∈ ( θ , θ + d θ ) contenga un número de puntos proporcional a su área, que es r d r d θ . Ahora, si pretendemos que estamos hablando de una variable aleatoria aquí, esto tiene una interpretación sencilla como decir que la densidad de probabilidad conjunta para ( R , Θ ) es simplemente crpara alguna constante = 1 / π.c . La normalización en el disco unitario forzaría entonces c
Ahora déjame presentarte un truco. Proviene de la teoría de la probabilidad, donde se conoce como muestreo de la CDF inversa : suponga que desea generar una variable aleatoria con una densidad de probabilidad f ( z ) y tiene una variable aleatoria U ~ Uniforme (0, 1), tal como sale de random()
en la mayoría de los lenguajes de programación. ¿Cómo haces esto?
- Primero, convierta su densidad en una función de distribución acumulativa o CDF, que llamaremos F ( z ). Un CDF, recuerde, aumenta monótonamente de 0 a 1 con la derivada f ( z ).
- Luego calcule la función inversa F -1 ( z ) de la CDF .
- Encontrará que Z = F -1 ( U ) se distribuye de acuerdo con la densidad objetivo. (Nota 3).
Ahora, el truco de la espiral de la proporción áurea espacia los puntos en un patrón uniforme para θ, así que integremos eso; para el disco unitario nos queda F ( r ) = r 2 . Entonces la función inversa es F -1 ( u ) = u 1/2 , y por lo tanto generaríamos puntos aleatorios en el disco en coordenadas polares conr = sqrt(random()); theta = 2 * pi * random()
.
Ahora, en lugar de muestrear aleatoriamente esta función inversa, la estamos muestreando uniformemente , y lo bueno del muestreo uniforme es que nuestros resultados sobre cómo se distribuyen los puntos en el límite de N grande se comportarán como si lo hubiéramos muestreado aleatoriamente. Esta combinación es el truco. En lugar de random()
usamos (arange(0, num_pts, dtype=float) + 0.5)/num_pts
, de modo que, digamos, si queremos muestrear 10 puntos, lo son r = 0.05, 0.15, 0.25, ... 0.95
. Tomamos muestras de r uniformemente para obtener un espaciado de áreas iguales y usamos el incremento de girasol para evitar “barras” de puntos horribles en la salida.
Ahora haciendo el girasol en una esfera
Los cambios que debemos hacer para salpicar la esfera con puntos simplemente implican cambiar las coordenadas polares por coordenadas esféricas. La coordenada radial, por supuesto, no entra en esto porque estamos en una esfera unitaria. Para mantener las cosas un poco más consistentes aquí, aunque fui entrenado como físico, usaré las coordenadas de los matemáticos donde 0 ≤ φ ≤ π es la latitud que desciende del polo y 0 ≤ θ ≤ 2π es la longitud. Entonces, la diferencia con respecto a lo anterior es que básicamente estamos reemplazando la variable r con φ .
Nuestro elemento de área, que era r d r d θ , ahora se convierte en el pecado no mucho más complicado ( φ ) d φ d θ . Entonces, nuestra densidad conjunta para un espaciado uniforme es sin ( φ ) / 4π. Integrando θ , encontramos f ( φ ) = sin ( φ ) / 2, entonces F ( φ ) = (1 - cos ( φ )) / 2. Al invertir esto, podemos ver que una variable aleatoria uniforme se vería como acos (1 - 2 u ), pero tomamos muestras de manera uniforme en lugar de aleatoria, por lo que en su lugar usamos φ k = acos (1 - 2 ( k+ 0,5) /N ). Y el resto del algoritmo solo proyecta esto en las coordenadas 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()
Nuevamente, para n = 100 yn = 1000, los resultados se ven así:
Más investigación
Quería dar un saludo al blog de Martin Roberts. Tenga en cuenta que antes creé un desplazamiento de mis índices agregando 0.5 a cada índice. Esto fue visualmente atractivo para mí, pero resulta que la elección de la compensación es muy importante y no es constante durante el intervalo y puede significar obtener hasta un 8% más de precisión en el empaque si se elige correctamente. También debería haber una manera de hacer que su secuencia R 2 cubra una esfera y sería interesante ver si esto también producía una cobertura uniforme y agradable, tal vez tal como está, pero tal vez necesitando ser, digamos, tomado de solo la mitad de el cuadrado de la unidad cortó en diagonal más o menos y se estiró para formar un círculo.
Notas
Esas "barras" están formadas por aproximaciones racionales a un número, y las mejores aproximaciones racionales a un número provienen de su expresión de fracción continua, z + 1/(n_1 + 1/(n_2 + 1/(n_3 + ...)))
donde z
es un número entero y n_1, n_2, n_3, ...
es una secuencia finita o infinita de enteros positivos:
def continued_fraction(r):
while r != 0:
n = floor(r)
yield n
r = 1/(r - n)
Dado que la parte de la fracción 1/(...)
siempre está entre cero y uno, un número entero grande en la fracción continua permite una aproximación racional particularmente buena: "uno dividido por algo entre 100 y 101" es mejor que "uno dividido por algo entre 1 y 2". El número más irracional es, por tanto, el que es 1 + 1/(1 + 1/(1 + ...))
y no tiene aproximaciones racionales particularmente buenas; se puede resolver φ = 1 + 1 / φ multiplicando por φ para obtener la fórmula de la proporción áurea.
Para las personas que no están tan familiarizadas con NumPy, todas las funciones están "vectorizadas", por lo que sqrt(array)
es lo mismo que escribirían otros lenguajes map(sqrt, array)
. Así que esta es una aplicación componente por componente sqrt
. Lo mismo también se aplica a la división por un escalar o la suma con escalares, que se aplican a todos los componentes en paralelo.
La prueba es simple una vez que sabes que este es el resultado. Si pregunta cuál es la probabilidad de que z < Z < z + d z , esto es lo mismo que preguntar cuál es la probabilidad de que z < F -1 ( U ) < z + d z , aplique F a las tres expresiones y observe que es una función que aumenta monótonamente, por lo tanto F ( z ) < U < F ( z + d z ), expanda el lado derecho hacia afuera para encontrar F ( z ) + f( z ) d z , y dado que U es uniforme, esta probabilidad es solo f ( z ) d z como se prometió.