Prüfungsrelevante Themen

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

Mögliche Beispielaufgaben der Klausur

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.

Einige Beispielaufgaben des ersten Teils der Vorlesung (bis inklusive Vorlesung 6)

C++: Integrierte Datentypen

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));
}

Fehler im C++ Programm finden

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);
}

Numerische Berechnung einer Summe

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$.


Mathematische Funktionen in C++

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$ ).


Nullstellensuche mittels des Intervallhalbierungsverfahrens

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.


Methode der Lagrange Polynome

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.


Numerische Differentation

Verwenden Sie die folgenden beiden Differentiationsregeln der numerischen Mathematik

\[ \begin{eqnarray} \hbox{Dreipunkte-Mittelpunkt-Formel (n=2):}\,\, && \,\, f^\prime(x_0) \approx \frac{1}{2h} \left[ f(x_0 + h) - f(x_0 - h) \right] \\ \hbox{Fünfpunkte-Mittelpunkt-Formel (n=4):}\,\, && \,\, f^\prime(x_0) \approx \frac{1}{12 \, h} \left[ f(x_0 - 2 \, h) - 8 \, f(x_0 - h) + 8 \, f(x_0 + h) - f(x_0 + 2 \, h) \right] \end{eqnarray} \]

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); 
}

Einige Beispielaufgaben des zweiten Teils der Vorlesung (beginnend ab Vorlesung 7)

Benutzerdefinierter Typ in C++

Was versteht man unter einem benutzerdefinierten Typ in C++ ? Nennen Sie ein Beispiel und definieren Ihren eigenen Typ mit dem Namen 'Eigener_Typ'.


Eine Klasse für die Integration einer Funktion

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");
}

Der vector-Container der Standardbibliothek

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.

Musterlösung

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.

Vector_1L.cpp
#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);
}
Vector_1La.cpp
#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);
}
Vector_1Lb.cpp (nicht Klausur relevant!)
#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.


Eine Kiste voller Pendel

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.

Pendel.hpp
/* 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'
Pendel.cpp
/* 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.


Musterlösung: Eine Kiste voller Pendel

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.

Pendel_Container.cpp
/* 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.

Pendel_Container.py
# 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.