Die goldene Spiralmethode
Sie sagten, Sie könnten die Methode der goldenen Spirale nicht zum Laufen bringen, und das ist eine Schande, weil sie wirklich sehr, sehr gut ist. Ich möchte Ihnen ein umfassendes Verständnis davon geben, damit Sie vielleicht verstehen, wie Sie verhindern können, dass dies „zusammengeballt“ wird.
Hier ist eine schnelle, nicht zufällige Methode, um ein Gitter zu erstellen, das ungefähr korrekt ist. Wie oben diskutiert, ist kein Gitter perfekt, aber dies kann gut genug sein. Es wird mit anderen Methoden verglichen, z. B. bei BendWavy.org, aber es hat nur ein schönes und hübsches Aussehen sowie eine Garantie für gleichmäßige Abstände im Limit.
Grundierung: Sonnenblumenspiralen auf der Gerätescheibe
Um diesen Algorithmus zu verstehen, lade ich Sie zunächst ein, sich den 2D-Sonnenblumen-Spiral-Algorithmus anzusehen. Dies basiert auf der Tatsache, dass die irrationalste Zahl der goldene Schnitt ist. (1 + sqrt(5))/2
Wenn man Punkte durch den Ansatz "in der Mitte stehen, einen goldenen Schnitt ganzer Umdrehungen drehen und dann einen anderen Punkt in diese Richtung emittieren" ausgibt, konstruiert man natürlich a Spirale, die, wenn Sie zu einer immer höheren Anzahl von Punkten gelangen, sich dennoch weigert, genau definierte „Balken“ zu haben, auf denen die Punkte ausgerichtet sind. (Anmerkung 1.)
Der Algorithmus für einen gleichmäßigen Abstand auf einer Platte lautet:
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()
und es erzeugt Ergebnisse, die wie folgt aussehen (n = 100 und n = 1000):
Abstand der Punkte radial
Der Schlüssel seltsam ist die Formel r = sqrt(indices / num_pts)
; Wie bin ich zu diesem gekommen? (Anmerkung 2.)
Nun, ich verwende hier die Quadratwurzel, weil ich möchte, dass diese einen gleichmäßigen Flächenabstand um die Platte haben. Das ist das gleiche wie zu sagen, dass in der Grenze von großem N ich möchte, dass ein kleiner Bereich R ∈ ( r , r + d r ), Θ ∈ ( θ , θ + d θ ) eine Anzahl von Punkten enthält, die proportional zu seiner Fläche sind, das ist r d r d θ . Wenn wir nun so tun, als würden wir hier von einer Zufallsvariablen sprechen, hat dies eine einfache Interpretation, die besagt, dass die gemeinsame Wahrscheinlichkeitsdichte für ( R , Θ ) nur cr istfür eine Konstante c . Eine Normalisierung auf der Einheitsscheibe würde dann c = 1 / π erzwingen .
Lassen Sie mich nun einen Trick vorstellen. Es stammt aus der Wahrscheinlichkeitstheorie, in der es als Abtastung der inversen CDF bekannt ist : Angenommen, Sie wollten eine Zufallsvariable mit einer Wahrscheinlichkeitsdichte f ( z ) erzeugen und haben eine Zufallsvariable U ~ Uniform (0, 1), genau wie aus in den meisten Programmiersprachen. Wie machst Du das?random()
- Verwandeln Sie Ihre Dichte zunächst in eine kumulative Verteilungsfunktion oder CDF, die wir F ( z ) nennen. Denken Sie daran, dass eine CDF mit der Ableitung f ( z ) monoton von 0 auf 1 ansteigt .
- Berechnen Sie dann die Umkehrfunktion der CDF F -1 ( z ).
- Sie werden feststellen, dass Z = F -1 ( U ) entsprechend der Zieldichte verteilt ist. (Notiz 3).
Jetzt räumt der Spiraltrick mit dem goldenen Schnitt die Punkte in einem schön gleichmäßigen Muster für θ auf, also integrieren wir das heraus; für die Einheitsscheibe bleibt F ( r ) = r 2 übrig . Die Umkehrfunktion ist also F -1 ( u ) = u 1/2 , und daher würden wir zufällige Punkte auf der Scheibe in Polarkoordinaten mit erzeugen r = sqrt(random()); theta = 2 * pi * random()
.
Anstatt diese Umkehrfunktion zufällig abzutasten, werden sie nun einheitlich abgetastet, und das Schöne an der gleichmäßigen Abtastung ist, dass sich unsere Ergebnisse darüber, wie Punkte in der Grenze von großem N verteilt sind, so verhalten, als hätten wir sie zufällig abgetastet. Diese Kombination ist der Trick. Anstelle von verwenden random()
wir (arange(0, num_pts, dtype=float) + 0.5)/num_pts
, so dass zum Beispiel, wenn wir 10 Punkte abtasten wollen, sie sind r = 0.05, 0.15, 0.25, ... 0.95
. Wir probieren r gleichmäßig ab , um einen gleichmäßigen Abstand zu erhalten, und wir verwenden das Sonnenblumeninkrement, um schreckliche „Balken“ von Punkten in der Ausgabe zu vermeiden.
Jetzt mache ich die Sonnenblume auf einer Kugel
Die Änderungen, die wir vornehmen müssen, um die Kugel mit Punkten zu versehen, umfassen lediglich das Vertauschen der Polarkoordinaten gegen Kugelkoordinaten. Die Radialkoordinate geht natürlich nicht ein, weil wir uns auf einer Einheitskugel befinden. Um die Dinge hier ein wenig konsistenter zu halten, verwende ich, obwohl ich als Physiker ausgebildet wurde, die Koordinaten von Mathematikern, wobei 0 ≤ φ ≤ π der vom Pol herabfallende Breitengrad und 0 ≤ θ ≤ 2π der Längengrad ist. So ist der Unterschied von oben ist , dass wir im Prinzip die Variable ersetzen r mit φ .
Unser Flächenelement, das r d r d θ war , wird nun zur nicht viel komplizierteren sin ( φ ) d φ d θ . Unsere Gelenkdichte für einen gleichmäßigen Abstand ist also sin ( φ ) / 4π. Die Integration aus θ finden wir f ( φ ) = sin ( φ ) / 2, so dass F ( φ -) = (cos (1 φ / 2)). Wenn wir dies umkehren, können wir sehen, dass eine einheitliche Zufallsvariable wie acos (1 - 2 u ) aussehen würde , aber wir probieren einheitlich statt zufällig, also verwenden wir stattdessen φ k = acos (1 - 2 ( k)+ 0,5) / N ). Und der Rest des Algorithmus projiziert dies nur auf die x-, y- und z-Koordinaten:
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()
Wiederum für n = 100 und n = 1000 sehen die Ergebnisse so aus:
Weitere Nachforschungen
Ich wollte Martin Roberts Blog einen Gruß aussprechen. Beachten Sie, dass ich oben einen Versatz meiner Indizes erstellt habe, indem ich jedem Index 0,5 hinzugefügt habe. Dies war nur optisch ansprechend für mich, aber es stellt sich heraus, dass die Wahl des Versatzes sehr wichtig ist und über das Intervall nicht konstant ist und bei richtiger Wahl eine um bis zu 8% bessere Genauigkeit beim Verpacken bedeuten kann. Es sollte auch eine Möglichkeit geben, seine R 2 -Sequenz dazu zu bringen, eine Kugel abzudecken, und es wäre interessant zu sehen, ob dies auch eine schöne gleichmäßige Abdeckung hervorbrachte, vielleicht so wie sie ist, aber vielleicht nur von der Hälfte genommen werden muss Das Einheitsquadrat schnitt diagonal oder so und streckte sich, um einen Kreis zu erhalten.
Anmerkungen
Diese „Balken“ werden durch rationale Annäherungen an eine Zahl gebildet, und die besten rationalen Annäherungen an eine Zahl ergeben sich aus ihrem fortgesetzten Bruchausdruck, z + 1/(n_1 + 1/(n_2 + 1/(n_3 + ...)))
wobei z
es sich um eine Ganzzahl handelt und n_1, n_2, n_3, ...
es sich entweder um eine endliche oder eine unendliche Folge positiver Ganzzahlen handelt:
def continued_fraction(r):
while r != 0:
n = floor(r)
yield n
r = 1/(r - n)
Da der Bruchteil 1/(...)
immer zwischen Null und Eins liegt, ermöglicht eine große Ganzzahl im fortgesetzten Bruch eine besonders gute rationale Annäherung: "Eins geteilt durch etwas zwischen 100 und 101" ist besser als "Eins geteilt durch etwas zwischen 1 und 2." Die irrationalste Zahl ist daher diejenige, die 1 + 1/(1 + 1/(1 + ...))
keine besonders guten rationalen Annäherungen hat und hat; man kann lösen φ = 1 + 1 / φ indem man mit φ multipliziert , um die Formel für den Goldenen Schnitt zu erhalten.
Für Leute, die mit NumPy nicht so vertraut sind - alle Funktionen sind "vektorisiert", so dass dies sqrt(array)
das gleiche ist, was andere Sprachen schreiben könnten map(sqrt, array)
. Dies ist also eine komponentenweise sqrt
Anwendung. Gleiches gilt auch für die Division durch einen Skalar oder die Addition mit Skalaren - diese gelten für alle Komponenten parallel.
Der Beweis ist einfach, sobald Sie wissen, dass dies das Ergebnis ist. Wenn Sie fragen, wie hoch die Wahrscheinlichkeit ist, dass z < Z < z + d z ist , entspricht dies der Wahrscheinlichkeit, dass z < F -1 ( U ) < z + d z ist , und wenden Sie F auf alle drei Ausdrücke an Eine monoton ansteigende Funktion, also F ( z ) < U < F ( z + d z ), erweitert die rechte Seite nach außen, um F ( z ) + f zu finden( z ) d z , und da Uist einheitlich diese Wahrscheinlichkeit ist nur f ( z ) d z wie versprochen.