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 wichtigsten integrierten Datentypen von C++ (bool, char, int, float, double), Speicherbedarf, sizeof(...) Bezeichner auto Vorlesung 2
Arithmetik und Operatoren arithmetischen 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 Schleifenanweisungen, 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 in 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 in 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 am Beispiel eines Integer/Double Vektors, push_back(...), insert(...), resize(...), die Klasse als ein Container von Objekten, bereichsbasierte for()-Schleifen und die Klasse 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).
Beispiel siehe z.B. Aufgabe 4 in Musterlösung Übungsblatt Nr. 3
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 in Übungsblatt Nr. 5
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. 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

Das folgende C++ Programm (Pendel.cpp), zusammen mit der folgenden Header-Datei (Pendel.hpp) berechnet die zeitliche Entwicklung eines physikalischen Pendels mit Reibung mittels des Runge-Kutta Verfahrens.

Pendel.hpp
/* Beispiel einer von der Basisklasse 'Ding' abgeleitete Klasse 'Pendel' und einer davon abgeleiteten Sub-Subklasse Pendel_math
 * Zwei zusaetzliche private Instanzvariablen (l und beta) und die Winkelspezifischen Anfangswerte (phi und v_phi) 
 * wurden der abgeleiteten Pendel-Klasse hinzugefuegt
 * Der Konstruktor der Klasse Pendel erzeugt ein Objekt Ding und initialisiert seine eigenen Daten-Member
 * Der Konstruktor der, von der Klasse Pendel abgeleiteten Subklasse Pendel_math, erzeugt ein Objekt Pendel (und implizit somit auch ein Objekt Ding`) 
*/
#include <iostream>             // Ein- und Ausgabebibliothek
#include <cmath>                // Bibliothek fuer mathematisches (e-Funktion, Betrag, ...)
#include <vector>               // Vector-Container der Standardbibliothek
using namespace std;            // Benutze den Namensraum std

// Definition der Klasse 'Ding'
class Ding{
    public:
        // Oeffentliche Instanzvariablen (Daten-Member) der Klasse
        double t = 0.0;                       // Aktueller Zeitpunkt
        vector<double> r = { 0.0, 0.0, 0.0 }; // Ortsvektor zum Ort des Dings ++
        vector<double> v = { 0.0, 0.0, 0.0 }; // Geschwindigkeit des Dings
        
        // Konstruktor mit drei Argumenten zum Initialisieren
        Ding(double t_, vector<double> r_, vector<double> v_) : t{t_}, r{r_}, v{v_} { }
};

// Definition der Klasse 'Pendel' (hier nur eindimensionales (x,y), beschrieben durch Winkel phi, Aufhaengepunkt bei (0,0,0))
class Pendel : public Ding {
    // Private Instanzvariablen (Daten-Member) der Klasse
    vector<double> phi;    // Deklaration eines double Vektors zum Speichern der Loesung fuer u_1
    vector<double> v_phi;  // Deklaration eines double Vektors zum Speichern der Loesung fuer u_2
    vector<double> Zeit;   // Deklaration eines double Vektors zum Speichern der Zeit-Werte
    unsigned N_sim = 2500; // Anzahl der Gitter-Zeitpunkte der Simulation (Dimension der folgenden Loesungsvektoren)
    unsigned index = 0;    // Anzahl der schon berechneten Loesungswerte
    
    double k1_1,k2_1,k3_1,k4_1; // Deklaration der vier Runge-Kutta Parameter fuer u_1
    double k1_2,k2_2,k3_2,k4_2; // Deklaration der vier Runge-Kutta Parameter fuer u_2

    public:
        // Oeffentliche Instanzvariablen (Daten-Member) der Klasse
        double l = 1.0;       // Laenge des Pendels
        double beta = 0.0;    // Reibungsparameter
        
        // Konstruktor erzeugt ein Objekt Ding und initialisiert die Laenge l des Pendels und den Reibungskoeffizienten beta
        Pendel(double t_, double phi_, double v_phi_, double l_, double beta_, unsigned N_sim_) : 
        Ding( t_,  { l_ * sin(phi_), - l_ * cos(phi_), 0 },  { l_ * cos(phi_) * v_phi_, l_ * sin(phi_) * v_phi_, 0 }), l{l_}, beta{beta_}, N_sim{N_sim_}{ 
            Zeit.resize( N_sim + 2 );        // Die Anzahl der Eintraege im Vektor Zeit wird auf N+2 erhoeht
            phi.resize( N_sim + 2 );         // Die Anzahl der Eintraege im Vektor phi wird auf N+2 erhoeht
            v_phi.resize( N_sim + 2 );       // Die Anzahl der Eintraege im Vektor v_phi wird auf N+2 erhoeht
            
            Zeit[0] = t_;                    // Zum Zeit-Vektor die Endzeit eintragen
            phi[0] = phi_;                   // Zum y-Vektor den Anfangswert alpha_1=phi(a) eintragen
            v_phi[0] = v_phi_;               // Zum y'-Vektor den Anfangswert alpha_2=v_phi(a) eintragen
        }
        
        double f_1(double t, double u_1, double u_2){      // Deklaration und Definition der Funktion f_1(t,u_1,u_2) 
            double wert;
            wert = u_2;                                    // Eigentliche Definition der Funktion
            return wert;                                   // Rueckgabewert der Funktion f_1
        }                                                  // Ende der Funktion f_1
        
        virtual double f_2(double t, double u_1, double u_2){ // Deklaration und Definition der Funktion f_2(t,u_1,u_2)
            double wert;                                      // virtuell, da diese Funktion in weiteren abgeleiteten Klassen ueberschieben werden wird
            wert = - 9.81 / l * sin(u_1) - beta * u_2;        // DGl des physikalischen Pendels mit Reibung (Stokesscher Ansatz)
            return wert;                                      // Rueckgabewert der Funktion f_2
        }                                                     // Ende der Funktion f_2
        
        void Gehe_Zeitschritt(double dt, int N){              // Einen Zeitschritt um dt weiter mittels Runge-Kutta  
            double t_;
            double a = Zeit[index];    // Letzter berechneter Zeitpunkt
            double h = dt / N;         // Abstand dt zwischen den aequidistanten Punkten des t-Intervalls (h=dt)  
            double u_1 = phi[index];   // Definition der lokalen Variable u_1=y und Initialisierung mit dem letzten berechneten Wert 
            double u_2 = v_phi[index]; // Definition der lokalen Variable u_2=y' und Initialisierung mit dem letzten berechneten Wert
            
            for(int i=0; i <= N; ++i){                         // for-Schleife ueber die einzelnen Punkte des t-Intervalls
                t_ = a + i*h;                                  // Zeit-Parameter wird um h erhoeht

                k1_1 = h*f_1(t_,u_1,u_2);                      // Runge-Kutta Parameter k1 fuer u_1
                k1_2 = h*f_2(t_,u_1,u_2);                      // Runge-Kutta Parameter k1 fuer u_2
                k2_1 = h*f_1(t_+h/2,u_1+k1_1/2,u_2+k1_2/2);    // Runge-Kutta Parameter k2 fuer u_1
                k2_2 = h*f_2(t_+h/2,u_1+k1_1/2,u_2+k1_2/2);    // Runge-Kutta Parameter k2 fuer u_2
                k3_1 = h*f_1(t_+h/2,u_1+k2_1/2,u_2+k2_2/2);    // Runge-Kutta Parameter k3 fuer u_1
                k3_2 = h*f_2(t_+h/2,u_1+k2_1/2,u_2+k2_2/2);    // Runge-Kutta Parameter k3 fuer u_2
                k4_1 = h*f_1(t_+h,u_1+k3_1,u_2+k3_2);          // Runge-Kutta Parameter k4 fuer u_1
                k4_2 = h*f_2(t_+h,u_1+k3_1,u_2+k3_2);          // Runge-Kutta Parameter k4 fuer u_2 
                u_1 = u_1 + (k1_1 + 2*k2_1 + 2*k3_1 + k4_1)/6;
                u_2 = u_2 + (k1_2 + 2*k2_2 + 2*k3_2 + k4_2)/6;
            }                                                  // Ende for-Schleife ueber die einzelnen Punkte des t-Intervalls   
            
            Zeit[index+1] = t_;                                // Zum Zeit-Vektor den neuen Wert eintragen
            phi[index+1] = u_1;                                // Zum y-Vektor den neuen Wert eintragen
            v_phi[index+1] = u_2;                              // Zum y'-Vektor den neuen Wert eintragen
            index++;                                           // index um eins erhoehen
            
            t = t_;                                            // Neuen Zeit-Wert des Pendels in der Klasse Ding aktualisieren
            r = { l * sin(u_1), - l * cos(u_1), 0 };           // Neuen Ort Wert des Pendels in der Klasse Ding aktualisieren
            v = { l * cos(u_1) * u_2, l * sin(u_1) * u_2, 0 }; // Neuen Geschwindigkeitswert des Pendels in der Klasse Ding aktualisieren
        }                                                      // Ende der Gehe_Zeitschritt Funktion
};                                                             // Ende der Klasse 'Pendel'

//Definition der Klasse 'Pendel_math' (mathematische Pendel, harmonischer Oszillator) als abgeleitete Klasse der Klasse Pendel
class Pendel_math : public Pendel {
    public:
        // Konstruktor erzeugt ein Objekt 'Pendel' und implizit auch ein Objekt Ding`
        Pendel_math(double t_, double phi_, double v_phi_, double l_, double beta_, unsigned N_sim_) : Pendel(t_, phi_, v_phi_, l_, beta_, N_sim_){}
    
            double f_2(double t, double u_1, double u_2) override { // Ueberschreiben der virtuellen Funktion aus der Klasse 'Pendel'
                double wert;
                wert = - 9.81 / l * u_1 - beta * u_2;               // DGl des harmonischen Oszillators (omega_0**2 = g/l) mit Reibung (Stokesscher Ansatz)
                return wert;                                        // Rueckgabewert der Funktion f_2
            }                                                       // Ende der Funktion f_2                                                
};                                                                  // Ende der Klasse 'Pendel_math'
Vorlage von Pendel.cpp
/* Vorlage von Pendel.cpp (ohne mathematisches Pendel)
*/
#include "Pendel.hpp"          // Pendel und Ding Klassen
#include <iostream>            // Ein- und Ausgabebibliothek

int main(){                    // Hauptfunktion
    double a = 0.0;            // Untergrenze des Zeit-Intervalls
    double b = 10.0;           // Untergrenze des Zeit-Intervalls
    int N_RK = 1000;           // Anzahl der Gitter-Zeitpunkte des Runge-Kutta Ordnung vier Verfahrens
    int N_sim = 2000;          // Anzahl der Gitter-Zeitpunkte der Simulation (Anzahl der ausgegebenen Punkte)
    double dt = (b - a)/N_sim; // Abstand dt zwischen den aequidistanten Punkten des Sim-Intervalls (h=dt)   
    
    Pendel Pendel_A = Pendel(a, 0.0, 8.2, 0.5, 0.0, N_sim); // Konstruktor bilded eine Instanz der Subklasse 'Pendel' (Basisklasse 'Ding')
    
    printf("# 0: Index i \n# 1: t-Wert \n");                // Beschreibung der ausgegebenen Groessen
    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("%5d %19.15f %19.15f %19.15f %19.15f %19.15f \n",0, Pendel_A.t, Pendel_A.r[0], Pendel_A.r[1], Pendel_A.v[0], Pendel_A.v[1]);
    
    // Terminalausgabe von Ort und Geschwindigkeit des Pendels 
    for(int i=1; i  <= N_sim; ++i){                          // for-Schleife zur Terminalausgabe der Loesung (Zeitgitterpunkte)
        Pendel_A.Gehe_Zeitschritt(dt, N_RK);                 // Aufruf der Methode Gehe_Zeitschritt(dt, N) der Klasse Pendel
        printf("%5d %19.15f %19.15f %19.15f %19.15f %19.15f \n",i, Pendel_A.t, Pendel_A.r[0], Pendel_A.r[1], Pendel_A.v[0], Pendel_A.v[1]); 
    }                                                        // Ende for-Schleife (Zeitgitterpunkte)
}                                                            // Ende main()-Funktion

Verändern Sie das C++ Programm Pendel.cpp, sodass 25 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. Dabei wurde (wie auch bei der sonstigen Terminalausgabe) die 'printf()-Familie' der Standardbibliothek benutzt (siehe 'fprintf(ausgabe, ...)') und nicht die C++ Ausgabe mittels 'std::ofstream file_out{"..."};' (siehe Vortrag von J. Misch und A. Gehwald, C++ for Physicists: C++ Features, File I/O).

Pendel_Container.cpp
/* Beispiel einer, von der Basisklasse 'Ding' abgeleitete Klasse 'Pendel'
 * Zwei zusaetzliche private Instanzvariablen (l und beta) und die winkelspezifischen Anfangswerte (phi und v_phi) 
 * wurden der abgeleiteten Pendel-Klasse hinzugefuegt
 * Der Konstruktor der Klasse Pendel erzeugt ein Objekt Ding und initialisiert seine eigenen Daten-Member
 * Von der Klasse Pendel wurde eine weitere Subklasse 'Pendel_math' abgeleitet, welche die lineare Näherung der Pendel-DGL benutzt (gedaempfter harmonischer Ozillator)
 * Direkte Ausgabe der berechneten Werte von 25 Pendel-Simulationen in eine Datei (Visualisierung mittels Pendel_Container.py)
*/
#include "Pendel.hpp"          // Pendel und Ding Klassen
#include <iostream>            // Ein- und Ausgabebibliothek
#include <vector>              // Sequenzieller Container vector<Type> der Standardbibliothek

int main(){                    // Hauptfunktion
    const int Anz_Pendel = 25; // Anzahl der zu berechnenden Pendel-Simulationen
    double v_phi_0 = 8.2;      // Anfangsgeschwindigkeitsbereich (kleinster Wert) der Pendelsimulationen
    double beta = 0.0;         // Anfangsgeschwindigkeitsbereich (kleinster Wert) der Pendelsimulationen
    double a = 0.0;            // Untergrenze des Zeit-Intervalls
    double b = 10.0;            // Untergrenze des Zeit-Intervalls
    int N_RK = 1000;            // Anzahl der Gitter-Zeitpunkte des Runge-Kutta Ordnung vier Verfahrens
    unsigned int N_sim = 2000; // Anzahl der Gitter-Zeitpunkte der Simulation (Anzahl der ausgegebenen Punkte)
    double dt = (b - a)/N_sim; // Abstand dt zwischen den aequidistanten Punkten des Sim-Intervalls (h=dt)
    
    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;         // Deklaration eines vector-Containers fuer die einzelnen Pendel-Objekte
    
    for (unsigned int n = 0; n < Anz_Pendel; ++n){                                  // for-Schleife zum Auffuellen des Containers mit Elementen vom Typ 'Pendel' 
        if ((n % 5) == 0) { v_phi_0 = v_phi_0 * ( 1 + double(n)/55 ); beta = 0.0; } // Jeweils 5 Simulationen werden mit gleicher Anfangsgeschwindigkeit gemacht
        Kiste_Pendel.push_back( Pendel {a, 0.0, v_phi_0, 0.5, beta, N_sim} );       // Initialisierung der Pendel-Objekte
        beta = beta + 0.2;                                                          // Der Parameter beta der Reibung wird veraendert
    }
    
//    Pendel_math Pendel_B = Pendel_math(a, 0.0, 8.2, 0.5, 0.0, N_sim); // Konstruktor bilded eine Instanz der Sub-Subklasse Pendel_math 
    
    // Beschreibung der in die Ausgabedatei "Pendel.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", "...");
    
    // Zeitliche Simulation 
    for(int i=0; i  <= N_sim; ++i){                              // for-Schleife  ueber die Zeitgitterpunkte der Simulation 
        fprintf(ausgabe, "%10d %20.12f ",i,  Kiste_Pendel[0].t); // 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[0], n.r[1], n.v[0], n.v[1]); // Ausgabe der Orts- und Geschwindigkeitswerte des Pendel-Objektes in die Ausgabedatei
            n.Gehe_Zeitschritt(dt, N_RK);                        // Aufruf der Funktion Gehe_Zeitschritt(...)
        }                                                        // Ende for-Schleife (Pendel-Objekte)
        fprintf(ausgabe, "\n");                                  // Neue Zeile in der Ausgabedatei
    }                                                            // Ende for-Schleife (Zeitgitterpunkte)
    fclose(ausgabe);            // Schliessen der Ausgabedatei
}                    // Ende main()-Funktion

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 200 -i './Pendel_%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(len(data[:,0])):      # for-Schleife fuer die zeitliche Entwicklung der Dinge in der Kiste
for it in range(3):  
    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 unten rechts). 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.