Simulation mechatronischer Systeme
Transcrição
Simulation mechatronischer Systeme
Simulation mechatronischer Systeme Prof. Dr.-Ing.habil. G.P. Ostermeyer, Institut für Dynamik und Schwingungen, Technische Universität Braunschweig,2009 2 Simulation mechatronischer Systeme Simulation mechatronischer Systeme 3 Inhalt 1. Einleitung 7 Die Grundidee zur Simulation mit PLAN Installation von PLAN Das einfachste Programm mit PLAN Die Grundfunktionalität von PLAN 10 12 16 19 2. Methoden in PLAN, Teil I 26 1.3 1.4 2.1 2.2 2.3 2.4 2.5 Grafik Animationen Usertasten 2D - Plots Mausevents und Ausschnittvergrößerung 3. Spielereien 3.1 3.2 Animierte Bilder und Bildschirmschoner Ping-Pong Spiele 4. Folgen und Reihen 4.1 4.2 4.3 4.4 Nullstelleniteration Feigenbaumdiagramm Mandelbrot- und Juliamengen Taylorreihen 27 35 38 42 54 62 63 68 77 77 82 87 93 4 Simulation mechatronischer Systeme 5. Zelluläre Automaten 5.1 5.2 5.3 Sierpinski Dreiecke Verkehrssimulation (Nagel-Schreckenbergmodell) Convay's Spiel des Lebens 6. Methoden in PLAN, Teil II 6.1 6.2 6.3 6.4 Vektoren und Matrizen Interpolation und Fourierreihen Numerische Integration Plotanimationen und 3D-Plots 7. Einfache dynamische Systeme 7.1 7.2 7.3 7.4 Ein linearer Einmassenschwinger Phasenportrait einer nichtlinearen Differentialgleichung Das Doppelpendel Laufrad auf rauem Untergrund 97 99 102 109 120 120 131 148 158 168 169 176 180 190 Simulation mechatronischer Systeme 5 6 Simulation mechatronischer Systeme Simulation mechatronischer Systeme 7 1. Einleitung Wenn man sich mit der mathematisch-physikalischen Beschreibung dynamischer Systeme beschäftigt, muss man zwangsläufig Methoden zu deren – meist numerischer – Lösung nutzen. Nur so erschließt sich dem Nutzer die oft auf den ersten Blick nicht ersichtliche Komplexität der Dynamik. Zur numerischen Analyse dynamischer Systeme gibt es eine große Zahl sehr bequemer und sicherer Softwarewerkzeuge, die mit der Erfahrung im Umgang mit diesen Programmen schnell Auskunft geben können. Diese Programme sind aber gerade aufgrund ihrer Bequemlichkeit und Sicherheit selbst wieder meist komplexe Oberflächen mit noch komplexeren Sprachen geworden. Ihre volle Leistungsfähigkeit ist wohl nur noch Usern mit langjährigen Simulationserfahrungen sichtbar. Dadurch entsteht das Paradoxon, dass man sich immer mehr vom System entfernt, je mächtiger die Untersuchungswerkzeuge werden. Ursprünglich für das Verständnis des Systems geschaffen, machen sie selbst erfahrene User zu Black-Box-Nutzern. Wenn Sie beispielsweise einen einfachen Zweimassenschwinger untersuchen, müssen Sie die vom Programm gewählte interne Lösungsstrategie akzeptieren und anhand von mehr oder weniger sinnvollen grafischen Darstellungen deren Ergebnis verifizieren oder falsifizieren. Dazu muss man aber zumindest wissen, was das Berechnungsprogramm gemacht hat und mit welchen prinzipiellen Lösungsstrategien welche Aussagequalitäten erreichbar sind. Dazu sollte die Ausbildung des Ingenieurs die Erfahrung der Qualität numerischer Verfahren an einfachen, überschaubaren dynamischen Systemen bieten. Auch zeigt sich das Verständnis dynamischer Systeme wesentlich intensiver, wenn an mathematischen und numerischen Parametern "gespielt" wird und sofort entsprechende Rückmeldungen gegeben werden. Üblicherweise werden für den angehenden Ingenieur an den Hochschulen die Themen: Mechatronik o.ä. (Bewegungsgleichungen dynamischer Systeme) Schwingungen (Stabilität, Eigenformen, Frequenzen) Numerische Verfahren linearer Systeme (Matrizen, Eigenwerte, Eigenvektoren) Numerische Integration gewöhnlicher Differentialgleichungen 8 Simulation mechatronischer Systeme Finite-Elemente Verfahren Techniken des Softwareengineerings Programmierkurse in unterschiedlichsten Vorlesungen geboten. Erst im Beruf müssen dann all diese Informationen vernetzt und integriert werden. Hier setzt die Vorlesung "Simulation mechatronischer Systeme" an. Zur sicheren und "schnörkellosen" Simulation wurde PLAN entwickelt. PLAN gibt in einfacher Form Einblick in das Prinzip der Simulation dynamischer Systeme. PLAN ermöglicht sehr schnell und einfach die Analyse dynamischer Systeme. PLAN bietet eine ansprechende Oberfläche und die klassischen numerischen Verfahren zur Beschreibung und Untersuchung dynamischer Systeme. Das "Erfahren" einfacher Systeme mit PLAN bündelt die oben genannten unterschiedlichen Lehrinhalte, zeigt Gefahren einer unkritischen Nutzung unterschiedlicher Untersuchungsmethoden auf, führt in einfacher Form moderne Verifikationsmethoden wie etwa "real time simulation" und "hardware-in-the-loop"-Techniken auf. Die Vorlesung will so zu einem sichereren und verantwortungsvolleren Umgang mit meist berufs- oder arbeitgeberspezifischen Simulationsumgebungen führen und neue Möglichkeiten der Verifikation dynamischer Systeme aufzeigen. Diese Zielsetzung der Vorlesung ist Resultat langjähriger Erfahrungen des Autors in Berechnungsabteilungen der Automobilindustrie, wo mit unterschiedlichsten Simulationsumgebungen hochkomplexe Modelle analysiert, Ergebnisse verifiziert, ... und schließlich marktfähige Aussagen ermittelt werden mussten. Die Vorlesung selbst ist in mehreren Jahren durch Nachfragen und Nutzung vieler Studenten gewachsen und optimiert. Die Vorlesung erfreut sich stark wachsender Beliebtheit, da sie zum einen eine komplette Softwareentwicklung zur Bearbeitung dynamischer Systeme in Studien- und Diplomarbeiten bietet und zum anderen den Umgang etwa mit MATLAB mit wenig zusätzlichem Aufwand ohne Probleme möglich macht. Der Aufbau der Vorlesung spannt einen weiten Bogen über "dynamische Systeme", von Computerspielen bis hin zu hardware-in-the-loop Experimenten mit geeigneter Sensorik und Aktorik für einfache adaptive Systeme. Ein solcher Bogen wird üblicherweise nicht in den fachspezifischen Vorlesungen geboten. Dieser interdisziplinäre Überblick ist aber wesentlich, um für die Simulationsaufgaben im Beruf gerüstet zu sein. Im Einzelnen: Es werden zunächst einfache grafische Animationen analysiert und realisiert, wie sie etwa bei Film, Fernsehen und Videospielen heute üblich sind. Dies ist die klassische Form sichtbarer "dynamischer" Systeme. Dann steht die Dynamik mathematischer Iterationen im Blickfeld. Unter anderem werden Wege ins Chaos verfolgt, selbstähnliche Strukturen realisiert und unter anderem das Apfelmännchen visualisiert. Simulation mechatronischer Systeme 9 Natürlich werden auch numerische Integrationsverfahren für lineare und nichtlineare Differentialgleichungssysteme und deren Anwendung auf verschiedenste mechatronische Systeme erklärt und dargestellt. Hierbei werden insbesondere auch die Probleme bei einseitigen Bindungen, Stoßvorgängen oder einfach nur differentialalgebraischen Gleichungen aufgezeigt. Schließlich werden auch Anwendungen mit nichtklassischen Werkzeugen vorgestellt. So werden etwa Simulationen virtueller elektrischer Schaltungen mit Hilfe von zellulären Automaten dargestellt wie auch Selbstorganisationseffekte in diskreten Verkehrsmodellen. Den Abschluss bilden das Prinzip einfacher Mess- und Regelungsaufgaben mit hardware-in-the-loop Techniken und ihre Realisierung. Insbesondere werden grundsätzliche Probleme der realtime-Simulationen aufgezeigt. Mit diesen Inhalten hat der Leser das Rüstzeug, komplexere Projekte der Steuerung und Regelung realer Maschinen wie auch die Simulation allgemeiner dynamischer Systeme anzugehen. Die Studenten in den Vorlesungen haben dazu üblicherweise alte Maschinen, Motoren und Steuergeräte bekommen, die sie mit großem Elan zu neuem Leben erweckt haben. Das Ziel der zweisemestrigen Vorlesung ist die Vermittlung der prinzipiellen Funktionalität von Simulationsprogrammen (z.B. Matlab, ...) und deren Probleme bei komplexen dynamischen Systemen und die Realisierung jeder Menge selbstgemachter, beliebig unter Windows lauffähiger Schulungs- und Anwendungsprogramme. Zur Verfügung gestellt wird die Klassenbibliothek PLAN. Mit der Bibliothek PLAN lassen sich mit einfachsten C/C++ - Kenntnissen ab der ersten Vorlesungsstunde mathematische Tools nutzen und grafische Animationen unter Windows realisieren. Diese Software ist auch unter Visual C++ (.net), Borland C++ (CodeGear) und Linux nutzbar. 10 1.1 Simulation mechatronischer Systeme Die Grundidee zur Simulation mit PLAN Es gibt viele Möglichkeiten, einfache, schnell handhabbare Simulationsumgebungen aufzubauen. Die Literatur führt viele Beispiele an. So werden auf der einen Seite kleine Programme oder Unterprogramme angeboten. Deren Programmiersprache soll einfach und praktisch sein und von allen verstanden werden. So sind das meist Sprachen wie Basic. Zum anderen werden Interpreterlösungen angeboten, die mit unterschiedlichsten Sprach- und Funktionsumfängen werben. Sehr bekannte Lösungen sind etwa Mathematica oder MATLAB. Während ersteres Programm für die mathematische Untersuchung unterschiedlichster Zusammenhänge eine große Vielfalt an Methoden und Analysewerkzeugen bietet, die für viele vertiefende Fragen an das zu untersuchende System unerlässlich sind, hat sich MATLAB zu einem auch in der Industrie etablierten Standard entwickelt. Mit wenigen einfachen Befehlen lassen sich etwa dynamische Systeme auf Interpreterbasis analysieren und in Plots auswerten. Auch hier ist eine wachsende Anzahl von Zusatzbibliotheken erhältlich, mit denen zum Beispiel messdatenverarbeitende Prozeduren, regelungstechnische Anwendungen und statistische Auswertungen genutzt werden können. Kritisch bei diesen Programmen sind in der Regel allerdings die Rechenzeiten. Da die Usergemeinde im Allgemeinen diese Programme immer komfortabler ausgestaltet haben will und für die vermeintlich allgemein interessierenden Aufgaben die Programmabläufe automatisiert werden, bleibt die Nähe zum Algorithmus der Lösung einer mathematischen Aufgabe auf der Strecke. Dies ist gut, wenn man umfangreiche Erfahrungen mit der Simulation hat. Aber am Anfang sollte zunächst ein möglichst einfaches Werkzeug stehen, das die prinzipielle mathematische Lösungsstrategie wie auch die prinzipielle softwaretechnische Lösung von Simulationsaufgaben hautnah zu erleben gestattet. Darum ist PLAN anders. PLAN will eine Top-Down Programmierumgebung bieten, die das tut, was man zur Simulation braucht: eine übersichtliche grafische Oberfläche, die eine einfache Steuerung der Simulation erlaubt, eine Methode Init(), die am Anfang der Simulation gegebenenfalls Daten und Strukturen aufbaut, eine Methode Run(), die die eigentliche dynamische Simulation durchführt und dann eine Methode Reset(), um gegebenenfalls die Simulation wieder auf den Anfang zurückzustellen, damit die Simulation wiederholt werden kann. Schließlich wird noch eine Methode Finish() benötigt, um gegebenenfalls die in Init() aufgebauten Daten und Strukturen wieder abzubauen. Was Sie in Run() machen, und natürlich welche Datenstrukturen Sie haben wollen, bleibt Ihnen völlig überlassen. Die Methoden Run() und Reset() werden in einer optisch ansprechenden Umgebung durch das Betätigen von grafischen Tasten auf dem Bildschirm per Maus initiiert. Wenn Sie diese Methoden nicht definieren, passiert eben nichts, wenn Sie die entsprechenden Tasten drücken. Das Programm bleibt immer lauffähig. Simulationsergebnisse sind durch eine einfach zu handhabende Grafik darstellbar, die etwa durch Knopfdruck auf den Drucker, in ein BMP-File oder Worddokument geschrieben werden kann. Simulation mechatronischer Systeme 11 PLAN ist in der objektorientierten Sprache C++ als Klasse TPlan geschrieben worden, die unterschiedlichste mathematische Methoden, grafische Methoden und Simulationssteuerungen enthält. Der Nutzer leitet sich eine Klasse, zum Beispiel TUser ab, die alle Eigenschaften von TPlan erbt. Die prinzipielle Programmstruktur ist nachfolgend skizziert. #include "Plan.h" class TUser : public TPlan { public: void Init(); void Run(); void Reset(); void Finish(); }; int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0;} Die Funktionen der Klasse TUser, zum Beispiel Init(), sollten von Ihnen mit Leben gefüllt werden. Wie, das wird Ihnen an vielen Beispielen in den nachfolgenden Kapiteln gezeigt. Das TUser Objekt a erbt die Methode Execute(). Damit wird in der main()-Funktion die eigentliche Simulation ausgelöst. So wird zunächst Init() ausgeführt und dann eine Simulationsoberfläche aufgebaut, die Tasten zur Ausführung von Run() oder Reset() bietet. Zum Beenden der Simulation gibt es eine Taste 'Exit'. Es wird dann zunächst die Funktion Finish() aufgerufen und softwaretechnisch alles aufgeräumt. So lassen sich mit nur geringen Kenntnissen in C/C++ Plattform unabhängig unter Windows, Linux/Unix oder Solaris auch komplexe Simulationen durchführen. Lassen Sie sich überraschen, was mit wenigen Mitteln schon möglich ist. 12 Simulation mechatronischer Systeme 1.2 Installation von PLAN PLAN ist eine Dynamic Link Library (dll). Es gehören folgende Dateien dazu: Plan.h Plan.lib Plan.dll Auf der beiliegenden CD sind für die Borland C++ - Versionen 5.0, 6.0 und Code Gear 2007 jeweils Ordner enthalten. Jeder dieser Ordner enthält einen Ordner Planprojekt. In diesem Ordner sind neben den oben angegebenen Files auch fertige Projektrümpfe enthalten: Ordner Planprojekt (Borland 5.0, Borland 6.0) Plan.h Plan.lib Plan.dll Simulation1.cpp PrSimulation1.bpr PrSimulation1.bpf Wenn Sie auf die Datei mit der Endung .bpr klicken, wird automatisch die Borland Entwicklungsumgebung 5.0 oder 6.0 aufgerufen. Das Borland C++ 5.0 Programm zeigt sich am Bildschirm, wie Abbildung 1.1 zeigt. Ähnlich sieht das Borland C++ 6.0 aus. Simulation mechatronischer Systeme 13 Abbildung 1.1: Borland C++ 5.0 nach Anklicken der Datei: PrSimulation1.bpr In der Mitte befindet sich das Editorfenster mit der Datei Simulation1.cpp. Am oberen Rand sieht man das Borlandband, welches die wesentlichen Möglichkeiten des Borland-Compilers enthält. Wesentlich ist zunächst das Datei-Menü. Mit Datei > Speichern unter ... können Sie der Simulation1.cpp-Datei einen anderen Namen geben und mit Datei -> Projekt Speichern unter ... auch der Projektdatei PrSimulation1. Dieser letzte Name ist dann auch der Name der .exe – Datei, die das Projekt liefert. Weiteres wichtiges Element ist das kleine grüne Dreieck in der dritten Zeile des Borlandbandes. Bei Betätigen dieser Taste wird das Projekt compiliert, gelinkt und, wenn es fehlerfrei ist auch gleich ausgeführt. Diese Projektrümpfe stellen einfach nur Konsoleprojekte dar – siehe Funktion main() -, die ein Objekt a der Klasse TUser – abgeleitet von der Klasse TPlan nutzen. Solch ein Projektrumpf stellt das einfachste Projekt mit PLAN dar und ist in dieser Form schon voll lauffähig. Bei der Borland 2007 (Code Gear)- Version sind im Ordner Planprojekt auch Unterordner enthalten: 14 Simulation mechatronischer Systeme Ordner Planprojekt (Borland 2007) __history (Unterordner) DEBUG (Unterordner, dieser enthält PLAN.DLL) Plan.h Plan.lib PlanProjekte.groupproj PlanProjekte.groupproj.local PrSimulation1.cbproj PrSimulation1.cbproj.local Simulation1.cpp Abbildung 1.2: te.groupproj. Borland C++ 2007 nach Anklicken der Datei: PlanProjek- Hier sollten Sie auf PlanProjekte.groupproj klicken oder auf PrSimulation1.cbproj. Simulation mechatronischer Systeme 15 Das Aussehen am Bildschirm ist in Abbildung 1.2 dargestellt. Gegebenenfalls müssen Sie dort noch auf den Dateinamen Simulation1.cpp im rechten, oberen Fenster anklicken, um in der Mitte das File im Editorfenster zu sehen. Auch wenn bei dieser Borland-Version weitere Fenster gezeigt werden, ist das prinzipielle Vorgehen wie bei den anderen Versionen gezeigt. 16 Simulation mechatronischer Systeme 1.3 Das einfachste Programm mit PLAN Wie in Kapitel 1.2 dargelegt, genügt ein einfaches Konsoleprojekt, um mit Plan zu starten. Dazu nutzen Sie am besten ein fertiges Rumpfprojekt. Dies liefert eine Datei Simulation1.cpp (oder wie immer Sie diese Datei umbenennen wollen) mit dem folgenden Inhalt: //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Projektbeschreibung: // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { }; //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Mit diesem Sourcecode definieren Sie eine neue Klasse TUser, die von der Klasse TPlan abgeleitet wird. TUser enthält nichts außer dem, was in TPlan schon definiert ist. In der main()-Funktion wird ein Objekt a vom Typ der neuen Klasse TUser erzeugt und darin die Methode Execute() aufgerufen. Alle Programme entstehen durch den Aufbau dieser Klasse TUser, oder wie immer Sie Ihre Klasse nennen wollen. Wenn Sie das vorne genannte Rumpfprojekt nicht nutzen wollen, gehen Sie wie folgt vor: 1. Legen Sie ein neues Projekt, ein Konsoleprojekt, an. Dazu erscheint ein Konsolenexperte: Simulation mechatronischer Systeme 17 Setzen Sie die Optionen wie dargestellt und drücke Sie ok. Sie können auch den Haken bei Konsolenanwendung im dargestellten Fenster entfernen, dann wird aus der main()-Funktion eine winmain()-Funktion und das PLANFenster das Hauptfenster. Üblicherweise erhalten Sie ein Projekt mit dem Namen Projekt1 und eine Datei User1.cpp mit dem Inhalt: //------------------------------------------------------#pragma hdrstop //------------------------------------------------------#pragma argsused int main(int argc, char* argv[]) { return 0; } //------------------------------------------------------- 2. Speichern Sie Datei und Projekt in einen Ordner, der die Dateien Plan.h, Plan.lib und Plan.dll enthält. Die letztere Datei muss bei dem Compiler 2007 gegebenenfalls auch in ein Unterverzeichnis _RELEASE oder _DEBUG kopiert werden, je nachdem, mit welcher Linkeroption Sie dann arbeiten. 3. Ergänzen Sie die .ccp – Datei wie oben angegeben, d.h. Plan.h includen und eine eigene Klasse von der Klasse TPlan ableiten. TPlan selbst ist nicht instanziierbar. In der main() – Funktion schließlich muss die von TPlan geerbte Methode Execute() in einem Objekt Ihrer Klasse aufgerufen werden. 4. Fügen Sie mit Projekt -> dem Projekt hinzufügen.. Ihrem Projekt die Datei Plan.lib hinzu. 5. Wenn Sie Ihre Programme auf Rechner portieren wollen, die kein Borland C++ enthalten, gibt es Probleme. Entweder suchen Sie noch einzelne BorlandLibraries heraus (siehe die jeweiligen Anleitungen) oder Sie gehen wie folgt vor: Wählen Sie Projekt->Optionen... und dann gehen Sie auf a) Karte Linker: Haken bei dynamische RTL verwenden entfernen b) Karte Packages: Haken bei Mit Laufzeit-Packages compilieren entfernen 18 Simulation mechatronischer Systeme Das oben dargestellte Programm ist schon voll lauffähig. Wenn Sie es übersetzen und ausführen – das grüne Dreieck drücken! – erscheint am Bildschirm das in Abbildung 1.3 gezeigte PLAN-Simulationsfenster. Abbildung 1.3.: Das PLAN – Simulationsfenster Es zeigt im wesentlichen eine weiße Zeichenfläche, rechts ein Band mit Tasten, die der Programmablaufsteuerung dienen und ganz unten ein zweigeteiltes Informationsband. Links werden Informationen zu den Objekten gegeben, über die die Maus gleitet. Wenn Sie die Maus etwa über die Tasten ziehen, bekommen Sie Informationen zu genau der jeweiligen Taste. Bei der Taste Exit etwa steht im Informationsband: unmittelbares Beenden des Programms. Und genau das tut diese Taste auch, wenn Sie diese mit der Maus betätigen. Simulation mechatronischer Systeme 1.4 19 Die Grundfunktionalität von PLAN Erstellen Sie ein neues Projekt, wie in Kap. 1.3 beschrieben. Starten Sie den C++ Builder. Der C++ Builder compiliert durch Drücken des grünen Pfeils die Dateien und führt das Programm aus. Diese Compilierung kann beim ersten Mal etwas länger dauern. Dabei werden verschiedene zusätzliche Dateien angelegt, welche die nächsten Compilierungen aber wesentlich schneller machen. Es erscheint das in Abbildung 1.3 dargestellte, den ganzen Bildschirm einnehmende Layout. Neben einer großen weißen Fläche, der Zeichenfläche, ist rechts ein Tastenfeld zu erkennen, sowie am unteren Rand eine zweigeteilte Informations- und Statusanzeige. Bewegt man die Maus über den Bildschirm, werden in der Informationsanzeige Erklärungen zu dem jeweiligen Objekt, auf dem der Mauszeiger steht, angezeigt. Zunächst sei das Tastenfeld genauer betrachtet. Unter dem PLAN-Logo ist zunächst der Titel des Programms: ====== PLAN ====== zu lesen. Hier wird später der Name Ihres Programms stehen. Im Tastenfeld gibt es zwei Tastengruppen. Unter "Ausführen" sind die Tasten Run, Reset und Info angeordnet. Wenn Sie die Taste Run drücken, verändert sich der Tastenname zu Weiter. Wenn Sie die Taste Reset drücken, erscheint auf dieser Taste wieder Run. Die Info-Taste liefert ein weiteres Windows-Fenster, in dem nur steht, das es keine benutzerdefinierten Informationen gibt. Unter dem Namen "Auswerten" sind die vier Tasten Clear, BMP-File, Zoom und Print zu sehen. Mit Clear wird das Zeichenfenster gelöscht. Man sieht natürlich nichts, da das Fenster ja leer ist. Die Taste BMP-File erstellt ein Bitmapfile von der Zeichenfläche und speichert diese unter dem Filenamen PLAN_0000.bmp (siehe Informationszeile!) ab. Wenn Sie noch einmal auf diese Taste drücken, speichert sie wieder das Zeichenfenster in ein BMP-File, diesmal heißt es aber PLAN_0001.bmp. Wenn Sie sich speziell diese Files später ansehen, z.B. durch Einbinden dieser Files in Word oder Einbinden in irgend ein Grafikprogramm (z.B. Paint), seien Sie nicht enttäuscht. Sie zeigen nur exakt das, was auf der Zeichenfläche dargestellt ist, nämlich nichts! Wenn Sie die Taste Zoom drücken, erscheint am Bildschirm ein anderes Layout. Die Zeichenfläche nimmt nun den Bildschirm vollständig ein. Nur am unteren Rand ist noch die Statusleiste bzw. die Informationszeile zu sehen. 20 Simulation mechatronischer Systeme Abbildung 1.4: Das PLAN – Simulationsfenster, gezoomt. Abbildung 1.5: Durch Eingeben von ? erzeugtes PLAN – Hilfefenster. Simulation mechatronischer Systeme 21 Wenn Sie, egal, ob Sie im gezoomten Zustand sind oder nicht, die Taste ? auf der Tastatur drücken, erscheint wieder ein neues Fenster mit Erklärungen zu Tastenkürzeln, die in diesem Zustand von PLAN verstanden werden. Wichtigste Taste ist die Taste n (normal) oder ESC die wieder das nichtgezoomte Ausgangsbild darstellen. Die letzte Taste in diesem Feld ist die Taste Print. Sie macht genau das, was darauf steht: Sie sendet den Bildschirminhalt direkt zum Standarddrucker Ihres Systems! Die Tasten Run, Reset und Info sollen natürlich in Ihrem Programm mehr bewirken, als das, was sie hier tun! Tatsächlich rufen sie virtuelle Methoden von PLAN auf, die noch leer sind, und von Ihnen durch eigene Funktionen mit Leben erfüllt werden müssen. Die wesentlichen virtuellen Methoden von PLAN sind: void void void void void Init(void); InitDraw(void); Run(void); Reset(void); Finish(void); Die Funktion Init() wird genau einmal zu Beginn des Programms aufgerufen. Intern wird diese Funktion unmittelbar nach dem Erstellen der Windowsspezifischen Details der PLAN – Klassen aufgerufen. Diese Funktion eignet sich, um Ihre persönlichen Datenstrukturen usw. im Programm zu initialisieren oder Speicherplatz zu allokieren. Die Funktion Finish() ist das Gegenstück zur Methode Init() und wird ebenfalls genau einmal aufgerufen, und zwar unmittelbar vor der Beendigung des Programms. Hier können Sie z.B. Ihre Datenstrukturen wieder freigeben. Die Methode Run() wird von PLAN aufgerufen, wenn Sie die Taste Run drücken. Die Aufschrift der Run-Taste ändert sich beim ersten Drücken dieser Taste auf Weiter. Erneutes Drücken dieser Taste führt wieder Ihre Run()-Funktion aus, die Aufschrift der Taste ist weiterhin Weiter. In der Informationszeile am unteren Rand wird Ihnen mitgeteilt, wie oft Run() ausgeführt wurde. Sie können PLAN dazu veranlassen, die Run() – Funktion mit nur wenigen Millisekunden Abstand immer wieder aufrufen zu lassen. Dann erscheint auf der Run – Taste der Titel Halt. Wenn Sie dann diese Taste drücken, wird der automatische Selbstaufruf der Run()- Funktion unterbrochen und auf der Run – Taste steht wieder Weiter. Diese Option ist wichtig, wenn Sie etwa Animationen darstellen wollen. Dann wird in der Run() – Funktion die aktuelle Position zum Beispiel eines Männchens berechnet und dann die Programmausführung kurz an Windows gegeben, das sich um eventuelle Tasten- und Mausereignisse kümmert und eben auch die Grafik aktualisiert. Dann reicht Windows den Staffelstab an Run() zurück. Run() berechnet die wieder neue Position und so 22 Simulation mechatronischer Systeme weiter. Dazu später mehr. Erst wenn Sie die Taste Reset drücken, wird die Tastenaufschrift Weiter oder Halt in Run zurückgeändert. Gleichzeitig wird dann in Ihrem Programm die Methode Reset() aufgerufen! Die Funktion InitDraw() wird zu Programmbeginn sowie immer dann aufgerufen, wenn Sie den Bildschirm mit der Taste Clear löschen. Implizit geschieht das auch, wenn Sie zwischen Normal- und Zoommodus hin- und herschalten. InitDraw() wird benötigt, wenn die Grafikfläche zu Beginn etwas anderes als den leeren Bildschirm zeigen soll. Wenn Sie beispielsweise ein Männchen im Wald simulieren wollen, so könnte InitDraw() den Wald zeichnen und die Simulation würde das Männchen dazu zeichnen. Wenn Sie dann die Taste Clear drücken, würde nur das Männchen vom Bildschirm verschwinden, weil der Wald ohne Männchen neu gezeichnet würde. Ganz analog können Sie die Linien einer Tabelle in InitDraw() zeichnen, deren Spalten von der durchzuführenden Simulation, etwa der Methode Run(), gefüllt werden. Immer wenn Sie Clear drücken, wird dann die Tabelle geleert sein. Neben der Exit – Taste sind zwei LED's zu sehen. Die erste zeigt den Zustand des Run()-Modus, grün ist sie, wenn Run() noch nicht ausgeführt wurde, gelb wird sie, wenn die Run() – Funktion einzeln ausgeführt wird, rot schließlich, wenn Run() kontinuierlich aufgerufen wird. Die zweite LED – Leuchte zeigt den Zustand der numerischen Integration, darauf wird später eingegangen. Die Exit – Taste schließlich, wie oben schon gesagt, schließt das Programm. Zu Beginn soll zunächst die Funktion Init() genutzt werden. Diese am Anfang einmal aufgerufene Funktion eignet sich neben der Initialisierung Ihrer eigenen Simulationsdaten besonders, um dem Programm auch optisch einen Programmnamen sowie einen erklärenden Text zu geben, der beim Drücken der Taste Info angezeigt wird. Um die virtuelle Methode Init() zu installieren, müssen im Prinzip zwei Schritte ausgeführt werden. Zunächst muss in der Klassendefinition diese Funktion deklariert werden. Selbstverständlich muß das eine öffentliche Methode sein (Schlüsselwort public:). class TUser : public TPlan { public: void Init(); }; Jetzt muss diese Funktion noch mit Leben erfüllt werden. Dies kann in der Klasse geschehen oder aber außerhalb. Man beachte, dass die Funktionsdefinition exakt mit der vorgegebenen Signatur void Init(void); übereinstimmt. Nur dann wird diese Funktion vom Programmablauf als Initiierungsfunktion genutzt. Tatsächlich ist diese Funktion als virtuelle Funktion in TPlan schon Simulation mechatronischer Systeme 23 definiert. Mit der Definition in Ihrer Klasse sorgen Sie dafür, dass über den Mechanismus des "late bindings" die internen Funktionen von TPlan die zuletzt definierte Funktion Init(), also Ihre Funktion, nutzen. Selbstverständlich könnten Sie auch eine Funktion Init() mit anderer Signatur definieren: int Init(void); void Init(int i); .... das sind dann aber Funktionen, die nicht in der Klasse TPlan definiert sind und für deren Verwendung Sie selbst Verantwortung tragen müssen. In der Funktion Init() wird nun den Größen ProgrammName und ProgrammInfo je ein Charakterstring zugewiesen. Diese Größen lassen sich praktisch wie Variable behandeln, sind aber sogenannte Properties, die intern mehr oder weniger komplexe Funktionen aufrufen, wenn diese Größen gelesen oder beschrieben werden. In der Klasse TPlan und seinen Unterklassen gibt es eine ganze Reihe solcher Properties, die partiell nur gelesen oder nur beschrieben werden können. void TUser::Init(void){ ProgrammName = "Testprogramm"; ProgrammInfo = "Dies ist ein erster Versuch\r" "eines Testprogrammes mit der\r" "Simulationsumgebung PLAN";} Kompilieren Sie das Programm und führen Sie es aus. Es erscheint das schon bekannte Layout auf dem Bildschirm, aber mit kleinen Änderungen, siehe Abbildung 1.7. Unmittelbar unter dem Logofeld rechts oben erscheint nun Ihr Programmname "Testprogramm". Wenn Sie die Infotaste drücken, erscheint ein neues Fenster, das eben den an die Größe ProgrammInfo gegebenen Text zeigt. Mit der OK-Taste in diesem Infofenster kehren Sie wieder zum normalen Layout zurück. Abbildung 1.6: Das Infofenster des Programms 1 in Kapitel 1.4. 24 Simulation mechatronischer Systeme So können Sie gegebenenfalls dem User Ihres Programms erste Informationen mitgeben. Abbildung 1.7: Das PLAN – Simulationsfenster, Programm 1 Kapitel 1.4. Das vollständige Programmlayout ist nachfolgend gegeben. Man beachte, dass nur in der benutzerdefinierten Klasse TUser Ergänzungen vorgenommen wurden. //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Programm 1 Kapitel 1.4 //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { public: void Init(); }; Simulation mechatronischer Systeme 25 void TUser::Init(){ ProgrammName = "Testprogramm"; ProgrammInfo = "Dies ist ein erster Versuch\r" "eines Testprogrammes mit der\r" "Simulationsumgebung PLAN";} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ 26 Simulation mechatronischer Systeme 2. Methoden in PLAN, Teil I Nachfolgend werden erste Methoden und Datenstrukturen zur Erstellung von Simulationsprogrammen mit PLAN angeführt. Konkrete Simulationsaufgaben findet man in den nachfolgenden Kapiteln 3 ff. Im ersten Abschnitt stellen wir einzelne Grafik- und Schreibmethoden in PLAN vor. In den weiteren Schritten dieses Kapitels werden einfache grafische Animationen und deren Steuerung erklärt. Außerdem werden erste Plotdarstellungen, Mausevents und Ausschnittvergrößerungen eingeführt. Dieses Kapitel will mit sehr wenigen Methoden das prinzipielle Vorgehen für die Simulation aufzeigen. Alle Methoden und Befehle, auch die hier nicht angeführten, kann man im PLAN-Werkzeugkasten ausführlich dokumentiert nachlesen. Alle nachfolgenden Beispiele finden Sie auch auf der begleitenden CD. Im folgenden wird generell davon ausgegangen, dass Sie ein Konsoleprojekt mit einer main()-Funktion erstellen, in das Sie die PLAN-Bibliothek einbinden oder, wie vorne geschildert, ein fertiges Rumpfprojekt nutzen. Gegebenenfalls angegebene Zeiten für Compilierung etc. beziehen sich auf die Zeiten bei einem Pentium IV Prozessor mit 2 GHz unter Windows XP. Simulation mechatronischer Systeme 27 2.1 Grafik in PLAN Eine der wesentlichen Eigenschaften von PLAN ist natürlich die Möglichkeit, auf der Zeichenfläche zu zeichnen. Um erste elementare Befehle kennen zu lernen, soll die Funktion Run() mit Leben erfüllt werden. Immer, wenn die Taste Run gedrückt wird, soll eine einfache Grafik erstellt werden. In PLAN sind standardmäßig einige Farben (Typ TColor) vordefiniert: Schwarz Grau Rot Gelb Gruen Blau Violett Weiss Hellgrau Hellrot Hellgelb Hellgruen Hellblau Hellviolett Klar Selbstverständlich kann jede andere Farbe, die Ihr Windowssystem hergibt, ebenfalls verwendet werden. Der Wertebereich der RGB-Farben ist 0-255 für alle drei Farbanteile. So wird etwa die RGB-Farbe 124,124,124 (= grau) mit folgender Anweisung gesetzt. Farbe = RGBSkala(real r100, real g100, real b100); Hierin ist r100 der Rotanteil in Prozent, entsprechend ist g100 der Gelbanteil und b100 der Blauanteil jeweils auch in Prozent. Die oben genannte graue Farbe gibt es also mit Farbe = RGBSkala(50,50,50); // 50% von 255 = 124 Um in Windows ein Objekt zu zeichnen, muss man einen Pen und ein Brush definieren. Objekte wie etwa ein Rechteck werden mit Pen gezeichnet und mit Brush ausgefüllt. Mit den Befehlen SetPen(Farbe1); SetBrush(Farbe2); erhält Pen die Farbe 1 und Brush die Farbe 2. Diese Farben bleiben global in PLAN erhalten, bis ein erneuter Aufruf dieser Funktionen die jeweilige Farbe ändert. 28 Simulation mechatronischer Systeme Erwähnenswert ist die Farbe Klar. Dies ist keine Windowsfarbe, sondern eine tatsächlich fiese grüne Farbe – dies ist zumindest die Empfindung des Autors -, die softwaretechnisch so abgefangen wird, dass sie nicht gezeichnet wird. Ein Rechteck wird mit der Funktion void Rectangle(int x, int y, int w, int h) gezeichnet. Hierin sind (x,y) die (Pixel-)Koordinaten des linken oberen Punktes, w die Weite und h die Höhe des zu zeichnenden Rechteckes ebenfalls in Pixeln. SetPen(Hellrot); SetBrush(Gelb); Rectangle(50,50,100,200); zeichnet also ein gelbes Rechteck mit hellroter Umrahmung vom Punkt (50,50) mit der Weite 100 und der Höhe 200. Setzt man die Farbe des Pinsels auf Klar: SetBrush(Klar); Rectangle(80,80,100,100); wird nur ein Rechteck mit hellroter Umrahmung gezeichnet. Das Innere des Rechteckes wird nicht gezeichnet, bleibt also klar! Der Stift hat nicht nur eine Farbe, sondern auch eine Strichstärke. Diese wird in Pixel angegeben und wird einfach als zweites Argument der Funktion SetPen() angegeben. SetPen(Blau,9); Ab dieser Anweisung hat der Strich die Farbe Blau und eine Strichstärke von 9 Pixeln. Weitere elementare Grafikfunktionen in PLAN sind: void Circle(int x, int y, int r); void Circle(int x, int y, int rx, int ry); Die Funktion Circle() zeichnet Kreise und Ellipsen um den Punkt (x,y) mit dem Radius r bzw. mit den Radien rx in x-Richtung (horizontal) und ry in y-Richtung (vertikal). Damit sind die Befehle in der nachfolgend angegebenen Funktion Run() klar. Das resultierende Bild am Bildschirm zeigt Abbildung 2.1. Weitere Grafikbefehle wie Polygone, Kreisbögen, elliptische Bögen usw. sehe man im Anhang, bzw. in der Kurzübersicht der Befehle von PLAN nach! Simulation mechatronischer Systeme 29 Abbildung 2.1: Das Programm 2 nach Drücken der Run - Taste. Hier der Sourcecode des Programms 2: //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Programm 2 (Kapitel 2.1) // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { public: void Init(void); void Run(void); }; 30 Simulation mechatronischer Systeme void TUser::Init(void){ ProgrammName = "Testprogramm"; ProgrammInfo = "Dies ist ein erster Versuch\r" "eines Testprogrammes mit der\r" "Simulationsumgebung PLAN";} void TUser::Run(void){ SetPen(Hellrot,1); SetBrush(Hellgelb); Rectangle(50,50,800,700); SetPen(Blau); SetBrush(Klar); Rectangle(80,80,800,700); SetPen(Blau,3); Circle(330,330,300); SetPen(Grau,3); SetBrush(Hellgruen); Circle(330,330,300,100); } //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Bei dem oben angeführten Beispiel sind die Grafikobjekte einfach in absoluten Pixelgrößen angegeben. Dies kann insbesondere dann zu Schwierigkeiten führen, wenn Sie etwa den Bildschirm auf unterschiedlichen Rechnern mit Ihrem Programm passgenau füllen wollen. Tatsächlich ändert sich die Größe des Zeichenfensters, das heißt, die Anzahl der Pixel auch, wenn man nur vom Normalmodus zum Zoommodus übergeht. Hierfür gibt es die Funktionen: int GetMaxW(); int GetMaxH(); Diese liefern die jeweils maximale Anzahl der Pixel der Zeichenfläche in x- bzw. yRichtung. Alle Objekte, die größer sind als die verfügbare Zeichenfläche werden automatisch abgeschnitten. Man kann auf der Zeichenfläche auch Ausschnitte, sog. Clipregionen, definieren, in denen man zeichnen will. Außerhalb dieser Ausschnitte werden die Zeichenvorgänge abgeschnitten. Die zugehörigen Funktionen sind: void View(); void View(int x, int y, int w, int h); void Clear(TColor Farbe); Simulation mechatronischer Systeme 31 Mit View() wird die gesamte Zeichenfläche für Zeichenaktionen freigegeben. Mit View(10,10,100,100) werden beliebige grafische Anweisungen nur noch im Rechteckausschnitt von (10,10) bis (110,110) sichtbar. Mit einem erneuten Aufruf von View() mit oder ohne Argumente kann dieser Ausschnitt wieder umdefiniert werden. Mit dem Befehl Clear() wird der freigegebene Ausschnitt des Zeichenfensters gelöscht. Defaultmäßig wird hierbei der aktive Ausschnitt mit Weiß gelöscht, Sie können aber auch jede andere Farbe dazu nutzen. Die Statementfolge: View(); Clear(Grau); View(10,20,400,500); Clear(); // // // // ganzer Bildschirm aktiv löscht mit Grau Ausschnitt aktiv aktuellen Ausschnitt löschen x (0,0) (10,20) y (410,520) (GetMaxW(),GetMaxH()) Abbildung. 2.2: Bildschirm mit View() und Clear() Anweisungen löscht den gesamten Bildschirm mit der Farbe Grau. Das Feld mit den Koordinaten (10,20) für die linke obere Ecke und (410,520) für die rechte untere wird mit der Farbe Weiß gelöscht. Auf der grauen Zeichenfläche erscheint ein weißes Rechteck, siehe Abbildung 2.2. Die Größe des gerade aktiven Ausschnittes auf der Zeichenfläche lässt sich mit den Funktionen int int int int erfragen. GetX(); GetY(); GetW(); GetH(); // // // // hier hier hier hier = = = = 10 20 400 500 32 Simulation mechatronischer Systeme Wenn man im Programm 2 (Kap.2.1) die Run()-Funktion mit einem View()-befehl ergänzt, kann man zum Beispiel die dort dargestellten Kreise passend beschneiden, sodass sie in das erste Rechteck passen. void TUser::Run(void){ SetPen(Hellrot,1); SetBrush(Hellgelb); Rectangle(50,50,800,700); SetPen(Blau); SetBrush(Klar); View(50,50,800,700); Rectangle(80,80,800,700); SetPen(Blau,3); Circle(330,330,300); SetPen(Grau,3); SetBrush(Hellgruen); Circle(330,330,300,100); } // Ergaenzung !!! Das resultierende Bild zeigt Abbildung 2.3. Abbildung 2.3: Das modifizierte Programm 2 nach Drücken der Run - Taste. Simulation mechatronischer Systeme 33 Man kann den aktuellen Ausschnitt auch skalieren. Dadurch ist jeder Punkt im aktiven Ausschnitt durch ein reelles Zahlenpaar gegeben. Mit den Funktionen void Scale(real xleft,real xright,real ybottom, real ytop); void Scale(real xleft,real xright,real ybottom); lässt sich ein reelles Koordinatensystem auf den Ausschnitt legen, dessen Pixel dann mit real (=double) Größen angesprochen werden können. Alle Grafikbefehle akzeptieren auch float- oder double-Argumente statt der Integer-Argumente, die sie dann in Pixelwerte umrechnen. Diese Umrechnung bezieht sich immer auf den Letzten Scale() – Befehl im Programm. Der zweite Scale()-Befehl berechnet intern den Wert für ytop auf der Hochachse so, dass eine Proportionalskalierung erzwungen wird, unabhängig von dem Größenverhältnis von Breite und Höhe des Ausschnittes. Damit wird ein Kreis auch wirklich ein Kreis. Eine nichtproportionale Skalierung macht aus jedem Kreis eine Ellipse. Man beachte, dass der Punkt (xleft,ybottom) immer links unten im Ausschnitt liegt. (x,y) Pixelkoordinatensystem: x,y,w,h (xright,ytop) Grafikfläche oder Ausschnitt reelles Koordinatensystem: xleft, xright, ybottom, ytop (xleft,ybottom) (x+w,y+h) Abbildung 2.4: Koordinatensysteme auf der Bildfläche. Umrechnungen dieser Koordinatensysteme sind gegeben mit den Funktionen IntToX(), IntToY() sowie XToInt(), YToInt(), die die Absolutwerte liefern. Relativwerte, also Längen von Strecken, werden durch die Funktionen IntToDX(), IntToDY(), DXToInt(), DYToInt() geliefert. So gilt etwa: xright = IntToX(x+w); w = DXToInt(xright - xleft); 34 Simulation mechatronischer Systeme Textausgaben lassen sich mit den Befehlen SetText(Farbe); SetText(Farbe,Size); Text(int x, int y, char *s); TextRect(int x, int y, int w, int h, char *s, int center=0); bewerkstelligen. Mit Size wird die Schriftgröße in Pixeln gewählt, mit dem Argument 0 wird die Standardschriftgröße Ihres Windowsystems gesetzt, das ist die Größe der Schrift auf den PLAN – Tasten und die Defaulteinstellung in PLAN. Mit Farbe lässt sich die Textfarbe angeben. Der Hintergrund der Schrift wird durch die Farbe des Pinsels, siehe SetBrush(), bestimmt! Wollen Sie also auf beliebigem Untergrund nur die Schriftzeichen ausgeben, ohne das der Hintergrund überdeckt wird, müssen Sie den Pinsel auf Klar setzen! Text() gibt den im Argument angegebenen String ab der Position (x,y) aus (linke obere Ecke des Strings). TextRect() gibt den String im angegebenen Rechteck aus und schneidet gegebenenfalls überstehende Zeichen ab. Ist der String kleiner als das Rechteck (x,y,w,h), kann mit dem Parameter center der Text linksbündig (=0), mittig (=1) oder rechtsbündig (=2) im Rechteck ausgegeben werden. Die aktuelle Texthöhe oder Weite eines Strings in Pixeln kann mit den Funktionen int TextH(char *s); int TextW(char *s); erfragt werden. Simulation mechatronischer Systeme 35 2.2 Animationen Eine der bekanntesten und auch spektakulärsten Eigenschaften von Simulationen sind die grafischen Animationen. Hier soll ein sehr einfaches Beispiel angegangen werden. Ein Rechteck soll sich über den Bildschirm bewegen. Dazu werden in der Userklasse TUser zwei Integer-Koordinaten xKoord, yKoord im private Abschnitt der Klasse definiert, die in der Init()-Funktion einen Startwert erhalten. Die Run()-Funktion soll nun zunächst das Rechteck löschen, d.h. weiß übermalen, die Koordinaten neu setzen und dann das Rechteck mit den neuen Koordinaten zeichnen. Die Reset()-Funktion dient hier dazu, die Koordinaten wieder auf ihre Startwerte zu setzen. Es ist bequem, die Reset() - Funktion auch in der Init() - Funktion aufzurufen, da so nur an einer Stelle im Programm die Anfangswerte definiert werden müssen. Der Sourcecode könnte also etwa so aussehen: //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Programm 3 (Kapitel 2.2) // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { int xKoord, yKoord; public: void Init(void); void Run(void); void Reset(void); }; void TUser::Init(void) { ProgrammName = "Animation"; ProgrammInfo = "Programm zur Demonstration\r" "einer einfachen Animation"; Reset();} void TUser::Run(void){ SetPen(Weiss); SetBrush(Weiss); Rectangle(xKoord,yKoord,100,100); xKoord +=5; yKoord +=5; SetPen(Schwarz); SetBrush(Hellrot); Rectangle(xKoord,yKoord,100,100);} 36 Simulation mechatronischer Systeme void TUser::Reset(void) { Clear(); xKoord = 10; yKoord = 10;} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Starten Sie jetzt das Programm. Wenn Sie auf die Taste Run drücken, wird ein schwarzgerändertes rotes Quadrat gezeichnet. Wenn Sie wieder auf diese Taste drücken - sie hat jetzt den passenden Namen Weiter – dann scheint sich das Quadrat zu bewegen. Wenn Sie genügend Geduld haben und oft genug diese Taste drücken, werden Sie das Quadrat an der rechten unteren Ecke der Zeichenfläche verschwinden sehen. Um das Rechteck wieder zu sehen, müssen Sie die Reset-Taste drücken. Die WeiterTaste heißt jetzt wieder Run, und Sie können das natürlich nicht besonders interessante Spiel von vorne beginnen. Störend hierbei ist natürlich unter anderem die Tatsache, dass Sie immer einen Knopf drücken müssen, um das Rechteck zu bewegen. Dies ließe sich natürlich dadurch beheben, dass Sie eine for(..), while(..) oder sonstige Schleife in der Run()-Funktion einbauen. Aber beachten Sie, dass Sie dann keine Animation haben. Windows gibt alle Kapazitäten an Ihre Run()Funktion, welche die Schleife ausführt. Mausbewegungen oder gar grafische Ausgaben kann Windows nicht erfassen beziehungsweise durchführen, solange Ihre Run()-Funktion arbeitet. PLAN führt Ihre Run-Funktion solange aus, bis diese fertig ist. Wenn Sie kein Abbruchkriterium in den Schleifen haben, müssen Sie Windows sogar persönlich um Hilfe bitten, um Ihr Programm zu beenden. Da das immer mal passieren kann, sei hier dieser rabiate Programmabbruch skizziert: Drücken Sie die Tasten Strg + Alt + Entf. Windows meldet sich mit einem Fenster, in dem unter anderem Ihr Programm steht in der Form "task ... reagiert nicht". Hier können Sie Windows dann veranlassen, diesen task zu beenden. Ein eleganterer Weg zu einer kontinuierlichen Animation ist der Weg, PLAN dazu zu veranlassen, die Funktion Run() immer wieder aufzurufen, bis Sie eingreifen. Dann kann Windows nach jedem Run()-Aufruf nachsehen, was an Grafik gemacht werden muss und ob Sie vielleicht die Maus bewegt oder eine Funktionstaste am Bildschirm betätigt haben. Dies kann einfach mit einem zusätzlichen Statement in der Run()-Funktion veranlasst werden: Simulation mechatronischer Systeme 37 void TUser::Run(void){ SetPen(Weiss); SetBrush(Weiss); Rectangle(xKoord,yKoord,100,100); xKoord +=5; yKoord +=5; SetPen(Schwarz); SetBrush(Hellrot); Rectangle(xKoord,yKoord,100,100); CallRun = true;} Ist die Größe CallRun auf true gesetzt, veranlasst PLAN nach Aktualisierung der Grafik und anderen kleinen Geschäften, nach ca. 20 Millisekunden den Wiederaufruf der Funktion Run(). Diese Zeit können Sie mit der Größe CallRunTime einstellen. darauf wird später eingegangen. Wenn Sie jetzt das Programm starten und die Run-Taste drücken, sehen Sie das sich nach rechts unten bewegende Quadrat. Die Run-Taste wechselt wieder ihren Namen und heißt jetzt Halt. Sie können nun jederzeit während der "Animation" die Halt-Taste drücken. PLAN stoppt dann die kontinuierlichen Aufrufe Ihrer Run()-Funktion, tauft die Run- bzw. Halt-Taste wieder um auf Weiter und wartet. Drücken Sie Weiter, dann ruft PLAN ihre Run()-Funktion weiter auf und zeigt wieder die Halt-Taste. Drücken Sie die Reset-Taste, wird die Run-, Halt-, Weiter-Taste wieder zur Run-Taste, und Sie können von vorn mit der "Animation" anfangen. Übrigens wird am unteren Rand auf der rechten Seite der PLAN Oberfläche die Anzahl der Run() – Aufrufe explizit angegeben. Achten Sie mal darauf. Erst wenn Sie die Reset – Taste drücken, wird diese Größe, sie heisst CallRunCount, wieder auf Null gesetzt. Sie können übrigens auch explizit auf diese Größe zugreifen und zum Beispiel die Anzahl von Run() – Aufrufen anzahlmäßig begrenzen, in dem Sie etwa den Wiederaufruf von Run() damit konditionieren: CallRun = (CallRunCount<1000); Hier wird die Run() - Funktion also nur 1000 mal aufgerufen. 38 Simulation mechatronischer Systeme 2.3 Usertasten Die "Animation" des vorhergehenden Abschnittes ist natürlich noch recht langweilig. In diesem Abschnitt soll nur ein weiterer Aspekt angesprochen werden. Wie kann ich die Attribute der Animation oder eben andere Parameter in der Simulation während des Programmlaufs ändern? Die einfachste Möglichkeit ist, weitere Tasten in PLAN zu integrieren, die eben diese Aufgabe übernehmen. PLAN hat intern zwanzig weitere Tasten für Sie definiert. Sie können diese aktivieren (also sichtbar machen und ausführen) mit den Befehlen void void void void void ... void InsertTaste(int Nr, char *Name, char *info); DeleteTaste(int Nr); DeleteAllTasten(); RunTaste0(void); RunTaste1(void); RunTaste19(void); Die Funktion InsertTaste() fügt eine Taste in das untere Tastenfeld von PLAN ein. Nr. ist hierbei eine Zahl von 0 bis 19. Die Taste Nr. 0 befindet sich links in der ersten Reihe, die Taste Nr. 1 rechts in der ersten Reihe. Entsprechend ist die Taste Nr. 19 rechts in der untersten Reihe. Der String Name kennzeichnet die Aufschrift auf der Taste, der String info wird in der Informationszeile von PLAN angezeigt, wenn Sie mit dem Mauszeiger über diese Taste wandern. Damit können Sie dem Nutzer Ihres Programms ausführlicher sagen, wozu Ihre Taste nun gut ist. Die Funktion DeleteTaste() löscht diese Taste wieder, d.h. macht sie unsichtbar und unwirksam. DeleteAllTasten() macht alle zur Zeit aktiven Tasten von Ihnen auf einmal unwirksam. Wesentlich ist, das PLAN diese zusätzlichen Tasten korrekt verwalten kann, also im Zoommodus optisch unterdrückt aber die Hotkeyeigenschaften weiter aktiv sein lässt. Was passiert nun, wenn eine Taste gedrückt wird? PLAN ruft dann die virtuelle Funktion RunTasteNr() auf, wobei Nr für die Nummer der jeweiligen Taste steht. Es liegt also wieder an Ihnen, den zusätzlichen Tasten einen entsprechenden Sinn zu geben. Wenn Sie diese Funktionen RunTaste0() und so weiter nicht definieren, werden zwar die Tasten angezeigt, aber der Mausklick auf diese Tasten bleibt wirkungslos. Wie bei den beiden oberen Tastenfeldern, können Sie auch Ihren Tasten eine Tastenfeldüberschrift geben. Dazu brauchen Sie der Größe TastenfeldName nur einen entsprechenden String zuzuweisen. Wie lassen sich diese zusätzlichen Tasten nun konkret nutzen. Bauen wir auf das einfache Animationsprogramm aus dem vorhergehenden Abschnitt auf. Eine zur Laufzeit Simulation mechatronischer Systeme 39 zu ändernde Option könnte das Löschen des Quadrates sein. Wird dieses nicht, wie im vorhergehenden Abschnitt jedes Mal gelöscht, verbleibt auf dem Bildschirm eine Spur. Ob nun das Löschen stattfinden soll oder nicht, könnte man mit einer weiteren booleschen Variablen, nennen wir sie SpurOn, steuern. Diese soll durch zwei zusätzliche Tasten auf wahr oder falsch gesetzt werden können. Es bietet sich der Einfachheit halber an, die Tasten schon im Init()-Teil von PLAN zu initialisieren. Der zugehörige Sourcecode dürfte keine großen Überraschungen bieten. //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Programm 4 (Kapitel 2.3) // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { int xKoord, yKoord; bool SpurOn; public: void Init(void); void Run(void); void Reset(void); void RunTaste0(); void RunTaste1(); }; void TUser::Init(void) { ProgrammName = "Animation"; ProgrammInfo = "Programm zur Demonstration\r" "einer einfachen Animation"; Reset(); TastenfeldName = "Objektspur"; InsertTaste(0,"Spur ein","Rechteck hinterlässt eine Spur"); InsertTaste(1,"Spur aus","Rechteck hinterlässt keine Spur"); SpurOn = false;} void TUser::Run(void){ if(!SpurOn){ SetPen(Weiss); SetBrush(Weiss); Rectangle(xKoord,yKoord,100,100);} xKoord +=5; yKoord +=5; SetPen(Schwarz); SetBrush(Hellrot); Rectangle(xKoord,yKoord,100,100); CallRun = true;} void TUser::Reset(void) { Clear(); 40 Simulation mechatronischer Systeme xKoord = 10; yKoord = 10;} void TUser::RunTaste0(){ SpurOn = true;} void TUser::RunTaste1(){ SpurOn = false;} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Man starte das Programm. Nach den Drücken der Run-Taste beginnt das Quadrat nach unten zu wandern. Während dieser Simulation können Sie eine der neuen Tasten drücken, und Sie erhalten eine Spur oder eben nicht. Um dieses Umschalten durchzuführen, müssen Sie nicht einmal die Simulation abbrechen ! Der Bildschirm sollte nun – nach einigen Tastenaktionen etwa wie in Abbildung 2.7 aussehen. Beachten Sie das geänderte Layout von PLAN im rechten Tastenfeld! Abbildung 2.5: Animation mit Programm 4. Simulation mechatronischer Systeme 41 Natürlich kann man das Schalten einer boolschen Variablen auch mit nur einer Taste gestalten. Der Titel der Tasten kann jederzeit geändert werden. So könnte etwa die Funktion RunTaste0() wie folgt aussehen. void TUser::RunTaste0(void) { SpurOn = !SpurOn; InsertTaste(0,(SpurOn)?"Spur aus":"Spur ein", "Ein- Ausschalten einer Spur");} Diese RunTaste0() – Funktion ersetzt die beiden entsprechenden Funktionen in Programm 4. Natürlich muss auch nicht mehr die Taste 1 in der Init() – Funktion generiert werden. Der Aufbau der Taste 0 muss dort auch nicht mehr mit der InsertTaste() – Funktion realisiert werden. Diese Funktion ist ja in der RunTaste0() – Funktion schon angegeben. Man kann in Init() einfach angeben: SpurOn = false; RunTaste0(); 42 Simulation mechatronischer Systeme 2.4 Plotgrafiken PLAN enthält eine Reihe von Standardplotdarstellungen. Die ersten 10 Plots können über die Variablen Plot0, Plot2,...Plot9 angesprochen werden. Plots sind komplexe Objekte, die über dem Zeichenfeld von PLAN liegen. Unter den Plots wird also gegebenenfalls eine Animation weitergeführt. Die Tasten Clear, Print und BMP-File von PLAN arbeiten nur auf der PLAN-Zeichenfläche, sie berühren die Plotdarstellungen darum nicht. Für das folgende sei ein Plot auf der Zeichenfläche geplant. Der Einfachheit halber sei die Plotvariable Plot0 genutzt. Der erste Schritt ist, die Größe des Plots bezüglich der Zeichenfläche festzulegen. Hierzu ruft man die Methode Size(x,y,w,h) in Plot0 auf. Sie erwartet als Parameter die Pixel der linken oberen Ecke (x,y) sowie die Weite w und Höhe h in Pixel. Wird diese Methode ohne Argumente aufgerufen, nimmt das Plot die gesamte Zeichenfläche ein. Plot0->Size(); Plot0->Size(10,10,600,600); // Plot nimmt ganzes Bild ein mit der zweiten Anweisung wird das Plot Plot0 als 600x600 Pixel großes Feld vereinbart. Defaultmäßig gehen die Achsenskalen von 0.0 bis 1.0 und alle Achsentexte, Titel und Untertitel sind leer. Nun sollten natürlich noch Grundinformationen an das Plot gegeben werden wie etwa der Titel oder die Achsenbeschriftung. Dazu dienen die folgenden Methoden im Plot: Plot0->Titel = "Funktionsdarstellungen"; Plot0->Untertitel = "y = sin(x)"; Plot0->Achse(int Nr, real min, real max); Plot0->Achse(int Nr, char *s); Die beiden letzteren Funktionen erwarten in Nr eine Zahl von 0 bis 2, dabei ist 0 die x-Achse. Diese verläuft horizontal von links nach rechts. Die y-Achse mit der Nr. 1 verläuft immer von unten nach oben und die z-Achse mit der Nr 2 ist im Fall einer 3D-Darstellung die in den Raum zeigende Achse. Mit min und max gibt man die gewünschten Minimal- und Maximalwerte auf der jeweiligen Achse an. PLAN berechnet automatisch eine Achseinteilung, die zu mehr als zwei bzw. zu weniger als zehn Achsenabschnitten führen würde. Der String s in der letzten Funktion wird als Achsenbeschriftung der Achsen 0, 1 oder 2 gesetzt. Mit den Befehlen Plot0->Visible = true; Plot0->Visible = false; Simulation mechatronischer Systeme 43 kann das Plot sichtbar oder unsichtbar gemacht werden. Unabhängig von dieser Variablen bleibt das Plot physikalisch erhalten, und kann Punkte, Kurven oder Flächen generieren, auch wenn es nicht sichtbar ist. Dies ist manchmal wichtig, wenn man sich abwechselnd auf eine grafische Animation und auf ein Plot konzentrieren will, ohne das jeweils andere abbrechen zu müssen. Üblicherweise wird man die Designangaben wie Achsenbeschriftung, Größe usw. in der Init-Funktion des Programms angeben. Man kann aber zu jedem Zeitpunkt alle Daten des Plots beliebig ändern. Ist dabei das Plot sichtbar, werden diese Änderungen unmittelbar am Bildschirm angezeigt. Mit den Angaben zur Größe des Plots, zum Titel, zur Achsbeschriftung und der Vermaßung der Achsen erscheint defaultmäßig ein zweidimensionales Plot. Für eine erste Plotversion benötigt man also nur die Funktion Init() in Ihrer Anwenderklasse. //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Programm 6 (Kapitel 2.4) // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { public: void Init(void); }; void TUser::Init(void){ Plot0->Size(100,100,600,600); Plot0->Titel = "Funktionsdarstellungen"; Plot0->Achse(0,0.0,6.3); Plot0->Achse(0,"x-Achse"); Plot0->Achse(1,-10.0,10.0); Plot0->Achse(1,"y-Achse");} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Das resultierende Layout des Plotbildes in PLAN ist in Abbildung 2.6 dargestellt. Es gibt eine Reihe von Eigenschaften des Plots, die man beliebig setzen kann. 44 Simulation mechatronischer Systeme Plot0->AchsenZahlen = true; // oder = false; Mit AchsenZahlen kann man die Angabe von Achsenzahlen unterdrücken. Analoges gilt für die Achsenbeschriftung: Plot0->AchsenText = true; // oder = false; und auch für den Titel und Untertitel des Plots: Plot0->PlotTitel = true; // oder = false; Je nach Wert dieser Größen wird das Plot entsprechend vergrößert oder verkleinert, sodass das von Ihnen angegebene Grafikfeld gerade ausgefüllt wird. Abbildung 2.6: Programm 6 mit Plot0. Bitte beachten Sie, dass das Plot nicht die Zeichenfläche von PLAN behindert, sondern über dieser Zeichenfläche liegt. Man kann also parallel zum Plot die Zeichenfläche beliebig nutzen. Wird das Plot ausgeblendet (siehe unten), ist die Grafik auf der PLAN - Zeichenfläche vollständig! Simulation mechatronischer Systeme 45 Wenn Sie auf das Plot klicken, erscheint am oberen linken Rand des Plots ein kleines Fenster mit nur 4 Hotkeys: Abbildung 2.7: Hotkeys nach Mausklick auf Plot. Wenn Sie auf die Diskette klicken erscheint ein Bildsavemenue, wo Sie Name und Pfad (nur) des Plotbildes eingeben können. Generiert werden Bitmaps. Bei Klick auf den Drucker wird das Bitmapbild des Plots an den aktiven Drucker geschickt. Abbildung 2.8: Plotbitmap, wenn auf den Hotkey mit der Diskette geklickt wird. 46 Simulation mechatronischer Systeme Ein Klick auf den Hammer schließlich führt auf ein weiteres Menü, wo Sie praktisch alle Daten und Eigenschaften des Plots jederzeit definieren oder verändern können. Probieren Sie es einfach aus! Abbildung 2.9: Plotmenü, erscheint bei Druck auf den Hammer Hotkey. Mit der Größe Links bzw. Rechts Plot0->Links = true; Plot0->Rechts = true; // oder: Plot0->Rechts = false; // oder :Plot0->Links = false; wird im 2D-Plot die Seite angegeben, auf der die Beschriftung und Vermaßung der yAchse angegeben wird. Die Plotachsendarstellung lässt sich über den Parameter AchsenTyp einstellen. Plot0->AchsenTyp = i; // i = -2, -1, 0, 1, 2 Für i = 0 werden keine Achsen im Inneren des Plotfeldes gezeichnet. Bei i = 1 wird nur die Nullachse, bei i = 2 alle Achsen gezeichnet. Ist i negativ, werden die Achsen gestrichelt gezeichnet. Defaultwert ist i = 2; Auch lassen sich allen Flächen, Strichen und Texten eigene Farben zuordnen. Mit den zusätzlichen Angaben Simulation mechatronischer Systeme Plot0->PlotFarbe = Plot0->FeldFarbe = Plot0->AchsenFarbe Plot0->AchsenTyp = Hellgrau; Grau; = Weiss; 2; in der Init()-Funktion erhält man ein Plotbild wie in Abbildung 2.10. Abbildung 2.10: Plot mit verändertem Layout. Für Eilige gibt es 5 fertige Layouts, die mit Plot0->Layout(int i); 47 48 Simulation mechatronischer Systeme angesprochen werden können oder aber im Plotmenü durch Klicken auf die Hotkeys des Feldes Layout am unteren Ende erstellt werden. Abbildung 2.11: Plots mit vordefiniertem Layout 0, 1, 2, 3, 4. Auch lassen sich mit Klick auf 3D Plot im Feld Stil des Plotmenüs (Abbildung 2.9) die inaktiven Elemente für die Gestaltung von 3D – Plots aktivieren. Bitte sehen Sie im Handbuch für weitere Optionen des Plotlayouts nach. Der Inhalt des Plots sind natürlich Kurven, Punkte oder ganze Flächen. In einem Plot können bis zu 10 unterschiedliche Kurven parallel animiert dargestellt werden. Sie werden über die Variablen Kurve0, Kurve1 bis Kurve9 angesprochen. Auf die Animation von Kurven wird später eingegangen. Mit jeder dieser Variablen können natürlich beliebig viele normale Kurven in einem Plot dargestellt werden. Den Kurven können Farben und Strichstärken zugeordnet werden, die jederzeit auch geändert werden können. Plot0->Kurve0->SetPen(Schwarz,3); Plot0->Kurve1->SetPen(Gelb); Hier hat Kurve0 die Attribute: Farbe Schwarz und Strichstärke 3, Kurve1 ist gelb und hat die Strichstärke 1. Die einfachsten Grafikbefehle in Kurve0 (und allen anderen Größen Kurve1,..) sind bool MoveTo(real x, real y); bool LineTo(real x, real y); Diese Befehle geben den boolschen Wert true zurück, wenn der Punkt im aktuellen Plotfeld liegt, entsprechend wird der Wert false geliefert, wenn der Punkt außerhalb der von Ihnen angegebenen Achsenwerte liegt. Damit lässt sich das Programm 6 einfach erweitern, um etwa die Funktion y = 9*sin(x) darzustellen. Simulation mechatronischer Systeme 49 //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Programm 6 (Kapitel 2.5) // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { public: void Init(); void Run(); }; void TUser::Init(){ Plot0->Size(100,100,600,600); Plot0->Titel = "Funktionsdarstellungen"; Plot0->Achse(0,0.0,6.3); Plot0->Achse(0,"x-Achse"); Plot0->Achse(1,-10.0,10.0); Plot0->Achse(1,"y-Achse"); Plot0->Kurve0->SetPen(Rot,3);} void TUser::Run() { real x,a = 9.0; Plot0->Kurve0->MoveTo(x,a*sin(x)); for(x=0.2;x<=6.4;x+=0.2) Plot0->Kurve0->LineTo(x,a*sin(x));} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Das resultierende Plot zeigt Abbildung 2.12. 50 Simulation mechatronischer Systeme Abbildung 2.12: Plot des Programms 6. Wenn man Messpunkte miteinander verbindet, will man sicher auch die eigentlichen Messpunkte kennzeichnen. Hierzu kann optional in den oben schon dargestellten Befehlen MoveTo() und LineTo() je ein zusätzlicher boolescher Parameter zur Zeichnung von Markern angegeben werden bool MoveTo(real x, real y, bool ma = false); bool LineTo(real x, real y, bool ma = false); Dieser zusätzliche Parameter ma ist defaultmäßig auf false gesetzt und unterdrückt so die Darstellung der Punkte. Die Punktmarkierung selbst wird über die Größen PointTyp ( ein Wert zwischen 0 und 19) und PointColor charakterisiert. Für Simulation mechatronischer Systeme 51 PointTyp = 0 wird kein extra Marker gezeichnet. Beide Größen lassen sich wie bei der PEN - Einstellung mit einem Befehl einstellen: void SetMarker(Farbe, Typ); Nachfolgend sind die Init()– und die Run()-Funktion in Programm 6 leicht modifiziert. Run() zeichnet 19 Cosinusfunktionen mit Markern unterschiedlichen Typs. Der Anfangswert der Cosinusfunktion ( = 1, .., 19) kennzeichnet so den Markertyp, der für die entsprechende Kurve genutzt wurde; void TUser::Init(){ Plot0->Size(100,100,600,600); Plot0->Titel = "Funktionsdarstellungen"; Plot0->Untertitel = "y = a*cos(x)"; Plot0->Layout(1); Plot0->AchsenTyp = 0; Plot0->Achse(0,0.0,1.5); Plot0->Achse(0,"x-Achse"); Plot0->Achse(1,0.0,20.0); Plot0->Achse(1,"Markertypen i, y = i*cos(x)"); Plot0->Kurve0->SetPen(Hellgrau);} void TUser::Run() { real x,a; int i; for(i=0;i<19;i++) { a = (real)i; Plot0->Kurve0->SetMarker(Schwarz,i); Plot0->Kurve0->MoveTo(0.0,a*cos(0.0),true); for(x=0.1;x<=1.6;x+=0.1) Plot0->Kurve0->LineTo(x,a*cos(x),true);}} Das entsprechende Plotbild ist in Abbildung 2.13 zu sehen. Wenn man nur Punkte oder Punktmarker zeichnen will, so kann man auch den Befehl bool Point(real x, real y); nutzen. Dieser markiert auf dem Plotfeld je nach Einstellung von PointTyp einen entsprechenden Marker. Für PointTyp = 0 wird ein einzelnes Pixel in der Kurvenfarbe gezeichnet. Wenn man etwa die numerische Integration wiederholen will, wird man die ResetTaste drücken. Die Plotdarstellung soll dann im Allgemeinen auch zurückgestellt wer- 52 Simulation mechatronischer Systeme den, insbesondere der Plotinhalt gelöscht werden. Dies lässt sich einfach mit dem Befehl Reset() im Plot bewerkstelligen. Abbildung 2.13: Plot des Programms 6. Das Programm 6 sollte also um eine Reset()-Funktion erweitert werden, die dann auch das Plot zurücksetzt. Hier nun Programm 6 vollständig: //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== Simulation mechatronischer Systeme 53 // Programm 6 (Kapitel 2.5) // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { public: void Init(); void Run(); void Reset(); }; void TUser::Init(){ Plot0->Size(100,100,600,600); Plot0->Titel = "Funktionsdarstellungen"; Plot0->Untertitel = "y = a*cos(x)"; Plot0->Layout(1); Plot0->AchsenTyp = 0; Plot0->Achse(0,0.0,1.5); Plot0->Achse(0,"x-Achse"); Plot0->Achse(1,0.0,20.0); Plot0->Achse(1,"Markertypen i, y = i*cos(x)"); Plot0->Kurve0->SetPen(Hellgrau);} void TUser::Run() { real x,a; int i; for(i=0;i<19;i++) { a = (real)i; Plot0->Kurve0->SetMarker(Schwarz,i); Plot0->Kurve0->MoveTo(0.0,a*cos(0.0),true); for(x=0.1;x<=1.6;x+=0.1) Plot0->Kurve0->LineTo(x,a*cos(x),true);}} void TUser::Reset(){ Plot0->Reset();} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ 54 Simulation mechatronischer Systeme 2.5 Mausevents und Ausschnitte Recht häufig sind Beeinflussungen der Simulation am Bildschirm notwendig, hier können etwa Mausklicks auf der Zeichenfläche, oder das Ziehen von Punkten mit der Maus bei gedrückter linker Maustaste hilfreich sein. Dies wird ebenfalls in einfacher Form in PLAN unterstützt. Insbesondere wird eine fest programmierte Form zum userbedienten Zoomen vorgestellt. Das Programm PLAN enthält auch Methoden, um Mausevents auf der Zeichenfläche zu detektieren: void void void void BildMouseDown(int x, int y); BildMouseMove(int x, int y, int leftdown); BildMouseUp(int x, int y); BildMouseClick(void); Diese werden immer dann aufgerufen, wenn der User entsprechende Mausaktionen auf der Zeichenfläche von PLAN durchführt. Über das Funktionsargument erhält man die momentane Position des Mauszeigers sowie die Information, ob die linke Maustaste gedrückt ist (leftdown = 1) oder nicht. Eine sehr einfache Anwendung könnte etwa die Zeichnung eines kleinen Kreises sein als Kennzeichnung der Mauszeigerposition auf der Zeichenfläche von PLAN, wenn die Maustaste gedrückt wird. Zusätzlich kann man die Pixelkoordinaten des Mausklickortes schreiben lassen. //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Programm Mausklick (Kapitel 2.5) // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { public: void BildMouseDown(int x, int y); }; void TUser::BildMouseDown(int x, int y){ Circle(x,y,10); char s[100]; sprintf(s," Mauszeiger: (%d,%d)",x,y); Text(20,20,s);} //==================================================================== Simulation mechatronischer Systeme 55 #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Am Bildschirm wird das folgende Bild ausgegeben: Abbildung 2.14: Programm Mausklick. Etwas sinnvoller ist die Möglichkeit, mit der Maus Handzeichnungen auf dem Bildschirm durchzuführen. Um in einen Zeichenmodus zu schalten, wenn die linke Maustaste gedrückt wird, zu zeichnen, wenn die Maus mit gedrückter Taste bewegt wird und in den Normalmodus zu schalten, wenn die Maustaste losgelassen wird können die oben genannten Funktionen genutzt werden. Im nachfolgend dargestellten Programm sind die Integer-Variablen xalt, yalt definiert worden. Ihnen wird bei jeder Mausbewegung der aktuelle Wert des Mauszeigers zugewiesen. Wenn dann mit gedrückter Maustaste eine Mausbewegung detektiert wird, wird eine Linie von (xalt,yalt) nach (x,y) gezeichnet. Damit sichert man sich vor Überraschungen, denn der interne Mauszeiger kann durch Nebenaktionen wie etwa die Textausgabe verstellt werden. Man kann also nicht unbedingt darauf hoffen, das man zu irgend einem Zeitpunkt in PLAN MoveTo(x,y) 56 Simulation mechatronischer Systeme angibt und diese Position unverändert über längere Zeit gehalten wird. Tatsächlich wird von vielen internen Grafikaktualisierungen diese Funktion genutzt. //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Programm Mausklick 2 (Kapitel 2.5) // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { int xalt, yalt; public: void BildMouseDown(int x, int y); void BildMouseMove(int x, int y, int leftdown); void BildMouseUp(int x, int y); }; void TUser::BildMouseDown(int x, int y){ MoveTo(x,y); xalt = x; yalt = y;} void TUser::BildMouseMove(int x, int y, int leftdown){ if(leftdown) { MoveTo(xalt,yalt); LineTo(x,y); xalt = x; yalt = y;} char s[100]; sprintf(s," Mausposition in Pixeln (%d,%d) ",x,y); Text(20,20,s); } void TUser::BildMouseUp(int x, int y){ MoveTo(xalt,yalt); LineTo(x,y);} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Mit diesem Programm kann man schon freihändige Zeichnungen erstellen. Ein resultierendes Ergebnis ist in Abbildung 2.15 dargestellt. Simulation mechatronischer Systeme 57 Abbildung 2.15: Programm Mausklick 2 Eine immer wieder benötigte Anwendung dieser Mausfunktionen ist etwa die Auswahl eines Bildausschnittes durch den User. Damit kann der User sich in die Animationswelt hineinzoomen. Dies ist insbesondere wertvoll bei komplexen Animationen. Man denke etwa an eine Verkehrssimulation einer Stadt und die Möglichkeit, das Verkehrsgeschehen in nur einer Straße zu beobachten. Hierzu gibt es in PLAN eine vordefinierte Methode: Ausschnitt = true; void AusschnittDefiniert(int x, int y, int w, int h); Wird die Größe Ausschnitt auf true gesetzt, kann der User auf der Zeichenfläche einen Ausschnitt definieren. Dazu braucht er nur mit der Maus, linke Maustaste gedrückt haltend, ein Rechteck zu markieren. Dieses wird dem User im Bild gestrichelt angezeigt (XOR – Modus, Ihre Zeichnungen werden nicht zerstört!). Sobald der User das Rechteck über die Grenzen der Zeichenfläche hinwegsetzen will, oder wenn er ein Rechteck markiert hat, schaltet sich die Option Ausschnitt wieder auf false. Wenn der User einen gültigen Ausschnitt definiert hat, ruft PLAN die Funktion AusschnittDefiniert() auf, in der Sie die aktuellen Rechteckparameter für Ihre Zwecke nutzen können. 58 Simulation mechatronischer Systeme PLAN nutzt, wenn Sie die Größe Ausschnitt auf true setzen, temporär selbst die BildMouse...()-Funktionen, gibt sie aber dann sofort wieder frei. Eine sehr einfache Realisierung ist die nachfolgend beschriebene Bildausschnittsvergrößerung. Nehmen wir an, Sie haben eine Funktion Draw(), die ein beliebig komplexes Bild auf dem Bildschirm darstellt. Alle Zeichenfunktionen von Draw() sollen sich auf eine Skalierung der Bildfläche stützen. Mit Hilfe der Ausschnittfunktion kann man die Bildfläche so umskalieren, dass nur entsprechende Ausschnitte gezeichnet werden. Sie sollten aber dafür sorgen, dass das Bild auch wieder in den Ausgangsmaßstab gesetzt werden kann! Dazu werden hier zwei Tasten eingeführt, die zum einen die AusschnittVergrößerung, und zum anderen die Normalansicht bieten. Die Ausgangsgrößen zur Skalierung des Bildschirms sind die Variablen X0Min, X0Max und Y0Min. Die eigentliche Skalierung findet mit den Variablen XMin, XMax und YMin statt. Zu Anfang und wenn die Taste "Normalsicht" gedrückt wird, werden diese Werte auf die Ausgangswerte X0Min usw. gesetzt. Der nachfolgende Sourcecode definiert in einer Funktion Draw() eine Grafik aus vielen Dreiecken. Dabei nutzt Draw() den Scale() – Befehl mit den globalen Variablen XMin, XMax, YMin, die in der AusschnittDefiniert() – Funktion gesetzt werden, wenn denn die Taste 1 gedrückt und damit diese Funktion von PLAN aufgerufen wird. In dieser Funktion wird auch ein Polygonzug mit den Befehlen void SetPoint(int x, int y); // n – mal void Poly(); void ClearPoints(); gezeichnet, der einen internen Stack für die von Ihnen angegebenen Punkte erzeugt. Dieser Stack kann bei Bedarf sehr viele Punkte aufnehmen und eignet sich für das Zeichnen von offenen und geschlossenen Polygonzügen, dies geschieht mit der Funktion Poly(). Man sehe im Programmierhandbuch nach. Mit der Taste 0 werden die Größen XMin usw. wieder auf ihren Ausgangswert gesetzt und die Zeichnung in Originalgröße wiedergegeben. //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Programm Ausschnitt (Kapitel 2.5) // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" Simulation mechatronischer Systeme class TUser : public TPlan { real X0Min, X0Max, Y0Min; real XMin, XMax, YMin; public: void Init(void); void Draw(void); void AusschnittDefiniert(int x, int y, int w, int h); void RunTaste0(void); void RunTaste1(void); }; void TUser::Init(void){ X0Min = XMin = -1.5; Y0Min = YMin = -1.5; X0Max = XMax = 1.5; TastenfeldName = "Ausschnittveränderung"; InsertTaste(0,"Normalsicht",""); InsertTaste(1,"Ausschnitt",""); Draw();} void TUser::Draw(void){ View(); Clear(); Scale(XMin,XMax,YMin); //=== Definiere zwei Punkte und den Punkt (0.0,0.0); real P1X, P1Y, P2X, P2Y; P1X = 1.0; P1Y = 0.0; P2X = 0.7; P2Y = 0.1; real s,c; for(int i = 0;i<200; i++) { //=== Drehe das oben def. Dreieck s = sin(i*M_PI/100.0); c = cos(i*M_PI/100.0); ClearPoints(); SetPoint(c*P1X-s*P1Y,s*P1X+c*P1Y); SetPoint(c*P2X-s*P2Y,s*P2X+c*P2Y); SetPoint(0.0,0.0); //=== Zeichne mit verschiedenen Farben SetBrush(FarbSkala(((real)i)/2.0)); Poly();}} void TUser::AusschnittDefiniert(int x, int y, int w, int h){ XMin = IntToX(x); XMax = IntToX(x+w); YMin = IntToY(y+h); Draw();} void TUser::RunTaste0(void){ XMin = X0Min; XMax = X0Max; YMin = Y0Min; Draw();} void TUser::RunTaste1(void){ Ausschnitt = true;} 59 60 Simulation mechatronischer Systeme //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Am Bildschirm erscheint die nachfolgende Zeichnung. Abbildung 2.16: Programm Ausschnitt Wird die Taste Ausschnitt gedrückt, wird ausgehend von dem Ort, an dem die Maustaste das erste Mal gedrückt wird, ein Rechteck zu dem aktuellen Mausort gezeichnet, solange die Maustaste gedrückt bleibt. Verlässt man mit der Maus das Zeichenfeld von Plan, passiert nichts. Andernfalls wird die Funktion AusschnittDefiniert() aufgerufen, und es hängt natürlich von Ihnen ab, was Sie da machen wollen. Bei diesem Beispiel kann man sich sehr weit in die Einzelheiten der Figur hineinzoomen. Der Mittelpunkt der Figur ist übrigens selbstähnlich, Sie können sich beliebig in den Mittelpunkt der Figur hineinzoomen und erhalten trotzdem immer dasselbe Bild! Simulation mechatronischer Systeme Abbildung 2.17: Programm Ausschnitt, unten einmal gezoomt. 61 62 Simulation mechatronischer Systeme 3. Spielereien In den nachfolgenden Beispielen sollen erste einfache Anwendungen des vorgestellten Werkzeuges PLAN angeführt werden. Es geht dabei nicht um die mathematisch vollständige Erfassung der auftretenden Phänomene, sondern um einen schnellen Einstieg in Grundfragen des dynamischen Verhaltens von Systemen. Als erstes werden einfache Grafiksimulationen konstruiert. Diese Programme sind die wohl einfachste Form von dynamischen Systemen. Jeder Bildschirmschoner ist von dieser Art. Sie zeigen ohne Ballast die prinzipielle Struktur der Simulation auch komplexer dynamischer Systeme. In Kapitel 3.2 wollen wir einfache interaktive Spiele aufbauen. Auch wenn diese Spielereien wenig physikalisch erscheinen, haben sie aber doch eine entscheidende Rolle bei der Entwicklung dynamischer Systeme in Rechnerwelten gespielt. Auch sollte man sich klar machen, dass eines der ersten und erfolgreichsten Spiele, Pong, erst 1972 veröffentlicht wurde. Hierbei handelte es sich um die Realisierung eines 2dimensionalen Tennis, bzw. Tischtennisspieles, bei dem ein oder zwei Spieler auf dem Bildschirm Schläger bewegen konnten. Diese schienen dann den virtuellen Ball zu steuern. Atari hatte damals das Spiel auf speziellen Spielkonsolen realisiert, die nicht einmal einen Mikrocontroller besaßen. Spätestens seit dem legendären Heimcomputer Commodore-64 ist solche Spezialhardware nicht mehr sinnvoll gewesen. Wenngleich man heute nur müde darüber lächeln mag, so ist gerade dieses Spiel der sensationelle Beginn der heute so selbstverständlichen interaktiven Kommunikation mit dem Rechner gewesen. Solche Spiele sind sehr gut geeignet, eventgesteuerte Programme mit Eigendynamik darzustellen. Man mache sich klar, dass die Programme in den Übungen Sie vor 40 Jahren zum Multimillionär gemacht hätten ! Übrigens waren weitere Meilensteine das Pac-Man Spiel von 1979, Tetris von 1985 und Doom von 1993. All diese Spiele sind auch heute noch aktuell, zur Zeit erobern sie die Handys. Simulation mechatronischer Systeme 3.1 63 Animierte Bilder und Bildschirmschoner In Kapitel 2.2 haben wir ein Rechteck über den Bildschirm laufen lassen. Das dort angegebene Programm hatte natürlich Schönheitsfehler. Das Rechteck lief nach kurzer Zeit aus dem Bildschirm heraus. Um das zu verhindern, muss die Bewegung des Rechteckes bei Berührung des unteren Randes oder der Seitenbegrenzung des Zeichenfensters jeweils umgedreht werden. Hier bietet es sich an, noch zwei weitere Variable für das Inkrementieren der Größen xKoord und yKoord zu definieren, die genau dann das Vorzeichen wechseln, wenn das Rechteck aus dem Bildschirm herauslaufen will. Mit den Variablen dx, dy, definiert im private – Abschnitt der Klasse TUser, initialisiert je mit dem Wert 5, könnte das Programm etwa wie folgt aussehen: //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Bildschirmschoner 1 (Kapitel 3.1) // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { int xKoord, yKoord, dx, dy; bool SpurOn; public: void Init(void); void Run(void); void Reset(void); void RunTaste0(void); }; void TUser::Init(void) { ProgrammName = "Animation"; ProgrammInfo = "Programm zur Demonstration\r" "einer einfachen Animation"; Reset(); TastenfeldName = "Objektspur"; SpurOn = true; RunTaste0(); } void TUser::Run(void){ if(!SpurOn) { SetPen(Weiss); SetBrush(Weiss); Rectangle(xKoord,yKoord,100,100);} xKoord += dx; yKoord += dy; 64 Simulation mechatronischer Systeme SetPen(Schwarz); SetBrush(Hellrot); Rectangle(xKoord,yKoord,100,100); if((xKoord<0)||(xKoord>GetMaxW()-100)) dx *=-1; if((yKoord<0)||(yKoord>GetMaxH()-100)) dy *=-1; CallRun = true;} void TUser::Reset(void) { Clear(); xKoord = 10; yKoord = 10; dx = 5; dy = 5;} void TUser::RunTaste0(void) { SpurOn = !SpurOn; InsertTaste(0,(SpurOn)?"Spur aus":"Spur ein", "Ein- Ausschalten einer Spur");} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08 Die Variablen dx und dy müssen in der Reset()-Funktion wie die Variablen xKoord und yKoord gesetzt werden! Die RunTaste0() schaltet die boolsche Variable SpurOn ein oder aus. Wartet man lange genug, wird mit diesem Programm langsam der gesamte Bildschirm vollgeschrieben. Das entsprechende Bild ist in Abbildung 3.1 dargestellt. In diesem Programm erkennt man auch den Sinn des Abdeckens der Spur des Rechteckes am Eingang der Runfunktion(). Tatsächlich hätte man diese Passage ja auch einfach mit dem Statement if(!SpurOn) Clear(Weiss); erledigen können. Doch dann würde jeweils der gesamte Bildschirm gelöscht werden, wenn die Spur abgeschaltet werden soll. Durch das Neuzeichnen des Rechteckes in weiß lassen sich interessante optische Erscheinungen programmieren. Simulation mechatronischer Systeme 65 Abbildung 3.1.: Bildschirmschoner 1 Interessantere Muster erhält man am Bildschirm, wenn das Rechteck mit Klar ausgefüllt wird, man sieht dann immer auch die darunter liegenden Spuren. Gar selbstähnlich wird das Bild, wenn man etwa 10 mal das Rechteck nicht löscht, dann aber wieder 10 mal löscht. Verzichtet man auf Steuerungen, so ist das Pausenbildprogramm nur noch: //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Bildschirmschoner 2 (Kapitel 3.1) // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { int xKoord, yKoord, dx, dy; 66 Simulation mechatronischer Systeme public: void Init(void); void Run(void); }; void TUser::Init(void) { xKoord = yKoord = 10; dx = dy = 5; SetBrush(Klar);} void TUser::Run(void){ if(!CallRunCount) Zoom = true; if((CallRunCount%20)<10) { SetPen(Weiss); Rectangle(xKoord,yKoord,100,100);} xKoord += dx; yKoord += dy; SetPen(Schwarz); Rectangle(xKoord,yKoord,100,100); if((xKoord<0)||(xKoord>GetMaxW()-100)) dx *=-1; if((yKoord<0)||(yKoord>GetMaxH()-100)) dy *=-1; CallRun = true;} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Im ersten Aufruf der Run() – Funktion wird auf den Zoom- Modus geschaltet. Bitte beachten Sie, das alle Tasten mit den Hotkey's natürlich zur Verfügung stehen. Drücken Sie einfach ? und, wie vorne geschildert, wird ein Hilfefenster Ihnen erläutern, was Sie machen können. Zum Beispiel mit 'x' (Hotkey von Exit) beenden Sie das Programm. Übrigens egal, wie lange Sie das Programm laufen lassen, der Bildschirm scheint sich ständig zu verändern, bleibt aber doch immer sich selbst ähnlich. Probieren Sie es aus! Versuchen Sie einfach mal selbst, einen Bildschirmschoner aufzubauen. Lassen Sie ihre Fantasie spielen. Es ist einfacher, als Sie denken! Simulation mechatronischer Systeme Abbildung 3.2.: Bildschirmschoner 2 67 68 3.2 Simulation mechatronischer Systeme Ping-Pong Spiele Es soll ein einfaches Ping-Pong-Spiel realisiert werden. Das Spielfeld selbst soll durch drei Seiten, oben, rechts und unten gekennzeichnet sein. Die linke Seite bleibt offen. Im Feld soll ein rechteckiger Schläger durch den Mauszeiger geführt werden. Ein Ball kann durch den Schläger in seiner Bahn verändert werden. Der Ball stößt gegen die drei Wände wie auch gegen den Schläger rein elastisch. Sinnvollerweise soll am oberen Bildschirmrand eine Anzeige ermöglicht werden. Man könnte etwa die Anzahl der Bandenstöße oder das Verhältnis von Balltreffern zu Nieten oder ähnliches darstellen. Zunächst soll der Schläger nur vertikal bewegt werden können. Für die Grafik werden einzelne Funktionen aufgebaut. Die Funktion Feld() zeichnet das eigentliche Spielfeld. Man beachte, dass die Umrandung hier durch eine Folge von View() und Clear() Aufrufen realisiert wird. Dies geschieht einfach nur deshalb, um die Strichstärke 5 Pixel als Umrandung des Feldes außerhalb der Feldabmessungen zu erhalten. Natürlich könnte man auch eine Folge von Linien der Stärke 5 zeichnen, muss dann aber die zu verbindenden Punkte 3 Pixel außerhalb des Spielfeldes setzen, um zu verhindern, dass die Strichdicke in das Spielfeld hineinragt. Ball und Schläger müssen sowohl gezeichnet, als auch wieder gelöscht werden. Hierbei bietet es sich an, Funktionen der Form void Ball(bool draw); void Schlaeger(bool draw); zu schreiben, die im Argument mit draw = true das Objekt zeichnen und mit draw = false das Objekt in der Hintergrundfarbe des Spielfeldes zeichnen, das Objekt also löschen. Die Startkoordinaten von Ball und Schläger setzt man am besten in der Reset()Funktion, feste Werte wie Bildgröße oder Schrittweiten in der Init()-Funktion. Nachfolgend eine erste Version des Quellcodes zur Erzeugung des o.a. Spielfeldes //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Ping-Pong (Kapitel 3.2) // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" Simulation mechatronischer Systeme class TUser : public TPlan { int SpielX, SpielY, // Spielfeldgrenzen SpielW,SpielH; int x, y, r, // Ballort, Ballradius dx,dy; // Ballbewegung int SchlagX, SchlagY, // Schlägergrenzen SchlagW, SchlagH; bool BallAus; // Ball hat Spiel verlassen public: void Init(); void Reset(); void Feld(); void Schlaeger(bool draw = true); void Ball(bool draw = true); }; void TUser::Init(){ ProgrammName = "PingPong"; //=== Daten Spielfeld SpielX = SpielY = 100; SpielW = GetMaxW()-110; SpielH = GetMaxH()-110; //=== Daten Schläger SchlagW = 10; SchlagH = 50; //=== Daten Ballort r = 10; Reset();} void TUser::Reset(){ //=== Ball und Schläger SchlagX = SpielX+10; SchlagY = SpielY+50; x = SpielX+20; y = SpielY+20; dx = 3; dy = 5; BallAus = false; Feld(); Schlaeger(); Ball();} void TUser::Feld(){ Clear(); View(SpielX,SpielY-5,SpielW+5,SpielH+10); Clear(Schwarz); View(SpielX,SpielY,SpielW,SpielH); Clear(Hellgruen); } void TUser::Schlaeger(bool draw){ SetPen((draw)?Schwarz:Hellgruen); SetBrush((draw)?Hellgrau:Hellgruen); Rectangle (SchlagX,SchlagY,SchlagW,SchlagH);} 69 70 Simulation mechatronischer Systeme void TUser::Ball(bool draw){ SetPen((draw)?Schwarz:Hellgruen); SetBrush((draw)?Hellrot:Hellgruen); Circle(x,y,r);} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Dieser Programmcode erzeugt das nachfolgende Layout: Abbildung 3.3: Layout des Spielfeldes Durch Drücken der Run()-Funktion soll das Spiel beginnen, d.h. der Ball setzt sich mit vorgegebener Richtung und Schrittweite in Bewegung. Durch einfachen elastischen Stoß soll der Ball von den 3 anderen Spielfeldrändern abprallen. Durch Mausbewegungen soll der Schläger nach oben und unten bewegt werden können, um den Ball zurückzustoßen. Die Run() – Funktion muss nun wieder den Ball bewegen. Dazu wird zur aktuellen Position der Inkrementvektor (dx, dy) dazu addiert. Die Vorzeichen in dx, dy ändern Simulation mechatronischer Systeme 71 sich, wenn der Ball sich über einen der drei Ränder des Spielfeldes oder über den Schläger bewegen würde. Eine erste Version der Run() – Funktion könnte sein: void TUser::Run(){ if(x+r>SpielX+SpielW) dx *=-1; if((y-r<SpielY)||(y+r>SpielY+SpielH)) dy *= -1; if((x-r<SchlagX+SchlagW)&&(!BallAus)) { if((y+r>SchlagY)&&(y-r<SchlagY+SchlagH)) dx *=-1; else BallAus = true; } Ball(false); Feld(); x+=dx; y +=dy; Schlaeger(); Ball(); CallRun = true;} Da tatsächlich der Ball im Rahmen der Schritte dx, dy die Feldgrenzen überschreiten kann, wird hier zunächst auch das Feld jedes Mal neu gezeichnet. Für eine erste Version des Spieles fehlt natürlich noch die Bewegung des Schlägers. Hierzu werden wieder die virtuellen Funktionen BildMouseDown(), BildMouseMove() und BildMouseUp() genutzt. In BildMouseDown() wird zunächst geprüft, ob Sie in die Schlägerfläche geklickt haben, nur dann soll die Maus den Schläger bewegen können! Wenn also der Mauszeiger beim Drücken der linken Maustaste im Schläger ist, wird die boolsche Variable ZeigerAktiv auf true gesetzt. Außerdem wird gespeichert, welchen Abstand der Mauszeiger zur Schlägeroberkante hat. Dies ist wichtig, um sich zu merken, an welcher Stelle der Spieler den Schläger "angefasst" hat. void TUser::BildMouseDown(int x, int y){ if((x>=SchlagX)&&(x<=SchlagX+SchlagW) &&(y>=SchlagY)&&(y<=SchlagY+SchlagH)) ZeigerAktiv = true; if(!ZeigerAktiv) return; yalt = y; yToSchlag = SchlagY-y;} void TUser::BildMouseMove(int x, int y, int leftdown){ //=== Mauszeiger muss im Schlaeger bleiben! if((x<SchlagX)||(x>SchlagX+SchlagW) ||(y<SchlagY)||(y>SchlagY+SchlagH)) { ZeigerAktiv = false; return;} //=== Nur wenn Maustaste gedrückt if(!leftdown)return; Feld(); Ball(); 72 Simulation mechatronischer Systeme SchlagY = y+yToSchlag; if(SchlagY<SpielY) SchlagY = SpielY; if(SchlagY+SchlagH>SpielY+SpielH) SchlagY=SpielY+SpielH-SchlagH; xalt = x; yalt = y; Schlaeger();} void TUser::BildMouseUp(int x, int y){ ZeigerAktiv = false;} Jetzt ist tatsächlich schon ein erstes Spiel möglich. Probieren Sie es aus. Es fällt aber auf, dass der Schläger manchmal dem Mauszeiger nicht so schnell folgt und der Mauszeiger das Schlägerfeld verlässt. Dann bleibt der Schläger stehen und der Ball wird unter Umständen nicht mehr erreicht. Das kann für den Spieler frustrierend wirken, weil hier scheinbar die Hardware sein Geschick behindert. Darum sollte in der Funktion BildMouseMove() die Ortsabfrage aufgehoben werden. Nur beim ersten Anklicken muss sich der Zeiger im Schläger befinden, dann wird der Schläger so lange bewegt, wie die linke Maustaste gedrückt ist. Um das Spiel etwas interessanter zu gestalten, sollte man verschiedene Spielschwierigkeiten einbauen. Eine Schwierigkeitssteigerung ist etwa durch einen größeren Verschiebevektor dx, dy gegeben, eine weitere Schwierigkeit kann dadurch aufgebracht werden, dass die Schlägerhöhe kleiner gewählt wird. Höchstschwierigkeiten hat man im Spiel, wenn der Ball an der Bande nicht mehr kausal funktioniert, sondern mit stochastischen Werten von der Wand abprallt. Dann allerdings ist das Spiel nicht mehr wirklich physikalisch. Nachfolgend ist der Sourcecode des Ping-Pong-Spiels vollständig angegeben. Die oben genannten Schwierigkeitsstufen können per Tastendruck eingestellt werden. //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // PingPong (Kapitel 3.2) // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { int SpielX, SpielY, SpielW,SpielH; int x, y, r, dx,dy; int SchlagX, SchlagY, SchlagW, SchlagH; bool BallAus, ZeigerAktiv; int xalt, yalt, yToSchlag; // Spielfeldgrenzen // Ballort, Ballradius // Ballbewegung // Schlägergrenzen // Mauszeiger relativ zum Schläger Simulation mechatronischer Systeme int Anschlag, Kugel, SpielTyp; public: void Init(); void Run(); void Reset(); void BildMouseDown(int x, int y); void BildMouseMove(int x, int y, int leftdown); void BildMouseUp(int x, int y); void Feld(); void Schlaeger(bool draw = true); void Ball(bool draw = true); void Anzeige(); void RunTaste0(); void RunTaste2(); void RunTaste3(); void RunTaste4(); }; void TUser::Init(){ ProgrammName = "PingPong"; //=== Daten Spielfeld SpielX = SpielY = 100; SpielW = GetMaxW()-110; SpielH = GetMaxH()-110; //=== Daten Schläger SchlagW = 20; SchlagH = 50; //=== Daten Ballort r = 10; Kugel = 0; SpielTyp = 0; InsertTaste(0,"Neuer Spieler",""); InsertTaste(2,"leicht",""); InsertTaste(3,"mittel",""); InsertTaste(4,"schwer",""); RunTaste2();} void TUser::Run(){ real dxalt = dx, dyalt = dy; if(x+r>SpielX+SpielW) dx *=-1; if((y-r<SpielY)||(y+r>SpielY+SpielH)) dy *= -1; if((x-r<SchlagX+SchlagW)&&(!BallAus)) { if((y+r>SchlagY)&&(y-r<SchlagY+SchlagH)) dx *=-1; else BallAus = true; } Ball(false); if((dxalt!=dx)||(dyalt!=dy)) { if(SpielTyp>1){ randomize(); dxalt = rand() %15+1; dyalt = rand() %15+1; dx = (dx<0)?-dxalt:dxalt; dy = (dy<0)?-dyalt:dyalt; } Anschlag++; Anzeige();} Feld(); 73 74 Simulation mechatronischer Systeme x += dx; y +=dy; Schlaeger(); Ball(); CallRun = true;} void TUser::Reset(){ //=== Ball und Schläger SchlagX = SpielX; SchlagY = SpielY+50; x = SpielX+30; y = SpielY+20; dx = abs(dx); dy = abs(dy); Feld(); Schlaeger(); BallAus = false; ZeigerAktiv = false; Anschlag = 0; Kugel++; Ball(); Anzeige();} void TUser::BildMouseDown(int x, int y){ if((x>=SchlagX)&&(x<=SchlagX+SchlagW) &&(y>=SchlagY)&&(y<=SchlagY+SchlagH)) ZeigerAktiv = true; if(!ZeigerAktiv) return; yalt = y; yToSchlag = SchlagY-y;} void TUser::BildMouseMove(int x, int y, int leftdown){ //=== Nur wenn Maustaste gedrückt if(!leftdown)return; Feld(); Ball(); SchlagY = y+yToSchlag; if(SchlagY<SpielY) SchlagY = SpielY; if(SchlagY+SchlagH>SpielY+SpielH) SchlagY=SpielY+SpielH-SchlagH; xalt = x; yalt = y; Schlaeger();} void TUser::BildMouseUp(int x, int y){ ZeigerAktiv = false;} void TUser::Feld(){ View(0,SpielY-10,GetMaxW(),GetMaxH()-SpielY+10); Clear(); View(SpielX,SpielY-5,SpielW+5,SpielH+10); Clear(Schwarz); View(SpielX,SpielY,SpielW,SpielH); Clear(Hellgruen); SetPen(Gruen); SetBrush(Gruen); Rectangle(SpielX,SpielY,20,SpielH);} void TUser::Schlaeger(bool draw){ Simulation mechatronischer Systeme 75 SetPen((draw)?Schwarz:Hellgruen); SetBrush((draw)?Hellgrau:Hellgruen); Rectangle (SchlagX,SchlagY,SchlagW,SchlagH);} void TUser::Ball(bool draw){ SetPen((draw)?Schwarz:Hellgruen); SetBrush((draw)?Hellrot:Hellgruen); Circle(x,y,r);} void TUser::Anzeige(){ View(0,0,GetMaxW(),SpielY-10); Clear(); SetText(Rot,30); SetBrush(Klar); PlanString s; switch(SpielTyp){ case 1: s = "Spiel : mittelschwer"; break; case 2: s = "Spiel : schwer";break; default:s = "Spiel : leicht";break;} Text(100,20,s); s = "Ball Nummer : "; s+= Kugel; Text(100,60,s); s = " Bandenstösse : "; s+= Anschlag; Text(350,60,s);} void TUser::RunTaste0(){ Kugel = 0; Reset();} void TUser::RunTaste2(){ SpielTyp = 0; dx = 5; dy = 3; SchlagH = 50; Reset();} void TUser::RunTaste3(){ SpielTyp = 1; SchlagH = 30; dx = 8; dy = 3; Reset();} void TUser::RunTaste4(){ SpielTyp = 2; SchlagH = 30; dx = 8; dy = 6;} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Am Bildschirm zeigt sich das Programm in folgender Aufmachung: 76 Simulation mechatronischer Systeme Abbildung 3.4: Das Spiel Ping-Pong Als Aufgabe bietet es sich an, das Spiel zu verbessern, indem einerseits die Wandbedingungen genau eingehalten werden. Man beachte, das hier bis jetzt die Wand die Unschärfe dx, dy hatte! Andererseits sollten die Stöße nicht voll elastisch angenommen werden und die Kugel darüber hinaus auch einen Drehfreiheitsgrad bekommen, der in Verbindung mit Reibung das Stoßgeschehen wesentlich beeinflusst. Auch kann man über ein Billardspiel nachdenken, bei dem dann aber auch ein Queue bewegt werden muss. Aber völlig unabhängig von dem was Sie an Grafik investieren, das Spiel wird unbefriedigend sein, weil es eben keine dynamische Simulation sondern nur eine grafische Animation ist! Vielleicht kennen Sie Filme über Dinosaurier, die in zur Zeit ja sehr angesagten populärwissenschaftlichen Darstellungen fröhlich über die virtuellen Frühzeitlandschaften hüpfen. Wenn Sie die Massen dieser Größenordnung in einfachen mechanischen Schwingungsmodellen berechnet haben, werden Sie feststellen, dass diese großen Viecher am Bildschirm rein geometrisch – völlig der Fantasie der Computergrafiker ausgesetzt – animiert wurden. Leider werden Sie sehr viele Beispiele von suggestiven Grafikanimationen in unserer Welt als Wirklichkeitsersatz angeboten bekommen. Sie werden die Aufgabe haben, ob Sie wollen oder nicht, die richtigen Schlüsse daraus zu ziehen! Simulation mechatronischer Systeme 77 4. Folgen und Reihen Nach den rein grafischen Animationen sollen jetzt erste mathematische Objekte genauer beleuchtet werden. Zunächst werden Folgen, hier insbesondere einfache Nullstelleniterationen, betrachtet. Geht man an die Konvergenzgrenzen – siehe Banach' scher Fixpunktsatz – so sind merkwürdige Phänomene wie etwa der Übergang ins "Chaos" zu beobachten. Wir werden ein einfaches Feigenbaumdiagramm ermitteln und den Übergang ins "Chaos" simulieren. Betrachtet man Iterationsfolgen in höherdimensionalen Räumen, sind die Konvergenzgrenzen entsprechend komplexer. Hier betrachten wir ein komplexes, also zweidimensionales Beispiel, dessen Konvergenzgrenzen im zweidimensionalen Raum die erstmals von Mandelbrot aufgezeigte "Apfelmännchen"-Struktur aufweist. Zum Schluss dieses Kapitels werden noch Visualisierungen der Taylorreihenapproximation an verschiedene Funktionen aufgezeigt. Dies einfach nur deshalb, weil die Linearisierung eine der mächtigsten Werkzeuge auf dem Weg zum Verständnis nichtlinearer dynamischer Systeme ist. 4.1 Iterationen Zunächst soll eine einfache Nullstelleniteration betrachtet werden. Das Prinzip ist einfach. Gegeben sei eine hinreichend glatte Funktion f(x), von der wir eine Nullstelle suchen. Die Folge x n mit xn 1 f xn konvergiert sicher gegen einen Fixpunkt von f, wenn f x 1 gilt. Dann gilt also für genügend großes n: xn+1 = xn . Betrachten wir die Funktion 78 Simulation mechatronischer Systeme gx k x x2 mit k 1 . Es handelt sich um eine nach unten geöffnete Parabel, die bei x = 0 und x = 1 eine Nullstelle unabhängig vom aktuellen Wert von k besitzt. Für die Fixpunktiteration x f x x gx x k x x2 folgt aus der Konvergenzbedingung, dass der Fixpunkt x = 0 nicht erreicht werden kann und der Fixpunkt x = 1, das ist eine Nullstelle von g(x), attraktiv nur für k < 2 ist. Für k >2 ist die eindeutige Konvergenz zwar nicht mehr gegeben, aber es wird sich ein außerordentlich komplexes Iterationsverhalten ergeben, das von der eindeutigen Lösung zunächst zwei, dann vier, acht und so weiter lösungsähnliche Werte liefert. Hier soll diese Fixpunktiteration als Plot dargestellt werden. Die wesentlichen Daten der Funktion f(x) sind sicher im Intervall [0.0,2.0] zu erwarten. Außerhalb dieses Intervalls ist die Funktion f(x) sicher negativ. Das Maximum von f(x,k) ist für Werte k mit k>1 und k<3 im oben genannten Intervall von x sicher kleiner als 1.5. Um diese Iteration zu verfolgen, soll immer vom Startwert x0 = 0.1 ausgegangen werden. In der Funktion Init() wird zunächst das Plot definiert: void TUser::Init(){ Plot0->Size(); Plot0->Titel = "Fixpunktiteration"; Plot0->Untertitel = "x = f(x) mit x + k*x*(1 - x)"; Plot0->Achse(0,0.0,2.0); Plot0->Achse(0,"x - Koordinate"); Plot0->Achse(1,0.0,1.5); Plot0->Achse(1,"Funktion f(x)"); } Um die Iteration sinnvoll darstellen zu können, wird zunächst die Funktion y = f(x) im Plot dargestellt sowie die Hilfsgerade y = x. Dann geht man vom gewählten Startwert x0 aus und berechnet f(x). Dieser Wert wird dann das neue x1. Sukzessive nähert man sich so dem Fixpunkt. Dieser Weg soll als dritte Kurve im Plot dargestellt werden. Es bietet sich also an, drei Kurventypen im Plot zu definieren. Entsprechend wird die Init() – Funktion erweitert um Plot0->Kurve0->SetPen(Schwarz,3); Plot0->Kurve1->SetPen(Hellgrau,3); Plot0->Kurve2->SetPen(Hellrot); // Grafik y = f(x) // Hilfsgerade y = x // Iterationsschritte Um die Funktionswerte der Funktion f(x) bereit zu halten, bietet es sich an, diese als eigene Funktion real f(real x); Simulation mechatronischer Systeme 79 in der Klasse TUser zu definieren. Die Funktion selbst kann dann so aussehen real TUser::f(real x){ return x + k*x*(1.0 - x);} Die Größe k wird in der Klasse TUser definiert, um sie dann in allen Funktionen der Klasse verfügbar zu haben und dort diese Größe zu variieren. Das Zeichnen der Funktion y = f(x) und der Hilfsgeraden y = x kann man in einer Funktion Draw() ablegen. Diese sorgt erst mal mit dem Aufruf von Plot0->Reset(); für ein Aufräumen im Plot und Löschen des Plotinhaltes. Die eigentliche Iteration wird in der Run() – Funktion durchgeführt. Dort sollen die Iterationen dargestellt werden. Eine erste Version von Run() könnte sein: void TUser::Run(){ Draw(); int i; real x = 0.1; Plot0->Kurve2->MoveTo(x,0.0); for(i = 0; i<150; i++){ Plot0->Kurve2->LineTo(x,f(x)); x = f(x); Plot0->Kurve2->LineTo(x,x);}} Nun macht es eigentlich keine Schwierigkeiten mehr, bei jedem Aufruf von Run() den Parameter k von k = 1.0 bis etwa k = 3.0 laufen zu lassen. Man kann in der Run() - Funktion k um 0.01 inkrementieren und CallRun solange auf true setzen, wie k kleiner als 3 ist. Man beachte, dass bei so vielen Iterationen pro Run() – Aufruf die numerischen Werte der Iteration aus dem Bereich der darstellbaren Zahlen laufen können. Es macht Sinn, für x < 0 die Iteration abzubrechen. In der Reset() – Funktion wird der Anfangswert von k festgeschrieben. Die Reset() – Funktion selbst wird dann auch in der Init() – Funktion aufgerufen, um k zu Beginn auch einen Wert zuzuweisen. Der Source Code ist jetzt also: //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== 80 Simulation mechatronischer Systeme // Fixpunktiteration // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { real k; public: void Init(); real f(real x); void Draw(); void Run(); void Reset(); }; void TUser::Init(){ Plot0->Size(); Plot0->Titel = "Fixpunktiteration"; Plot0->Untertitel = "x = f(x) mit x Plot0->Achse(0,0.0,2.0); Plot0->Achse(0,"x - Koordinate"); Plot0->Achse(1,0.0,1.5); Plot0->Achse(1,"Funktion f(x)"); Plot0->Kurve0->SetPen(Schwarz,3); Plot0->Kurve1->SetPen(Hellgrau,3); Plot0->Kurve2->SetPen(Hellrot); Reset(); } real TUser::f(real x){ return x + k*x*(1.0 - x);} void TUser::Draw(){ real x; Plot0->Reset(); Plot0->Kurve0->MoveTo(0.0,f(0.0)); for(x=0.02; x<=2.0; x+=0.02) Plot0->Kurve0->LineTo(x,f(x)); Plot0->Kurve1->MoveTo(-0.1,-0.1); Plot0->Kurve1->LineTo(2.1,2.1); } void TUser::Run(){ Draw(); int i; real x = 0.1; Plot0->Kurve2->MoveTo(x,0.0); for(i=0;i<250;i++){ Plot0->Kurve2->LineTo(x,f(x)); x = f(x); if(x<0.0) break; Plot0->Kurve2->LineTo(x,x);} k+=0.005; CallRun = (k<3.0); + k*x*(1 - x)"; // Grafik y = f(x) // Hilfsgerade y = x // Iterationsschritte Simulation mechatronischer Systeme 81 } void TUser::Reset(){ k = 1.0; Draw();} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Abbildung 4.1: Layout des Programms Fixpunktiteration In Abbildung 4.1 ist der Parameter k etwa gleich 1.9. Sinnvoll ist es sicherlich, den aktuellen Wert von k explizit mit anzugeben. 82 Simulation mechatronischer Systeme 4.2 Feigenbaumdiagramme Die Iteration aus Kapitel 4.1 zeigt, dass bei k 2 der attraktive Fixpunkt x = 1, gegen den die Funktionswerte von f streben, seine Stabilität verliert. Für etwas größere Parameterwerte k gibt es eine Folge von Lösungspunkten der Iteration, zunächst mit der Periode zwei. Für k > 2 treten also zunächst, wenn i hinreichend groß ist, Iterationspunkte in xi f xi 1 auf mit xi xi 1 aber x i 2 xi . Hinreichend groß für die numerische Darstellung ist i bei dieser Fixpunktiteration etwa bei i = 50. Wird nun k weiter erhöht, erhält man vier – periodische, dann acht periodische Lösungen und so weiter. Diese Fixpunkte kann man über k auftragen. Man erhält dann ein sehr berühmtes Diagramm, das auf Feigenbaum zurückgeht. Mathematisch ist dieses Diagramm eines von drei generischen Modellen in das Chaos. Der Weg im Feigenbaum ist die Periodenverdopplung. Hier soll zunächst parallel zur Fixpunktiteration des vorhergehenden Unterkapitels ein solches Feigenbaumdiagramm erstellt werden. Am einfachsten ist es sicherlich, auf das Plot0 mit der Iteration ein zweites Plot, es sei Plot1, zu legen, in das diese Fixpunkte über k eingetragen werden. Das Plot wird ebenfalls in der Init() – Funktion nach der Definition von Plot0 definiert. Über der Zeichenfläche von PLAN liegt nun zunächst Plot0 und über Plot0 liegt nun das Plot1. Plot1 Plot0 PLAN Zeichenfläche Abbildung 4.2: Übereinanderliegende Plots auf der Zeichenfläche Wenn Sie jetzt Plot1 unsichtbar machen, können Sie auf Plot0 schauen und wird auch dieses Plot unsichtbar gemacht, sehen Sie direkt auf die Zeichenfläche von PLAN. Auf allen drei Ebenen können Sie völlig unabhängig zeichnen und arbeiten. Simulation mechatronischer Systeme 83 Diese Arbeitsblätter werden korrekt gefüllt auch unabhängig vom jeweiligen Zustand sichtbar oder unsichtbar. Das Plot1 soll also durch eine Taste sichtbar oder unsichtbar geschaltet werden. Diese Taste sei die Taste0, und die zugehörige RunTaste0() – Funktion könnte eine Anweisung der Form Plot1->Visible = !Plot1->Visible; enthalten. Damit sollte der nachfolgende Code keine Probleme bereiten. //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Fixpunktiteration // und Feigenbaumdiagramm //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser: public TPlan { real k; public: void Init(void); real F(real x); void Draw(void); void Run(void); void Reset(void); void RunTaste0(void); }; void TUser::Init(void){ ProgrammName = "Iteration"; ProgrammInfo = "\t\tFixpunktiteration \r\r" "Es wird eine Fixpunktiteration " "durchgeführt mit der Gleichung:\r\r" "\tx = f(x), mit f(x) = x + k*x*(1-x).\r\r" "Der Startwert der Iteration ist x = 0.1.\r" "Es werden zwei Plots dargestellt, " "die mit der Taste <Feigenbaum>\r" "umgeschaltet werden können.\r" "Ein Plot zeigt die Funktion f(x) - schwarz" " - und die Iterationsfolge rot.\r" "Das zweite Plot zeigt den Wert der Iteration " "in Abhängigkeit von dem \r" "Strukturparameter k in der Funktion f(x).\r\r" "Mit der Taste <Run> wird die Iteration gezeigt, " "wobei der Strukturparameter k\r" "langsam variiert wird.\r" "Es kann dabei beliebig zwischen den Plots hin- " 84 Simulation mechatronischer Systeme "und hergeschaltet werden,\r" "oder die Simulation angehalten werden\r\r" "Das zweite Plot zeigt das berühmte Feigenbaum" "diagramm, welches einen Weg\r" "ins Chaos durch Periodenverdopplung aufzeigt\r"; Plot0->Size(0,0,GetMaxW(),GetMaxH()-50); Plot0->Titel = "Fixpunktiteration"; Plot0->Untertitel = "x = x + k*x*(1-x)"; Plot0->Achse(0,-0.1,2.0); Plot0->Achse(1,-0.1,1.5); Plot0->Kurve0->SetPen(Schwarz,3); Plot0->Kurve1->SetPen(Hellgrau,3); Plot0->Kurve2->SetPen(Hellrot); Plot1->Size(0,0,GetMaxW(),GetMaxH()-50); Plot1->Titel = "Feigenbaumdiagramm"; Plot1->Untertitel = "x = x + k*x*(1-x)"; Plot1->Achse(0,1.0,3.0); Plot1->Achse(0,"Parameter k"); Plot1->Achse(1,0.0,2.0); Plot1->Achse(1,"Fixpunkte"); Plot1->Kurve0->SetPen(Schwarz); Plot1->Visible = true; RunTaste0(); k = 1.0; Draw(); } real TUser::F(real x){ return x+k*x*(1.0-x);} void TUser::Draw(void){ real x = -0.5; Plot0->Reset(); Plot0->Kurve0->MoveTo(x,F(x)); while(x<2.0) { x+=0.05; Plot0->Kurve0->LineTo(x,F(x));} Plot0->Kurve1->MoveTo(0.0,0.0); Plot0->Kurve1->LineTo(2.0,2.0); PlanString s = "Kurvenparameter k = "; s+=k; Text(30,GetMaxH()-20,s);} void TUser::Run(void){ int i; real x = 0.1; Draw(); Plot0->Kurve2->MoveTo(x,0.0); for(i=0;i<150;i++) { Plot0->Kurve2->LineTo(x,F(x)); x = F(x); if(x<0.0) break; Plot0->Kurve2->LineTo(x,x); if(i>50) Plot1->Kurve0->Point(k,x);} k += 0.001; CallRun = (k<3.0)?true:false;} Simulation mechatronischer Systeme 85 void TUser::Reset(void){ k = 1.0; Plot1->Reset(); Draw();} void TUser::RunTaste0(void){ Plot1->Visible = !Plot1->Visible; DeleteTaste(0); if(Plot1->Visible) InsertTaste(0,"Iteration",""); else InsertTaste(0,"Feigenbaum",""); } //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Die nachfolgenden Bilder zeigen einige der Lösungen auf. Abbildung 4.3: Iteration, k = 1.925 Abbildung 4.4: Iteration und Feigenbaumdiagramm, k = 2.059 86 Simulation mechatronischer Systeme Abbildung 4.5: Iteration und Feigenbaumdiagramm, k = 2.472 Abbildung 4.6: Iteration und Feigenbaumdiagramm, k = 2.855 Abbildung 4.7: Iteration und Feigenbaumdiagramm, k = 3.000 Simulation mechatronischer Systeme 4.3 87 Mandelbrot- und Juliamengen Eine zweidimensionale Fixpunktiteration ist durch die Gleichung zi 1 z2 c gegeben, worin z und c komplexe Größen sind. Wir wollen hier mit dem Startwert z0 = 0 + i*0 beginnen und mit dem Kontrollparameter c jeden Punkt der komplexen Ebene im Bereich (-2.0,1.0) x i (-1.2,1.2) beschreiben. Für jedes so gewählte c ermitteln wir, ob die Iterationsfolge in z beschränkt bleibt oder nicht. Dazu führen wir die Iteration 200 mal aus. Wenn nun die Iterationsfolge beschränkt bleibt, wird der entsprechende Punkt c in der Ebene mit zum Beispiel Schwarz gekennzeichnet, alle anderen Punkte werden weiß oder in Abhängigkeit ihres Divergenzverhaltens mit einer entsprechenden Farbabstufung dargestellt. Da die Abtastung jedes einzelnen Pixels mit jeweils einer Iterationsfolge beaufschlagt ist, kann die Erstellung des Bildes etwas dauern. Um die Zeit, wo am Bildschirm nichts passiert, abzukürzen, bietet es sich an, den Bildschirm in Streifen zu teilen, jeweils den Streifen nach obiger Vorschrift zu bearbeiten und dann Windows zu bitten, erst mal den Streifen zu zeichnen. Das sich einstellende Bild zeigt das sogenannte "Apfelmännchen". Diese Menge ist die Menge der Punkte c, für den die Iterationsfolge beschränkt bleibt. Die unendlich schmale Linie zwischen den Punkten c, für die z konvergent oder nicht konvergent ist, nennt man Juliamenge. Sie schließen die zusammenhängende Menge der Punkte c mit konvergentem z, die sogenannte Mandelbrotmenge, ein. Die Apfelmännchen, spezieller Teil der Mandelbrotmenge, findet man auf beliebigen Größenskalen wieder. Darum macht es Sinn, sich in das Gebiet hineinzuzoomen, um im größeren Detail Teilmengen der oben genannten berechnen zu können. Dazu kann man PLAN um Hilfe bitten. Wenn die Größe Ausschnitt, zum Beispiel über einen Tastendruck, zu Ausschnitt = true gesetzt wird, übernimmt PLAN die Funktionen BildMouseDown(), BildMouseMove() und BildMouseUp() kurzzeitig, um Ihnen die Möglichkeit zu geben, mit der Maus ein Rechteck auf dem Bildschirm zu kennzeichnen. Wenn Sie die rechte Maustaste drücken, wird ein Punkt markiert und mit der Bewegung der Maus mit gedrückter Taste ein entsprechendes Rechteck gekennzeichnet. Anschlie- 88 Simulation mechatronischer Systeme ßend werden die oben genannten Mausfunktionen wieder freigegeben. Wenn erfolgreich ein Rechteck definiert wurde, und nur dann, ruft PLAN die virtuelle Funktion void AusschnittDefiniert(int x, int y, int w, int h); auf. Diese können Sie in ihrer Klasse definieren, und Sie können etwa die zu den Pixelwerten x,y,w,h gehörigen reellen Skalenwerte berechnen und mit einem neuen Scale() – Aufruf die Zeichenfläche entsprechend neu skalieren. Einfachste Form ist etwa void TUser::AusschnittDefiniert(int x, int y, int w, int h){ Reset(); Scale(IntToX(x),IntToX(x+w),IntToY(y+h));} Bitte beachten Sie, dass man sinnvoller weise auch noch eine Taste einbaut, die den Ausgangsskalenbereich wieder einstellt. Nachfolgend ist der Source Code angegeben, er sollte keine Schwierigkeiten bereiten. //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Mandelbrotmenge (Apfelmännchen) und Juliamengen // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" //==================================================================== // Klasse TUser //==================================================================== class TUser : public TPlan { long double zx, zy, cx, cy, zdum; int DeltaH, Abschnitt, Layout; public: void Init(void); void Run(void); void Reset(void); void AusschnittDefiniert(int x, int y, int w, int h); void RunTaste0(void); void RunTaste1(void); void RunTaste4(void); void RunTaste5(void); void RunTaste6(void); }; Simulation mechatronischer Systeme void TUser::Init(void){ ProgrammName = "Mandelbrotmenge"; TastenfeldName = "Ausschnittvergrösserung"; ProgrammInfo = "Mit diesem Programm wird das sogenannte " "Apfelmännchen dargestellt.\r\r" "Das Apfelmännchen stellt die Punkte c" " im Raum dar, für die\r" "die komplexe Gleichung:\r" "z = z**2 + c \r" "konvergiert.\r\r" "Man kann sich in die Figur " "hineinzoomen, aber man beachte\r" "die numerische Auflösung."; //=== Tasten für Ausschnittwahl InsertTaste(0,"Ausschnitt", "Bitte mit Maus Ausschnitt wählen"); InsertTaste(1,"Normal", "Stellt Ausgangsskalierung wieder her"); //=== Layoutwahl Layout = 1; InsertTaste(4,"Layout 1",""); InsertTaste(5,"Layout 2",""); InsertTaste(6,"Layout 3",""); //=== Einteilung des Bildschirms DeltaH = GetMaxH()/20; RunTaste1();} void TUser :: Run(void){ //=== beim ersten Abschnitt Textausgabe if(Abschnitt>20) Abschnitt = 0; if(Abschnitt==0){ SetBrush(Hellgelb); SetText(Schwarz); TextRect(GetMaxW()-200,GetMaxH()-30,200,30, " Bitte einen Moment ...");} int jmin = Abschnitt*DeltaH; //=== nächster Abschnitt Abschnitt++; //=== Iteration mit long double int i,j,count; real g,h; long double dum; for(j=jmin;j<jmin+DeltaH;j++) { for(i=0;i<GetMaxW();i++) { zx = zy = 0.0; cx = (long double)IntToX(i); cy = (long double)IntToY(j); count = 0; while ((zx*zx+zy*zy) < 4.0 && count < 201){ 89 90 Simulation mechatronischer Systeme dum = zx*zx - zy*zy + cx; zy = 2.0*zx*zy + cy; zx = dum; count++;} //=== Grafik if (count >=200) SetPixel(i,j,(Layout==2)?Weiss:Schwarz); else { if(Layout==1) SetPixel(i,j,RGBSkala(100.,100.,100.)); if(Layout==2) SetPixel(i,j,RGBSkala(100-count/2, 100-count/2,100-count/2)); if(Layout==3) SetPixel(i,j,RGBSkala(count/2, count/2,count/2));}}} //=== bei fertigem Bild aktuelle Skalenlänge ausgeben if(Abschnitt>20){ PlanString s = " Skalenvergrösserung 1 : "; real z = 3.0/(IntToX(GetMaxW()) - IntToX(0)); s += z; TextRect(GetMaxW()-200,GetMaxH()-30,200,30,s);} //=== letzter Abschnitt erledigt? CallRun = (Abschnitt>20)?false:true; } void TUser::Reset(void){ View(); Clear(); Abschnitt=0;} void TUser::AusschnittDefiniert(int x, int y, int w, int h){ Reset(); Scale(IntToX(x),IntToX(x+w),IntToY(y+h));} void TUser::RunTaste0(void){ Ausschnitt = true;} void TUser::RunTaste1(void){ Scale(-2.0,1.0,-1.2); Reset(); } void TUser::RunTaste4(void){ Layout = 1; } void TUser::RunTaste5(void){ Layout = 2; } void TUser::RunTaste6(void){ Layout = 3; } //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Simulation mechatronischer Systeme 91 Am Bildschirm entstehen sehr komplexe Gebilde. Abb. 4.9 Abbildung 4.8: Skalenvergrößerung 1 : 1 Abb. 4.10 Abbildung 4.9: Skalenvergrößerung 1 : 18.8 92 Simulation mechatronischer Systeme Abbildung 4.10: Skalenvergrößerung 1 : 422 Probieren Sie das Programm aus. Beachten Sie, dass ein Skalenfaktor von 1 : 1.000.000 wohl ungefähr die Grenze der numerischen Auflösung darstellt, die aufgrund der Rundungsfehler in der Iterationskette doch erstaunlich niedrig ist. Simulation mechatronischer Systeme 4.4 93 Taylorreihen Eine der bekanntesten Reihendarstellungen von Funktionen sind Taylorreihen. Sie dienen bei hinreichend oft differenzierbaren Funktionen auch als Mittel einer Approximation, das ist der Aufbau einer Stellvertreterfunktion mit hinreichend ähnlichem Verhalten. Letzteres gilt im Allgemeinen nur in kleinen Umgebungen des sogenannten Entwicklungspunktes x0. Die häufigste Approximation ist die lineare Näherung. Nachfolgend ist ein einfaches Demonstrationsprogramm angegeben, mit dem die Approximationsgüte optisch sichtbar gemacht wird durch einfachen Vergleich von Ausgangsfunktion und Taylorreihen unterschiedlicher Länge. Approximiert wird die sin() – Funktion. Der nachfolgende Code nutzt die Techniken, wie in Kap. 4.1 dargestellt, in zwei Plots die Approximation und den Fehler, d.i. die Differenz zwischen Approximation und der Ausgangsfunktion, darzustellen. //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Taylorreihenentwicklung an sin(x) // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { int Ordnung; public: void Init(); void Draw(); real F(real x); real FTaylor(real x); void RunTaste0(); void RunTaste4(); void RunTaste5(); void RunTaste6(); void RunTaste7(); void RunTaste8(); void RunTaste9(); }; void TUser::Init(){ ProgrammName = "Taylorreihenentwicklung"; ProgrammInfo = "Es werden Taylorreihen" " unterschiedlicher Ordnung angezeigt.\n"; TastenfeldName = "Bitte wählen Sie"; Plot0->Size(); Plot0->Titel = "Taylorreihenapproximation"; 94 Simulation mechatronischer Systeme Plot0->Untertitel = "Ordnung = 1"; Plot0->Achse(0,-10.0,10.0); Plot0->Achse(1,-1.5,1.5); Plot0->Kurve0->SetPen(Schwarz,3); Plot0->Kurve1->SetPen(Hellrot,3); Plot1->Size(); Plot1->Titel = "Fehler der Taylorreihenapproximation" " (Rot: Fehler < 0.01)"; Plot1->Untertitel = "Ordnung = 1"; Plot1->Achse(0,-10.0,10.0); Plot1->Achse(1,-1.0,1.0); Plot1->Kurve0->SetPen(Schwarz,3); Plot1->Kurve1->SetPen(Hellgruen,3); Ordnung = 1; RunTaste0(); InsertTaste(4,"Ordnung 1",""); InsertTaste(5,"Ordnung 3",""); InsertTaste(6,"Ordnung 5",""); InsertTaste(7,"Ordnung 7",""); InsertTaste(8,"Ordnung 9",""); InsertTaste(9,"Ordnung 11",""); Draw();} void TUser::Draw(){ PlanString s = "Ordnung = "; s +=Ordnung; Plot0->Untertitel = s; Plot0->Reset(); Plot1->Untertitel = s; Plot1->Reset(); real x = -10.0, y1, y2; while(x<10.0){ y1 = F(x); y2 = FTaylor(x); Plot0->Kurve0->LineTo(x,y1); Plot0->Kurve1->LineTo(x,y2); Plot1->Kurve0->LineTo(x,y1); y1 -= y2; if(fabs(y1)<=0.01) Plot1->Kurve1->SetPen(Hellrot,3); else Plot1->Kurve1->SetPen(Hellgruen,3); Plot1->Kurve1->LineTo(x,y1); x +=0.1;}} real TUser::F(real x){ return sin(x);} real TUser::FTaylor(real x){ real wert = x; real fak = 1.0, xpotenz = x; if(Ordnung == 1) return wert; fak *= 2.0*3.0; xpotenz *= x*x; wert -= xpotenz/fak; if(Ordnung == 3) return wert; Simulation mechatronischer Systeme 95 fak *= 4.0*5.0; xpotenz *= x*x; wert += xpotenz/fak; if(Ordnung == 5) return wert; fak *= 6.0*7.0; xpotenz *= x*x; wert -= xpotenz/fak; if(Ordnung == 7) return wert; fak *= 8.0*9.0; xpotenz *= x*x; wert += xpotenz/fak; if(Ordnung == 9) return wert; fak *= 10.0*11.0; xpotenz *= x*x; wert -= xpotenz/fak; if(Ordnung == 11) return wert; fak *=12.0*13.0; xpotenz *= x*x; wert += xpotenz/fak; return wert; } void TUser::RunTaste0(){ Plot1->Visible = !Plot1->Visible; InsertTaste(0,Plot1->Visible?"Approx.":"Fehler","");} void TUser::RunTaste4(){ Ordnung = 1; Draw();} void TUser::RunTaste5(){ Ordnung = 3; Draw();} void TUser::RunTaste6(){ Ordnung = 5; Draw();} void TUser::RunTaste7(){ Ordnung = 7; Draw();} void TUser::RunTaste8(){ Ordnung = 9; Draw();} void TUser::RunTaste9(){ Ordnung = 11; Draw();} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ 96 Simulation mechatronischer Systeme Abbildung 4.11: Approximation und Fehler, Taylorpotenzreihe bis x1. Abbildung 4.12: Approximation und Fehler, Taylorpotenzreihe bis x5. Simulation mechatronischer Systeme 97 5. Zelluläre Automaten Zelluläre Automaten sind mathematische Idealisierungen von Systemen, in denen in der Regel Raum und Zeit diskret abgebildet werden. Sie wurden erstmals von Stanislaw Ulam um 1940 in Los Alamos vorgestellt. Gewöhnlich besteht ein zellulärer Automat aus einer Menge von Zellen. Jede Zelle kann eine endliche oder unendliche Zahl von Zuständen annehmen und hat eine begrenzte Zahl von Nachbarzellen. In jedem Zeitschritt wird der neue Zustand einer Zelle durch den eigenen wie auch den Zustand seiner Nachbarzellen bestimmt. Dies geschieht über vorgegebene Regeln oder Bilanzgleichungen. Im allgemeinen werden alle Zellen mit diesen Übergangsregeln pro Zeitschritt erneuert. Dadurch ändert sich das Muster des gesamten zellulären Automaten. Diese Übergangsregeln können deterministischer oder stochastischer Natur sein. Theoretisch kann der Zellraum des Automaten beliebig viele Dimensionen besitzen. In der Praxis treten allerdings mehrheitlich ein und zweidimensionale Strukturen auf, die wir hier auf Vektoren oder matrixähnliche Strukturen abbilden. Übliche Nachbarschaftsregeln für einen beliebigen Zellraum sind die MooreNachbarschaft und die von-Neumann-Nachbarschaft sowie deren Erweiterungen. von Neumann Nachbarschaft: Ni , j k, l L k i l j 1 . Moore' sche Nachbarschaft: Ni , j k, l L k i 1 and l j 1 . von Neumann mit erweiterter Nachbarschaft: Ni , j k, l L k i l j r . Moore mit erweiterter Nachbarschaft: Ni , j k, l L k i r and l j r . 98 Simulation mechatronischer Systeme Natürlich können auch völlig andere Nachbarschaften definiert werden, so lange sie für alle Zellen identisch und endlich sind. Die o.a. Nachbarschaften sind für zweidimensionale Automaten in Abbildung 5.1 dargestellt. von Neumann r= 1 Moore r= 2 r= 1 willkürlich r= 2 Abbildung 5.1: Beispiele für verschiedene Nachbarschaften in zweidimensionalen Automaten. In den nachfolgenden Unterkapiteln werden zunächst eindimensionale Zelluläre Automaten betrachtet. Der wohl einfachste Automat ist in 5.1 beschrieben und produziert das sogenannte Sierpinski – Dreieck. Kapitel 5.2 betrachtet ebenfalls einen eindimensionalen zellulären Automaten, der als einfaches Modell des Straßenverkehrs dienen kann. Die entsprechenden Regeln sind von Nagel und Schreckenberg formuliert worden. Anschließend betrachten wir zweidimensionale Automaten. Am bekanntesten ist wohl das Spiel des Lebens, von Conway 1970 eingeführt. Auf ähnlichen Regeln beruht auch die Beschreibung elektrischer Digitalschaltkreise, die Elemente der Wireworld – Computer sind. Tatsächlich sind auch die meisten Filtertechniken der Bilddatenverarbeitung Regeln eines zweidimensionalen Automaten. Man sehe mal in die Literatur. Simulation mechatronischer Systeme 99 5.1 Sierpinski - Dreiecke Betrachtet wird ein eindimensionaler Zellulärer Automat mit n Zellen. Die Zellen seien in einer Reihe angeordnet, sodass jede Zelle zwei direkte Nachbarn hat. Jede Zelle kann zwei Zustände annehmen, die mit Null oder Eins gekennzeichnet werden. Im Ausgangszustand ist genau eine Zelle im Zustand Eins, alle anderen sind Null. Es gelte nun folgende Regel für den Übergang in den nächsten Zeitpunkt: Ist zn(t) der Zustand der Zelle n zum Zeitpunkt t, so gelte zum Zeitpunkt t+1: zn(t+1) = ((zn-1(t)+zn+1(t)) modulo 2. Also nur dann, wenn genau eine der beiden direkten Nachbarzellen den Zustand 1 hat, wird der Zustand der betrachteten Zelle zum Zeitpunkt t+1 zu 1, andernfalls zu Null, unabhängig vom Zustand der Zelle zum Zeitpunkt t. Um diesen Automaten zu simulieren, werden folgende Schritte durchgeführt. Zunächst werden zwei Felder, hier jeweils als Integerfeld, realisiert. int Feld[500], HilfsFeld[500]; Dabei hält Feld den aktuellen Stand des Automaten, das Hilfsfeld wird nur zur Aktualisierung benötigt. Hier werden entsprechend der Regel die neuen Werte der jeweils betrachteten Zelle hineingeschrieben. Erst nachdem alle Zellen aktualisiert wurden, wird das Hilfsfeld auf das eigentliche Feld zurückkopiert. Dies ist nötig, damit die Nachbarschaftsbeziehungen nicht durch den Aktualisierungsschritt selbst lokal verändert werden. Nach jedem Aktualisierungsschritt wird der Zustand des Automaten auf dem Bildschirm dargestellt. Hier bietet es sich an, für jede Zelle ein Pixel zu nutzen, welches entweder Schwarz oder Weiß ist, je nach Zustand der Zelle. Zum Zeitpunkt t = 0, dem Beginn, wird der Automat in die erste Pixelreihe geschrieben, beim nächsten Schritt eine Pixelreihe tiefer und so weiter. Hierfür wird noch eine Variable Zeile benötigt, die bei jedem Aktualisierungsschritt der Run() - Funktion um eins erhöht wird. Das Programm im Detail: 100 Simulation mechatronischer Systeme //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Projektbeschreibung: // Zelluläre Automaten : Sirpinski Dreieck //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" const int NMax = 1000; class TUser : public TPlan { int Feld[NMax], HilfsFeld[NMax]; int Zeile; public: void Init(); void Draw(); void Run(); }; void TUser::Init(){ int i; Zeile = 0; for(i=0;i<NMax;i++) Feld[i] = HilfsFeld[i] = 0; Feld[200] = 1; Draw();} void TUser::Draw(){ int i; for(i=0;i<NMax;i++) if(Feld[i])SetPixel(i,Zeile,Schwarz); Zeile++;} void TUser::Run(){ int i,k; for(i=0;i<NMax;i++) HilfsFeld[i] = 0; for(i=1;i<NMax-1;i++){ k = Feld[i-1]+Feld[i+1]; HilfsFeld[i] = k%2;} for(i=0;i<NMax;i++) Feld[i] = HilfsFeld[i]; Draw(); CallRun = (Zeile<GetMaxH());} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Simulation mechatronischer Systeme Das grafische Muster dieses Automaten wird Sierpinski – Dreieck genannt. Abbildung 5.2: Programm Sierpinski Dreieck. 101 102 Simulation mechatronischer Systeme 5.2 Verkehrssimulation Zellularautomaten zur Simulation von Verkehrsflüssen wurden 1992 von Schreckenberg eingeführt. In dieser ursprünglichen Variante wird eine einspurige Straße in Zellen aufgeteilt, welche jeweils eine Länge von 7.5 m aufweisen. Diese Länge wird von einem Auto im Durchschnitt im Stau eingenommen. Jede Zelle kann dabei den Zustand besetzt oder frei annehmen, wobei besetzten Zellen zusätzlich die aktuelle Geschwindigkeit des entsprechenden Fahrzeuges zugeordnet wird (Abb. 5.3). Abbildung 5.3: Ein Straßenabschnitt der Verkehrssimulation mit drei Fahrzeugen n-1, n, n+1 . Mit den Zeitschritten t = 1 sec ist durch die Ortsdiskretisierung eine Geschwindigkeitsdiskretisierung vorgegeben, denn die Fahrzeuge können sich ja in jedem Zeitschritt nur in jeweils einer Zelle aufhalten. Die möglichen Geschwindigkeiten der einzelnen Fahrzeuge betragen vn = 0, 1, ...,vn.max wobei eine Geschwindigkeit 1 das Fahrzeug in einem Zeitschritt um 1 Zelle fortbewegt. Der Wert vn.max entspricht der erlaubten Maximalgeschwindigkeit bzw. der Wunschgeschwindigkeit eines Fahrers n und wird häufig zu vn,max = 5 gesetzt. Bei der oben gewählten Zeitdiskretisierung von t = 1 sec entspricht das einer Wunschgeschwindigkeit von etwa 135 km/h. Schreckenberg hat diesen Zeitschritt gewählt mit der Begründung, dass die durchschnittlichen Reaktionszeit der Fahrer plus der Zeit bis zur Handlungseinleitung etwa diesem Zeitraum entspricht. Die Dynamik der Fahrzeuge, also die Aktualisierung der individuellen Fahrzeuggeschwindigkeiten und –orte, wird durch vier Regeln festgelegt. Sei zu irgendeinem Zeitpunkt t die Geschwindigkeit des Fahrzeuges n gleich v n(t). Zur Berechnung von vn(t+1) müssen folgende Schritte durchgeführt werden. Regel 1: (Beschleunigung) vn(t+1) = vn(t) + 1, wenn vn(t) < vn,max. Wenn das Fahrzeug langsamer fährt als seine Wunschgeschwindigkeit, erhöht es seine aktuelle Geschwindigkeit um 1. Regel 2: (Abbremsen) Seien k Zellen vor dem Fahrzeug n frei, dann gilt: Simulation mechatronischer Systeme 103 vn(t+1) = k, wenn vn(t) > k. Das Fahrzeug bremst generell so ab, dass es keinen Crash mit dem Vordermann gibt. Regel 3: (Trödeln) Mit einer Wahrscheinlichkeit von p gilt: vn(t+1) = vn(t)-1, wenn vn(t) > 0, also das Fahrzeug bremst ohne Grund mit einer vorgegebenen Wahrscheinlichkeit p ab. Regel 4: (Bewegung) Das Fahrzeug n wird um vn(t+1) Zellen nach vorne bewegt. Diese Regeln entsprechen dem Bedürfnis des Fahrers, seine Wunschgeschwindigkeit zu erreichen und zu halten. Dabei ist aber ein Sicherheitsabstand zum Vordermann zu beachten. Hier wird auch die Fahrzeug-Fahrzeug-Wechselwirkung beschrieben, die allerdings erst greift, wenn der Abstand zum Vordermann zu klein für die aktuelle Geschwindigkeit ist. Das Modell ist also per Definition kollisionsfrei. Stochastische Elemente kommen durch die 'Trödel'-Regel in das Modell. Diese 'Trödel'-Regel berücksichtigt Fluktuationen im Fahrerverhalten, die zum Beispiel durch Überreaktionen beim Bremsen, verzögertes Wiederanfahren oder nicht optimale Abstandseinhaltung verursacht werden. In der Verkehrsflusssimulation spielen die durch Regel 3 gegebenen Effekte eine große Rolle. In analytischen Modellen versucht man mit stochastischen Totzeiteffekten der Fahrerreaktionen ähnliche Einflüsse zu generieren, um die Modellierung von stop and go Wellen oder die Beschreibung von Staus, die sich scheinbar aus dem Nichts bilden, zu ermöglichen. Die Aktualisierungsreihenfolge könnte beliebig gewählt werden, da lediglich der Gesamtzustand des Systems in diskreten Zeitabständen nach Aktualisierung betrachtet wird. Wie üblich sollen hier lokale Verzerrungen des Systemszustandes dadurch vermieden werden, dass die Aktualisierung der Fahrzeuge in der Reihenfolge, in der sie in Fahrtrichtung aufeinander folgen durchgeführt wird. Dies ist nahezu äquivalent zur gleichzeitigen Aktualisierung. Für die Simulation definierten wir einige Daten in unserer Klasse TUser. int *Strasse, *HStrasse; int NMax, PTroedel, VMax; int Zeile; Hier sind die beiden Felder Strasse und HStrasse als Zeiger auf Integerwerte definiert. Ihnen muss mit dem new – Operator ein passendes Integerfeld zugewiesen 104 Simulation mechatronischer Systeme werden. Die Größe NMax bezeichnet die Anzahl der Zellen in den Feldern. PTroedel ist die Trödelwahrscheinlichkeit in Prozent, VMax schließlich die Wunschgeschwindigkeit im Automaten. Die Größe Zeile nutzen wir wieder für die Darstellung des Automaten in Zeilenform, wobei der jeweils nächste Zeitschritt in der darauffolgenden Zeile gezeichnet wird. Die Feldallokation erfolgt sinnvoller weise in der Init() – Funktion. NMax = 1000; PTroedel = 20; VMax = 5; Zeile = 0; Strasse = new int [NMax]; HStrasse = new int [NMax]; // // // // Feldelemente Trödelwahrscheinlichkeit Wunschgeschwindigkeit Grafikzeile Ausgabe Mit der dynamischen Allokation verbunden ist die Aufgabe, am Ende der Simulation diese Felder wieder freizugeben. Dafür ist die (virtuelle) Funktion Finish() gedacht, die von PLAN als letzte Ihrer Funktionen aufgerufen wird. Dort werden die Felder wieder freigegeben. delete [] Strasse; delete [] HStrasse; Die Feldelemente werden mit –1 initiiert. Da die Fahrzeuge nicht rückwärts fahren, kann ihre Geschwindigkeit nie negativ werden. Wenn ein Fahrzeug aus dem Feld herausläuft, wird es der Einfachheit halber an den Begin des Feldes geführt. Wir wollen das Feld also mit periodischen Randbedingungen versehen. Man beachte, dass die Beschreibung leerer Feldelemente mit Null eine Unterscheidung von leeren Straßenabschnitten mit stehenden Fahrzeugen nicht möglich macht. Würde man so vorgehen, würden beim ersten Stau die stehenden Fahrzeuge 'weggebeamt' und ein immer angenehmer Verkehrsfluss mit immer weniger Fahrzeugen suggeriert! Für den Beginn der Simulation muss eine Anfangsbelegung der Strasse mit fahrenden Fahrzeugen gegeben sein. Dies wird hier in einer eigenen Funktion InitVerkehr() durchgeführt. Zunächst werden alle Elemente des Feldes auf –1 gesetzt, also eine leere Strasse dargestellt. Mit den Integervariablen i und j kann man etwa so vorgehen: i=0; while(i<NMax-20){ j = random(5)+3; Strasse[i+j] = random(2)+3; i+=j;} Simulation mechatronischer Systeme 105 Die Funktion random(k) ist eine Funktion der c – Bibliothek stdlib.h und liefert eine ganze Zufallszahl zwischen 0 und k-1. In der while-Schleife wird dem Integerwert j ein zufälliger Wert zwischen 3 und 7 zugewiesen. Von der aktuellen Straßenposition, Variable i, wird das nächste Fahrzeug im Abstand von j platziert. Für die Geschwindigkeit dieses Fahrzeuges wird wieder die random() – Funktion genutzt, um eine Geschwindigkeit zwischen 3 und 4 einzustellen. In der Run() – Funktion findet die Umsetzung der Regeln statt. Hier wird der Aktualisierungsschritt durchgeführt. Die Run() – Funktion sei etwas genauer betrachtet. void TUser::Run(){ int i,j,k,v; for(i=0;i<NMax;i++) HStrasse[i] = -1; Hier wird das Hilfsfeld HStrasse leer gesetzt. In dieses Feld sollen die aktualisierten Positionen hineingeschrieben werden. i=0; while ((Strasse[i]<0)&&(i<NMax)) i++; if(i==NMax) return; Hier wird das erste Auto im Feld Strasse gesucht, am Ende der while – Schleife hat i diesen Wert, oder aber i ist aus dem Feld herausgelaufen. Dies kann nur passieren, wenn im Feld Strasse kein einziges Auto ist. Dann muss man natürlich auch nicht mehr simulieren, man kann die Run() – Funktion beenden. while(i<NMax){ v = Strasse[i]; Diese while – Schleife soll, solange Autos im Feld sind, arbeiten. Der Integer i hat am Eingang zu dieser while – Schleife den Wert des Feldes, welches das erste Auto enthält, v ist dessen Geschwindigkeit. v wird durch die Regeln verändert. if(v<VMax) v++; Dies ist die erste Regel. Für die zweite Regel muss der Abstand zum Vordermann gefunden werden. Wenn dann der Abstand bekannt ist, kann die Geschwindigkeit wie gefordert geändert werden. 106 Simulation mechatronischer Systeme for(k=1;Strasse[(i+k)%NMax]<0;k++); if(v>k-1) v = k-1; Man beachte das Argument des Feldes Strasse. Mit der modulo – Funktion werden ortsperiodische Randbedingungen erzwungen! Die Trödelregel lässt sich einfach mit der random() – Funktion realisieren. if((v>0) && (random(101)< PTroedel)) v--; Entsprechend der vierten Regel muss das Fahrzeug um seine Geschwindigkeit vorwärts bewegt werden. Dies wird zunächst im Hilfsfeld gekennzeichnet. Am Ende dieser while – Schleife wird der Laufindex i auf das nächste Fahrzeug gesetzt, dessen Relativposition in k enthalten ist. HStrasse[(i+v)%NMax] = v; i+=k;} Zum Schluss muss das Hilfsfeld auf das eigendliche Feld kopiert werden, der Automat gezeichnet und Run() wieder aufgerufen werden. for(i=0;i<NMax;i++) Strasse[i] = HStrasse[i]; Draw(); CallRun = true;} Zum Zeichnen des Automaten empfiehlt es sich, nicht die einzelnen Autos als Rechteck oder ähnliches zu zeichnen, sondern die alte und die neue Position des Fahrzeuges mit einem Strich zu kennzeichnen. Das Bild zeigt sogenannte Trajektorien. Das ist die örtliche Änderung der Fahrzeugpositionen ( x – Richtung) mit der Zeit (y – Richtung). Aus der Neigung der Linien kann man die aktuellen Geschwindigkeiten der Fahrzeuge ablesen, Staus mit Fahrzeuggeschwindigkeiten=Null sind durch senkrechte Linien gekennzeichnet. Das Programm als Code: //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Projektbeschreibung: // Verkehrssimulation nach Schreckenberg und Nagel //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" Simulation mechatronischer Systeme 107 class TUser : public TPlan { int *Strasse, *HStrasse; int NMax, PTroedel, VMax; int Zeile; public: void Init(); void InitVerkehr(); void Draw(); void Run(); void Finish(); }; void TUser::Init(){ NMax = 1000; PTroedel = 20; VMax = 5; Zeile = 0; Strasse = new int [NMax]; HStrasse = new int [NMax]; InitVerkehr(); } // Feldelemente // Trödelwahrscheinlichkeit // Wunschgeschwindigkeit // initiale Verkehrssituation void TUser::InitVerkehr(){ int i,j; for(i=0;i<NMax;i++) Strasse[i] =-1; i=0; while(i<NMax-20){ j = random(5)+3; Strasse[i+j] = random(2)+3; i+=j;}} void TUser::Run(){ int i,j,k,v; for(i=0;i<NMax;i++) HStrasse[i] = -1; i=0; //=== erstes Auto suchen while ((Strasse[i]<0)&&(i<NMax)) i++; if(i==NMax) return; while(i<NMax){ v = Strasse[i]; //=== 1. Regel if(v<VMax) v++; //=== 2. Regel for(k=1;Strasse[(i+k)%NMax]<0;k++); if(v>k-1) v = k-1; //=== 3. Regel if((v>0) && (random(101)< PTroedel)) v--; //=== 4. Regel HStrasse[(i+v)%NMax] = v; Strasse[(i+v)%NMax] = v; i+=k;} for(i=0;i<NMax;i++) Strasse[i] = HStrasse[i]; Draw(); CallRun = true;} 108 Simulation mechatronischer Systeme void TUser::Draw(){ int i; if(Zeile>GetMaxH()) { Clear(); Zeile = 10;} SetPen(Schwarz); for(i=0;i<NMax;i++){ if(Strasse[i]>-1) { MoveTo(i-Strasse[i],Zeile-5); LineTo(i,Zeile);}} Zeile += 5; } void TUser::Finish(){ delete [] Strasse; delete [] HStrasse;} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Abbildung 5.4 Verkehrssimulation. Simulation mechatronischer Systeme 109 5.3 Conway's Spiel des Lebens Ein zweidimenionaler Zellulärer Automat ist das berühmte Spiel des Lebens, welches von Conway 1970 vorgestellt wurde. Der Automat besteht aus einer Matrix von Zellen, jede Zelle hat acht Nachbarn. Die Zellen selbst können zwei Zustände annehmen, sie seien mit Null und Eins gekennzeichnet. Entsprechend dem Namen des Spiels lassen sich Zellen mit dem Zustand Null als tot und mit dem Zustand 1 als lebendig kennzeichnen. Die Aktualisierung des Automaten führt auf die Geburt und den Tod von Zellen. Über den aktuellen Zustand einer Zelle zum Zeitpunkt t+1 entscheiden nur die Zustände der acht unmittelbaren Nachbarn der Zelle zum Zeitpunkt t. Das Spielfeld aus n x m Zellen kann an den Rändern periodisch fortgesetzt werden, wie der eindimensionale Verkehrsautomat in Kapitel 5.2, und eine geschlossene, jetzt torusförmige Fläche bilden. Die Regeln sind sehr einfach. Regel 1 (Geburt) Hat eine Zelle genau drei lebende Nachbarn, wird diese Zelle geboren, also auf Zustand Eins gesetzt. Regel 2 (Tod durch Einsamkeit) Hat eine lebende Zelle weniger als zwei lebende Nachbarn, stirbt sie, nimmt also Zustand 0 an. Regel 3 (Tod durch Überbevölkerung) Hat eine lebende Zelle mehr als drei lebende Nachbarn, stirbt sie, hat dann also den Zustand Null. Trotz dieser Einfachheit ist eine außerordentlich komplexe Dynamik möglich. Es gibt typische Strukturen, die sich im Laufe der Generationen einstellen. Dies sind statische Objekte, Objekte die über viele Generationen unverändert bleiben, ebenso gibt es oszillatorische Elemente und Objekte, die sich zielgerichtet über das Spielfeld bewegen. Zur numerischen Realisierung kann man natürlich wieder statische Integerfelder allokieren. Am einfachsten ist es, Strukturen der C++ Bibliotheken zu nutzen. Hier wollen wir der Einfachheit halber eine eigene Klasse für den zweidimensionalen Zellulären Automaten definieren. 110 Simulation mechatronischer Systeme class ZAutomat { int *cell, row, col; public: ZAutomat(void); ZAutomat(int i, int j); ZAutomat(const ZAutomat &a); ~ZAutomat(void); bool Size(int i, int j); void Clear(void); int &operator () (int i,int j); ZAutomat operator = (ZAutomat a); }; Die eigentlichen Daten des Automaten befinden sich im private – Abschnitt in dem Feld cell. Mit den Größen row und col wird die Größe der Matrix beschrieben. Die Matrixstruktur selbst ist virtuell und nur durch den Zugriffsoperator auf dem eindimensionalen Feld realisiert. Die Klasse enthält neben dem Standard-Konstruktor einen Konstruktor zum Aufbau von Matrixstrukturen, einen Kopierkonstruktor sowie eine Destruktor, um gegebenenfalls allokierte Felder wieder freizugeben. Die Methode Size(i,j) allokiert ein Feld mit i*j Elementen, auf dem die (i x j) Matrix Platz hat. Dazu löscht sie zunächst das Feld, falls es schon einmal früher allokiert wurde und setzt die Parameter row und col. Size(0,0) wird genutzt, um die Allokationen frei zu geben. bool ZAutomat::Size(int i, int j) { if(cell) delete[] cell; cell = 0; row = col = 0; if(!i*j) return true; cell = new int[i*j]; if(cell) { row = i; col = j;return true;} else cell = 0; return false;} Die Methode Clear() setzt alle Zellen auf Null, löscht quasi den Automaten. Wesentlich ist der Zugriffsoperator in dieser Klasse. Das ist eine Funktion mit dem Namen (). Sie nimmt zwei Argumente auf, das sind die gewünschte Zeilen- und Spaltennummer. Diese Funktion ist als Referenz ausgebildet. Sie kann nicht nur Werte liefern, sondern es kann dieser Funktion auch ein Integerwert zugeordnet werden. Wegen der letzteren Eigenschaft wird eine statische Variable z definiert, die auch außerhalb der Ausführungszeiten dieser Funktion definiert bleibt. Diese Variable Simulation mechatronischer Systeme 111 dient als Opfervariable, die Werte aufnimmt oder abgibt, auch wenn der Automat keine einzige Zelle besitzt. int &ZAutomat::operator () (int i, int j){ static int z = 0; if(!cell) return z; Zunächst werden periodische Randbedingungen realisiert. Das heißt, wenn etwa die Argumente i oder j größer sind als die maximale Anzahl von Zeilen oder Spalten oder kleiner als Null, so werden sie entsprechend modifiziert. while(i<0) i+=row; while(j<0) j+=col; i = i%row; j = j%col; Im Rückgabewert dieses Operators wird dann die Matrixstruktur auf die eindimensionale Struktur mit den Integerzeiger cell abgebildet. return *(cell +i*col+j);} Ein weiterer Operator ist der Zuweisungsoperator =. Er wird benötigt, wenn der Hilfsautomat, der die neuen Werte beim Abarbeiten der Regeln aufnimmt, anschließend auf den Hauptautomaten wieder zurückkopiert wird. Es muss einfach nur das Integerfeld unabhängig von der virtuellen Matrixstruktur auf das eigene Feld abgebildet werden. Zuvor muss man natürlich die Feldgrößen auf Gleichheit überprüfen. ZAutomat ZAutomat::operator = (ZAutomat a){ if((col!=a.col)||(row!=a.row)) Size(a.row,a.col); int i,j=row*col; for(i=0;i<j;i++) *(cell+i) = *(a.cell+i); return *this;} Mit dieser Klasse kann man nun alle Operationen eines zweidimensionalen zellulären Automaten realisieren. Beispielsweise werden ZA und ZB als Automaten definiert. Beide sollen die Größe 20 x 20 haben: 112 Simulation mechatronischer Systeme ZAutomat ZA, ZB; ZA.Size(20,20); ZB.Size(20,20); Zunächst werden alle Elemente von ZA auf Null gesetzt. Dann wird das Element (4,5) von ZA auf 1 gesetzt . ZA.Clear(); ZA(4,5) = 1; Anschließend soll ZA auf ZB kopiert werden, sodass ZB nun ebenfalls genau ein Element mit dem Wert Eins und sonst nur Zellen mit dem wert Null hat. ZB = ZA; Das Programm lässt sich jetzt einfach realisieren. In der Init() – Funktion werden zwei gleich große Zelluläre Automaten ZA und ZB aufgebaut, eine Anfangskonfiguration auf den Automaten ZA gegeben und in der Run()-Funktion die nächste Generation berechnet. Die Run()- Funktion definiert zunächst einige Hilfsvariable, die als Laufindizes benötigt werden. Dann wird der Automat ZA auf den Automaten ZB kopiert. void TUser::Run(){ int i,j, ih,jh, erg; ZB = ZA; Jetzt müssen die Regeln realisiert werden. Dazu wird über jedes Feld (i,j) des (size x size) großen Automaten gelaufen und eine Integervariable erg auf Null gesetzt. for(i=0;i<size;i++) for(j=0;j<size;j++){ erg = 0; Simulation mechatronischer Systeme 113 Jetzt wird mit den Hilfsindizes ih und jh die direkte Nachbarschaft der Zelle (i,j) abgelaufen. Dabei wird auch die Zelle (i,j) selbst erfasst. Die Variable erg nimmt die Werte der Zellen auf und zieht den Wert der Zelle (i,j) wieder ab. for(ih=i-1;ih<=i+1;ih++) for(jh=j-1;jh<=j+1;jh++) erg+=ZA(ih,jh); erg-=ZA(i,j); Jetzt hat die Variable erg einen Wert, der genau der Anzahl der lebenden Nachbarn angibt. Die Conway'schen Regeln lassen sich nun einfach realisieren. if((erg<2)||(erg>3)) ZB(i,j)=0; else if(erg==3) ZB(i,j)=1; } Anschließend muss der aktuelle Stand im Automaten ZB wieder auf den Ausgangsautomaten ZA kopiert werden und das System sinnvoller weise irgendwie gezeichnet werden. ZA = ZB; Draw(); CallRun = !step;} Da die Simulation sehr schnell vor sich geht, macht es Sinn, mit einer Variablen step, die mit Usertasten auf true oder false gesetzt wird, die Simulation kontinuierlich oder schrittweise ausführen zu lassen. Darüber hinaus kann man etwa die Feldgröße über Usertasten auswählen lassen und insbesondere verschiedene Anfangskonfigurationen zur Verfügung stellen. Besonders reizvoll ist es, stochastische Feldbelegungen anzubieten, die tatsächlich sehr schnell einen Überblick über länger oder kürzer lebende Strukturen aufzeigen. Einige der bekanntesten Strukturen ist der Gleiter, der über den Bildschirm krabbelt. Abbildung 5.5 Ein Gleiter. 114 Simulation mechatronischer Systeme In den Neunzigern des vorherigen Jahrhunderts wurden auch soziologische Fragestellungen mit Gesellschaften untersucht, die Auswanderer produzieren. Die nachfolgende Struktur gebiert in gleichmäßigen Abständen Gleiter, s. Abbildung 5.5. Wenn nun die Ränder des Spielfeldes periodisch fortgesetzt werden, stoßen die Auswanderer früher oder später auf ihre Muttergesellschaft. Dabei wird die Muttergesellschaft in der Regel zerstört. Aber es gibt auch Ausnahmen. Man spiele einfach einmal das Conway'sche Spiel des Lebens! Abbildung 5.6 Eine Gesellschaft, die Gleiter generiert. Nachfolgend nun der vollständige Sourcecode des Programmes. //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Projektbeschreibung: // Conway's Spiel des Lebens //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" //==================================================================== // Klasse ZAutomat für zellulären Automaten // mit randperiodischen Zugriffsregeln (!) //==================================================================== class ZAutomat { int *cell, row, col; public: ZAutomat(void); ZAutomat(int i, int j); ZAutomat(const ZAutomat &a); ~ZAutomat(void); bool Size(int i, int j); void Clear(void); int &operator () (int i,int j); ZAutomat operator = (ZAutomat a); }; ZAutomat::ZAutomat(void){ row = col = 0; cell = 0;} ZAutomat::ZAutomat(int i, int j){ cell = 0; Size(i,j);} Simulation mechatronischer Systeme ZAutomat::ZAutomat(const ZAutomat &a){ row = col = 0; cell = 0; if(a.cell) { Size(a.row,a.col); int i,j = row*col; for(i=0;i<j;i++) *(cell+i) = *(a.cell+i);}} ZAutomat::~ZAutomat(void){ Size(0,0);} bool ZAutomat::Size(int i, int j) { if(cell) delete[] cell; cell = 0; row = col = 0; if(!i*j) return true; cell = new int[i*j]; if(cell) { row = i; col = j;return true;} else cell = 0; return false;} void ZAutomat::Clear(void){ if(!cell) return; int i,j = row*col; for(i=0;i<j;i++) *(cell+i) = 0;} int &ZAutomat::operator () (int i, int j){ static int z = 0; if(!cell) return z; while(i<0) i+=row; while(j<0) j+=col; i = i%row; j = j%col; return *(cell +i*col+j);} ZAutomat ZAutomat::operator = (ZAutomat a){ if((col!=a.col)||(row!=a.row)) Size(a.row,a.col); int i,j=row*col; for(i=0;i<j;i++) *(cell+i) = *(a.cell+i); return *this;} //==== Klasse TUser class TUser:public TPlan { ZAutomat ZA, ZB; int size, start, step; public: void Init(); void Run(); void Reset(); void Draw(); void RunTaste0(); void RunTaste1(); void RunTaste2(); void RunTaste3(); void RunTaste4(); void RunTaste5(); 115 116 Simulation mechatronischer Systeme void void void void void void void RunTaste8(); RunTaste9(); RunTaste12(); RunTaste13(); RunTaste14(); RunTaste15(); RunTaste16(); }; void TUser::Init(){ ProgrammName = "Zelluläre Automaten"; size = 20; start = 0; step = 0; ZA.Size(size,size); ZB.Size(size,size); Reset(); TastenfeldName = "Anfangsbedingungen"; InsertTaste(0,"Linie"); InsertTaste(1,"Kreuz"); InsertTaste(2,"Zufall 5%"); InsertTaste(3,"Zufall 20%"); InsertTaste(4,"Gleiter"); InsertTaste(5,"Gesell."); InsertTaste(8,"Einzelschritt"); InsertTaste(9,"kontinuierlich"); InsertTaste(12,"20x20"); InsertTaste(13,"50x50"); InsertTaste(14,"100x100"); InsertTaste(15,"200x200"); InsertTaste(16,"400,400");} void TUser::Run(){ int i,j, ih,jh, erg; ZB = ZA; for(i=0;i<size;i++) for(j=0;j<size;j++){ erg = 0; for(ih=i-1;ih<=i+1;ih++) for(jh=j-1;jh<=j+1;jh++) erg+=ZA(ih,jh); erg-=ZA(i,j); if((erg<2)||(erg>3)) ZB(i,j)=0; else if(erg==3) ZB(i,j)=1; } ZA = ZB; Draw(); CallRun = !step;} void TUser::Reset(){ ZA.Clear(); int i,j,k; switch(start){ case 5: k = size/2; ZA(k-16,k+3) = ZA(k-15,k+3) = 1; ZA(k-16,k+4) = ZA(k-15,k+4) = 1; Simulation mechatronischer Systeme ZA(k-3,k) = 1; ZA(k-4,k+1) = ZA(k-2,k+1) = 1; ZA(k-5,k+2) = ZA(k-1,k+2) = ZA(k,k+2) = 1; ZA(k-5,k+3) = ZA(k-1,k+3) = ZA(k,k+3) = 1; ZA(k-5,k+4) = ZA(k-1,k+4) = ZA(k,k+4) = 1; ZA(k-4,k+5) = ZA(k-2,k+5) = 1; ZA(k-3,k+6) = 1; ZA(k+9,k+2) = 1; ZA(k+6,k+3)=ZA(k+7,k+3)=ZA(k+8,k+3)=ZA(k+9,k+3)=1; ZA(k+5,k+4)=ZA(k+6,k+4)=ZA(k+7,k+4)=ZA(k+8,k+4)=1; ZA(k+5,k+5) =ZA(k+8,k+5)=1; ZA(k+5,k+6)=ZA(k+6,k+6)=ZA(k+7,k+6)=ZA(k+8,k+6)=1; ZA(k+6,k+7)=ZA(k+7,k+7)=ZA(k+8,k+7)=ZA(k+9,k+7)=1; ZA(k+9,k+8) = 1; ZA(k+14,k+3) = ZA(k+14,k+4) = 1; ZA(k+18,k+5) = ZA(k+19,k+5) = 1; ZA(k+18,k+6) = ZA(k+19,k+6) = 1; break; case 4: k = size/2; ZA(k,k)= ZA(k,k+1) = 1; ZA(k-1,k+2) = ZA(k+1,k+1) = ZA(k+1,k+2) = 1; break; case 3: k = 20; for(i=0;i<size;i++) for(j=0;j<size;j++) ZA(i,j) = (random(101)<k)?1:0; break; case 2: k = 5; for(i=0;i<size;i++) for(j=0;j<size;j++) ZA(i,j) = (random(101)<k)?1:0; break; case 1: k = size/2; for(i=0;i<size;i++) ZA(i,k) = 1; default: case 0: k = size/2; for(j=0;j<size;j++) ZA(k,j) = 1; break; } Draw();} void TUser::Draw(){ View(); SetText(Schwarz,25); SetBrush(Weiss); PlanString s = "Conway's Spiel des Lebens, Generation "; s += CallRunCount; TextRect(0,0,GetMaxW(),30,s,1); int i = GetMaxH()-30; int j = GetMaxW()-i; View(j/2,30,i,i); Clear(Hellgelb); Scale(0.0,(real)size,0.0,(real)size); SetPen(Schwarz); SetBrush(Schwarz); for(i=0;i<size;i++) for(j=0;j<size;j++){ if(ZA(i,j)) Rectangle((real)i,(real)j,1.0,1.0);}} 117 118 Simulation mechatronischer Systeme void TUser::RunTaste0(){ start = 0; Reset();} void TUser::RunTaste1(){ start = 1; Reset();} void TUser::RunTaste2(){ start = 2; Reset();} void TUser::RunTaste3(){ start = 3; Reset();} void TUser::RunTaste4(){ start = 4; Reset();} void TUser::RunTaste5(){ start = 5; Reset();} void TUser::RunTaste8(){ step = 1;} void TUser::RunTaste9(){ step = 0;} void TUser::RunTaste12(){ size = 20; ZA.Size(size,size); ZB.Size(size,size); Reset();} void TUser::RunTaste13(){ size = 50; ZA.Size(size,size); ZB.Size(size,size); Reset();} void TUser::RunTaste14(){ size = 100; ZA.Size(size,size); ZB.Size(size,size); Reset();} void TUser::RunTaste15(){ size = 200; ZA.Size(size,size); ZB.Size(size,size); Reset();} void TUser::RunTaste16(){ size = 400; ZA.Size(size,size); ZB.Size(size,size); Reset();} //==================================================================== Simulation mechatronischer Systeme 119 #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ 120 Simulation mechatronischer Systeme Am Bildschirm zeigt sich das Programm in der nachfolgenden Form. Dargestellt ist der Zustand nach 165 Generationen ausgehend von der in Abbildung 5.6 dargestellten Gesellschaft. Nach rechts unten laufen die Gleiter heraus. Abbildung 5.7 Das Programmlayout zu Conway's Spiel des Lebens Ändern Sie das Programm so ab, dass Sie prüfen können, wie lange dynamische, aber nicht oszillatorische Objekte im Conwaykontinuum leben. Lassen Sie Ihr Programm mal eine Nacht durchrechnen, wobei Sie zufällige Anfangsbedingungen wählen und so Konfigurationen mit besonders langlebiger Dynamik herauslesen. Lesen Sie mal im Internet (zum Beispiel Wikipedia) oder in der Literatur nach, welche wissenschaftlichen Fragestellungen an diesen Systemen untersucht werden. Simulation mechatronischer Systeme 121 6. Methoden in PLAN, Teil II 6.1 Vektoren und Matrizen PLAN stellt für Vektoren und Matrizen die Klassen TVektor und TMatrix bereit. Diese Klassen bieten eine Reihe nützlicher Operatoren und Methoden. Sie können im Rahmen Ihrer Systemressourcen beliebig groß gewählt werden. Intern wird in PLAN bei der numerischen Integration wie bei den Plotdarstellungen mit diesen Vektoren gearbeitet. Vektoren sind Objekte der Klasse TVektor, Matrizen sind Objekte der Klasse TMatrix. TVektor v; TMatrix M; Die Länge des Vektors v lässt sich mit der Methode Size() einstellen: if(!v.Size(100000)) PechGehabt(); Die Methode Size() liefert den Wert 0 zurück, wenn die Allokation fehlgeschlagen ist. Die Elemente des Vektors sind real – Größen, also in der Regel double – Zahlen. v(990) = 1.0; v(991) = -v(990); Ganz analog lassen sich (n x m) Matrizen aufbauen. Auch sie bestehen aus real – Zahlen. 122 Simulation mechatronischer Systeme if(!M.Size(5,9)) return; M(0,0) = 1.0; M(4,8) = 1.0; Wie in C++ üblich, fängt der Index über die Felder bei 0 an und hört bei einem Feld der Länge n bei n-1 auf. In diesen Klassen wird der Index aber abgefangen, sodass ein Code der Form v.Size(100); for(i=-10;i<500;i++) v(i) = i; korrekt die 100 Elemente v(0) bis v(99) des Vektors v mit der Zahl 1.0 füllt. Die aktuelle Anzahl von Zeilen und Spalten lässt sich beim Vektor wie bei der Matrix explizit abfragen. Dazu dienen die Methoden Zeilen() und Spalten(). int int int int TVektor::Zeilen(void); TVektor::Spalten(void); TMatrix::Zeilen(void); TMatrix::Spalten(void); Also kann man etwa Schleifen der nachfolgenden Form aufbauen: TVektor v; v.Size(156); ... for(int i = 0; i<v.Zeilen(); i++) ... Die Spaltenzahl ist bei Objekten vom Typ TVektor immer 1. Weitere Methoden sind: void TVektor::Clear(void); void TMatrix::Clear(void); Hiermit werden alle Elemente zu Null gesetzt. Eine manchmal nützliche Variante hiervon ist void TVektor::Clear(real eps); void TMatrix::Clear(real eps); wobei nur Elemente, die betragsmäßig kleiner als eps sind, zu Null gesetzt werden. Mit Vektoren und reellen Zahlen sind eine Reihe von Operationen definiert. Seien gegeben die Vektorgrößen u, v und die reelle Zahl z: Simulation mechatronischer Systeme 123 TVektor u,v; real z; Dann sind folgende Operationen mit diesen Größen möglich: u u u u u u u u u z u = v; +=v; -=v; *= z; /= z; = v*z; = z*v; = u+v; = u–v; = u*v; = u/z; Ebenso sind natürlich Operationen mit Matrizen definiert. Mit der reellen Größe z, den Vektoren u, v sowie den Matrizen M, W: TMatrix M,W; TVektor u,v; real z; ist definiert: M M M M M M M M M M M M u u = W; = M+W; = M-W; = M*W; = z*W; = W*z; = W/z; += W; -= W; *= W; *= z; /= z; = v*M; = M*v; M = W^i M = W^”t” M = W^”s” M = W^”a” // // // // // = Wi, i kann bel. pos. oder neg. ganze Zahl sein! transponiert sym. Anteil von W antimetrischer Teil von W 124 Simulation mechatronischer Systeme Man beachte die Anwendungen des Operators ^ bei Matrizen. Eine Anweisung der Form M = W^-5; macht Sinn und wird korrekt ausgewertet, wenn die Matrix nicht (praktisch) singulär ist. Wenn eine Operation nicht fehlerfrei ausgeführt wurde, sei es, die Dimensionen stimmten nicht oder eine Matrix war singulär oder ähnliches, wird eine Fehlervariable in den betreffenden Objekten auf true gesetzt. Jeder Vektor und jede Matrix enthält die Methode Error(), welche die Fehlervariable abfragt. Ist der Wert dieser Funktion true, ist irgendwo in der Berechnungskette mit diesem Objekt ein Fehler aufgetreten. bool TVektor::Error(void); bool TMatrix::Error(void); Beispiel: TVektor v = .... if(v.Error()) DaGingWasSchief(); Ist ein Vektor (oder eine Matrix) fehlerhaft, so bleibt er es. Jeder weitere Vektor (oder Matrix), der mit diesem Vektor über beliebige Operationen verändert werden soll, wird ebenfalls fehlerhaft, das heißt, dessen Methode Error() liefert dann auch true. Der Fehlerstatus eines Vektors oder einer Matrix kann nur über die Methode Reset() zurückgesetzt werden. void TVektor::Reset(void); void TMatrix::Reset(void); if(M.Error()) M.Reset(); Beim Anwenden dieser Funktion ist Vorsicht geboten, denn der Inhalt der Matrix in der letzten Zeile ist wohl falsch. Für Vektoren und Matrizen gibt es eine Reihe von hilfreichen Funktionen. real Norm(TVektor v); real MaxNorm(TVektor v); int EinheitsVektor(TVektor &v); Wie üblich, liefert Norm() die L2-Norm und MaxNorm() die L1-Norm des Vektors. Simulation mechatronischer Systeme 125 Die letzte Funktion EinheitsVektor() liefert einen Wert ungleich Null, wenn der Vektor im Argument zum Einheitsvektor gemacht werden konnte. TMatrix EinheitsMatrix(int n); TMatrix DrehMatrix(real rad); TMatrix DrehMatrix(int Achse, real rad); Die Funktion EinheitsMatrix() liefert eine Einheitsmatrix der Ordnung n. Die Anweisung I = EinheitsMatrix(4); liefert also: 1 0 0 0 I 0 1 0 0 . 0 0 1 0 0 0 0 1 Die Funktion Drehmatrix(real rad) liefert eine 2D- Drehmatrix. M = DrehMatrix(rad); führt auf: M cos(rad) sin(rad) sin(rad) cos(rad) worin rad eine Winkelangabe in Radiant ist. Und D0 = DrehMatrix(0,rad); D1 = DrehMatrix(1,rad); D2 = DrehMatrix(2,rad); führen auf 3D-Drehmatrizen, die Drehungen im mathematisch positiven Sinn um den x-Basisvektor, den y-Basisvektor und den z-Basisvektor beschreiben: D0 1 0 0 D1 0 cos(rad) 0 sin(rad) , sin(rad) cos(rad) cos(rad) 0 0 1 sin(rad) 0 sin(rad) 0 , cos(rad) 126 Simulation mechatronischer Systeme D2 cos(rad) sin(rad) 0 sin(rad) cos(rad) 0 . 0 0 1 Für die Umrechnung von Grad in Radiant und umgekehrt existieren die Hilfsfunktionen real GradToRad(real grad); real RadToGrad(real rad); Um das Arbeiten mit Vektoren und Matrizen etwas zu illustrieren, soll ein Programm geschrieben werden, mit dem Vektoren und Matrizen ausgegeben werden. Hier soll dafür die grafische Oberfläche von PLAN genutzt werden. Dazu wird intern wie bei einem Cursor der Ort (x,y) am Bildschirm verschoben. Wenn ein Charakterstring am Ort (x,y) ausgegeben wird, muss die Koordinate x um eben die Länge des Strings vergrößert werden. Nutzt man die Defaultgrößen der Stringausgabe, so kann die nächste Zeile etwa 20 Pixel tiefer ausgegeben werden, ohne dass der vorhergehende String verändert oder partiell gelöscht wird. Also kann man mit den Funktionen void Ausgabe(char *s=""); void NewLine(void); void ClearScreen(void); Texte hintereinander ausgeben und, je nach Wunsch in einer neuen Zeile beginnen lassen. Die Funktion NewLine() muss dazu x wieder auf 0 setzen und y um 20 erhöhen. ClearScreen() schließlich setzt x und y auf 0 und löscht den Bildschirm. Mit dieser Methode lassen sich auch Vektoren und Matrizen ausgeben, hierbei muss aber beachtet werden, dass die Höhen der Matrizen und Vektoren von ihrer Zeilenanzahl abhängen, wobei jede Zeile wie bei obiger Stringausgabe 20 Pixel groß gewählt wird. Dazu kann eine Variable h genutzt werden, die die maximal notwendige Höhe enthält, damit bei einem Zeilenwechsel y um eben diese Höhe vergrößert wird. Die Länge der Matrizen hängt von deren Spaltenanzahl ab. Um grafisch Vektoren und Matrizen einigermaßen ebenmäßig ausgeben zu können, wird noch eine Funktion eingefügt, die Zahlen in einem festen Format in einen Charakterstring umwandeln. Nachfolgend ist das Programm angegeben. //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Projektbeschreibung: // einfache Vektor- und Tensoroperationen und Ausgabe //==================================================================== #include <vcl.h> Simulation mechatronischer Systeme #pragma hdrstop #include "Plan.h" class TUser : public TPlan { int x,y,w,h; char *RealToStr(real z); public: void Init(); void Run(); void ClearScreen(); void NewLine(); void Ausgabe(char *s=""); void Ausgabe(real z); void Ausgabe(TVektor &v); void Ausgabe(TMatrix &m); }; char *TUser::RealToStr(real z){ static char ss[100]; sprintf(ss,"% 8.3le",z); return ss;} void TUser::Init(void){ ProgrammName = "Vektor- und Tensorrechnung"; ClearScreen(); w = TextW(RealToStr(1.23456e-12)); h = 20;} void TUser::ClearScreen(void){ x=5; y=5; h = 20; Clear();} void TUser::NewLine(void){ y+=h+5; x=5; h=20;} void TUser::Ausgabe(char *s){ BrushColor = Klar; Text(x,y,s); x+= TextW(s)+5;} void TUser::Ausgabe(real z){ BrushColor = Klar; PlanString s; s = z; Text(x,y,s); x+= TextW(s)+5;} void TUser::Ausgabe(TVektor &v){ int k = v.Zeilen()*20+10; if(h<k+1) h = k+1; SetPen(Schwarz); SetBrush(Hellgelb); Rectangle(x,y-5,w+10,k); x+=5; int yh = y; for(int i=0;i<v.Zeilen();i++){ Text(x,yh,RealToStr(v(i))); yh+=20;} 127 128 Simulation mechatronischer Systeme x +=w+15; } void TUser::Ausgabe(TMatrix &m){ int k = m.Zeilen()*20+10; if(h<k) h = k; int l = (w+5)*m.Spalten()+10; SetPen(Schwarz); SetBrush(Hellgelb); Rectangle(x,y-5,l,k); x+=5; int j,yh = y; for(int i=0;i<m.Zeilen();i++){ for(j=0;j<m.Spalten();j++) Text(x+j*(w+5),yh,RealToStr(m(i,j))); yh+=20;} x +=l+5; } void TUser::Run(void){ TVektor v(1.0,2.0,3.0); Ausgabe("Vektor v = "); Ausgabe(v); Ausgabe(", Norm(v) = "); Ausgabe(Norm(v)); Ausgabe(", MaxNorm(v) = "); Ausgabe(MaxNorm(v)); NewLine(); TMatrix M; M.Size(3,3); M(0,0) = 1.0; M(0,1) = 2.0; M(0,2) = 3.0; M(1,1) = 4.0; M(1,2) = 4.0; M(2,0) = 0.1; M(2,2) = 0.2; Ausgabe("Matrix M = "); Ausgabe(M); TMatrix W= M^4; Ausgabe(" Matrix W = M^4 = "); Ausgabe(W); TMatrix H = W^-1; Ausgabe("Matrix W^-1 = "); Ausgabe(H); NewLine(); TMatrix K = W*H;; Ausgabe("Matrix K = W*W^-1 = "); Ausgabe(K); NewLine(); Ausgabe("K.Clear(1.0e-9) zeigt die Struktur von K"); NewLine(); K.Clear(1.0e-9); Ausgabe("Matrix K = "); Ausgabe(K); Ausgabe((K.Error()?"Fehler":"kein Fehler")); NewLine(); NewLine(); TMatrix D = DrehMatrix(0,0.1)*DrehMatrix(1,-0.1) *DrehMatrix(0,0.2); Ausgabe("Drehmatrix D "); Ausgabe(D); Ausgabe(" aus den Kardanwinkeln (0.2,-0.1,0.1)"); NewLine(); M = EinheitsMatrix(6); Ausgabe("Einheitsmatrix E(6) = "); Ausgabe(M); } //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } Simulation mechatronischer Systeme 129 //__________________________________________________________Ost08_____ In der Funktion Run() sind einige Testrechnungen explizit angegeben. Dies sei genauer betrachtet. void TUser::Run(void){ TVektor v(1.0,2.0,3.0); Ausgabe("Vektor v = "); Ausgabe(v); Ausgabe(", Norm(v) = "); Ausgabe(Norm(v)); Ausgabe(", MaxNorm(v) = "); Ausgabe(MaxNorm(v)); NewLine(); Hier wird also ein 3x1 Vektor v generiert und dessen Norm beziehungsweise dessen Maximumsnorm ausgegeben. TMatrix M; M.Size(3,3); M(0,0) = 1.0; M(0,1) = 2.0; M(0,2) = 3.0; M(1,1) = 4.0; M(1,2) = 4.0; M(2,0) = 0.1; M(2,2) = 0.2; Ausgabe("Matrix M = "); Ausgabe(M); TMatrix W= M^4; Ausgabe(" Matrix W = M^4 = "); Ausgabe(W); TMatrix H = W^-1; Ausgabe("Matrix W^-1 = "); Ausgabe(H); NewLine(); Hier wird eine 3x3 Matrix M definiert, die zunächst hoch 4 genommen und dann invertiert wird. TMatrix K = W*H;; Ausgabe("Matrix K = W*W^-1 = "); Ausgabe(K); NewLine(); Hier wird der Test ausgeführt, eine Matrix und ihre Inverse zu multiplizieren. Man erhält, wie in solchen Fällen immer, eine numerisch verrauschte Einheitsmatrix. Die Elemente, die Null groß sein sollten sind numerisch irgendwo zwischen 10-10 und 1012 groß. Dies ist numerisch völlig in Ordnung. Hier kann man aber auch die Clear(real eps)-Funktion nutzen, welche die sehr kleinen Elemente der Matrix zu Null setzt. 130 Simulation mechatronischer Systeme Ausgabe("K.Clear(1.0e-9) zeigt die Struktur von K"); NewLine(); K.Clear(1.0e-9); Ausgabe("Matrix K = "); Ausgabe(K); Ausgabe((K.Error()?"Fehler":"kein Fehler")); Am Ende solcher Berechnungsketten sollte man sicherheitshalber den Fehlerstatus abfragen. Drehmatrizen aus Eulerwinkeln oder Kardanwinkeln lassen sich sehr einfach produzieren: NewLine(); NewLine(); TMatrix D = DrehMatrix(0,0.1)*DrehMatrix(1,-0.1) *DrehMatrix(0,0.2); Ausgabe("Drehmatrix D "); Ausgabe(D); Ausgabe(" aus den Kardanwinkeln (0.2,-0.1,0.1)"); Matrizen lassen sich auch jederzeit umdefinieren. Hier wird die obige Matrix M als 6x6-Einheitsmatrix neu definiert. NewLine(); M = EinheitsMatrix(6); Ausgabe("Einheitsmatrix E(6) = "); Ausgabe(M); } Mit dieser Run()-Funktion erhält man am Bildschirm die Ausgabe: Simulation mechatronischer Systeme 131 Abbildung. 6.1: Ausgabe des Vektorprogrammes. Die Textausgabe lässt sich natürlich sehr viel einfacher auf dem DOS-Bildschirm realisieren, der ja bei diesen Projekten (Konsoleprojekt!) immer dabei ist. Dazu mehr im nächsten Kapitel. 132 Simulation mechatronischer Systeme 6.2 Interpolation und Fourierreihen Interpolationsaufgaben sind in der Simulation dynamischer Systeme allgegenwärtig. Gegeben sei eine beliebige, bezüglich der x-Werte sortierte Menge an Gitterpunkten (xi,yi). y (xi,yi) x Abbildung 6.2 Menge von Gitterpunkten Für die Simulation oder für deren Auswertung ist man aber auch an beliebigen Zwischenpunkten x (xi-1,xi) an einem Wert y interessiert. Dies gelingt, wenn die Gitterpunktmenge zu einem kontinuierlichen Graphen interpoliert wird. Das einfachste Vorgehen hierbei ist die lineare Interpolation der Punktemenge. y (xi,yi) x Abbildung 6.3 Lineare Interpolation Hierzu bietet PLAN die Klasse TInterpolation an. Diese Klasse besitzt im wesentlichen drei Methoden: int TInterpolation::Set(real x, real y); real TInterpolation::Get(real x); void TInterpolation::Clear(void); Simulation mechatronischer Systeme 133 Die Methode Set() setzt ein Wertepaar. Intern wird dieses Wertepaar bezüglich seines x-Wertes sortiert. Der Returnwert gibt an, ob der Punkt in die Interpolationstabelle aufgenommen werden konnte oder nicht. Die Methode Get() liefert den zum Argument x gehörigen Wert y. Die Methode Clear() schließlich löscht die interne Tabelle. Dies ist nur dann von Interesse, wenn dasselbe Objekt der Klasse TInterpolation noch einmal für eine andere Interpolationsaufgabe genutzt werden soll. Ein illustres, aber nicht unbedingt sinnvolles Anwendungsbeispiel ist die Berechnung des Betrages einer beliebigen Funktion f(x): TInterpolation M; M.Set(2.0,2.0); M.Set(-1.0,1.0); M.Set(0.0,0.0); ..... real z = M.Get(f(x)); Hier ist z = f (x) . Sinnvoller ist diese Form der Interpolation, wenn etwa die Bodenunebenheiten unter einem fahrenden Objekt nur als Punktmenge gegeben sind, und schnelle Interpolationen für die Grafik oder Simulation benötigt werden. Wesentlich ist, dass diese Interpolation auch dann arbeitet, wenn kein Punkt eingegeben (Ergebnis für beliebiges x: y 0) oder nur ein Punkt (x0,y0) eingegeben wurde ( Ergebnis für beliebiges x: y y0). Diese Klasse kann auch nichtlineare (Spline-)Interpolationen durchführen, das wird in den PLAN-Methoden ,Teil 3 dargelegt. Zur Analyse des Frequenzgehaltes von Signalen, Prozessen oder gegebenen Funktionen dient die Fourierreihendarstellung. Ist eine Funktion f auf dem Intervall [0,2 ] gegeben f:x [0,2 ] f(x) ( die periodisch fortgesetzt gedacht werden sollte: f(x) f(x modulo 2 ) ), so ist die Fourierreihendarstellung dieser Funktion gegeben durch a0 a cos x b sin x . 2 1 Hier soll nicht auf den mathematischen Hintergrund der Konvergenz solcher Reihen und die dazu notwendigen Voraussetzungen an die Funktionen f eingegangen werden. f (x) In PLAN existieren die Funktionen real FourierCos(int i, TVektor Fkt, int von, int bis); 134 Simulation mechatronischer Systeme real FourierSin(int i, TVektor Fkt, int von, int bis); Die Funktion FourierCos(int i,....) liefert als Returnwert den Koeffizienten ai, wobei i gleich null ( das liefert den Mittelwert * 2.0: a0) oder größer null gewählt werden kann. Die Funktion FourierSin(int i,...) liefert entsprechend die Koeffizienten bi, hier muss i größer als 0 sein. Im Vektor Fkt erwarten beide Funktionen Funktionswerte auf einem äquidistanten Gitter (!), die optionalen Größen von und bis geben den Koeffizientenbereich des Vektors Fkt an, mit dem die Fourierkoeffizienten berechnet werden. Werden die Größen von und bis nicht angegeben, gehen die Funktionen davon aus, dass alle Koeffizienten im Vektor Fkt zur Auswertung herangezogen werden müssen. Es gibt die folgenden selbsterklärenden Modifikationen dieser Funktionen. real real real real FourierCos(int FourierSin(int FourierCos(int FourierSin(int i, i, i, i, TVektor TVektor TVektor TVektor Fkt, int bis); Fkt, int bis); Fkt); Fkt); Die Funktionswerte werden auf einem äquidistanten Punktgitter im Intervall von 0 bis 2 angenommen. Werden Funktionen auf anderen Intervallen untersucht, müssen entsprechend der Intervalllänge Skalierungen der Koeffizienten vorgenommen werden. Will man nicht nur einzelne oder wenige Fourierkoeffizienten berechnen, sondern die Fourierreihe bis zu einen Index k durchrechnen, lassen sich bei einer geeigneten, von k abhängigen Anzahl von Funktionswerten sehr effektiv die Koeffizienten berechnen (Fast-Fourier-Transformation), darauf wird später eingegangen. Beispiel: Gegeben sei die Sägezahnfunktion f:x 0,2 x- . Ihre Approximation über Fourierreihen unterschiedlicher Ordnung zeigen die nachfolgenden Bilder. Hierin ist Schwarz der Graph der Betragsfunktion im Intervall von 0 bis 2 und Rot die entsprechende Fourierreihe der Ordnung n, n 0 f (x) a0 2 n a cos x 1 b sin x . Simulation mechatronischer Systeme Abbildung 6.5: Fourierreihen der Ordnung 0, 1, 2 für die Sägezahnfunktion 135 136 Simulation mechatronischer Systeme Abbildung 6.6: Fourierreihen der Ordnung 5, 10 und 50 für die Sägezahnfunktion Simulation mechatronischer Systeme 137 In den Abbildungen 6.5 und 6.6 findet man das sogenannte Gibbs'sche Phänomen, ein Überschwingverhalten der Fourierapproximation der Funktion an bestimmten Unstetigkeitsstellen. Um die Wirkung der Fourierreihenapproximation aufzuzeigen, wird nachfolgend ein Programm beschrieben, welches zum einen mehrere Funktionen zur Analyse anbietet und zum anderen die Anzahl der Koeffizienten der Fourierreihe über Tasten variabel einzustellen erlaubt. Das Bildschirmlayout sei gewählt zu Abbildung 6.7: Bildschirmlayout des Fourierprogramms Links sollen die Fourierkoeffizienten aufgeschrieben werden, rechts auf der Bildfläche soll die gewählte Funktion angegeben werden sowie grafisch die Funktion, ihre Fourierapproximation und das Spektrum der Fourierkoeffizienten dargestellt werden. Die dazu nötigen Funktionen sind in der nachfolgenden Klassendefinition gegeben. class TUser : public TPlan { TVektor X, Fkt, FFkt, AI, BI; int FourierAnz, Funkt; public: 138 Simulation mechatronischer Systeme void void void void void void void void void void void void void void void void void void void void void void void void void SchreibeTitel(void); SchreibeKoeffizienten(void); SetzeFunktion(int n); ZeichneFunktion(void); SetzeKoeffizienten(int n); ZeichneFourier(void); Init(void); InitDraw(void); RunTaste0(void); RunTaste1(void); RunTaste2(void); RunTaste3(void); RunTaste4(void); RunTaste5(void); RunTaste6(void); RunTaste7(void); RunTaste8(void); RunTaste9(void); RunTaste10(void); RunTaste11(void); RunTaste12(void); RunTaste13(void); RunTaste15(void); RunTaste17(void); RunTaste19(void); }; Die Vektoren enthalten: X die äquidistanten Stützstellen xi 0,2 ], Fkt die zugehörigen Funktionswerte, FFkt die zugehörigen Funktionswerte der Fourierreihe. Die Vektoren AI und BI enthalten die Fourierkoeffizienten. Die Größe FourierAnz gibt die gewählte Anzahl von Fourierkoeffizienten an, Funkt die Nummer der aktuellen Funktion. Die Aufgaben der Funktionen dieses Beispieles im Einzelnen: void SchreibeTitel(void); Teilt den Bildschirm in Weiß und Hellgrau auf und schreibt Überschriften und die aktuelle Funktion. void SchreibeKoeffizienten(void); Schreibt in das weiße Feld die Fourierkoeffizienten. void SetzeFunktion(int n); Setzt eine der vordefinierten Funktionen und berechnet die Funktionswerte zu den Werten im Vektor X. Hierbei werden links und rechts vom Intervall [0,2 ] periodische Fortsetzungen erzwungen. void ZeichneFunktion(void); Hier wird nur die aktuell ausgewählte Funktion in das Plot gezeichnet. Simulation mechatronischer Systeme 139 void SetzeKoeffizienten(int n); Hier werden die Fourierkoeffizienten berechnet und in die Vektoren AI und BI geschrieben. void ZeichneFourier(void); In dieser Funktion werden die Koeffizienten zum Aufbau der Fourierreihe genutzt und die entsprechenden Funktionswerte in den Vektor FFkt geschrieben. Dieser wird schließlich in das Plot eingezeichnet. void Init(void); Wie üblich werden hier Felder allokiert und die Plots generiert. Auch werden hier sinnvoller weise die Usertasten definiert. void InitDraw(void); Dient der grafischen Initialisierung. void RunTaste0(void); Wie in allen anderen RunTasteN()-Funktionen auch werden hier Einstellungen zu Funktionsauswahl und Anzahl der Fourierkoeffizienten vorgenommen. Das nachfolgende Programm hat nun die Form: //================================================================== // Programm Fourier (Kap. 6.2) //-----------------------------------------------------------------#include <vcl.h> #pragma hdrstop //-----------------------------------------------------------------#pragma argsused #include "Plan.h" //=== Namen der Funktionen global definiert char *Funktionen[] = { "f(x) = sin(x) - 0.1*cos(5x)", "f(x) = 0.5 + 1*cos(x) - 1*sin(3x) + 0.4*cos(6x)- 0.3*sin(49x)", "f(x) = cos(x + 3.0*x*x)", "f(x) = (x-pi)*cos(5.26(x-pi)**2)", "f(x) = Betrag(x - pi) ", "f(x) = x - pi ", "f(x) = x**2 "}; int FunktionenAnz = 7; class TUser : public TPlan { TVektor X, Fkt, FFkt, AI, BI; 140 Simulation mechatronischer Systeme int FourierAnz, Funkt; public: void SchreibeTitel(void); void SchreibeKoeffizienten(void); void SetzeFunktion(int n); void ZeichneFunktion(void); void SetzeKoeffizienten(int n); void ZeichneFourier(void); void Init(void); void InitDraw(void); void RunTaste0(void); void RunTaste1(void); void RunTaste2(void); void RunTaste3(void); void RunTaste4(void); void RunTaste5(void); void RunTaste6(void); void RunTaste7(void); void RunTaste8(void); void RunTaste9(void); void RunTaste10(void); void RunTaste11(void); void RunTaste12(void); void RunTaste13(void); void RunTaste15(void); void RunTaste17(void); void RunTaste19(void); }; void TUser::SchreibeTitel(void){ View(240,0,GetMaxW()-240,GetMaxH()); Clear(Hellgelb); BrushColor = Klar; TextSize = 40; TextColor = Hellrot; TextRect(250,0,GetMaxW()-250,50,"Fourieranalyse",0); TextSize = 25; TextColor = Schwarz; char ss[300]; sprintf(ss,"Funktion: %s",Funktionen[Funkt]); TextRect(250,50,GetMaxW()-250,40,ss);} void TUser::SchreibeKoeffizienten(void){ int x1, x2, y; View(0,0,240,GetMaxH()); Clear(); TextSize = 25; TextRect(10,10,230,30,"Berechnete",0); TextRect(10,40,230,30,"Fourierkoeffizienten",0); TextRect(10,70,110,30,"cos:"); TextRect(120,70,110,30,"sin:"); TextSize = 0; x1 = 10 +TextW("a99 = ")+ 5; x2 = x1 + 120; char ss[120]; Simulation mechatronischer Systeme 141 y = 100; sprintf(ss,"a 0 ="); Text(10,y,ss); sprintf(ss,"% 8.3le",AI[0]); Text(x1,y,ss); y+=10; for(int i=1;i<=FourierAnz;i++){ y+=12; sprintf(ss,"a%2d =",i); Text(10,y,ss); sprintf(ss,"b%2d =",i); Text(120,y,ss); sprintf(ss,"% 8.3le",AI[i]); Text(x1,y,ss); sprintf(ss,"% 8.3le",BI[i]); Text(x2,y,ss);} } void TUser::SetzeFunktion(int n) { if(n<0) n = Funkt; if(n>6) n=6; int i; Funkt = n; //=== Auswahl Funktion switch(n){ case 1: for(i=100;i<501;i++) Fkt(i) = 0.5+1.0*cos(X(i))-1.0*sin(3.0*X(i)) +0.4*cos(6.0*X(i))-0.3*sin(49.0*X(i)); break; case 2: for(i=100;i<501;i++) Fkt(i) = cos(X(i)+3.0*X(i)*X(i)); break; case 3: for(i=100;i<501;i++) Fkt(i) = (X(i)-M_PI) *cos(5.26*(X(i)-M_PI)*(X(i)-M_PI)); break; case 4: for(i=100;i<501;i++) Fkt(i) = fabs(X(i)-M_PI); break; case 5: for(i=100;i<501;i++) Fkt(i) = X(i)-M_PI; break; case 6: for(i=100;i<501;i++) Fkt(i) = X(i)*X(i); break; default: for(i=100;i<501;i++) Fkt(i) = sin(X(i))-0.1*cos(5.0*X(i)); break;} //=== periodische Fortsetzung for(i=0;i<100;i++) Fkt(i) = Fkt(i+400); for(i=500;i<600;i++) Fkt(i) = Fkt(i-400); } void TUser::ZeichneFunktion(void){ real Max = Fkt.Max(100,499); real Min = Fkt.Min(100,499); real z = (Max-Min)*0.3; 142 Simulation mechatronischer Systeme Max+=z; Min-=z; Plot0->Achse(1,Min,Max); Plot0->Kurve0->Size = 3; Plot0->Kurve0->Color = Schwarz; Plot0->Kurve0->PolyLine(X,Fkt,0,99); Plot0->Kurve0->PolyLine(X,Fkt,500,599); Plot0->Kurve0->Color = Hellrot; Plot0->Kurve0->PolyLine(X,Fkt,100,499);} void TUser::SetzeKoeffizienten(int n){ if(n<0) n = FourierAnz; if(n>99) n = 99; FourierAnz = n; AI.Clear(); BI.Clear(); AI(0) = FourierCos(0,Fkt,100,500); BI(0) = 0.0; int i; for(i=1;i<=FourierAnz;i++){ AI(i) = FourierCos(i,Fkt,100,500); BI(i) = FourierSin(i,Fkt,100,500);} int j; real z; for(i=100;i<500;i++){ FFkt(i) = AI(0)/2.0; for(j=1;j<=FourierAnz;j++){ z = X(i)*((real)j); FFkt(i)+=AI(j)*cos(z)+BI(j)*sin(z);}} //=== periodische Fortsetzung for(i=0;i<100;i++) FFkt(i) = FFkt(i+400); for(i=500;i<600;i++) FFkt(i) = FFkt(i-400); i = FourierAnz; if(i<10) i = 10; Plot1->Achse(0,1.0,(real)(i+1)); } void TUser::ZeichneFourier(void){ Plot0->Kurve1->Color = Hellblau; Plot0->Kurve1->Size = 1; Plot0->Kurve1->Line(X,FFkt,0,599); Plot1->Reset(); real z; for(int i=1;i<=FourierAnz+10;i++){ z = AI(i)*AI(i)+BI(i)*BI(i); if(z>1.0e-10) z = sqrt(z); Plot1->Kurve0->LineTo((real)i,z); Plot1->Kurve1->LineTo((real)i,fabs(AI(i))); Plot1->Kurve2->LineTo((real)i,fabs(BI(i)));}} void TUser::Init(void){ //=== Felder allokieren Simulation mechatronischer Systeme X.Size(600); real z = -0.5*M_PI; for(int i = 0;i<600;i++){ X(i) = z; z+=M_PI/200.0;} Fkt.Size(600); FFkt.Size(600); AI.Size(100); BI.Size(100); //=== Plots generieren int i = GetMaxH()-100; Plot0->Size(250,100,GetMaxW()-250,i/2-10); Plot0->Titel = "Fouriertransformation"; Plot0->Untertitel = "Schwarz/Rot: Funktion, Blau: Approximation"; Plot0->Achse(0,"unabhängige Variable"); Plot0->Achse(0,-0.5*M_PI,2.5*M_PI); Plot0->Achse(1,"Funktion, Approximation"); Plot0->ClearMode = false; Plot1->Size(250,100+i/2,GetMaxW()-250,i/2-10); Plot1->Titel = "Frequenzspektrum"; Plot1->Untertitel = "Schwarz Gesamt, Gelb: cos, Gruen: sin"; Plot1->Achse(0,"Frequenz (Einheit: Grundfrequenz)"); Plot1->Achse(0,0.0,10.0); Plot1->Achse(1,"Gewichte"); Plot1->Achse(1,-2.0,2.0,0.5); Plot1->Kurve0->Color = Schwarz; Plot1->Kurve0->Size = 3; Plot1->Kurve1->Color = Gelb; Plot1->Kurve1->Size = 3; Plot1->Kurve2->Color = Gruen; Plot1->Kurve2->Size = 3; SetzeFunktion(0); SetzeKoeffizienten(5); //=== Tasten generieren TastenfeldName = "FunktionsNr / Fourierordnung"; InsertTaste(0,"Funktion 0",""); InsertTaste(2,"Funktion 1",""); InsertTaste(4,"Funktion 2",""); InsertTaste(6,"Funktion 3",""); InsertTaste(8,"Funktion 4",""); InsertTaste(10,"Funktion 5",""); InsertTaste(12,"Funktion 6",""); InsertTaste(1,"Ordnung 0",""); InsertTaste(3,"Ordnung 1",""); InsertTaste(5,"Ordnung 2",""); InsertTaste(7,"Ordnung 3",""); InsertTaste(9,"Ordnung 5",""); InsertTaste(11,"Ordnung 10",""); InsertTaste(13,"Ordnung 20",""); InsertTaste(15,"Ordnung 30",""); 143 144 Simulation mechatronischer Systeme InsertTaste(17,"Ordnung 50",""); InsertTaste(19,"Ordnung 80",""); } void TUser::InitDraw(void){ SchreibeTitel(); SchreibeKoeffizienten(); ZeichneFunktion(); ZeichneFourier(); } void TUser::RunTaste0(void){ SetzeFunktion(0); SetzeKoeffizienten(-1); InitDraw();} void TUser::RunTaste2(void){ SetzeFunktion(1); SetzeKoeffizienten(-1); InitDraw();} void TUser::RunTaste4(void){ SetzeFunktion(2); SetzeKoeffizienten(-1); InitDraw();} void TUser::RunTaste6(void){ SetzeFunktion(3); SetzeKoeffizienten(-1); InitDraw();} void TUser::RunTaste8(void){ SetzeFunktion(4); SetzeKoeffizienten(-1); InitDraw();} void TUser::RunTaste10(void){ SetzeFunktion(5); SetzeKoeffizienten(-1); InitDraw();} void TUser::RunTaste12(void){ SetzeFunktion(6); SetzeKoeffizienten(-1); InitDraw();} void TUser::RunTaste1(void){ SetzeKoeffizienten(0); InitDraw();} void TUser::RunTaste3(void){ SetzeKoeffizienten(1); InitDraw();} void TUser::RunTaste5(void){ SetzeKoeffizienten(2); Simulation mechatronischer Systeme 145 InitDraw();} void TUser::RunTaste7(void){ SetzeKoeffizienten(3); InitDraw();} void TUser::RunTaste9(void){ SetzeKoeffizienten(5); InitDraw();} void TUser::RunTaste11(void){ SetzeKoeffizienten(10); InitDraw();} void TUser::RunTaste13(void){ SetzeKoeffizienten(20); InitDraw();} void TUser::RunTaste15(void){ SetzeKoeffizienten(30); InitDraw();} void TUser::RunTaste17(void){ SetzeKoeffizienten(50); InitDraw();} void TUser::RunTaste19(void){ SetzeKoeffizienten(80); InitDraw();} //-----------------------------------------------------------------int main(int argc, char* argv[]) { TUser U; U->Execute(); return 0; } //------------------------------------------------------------------ Am Bildschirm erhält man beispielsweise das folgende Bild: 146 Simulation mechatronischer Systeme Abbildung. 6.8 Die Oberfläche des Programms PrFourier.exe Wie genau die Ermittlung der Fourierkoeffizienten hier erfolgt, zeigt die Fourieranalyse der Funktion f(x) = 0.5 + 1.0*cos(x) – 1.0*sin(3x) + 0.4*cos(6x) - 0.3*sin(49x). Hier lassen sich die Koeffizienten einfach ablesen: a0=1.0, a1=1.0, b3=-1.0, a6=0.4, b49=0.3. In dem mit PLAN generierten Programm PrFourier.exe liefert die Analyse bis zur Ordnung 50: Simulation mechatronischer Systeme 147 Abbildung. 6.9: Zur Genauigkeit der numerischen Fourieranalyse. also exakt die oben angegebenen Werte, die übrigen Werte sind 14 (!) Größenordnungen kleiner, numerisch also gleich null, wie der folgende kleine Ausschnitt zeigt: 148 Simulation mechatronischer Systeme Abbildung 6.10: Zur Genauigkeit der berechneten Fourierkoeffizienten Dieses Beispiel nutzte nun die Tasten von PLAN intensiv, um die Schnittstelle zum User aufzubauen. Tatsächlich sind aber unter Borland, CodeGear oder Windows außerordentlich mächtige Werkzeuge zur Oberflächengestaltung gegeben, die man sehr einfach in PLAN einbinden kann. Dies wird im Methodenteil 3 aufgezeigt. Simulation mechatronischer Systeme 149 6.3 Numerische Integration Eine Animation wie in Kapitel 2.3 und 2.4 hat zunächst noch wenig mit Simulation zu tun. Eine Animation eines physikalischen Systems macht im allgemeinen nur Sinn, wenn man ein System nach den als richtig erkannten Bewegungsgleichungen führt bzw. in seinem Verhalten beobachtet. Die Bewegungsgleichungen sind üblicherweise Differentialgleichungen. Und da bei realen Systemen fast immer die Bewegungsgleichungen nichtlinear und praktisch analytisch nicht lösbar sind, ist man auf die numerische Integration angewiesen. Hierzu gibt es mächtige numerische Werkzeuge, die man in der Literatur zur numerischen Mathematik erklärt findet. In PLAN sind die numerischen Verfahren Differenzschrittverfahren (Eulersche Polygonzugmethode) Heunverfahren Runge-Kutta Verfahren 7/8 Ordnung mit den Fehlbergkoeffizienten eingebettet. Das Heunverfahren wie auch das Runge-Kutta-Fehlbergverfahren kann mit oder ohne Schrittweitensteuerung genutzt werden. Integriert werden soll die einfache Oszillatorgleichung m x + c x = 0 ( mit c/m = 1.0). Mit den Anfangswerten x (t0=0) = 0.0, x (t0=0) = 1.0 besitzt diese Gleichung die exakte Lösung x(t) = sin(t). Diese Lösung soll im Programm mit der numerischen Lösung bei unterschiedlichen Verfahren verglichen werden. Um eine numerische Integration der obigen Gleichung zu erhalten, müssen natürlich zunächst Angaben in PLAN gemacht werden, die die Verfahren über Art und Umfang der numerischen Integration aufklären. Im allgemeinen wird man in der Init()-Funktion diese Rahmendaten dem Programm mitteilen. GesamtOrdnung Anfangszeit Endzeit Schrittweite Toleranz Verfahren = 2; = 0.0; = 10.0; = 0.1; =1.0e-5; = 2; 150 Simulation mechatronischer Systeme Hier wird zunächst die Gesamtordnung des Systems angegeben. Dieses Statement nutzt PLAN, um interne Felder zu allokieren. Sie können diese Variable, genauer diese Property, auch gleich danach abfragen, ob die Initialisierung gelungen ist oder nicht. Beispiel: GesamtOrdnung = 5000; if(GesamtOrdnung!=5000) DasGingSchief(); else MacheWeiter(); Das Programm geht davon aus, daß Sie mit Differentialgleichungen 2. Ordnung arbeiten wollen, wie sie üblicherweise in der Mechanik auftreten. Wenn Sie auch mit Differentialgleichungen 1. Ordnung arbeiten wollen, können Sie die Propertygröße Gln1Ordnung auf einen Wert kleiner oder gleich GesamtOrdnung setzen. Defaultmäßig ist Gln1Ordnung auf Null gesetzt. Anfangszeit und Endzeit müssen natürlich gegeben sein, sonst weiß die numerische Integration nicht, was sie tun soll. Wirkungsgleich hierzu ist die Funktion Intervall(real von, real bis): Interval(0.0,10.0); Die Schrittweite sollte als Startwert für die numerische Integration angegeben werden. Defaultwert ist 1/1000 der Integrationsintervalllänge. Wenn keine Schrittweitensteuerung gewünscht wird, rechnet das Programm mit genau dieser Schrittweite. Der Toleranz gibt man den gewünschten Wert der Genauigkeit pro Integrationsschritt an. Wird die Toleranz zu Null oder gar negativ gewählt, geht PLAN davon aus, das Sie mit konstanter Schrittweite, also ohne Schrittweitensteuerung, rechnen wollen. Der Wert der Größe Verfahren schließlich bestimmt das Integrationsverfahren. Der Wert 0 steht für das Differenzenverfahren (Ordnung = O(h)). Bei diesem Verfahren wird der Wert von Toleranz ignoriert. Der Wert 1 steht für das Heunverfahren. Ist Toleranz <=0.0, so wird nur mit dem Heunverfahren mit konstanter Schrittweite gerechnet (Ordnung = O(h2)), andernfalls wird mit einem dazugehörenden Runge-Kutta-Verfahren (Ordnung = O(h3)) die Schrittweite immer so gewählt, dass die Genauigkeit pro Schritt möglichst kleiner als dieser Wert gehalten wird. Der Wert 2 schließlich steht für das Runge-Kutta-Fehlberg-Verfahren 7/8 (Ordnung = O(h8)), das geschwindigkeitsoptimiert mit den exakten Fehlbergkoeffizienten rechnet. Auch hier führt ein Wert kleiner oder gleich Null bei der Toleranz zur Rechnung mit fester Schrittweite und bei einem positiven Wert der Toleranz zur Rechnung mit Schrittweitensteuerung. Wie nun greift man auf die Variablen zu? Simulation mechatronischer Systeme 151 Die unabhängige Variable ist in PLAN die Größe Zeit. Diese (Property-)Variable kann man jederzeit lesen, überschreiben kann man sie nicht. Ein entsprechender Versuch führt zum Abbruch der Compilierung Ihres Programmes. Die abhängigen Variablen, die die Differentialgleichungen 2. Ordnung bilden, heißen X2O(int Nr) (x zweiter Ordnung) und sind als Referenzen ausgebildet. Die abhängigen Variablen für die Differentialgleichungen erster Ordnung heißen X1O(int Nr) (x erster Ordnung). Da in obigem Beispiel nur eine Variable (bzgl. der Differentiation) zweiter Ordnung vorliegt, ist die Zuordnung: X2O(0) X2OP(0) X2OPP(0) x(t) x (t) x(t) Die obige Differentialgleichung ist also einfach nur X2OPP(0) = - (c/m)*X2O(0); Die numerische Integration benötigt natürlich diese Differentialgleichung. Hier existiert in TPlan eine virtuelle Funktion DGL(), in der PLAN das Differentialgleichungssystem in expliziter Form erwartet. Mit den oben genannten Parametern lassen sich beliebige Differentialgleichungssysteme erster und zweiter Ordnung bearbeiten. Diese Systeme müssen allerdings in expliziter Form angegeben werden. Dies ist praktisch immer möglich, PLAN bietet dazu etwa komfortable Matrizenoperationen (Inversion) an. Beispiel: Gegeben seien die Differentialgleichungen x y 2.0 x (1.0 - x 2 ) x - sin ( y) 0.2 (y - x ) 0.1 y p 0.1 sin (t) 0.05 p 0.1 (x * y - p) Dies ist ein System 5.ter Ordnung, x und y sind die abhängigen Variablen 2.ter Ordnung und p ist von erster Ordnung. Zur weiteren Darstellung wird das System umgeschrieben: x y p 2.0 x (1.0 - x 2 ) x 0.2 (y - x ) 0.1 y 0.1 (x * y - p) sin ( y) 0.1 sin (t) 0.05 p Zur Beschreibung des Systems in PLAN benötigt man die Angaben: 152 Simulation mechatronischer Systeme GesamtOrdnung = 5; Gln1Ordnung = 1; .... und das Differentialgleichungssystem selbst wird beschrieben mit den Variablen X2O(0) (= x), X2O(1) (= y) und X1O(0) (= p): X2OPP(0) = X2OPP(1) = X1OP(0) = -2.0*X2OP(0)*(1.0-X2O(0)*X2O(0))-X2O(0) + sin(X2O(1))+0.1*sin(Zeit); -0.2*(X2OP(1)-X2OP(0))-0.1*X2O(1) +0.05*X1O(0); 0.1*(X2O(0)*X2O(1)-X1O(0)); Doch nun zurück zum Eingangsbeispiel. Die Differentialgleichung war: X2OPP(0) = - c*X2O(0)/m; Nun braucht die numerische Integration natürlich noch Anfangswerte. Mit den oben genannten Anfangsbedingungen gilt: X2O(0) = 0.0; X2OP(0) = 1.0; Da diese Anfangswerte sinnvollerweise immer dann gesetzt werden müssen, wenn man die Integration wieder von vorn anfängt, werden diese Informationen in die Reset()-Funktion eingesetzt. Dort wird auch die Funktion InitIntegration(); aufgerufen, welche die internen Daten bei der numerischen Integration wieder auf den Anfang setzt. Die eigentliche Integration wird mit einer der Funktionen Integrationsschritt(int anz=1); Integrationsschritt(real dt); ausgeführt. Die erste Funktion führt anz Integrationsschritte aus, die zweite Funktion integriert die unabhängige Variable um dt weiter, egal wie viele Schritte dazu notwendig sind. Wenn die Endzeit der Integration erreicht ist, oder ein Fehler aufgetreten ist, ist die boolsche (property-)Variable Fertig gleich true. Die boolsche (Property-) Variable Fehler wird auf true gesetzt, wenn etwas schief ging. Beispiele: Simulation mechatronischer Systeme 153 while(!Fertig) Integrationsschritt(); Hier wird solange ein Integrationsschritt nach dem anderen ausgeführt, bis entweder ein Fehler oder aber das Ende des Integrationsintervalls erreicht ist. Um die Ausgabe der Daten in geeigneter Form muss man sich selbst kümmern. Dazu kann man etwa eine Funktion Ausgabe() definieren, die diese Aufgabe erledigt. while(!Fertig) Integrationsschritt(); Ausgabe(); Dieser Code würde nach jedem Integrationsschritt die Funktion Ausgabe() aufrufen. Üblicherweise ist man aber nur an Ausgaben nach jeweils z.B. 100 Schritten interessiert: while(!Fertig) Integrationsschritt(100); Ausgabe(); oder zu festen Zeitpunkten; bei der Animation etwa nach jeweils 0.1 sec im System, unabhängig von den aktuellen Schrittweiten: while(!Fertig) Integrationsschritt(0.1); Ausgabe(); Um nun noch Einfluss auf die numerische Integration nehmen zu können (z.B. sie zu stoppen oder das System zu animieren), wird man in der Run()-Funktion etwa wie folgt vorgehen: void TUser::Run(void) Integrationsschritt(0.1); Ausgabe(); CallRun = !Fertig; Hier wird also nach einer Integration um 0.1 sec kurz in das Betriebssystem zurückgeschaltet, das erst mal anliegende Mausklicks etc. untersucht und dann wieder Ihre Run()-Funktion aufruft. Um zu entscheiden, ob bei der numerischen Integration ein Fehler unterlaufen ist, kann man noch die boolsche Variable Fehler abfragen. Die Numerik ist sinnvoll nur dann abgeschlossen, wenn Fertig gleich true und Fehler = false ist. 154 Simulation mechatronischer Systeme Die Grundstruktur eines Simulationsprogramms mit numerischer Integration besteht also in Stichworten aus den Funktionen/Aufgaben: void Init(void) Angaben zu Gesamtordnung, zum Integrationsintervall, Aufruf von Reset(), weitere Initialisierungen ... void Reset(void) Angabe der Anfangswerte, Aufruf der Integrationsinitialisierung InitIntegration() void DGL(void) Angabe des Differentialgleichungssystems void Run(void) Durchführung von Integrationsschritten, Ausgabe, Zeichnung Mit dieser Struktur können Sie jederzeit die Integration mit der Reset-Taste zurücksetzen und wiederholen. Mit diesen Bemerkungen dürfte der nachfolgende Sourcecode keine Schwierigkeiten mehr machen. Die Ausgabe ist hier einfach nur ein entsprechender Text auf dem Bildschirm. Die Tabelle wird in der Funktion void InitDraw(void) optisch generiert. //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Numerische Integration // Programm 1 (Kapitel 6.3) // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { int y; public: void Init(void); void InitDraw(void); void DGL(void); void Run(void); void Reset(void); }; void TUser::Init(void){ GesamtOrdnung = 2; Schrittweite = 0.01; Toleranz = 1.0e-8; Intervall(0.0,6.24); Verfahren = 2; Simulation mechatronischer Systeme 155 Reset();} void TUser::InitDraw(void){ char ss[199], sh[50]; switch(Verfahren){ case 2: sprintf(sh,"Runge Kutta Fehlberg 7/8"); break; case 1: sprintf(sh,"Heun, Runge Kutta 3"); break; default: sprintf(sh,"Eulerverfahren");} sprintf(ss,"Harmonischer Oszillator, Intervall: [%10.3e,%10.3e]," " Verfahren : %s", Anfangszeit, Endzeit,sh); TextSize = 20; Text(20,20,ss); TextSize = 0; Text(20,60,"Aufgelistete Daten sind Zeit, x-Punkt," " x und der abs. Fehler"); SetPen(Schwarz); MoveTo(0,80); LineTo(GetMaxW(),80); } void TUser::DGL(void){ X2OPP(0) = -X2O(0);} void TUser::Run(void){ Integrationsschritt(); CallRun = !Fertig; char ss[200]; sprintf(ss,"t = % 8.3le, xp = % 8.3le, x = % 8.3le," " Fehler = % 8.3le", Zeit,X2OP(0),X2O(0),X2O(0)-sin(Zeit)); Text(20,y,ss); y+=20;} void TUser::Reset(void){ y = 90; X2O(0) = 0.0; X2OP(0) = 1.0; InitIntegration();} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Mit diesen Zeilen erhält man die folgende Ausgabe am Bildschirm: 156 Simulation mechatronischer Systeme Abbildung 6.11: Numerische Integration, Ausgabe Programm 1 Die Funktion InitDraw()dient hier nur dazu, einen Tabellenkopf für die Textausgabe bereitzustellen. In der letzten Spalte wird der aktuelle Fehler bezogen auf die exakte Lösung angegeben. Die Gesamtsicht des Bildschirms gibt Abbildung 6.12 wieder. Man beachte das Auftreten einer neuen Anzeige direkt unter dem Logo während der numerischen Integration, die den aktuellen Integrationsfortschritt anzeigt. Wird die Integration abgeschlossen, erscheint wieder das bekannte Ausgangsbild. Simulation mechatronischer Systeme 157 Abbildung 6.12: Numerische Integration, Ausgabe Programm 1 Natürlich ist die Textausgabe etwas umständlich, wenn sie etwa für Prüfzwecke eines Berechnungsprogramms zwischen bewegten Objekten auf der Grafikoberfläche von PLAN erscheinen muss. Hier wird man natürlich wie gewohnt die Daten in eine Datei schreiben. Speziell bei PLAN gibt es noch eine weitere sehr einfache Möglichkeit. Da Ihr Projekt ein Konsoleprojekt ist, ist das Konsolefenster unter dem PLAN-Fenster offen. In dieses können Sie mit den klassischen Schreibbefehlen beliebige Informationen schreiben, aber auch Daten einlesen, wenn Sie wollen. Die Funktionen InitDraw() und Run() in Programm 1 lassen sich einfach umschreiben: void TUser::InitDraw(void){ char sh[50]; switch(Verfahren){ case 2: sprintf(sh,"Runge Kutta Fehlberg 7/8"); break; case 1: sprintf(sh,"Heun, Runge Kutta 3"); break; default: sprintf(sh,"Eulerverfahren");} 158 Simulation mechatronischer Systeme printf("Harmonischer Oszillator, Intervall: [%10.3e,%10.3e]\n\n," " Verfahren : %s\n", Anfangszeit, Endzeit,sh);} void TUser::Run(void){ Integrationsschritt(); CallRun = !Fertig; printf("t = % 8.3le, xp = % 8.3le, x = % 8.3le," " Fehler = % 8.3le\n", Zeit,X2OP(0),X2O(0),X2O(0)-sin(Zeit));} Wenn Sie das Programm haben laufen lassen, können Sie das PLAN – Fenster minimieren und das DOS – Fenster wird sichtbar. Abbildung 6.13: Numerische Integration, Ausgabe Programm 5 im DOS-Fenster Eleganter lässt sich die Ausgabe in dieses Fenster mit C++ und den Möglichkeiten von iostream darstellen. Dazu müssen Sie die Bibliothek iostream einbinden mit #include <iostream.h> und dann mit zum Beispiel cout << "Bitte Integer eingeben:"; int i; cin >> i; Ein- und Ausgaben machen. Simulation mechatronischer Systeme 159 6.4 Plotanimationen und 3D - Plots Im Folgenden soll die Funktion z = z(x, t) = e -0.5(x+t) cos(2(x 2 t 2 )) weiter untersucht werden. Zur grafischen Darstellung kann man ein Plot nutzen. Wenn man ein 2D-Plot aufbaut, gibt es die Möglichkeit, die Funktion z(x,t) für verschiedene Werte von t darzustellen. Soll t in einem ganzen Intervall variiert werden, so kann man sich vorstellen, die Funktion z(x,t) in t über der Zeit zu animieren. Dazu muß nur die Plotkurve z(x,t0) für jeweils fest gewähltes t0 gezeichnet werden. Anschliessend wird t0 inkrementiert, t1 = t0 + t und die neue Kurve z(x,t1) im Plot dargestellt. Um eine Funktion, zum Beispiel y = y(x), mit nur einem Befehl zeichnen zu können, gibt es den Plotbefehl bool PolyLine(TVektor &x, TVektor &y, int von, int bis); die den Vektor y über den Vektor x aufträgt. Mit den optionalen Parametern von und bis kann man sich Koeffizientenausschnitte aus den Vektoren darstellen. Dieser Plotbefehl ist in den Plotvariablen Kurve0 bis Kurve 9 enthalten. Will man also die Funktion mit den Attributen etwa von Kurve0 im Plot0 zeichnen, so lautet die Anweisung Plot0->Kurve0->PolyLine(x,y); Dieser Plotbefehl ist sehr schnell und man erhält den Eindruck einer in der Zeit animierten Plotkurve. Der nachfolgende Sourcecode setzt in der Funktion Init() die Plotdaten sowie den Vektor X. In der Run()-Funktion wird die Funktion über den Vektor X und der Größe T berechnet und ausgegeben. Nach Inkrementierung von T wird PLAN veranlasst, die Run()-Funktion wieder aufzurufen. Die Reset()-Funktion setzt den Parameter T wieder auf Null und löscht den Plotinhalt. //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Projektbeschreibung: // 3D - Plots und Animationen Kapitel 6.4 //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { TVektor X, Z; 160 real public: void void void }; Simulation mechatronischer Systeme T; Init(void); Run(void); Reset(void); void TUser::Init(void){ Plot0->Size(); Plot0->Titel = "Funktionen z = z(x,t)"; Plot0->Achse(0,0.0,6.0); Plot0->Achse(1,-1.0,1.0); Plot0->Achse(0,"Variable x"); Plot0->Achse(1,"Funktion z"); Plot0->Kurve0->SetPen(Hellrot,3); X.Size(301); Z.Size(301); real x = 0.0; for(int i=0;i<301;i++){ X(i) = x; x += 0.02;} Reset();} void TUser::Run(void){ for(int i=0;i<301;i++){ Z(i) = exp(-0.5*(T+X(i)))*cos(2.0*(X(i)*X(i)+T*T));} Plot0->Kurve0->PolyLine(X,Z); T+=0.02; CallRun = true;} void TUser::Reset(void){ T = 0.0; Plot0->Reset();} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Am Bildschirm erscheint die animierte Plotkurve. Simulation mechatronischer Systeme 161 Abbildung 6.14 Animierte Plotkurven. Intern wird im Plot erst die Plotkurve gelöscht, wenn man über die Größe Kurve0 ein weiteres Mal die Funktion ausgeben will. Selbstverständlich kann man so über jede der 10 Variablen Kurve0 bis Kurve9 eine Plotkurve animieren, also insgesamt 10 Kurven parallel animieren! Diesen Mechanismus des Löschens der Plotkurven kann man unterdrücken. Defaultmäßig ist die zugehörige (Property-)Variable ClearMode des Plots auf true gesetzt. Wird diese Größe auf false gesetzt, werden die Plotkurven ohne zwischenzeitliche Lösungen alle übereinandergezeichnet, also so etwas wie ein Trace-Modus bei den Plotkurven eingeschaltet. 162 Simulation mechatronischer Systeme Abbildung 6.15 Animierte Plotkurven, ClearMode = false. Eine weitere Möglichkeit der Darstellung dieser Funktion z = z(x,t) ist eine 3DPlotdarstellung. Hierzu bietet PLAN einige einfache Möglichkeiten. Eine dritte Achse kann mit den bekannten Funktionen für die Dateneinteilung oder Beschriftung der 0- oder 1- Achse angegeben werden. Plot0->Achse(0,0.0,6.0); Plot0->Achse(1,"z"); Plot0->Achse(2,"T"); Plot0->Achse(2,0.0,6.0); // // // // 0 1 2 2 – Achse Achse Achse Achse Wann immer im Plot die Größe Plot3D auf true gesetzt wird, wird die 2-Achse in den Raum gezeichnet. Äquivalent hierzu ist die Größe Plot2D, sodass ein einfaches Umschalten zwischen 2D – und 3D – Plot zum Beispiel mit der Anweisung Plot0->Plot3D = Plot0->Plot2D; // Umschalten 2D<>3D möglich ist. Ganz analog lassen sich die boolschen Größen Links und Rechts sowie Steil und Flach im Plot steuern. Plot0->Links = Plot0->Rechts; // Umschalten Links<>Rechts Plot0->Steil = Plot0->Flach; // Umschalten Steil<>Flach Simulation mechatronischer Systeme 163 Die Wirkung dieser Größen zeigt das nachfolgende Bild. Abbildung 6.16 Variation der Größen Steil, Flach, Links, Rechts im 3D-Plot. Mit dem Befehl PolyLine() können auch im 3D-Plot Kurven gezeichnet werden bool PolyLine(TVektor &x, TVektor &y, real z, int von, int bis); Ist die Variable z außerhalb des durch die 2-Achse gegebenen Intervalls, wird false zurückgegeben. Dies lässt sich zum Beispiel nutzen, um das 'Überlaufen' des Plots zu verhindern. Wesentlich ist, dass PolyLine() für aufsteigende z-Werte, also von "hinten" nach "vorne", ein Gitternetz zwischen den Knoten des vorhergehenden PolyLine()-Befehls und dem aktuellen PolyLine()-Befehl zeichnet. 164 Simulation mechatronischer Systeme Z PolyLine(X,Z,T0) PolyLine(X,Z,T1), T1>T0 T X Abbildung 6.17 Der PolyLine() – Befehl, ClearMode = false im 3D-Plot. Es liegt in der Verantwortung des Users, dass das Gitternetz nicht zu dicht wird. Ist im 3D-Plot die Größe ClearMode gleich false, Plot0->ClearMode = false; so ist das Gitternetz durchsichtig. Wenn ClearMode auf true gesetzt wird, wird die Fläche zwischen zwei aufeinander folgenden Polygonen in Dreiecke aufgeteilt, deren "obere" Seite mit der Farbe f1 und deren untere Fläche mit der Farbe f2 gezeichnet wird. Diese Farben können mit dem Befehl Plot0->Kurve0->SetPlaneColor(TColor f1, TColor f2) gesetzt werden. Defaultmäßig sind diese Farben auf Weiß und Schwarz gesetzt. Es wird hier also eine Visibilitätsberechnung durchgeführt. Dies ist eigentlich nur eine 2 ½ D Visualisierung, die bezüglich der dritten Achse richtungsgebunden ist. Es gibt aber auch noch den Befehl Plane() in unterschiedlichen Formen, der ein echtes 3D – Bild erstellt. Dies wird im Methodenteil drei aufgezeigt. Simulation mechatronischer Systeme 165 Z PolyLine(X,Z,T0) PolyLine(X,Z,T1), T1>T0 T Abbildung 6.18 Der PolyLine() – Befehl, ClearMode = true im 3D-Plot. Trägt man obige Funktion z = z(x,t) im 3D-Plot auf, so erhält man das Bild 6.19. Darin sind die Größen längs der 0- und 1-Achse mit je 80 ( = grob) und mit 150 (= fein) Stützpunkten ausgegeben. Abbildung 6.19 Funktion z(x,T) grob und fein, ClearMode = false. 166 Simulation mechatronischer Systeme Abbildung 6.20 Funktion z(x,T) grob, ClearMode = true. Natürlich kann man auch wieder mit der Maus auf das Plot klicken. Es öffnet sich ein kleines Fenster mit Hotkeys. Wird der Hammer gedrückt, können Sie alle Optionen des Plots verstellen und die resultierende Wirkung sofort sehen. Nachfolgend ist der einfache Code des Programms angegeben. //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Projektbeschreibung: // 3D - Plots und Animationen Kapitel 6.4 //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { TVektor X, Z; real T; int N; Simulation mechatronischer Systeme public: void void void void void void void void void }; Init(void); Run(void); Reset(void); RunTaste0(void); RunTaste1(void); RunTaste2(void); RunTaste3(void); RunTaste6(void); RunTaste7(void); void TUser::Init(void){ BMPSystem = true; Plot0->Size(); Plot0->Titel = "Funktionen z = z(x,t)"; Plot0->Achse(0,0.0,6.0); Plot0->Achse(1,-1.0,1.0); Plot0->Achse(0,"x"); Plot0->Achse(1,"z"); Plot0->Achse(2,0.0,6.0); Plot0->Achse(2,"T"); Plot0->Kurve0->SetPen(Hellrot); Plot0->Kurve0->SetPlaneColor(Weiss,Schwarz); X.Size(301); Z.Size(301); N = 80; Reset(); Plot0->ClearMode = false; InsertTaste(0,"ClearMode", "Umschalten des ClearModes"); InsertTaste(1,"2D - 3D", "Umschalten zwischen 2D-Plot und 3D-Plot"); InsertTaste(2,"Steil/Flach"); InsertTaste(3,"Links/Rechts"); InsertTaste(6,"Fein"); InsertTaste(7,"Grob"); } void TUser::Run(void){ for(int i=0;i<N+1;i++){ Z(i) = exp(-0.5*(T+X(i)))*cos(2.0*(X(i)*X(i)+T*T));} CallRun = Plot0->Kurve0->PolyLine(X,Z,T,N); T+=6.0/((real)N); } void TUser::Reset(void){ T = 0.0; real x = 0.0; real dx = 6.0/((real)N); for(int i=0;i<N+1;i++){ X(i) = x; x += dx;} Plot0->Reset();} void TUser::RunTaste0(void){ 167 168 Simulation mechatronischer Systeme Plot0->ClearMode = !Plot0->ClearMode;} void TUser::RunTaste1(void){ Plot0->Plot3D = Plot0->Plot2D;} void TUser::RunTaste2(void){ Plot0->Steil = Plot0->Flach;} void TUser::RunTaste3(void){ Plot0->Links = Plot0->Rechts;} void TUser::RunTaste6(void){ N = 150; Reset();} void TUser::RunTaste7(void){ N = 80; Reset();} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Abbildung 6.21: Layout des Plotprogramms dieses Kapitels Simulation mechatronischer Systeme 169 7. Einfache dynamische Systeme In den nachfolgenden Beispielen sollen erste einfache Anwendungen des vorgestellten Werkzeuges PLAN angeführt werden. Es geht dabei nicht um die mathematisch vollständige Erfassung der auftretenden Phänomene, sondern um einen schnellen Einstieg in Grundfragen des dynamischen Verhaltens von Systemen. Das erste Beispiel will an einem einfachen linearen Einmassenschwinger sowohl die grafische Animation als auch eine erste Plotdarstellung der Lösung aufzeigen. Mit verschiedenen Usertasten werden unterschiedliche Dämpfungen eingestellt. Das zweite Beispiel führt Techniken zu Gewinnung eines Phasenportraits einer nichtlinearen Differentialgleichung an. Der Rechner bietet dabei zwar große Hilfen, aber um alle Strukturen der Phasenebene zu erhalten, muss man etwas nachdenken oder ausprobieren. Entsprechend werden unterschiedliche Herangehensweisen aufgrund der Numerik angeführt. Das entsprechende Programm ist sehr klein (Stichwort: experimentelle Mathematik!). Sehen Sie in ein Buch über nichtlineare Schwingungen! Das dritte Beispiel will ein mathematisches Doppelpendel simulieren und animieren. Es ist bekannt, dass das Doppelpendel bei bestimmten Anfangsbedingungen auch chaotische Schwingungen ausführen kann. Hier soll nur die prinzipielle Vorgehensweise zur Animation dargestellt werden, wobei einige ausgewählte Anfangswerte fest vorgegeben werden. Das letzte etwas komplexere Beispiel schließlich untersucht ein Laufrad, das auf rauem Untergrund läuft und gegen einen künstlichen Horizont – der hier die Karosserie ersetzt – über Feder und Dämpfer abgestützt wird. Neben der Animation wird hier die Frage untersucht, wie der Dämpfer die Kontaktzeit des Laufrades mit dem Untergrund beeinflusst. Diese Frage lässt sich elegant durch eine Erweiterung des Differentialgleichungssystems um eine Gleichung erster Ordnung lösen. Diese Technik, Mittelwerte mit Hilfe der numerischen Integration zu erhalten, wird man sicher immer wieder benötigen. 170 7.1 Simulation mechatronischer Systeme Ein Feder-Masse System Dieses Beispiel soll die Anwendung der numerischen Integration in PLAN aufzeigen. Das hier zu untersuchende Modell ist ein Einmassenschwinger. In der Mechanik wird ein solches Modell in der folgenden Form skizziert: c b m Abbildung 7.1: Symbol eines Einmassenschwingers Die zugehörige Bewegungsgleichung lautet einfach m x b x cx 0. Die Division durch die Masse m liefert eine Standardform dieser Gleichung: x 2 D x 2 x 0, c , D m b , 2 c m in der die Kreisfrequenz und D das Lehrsche Dämpfungsmaß angibt. Je nach Wert von D erhält man angefachte (D<0), ungedämpfte (D=0), schwach gedämpfte Schwingungen (0<D<1) oder stark gedämpfte Schwingungen. Für die Simulation wird eine feste Auslenkung als Anfangswert geplant. Die Dämpfung soll variiert werden können. Es bietet sich an, fünf bis zehn unterschiedliche Werte über zusätzliche Tasten vorzugeben. Nun ist die Lösung natürlich gleich hinzuschreiben, trotzdem soll die Differentialgleichung explizit numerisch gelöst werden. Bei der Simulation bietet es sich an, neben der Bewegung des Einmassenschwingers ein Plot aufzubauen, dass die Bewegung der Masse simultan aufnimmt. Das Layout der Zeichenfläche könnte also wie folgt aussehen: Simulation mechatronischer Systeme 171 Plot Abbildung 7.2: Geplantes Layout des Bildschirms. Die linke Hälfte nimmt die grafische Darstellung eines Massenpunktes auf, der sich auf- und ab bewegt, die rechte Hälfte enthält ein Plot, das die Bewegung der Masse simultan mitschreibt. Damit lassen sich schon die notwendigen Funktionen in der von TPlan abgeleiteten Klasse TUser und deren Aufgaben skizzieren: class TUser : public TPlan { public: void Init(void); void DGL(void); void Run(void); void Reset(void); void Draw(void); void RunTaste1(void); void RunTaste2(void); void RunTaste3(void); }; Dazu wird vereinbart, dass als Anfangsbedingung eine Auslenkung von 1.0 und eine Anfangsgeschwindigkeit von 0.0 gegeben wird. Die Frequenz soll 1.0 groß sein. Zu den Aufgaben der Funktionen void TUser::Init() Hier muss die numerische Integration (GesamtOrdnung = 2, Toleranz = 1.0e-8 als Genauigkeitsvorgabe für die Schrittweitensteuerung, Integrationsintervall von 0.0 bis 100.0) initiiert werden. Auch das Plot wird hier definiert mit Titel- und Achsentexten und Wertebereichen (Abzisse = Zeit von 0.0 bis 100.0, Ordinate = Ausschlag, z.B. von –1.5 bis 1.5). 172 Simulation mechatronischer Systeme Init() sollte die Funktion Reset() aufrufen, in der Wahl der Anfangsbedingungen, sowie die eigentliche Initiierung der Integration durchgeführt wird. void TUser::DGL() Hier muss die oben beschriebene Differentialgleichung X2OPP(0) = ... angegeben werden. void TUser::Run() Hier wird einfach nur ein Integrationsschritt ausgeführt, solange das Integrationsintervall noch nicht abgearbeitet ist, eine Funktion Draw() zum Zeichnen der Masse und die Plotzeichenfunktion aufgerufen und schließlich die Variable CallRun auf true gesetzt, um PLAN zu veranlassen, diese Funktion Run() wieder aufzurufen. void TUser::Reset() Wie in der Init()-Funktion schon angedeutet, müssen hier die Anfangswerte gesetzt werden (X2O(0) = 1.0, X2OP(0) = 0.0), die Integration mit InitIntegration() initiiert werden und im Plot Reset() aufgerufen werden, also der Plotinhalt gelöscht werden. void TUser::Draw() Diese Funktion soll die bewegte Masse zeichnen. Dazu wird die linke Hälfte des Bildschirms skaliert und mehr oder weniger aufwendig der Einmassenschwinger skizziert. void TUser::RunTaste1(), RunTaste2(),... Diese Tasten sollen genutzt werden, die aktuellen Dämpfungswerte einzustellen. Denkbar sind die Werte D = -0.01, 0.0, 0.01, 0.05, 0.1, 0.709, 1.0, 20.0. Gerade auch zum Einstellen der Dämpfungswerte ist es sinnvoll, klassenweite Variablen zu definieren. Hier sollen die Dämpfung D, die Kreisfrequenz OM und der Anfangswert der Auslenkung X0 definiert werden. Sie können über weitere Tastenmenues auch noch einstellbar gemacht werden. Das Programm sollte jetzt keine Schwierigkeiten mehr enthalten. //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Projektbeschreibung: // Feder-Masse Schwinger Kapitel 7.1 //==================================================================== #include <vcl.h> #pragma hdrstop Simulation mechatronischer Systeme 173 #include "Plan.h" class TUser : public TPlan { real D, X0, OM; public: void Init(void); void DGL(void); void Run(void); void Reset(void); void Draw(void); void RunTaste1(void); void RunTaste2(void); void RunTaste3(void); void RunTaste4(void); void RunTaste5(void); void RunTaste6(void); void RunTaste7(void); void RunTaste8(void); void RunTaste9(void); }; //================================================================== void TUser::Init(void){ ProgrammName = "Feder-Masse System"; //=== Variable OM = 1.0; D = 0.0; X0 = 1.0; //=== Integration GesamtOrdnung = 2; Intervall(0.0,100.0); Toleranz = 1.0e-8; Schrittweite = 0.1; Reset(); //=== Plotgestaltung Plot0->Size(GetMaxW()/2,20,GetMaxW()/2,GetMaxH()-20); Plot0->Titel = "Harmonischer Oszillator"; Plot0->Untertitel = "für verschiedene Dämpfungen"; Plot0->Achse(0,"Zeit"); Plot0->Achse(0,0.0,100.0); Plot0->Achse(1,"Auslenkung"); Plot0->Achse(1,-1.5,1.5); Plot0->Kurve0->SetPen(Hellrot,3); //=== zusätzliche Tasten TastenfeldName = "Dämpfung"; InsertTaste(1,"D = -0.01"); InsertTaste(2,"D = 0.00"); InsertTaste(3,"D = 0.01"); InsertTaste(4,"D = 0.05"); InsertTaste(5,"D = 0.10"); 174 Simulation mechatronischer Systeme InsertTaste(6,"D InsertTaste(7,"D InsertTaste(8,"D InsertTaste(9,"D = = = = 0.20"); 0.71"); 1.00"); 10.0"); } void TUser::DGL(void){ X2OPP(0) = - 2.0*D*OM*X2OP(0)-OM*OM*X2O(0);} void TUser::Run(void){ CallRun = Integrationsschritt(0.1); Plot0->Kurve0->LineTo(Zeit,X2O(0)); Draw();} void TUser::Reset(void){ X2O(0) = X0; X2OP(0) = 0.0; InitIntegration(); Draw(); Plot0->Reset(); Plot0->Kurve0->MoveTo(Zeit,X0);} void TUser::RunTaste1(void){ D = -0.01;} void TUser::RunTaste2(void){ D = 0.0;} void TUser::RunTaste3(void){ D = 0.01;} void TUser::RunTaste4(void){ D = 0.05;} void TUser::RunTaste5(void){ D = 0.1;} void TUser::RunTaste6(void){ D = 0.2;} void TUser::RunTaste7(void){ D = 0.71;} void TUser::RunTaste8(void){ D = 1.0;} void TUser::RunTaste9(void){ D = 10.0;} void TUser::Draw(void){ View(0,0,GetMaxW()/2,GetMaxH()); Clear(); Scale(-1.0,1.0,-1.6); SetPen(Schwarz); Simulation mechatronischer Systeme 175 //=== Dämpfer und Masse SetBrush(Hellgrau); Rectangle(-0.03,X2O(0),0.06,1.6-X2O(0)); BrushColor = Hellrot; Rectangle(-0.3,X2O(0)-0.07,0.6,0.14); //=== Aufhängung SetBrush(Grau); Rectangle(-0.05,1.5,0.1,2.0); Rectangle(0,0,GetMaxW()/2,10); //=== Feder SetPen(Schwarz,3); real a = (1.6-X2O(0))/20.0; real b = 0.1; real c = 1.6; ClearPoints(); SetBrush(Klar); SetPoint(0.0,c); c-=a; SetPoint(-b,c); for(int i=0;i<4;i++){ c -=2.0*a; SetPoint(b,c); c -=2.0*a; SetPoint(-b,c);} c-=2.0*a; SetPoint(b,c); SetPoint(0.0,c-a); Poly();} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Am Bildschirm erscheint das nachfolgende Bild. 176 Simulation mechatronischer Systeme Abbildung 7.3: Das Programmlayout des Feder-Masse-Beispiels Simulation mechatronischer Systeme 7.2 177 Phasenportrait einer nichtlinearen DGL Gegeben sei die Differentialgleichung: x x 2 - 2 x x 0.3 x 3 0. Man nennt die Darstellung einiger Lösungen in einem Diagramm x (x) Phasenportrait der Differentialgleichung. Jedem Punkt in der Phasenebene entsprechen Anfangswerte. Das Phasenportrait ermöglicht die Verfolgung der Lösung im Hinblick auf stationäre Schwingungszustände, die sich einstellen können. Bei einem gedämpften harmonischen Schwinger, siehe Kap. 7.1 etwa, geht jede Lösung für D>0 in den stabilen Fixpunkt (0,0), dieser entspricht der statischen Ruhelage des Systems. Bei der vorliegenden Differentialgleichung erahnt man, dass sich nicht ein einzelner stabiler Fixpunkt, sondern ein Grenzzyklus ausbilden wird. Bei kleinen Amplituden wird das System angefacht, bei großen Amplituden ist die Klammer vor x in der obigen Gleichung positiv, dem System wird also Energie entzogen. Es ist zu vermuten, dass sich eine stationäre Schwingung bei diesem System einstellt. Egal, von wo man im Phasenraum mit den jeweils gewählten Anfangsbedingungen startet, nach einer Einschwingzeit zeigt sich dieser Grenzzykel. Numerisch kann man sehr schnell einen Überblick über das Schwingungsgeschehen bekommen. Man muss nur die Differentialgleichung mit einer Anzahl von unterschiedlichen Anfangsbedingungen integrieren und die resultierenden Lösungen in einem 2D-Plot x (x) ausgeben. Wie im vorhergehenden Beispiel müssen die Funktionen Init(), DGL(), Run() und Reset() definiert werden. Im Plot müssen dann auf der Abszisse der x-Wert und auf der Ordinate der x - Wert aufgetragen werden. Das hier angegebene Beispiel weist einen sogenannten Grenzzykel auf. Alle Lösungen laufen im Phasenportrait auf diesen Grenzzykel zu. Kreis mit Anfangsbedingungen Grenzzyklus Abbildung 7.4: Prinzipskizze zum Phasenportrait. Das Prinzipbild des Phasenportraits ist in Abbildung 7.4 dargestellt. Eine Vorabrechnung liefert die ungefähren Ausmaße des Grenzzykels: (xmin,xmax) (-3,3) und ( x min, (-7,7). Um ein recht dicht gezeichnetes Phasenportrait zu bekommen, wird x max) 178 Simulation mechatronischer Systeme die Differentialgleichung mit Anfangsbedingungen gestartet, die auf einem Kreis mit dem Radius 25.0 liegen. Um nun ein typisches Phasenportrait zu erhalten, braucht man ein dichtes Gitter von Lösungen, die in den Grenzzykel einlaufen: Es bietet sich an, mit einem gegebenen Radius rout die Anfangsbedingungen über eine Schleife zu generieren. real rout = 25.0; X2O(0) = rout*cos(GradToRad(phi)); X2OP(0) = rout*sin(GradToRad(phi)); phi += 5.0; Die Funktion GradToRad() ist eine bequeme Hilfsfunktion in PLAN zur Umrechnung von Grad in Rad, natürlich gibt es auch die Funktion RadToGrad(). Der Winkel phi sollte global in der Klasse verfügbar sein. Die Run()-Funktion muss also nun für je einen Satz von Anfangswerten die Differentialgleichung numerisch lösen und in das Plot zeichnen. Dann wird der Winkel phi inkrementiert und PLAN veranlasst, die Run()-Funktion wieder aufzurufen. Da der Grenzzykel auch von "innen" stabil angelaufen wird, sollte noch ein Kreis von Anfangswerten im Inneren des Grenzzykels durchlaufen werden. Dazu kann zum Beispiel die Winkelvariable phi von 0 bis 720 Grad laufen, und für phi > 360.0 Grad wird ein kleiner Radiuswert angenommen. Die Plotkurven sollten dann auch eine neue Farbe bekommen, um den inneren vom äußeren Bereich trennen zu können. Die Klasse TUser könnte wie folgt aussehen: //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Projektbeschreibung: // Phasenportrait Kapitel 7.2 //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { real phi, dphi, rout, rin; int status; public: void Init(void); void DGL(void); void Run(void); void Reset(void); }; In dieser Klassendefinition sind rout der äußere Kreisradius und rin der innere Kreisradius. Die Werte für diese Variablen muss man erproben. Die Variable dphi Simulation mechatronischer Systeme 179 inkrementiert den Winkel vom Ursprungspunkt zum aktuellen Punkt der Anfangsbedingungen auf dem Kreis. Diese Variable ist auf dem äußeren Kreis deutlich kleiner. Die Funktionsdefinitionen des Programms: void TUser::Init(void){ //=== Integration GesamtOrdnung = 2; Toleranz = 1.0e-9; Intervall(0.0,30.0); rout = 15.0; rin = 0.1; dphi = 3.0; //=== Plotgestaltung Plot0->Size(); Plot0->Titel = "Phasenportrait"; Plot0->Untertitel = "einer nichtlinearen Differentialgleichung"; Plot0->Achse(0,-17.0,17.0); Plot0->Achse(0,"Ort"); Plot0->Achse(1,-17.0,17.0); Plot0->Achse(1,"Geschwindigkeit"); //=== Anfangswerte Reset(); } void TUser::DGL(void){ X2OPP(0) = (2.0-X2O(0)*X2O(0))*X2OP(0) -(1.0 + 0.3*X2O(0)*X2O(0))*X2O(0);} void TUser::Run(void){ if(phi>360.0) { status++; phi-=360.0; dphi = 10.0; Plot0->Kurve0->SetPen(Hellblau);} if(status >1) return; real r = (status)?rin:rout; X2O(0) = r*cos(GradToRad(phi)); X2OP(0) = r*sin(GradToRad(phi)); Plot0->Kurve0->MoveTo(X2O(0),X2OP(0)); phi +=dphi; InitIntegration(); while (!Fertig) { Integrationsschritt(); Plot0->Kurve0->LineTo(X2O(0),X2OP(0));} CallRun = true;} void TUser::Reset(void){ X2O(0) = 0.0; X2OP(0) = 0.0; phi = 0.0; 180 Simulation mechatronischer Systeme dphi = 3.0; status = 0; Plot0->Kurve0->SetPen(Hellrot); Plot0->Reset();} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Hierin ist status eine Integer - Variable, die um 1 erhöht wird, wenn der Winkel größer als 360 Grad wird. Ist status = 1, wird der innere Kreis abgearbeitet und für status > 1 ist offensichtlich das Portrait beendet. Das resultierende Phasenportrait ist in Abbildung 7.5 dargestellt. Abbildung 7.5 Programm zum Phasenportrait. Simulation mechatronischer Systeme 7.3 181 Das Doppelpendel Dieses etwas komplexere Beispiel will ein mathematisches Doppelpendel simulieren und animieren. Es ist bekannt, dass das Doppelpendel bei bestimmten Anfangsbedingungen auch chaotische Schwingungen ausführen kann. Hier soll nur die prinzipielle Vorgehensweise zur Animation dargestellt werden, wobei einige ausgewählte Anfangswerte fest vorgegeben werden. Für weitere Details sehe man in der Literatur nach! Betrachtet wird ein Doppelpendel in der Ebene. Das System ist in Abbildung 7.6 skizziert. e1 g 1 e2 m (x, y) ' 2 m' (x', y') Abbildung 7.6: Das Doppelpendel Um die Bewegungsgleichungen zu erhalten gibt es viele Wege. Hier wird der Lagrangeformalismus genutzt. Die Lagrangefunktion des Systems ist in "bequemen kartesischen Koordinaten" offenbar: m 2 2 m' 2 2 x y + x' y' mgy m gy . 2 2 Da das System aber nur zwei Freiheitsgrade besitzt, sind die Koordinaten x, x', y und y' abhängig. Die Lage der Massenpunkte m und m' lässt sich eindeutig durch die zwei generalisierten Variablen 1 und 2 beschreiben. Mit diesen Variablen lassen sich dann auch die Lagrangeschen Gleichungen aufstellen. L x sin 1 y cos 1 x' x 'sin y' y 'cos 2 2 182 Simulation mechatronischer Systeme Die Lagrangefunktion des Systems transformiert sich zu L m 2 2 m' 2 2 x y + x' y' 2 2 m m' 2 2 + x y 2 2 mgy m gy ) m' 2x' 2 cos 2 '2 22 cos 2 2 m' 2y ' 2 sin 2 ' 2 22 sin2 2 2 mgcos 1 m g cos 1 cos 2 2 m m' 2 2 m' 2 2 1 ' 2 2' 1 2 cos 2 2 m m gcos 1 m g cos 2 1 2 Die resultierenden Bewegungsgleichungen erhält man mit 1 2 beliebig : beliebig : d dt L 1 L d dt L 2 L 0 1 0 2 und den hier vereinfachenden Annahmen m = m' und = ' zu 2 m 2 2 m cos( 1 m 2 cos( - 2 ) m 1 2 - 2 ) 1 2 m 2 22 sin( 1 - m 2 12 sin( 1 - mgl cos - mgl(cos 1 2 ) 2 ) 1 cos 2 ) Die Matrix links lässt sich einfach algebraisch invertieren. Man beachte, dass die Determinante immer ungleich Null ist. Zur Animation wird der Aufhängepunkt des Doppelpendels in der Bildmitte installiert, damit das Pendel auch bei Überschlägen noch voll dargestellt werden kann. Das Animationsbild wird alle 0.1 sec der Integrationszeit ausgegeben. Die Zeichnung des Pendels wird in eine Funktion Draw() verlagert, die die in PLAN global verfügbaren Integrationsvariablen verwendet. Simulation mechatronischer Systeme 183 Für die Animation stehen 6 verschiedene Sätze von Anfangsbedingungen zur Verfügung, die über Tastendruck abrufbar sind. Die Integrationszeit ist mit 0 – 5000 s sehr groß dimensioniert. Abgesehen davon, dass Sie die Simulation jederzeit abbrechen können, können Sie sich das in Natura etwa 83 min dauernde Spektakel aber in Falllupe ( je nach Rechner) ansehen. Hilfreich ist die Option, ein Trace-Modus einzuschalten, der einfach das Löschen der alten Pendelstellung vor dem Zeichen der neuen Position verhindert. Diese Option bietet den Vorteil, die Bewegung über einen längeren Zeitraum grafisch präsent zu haben. //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Projektbeschreibung: // Doppelpendel Kapitel 7.3 //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { int Anfangswerte; bool Trace; real DeltaT; public: void Draw(void); void Init(void); void InitDraw(void); void Reset(void); void Run(void); void DGL(void); void RunTaste0(void); void RunTaste1(void); void RunTaste2(void); void RunTaste3(void); void RunTaste4(void); void RunTaste5(void); void RunTaste8(void); void RunTaste9(void); void RunTaste10(void); void RunTaste11(void); void RunTaste12(void); }; Das Pendel wird in einer Funktion Draw() dargestellt. Die Pendelstangen selbst sind einfach nur zwei übereinandergezeichnete Linien zuerst der Stärke 6 und dann der Stärke 2. 184 Simulation mechatronischer Systeme Abbildung 7.7 Das Doppelpendel dargestellt in der Funktion Draw(). void TUser::Draw(void){ View(); if(!Trace) Clear(); Scale(-3.0,3.0,-2.3); //=== Aufhaengung int i; real z = -0.2; SetPen(Schwarz); SetBrush(Grau); for(i=0;i<20;i++) { MoveTo(z,0.15); LineTo(z+0.01,0.2); z+=0.02;} ClearPoints(); SetPoint(0.0,0.0); SetPoint(-0.1,0.15); SetPoint(0.1,0.15); Poly(); SetPen(Schwarz,3); MoveTo(-0.2,0.15); LineTo(0.2,0.15); //=== Pendel real x1 = 1.1*sin(X2O(0)); real y1 = -1.1*cos(X2O(0)); real x2 = 1.1*sin(X2O(1)); real y2 = -1.1*cos(X2O(1)); x2 += x1; y2 += y1; SetPen(Rot,6); MoveTo(0.0,0.0); Simulation mechatronischer Systeme 185 LineTo(x1,y1); LineTo(x2,y2); SetPen(Weiss,2); MoveTo(0.0,0.0); LineTo(x1,y1); LineTo(x2,y2); SetBrush(Hellrot); SetPen(Schwarz); Circle(x1,y1,0.1); Circle(x2,y2,0.1);} Die Init()-Funktion bereitet neben der numerischen Integration und den Tasten auch ein Plot vor. Das Plot soll später eine Überblick darüber geben, wie oft sich das obere oder untere Pendel nach links oder rechts überschlägt. void TUser::Init(void){ Plot0->Size(10,10,600,400); Plot0->Untertitel ="blau: inneres Pendel, rot: äusseres Pendel"; Plot0->Achse(0,0.0,1000.0); Plot0->Achse(1,-50.0,50.0); Plot0->Kurve0->SetPen(Hellrot); Plot0->Kurve1->SetPen(Hellblau); Plot0->Visible = false; Anfangswerte = 2; GesamtOrdnung = 4; Toleranz = 1.0e-6; Intervall(0.0,1000.0); Schrittweite = 0.1; Reset(); DeltaT = 0.1; Trace = false; ProgrammName = "Mathematisches Doppelpendel"; TastenfeldName = "Einstellungen"; InsertTaste(0,"Anfwerte 1",""); InsertTaste(1,"Anfwerte 2",""); InsertTaste(2,"Anfwerte 3",""); InsertTaste(3,"Anfwerte 4",""); InsertTaste(4,"Anfwerte 5",""); InsertTaste(5,"Anfwerte 6",""); InsertTaste(8,"Realtime",""); InsertTaste(9,"Schnell",""); InsertTaste(10,"Trace ein",""); InsertTaste(11,"Trace aus",""); InsertTaste(12,"History","");} void TUser::InitDraw(void){ Draw();} 186 Simulation mechatronischer Systeme Die oben skizzierte Differentialgleichung muss in expliziter Form eingegeben werden. Hier ist die Massenmatrix per Hand invertiert worden. Selbstverständlich hätte man mit dem Datentyp TMatrix die Invertierung bequem numerisch durchführen lassen können. void TUser::DGL(void){ real c01,s01,det,z1, z2; c01 = cos(X2O(0)-X2O(1)); s01 = sin(X2O(0)-X2O(1)); z1 = X2OP(1)*X2OP(1)*s01 + 2.0*sin(X2O(0)); z2 = -X2OP(0)*X2OP(0)*s01 + sin(X2O(1)); det = 2.0-c01*c01; X2OPP(0) = -(z1 - c01*z2); X2OPP(1) = -(-z1*c01 +2.0*z2); X2OPP(0) /= det; X2OPP(1) /= det;} Die Run()-Funktion hat die üblich Gestalt. Sie integriert die Bewegungsgleichungen um das Zeitintervall DeltaT. Diese Variable wird bei den Tasten 8 und 9 auf unterschiedliche Werte gesetzt. Damit soll eine schnelle Animation und eine nahezu realtime ähnliche Simulation ermöglicht werden. Dies ist einfach nur numerisch ausgemessen und hat noch nichts mit realtime – Simulation zu tun! Im Plot werden die Winkelvariablen modulo 2 dargestellt, also die jeweilige Anzahl von Überschlägen in die jeweils eine wie andere Richtung. void TUser::Run(void){ if(!Fertig){ Integrationsschritt(DeltaT); real z = (real)((int)(X2O(1)/6.24)); Plot0->Kurve0->LineTo(Zeit,z); z = (real)((int)(X2O(0)/6.24)); Plot0->Kurve1->LineTo(Zeit,z); Draw();} CallRun = !Fertig;} Die Reset()-Funktion setzt in Abhängigkeit der Variablen Anfangswerte die Anfangswerte der Differentialgleichung. Man beachte, dass die Reset()-Funktion auch in den Plottitel den aktuellen Anfangswertesatz einschreibt. Die Reset()Funktion im Plot löscht die im Plot dargestellten Kurven. void TUser::Reset(void){ switch(Anfangswerte){ Simulation mechatronischer Systeme 187 case 1: X2O(0) = 0.0; X2O(1) = M_PI/2.0; X2OP(0) = -0.5; X2OP(1) = 0.5; break; case 2: X2O(0) = 0.0; X2O(1) = M_PI/2.0; X2OP(0) = -1.0; X2OP(1) = 1.0; break; case 3: X2O(0) = 0.0; X2O(1) = M_PI/2.0; X2OP(0) = -1.5; X2OP(1) = 1.5; break; case 4: X2O(0) = 0.0; X2O(1) = M_PI/2.0; X2OP(0) = -2.0; X2OP(1) = 2.0; break; case 5: X2O(0) = 0.0; X2O(1) = M_PI/2.0; X2OP(0) = -3.0; X2OP(1) = 4.0; break; default: X2O(0) = 0.0; X2O(1) = M_PI/2.0; X2OP(0) = -0.01; X2OP(1) = 0.0;} InitIntegration(); char ss[200]; sprintf(ss,"Umdrehungen, Anfangswerte %d",Anfangswerte+1); Plot0->Titel = ss; Plot0->Reset();} Die Runtasten 0 bis 5 definieren unterschiedliche Anfangswerte. Die Realisation dieser Anfangswerte ist in der Reset()-Funktion enthalten, darum muss diese in den Tastenfunktionen mit aufgerufen werden. void TUser::RunTaste0(void) { Anfangswerte = 0; Reset(); Draw();} void TUser::RunTaste1(void) { Anfangswerte = 1; Reset(); Draw();} void TUser::RunTaste2(void) { Anfangswerte = 2; Reset(); Draw();} void TUser::RunTaste3(void) { Anfangswerte = 3; Reset(); Draw();} 188 Simulation mechatronischer Systeme void TUser::RunTaste4(void) { Anfangswerte = 4; Reset(); Draw();} void TUser::RunTaste5(void) { Anfangswerte = 5; Reset(); Draw();} Die Runtasten 8 und 9 dienen dazu – siehe die Beschriftung dieser Tasten in der Init()-Funktion – die Animationszeit umzuschalten. Im Normalmodus dieses Programms – Taste 9 – wird über je 0.1 sec integriert und das entsprechende Bild gezeichnet. Bei Taste 8 wird das Integrationsintervall von Frame zu Frame auf 1/20 sec gesetzt und mit der (Property-)Variablen CallRunTime das Zeitintervall in Millisekunden angegeben, die PLAN in etwa warten soll, bis die Run()-Funktion wieder aufgerufen wird, hier also ebenfalls eine 1/20 sec ( = 50 Millisekunden). void TUser::RunTaste8(void) { DeltaT = 0.05; CallRunTime = 50; } void TUser::RunTaste9(void) { DeltaT = 0.1; CallRunTime = 10;} Die Runtasten 9 und 10 schalten zwischen dem Trace-Modus und dem Normalmodus hin und her. void TUser::RunTaste10(void) { Trace = 1; } void TUser::RunTaste11(void) { Trace = 0; } Die Taste 12 schließlich schaltet abwechselnd das Plot sichtbar oder unsichtbar. Man beachte, dass das Plot auch im nicht sichtbaren Zustand seine volle Funktionsfähigkeit behält, insbesondere also auch die Plotkurven weiter generiert! void TUser::RunTaste12(void) { Plot0->Visible = !Plot0->Visible;} Die main()-Funktion schließlich blättert die Windowssimulation auf. Sie bleibt wie üblich unverändert. //==================================================================== Simulation mechatronischer Systeme 189 #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Wenn Sie auf die Tasten Werte 4 bzw. Werte 5 drücken, erleben Sie sog. chaotische Schwingungen. Achten Sie hierbei etwa auf die völlig irregulär verlaufenden Pendelüberschläge, die ein paar mal nach links, ein paar mal nach rechts erfolgen und ein paar mal sogar ganz ausbleiben. Vorhersagen kann man diese Überschläge nicht. Abbildung 7.8: Das Doppelpendel im Tracemodus. Das resultierende Bild zeigt Abbildung 7.8. Man beachte, dass die Pendel sich zwar sehr oft überschlagen, die Überschläge sich aber doch erstaunlich gleichmäßig gegeneinander aufheben. Die absolute Zahl der Umdrehungen nach links oder rechts vom Ausgangszustand ist sehr klein. Die u.a. Grafik entsteht bei den Anfangswerten der Taste 4 (Chaos). 190 Simulation mechatronischer Systeme Abbildung 7.9: Die absolute Position des Pendels über der Zeit Abbildung 7.10: Das Bildschirmlayout des Programms Doppelpendel Simulation mechatronischer Systeme 191 7.4 Laufrad auf rauem Untergrund Die Kollegen in Ihrem Betrieb fragen Sie, was die optimale Dämpfung für ein gummibereiftes Rad etwa eines Fahrzeuges ist, so dass die Kontaktzeiten zwischen Rad und Boden maximal sind. Zur ersten Einschätzung dieser Frage erstellen Sie folgendes Modell: v b c g y r (x,y) m x u(x) Abbildung 7.11: Laufrad auf rauem Untergrund Ein Rad der Masse m wird über Feder und Dämpfer gehalten. Es habe nur einen Freiheitsgrad in y-Richtung, in x-Richtung wird das Gebilde mit einer vorgegebenen Geschwindigkeit v gezogen. Das Rad soll sich auf dem Boden, u = u(x), abstützen, wobei in Anlehnung an eine Reifensteifigkeit zwischen Rad und Boden eine Elastizität wirkt. Nach (meist mühsamen) weiteren Gesprächen erhalten Sie folgende für Ihr Modell relevante Daten: Masse Rad: M = 10.0 [kg] Radius Rad: r = 0.5 [m] Frequenz Rad gegen Aufbau: fr = 1 Frequenz Rad gegen Boden: fb = 10.0 [Hz] stat. Absenkung Rad in Boden: ys = - 0.01 [m] [Hz] Als erstes werden Sie die Bewegungsgleichungen aufstellen: M y b y c ( y - y0) -Mg - F Hierin ist F eine Kraft, die nur dann ungleich Null ist, wenn das Rad den Boden berührt: 192 Simulation mechatronischer Systeme (y - r - u(vt)) 0 F wenn (y - r - u) 0 sonst Die Daten für Masse und Radius können Sie direkt einsetzen. Die Frequenzangaben lassen sich verwenden, um die übrigen Daten zu ermitteln: Die Frequenz des Rades gegen den Aufbau ist c f ! 1 Hz . 2 2 M Damit berechnet sich die Federsteifigkeit c zu c = 39.5 M [N/m] und ganz analog die Federsteifigkeit des Reifens zu = 3950 M [N/m]. Mit der Erdbeschleunigung g = 9.81 [m/s2] ist die statische Einsenkung des Rades auf glattem Boden ( u = -r) gegeben durch ys - M g - c y0 . c Ohne Vorspannung bzw. mit einer Federruhlänge y0 = 0 ist die statische Einsenkung nur ys = -2.5 [mm]. Die Federruhlänge muss auf ca. y0 = - 0.75 [m] eingestellt werden, um die geforderte statische Absenkung zu erhalten! Um die Dämpfung mit dem Lehrschen Dämpfungsmaß D einstellen zu können, muss noch beachtet werden: b 2D cM b 4 D M. Die explizite Differentialgleichung lautet also y - 12.57 D y - 39.5 (y 0.75) - 9.81 - F, mit F 3950 (y - 0.5 - u(vt)) für (y - 0.5 - u(vt)) und F 0 sonst 0 Sinnvollerweise sollte neben dem Dämpfungsmaß D auch die Geschwindigkeit v einstellbar sein! Simulation mechatronischer Systeme 193 Zunächst sei die Bodenanregung betrachtet. Hier hat man von der Aufgabenstellung her alle Freiheiten. Üblicherweise setzt man eine Strecke von etwa 100 m an, die durch langwellige periodische Funktionen modelliert wird. Innerhalb dieser 100 m wird man dann noch einen Abschnitt von 30 m mit kurzwelligen periodischen Funktionen modellieren und gegebenenfalls sogar noch eine Rampe in das Straßenprofil einbauen. Da man ja nicht a priori weiß, wie lange die Simulation jeweils läuft, also welcher Weg pro Simulation zurückgelegt wird, sollte man den Weg nach jeweils 100 m wieder von vorn beginnen. Zur Realisierung bietet sich eine Funktion Boden() an, die in Abhängigkeit einer beliebigen Strecke die jeweilige Bodenerhebung u(x) – r liefert: real TUser::Boden(real x){ if((x>100.0) || (x<0.0)) x = x - (real)((int)(x/100.0))*100.0; real z = 0.1*cos(x) + 0.05*sin(3*x); if(x>20.0 && x<50.0) z += 0.02*sin(10.0*x); if(x>60.0 && x<65.0) z+=(x-60.0)*0.4/5.0; return z;} Dieses Straßenprofil kann man erst einmal für sich am Bildschirm untersuchen, ob es denn das Layout hat, das man sich vorgestellt hat. Weg = vt 3.5 [m] 3.5 [m] Abbildung 7.12: Bildschirmlayout für das Laufrad Sinnvollerweise sollte das Laufrad immer zentral auf dem Bildschirm sichtbar sein. Das heißt aber, dass das Straßenprofil mit der Geschwindigkeit –v über den Bildschirm wandern muss. 194 Simulation mechatronischer Systeme Der Ort des Rades ergibt sich einfach durch das Produkt von Integrationszeit mit der eingestellten Geschwindigkeit, wenn man ohne Beschränkung der Allgemeinheit für t = 0 den Ort x = 0 annimmt. Zur Untersuchung der Kontaktzeiten bietet sich ein Plot an. Man sollte also den Bildschirm teilen: In der oberen Hälfte soll die eigentliche Animation stattfinden, in der unteren Hälfte sollen die Auswertungen laufen. Im Animationsfenster soll ein vertikaler Ausschnitt von ca. 7 [m] um den jeweiligen Ort des Rades herum dargestellt werden. Mit einer im private-Abschnitt von TUser definierten globalen Variablen Weg kann der Ausschnitt entsprechend skaliert und der Boden gezeichnet werden. Dies wird man in einer Funktion Draw() ausführen: void TUser::Draw(void){ real z; int i; View(0,0,GetMaxW(),GetMaxH()/2); Clear(); Scale(Weg-3.5,Weg+3.5,-1.0); z = Weg-3.5; ClearPoints(); SetPoint(Weg-3.5,-1.0); for(i=0;i<101;i++) { SetPoint(z,Boden(z)-0.5); z+=0.07;} SetPoint(Weg+3.5,-1.0); PenColor = Gruen; BrushColor = Gruen; Poly(); } Mit einer einfachen Run()- und Reset()-Funktion kann man den Boden testen: void TUser::Run(void) { Weg += 0.1; Draw(); CallRun = true;} void TUser::Reset(void){ Weg = 0.0; Draw(); } Wenn Sie das Programm starten, sollte der grüne Boden kontinuierlich nach links wandern. Simulation mechatronischer Systeme 195 Die Bewegungsgleichung in das Programm einzubauen, sollte keine Schwierigkeiten bereiten. In der Init()-Funktion wird das DGL-System angemeldet und globale Variable, definiert im private-Abschnitt von TUser, initiiert: void TUser::Init(void){ Weg = 0.0; Geschw = 0.5; D = 0.7; GesamtOrdnung = 2; Verfahren = 2; Toleranz = 1.0e-6; Schrittweite = 0.1; Anfangszeit = 0.0; Endzeit = 1000.0; Reset();} und in der virtuellen Funktion DGL() schließlich die Gleichung selbst aufgeführt. void TUser::DGL(void){ real z = X2O(0) - Boden(Geschw*Zeit); X2OPP(0) = - 12.57*D*X2OP(0) - 39.5*(X2O(0)+0.75) - 9.81; if(z<0.0) X2OPP(0) -= 3950.0*z;} Die Reset()-Funktion enthält wieder die Anfangswerte sowie die Initiierung der Integration. void TUser::Reset(void) { Weg = 0.0; X2O(0) = Boden(0.0); X2OP(0) = 0.0; InitIntegration(); Draw();} In der Run()-Funktion wird nun die Integration jeweils z.B. 0.1 Sekunden ausgeführt: void TUser::Run(void){ Integrationsschritt(0.1); Weg = Geschw*Zeit; Draw(); CallRun = (!Fertig);} Das Programm ist nun voll lauffähig. Um das Rad zu sehen, sollte die Draw()Funktion erst einmal nur wie folgt erweitert werden: 196 Simulation mechatronischer Systeme void TUser::Draw(void){ real z; int i,j,k; View(0,0,GetMaxW(),GetMaxH()/2); Clear(); Scale(Weg-3.5,Weg+3.5,-1.0); //=== Rad PenColor = Schwarz; BrushColor = Hellgrau; Circle(Weg,X2O(0),0.5); //=== Boden z = Weg-3.5; ClearPoints(); SetPoint(Weg-3.5,-1.0); for(i=0;i<101;i++) { SetPoint(z,Boden(z)-0.5); z+=0.07;} SetPoint(Weg+3.5,-1.0); PenColor = Gruen; BrushColor = Gruen; Poly(); } Wenn Sie das Programm laufen lassen, sollten Sie einen Ball über den rauen Untergrund hoppeln sehen: Abbildung 7.13: Laufrad auf rauem Untergrund, kurz vor der Rampe Bevor nun die Grafik verfeinert wird und die Auswertung selbst angegangen wird, muss man sich überlegen, wie man die Parameter Geschwindigkeit und Dämpfungsmaß im Programm verändern will. Dazu bietet sich ein Menü an. Dieses kann man mit den Mitteln von PLAN einfach realisieren. Dazu wird im private-Abschnitt eine globale Integervariable Menu definiert. Ihr Wert soll gleich Null sein, wenn das Hauptmenü zu sehen ist. Simulation mechatronischer Systeme 197 Das Hauptmenü besteht aus 2 Tasten: Dämpfung und Geschwindigkeit. Wenn die Taste Dämpfung gedrückt wird, sollen sechs neue Tasten erscheinen, die Dämpfungsmaße von D = 0.1 bis D = 1.0 bieten. Analoges soll beim Drücken der Taste Geschwindigkeit passieren. Wenn die Taste Dämpfung gedrückt wird, müssen zunächst erst einmal alle Tasten des Hauptmenüs gelöscht werden und neue Tasten definiert werden. Deren Ausführungsfunktionen RunTaste0() usw. müssen nun aber eine andere Funktion ausüben! Dazu bietet es sich an, die globale Variable Menu zu nutzen. Wenn das Dämpfungsmenü sichtbar ist, soll diese Variable gleich 1 sein. Entsprechend soll diese Variable gleich 2 sein, wenn das Geschwindigkeitsmenü sichtbar ist. Die Ausführungsfunktionen müssen nun diese globale Variable auf ihren Wert hin prüfen, um zu wissen, in welchem Kontext sie genutzt werden und was sie tun sollen. Programmtechnisch lässt sich dies einfach lösen. Man nutzt drei neue Funktionen für den Aufbau des Hauptmenüs, des Dämpfungsmenüs und des Geschwindigkeitsmenüs. void TUser::Hauptmenu(void){ Menu = 0; DeleteAllTasten(); TastenfeldName = "Hauptmenü"; InsertTaste(1,"Dämpfung","Menü zum Einstellen" " der Lehrschen Dämpfungsmaße"); InsertTaste(2,"Geschw.","Menü zum Einstellen" " der Geschwindigkeit"); void TUser::Daempfungsmenu(void){ Menu = 1; DeleteAllTasten(); TastenfeldName = "Lehrsches Dämpfungsmaß"; InsertTaste(0,"D = 0.1",""); InsertTaste(1,"D = 0.3",""); InsertTaste(2,"D = 0.5",""); InsertTaste(3,"D = 0.7",""); InsertTaste(4,"D = 0.9",""); InsertTaste(5,"D = 1.0",""); InsertTaste(19,"Zurück","");} void TUser::Geschwindigkeitsmenu(void){ Menu = 2; DeleteAllTasten(); TastenfeldName = "Geschwindigkeit [m/s]"; InsertTaste(0,"v = 1.0",""); InsertTaste(1,"v = 3.0",""); InsertTaste(2,"v = 5.0",""); InsertTaste(3,"v = 7.0",""); InsertTaste(4,"v = 9.0",""); InsertTaste(5,"v = 11.0",""); InsertTaste(19,"Zurück","");} 198 Simulation mechatronischer Systeme In den Nebenmenüs ist jeweils die (unterste) Taste 20 als Zurücktaste definiert, mit der man wieder in das Hauptmenü kommt. Die Tastenausführungsfunktionen sind einfach: void TUser::RunTaste0(void){ switch(Menu) { case 0: Daempfungsmenu(); break; case 1: D = 0.1; Reset(); break; case 2: Geschw = 1.0; Reset();}} void TUser::RunTaste1(void){ switch(Menu) { case 0: Geschwindigkeitsmenu(); break; case 1: D = 0.3; Reset(); break; case 2: Geschw = 3.0; Reset();}} void TUser::RunTaste2(void){ switch(Menu) { case 0: break; case 1: D = 0.5; Reset(); break; case 2: Geschw = 5.0; Reset();}} void TUser::RunTaste3(void){ switch(Menu) { case 0: break; case 1: D = 0.7; Reset(); break; case 2: Geschw = 7.0; Reset();}} void TUser::RunTaste4(void){ switch(Menu) { case 1: D = 0.9; Reset(); break; case 2: Geschw = 9.0; Reset();}} void TUser::RunTaste5(void){ switch(Menu) { case 1: D = 1.1; Reset(); break; case 2: Geschw = 11.0; Reset();}} void TUser::RunTaste19(void) { Hauptmenu();} Probieren Sie es aus! Man kann nun eine etwas komplexere Grafik angehen, die realitätsnäher aussieht. Dazu mache man sich klar, dass bei der Simulation im allgemeinen die numerische Integration sowie das Updaten der Grafikoberfläche die zeitsensitiven Faktoren sind. Ob nun ein Rechteck mehr oder weniger gezeichnet wird, macht sich für den User kaum bemerkbar! Ein Vorschlag für die Grafik des Systems ist nachfolgend angeführt. Es wird neben einem Dämpfer und einer Feder auch eine Leitpfahl alle 10 m auf der Strecke gezeichnet. Dies gibt einen sehr guten Eindruck von der Geschwindigkeit des Rades: Simulation mechatronischer Systeme void TUser::Draw(void){ View(0,0,GetMaxW(),GetMaxH()/2); Clear(); Scale(X1O(0)-3.5,X1O(0)+3.5,-1.0); //=== Rad, Dämpfer und Wand PenColor = Schwarz; PenSize = 1; BrushColor = Hellgrau; Circle(X1O(0),X2O(0),0.5); BrushColor = Weiss; Circle(X1O(0),X2O(0),0.3); BrushColor = Grau; Rectangle(X1O(0)-0.05,X2O(0),0.1,2.0); Circle(X1O(0),X2O(0),0.05); Rectangle(X1O(0)-0.1,1.0,0.2,2.0); BrushColor = Schwarz; Rectangle(X1O(0)-0.4,IntToY(0)-0.1,0.8,0.2); //=== Text BrushColor = Klar; TextSize = 15.0; char ss[100]; sprintf(ss,"Geschwindigkeit v = %10.3e m/s",Geschw); Text(X1O(0)+0.5,IntToY(10),ss); sprintf(ss,"Dämpfungsmaß D = %10.3e",D); Text(X1O(0)+0.5,IntToY(30),ss); //=== Feder real y = X2O(0)+0.7; real z = 1.6 -y; z /=20.0; ClearPoints(); SetPoint(X1O(0),y); y+=z; SetPoint(X1O(0)-0.3,y); y += 2.0*z; SetPoint(X1O(0)+0.3,y); y += 2.0*z; SetPoint(X1O(0)-0.3,y); y += 2.0*z; SetPoint(X1O(0)+0.3,y); y += 2.0*z; SetPoint(X1O(0)-0.3,y); y += 2.0*z; SetPoint(X1O(0)+0.3,y); y += 2.0*z; SetPoint(X1O(0)-0.3,y); y += 2.0*z; SetPoint(X1O(0)+0.3,y); y += 2.0*z; SetPoint(X1O(0)-0.3,y); y += 2.0*z; SetPoint(X1O(0)+0.3,y); y += z; SetPoint(X1O(0),1.6); PenColor = Schwarz; PenSize = 3; Poly(); //=== Boden z = X1O(0)-3.5; ClearPoints(); SetPoint(z,-1.0); for(int i=0;i<101;i++) { SetPoint(z,Boden(z)-0.5); 199 200 Simulation mechatronischer Systeme z+=0.07;} SetPoint(X1O(0)+3.5,-1.0); PenColor = Gruen; BrushColor = Gruen; Poly(); //=== Leitpfahl z = (real)((int)((X1O(0)+3.5)/10.0))*10.0; PenColor = Schwarz; BrushColor = Weiss; Rectangle(z,0.5,0.1,-0.2); BrushColor = Schwarz; Rectangle(z,0.3,0.1,-1.1); } Einen Eindruck von der Grafik gibt das nachfolgende Bild 7.14. Hier werden am oberen Rand auch die aktuell eingestellte Geschwindigkeit und das Dämpfungsmaß angezeigt. Zur Analyse der Eingangsfrage, wie sich die Kontaktzeiten in Abhängigkeit von den Dämpfungsmaßen darstellen, bietet sich ein Plot an, das die Kontaktzeiten über einen Weg von 100 m darstellt. Ausgaben über einen längeren Weg lohnen sich nicht, da der Weg nach Konstruktion ja nach 100 Metern wiederholt wird. Nun macht es natürlich wenig Sinn, einfach den binären Wert Kontakt ja-nein aufzutragen. Man sollte den Prozentsatz des Weges mit bzw. ohne Kontakt darstellen. Dies gelingt, indem man die Wegstrecke mit Kontakt, bezogen auf die Gesamtweglänge aufsummiert. Ein famoses Instrument für diese Summation ist die numerische Integrationsroutine, die quasi huckepack auch diese Aufgabe mit erledigen kann! Die notwendige Wegintegration kann wegen dx = v dt auf die Zeitintegration zurückgeführt werden. Die Variable z mit v wenn Kontakt 0 sonst stellt gerade den Prozentsatz an Wegstrecke dar, wo das Rad Kontakt hatte.Die Gesamtwegstrecke berechnet sich zu : z z v Die Differentialgleichung in DGL() muss nur um zwei Gleichungen 1. Ordnung erweitert werden. Die Gesamtordnung ist nun vier. In Init() wird nun für den Aufbau der Numerischen Integration benötigt: Simulation mechatronischer Systeme 201 Abbildung 7.14: Animation des Laufrades void TUser::Init(void){ Weg = 0.0; Geschw = 0.5; D = 0.7; GesamtOrdnung = 4; Gln1Ordnung = 2; Verfahren = 2; Toleranz = 1.0e-6; Schrittweite = 0.1; Intervall(0.0,1000.0); Reset();} Die Variablen der Differentialgleichungen 1.ter Ordnung werden mit X1O(i) bzw. mit X1OP(i) angesprochen. Das Differentialgleichungssystem in der Funktion DGL() lautet nun: 202 Simulation mechatronischer Systeme void TUser::DGL(void){ X1OP(0) = Geschw; X1OP(1) = 0.0; real z = X2O(0) - Boden(X1O(0)); X2OPP(0) = - 12.57*D*X2OP(0) - 39.5*(X2O(0)+0.75) - 9.81; if(z<0.0) { X2OPP(0) -= 3950.0*z; X1OP(1) = Geschw;}} Zur Darstellung der prozentualen Kontaktzeiten, bzw. des Kraft über Weg Diagramms werden zwei sich überlagernde Plots je nach Tastenwahl, sichtbar geschaltet. Der vollständige Sourcecode ist nachfolgend angeführt. Als Ergebnis mit der Einstellung D=0.7 und v = 1m/s ergibt sich das abschließend dargestellte Bild 7.15. //==================================================================== // Simulation dynamischer Systeme mit PLAN //==================================================================== // Projektbeschreibung: // //==================================================================== #include <vcl.h> #pragma hdrstop #include "Plan.h" class TUser : public TPlan { real Geschw, D; int Menu; public: real Boden(real x); void Draw(void); void Init(void); void InitDraw(void); void Run(void); void DGL(void); void Reset(void); void Hauptmenu(void); void Daempfungsmenu(void); void Geschwindigkeitsmenu(void); void RunTaste0(void); void RunTaste1(void); void RunTaste2(void); void RunTaste3(void); void RunTaste4(void); void RunTaste5(void); void RunTaste6(void); void RunTaste7(void); void RunTaste10(void); }; real TUser::Boden(real x){ Simulation mechatronischer Systeme if((x>100.0) || (x<0.0)) x = x - (real)((int)(x/100.0))*100.0; real z = 0.1*cos(x) + 0.05*sin(3*x); if(x>20.0 && x<50.0) z += 0.02*sin(10.0*x); if(x>60.0 && x<65.0) z+=(x-60.0)*0.4/5.0; return z;} void TUser::Draw(void){ View(0,0,GetMaxW(),GetMaxH()/2); Clear(); Scale(X1O(0)-3.5,X1O(0)+3.5,-1.0); //=== Rad, Dämpfer und Wand PenColor = Schwarz; PenSize = 1; BrushColor = Hellgrau; Circle(X1O(0),X2O(0),0.5); BrushColor = Weiss; Circle(X1O(0),X2O(0),0.3); BrushColor = Grau; Rectangle(X1O(0)-0.05,X2O(0),0.1,2.0); Circle(X1O(0),X2O(0),0.05); Rectangle(X1O(0)-0.1,1.0,0.2,2.0); BrushColor = Schwarz; Rectangle(X1O(0)-0.4,IntToY(0)-0.1,0.8,0.2); //=== Text BrushColor = Klar; TextSize = 15.0; char ss[100]; sprintf(ss,"Geschwindigkeit v = %10.3e m/s",Geschw); Text(X1O(0)+0.5,IntToY(10),ss); sprintf(ss,"Dämpfungsmaß D = %10.3e",D); Text(X1O(0)+0.5,IntToY(30),ss); //=== Feder real y = X2O(0)+0.7; real z = 1.6 -y; z /=20.0; ClearPoints(); SetPoint(X1O(0),y); y+=z; SetPoint(X1O(0)-0.3,y); y += 2.0*z; SetPoint(X1O(0)+0.3,y); y += 2.0*z; SetPoint(X1O(0)-0.3,y); y += 2.0*z; SetPoint(X1O(0)+0.3,y); y += 2.0*z; SetPoint(X1O(0)-0.3,y); y += 2.0*z; SetPoint(X1O(0)+0.3,y); y += 2.0*z; SetPoint(X1O(0)-0.3,y); y += 2.0*z; SetPoint(X1O(0)+0.3,y); y += 2.0*z; SetPoint(X1O(0)-0.3,y); y += 2.0*z; SetPoint(X1O(0)+0.3,y); y += z; SetPoint(X1O(0),1.6); PenColor = Schwarz; PenSize = 3; Poly(); //=== Boden 203 204 Simulation mechatronischer Systeme z = X1O(0)-3.5; ClearPoints(); SetPoint(z,-1.0); for(int i=0;i<101;i++) { SetPoint(z,Boden(z)-0.5); z+=0.07;} SetPoint(X1O(0)+3.5,-1.0); PenColor = Gruen; BrushColor = Gruen; Poly(); //=== Leitpfahl z = (real)((int)((X1O(0)+3.5)/10.0))*10.0; PenColor = Schwarz; BrushColor = Weiss; Rectangle(z,0.5,0.1,-0.2); BrushColor = Schwarz; Rectangle(z,0.3,0.1,-1.1); } void TUser::Init(void){ BMPSystem = true; Geschw = 1.0; D = 0.7; GesamtOrdnung = 4; Gln1Ordnung = 2; Verfahren = 2; Toleranz = 1.0e-6; Schrittweite = 0.1; Intervall(0.0,1000.0); Reset(); Hauptmenu(); Plot0->Size(0,GetMaxH()/2,GetMaxW(),GetMaxH()/2-1); Plot0->Titel = "Kraft über Weg"; Plot0->Achse(0,"Weg"); Plot0->Achse(0,0.0,150.0); Plot0->Achse(1,"Kontaktkraft [N]"); Plot0->Achse(1,0.0,150.0); Plot0->Kurve0->Color = Rot; Plot0->Kurve0->Size = 3; Plot1->Size(0,GetMaxH()/2,GetMaxW(),GetMaxH()/2-1); Plot1->Titel = "prozentuale Kontaktzeiten"; Plot1->Achse(0,"Weg"); Plot1->Achse(0,0.0,150.0); Plot1->Achse(1,"Kontaktzeit in %"); Plot1->Achse(1,0.0,100.0); Plot1->Kurve0->Color = Blau; Plot1->Kurve0->Size = 3; Plot1->Visible = false; } void TUser::InitDraw(void){ Draw(); Plot0->Reset(); Plot1->Reset();} void TUser::Run(void){ Simulation mechatronischer Systeme Integrationsschritt(0.05); Draw(); if(!Plot0->Kurve0->InAchse0(X1O(0))) { Plot0->MoveAchse(0); Plot1->MoveAchse(0);} real z = X2O(0)-Boden(X1O(0)); if(z<0.0) z *= -3950.0; else z = 0.0; Plot0->Kurve0->LineTo(X1O(0),z); Plot1->Kurve0->LineTo(X1O(0),100.0*X1O(1)/(X1O(0)+1.0e-10)); CallRun = !Fertig;} void TUser::DGL(void){ X1OP(0) = Geschw; X1OP(1) = 0.0; real z = X2O(0) - Boden(X1O(0)); X2OPP(0) = - 12.57*D*X2OP(0) - 39.5*(X2O(0)+0.75) - 9.81; if(z<0.0) { X2OPP(0) -= 3950.0*z; X1OP(1) = Geschw;}} void TUser::Reset(void){ X1O(0) = 0.0; X1O(1) = 0.0; X2O(0) = Boden(X1O(0)); X2OP(0) = 0.0; InitIntegration(); Draw(); Plot0->Achse(0,0.0,150.0); Plot1->Achse(0,0.0,150.0); Plot0->Reset(); Plot1->Reset();} void TUser::Hauptmenu(void){ Menu = 0; DeleteAllTasten(); TastenfeldName = "Hauptmenü"; InsertTaste(0,"Dämpfung","Menü zum Einstellen" " der Lehrschen Dämpfungsmaße"); InsertTaste(1,"Geschw.","Menü zum Einstellen" " der Geschwindigkeit"); InsertTaste(4,"Kraft/Weg","mittlere Kontaktkraft über Weg"); InsertTaste(5,"Zeit/Weg","Kontaktfehlzeiten in Prozent");} void TUser::Daempfungsmenu(void){ Menu = 1; DeleteAllTasten(); TastenfeldName = "Lehrsches Dämpfungsmaß D"; InsertTaste(0,"D = 0.00",""); InsertTaste(1,"D = 0.01",""); InsertTaste(2,"D = 0.05",""); InsertTaste(3,"D = 0.1",""); InsertTaste(4,"D = 0.3",""); InsertTaste(5,"D = 0.7",""); 205 206 Simulation mechatronischer Systeme InsertTaste(6,"D = 1.0",""); InsertTaste(7,"D = 5.0",""); InsertTaste(10,"Zurück","");} void TUser::Geschwindigkeitsmenu(void){ Menu = 2; DeleteAllTasten(); TastenfeldName = "Geschwindigkeit [m/s]"; InsertTaste(0,"v = 1.0",""); InsertTaste(1,"v = 3.0",""); InsertTaste(2,"v = 5.0",""); InsertTaste(3,"v = 7.0",""); InsertTaste(4,"v = 9.0",""); InsertTaste(5,"v = 11.0",""); InsertTaste(6,"v = 13.0",""); InsertTaste(7,"v = 15.0",""); InsertTaste(10,"Zurück","");} void TUser::RunTaste0(void){ switch(Menu) { case 0: Daempfungsmenu(); break; case 1: D = 0.0; break; case 2: Geschw = 1.0;}} void TUser::RunTaste1(void){ switch(Menu) { case 0: Geschwindigkeitsmenu(); break; case 1: D = 0.01; break; case 2: Geschw = 3.0;;}} void TUser::RunTaste2(void){ switch(Menu) { case 0: break; case 1: D = 0.05; break; case 2: Geschw = 5.0; }} void TUser::RunTaste3(void){ switch(Menu) { case 0: break; case 1: D = 0.1; break; case 2: Geschw = 7.0;}} void TUser::RunTaste4(void){ switch(Menu) { case 0: Plot1->Visible = false; break; case 1: D = 0.3; break; case 2: Geschw = 9.0;}} void TUser::RunTaste5(void){ switch(Menu) { case 0: Plot1->Visible = true; break; case 1: D = 0.7; Reset(); break; case 2: Geschw = 11.0;}} void TUser::RunTaste6(void){ Simulation mechatronischer Systeme 207 switch(Menu) { case 1: D = 1.0; Reset(); break; case 2: Geschw = 13.0;}} void TUser::RunTaste7(void){ switch(Menu) { case 1: D = 5.0; Reset(); break; case 2: Geschw = 15.0;}} void TUser::RunTaste10(void) { Hauptmenu();} //==================================================================== #pragma argsused int main(int argc, char* argv[]){ TUser a; a.Execute(); return 0; } //__________________________________________________________Ost08_____ Abbildung 7.15 Ergebnis mit der Einstellung D=0.7 und V = 1m/