Im Folgenden werden die Themen aufgelistet, deren Verständnis in der schriftlichen Prüfung abgefragt werden könnte. Es werden für die jeweiligen Themen wichtige Schlagworte genannt, die in der Klausur z.B. in der folgenden Form abgefragt werden könnte: "Was versteht man unter Schlagwort?" oder "Erklären Sie bitte die Bedeutung des Schlagwortes an einem Beispiel."
Thema | Schlagworte | in Vorlesung |
---|---|---|
Programmiersprachen , Grundlegendes in Python |
Höhere Programmiersprachen, kompilierte Programmiersprache C++, Compiler, Skriptsprache Python, Unterschiede: C++ vs. Python | Vorlesung 1 Vorlesung 3 |
Datentypen und Variablen | Deklaration einer Variable, integrierte Datentypen in C++, Wert einer Variable, Initialisierung einer Variable, wichtigste integrierte Datentypen von C++ (bool, char, int, float, double), Speicherbedarf, sizeof(...), Bezeichner auto | Vorlesung 2 |
Arithmetik und Operatoren | arithmetische Operatoren, logischen Vergleichsoperatoren, weitere spezifische Operatoren | Vorlesung 2 |
Ein- und Ausgabe | Eingabe mittels "cin" und "scanf(...)", Ausgabe mittels "cout" und "printf(...)", Genauigkeit der Ausgabe, Formatspezifizierer "%..." bei printf(...) | Vorlesung 2 |
C++ Anweisungen: for-, while- und do-Schleifen | Schleifenanweisungen in C++, formale Struktur einer for-, while- und do-Schleife, bereichsbasierte for-Schleife (siehe auch später), for-Schleife und auto | Vorlesung 3 |
Folgen und Reihen | Mathematische Folgen und Reihen mittels for-Schleifen berechnen, kombinierte Summen/Produkte mittels verschachtelter for-Schleifen berechnen (siehe auch später) | Vorlesung 3 |
Die Computerarithmetik und der Fehler in numerischen Berechnungen | der Zahlenraum des Computers (ℝ$_C$), das Maschinen-Epsilon $\epsilon_M$, unterschiedliche Fehlerquellen bei numerischen Berechnungen: Rundungsfehler und Approximierungsfehler (Abschneidefehler bzw.'Truncation error') | Vorlesung 2 Vorlesung 3 |
C++ Anweisungen: Auswahlanweisungen mit if und switch | Auswahlanweisungen, formale Struktur einer if-, (if-else)- und switch-Anweisung, case-Marken | Vorlesung 4 |
Funktionen in C++ | Deklaration und Definition von Funktionen, formale Struktur einer C++ Funktion, Rückgabe Typ, Argumentenliste, Gemeinsamkeiten und Unterschiede zu mathematischen Funktionen, virtual, overloading (siehe auch später) | Vorlesung 4 |
Nullstellensuche einer Funktion | Grundlegendes Prinzip und C++ Implementierung der Methode der Bisektion (das Intervallhalbierungsverfahren) und des Newton-Raphson Verfahren zur Nullstellenermittlung | Vorlesung 4 |
C++ Arrays, Zeiger und Referenzen | Zeiger, Adressen und Referenzen, Dereferenzierung, Deklaration und Initialisierung von eindimensionale Arrays, Arrays und Zeiger, sizeof(...), Strings als Arrays von Zeichen, eindimensionale Arrays an Funktionen übergeben (siehe auch später mittels 'vector') | Vorlesung 5 |
Interpolation und Polynomapproximation | Grundlegendes Prinzip und C++ Implementierung der Methode der Lagrange Polynomapproximation, Vektor der Stützstellenpunkte, n-tes Lagrangesche Polynom | Vorlesung 5 |
Mehrdimensionale C++ Arrays | Deklaration und Initialisierung von mehrdimensionale C++ Arrays, mehrdimensionale C++ Arrays und Zeiger, Indexzugriff vs. Zeigerzugriff, Matrizen als mehrdimensionale C++ Arrays, Multiplikation zweier Matrizen in C++, mehrdimensionale C++ Arrays und Funktionen | Vorlesung 6 |
Numerische Differentiation | Differentiationsregeln der numerischen Mathematik in einem C++ Programm verwenden | Vorlesung 6 |
Objekt-orientierte Programmierung und C++ Klassen | Prozeduralen Programmierung vs. Objekt-orientierten Programmierung, Benutzerdefinierte Typen und Abstraktionsmechanismen in C++, Klassenkonzept in C++, Grundstruktur einer Klasse, Instanzvariablen (Daten-Member), Konstruktoren, Member-Funktionen, Destruktor, öffentlich/private Zugriffssteuerung, Konkrete und abstrakte Klassen, die Klasse der Lagrange Polynom Methode | Vorlesung 7 |
Numerische Integration: Theorie und Anwendung | Integrationsregeln der numerischen Mathematik in einem C++ Programm verwenden | Vorlesung 7 |
C++ Container, vector Klasse der Standardbibliothek | STL-Container, sequentielle Container, assoziative Container, die Klasse <vector> am Beispiel eines Integer/Double Vektors, push_back(...), insert(...), resize(...), die Klasse <complex> für komplexwertige Zahlen, die Klasse <vector> als ein Container von Objekten, bereichsbasierte for()-Schleifen und die Klasse <vector> | Vorlesung 8 |
Numerisches Lösung von Differentialgleichungen erster Ordnung | Prinzip des einfachen Euler Verfahrens zum Lösen einer DGL, Implementierung des Verfahrens in einem C++ Programm, weitere numerische Verfahren (z.B. Runge-Kutta Ordnung vier) in einem C++ Programm zum Lösen einer DGL verwenden | Vorlesung 8 |
Systeme von gekoppelten Differentialgleichungen und Differentialgleichungen zweiter Ordnung | Grundlegendes Prinzip und C++ Implementierung der Lösung eines Systems von gekoppelten Differentialgleichungen erster Ordnung, Umschreibe-Trick zum Lösen einer DGL 2.Ordnung, grundlegendes Prinzip und C++ Implementierung der Lösung einer DGL zweiter Ordnung | Vorlesung 9 |
Abgeleitete Klassen, Vererbung von Klassenmerkmalen und Klassenhierarchien | Basisklasse (auch Oberklasse oder Superklasse), abgeleitete Klasse (auch Subklasse), Schnittstellenvererbung, Implementierungsvererbung, formale C++ Struktur einer abgeleiteten Klasse, Mehrfachvererbung, Klassenhierarchien | Vorlesung 10 |
Die Klausur wird aus mehreren Teilaufgaben bestehen, wobei man insgesamt eine gewisse Anzahl an Punkte erreichen kann und die Kennzeichnung der erreichbaren Punktezahl neben jeder Teilaufgabe separat angegeben wird. Es folgen einige mögliche Beispielaufgaben der Klausur.
Art der Aufgabe | Beschreibung |
---|---|
Frage zum allgemeinen Verständnis | Beschreiben Sie die Bedeutung eines, oder mehrerer Themenschlagworte und zeigen Sie die Verwendung des Schlagwortes in einem C++ Beispielprogramm. Siehe Beispiel 1 und Beispiel 2. |
Fehler im C++ Programm finden | Finden Sie Fehler in einem vorgegebenen C++ Programm. Siehe Beispiel |
Folge, Summe, Produkt berechnen | Berechnen Sie mittels einer for-Schleife den Wert einer mathematischen Folge, Summe, oder eines Produktes (auch Doppelsummen und kombinierte Summe-Produkt Ausdrücke). Siehe Beispiel |
Rekursiv definierte Folge berechnen | Berechnen Sie mittels einer for-Schleife den Wert einer rekursiv definierten Folge (z.B. Fibonacci-Folge, logistische Abbildung, Bernoulli Abbildung, Mandelbrot-Menge). Beispiel siehe z.B. Aufgabe 2 im Übungsblatt Nr. 4 und Aufgabe 2 im Übungsblatt Nr. 5 |
Mathematische Funktion als C++ Funktion definieren | Definieren Sie eine C++ Funktion bei vorgegebenem mathematischen Ausdruck. Siehe Beispiel |
Nullstellensuche | Ihnen wird entweder ein C++ Programm der Methode der Bisektion (das Intervallhalbierungsverfahren) oder des Newton-Raphson Verfahren zur Nullstellenermittlung vorgegeben (ohne Komentare). Erklären Sie wie das Programm die Nullstellenermittlung durchführt und verändern Sie das Programm um die Nullstelle einer speziellen, vorgegebenen Funktion zu bestimen. Siehe Beispiel |
Einen Teil eines C++ Programmes als Funktion(Methode) aus dem main-Programm auslagern | Definieren Sie eine C++ Funktion außerhald des C++ main-Programmes, die einen Teil des Programms (z.B. den Kernalgorithmus einer komplexen, zusammenhängenden Berechnung) als Funktion auslagert. Beispiel siehe z.B. Aufgabe 1 im Übungsblatt Nr. 9 |
Lagrange Polynom Methode | Ihnen wird ein C++ Programm der Methode der Lagrange Polynom vorgegeben (ohne Komentare). Erklären Sie was das Programm macht und verändern Sie das Programm um eine Berechnung bei neu vorgegebenen Stützstellen zu berechnen. Siehe Beispiel |
Ein- und mehrdimensionale Arrays | Definieren Sie ein- und mehrdimensionale Arrays um Berechnungen des Skalarproduktes oder Kreuzproduktes zu programmieren. Beispiel siehe z.B. Aufgabe 2 in Übungsblatt Nr. 6 |
Numerische Differentation | Ihnen wird eine, oder mehrere Formeln zur approximativen Berechnung der Ableitung einer Funktion (Differentationsregeln der numerischen Mathematik) bereitgestellt. Berechnen Sie die Ableitung einer vorgegebenen Funktion $f(x)$ an der Stelle $x$. Lassen Sie sich die Ableitung der Funktion in einem gewissen Teilintervall $x \in [a,b]$ im Terminal ausgeben. Siehe Beispiel |
Einen Teil eines C++ Programmes als Klasse aus dem main-Programm auslagern | Definieren Sie eine C++ Klasse außerhalb des C++ main-Programmes, die einen Teil des Programms (z.B. den Kern-Algorithmus einer komplexen, zusammenhängenden Berechnung) als eine eigene Klasse auslagert. Beispiel siehe z.B. Aufgabe 1 in Übungsblatt Nr. 7 und Übungsblatt Nr. 8 |
Numerische Integration | Ihnen wird eine, oder mehrere Formeln zur approximativen Berechnung des Integrals einer Funktion (Integrationsregeln der numerischen Mathematik) bereitgestellt. Berechnen Sie das bestimmte Integral bei vorgegebenen Funktion $f(x)$ mittels eines C++ Programmes. Siehe auch Beispiel |
Verwendung des vector-Containers der Standardbibliothek | Ihnen wird ein Programm vorgegeben, das eine Berechnung unter Verwendung von integrierten eindimensionalen C++ Arrays durchführt. Schreiben Sie das Programm um und verwenden Sie anstelle des eindimensionalen C++ Arrays nun einen vector-Container der Standardbibliothek. Siehe auch Beispiel |
Numerische Lösen von Differentialgleichungen | Ihnen wird ein Programm vorgegeben, das die Lösung einer Differentialgleichung (oder eines Systems von Differentialgleichungen) bei vorgegebener Anfangsbedingung, mittels des Euler- oder Runge-Kutta Verfahrens durchführt. Verändern Sie das Programm nun, um eine andere Differentialgleichung mit auch anderen Anfangsbedingungen in einem Teilintervall $t \in [a,b]$ zu lösen. Siehe auch Beispiel |
Komplexe, umfangreiche Teilaufgabe | Mindestens eine der Klausuraufgaben wird eine mehrschichtige komplexe Transfer-Aufgabe sein. Es wird dabei ein neues, in der Vorlesung noch nicht behandeltes Problem dargestellt und die Studierenden sollen eine eigene numerische Lösung des Systems skizzieren. |
Was versteht man unter einem integrierten Datentyp in C++? Nennen Sie drei oft verwendete C++ Typen und Deklarieren und Initialisieren diese in einem C++ Programm.
#include <iostream> #include <cmath> int main(){ double q = 8.0/9; double k; const unsigned int N = 100; const double wert_approx = 0; printf("# 0: Summenindex k \n# 1: Approximierter Wert der Summe \n"); for(k=0; k<=N; --k){ wert_approx = wert_approx + q**k; printf("%4i %20.15f \n",k, wert_approx); } printf("# Approximierter Wert der Summe: %20.15f \n", wert_approx); printf("# Wert im Limes der geometrischen Reihe: %20.15f \n", 1.0/(1-q)); }
Das nebenstehende C++ Programm soll das Konvergenzverhalten der Folgenglieder der Partialsumme \[ \begin{equation} s_n = \sum_{k=0}^n q^k,\quad \hbox{mit:} \quad q = \frac{8}{9} \end{equation} \] für die ersten 100 Folgenglieder im Terminal ausgeben. Zusätzlich werden die berechneten Werte mit dem Grenzwert der unendlich geometrischen Reihe \[ \begin{equation} \sum_{k=0}^\infty q^k = \frac{1}{1-q},\quad \hbox{mit:} \quad q \in ℝ \,\, , \,\, \left| q \right| < 1 \end{equation} \] verglichen, der sich im Grenzwert $n \rightarrow \infty$ ergeben sollte. Leider hat das Programm einige Fehler (mindestens 4). Geben Sie diese an.
#include <iostream> #include <cmath> int main(){ double W_1; double W_2; double W_3; // Ihr Quelltext soll hier eingefügt werden. printf("%-24s %-24s %-24s \n","W_1","W_2","W_3"); printf("%-24.15f %-24.15f %-24.15f \n",W_1,W_2,W_3); }
Das nebenstehende Grundgerüst eines C++ Programms soll benutzt werden, um die folgenden Ausdrücke zu berechnen \[ \begin{equation} W_1 = \sum_{k=0}^{2536} \left( \frac{1}{2} \right)^k \quad,\quad W_2 = \sum_{k=3}^{45} k^{\frac{3}{5}} \quad,\quad W_3 = \sum_{k=-5}^{20} \frac{k^5}{e^{-k} + 1} \quad . \end{equation} \] Implementieren Sie die noch fehlenden Teile im Quelltext und berechnen Sie die Werte $W_1, W_2$ und $W_3$.
Erstellen Sie eine C++ Funktion, die als Rückgabewert den folgenden Funktionswert der mathematischen Funktion \[ \begin{equation} f(x,y) = x^3 - 8 \, (x + y)^2 + 2 \, x + 3 \end{equation} \] zurückgibt, wobei die Variablen $x$ und $y$ in der Argumentenliste der Funktion stehen sollten ($(x,y) \in$ ℝ$^2$ ).
Das folgende C++ Programm berechnet den Schnittpunkt der Funktionen $g(x)=x^3$ und $h(x)=$ln$(x)+5$ mittels der Methode der Bisektion (Intervallhalbierungsverfahren). Die ersten 11 Schritte des Intervallhalbierungsverfahrens werden im Terminal ausgegeben und mit dem Literaturwert des x-Wertes des Schnittpunktes ($x = 1.7729093296693715...$) verglichen.
#include <iostream> #include <cmath> double f (double x) { double wert; //---------------------- Zeile fehlt (Eintrag 1) return wert; } int main(){ int i; double a = 0.25; double b = 2.5; const int N = 10; //---------------------- Zeile fehlt (Eintrag 2) double fa=f(a); const double Loes = 1.7729093296693715; printf("# 0: Index i der Iteration \n# 1: Approximierter Wert der Nullstelle p_i \n"); printf("# 2: Funktionswert f(p_i) \n# 3: Relativer Fehler zum wirklichen Wert |(p-p_i)/p| \n"); printf("# 4: Untergrenze a \n# 5: Obergrenze b \n"); for(i=0; i<=N; ++i){ //---------------------- Zeile fehlt (Eintrag 3) fp = f(p); printf("%3d %14.10f %14.10f %14.10f %14.10f %14.10f \n",i, p, fp, fabs((Loes-p)/Loes), a, b); if( fa*fp > 0 ){ a = p; fa = fp; } else if( fa*fp < 0 ){ b = p; } else{ //---------------------- Zeile fehlt (Eintrag 4) } } }
An vier Stellen des Quelltextes fehlen leider die Zeilen. Tragen Sie bitte diese fehlenden Einträge nach.
Das folgende C++ Programm berechnet das Lagrangepolynom (vom Grade $n=2$), welches durch die Funktion $f(x)=1/x$ und die drei Stützstellenpunkte $\vec{x}=\left( 2, 2.5, 4 \right)$ bestimmt ist. Es werden 300 (x,y)-Werte des Polynoms im Teilintervall $[a,b]=[0.5, 6.0]$ ausgegeben.
#include <iostream> double f(double x){ double wert; wert = 1.0/x; return wert; } int main(){ double points[] = { 2, 2.5, 4 }; const unsigned int N_points = 3; double plot_a=0.5; double plot_b=6; const unsigned int N_xp=300; double dx = (plot_b - plot_a)/N_xp; double x = plot_a; double xp[N_xp+1]; double fp[N_xp+1]; double Pfp[N_xp+1]; double Lk; printf("# 0: Index j \n# 1: x-Wert \n# 2: f(x)-Wert \n"); printf("# 3: Approximierter Wert des Lagrange Polynoms P(x) \n"); printf("# 4: Fehler zum wirklichen Wert f(x)-P(x) \n"); for(int j = 0; j <= N_xp; ++j){ x = plot_a + j*dx; xp[j]=x; fp[j]=f(x); Pfp[j]=0; for(int k = 0; k < N_points; ++k){ Lk=1; for(int i = 0; i < N_points; ++i){ if(i != k){ Lk = Lk * (x - points[i])/(points[k] - points[i]); } } Pfp[j] = Pfp[j] + f(points[k])*Lk; } } for(int j = 0; j <= N_xp; ++j){ printf("%3d %14.10f %14.10f %14.10f %14.10f \n",j, xp[j], fp[j], Pfp[j], (fp[j] - Pfp[j])); } }
Verändern Sie das Programm, sodass nun das Lagrange Polynom vom Grade $n=8$ im Teilintervall $[a,b]=[0.91,9.07]$ mit $f(x) = 10 \, e^{-x/5} \cdot \hbox{sin}(3 \, x)$ und $\vec{x}=\left( 1,2,3,4,5,6,7,8,9 \right)$ berechnet wird. Markieren Sie im Programm die Zeilen, die Sie verändern möchten und schreiben die abgeänderten Zeilen auf Ihren Klausurbogen mit Angabe der Zeilenmarke.
Verwenden Sie die folgenden beiden Differentiationsregeln der numerischen Mathematik
um die Ableitung der Funktion $f(x) = e^{x^2} + x^3 - 2 \cdot x$ bei $x=5$ numerisch zu berechnen (bitte benutzen Sie $h=0.01$). Für beide Differentiationsregeln soll der approximierte Wert der Ableitung und dessen Fehler zu wirklichen Wert im Terminal ausgegeben werden. Benutzen Sie dabei das folgende Grundgerüst eines C++ Programms und geben an, was an den vier markierten Stellen eingetragen werden muss.
#include <stdio.h> #include <cmath> double f(double x){ double wert; // Ihr Quelltext soll hier eingefügt werden (Marke 1) return wert; } double f_strich(double x){ double wert; // Ihr Quelltext soll hier eingefügt werden (Marke 2) return wert; } int main(){ double x = 5; // Ihr Quelltext soll hier eingefügt werden (Marke 3) double f3_strich_p; double f5_strich_p; // Ihr Quelltext soll hier eingefügt werden (Marke 4) printf("h-Wert: h = %14.10f \n", h); printf("Wirklicher Wert: f'(x=%5.3f) = %14.10f \n", x, f_strich(x)); printf("Dreipunkte-Mittelpunkt-Formel: f'(x=%5.3f) = %14.10f , Fehler: %14.10f \n", x, f3_strich_p, f_strich(x) - f3_strich_p); printf("Fuenfpunkte-Mittelpunkt-Formel: f'(x=%5.3f) = %14.10f , Fehler: %14.10f \n", x, f5_strich_p, f_strich(x) - f5_strich_p); }
Was versteht man unter einem benutzerdefinierten Typ in C++ ? Nennen Sie ein Beispiel und definieren Ihren eigenen Typ mit dem Namen 'Eigener_Typ'.
Beschreiben Sie, was das folgende C++ Programm macht und kommentieren Sie die mit "// ... (Kommentar i)" (i $\in [1,2,..,10]$) markierten Stellen des Quelltextes.
#include <iostream> #include <cmath> // ... (Kommentar 1) class Integral{ // ... (Kommentar 2) double a; double b; unsigned ts; public: // ... (Kommentar 3) Integral(double set_a, double set_b, unsigned int set_ts) : a{set_a}, b{set_b}, ts{set_ts} { } Integral(double set_a, double set_b) : a{set_a}, b{set_b}, ts{10} { } Integral() : a{0}, b{1}, ts{10} { } // ... (Kommentar 4) double f(double x); // ... (Kommentar 5) double integrate() { double I_wert = 0; double h = (b-a)/(4*ts); double x_u; // ... (Kommentar 6) for(int i = 0; i < ts; ++i){ x_u = a + 4*i*h; I_wert = I_wert + 2*h/45*(7*f(x_u) + 32*f(x_u+h) + 12*f(x_u+2*h) + 32*f(x_u+3*h) + 7*f(x_u+4*h)); // ... (Kommentar 7) } return I_wert; } }; // ... (Kommentar 8) double Integral::f(double x){ double wert; wert = 10 * exp(-x/5) * sin(3*x); return wert; } int main(){ double I_analyt = - 4.7587485166819485; // ... (Kommentar 9) Integral I1 {1,2}; Integral I2 {1,2,50}; Integral I3 {1,2,100}; Integral I4 {1,2,1000000}; // ... (Kommentar 10) printf("I1 = %20.14f , Abs. Fehler %20.14f (Anzahl der Teilintervalle ist 10) \n", I1.integrate(), I1.integrate() - I_analyt); printf("I2 = %20.14f , Abs. Fehler %20.14f (Anzahl der Teilintervalle ist 50) \n", I2.integrate(), I2.integrate() - I_analyt); printf("I3 = %20.14f , Abs. Fehler %20.14f (Anzahl der Teilintervalle ist 100) \n", I3.integrate(), I3.integrate() - I_analyt); printf("I4 = %20.14f , Abs. Fehler %20.14f (Anzahl der Teilintervalle ist 1 Millionen) \n", I4.integrate(), I4.integrate() - I_analyt); printf("Analytisch I = %20.14f\n", I_analyt); }
#include <iostream> #include <vector> using namespace std; int main(){ vector<int> w = {1,4,6,8,9,5,3}; w.push_back(10); printf("w = ("); for (auto& n : w){ printf("%3i ", n); } printf(") \n \n"); w.insert(w.begin()+3,99); printf("w = ("); for ( int& n : w ){ printf("%3i ", n); } printf(") \n \n"); w[5] = 77; printf("w = ("); for(int i=0; i<w.size(); ++i){ printf("%3i ", w[i]); } printf(") \n"); }
Beschreiben Sie, was das folgende C++ Programm macht und geben Sie an, wie die Terminalausgabe beim Ausführen des kompilierten Programmes aussieht. Nennen Sie die Vorteile (und Nachteile), die ein vector-Container der Standardbibliothek gegenüber einem integrierten eindimensionalen Datenarray hat.
Definieren Sie einen weiteren Vektor ($\vec{v}$), und initialisieren diesen mit einer gewissen Anzahl von Gleitkommazahlen. Die Dimension des Vektors $\vec{v}$ soll der Dimension des Vektors $\vec{w}$ am Ende des Programmes entsprechen. Berechnen Sie dann, am Ende des Programmes, das Skalarprodukt der beiden Vektoren ($\vec{v} \cdot \vec{w}$) und lassen Sie sich berechneten Wert im Terminal ausgeben.
Lagern Sie die Berechnung des Skalarproduktes in einer C++-Funktion aus und definieren Sie dafür die Berechnung des Skalarproduktes außerhalb des Hauptprogramms.
Jetzt sei die nullte Komponente des Vektors $\vec{v}$ unbestimmt ($\vec{v} = \left( a, ... \right)$) und durch die Variable $a$ gekennzeichnet. Der Wert des Skalarproduktes der beiden Vektoren sei jedoch bekannt ($\vec{v} \cdot \vec{w} = 250.0$). Berechnen Sie am Ende des Programmes den unbekannten Wert $a$ der nullten Komponente des Vektors $\vec{v}$ und lassen Sie sich diesen im Terminal ausgeben.
Die folgenden zwei Programme stellen die Musterlösung der Aufgabe dar. Das C++ Programm auf der linken Seite stellt die Lösung ohne ausgelagerte Funktion der Berechnung des Skalarproduktes dar, und das Programm auf der rechten Seite berechnet das Skalarprodukt mittels einer ausgelagerten Funktion. Die Terminalausgabe der Programme ist in dem unteren Bild auf der linken Seite dargestellt.
#include <iostream> #include <vector> using namespace std; int main(){ vector<int> w = {1,4,6,8,9,5,3}; vector<double> v = {1.2,4.2,6.3,8.6,9.8,5.3,3.0,1.0,2.0}; double skalar_prod = 0; double a; w.push_back(10); printf("w = ("); for (auto& n : w){ printf("%3i ", n); } printf(") \n \n"); w.insert(w.begin()+3,99); printf("w = ("); for ( int& n : w ){ printf("%3i ", n); } printf(") \n \n"); w[5] = 77; printf("w = ("); for(int i=0; i<w.size(); ++i){ printf("%3i ", w[i]); } printf(") \n \n"); for(int i=0; i<w.size(); ++i){ skalar_prod = skalar_prod + w[i] * v[i]; } printf("Wert des Skalarproduktes w*v : %10.5f \n \n", skalar_prod); skalar_prod = 0; for(int i=1; i<w.size(); ++i){ skalar_prod = skalar_prod + w[i] * v[i]; } a = (250 - skalar_prod)/w[0]; printf("a = %10.5f \n \n", a); }
#include <iostream> #include <vector> using namespace std; double skalar_prod_f (vector<double> v, vector<int> w){ double skalar_prod = 0; if ( w.size() == v.size() ) { for(int i=0; i<w.size(); ++i){ skalar_prod = skalar_prod + w[i] * v[i]; } } else { printf("Dimensionen der Vektoren sind nicht gleich :-( \n"); } return skalar_prod; } int main(){ vector<int> w = {1,4,6,8,9,5,3}; vector<double> v = {1.2,4.2,6.3,8.6,9.8,5.3,3.0,1.0,2.0}; double skalar_prod = 0; double a; w.push_back(10); printf("w = ("); for (auto& n : w){ printf("%3i ", n); } printf(") \n \n"); w.insert(w.begin()+3,99); printf("w = ("); for ( int& n : w ){ printf("%3i ", n); } printf(") \n \n"); w[5] = 77; printf("w = ("); for(int i=0; i<w.size(); ++i){ printf("%3i ", w[i]); } printf(") \n \n"); printf("v = ("); for(int i=0; i<v.size(); ++i){ printf("%3.2f ", v[i]); } printf(") \n \n"); printf("Wert des Skalarproduktes w*v : %10.5f \n \n", skalar_prod_f(v,w) ); skalar_prod = 0; for(int i=1; i<w.size(); ++i){ skalar_prod = skalar_prod + w[i] * v[i]; } a = (250 - skalar_prod)/w[0]; printf("a = %10.5f \n \n", a); }
#include <iostream> #include <vector> using namespace std; template<typename T_v, typename T_w> double skalar_prod_f (vector<T_v> v, vector<T_w> w){ double skalar_prod = 0; if ( w.size() == v.size() ) { for(int i=0; i<w.size(); ++i){ skalar_prod = skalar_prod + w[i] * v[i]; } } else { printf("Dimensionen der Vektoren sind nicht gleich :-( \n"); } return skalar_prod; } int main(){ ... wie in Vector_1La.cpp ... }
Die Version mit ausgelagerter Funktion (Programm Vector_1Lb.cpp, oben rechts) hat die beiden Vektoren ($\vec{v}$ und $\vec{w}$) als Argumente und liefert den Wert des Skalarproduktes als Rückgabewert. Diese Version der Funktion hat den Nachteil, dass man beim Aufrufen der Funktion im Hauptprogramm ( skalar_prod_f(v,w) ) beachten muss, dass man den double-Vektor $\vec{v}$ als erstes Argument und den int-Vektor $\vec{w}$ als zweites Argument verwenden muss. Vertauscht man die beiden Argumente ( skalar_prod_f(w,v) ), so ergibt sich ein Typ-Fehler. Eine Möglichkeit diesem Problem zu entgehen wäre eine weitere Funktion mit gleichem Namen zu definieren (eine überladene Funktion), bei der die Vektoren-Argumente in umgekehrter Reihenfolge stehen: "double skalar_prod_f (vector<int> w , vector<double> v){ ...}". Jedoch auch dann hätte man das Problem, falls einer der Vektoren z.B. einen Vektor vom Typ 'float' repräsentiert. Hier liegt ein typisches Problem vor, welches man weit eleganter durch die Verwendung von Templates beseitigen kann, wie das nebenstehende Programm auf der linken Seite zeigt.
In der Vorlesung 10 hatten wir im Unterpunkt Abgeleitete Klassen, Vererbung von Klassenmerkmalen und Klassenhierarchien das folgende C++ Programm (Pendel.cpp), zusammen mit der folgenden Header-Datei (Pendel.hpp) benutzt um die zeitliche Entwicklung eines physikalischen und mathematischen Pendels mit Reibung mittels des Runge-Kutta Verfahrens zu berechnen.
/* Header-Datei * Das physikalische und mathematische Pendel mit Reibung (Stokesscher Ansatz) * Berechnung der Lösung einer Differentialgleichung zweiter Ordnung * bzw. eines Systems von zwei Differentialgleichungen erster Ordnung * mittels der Runge-Kutta Ordnung vier Methode * Verfahren zur Loesung der DGL ist in einer abstrakten Basisklasse ausgelagert * Berechnung der Lösung innerhalb einer Methode (Klassenfunktion) Runge-Kutta-Verfahren * Überschreibung der virtuellen Funktion der Bewegungsgleichungen in der abgeleiteten Klasse (Subklasse) Pendel * Zeitentwicklung der fuer unterschiedliche t-Werte in [a,b] * Konstruktor: Pendel(Masse des Pendels, Länge des Pendels l, Reibung beta, Anfangszeit a, Endzeit b, Anzahl der Punkte N, Anfangswerte alpha) * bzw. Konstruktor: Pendel_math(...) */ #include <cmath> // Bibliothek für mathematisches (e-Funktion, Betrag, ...) #include <vector> // Vector-Container der Standardbibliothek using namespace std; // Benutze den Namensraum std // Abstrakte Basisklasse 'dsolve' class dsolve { public: // Daten-Member der Klasse double a; // Untergrenze des Zeit-Intervalls double b; // Obergrenze des Intervalls int N; // Anzahl der Punkte vector<double> alpha; // Anfangswerte vector<vector<double>> ym; // Lösungsmatrix vector<double> t; // Zeit-Vektor // Virtuelle Funktion dgls, wird an anderer Stelle definiert virtual vector<double> dgls(double t, vector<double> u_vec) = 0; // Konstruktor mit vier Argumenten (Initialisierung der Daten-Member) dsolve(double a_, double b_, int N_, vector<double> alpha_) :a(a_), b(b_), N(N_), alpha(alpha_) { t.push_back(a_); // Zum Zeit-Vektor die Anfangszeit eintragen ym.push_back(alpha_); // Zur Loesungs-Matrix die Anfangswerte eintragen } // Methode der Runge-Kutta-Methode void solve() { double h = (b - a)/N; // Abstand dt zwischen den aequidistanten Punkten des t-Intervalls (h=dt) int n = alpha.size(); // Anzahl der Anfangswerte bzw. DGL-Gleichungen (hier n=2) vector<double> k1(n),k2(n),k3(n),k4(n); // Deklaration der vier Runge-Kutta Parameter fuer jede Loesung vector<double> y(n); // Temporaerer Hilfsvektor for (int i = 0; i < N; ++i) { // for-Schleife ueber die einzelnen Punkte des t-Intervalls for (int j = 0; j < n; ++j) k1[j] = h * dgls(t[i], ym[i])[j]; // for-Schleife ueber die n Runge-Kutta k1-Parameter for (int j = 0; j < n; ++j) y[j] = ym[i][j] + k1[j] / 2; // Temporaerer Hilfsvektor for (int j = 0; j < n; ++j) k2[j] = h * dgls(t[i] + h / 2, y)[j]; // for-Schleife ueber die n Runge-Kutta k2-Parameter for (int j = 0; j < n; ++j) y[j] = ym[i][j] + k2[j] / 2; // Temporaerer Hilfsvektor for (int j = 0; j < n; ++j) k3[j] = h * dgls(t[i] + h / 2, y)[j]; // for-Schleife ueber die n Runge-Kutta k3-Parameter for (int j = 0; j < n; ++j) y[j] = ym[i][j] + k3[j]; // Temporaerer Hilfsvektor for (int j = 0; j < n; ++j) k4[j] = h * dgls(t[i] + h, y)[j]; // for-Schleife ueber die n Runge-Kutta k4-Parameter for (int j = 0; j < n; ++j) y[j] = ym[i][j] + (k1[j] + 2 * k2[j] + 2 * k3[j] + k4[j]) / 6; // Temporaerer Hilfsvektor ym.push_back(y); // Zum Loesungs-Vektor den neuen Wert eintragen t.push_back(a + (i + 1) * h); // Zum Zeit-Vektor die neue Zeit eintragen } // Ende for-Schleife ueber die einzelnen Punkte des t-Intervalls } // Ende des Methode 'solve()' }; // Ende der Klasse 'dsolve' // Sub-Klasse 'Pendel' class Pendel : public dsolve { public: double m; // Masse des Pendels double l; // Länge des Pendels double beta; // Reibungskoeffizient // Überschreibung der virtuellen Funktion dgls vector<double> dgls(double t, vector<double> u_vec) override { vector<double> du_dt(u_vec.size()); // Die zwei DGLs erster Ordnung werden als Standard-Vektor definiert du_dt[0] = u_vec[1]; // Zwei DGLs erster Ordnung du_dt[1] = - 9.81 / l * sin(u_vec[0]) - beta / m * u_vec[1]; // DGl des physikalischen Pendels mit Reibung (Stokesscher Ansatz) return du_dt; // Rueckgabewert der DGLs als Standard-Vektor } // Ende: Definition der Bewegungsgleichung // Konstruktor mit sieben Argumenten (Initialisierung der Instanzvariablen (m,l,beta), vier Argumente für dsolve) Pendel(double m_ = 1.0, double l_ = 1.0, double beta_ = 0.0, double a_ = 0, double b_ = 1, int N_ = 10, vector<double> alpha_ = {0.1, 0}) : dsolve(a_, b_, N_, alpha_), m(m_), l(l_), beta(beta_) {} // Methode der Ausgabe der kartesische Koordinaten des Pendels am Stützpunkt i vector<double> r(size_t i) { vector<double> w = {0.0,0.0}; if (i < t.size()) { w = {l*sin(ym[i][0]), -l*cos(ym[i][0])};} return w; } // Methode der Ausgabe der Geschwindigkeit des Pendels in kartesische Koordinaten am Stützpunkt i vector<double> v(size_t i) { vector<double> w = {0.0,0.0}; if (i < t.size()) { w = {l*cos(ym[i][0])*ym[i][1], l*sin(ym[i][0])*ym[i][1]};} return w; } }; // Ende der Unterklasse 'Pendel' //Definition der Sub-Sub-Klasse 'Pendel_math' (mathematische Pendel, harmonischer Oszillator) als abgeleitete Klasse der Unterklasse 'Pendel' class Pendel_math : public Pendel { public: // Konstruktor mit sieben Argumenten (Initialisierung der Instanzvariablen (m,l,beta), vier Argumente für dsolve) Pendel_math(double m_ = 1.0, double l_ = 1.0, double beta_ = 0.0, double a_ = 0, double b_ = 1, int N_ = 10, vector<double> alpha_ = {0.1, 0}) : Pendel(m_, l_, beta_, a_, b_, N_, alpha_){} // Überschreibung der virtuellen Funktion dgls vector<double> dgls(double t, vector<double> u_vec) override { vector<double> du_dt(u_vec.size()); // Die zwei DGLs erster Ordnung werden als Standard-Vektor definiert du_dt[0] = u_vec[1]; // Zwei DGLs erster Ordnung du_dt[1] = - 9.81 / l * u_vec[0] - beta / m * u_vec[1]; // DGl des mathematischen Pendels mit Reibung (Stokesscher Ansatz) return du_dt; // Rueckgabewert der DGLs als Standard-Vektor } // Ende: Definition der Bewegungsgleichung }; // Ende der Klasse 'Pendel_math'
/* Das physikalische und mathematische Pendel mit Reibung (Stokesscher Ansatz) * Berechnung der Lösung einer Differentialgleichung zweiter Ordnung * bzw. eines Systems von zwei Differentialgleichungen erster Ordnung * mittels der Runge-Kutta Ordnung vier Methode * Verfahren zur Loesung der DGL ist in einer abstrakten Basisklasse ausgelagert * Berechnung der Lösung innerhalb einer Methode (Klassenfunktion) Runge-Kutta-Verfahren * Überschreibung der virtuellen Funktion der Bewegungsgleichungen in der abgeleiteten Klasse (Subklasse) Pendel * Zeitentwicklung der fuer unterschiedliche t-Werte in [a,b] * Konstruktor: Pendel(Masse des Pendels, Länge des Pendels l, Reibung beta, Anfangszeit a, Endzeit b, Anzahl der Punkte N, Anfangswerte alpha) * bzw. Konstruktor: Pendel_math(...) * Ausgabe zum Plotten mittels Python Jupyter Notebook Pendel.py: "./a.out > Pendel.dat */ #include "Pendel.hpp" // Header-Datei des physikalischen und mathematischen Pendels mit Reibung (Stokesscher Ansatz) #include <iostream> // Standard Input- und Output Bibliothek in C, z.B. printf(...) using namespace std; // Hauptprogramm int main() { double m = 1.0; // Masse des Pendels double l = 0.5; // Länge des Pendels double beta = 0; // Reibungskoeffizient double a = 0; // Untergrenze des Zeit-Intervalls double b = 5; // Obergrenze des Intervalls int N = 1000; // Anzahl der Punkte vector<double> alpha = {0.0,8.2}; // Anfangswerte Pendel P_A = Pendel(m,l,beta,a,b,N,alpha); // Konstruktor bilded eine Instanz der Klasse Pendel P_A.solve(); // Methode der Runge-Kutta-Methode ausführen Pendel_math P_B = Pendel_math(m,l,beta,a,b,N,alpha); // Konstruktor bilded eine Instanz der Klasse Pendel_math P_B.solve(); // Methode der Runge-Kutta-Methode ausführen // Terminalausgabe, Beschreibung der ausgegebenen Groessen printf("# 0: Index i \n# 1: t-Wert \n"); printf("# 2: x(t) , physikalisches Pendel \n# 3: y(t) , physikalisches Pendel \n"); printf("# 4: v_x(t) , physikalisches Pendel \n# 5: v_y(t) , physikalisches Pendel \n"); printf("# 6: x(t) , mathematisches Pendel \n# 7: y(t) , mathematisches Pendel \n"); printf("# 8: v_x(t) , mathematisches Pendel \n# 9: v_y(t) , mathematisches Pendel \n"); // Terminalausgabe, ausgegeben der Werte for(size_t i=0; i < P_A.t.size(); ++i){ printf("%3ld %19.15f ",i,P_A.t[i]); printf("%19.15f %19.15f %19.15f %19.15f ",P_A.r(i)[0],P_A.r(i)[1],P_A.v(i)[0],P_A.v(i)[1]); printf("%19.15f %19.15f %19.15f %19.15f ",P_B.r(i)[0],P_B.r(i)[1],P_B.v(i)[0],P_B.v(i)[1]); printf("\n"); } } // Ende Hauptprogramm
Verändern Sie das C++ Programm Pendel.cpp, sodass 25 physikalische Pendel mit unterschiedlichen Anfangs-/Parameterwerten berechnet werden. Deklarieren Sie dafür im Hauptprogramm einen vector-Container, der als Behälter/Kiste für Ihre Pendelobjekte dient. Es sollen fünf Simulationen mit verschwindendem Reibungsterm durchgeführt werden, wobei die Simulationen sich im initialisierten Anfangswert der Winkelgeschwindigkeit $\dot{\phi}(0)$ unterscheiden sollen (wobei $\phi(0)=0$ für alle Simulationen gilt). Für diese Anfangswerte sollen auch jeweils vier Simulationen mit nicht-verschwindenden Reibungstermen durchgeführt werden. Die berechneten Orte und Geschwindigkeiten der Pendel sollen dann im Terminal (bzw. in eine Datei) ausgegeben werden, damit man die durchgeführten Simulationen später mit einem Python Skript visualisieren kann.
Das folgende Programm stellt eine Musterlösung der Aufgabe dar. Es wurden die berechneten Orts- und Geschwindigkeitswerte der 25 Pendel direkt in eine Ausgabedatei (Pendel_Container.dat) geschrieben.
/* 25 Pendel-Simulationen in einem <vector>-Container * Das physikalische und mathematische Pendel mit Reibung (Stokesscher Ansatz) * Berechnung der Lösung einer Differentialgleichung zweiter Ordnung * bzw. eines Systems von zwei Differentialgleichungen erster Ordnung * mittels der Runge-Kutta Ordnung vier Methode * Verfahren zur Loesung der DGL ist in einer abstrakten Basisklasse ausgelagert * Berechnung der Lösung innerhalb einer Methode (Klassenfunktion) Runge-Kutta-Verfahren * Überschreibung der virtuellen Funktion der Bewegungsgleichungen in der abgeleiteten Klasse (Subklasse) Pendel * Zeitentwicklung der fuer unterschiedliche t-Werte in [a,b] * Konstruktor: Pendel(Masse des Pendels, Länge des Pendels l, Reibung beta, Anfangszeit a, Endzeit b, Anzahl der Punkte N, Anfangswerte alpha) * bzw. Konstruktor: Pendel_math(...) * Direkte Ausgabe der berechneten Werte von 25 Pendel-Simulationen in eine Datei (Visualisierung mittels Pendel_Container.py) */ #include "Pendel.hpp" // Header-Datei des physikalischen und mathematischen Pendels mit Reibung (Stokesscher Ansatz) #include <iostream> // Standard Input- und Output Bibliothek in C, z.B. printf(...) #include <vector> // Sequenzieller Container vector<Type> der Standardbibliothek using namespace std; // Hauptprogramm int main() { size_t Anz_Pendel = 25; // Anzahl der zu berechnenden Pendel-Simulationen double m = 1.0; // Masse des Pendels double l = 0.5; // Länge des Pendels double beta = 0; // Reibungskoeffizient double a = 0; // Untergrenze des Zeit-Intervalls double b = 10; // Obergrenze des Intervalls int N = 100000; // Anzahl der Punkte vector<double> alpha = {0.0,8.2}; // Anfangswerte FILE *ausgabe; // Deklaration eines Files fuer die Ausgabedatei ausgabe = fopen("Pendel_Container.dat", "w+"); // Ergebnisse werden in die Datei "Pendel.dat" geschrieben vector<Pendel> Kiste_Pendel(Anz_Pendel); // Deklaration eines vector-Containers fuer die einzelnen Pendel-Objekte for (size_t n = 0; n < Anz_Pendel; ++n){ // for-Schleife zum Auffuellen des Containers mit Elementen vom Typ 'Pendel' if ((n % 5) == 0) { alpha[1] *= (1 + double(n)/55); beta = 0.0; } // Jeweils 5 Simulationen werden mit gleicher Anfangsgeschwindigkeit gemacht Kiste_Pendel[n] = Pendel(m,l,beta,a,b,N,alpha); // Initialisierung der Pendel-Objekte Kiste_Pendel[n].solve(); // Methode der Runge-Kutta-Methode ausführen beta += 0.2; // Der Parameter beta der Reibung wird veraendert } // Beschreibung der in die Ausgabedatei "Pendel_Container.dat" ausgegebenen Groessen fprintf(ausgabe, "%10s %20s %20s %20s %20s %20s %20s %20s \n", "# Index i", "t-Wert", "x(t), Pendel 1", "y(t), Pendel 1", "v_x(t), Pendel 1", "v_y(t), Pendel 1", "x(t), Pendel 2", "..."); // Ausgabe der Werte for(size_t i=0; i < Kiste_Pendel[0].t.size(); ++i){ // for-Schleife ueber die Zeitgitterpunkte der Simulation if ((i % 100) == 0) { // nur jeden 100-ten Wert ausgeben lassen fprintf(ausgabe, "%10ld %20.12f ",i, Kiste_Pendel[0].t[i]); // Ausgabe des Zeit-Indexes und der aktuellen Zeit in die Ausgabedatei for (auto& n : Kiste_Pendel){ // Bereichsbasierte for-Schleife ueber die einzelnen Pendel-Objekte fprintf(ausgabe, "%20.12f %20.12f %20.12f %20.12f ",n.r(i)[0],n.r(i)[1],n.v(i)[0],n.v(i)[1]); // Ausgabe der Orts- und Geschwindigkeitswerte des Pendel-Objektes } // Ende for-Schleife (Pendel-Objekte) fprintf(ausgabe, "\n"); // Neue Zeile in der Ausgabedatei } // Ende if } // Ende for-Schleife (Zeitgitterpunkte) fclose(ausgabe); // Schliessen der Ausgabedatei } // Ende Hauptprogramm
Die in die Datei 'Pendel_Container.dat' geschriebenen Daten können dann mittels des folgenden Python Skriptes (Pendel_Container.py) in einer Animation (siehe weiter unten) visualisiert werden.
# Python Programm zum Visualisieren der Daten des Pendel_Container.cpp Programms, # Mathematisches vs. physikalisches Pendel ohne Reibung # Es werden hier mehrere Bilder der zeitlichen Entwicklung des Systems in einem Ordner 'Bilder' gespeichert # !!!! Sie muessen vor der Ausfuehrung des Programms den Ordner Bilder erstellen !!!! # Die einzelnen Bilder kann mann dann mittels des folgenden Terminalbefehls zu einem Video binden: # ffmpeg -framerate 100 -i './Pendel_Container_%04d.jpg' -s 800x800 Pendel.mp4 # (die Framerate ergibt sich durch N_sim/b = 1000/5) # Mehrere unterschiedliche Pendelsimuationen werden in einem vector-Container gespeichert import matplotlib import matplotlib.pyplot as plt # Python Bibliothek zum Plotten (siehe https://matplotlib.org/ ) import numpy as np # Python Bibliothek fuer Mathematisches (siehe https://numpy.org/ ) grid_dim = 5 Anz_Pendel = grid_dim*grid_dim data = np.genfromtxt("./Pendel_Container.dat") # Einlesen der berechneten Daten von Pendel.cpp for it in range(100): # for-Schleife über die ersten hundert Iterationen print(it) # Terminalausgabe der Erstellung des i-ten Bildes fig, axs = plt.subplots(nrows=grid_dim, ncols=grid_dim, constrained_layout=False, figsize=(20,20)) n=0 for ax in axs.flat: ax.cla() ax.axis('off') ax.set_xlim(-0.6, 0.6) # Limitierung der x-Achse ax.set_ylim(-0.6, 0.6) # Limitierung der y-Achse ax.scatter( 0, 0, s=5, marker='o', c="black") # Zeichnen des Pendel-Koerpers ax.scatter( data[it,2+n*4], data[it,3 +n*4], s=70, marker='o', c="blue") # Zeichnen des Pendel-Koerpers ax.plot( [0 , data[it,2+n*4]] , [0 , data[it,3 +n*4]] ,c="black",linewidth=0.8) # Zeichnen der Pendel-Stange n=n+1 # Bild-Ausgabe mit Speicherung eines individuellen Iteration-Namens pic_name = "./Bilder/" + "Pendel_Container_" + "{:0>4d}".format(it) + ".jpg" plt.savefig(pic_name, dpi=200,bbox_inches="tight",pad_inches=0.05,format="jpg") plt.close()
Die mittels des Python Skriptes erstellten Bilder wurden nun mittels des Programmes 'ffmpeg' zu einem Film verbunden (siehe Animation auf der rechten Seite). Die fünf Pendel-Simulationen ohne Reibung sind am linken Ende des Animationsbildes veranschaulicht, wobei der Anfangswert der Geschwindigkeit von oben nach unten zunimmt. Die Pendel-Simulation in der oberen linken Ecke zeigt eine typische Pendelbewegung eines physikalischen Pendels ohne Reibung. Der Anfangswert der Geschwindigkeit ist hier noch moderat, sodass sich kein 'Überschlag des Pendels' ergibt. Die Pendel-Simulation darunter, links in der 2. Zeile, besitzt auch keinen Reibungsterm, jedoch ist jetzt der Anfangswert der Geschwindigkeit so weit erhöht, dass sich gerade ein Überschlag des Pendels ergibt.
In jeder Zeile wurde der Reibungsparameter $\beta$ von links nach rechts erhöht. Im Laufe der zeitlichen Entwicklung erkennt man gut, dass die Amplituden der Pendelbewegungen desto schneller gedämpft werden, je weiter rechts sich das Pendel befindet. Die Animationen auf der rechten Seite stellen stark gedämpfte Pendelbewegungen dar, die am Ende der Simulation, nach 10 Sekunden ($t_{end}=10$ s), schon fast zum Stillstand gekommen sind.
Zusatzaufgabe: Stellen Sie diese Pendel-Simulationen für das mathematische Pendel dar und vergleichen Sie die unterschiedlichen Pendelbewegungen.