Einige Attraktoren verzaubern mit ihrer Schönheit auch in statischen Bildern. Wir wollten eine Anwendung entwickeln, mit der die meisten Attraktoren in der Dynamik, in 3D und ohne Verzögerungen visualisiert werden können.
Ăber uns
Wir sind Roman Venediktov, Vladislav Nosivskoy und Kirill Karnaukhov - Studenten im zweiten Studienjahr des Bachelor-Studiengangs "Angewandte Mathematik und Informatik" an der Hochschule fĂŒr Wirtschaft in St. Petersburg. Wir programmieren seit Schulzeiten gern. Alle drei beschĂ€ftigten sich mit der Programmierung von Olympiaden und gingen in verschiedenen Jahren in die Endphase der Allrussischen Olympiade fĂŒr SchĂŒler der Informatik ĂŒber, hatten jedoch zuvor keine Erfahrung mit industrieller Programmierung, und fĂŒr uns ist dies das erste groĂe Teamprojekt. Wir haben es als Hausarbeit ĂŒber C ++ verteidigt.
Modellieren
Es gibt viele Möglichkeiten, ein dynamisches System mit einem seltsamen Attraktor zu definieren. Am hÀufigsten wird jedoch ein System aus drei Differentialgleichungen erster Ordnung verwendet. Wir haben mit ihr angefangen.
Bevor Sie etwas visualisieren, mĂŒssen Sie den Prozess selbst simulieren und die Trajektorien der Punkte ermitteln. Genaue Modellierungsmethoden sind ziemlich mĂŒhsam und wir möchten dies so schnell wie möglich tun.
Bei der Implementierung der Modellierung haben wir uns fĂŒr die Metaprogrammierung entschieden und dabei auf std :: function und andere Ă€hnliche Mechanismen verzichtet. Sie hĂ€tten die Architektur und die Codierung vereinfachen können, aber sie hĂ€tten die Leistung stark reduziert, was wir nicht wollten.
FĂŒr die Modellierung wurde zunĂ€chst die einfachste Runge-Kutta-Methode 4. Genauigkeitsordnung mit konstantem Schritt verwendet. Bisher haben wir die Anzahl der Methoden und anderer mathematischer Komponenten des Modells nicht wieder erhöht, und jetzt ist dies die einzige vorgestellte Methode. Auf den meisten gefundenen Systemen ist es genau genug, um Bilder zu erzeugen, die Bildern aus anderen Quellen Ă€hneln.
Das Modell akzeptiert als Eingabe:
- der 'Derivate'-Funktor zum Erhalten von Derivaten durch die Koordinaten eines Punktes;
- der "Beobachter" -Funktor, der von dem Punkt an aufgerufen wird, sobald er empfangen wird;
- Simulationsparameter (Startpunkt, Schrittweite, Anzahl der Punkte).
In Zukunft können Sie eine ĂberprĂŒfung hinzufĂŒgen, um festzustellen, wie das dargestellte Bild mit dem tatsĂ€chlichen Bild ĂŒbereinstimmt, einige stĂ€rkere Methoden zur Modellierung (z. B. durch Verbinden der Boost.Numeric.Odeint-Bibliothek) und einige andere Analysemethoden, fĂŒr die unsere mathematischen Kenntnisse noch nicht ausreichen.
Systeme
Wir haben die beliebtesten seltsamen Attraktorsysteme gefunden, um die beste Leistung zu erzielen. An dieser Stelle möchten wir uns bei der Website chaoticatmospheres.com bedanken, die uns diese Suche sehr erleichtert hat.
Alle Systeme mussten so verpackt werden, dass sie trotz der Tatsache, dass sie alle "unsere Vorlagen" sind, in einen Container gestellt werden und normal mit ihnen in der Steuerung arbeiten können. Wir sind zu folgender Lösung gekommen:
- DynamicSystem âobserverâ, (, ...) std::function âcomputeâ. âComputeâ , , âderivativesâ .
- std::function , DynamicSystemInternal compute .
- DynamicSystemInternal âobserverâ, âderivativesâ. âderivativesâ, .
Wir haben mit dem HinzufĂŒgen eines DynamicSystemWrapper begonnen, dem das DynamicSystem gehören wĂŒrde und der die fĂŒr die Visualisierung erforderliche Vorverarbeitung durchfĂŒhren könnte (Auswahl einer Konstante fĂŒr die Normalisierung, akzeptabler Fehler fĂŒr Methoden mit SchrittlĂ€ngensteuerung ...), hatten aber keine Zeit zum Abschluss.
Visualisierung
Wir haben OpenGL aufgrund seiner Leistung und Funktionen als Rendering-Bibliothek ausgewĂ€hlt sowie Qt5, das einen praktischen Wrapper ĂŒber OpenGL bietet.
ZunĂ€chst wollten wir lernen, wie man zumindest etwas zeichnet, und nach einer Weile konnten wir unseren ersten WĂŒrfel herstellen. Kurz danach erschien eine einfache Version des mathematischen Modells, und hier ist die erste Visualisierung des Attraktors:
Mit der ersten Version der Visualisierung war auch eine sehr einfache Version der Kamera fertig. Sie wusste, wie man sich um einen Punkt dreht und sich nĂ€hert / wegbewegt. Wir wollten mehr Freiheit im Weltraum: Attraktoren sind unterschiedlich und mĂŒssen auf unterschiedliche Weise erforscht werden. Dann erschien eine zweite Version der Kamera, die sich drehen und frei in alle Richtungen bewegen konnte (wir wurden in Minecraft von der Kamera gefĂŒhrt). Zu dieser Zeit hatte die lineare Algebra gerade erst begonnen, und daher gab es nicht genĂŒgend Wissen: Wir mussten im Internet nach vielen Informationen suchen.
Die ganze Zeit waren die Bilder weiĂ, statisch und uninteressant. Ich wollte Farben und Dynamik hinzufĂŒgen. ZunĂ€chst haben wir gelernt, wie man das ganze Bild in einer Farbe malt, aber das hat sich auch als uninteressant herausgestellt. Dann haben wir folgende Lösung gefunden:
- Nehmen Sie eine Menge (100â500, Sie können mehr in den Einstellungen auswĂ€hlen, Hauptsache, dass es genĂŒgend Leistung gibt) Startpunkte nahe beieinander.
- Simulieren Sie die Flugbahn von jedem von ihnen.
- Rendern Sie die Trajektorien gleichzeitig, wÀhrend Sie sie in verschiedenen Farben fÀrben, und zeigen Sie nur das Segment der Trajektorie an.
Es stellte sich Folgendes heraus:
UngefÀhr ein solches Schema blieb bis zum Ende bestehen.
Es fiel uns auf, dass die Linien zu "eckig" sind, und wir beschlossen zu lernen, wie man sie glĂ€ttet. NatĂŒrlich haben wir versucht, den Simulationsschritt zu reduzieren, aber leider können selbst moderne Prozessoren eine solche Anzahl von Punkten nicht zĂ€hlen. Es war notwendig, nach einer anderen Option zu suchen.
Zuerst dachten wir, dass OpenGL ein LinienglĂ€ttungswerkzeug haben sollte, aber nach langem Suchen stellten wir fest, dass dies nicht der Fall ist. Dann kam die Idee auf, die Kurven zu interpolieren und zwischen jedem Paar benachbarter Punkte, die weit genug entfernt sind, einige weitere hinzuzufĂŒgen. Zu diesem Zweck musste eine Methode zum Interpolieren von Kurven ausgewĂ€hlt werden, und es gibt viele solcher Methoden. Leider mussten fĂŒr die meisten von ihnen (z. B. die Bezier-Kurve) einige weitere Punkte angegeben werden, was fĂŒr unsere Aufgabe eindeutig nicht geeignet war: Wir wollten, dass das Ergebnis nur von dem abhĂ€ngt, was uns das mathematische Modell gab. Nach einer Weile fanden wir eine geeignete Interpolation: die Catmull-Roma-Kurve. Es stellte sich so heraus:
Danach haben wir beschlossen, dass es schön wĂ€re, Videos in der App aufzunehmen. Wir wollten es plattformĂŒbergreifend halten, also haben wir uns fĂŒr die libav-Bibliothek entschieden (es gab fast keine Wahl unter den Bibliotheken). Leider ist die gesamte Bibliothek in C geschrieben und hat eine sehr umstĂ€ndliche OberflĂ€che, so dass wir lange gebraucht haben, um zu lernen, wie man etwas schreibt. Alle nachfolgenden Gifs werden mithilfe der integrierten Aufzeichnung erstellt.
Bis zu diesem Punkt wurden alle Kurvenfarben bei der Erstellung explizit angegeben. Wir haben beschlossen, dass wir fĂŒr ein schönes Bild die Farben anders einstellen mĂŒssen. Zu diesem Zweck wurden nur Kontrollfarben angezeigt, und der Rest wurde unter Verwendung eines linearen Gradienten berechnet. Dieser Teil wurde an Shader ĂŒbertragen (vorher waren sie Standard).
Wir fanden es interessant, die Flugbahnen so zu fÀrben, dass jede von ihnen ihre Farbe von Kopf bis Schwanz Àndert. Dies ermöglicht es uns, den Effekt der Geschwindigkeit zu beobachten:
Dann dachten wir, dass es sich lohnt, die Vorverarbeitungszeit fĂŒr die Trajektorie zu reduzieren: Das Interpolieren einer Kurve ist eine "teure" Operation. Es wurde beschlossen, diesen Teil an Shader zu ĂŒbertragen, damit die GPU die Interpolation jedes Mal berechnet, wenn sie aufgefordert wird, einen Teil der Flugbahn zu zeichnen. DafĂŒr haben wir den Geometry Shader verwendet. Diese Lösung bot viele Vorteile: Keine Verzögerung auf der Rendering-Seite vor dem Zeichnen, die Möglichkeit, Kurven noch mehr zu glĂ€tten (solche Berechnungen werden auf der GPU schneller als auf der CPU durchgefĂŒhrt), die Verwendung von weniger RAM (vorher mussten jetzt alle interpolierten Punkte gespeichert werden - nein ).
Controller und BenutzeroberflÀche
Nach der Auswahl von Qt5 als Basis-Framework verschwand die Frage nach der Auswahl der Technologien fĂŒr die Schnittstelle sofort. Der integrierte Qt Creator erfĂŒllt alle Anforderungen einer kleinen Anwendung ausreichend.
Um auf Benutzeranfragen zu antworten, mussten Sie einen Controller schreiben. GlĂŒcklicherweise bietet Qt bequeme Möglichkeiten, TastenanschlĂ€ge zu verarbeiten und Werte in Felder einzugeben. Dies nutzt die Hauptidee von Qt - den Signal- und Slot-Mechanismus. Wenn wir beispielsweise in unserer Anwendung die Taste drĂŒcken, die fĂŒr die Neuerstellung des Modells verantwortlich ist, wird ein Signal generiert, das vom Handler-Slot akzeptiert wird. Es wird der Wiederaufbau selbst starten.
Bei der Entwicklung fast jeder Anwendung mit einer Schnittstelle kommt frĂŒher oder spĂ€ter die Idee auf, die Anwendung multithreaded zu machen. Es schien uns notwendig: Der Bau eingebauter Modelle dauerte einige Sekunden, und der Bau eines benutzerdefinierten Modells dauerte 10 Sekunden. Gleichzeitig hing natĂŒrlich die Schnittstelle, da alle Berechnungen in einem Thread durchgefĂŒhrt wurden. Lange haben wir verschiedene Optionen diskutiert und ĂŒber AsynchronitĂ€t mit std :: async nachgedacht, aber am Ende wurde uns klar, dass wir in der Lage sein wollten, Berechnungen in einem anderen Thread zu unterbrechen. Dazu musste ich einen Wrapper ĂŒber std :: thread schreiben. Alles ist so einfach wie möglich: ein Atom-Flag zur ĂberprĂŒfung und ein ordentlicher Interrupt, wenn die ĂberprĂŒfung fehlschlĂ€gt.
Dies ergab nicht nur das gewĂŒnschte Ergebnis - die BenutzeroberflĂ€che blieb hĂ€ngen -, sondern fĂŒgte auch einige Funktionen hinzu: Aufgrund der Besonderheiten der Architektur und der Interaktion zwischen Modelldaten und Visualisierung wurde es möglich, alles wĂ€hrend des ZĂ€hlens online zu zeichnen. Bisher mussten Sie auf alle Daten warten.
Kundenspezifische Systeme
Die Anwendung enthĂ€lt bereits viele Attraktoren, aber wir wollten dem Benutzer auch ermöglichen, die Gleichungen selbst einzugeben. Zu diesem Zweck haben wir einen Parser geschrieben, der Variablen (x, y, z), mathematische Standardoperationen (+ - * / ^), Konstanten und viele mathematische Funktionen (sin, cos, log, atan, sinh, exp usw.) unterstĂŒtzt. und Klammern. So funktioniert es:
- Die ursprĂŒngliche Abfragezeichenfolge ist mit einem Token versehen. Als nĂ€chstes werden Token von links nach rechts analysiert und ein Ausdrucksbaum erstellt.
- Mögliche Operationen sind in Gruppen unterteilt. Jede Gruppe hat ihren eigenen Knoten. Gruppen: Plus-Minus, Multiplikationsteilung, Potenzierung, unÀres Minus, sogenannte BlÀtter (dazu gehören Konstanten, Variablen, Funktionsaufrufe).
- Jede Gruppe hat ihre eigene Berechnungsebene. Jede Ebene fĂŒhrt zu rekursiven Berechnungen auf den nĂ€chsten Ebenen. Sie können sehen, dass die Reihenfolge der Aufrufe die Verteilung der PrioritĂ€ten von VorgĂ€ngen beeinflusst. Wir haben sie in der oben beschriebenen Reihenfolge.
Weitere Details finden Sie im Parser-Quellcode .
Jede Ebene gibt eine Art Erbe des Knotens zurĂŒck. Es gibt vier davon:
- binÀrer Operator - speichert Zeiger auf zwei untergeordnete Elemente und seine eigene Art von Operation;
- unĂ€rer Operator - speichert einen Zeiger auf das untergeordnete Element und seinen eigenen Operationstyp. Dies schlieĂt Funktionen ein, da dies ein Sonderfall einer unĂ€ren Operation ist;
- konstant - speichert seinen Wert;
- Variable - speichert einen Zeiger auf die Stelle im Speicher, an der sein Wert liegt.
Die Knotenstruktur verfĂŒgt nur ĂŒber eine virtuelle Berechnungsfunktion, die den Wert ihres Teilbaums zurĂŒckgibt.
Die resultierende Ausgabe ist sehr bequem auf die zuvor beschriebene Systemarchitektur zugeschnitten. Ein Lambda wird einfach an DynamicSystemInternal ĂŒbergeben, das Zeiger auf die Wurzelknoten der drei erhaltenen BĂ€ume und die xyz-Speicherpositionen der Werte speichert. Beim Aufruf werden die dortigen Werte in die angegebenen Werte geĂ€ndert und calc von den Stammscheitelpunkten aufgerufen.
Ergebnis
Als Ergebnis haben wir ein Programm erhalten, das benutzerdefinierte Systeme visualisieren kann und auf einer groĂen Anzahl von Attraktoren basiert. Sie macht es ganz nett und optimiert, was eine gute Nachricht ist.
Aber es gibt noch viel Arbeit:
- prĂ€zisere Methoden hinzufĂŒgen;
- HinzufĂŒgen einer weiteren Ebene der Systemverarbeitung (Normalisierung und automatische Fehlerauswahl bei komplexeren Methoden);
- Verbesserung der Arbeit mit Benutzersystemen (UnterstĂŒtzung von Variablen, Speichern);
- Optimierung ihrer Arbeit (JIT-Kompilierung oder ein Dienstprogramm, das die gespeicherten Systeme in C ++ - Code konvertiert und einfach die Neukompilierung startet, damit sie die Leistung eingebetteter Systeme erreichen);
- HinzufĂŒgen von Funktionen zur Ergebnisanalyse oder -visualisierung, die Personen, die mit solchen Systemen arbeiten, wirklich benötigen;
- ...
Unser Repository .
Und noch ein paar Videos mit Attraktoren: