Anwendungsbeispiel: Folgen und Reihen

In diesem Unterpunkt sollen einige Anwendungsbeispiele der for-, while- und do-Schleifen (siehe C++ Anweisungen: Die for-, while- und do-Schleifen) diskutiert werden. Es wird im Speziellen die konvergente Folge der Eulersche Zahl $e$ und die Leibniz-Reihe zur Berechnung der Kreiszahl $\pi$ vorgestellt. Obwohl es bei der nummerischen Berechnung von Folgen und Reihen besser ist, eine for-Schleife zu verwenden (offensichtliche Schleifenvariable, Aktualisierung der Schleifenvariable am Ende des 'Anweisungsblockes'), wird in den folgenden Anwendungsbeispielen stets eine while-Schleife verwendet. Das Umschreiben der Programme unter Verwendung einer for-Schleife ist den Studierenden überlassen (siehe Übungsblatt Nr. 3 , Aufgabe 2).

Anwendung: Mathematische Folgen

Im Übungsblatt Nr. 2 in der Aufgabe 1 wurde bereits die Folge $(a_n)_{n \in ℕ}$ mit $a_n := \left( 1 + \frac{1}{n} \right)^n$ vorgestellt, die als Grenzwert die Eulersche Zahl $e$ hat: \[ \begin{equation} e = \lim \limits_{n \to \infty} \left( 1 + \frac{1}{n} \right)^n \end{equation} \] Wir sind nun zunächst nicht an dem genauen Wert dieses Grenzwertes interessiert, sondern möchten das Konvergenzverhalten der Folge $a_n$ studieren und uns die ersten $N$ Folgenglieder mittels eines C++ Programms ausgeben lassen. Das folgende Programm gibt z.B. die ersten 20 ($N=20$) Folgenglieder auf 15 Nachkommastellen genau aus

While_4.cpp
/* Die Eulerschen Zahl e wird mittels ihrer Folgendefinition approximiert 
 * Ausgabe zum Plotten (Gnuplot oder Python) mittels: "./a.out > While_4.dat" */
#include <iostream>                                                   // Ein- und Ausgabebibliothek
#include <cmath>                                                      // Bibliothek für mathematisches (e-Funktion, Betrag, ...)

int main(){                                                           // Hauptfunktion
    unsigned int n = 1;                                               // Deklaration und Initialisierung des Folgenindex als naturliche Zahl
    const unsigned int N=20;                                          // Deklaration und Initialisierung des maximalen Folgengliedes
    double e_Approx;                                                  // Deklaration der approximierten Gleitkommazahl von e 
    
    printf("# 0: Folgenindex n \n# 1: Approximierter Wert von e \n"); // Beschreibung der ausgegebenen Groessen
    
    while( n <= N ){                                                  // Schleife zur Berechnung von e mit Abbruchbedingung 
        e_Approx = pow(1 + 1.0/n,n);                                  // Mathematische Folgendefinition fuer die Eulerschen Zahl e
        printf("%3i %20.15f \n",n, e_Approx);                         // Ausgabe des approximierten Wertes von e 
        n++;                                                          // Index der Folge wird um eins erhöht (entspricht n=n+1)
    }                                                                 // Ende der Schleife
}

, wobei die nebenstehende Abbildung die erzeugte Terminalausgabe zeigt. Anhand der ausgegebenen Werte erkennt man schon gut das Konvergenzverhalten der Folge, jedoch wäre es doch schöner, die Daten in einem Diagramm sich darzustellen. Es gibt nun mehrere Möglichkeiten, sich die von einem C++ Programm berechneten Simulationsdaten zu visualisieren. Im Folgenden werden zwei unterschiedliche Varianten vorgestellt: Die Darstellung mittels Gnuplot (siehe Gnuplot Homepage und Installation von Gnuplot unter Linux) und Python (siehe Matplotlib: Visualization with Python), wobei wir im weiteren Verlauf der Vorlesung ausschließlich die Python-Variante verwenden werden. Für beide Varianten ist es jedoch zunächst nötig, die im Terminal ausgegebenen Daten in einer separaten Datei zu speichern (z.B. in der Datei 'While_4.dat'). Man könnte die Ausgabe zwar auch direkt vom C++ Programm in eine Datei schreiben (dies behandeln wir in einer späteren Vorlesung). Im Folgenden werden wir jedoch die Datenumleitung in eine externe Datei mit einem Linux-Trick machen, den wir schon in Vorlesung 1 (siehe Programmiersprachen) kennengelernt hatten. Hierbei gibt man die Eingabezeile './a.out > While_4.dat' im Linux-Terminal ein und leitet dadurch die Terminalausgabe in die Datei 'While_4.dat' um.

Für die Visualisierung der Daten mittels Gnuplot habe ich Ihnen schon ein fertiges Gnuplot-Shellskript zum Download bereitgestellt (siehe Gnuplot-Shellskript: GnuPlot_While_4.sh). Bitte laden Sie sich dieses Skript herunter, platzieren es in dem gleichen Ordner, in welchem sich die erzeugte Datei 'While_4.dat' befindet und ändern die Rechte der heruntergeladenen Skriptdatei, sodass es ausführbar wird (Eingabe von '.chmod +rwx GnuPlot_While_4.sh' im Terminal). Startet man nun das Gnuplot-Skript mittels './GnuPlot_While_4.sh', so erscheint die rechts oben abgebildete Ausgabe im Terminal und zusätzlich wird eine Bilddatei (GnuPlot_While_4.png) erzeugt, die unsere Daten visualisiert (siehe Abbildung unten links). Das entsprechende Gnuplot-Shellskript ist unten rechts abgebildet.

Gnuplot-Shellskript: GnuPlot_While_4.sh
# Shell Script zum Plotten der berechneten Daten von (While_4.cpp) mittels Gnuplot
FILE=While_4.dat
echo "Starte Plotten mit Gnuplot"
gnuplot -persist << PLOT
reset
set terminal pngcairo size 600,450 enhanced font 'Verdana,10'
################################################
set title "Approximation der Eulerschen Zahl e mittels ihrer Folgendefinition"
set ylabel "e_{approx}"
set xlabel "Index n"
set key right bottom
set output sprintf('GnuPlot_While_4.png')
plot "$FILE" using 1:2 with linespoints lt rgb "blue", [n=0:20] exp(1) with lines lt rgb "red"
################################################
quit
PLOT
echo "Fertig"

Im abgebildeten Gnuplot-Shellskript wird zunächst die Datei definiert, in der sich die zu plottenden Daten befinden ( FILE=While_4.dat ) und danach wird die eine Terminalausgabe mittels echo "Starte Plotten mit Gnuplot" gemacht. Es sei hier noch erwähnt, dass sowohl in Gnuplot als auch in Python, die Zeilen welche mit dem Rautesymbol '#' beginnen ignoriert werden und somit wie Komentarzeilen fungieren. Diese Eigenschaft wurde bei der 'Beschreibung der ausgegebenen Größen' im C++ Programm verwendet (printf("# 0: Folgenindex n \n# 1: Approximierter Wert von e \n");). Das Plot-Programm 'Gnuplot' startet man im Terminal mit dem Befehl 'gnuplot'. Es werden dann mit den Befehlen 'set ...' mehrere Plot-Eigenschaften spezifiziert (z.B. der Titel des Diagramms: set title "...", die Bezeichnung der x- und y-Achsen, die Position der Plotlegende: set key, oder der Dateiname des zu erstellenden Diagramms). Das eigentliche Plotten der Daten wird mit dem Befehl 'plot "$FILE" using 1:2 ...' bewerkstelligt. Zusätzlich zu den Daten wird noch eine horizontale rote Linie in das Diagramm eingefügt, die den Referenzwert der Zahl $e$ veranschaulicht (mittels '..., [n=0:20] exp(1) ...').
Gnuplot ist ein mächtiges Programm für die Visualisierung von Daten, auf das wir aber an dieser Stelle nicht weiter eingehen werden. Der interessierte Leser findet unter dem folgenden Link weiteres Material: Gnuplot Homepage

Im Folgenden werden wir die in die Datei 'While_4.dat' geschriebenen Daten mittels Python visualisieren. Hierfür habe ich Ihnen ebenfalls ein fertiges Python-Skript zum Download bereitgestellt (siehe Python-Skript: PythonPlot_While_4.py). Bitte laden Sie sich dieses Skript herunter und platzieren es in dem gleichen Ordner in welchem sich die erzeugte Datei 'While_4.dat' befindet. Im Gegensatz zum Gnuplot-Shellskript müssen Sie bei dem Python-Skript die Rechte der heruntergeladenen Datei nicht ändern, sodass es ausführbar wird. Startet das Python-Skript mittels des Terminalbefehls 'python3 PythonPlot_While_4.py', so wird die Bilddatei 'PythonPlot_While_4.png' erzeugt (siehe Abbildung unten links) und zusätzlich erscheint ein neues, interaktives Fenster mit dem x-y-Diagramm (siehe Abbildung unten rechts).

In dem interaktiven Diagrammfenster kann der Benutzer z.B., indem er zuvor auf das Symbol mit der Lupe klickt, gewisse Teilbereiche der Abbildung vergrößert darstellen. Um die Linux-Shell wieder benutzen zu können, muss man zuvor das interaktive Fenster schließen. In der unteren Abbildung ist das entsprechende Python-Skript dargestellt. Dieses wird in dem nächsten Unterpunkt der Vorlesung besprochen (siehe Eine kleine Einführung in die Programmiersprache Python).

Python-Skript: PythonPlot_While_4.py
# Python Programm zum Plotten der berechneten Daten von (While_4.cpp)
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/ )

data = np.genfromtxt("./While_4.dat")                      # Einlesen der berechneten Daten von While_4.cpp

plt.title(r'Approximation der Eulerschen Zahl e mittels ihrer Folgendefinition') # Titel der Abbildung
plt.ylabel(r'$e_{approx}$')                                # Beschriftung y-Achse
plt.xlabel('Index n')                                      # Beschriftung x-Achse
plt.plot(data[:,0],data[:,1], marker='+', color="blue")    # Plotten der Daten
plt.plot([data[0,0],data[-1,0]],[np.e,np.e], color="red")  # Horizontale Linie, die den exakten Wert von e markiert

plt.savefig("PythonPlot_While_4.png", bbox_inches="tight") # Speichern der Abbildung als Bild
plt.show()                                                 # Zusaetzliches Darstellen der Abbildung in einem separaten Fenster

Anwendung: Mathematische Reihen

Wir betrachten im Folgenden das mathematische Problem der Berechnung der Kreiszahl $\pi = 3.141592653589793...$ und verwenden dafür die im Jahre 1682 von Gottfried Wilhelm Leibniz vorgestellte iterative Annäherung. Die sogenannte alternierende Leibniz-Reihe konvergiert im Limes $\lim \limits_{N \to \infty}$ zu dem Wert der irrationalen Zahl $\frac{\pi}{4}$: \[ \begin{equation} \sum_{k=0}^N \frac{(-1)^k}{2 k +1} = 1 - \frac{1}{3} + \frac{1}{5} \, - ... + \frac{(-1)^N}{2 N +1} ,\quad \hbox{mit:} \quad \sum_{k=0}^\infty \frac{(-1)^k}{2 k +1} = \frac{\pi}{4} \end{equation} \] Das folgende C++ Programm gibt die ersten 100 ($N=100$) Folgenglieder der Partialsumme der Leibniz-Reihe auf 15 Nachkommastellen genau aus und vergleicht am Ende den so approximierten Wert von $\pi$ mit seinem Wirklichen:

While_2.cpp
/* Mittels der Leibniz-Reihe 
 * (Gottfried Wilhelm Leibniz, Zeitschrift Acta Eruditorum, 1682)
 * wird die Kreiszahl Pi approximiert 
 * (Anzahl der Summanden = N) 
 * Ausgabe zum Plotten (Gnuplot oder Python) mittels: "./a.out >> While_2.dat" */
#include <iostream>                                                    // Ein- und Ausgabebibliothek
#include <cmath>                                                       // Bibliothek für mathematisches (e-Funktion, Betrag, ...)

int main(){                                                            // Hauptfunktion
    unsigned int k = 0;                                                // Definition des Summenindex als ganze Zahl
    const unsigned int N = 100;                                        // Definition der Anzahl der mitgenommenen Summenglieder als konstante ganze Zahl
    double Pi_Approx = 0;                                              // Definition der approximierten Gleitkommazahl von Pi (Datentyp 'double')
    
    printf("# 0: Summenindex k \n# 1: Approximierter Wert von Pi \n"); // Beschreibung der ausgegebenen Groessen
    
    while( k <= N ){                                                   // Schleife zur Berechnung von Pi/4 mit Abbruchbedingung
        Pi_Approx = Pi_Approx + pow(-1,k)/(2*k + 1);                   // Innerer Teil der Leibniz-Reihe
        printf("%4i %20.15f \n",k, 4*Pi_Approx);                       // Ausgabe des approximierten Wertes von Pi 
        ++k;                                                           // Index der Summe wird um eins erhöht
    }                                                                  // Ende der Schleife
    
    Pi_Approx = Pi_Approx*4;                                           // Leibniz-Reihe liefert Pi/4, deshalb hier mal 4
    
    printf("# Approximierter Wert von Pi:     %20.15f \n", Pi_Approx); // Ausgabe des approximierten Wertes 
    printf("# Wirklicher Wert von Pi:         %20.15Lf \n", M_PIl);    // Ausgabe des wirklichen Wertes  
}

Der approximierte Zahlenwert von $\pi$ ($\pi_{approx}$) wird am Anfang des Programms als double-Variable deklariert und auf den Wert Null initialisiert (double Pi_Approx = 0;). Im Gegensatz zum Programm While_2.cpp der Folgenberechnung der Zahl $e$, muss man hier den approximierten Wert in einer Variable speichern (im Programm While_2.cpp) hätte man die einzelnen Folgenglieder auch direkt mittels des printf(...)-Befehls ausgeben lassen können, ohne eine extra Variable definieren zu müssen). Beim ersten Durchlauf der while-Schleife besitzt der Summenindex 'k' und die Variable 'Pi_Approx' somit den Wert Null. Durch die Anweisung 'Pi_Approx = Pi_Approx + pow(-1,k)/(2*k + 1);' wird der Variable 'Pi_Approx' ein neuer Wert zugewiesen, der sich aus einer Addition seines alten Wertes (Pi_Approx=0) mit dem Zahlenwert $\frac{(-1)^0}{2 \cdot 0 +1}=1$ ergibt. Nach dieser Anweisung besitzt die Variable somit den Wert Eins (Pi_Approx = 1). In der darauf folgenden printf(...)-Ausgabe wird der aktuelle Wert von $k$ ($k=0$) und der aktuelle Wert von vier mal 'Pi_Approx' ausgegeben. Eine Multiplikation mit vier wird hierbei deshalb gemacht, da die unendliche Leibniz-Reihe ja $\frac{\pi}{4}$ liefert). Nach der Ausgabe wird der Summenindex $k$ um Eins erhöht und die Anweisungsbedingung der Schleife while-Schleife ( k <= N ) testet, ob ein erneutes Durchlaufen der Schleife nötig ist. Da ja nun '1 = k <= N = 100' gilt, sind noch viele weitere Durchläufe nötig, bis schließlich '101 = k <= N = 100' eine falsche Aussage ist und die Schleife beendet wird. Beim zweiten Durchlauf der Schleife ist zunächst $k=1$ und 'Pi_Approx=1' und ein erneutes Berechnen der Anweisung 'Pi_Approx = Pi_Approx + pow(-1,k)/(2*k + 1);' liefert 'Pi_Approx'$=1+\frac{(-1)^1}{2 \cdot 1 +1}=1+\frac{-1}{3}=\frac{2}{3}$, sodass in der Terminalausgabe der Wert $4 \cdot \frac{2}{3} \approx 2.666666666666667$ erscheint. Die folgenden beiden Abbildungen zeigen das Konvergenzverhalten der Leibniz-Reihe (siehe linke untere Abbildung) und die letzten Werte der Terminalausgabe des C++ Programms (siehe rechte untere Abbildung).

Die Terminalausgabe der while-Schleife endet bei $k=100$ und die letzten beiden Zeilen stellen die beiden printf(...)-Befehle dar, die sich außerhalb der while-Schleife befinden. Es wird hier der letzte approximierte Zahlenwert von $\pi$ ($\pi_{approx}= 4 \cdot$ 'Pi_Approx') mit dem wirklichen Wert von $\pi$ verglichen, wobei der "wirkliche Wert" durch die in <cmath> vordefinierte long double Zahl 'M_PIl' gegeben ist. Man erkennt hierbei, dass der mit $N=100$ erhaltene approximierte Wert von $\pi$ lediglich auf drei Nachkommastellen genau ist. Dies liegt unter anderem an dem alternierenden Verhalten der Leibniz-Reihe, welches man gut in der linken Abbildung erkennt. Die Abbildung stellt die Zahlenwerte der printf(...)-Befehle des inneren Teils der while-Schleife dar und wurde mithilfe des unten abgebildeten Python-Skripts erzeugt, wobei zuvor die ausgegebenen Daten wieder mit dem Trick './a.out > While_2.dat' in eine Datei 'While_2.dat' umgeleitet wurden. Das Python-Skript: PythonPlot_While_2.py ist dem Skript Python-Skript: PythonPlot_While_4.py von seiner Struktur her sehr ähnlich und es wurden lediglich die eingelesene Datei, der Titel, die Beschreibungen der x- und y-Achsen, die Position der roten horizontalen Linie und der Name der zu speichernden Bilddatei verändert.

Python-Skript: PythonPlot_While_2.py
# Python Programm zum Plotten der berechneten Daten von (While_2.cpp)
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/ )

data = np.genfromtxt("./While_2.dat")                                     # Einlesen der berechneten Daten von While_2.cpp

plt.title(r'Approximation der Kreiszahl $\pi$ mittels der Leibniz-Reihe') # Titel der Abbildung
plt.ylabel(r'$\pi_{approx}$')                                             # Beschriftung y-Achse
plt.xlabel('Index k')                                                     # Beschriftung x-Achse
plt.plot(data[:,0],data[:,1], marker='+', color="blue")                   # Plotten der Daten
plt.plot([data[0,0],data[-1,0]],[np.pi,np.pi], color="red")               # Horizontale Linie, die den exakten Wert von Pi markiert

plt.savefig("PythonPlot_While_2.png", bbox_inches="tight")                # Speichern der Abbildung als Bild
plt.show()                                                                # Zusaetzliches Darstellen der Abbildung in einem separaten Fenster

In den folgenden Unterpunkten dieser Vorlesung möchte ich nochmals auf die Die Computerarithmetik und auf den Fehler in numerischen Berechnungen vertiefend eingehen und Ihnen dann noch eine kleine Einführung in die Programmiersprache Python geben.