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
mgcos 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 gcos 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/

Documentos relacionados