Das Projekt Die schwingende Kette ist ein Beispiel eines schwingenden, gekoppelten Massensystems und ein zentrales Problem aus der klassischen Mechanik (siehe Walter Greiner, 'Klassische Mechanik II' [8. Auflage, 2008, Kapitel I7. Seite 76]). Das System besteht aus einem masselosen Faden, der mit $N+1$ Massenpunkten (Dingen, Teilchen, Perlen) besetzt ist. Die $N+1$ 'Perlen' sollen in diesem Projekt als Objekte einer Klasse programmiert werden. Jede Perle besitzt eine ganzzahlige Instanzvariable $n$ (die Nummer der Perle), wobei die erste ($n=0$) und letzte Perle ($n=N+1$) so befestigt werden, dass sie sich nicht im Raum bewegen können. Beschreibt man das System im zweidimensionalen Raum und spannt die erste Perle bei ($x_0=0$ ,$y_0=0$) und die letzte Perle bei ($x_{N+1}=L$ ,$y_{N+1}=0$) ein, so soll eine über den gesamten Faden konstante Fadenspannung $T$ entstehen. Die Perlen seien in äquidistanten Abständen $a$ auf dem Faden angeordnet und die Auslenkung aus der Ruhelage in y-Richtung sei relativ klein, sodass die geringfügige Auslenkung in x-Richtung zu vernachlässigbar ist. Betrachten Sie somit zunächst den Fall einer ausschließlich vertikalen Auslenkung der Teilchen (Perlen).
Die y-Auslenkung des $n$-ten Teilchens $y_n$ wird durch die Auslenkung des ($n-1$)-ten Teilchens $y_{n-1}$ und des ($n+1$)-ten Teilchens $y_{n+1}$ beeinflusst. Die rücktreibenden Kräfte $\vec{F}_{n-1}$ und $\vec{F}_{n+1}$ besitzen die folgende Form \[ \begin{eqnarray} \vec{F}_{n-1} &=& - \left( T \cdot \hbox{sin}(\alpha_{n-1}) \right) \, \vec{e}_y \underbrace{\approx}_{\hbox{lineare Näherung}} - T \left( \frac{y_n - y_{n-1}}{a} \right) \, \vec{e}_y \\ \vec{F}_{n+1} &=& - \left( T \cdot \hbox{sin}(\alpha_{n+1}) \right) \, \vec{e}_y \underbrace{\approx}_{\hbox{lineare Näherung}} - T \left( \frac{y_n - y_{n+1}}{a} \right) \, \vec{e}_y \quad , \end{eqnarray} \] wobei $\alpha_{n-1}$ der mit der Horizontalen eingeschlossene Auslenkungswinkel zum ($n-1$)-ten Teilchen und $\alpha_{n+1}$ der Winkel zu seinem anderen Nachbarn, dem($n+1$)-ten Teilchen ist. Die Bewegungsgleichung des $n$-ten Teilchens in der linearen Näherung lautet somit: \[ \begin{eqnarray} m_n \, \frac{d^2 y_n}{dt^2} \, \vec{e}_y = \vec{F}_{n-1} + \vec{F}_{n+1} &=& - T \left( \frac{y_n - y_{n-1}}{a} \right) \, \vec{e}_y - T \left( \frac{y_n - y_{n+1}}{a} \right) \, \vec{e}_y \qquad \Leftrightarrow \\ \frac{d^2 y_n(t)}{dt^2} &=& \frac{T}{m_n \, a} \left( y_{n-1} - 2 \, y_n + y_{n+1} \right) \quad , \end{eqnarray} \] wobei $m_n$ die Masse des $n$-ten Teilchens ist und der Index $n$ von $n=1$ bis $n=N$ läuft. Die Bewegungsgleichung der schwingenden Kette stellt somit ein System von $N$ Differentialgleichungen zweiter Ordnung dar. Die Bewegungsgleichung für die erste und letzte freie Perle ($n=1$ und $n=N$) vereinfacht sich, da die ganz äußeren Eckperlen eingespannt sind (Randbedingung: $y_0(t)=0$ und $y_{N+1}(t)=0 \, , \,\, \forall \, t \in \,$ℝ) \[ \begin{equation} \frac{d^2 y_1(t)}{dt^2} = \frac{T}{m_1 \, a} \left( - 2 \, y_1 + y_{2} \right) \qquad \hbox{und} \qquad \frac{d^2 y_N(t)}{dt^2} = \frac{T}{m_N \, a} \left( y_{N-1} - 2 \, y_N \right) \quad . \end{equation} \]
Bevor wir die numerische Lösung simulieren betrachten wir uns zunächst die allgemeine Lösung des Systems von gekoppelten DGLs am Beispiel einer schwingenden Kette mit drei Perlen ($N=3$). Beim Klicken auf das nebenstehende Bild gelangen Sie zu dem Jupyter Notebook SchwingendeKette.ipynb in dem die analytischen und numerischen Lösungen in Python programmiert sind. Die analytische Lösung des Jupyter Notebooks basiert auf dem Python Modul "sympy", das ein Computer-Algebra-System für Python bereitstellt und eine Vielzahl an symbolischen Berechnungen im Bereich der Mathematik und Physik relativ einfach möglich macht.
Zunächst definieren wir uns das Systems von gekoppelten DGLs als sympy-Gleichung. Wir setzen der Einfachheit halber die Fadenspannung $T=1$ und die äquidistanten Abstände zwischen den Perlen auf $a=1$. Zusätzlich nehmen wir an, dass die Perlen alle die gleiche Masse hätten und setzen $m_1=m_2=m_3=1$. Mittels des sympy-Befehls "dsolve_system()" bestimmen wir die allgemeine analytische Lösung des Systems von gekoppelten DGLs. Die allgemeine analytische Lösung besitzt noch sechs unbestimmte Konstanten $C_1,C_2,C_3,C_4,C_5,C_6$, die durch die sechs Anfangsbedingung (Anfangsorte und Anfangsgeschwindigkeiten der drei Perlen) bestimmt sind. Wir setzen: $y_1(0)=\alpha_1 , y_2(0)=\alpha_2 , y_3(0)=\alpha_3$ , $\dot{y}_1(0)=\alpha_4 ,\dot{y}_2(0)=\alpha_5$ und $\dot{y}_3(0)=\alpha_6$ und geben diese allgemeine analytische Lösung des Systems von gekoppelten DGLs zum Vergleich mit unserem C++ Programm aus.
Um einen Einblick in die Bewegung der drei Perlen zu erhalten, müssen wir die Anfangsbedingungen festlegen. Wir lenken zum Beispiel zur Zeit $t=0$ nur die erste Perle um Eins aus ($y_1(0)=1$ , $y_2(0)=0$ , $y_3(0)=0$ , $\dot{y}_1(0)=0$ , $\dot{y}_2(0)=0$ und $\dot{y}_3(0)=0$). Die untere Abbildung veranschaulicht die Bewegung der schwingenden Kette in einer Animation. Wir erstellen diese Animation mittels des Python Moduls Matplotlib und benutzen hierbei die Funktion "FuncAnimation(...)" (siehe Animations using Matplotlib).
In dem Jupyter Notebook SchwingendeKette.ipynb berechnen wir auch zusätzlich die Eigenfrequenzen und Eigenschwingungen der schwingenden Kette auf analytischem Wege (für drei und fünf Perlen). Eine Eigenschwingung der Kette liegt dann vor, wenn alle Perlen mit der gleichen Frequenz $\omega$ schwingen. Die untere nebenstehende Abbildung veranschaulicht eine solche Eigenschwingung am Beispiel einer schwingenden Kette bestehend aus fünf Perlen.
Bevor wir uns mit dem C++ Programm zum numerischen Lösen der Bewegung der schwingenden Kette näher befassen, schreiben wir das System von $N$ Bewegungsgleichung zweiter Ordnung der schwingenden Kette in ein System von $2 N$ Differentialgleichungen erster Ordnung um. Unser System von Differentialgleichung zweiter Ordnung lautet wie folgt \begin{equation} \frac{d^2 y_n(t)}{dt^2} = \frac{T}{m_n \, a} \left( y_{n-1}(t) - 2 \, y_n(t) + y_{n+1}(t) \right) \,\, \quad \hbox{bzw.} \quad \ddot{y_n} = \frac{T}{m_n \, a} \left( y_{n-1} - 2 \, y_n + y_{n+1} \right) =: f_n(y_{n-1},y_n,y_{n+1}) \quad, \end{equation} wobei $n \in [1,N]$. Die befestigten Eckpunkte (Eckperlen) der schwingenden Kette werden durch $n=0$ und $n=N+1$ beschrieben. Wir machen die vorgegebene Variablenumbenennung ($u_n(t)=y_n(t)$, $u_{N+n}(t)=\frac{y_n}{dt}(t)=\dot{y}_n(t)$) und definieren das System von DGLs wie folgt: \begin{eqnarray} &\dot{u}_1(t) = \frac{d u_1}{dt} = \frac{d y_1}{dt} = u_{N+1}(t)& \\ &\dot{u}_2(t) = \frac{d u_2}{dt} = \frac{d y_2}{dt} = u_{N+2}(t) &\\ &... = ... &\\ &\dot{u}_N(t) = \frac{d u_N}{dt} = \frac{d y_N}{dt} = u_{2N}(t) &\\ &\dot{u}_{N+1}(t) = \frac{d u_{N+1}}{dt} = \frac{d \dot{y}_1}{dt} = \ddot{y}_1(t) =: f_1(y_{0},y_1,y_{2}) = f_1(u_{0},u_1,u_{2})&\\ &\dot{u}_{N+2}(t) = \frac{d u_{N+2}}{dt} = \frac{d \dot{y}_2}{dt} = \ddot{y}_2(t) =: f_2(y_{1},y_2,y_{3}) = f_2(u_{1},u_2,u_{3})&\\ &... = ... &\\ &\dot{u}_{2N}(t) = \frac{d u_{2N}}{dt} = \frac{d \dot{y}_N}{dt} = \ddot{y}_N(t) =: f_N(y_{N-1},y_N,y_{N+1}) = f_2(u_{N-1},u_N,u_{2N+1})& \quad , \end{eqnarray} wobei die befestigten Eckpunkte (Eckperlen) der schwingenden Kette durch $u_{0}$ und $u_{2N+1}$ beschrieben werden. Wir berechnen nun die numerische Lösung $\vec{u}(t) = \left( u_1(t), u_2(t), ..., u_{2N}(t) \right)$ in einem bestimmten Zeitintervall (z.B. $t \in [0,1]$) bei vorgegebenen Anfangsbedingungen $$ \begin{eqnarray} &u_n(0) = y_n(0) = \alpha_n \,\, , \forall n \in [1,N] & \\ &u_n(0) = \dot{y}_{n-N}(0) = \alpha_n \,\, , \forall n \in [N+1,2N] & \end{eqnarray} $$
Im Unterpunkt Differentialgleichungen: Numerische Lösung von Anfangswertproblemen hatten wir mittels des C++ Programmes DGL_1.cpp die numerische Lösung einer Differentialgleichung erster Ordnung berechnet. Dieses Programm erweiterten wir dann im Unterpunkt Systeme von gekoppelten Differentialgleichungen und Differentialgleichungen zweiter Ordnung um auch Systemen von gekoppelten Differentialgleichungen erster Ordnung und Differentialgleichungen zweiter Ordnung numerisch Lösen zu können (siehe DGL_2_a.cpp).
Im Hinblick, auch auf die noch folgenden Projekte, ist es sicherlich hilfreich zunächst den Kern-Algorithmus der Berechnung der numerischen Lösung (Runge-Kutta Ordnung vier Verfahren) als eine C++ Klasse auslagern, die wir dann zum simulieren der schwingende Kette verwenden werden. Das folgende C++ Programm stellt z.B. eine Klasse dar, welche die Lösung der folgenden Differentialgleichung erster Ordnung $$ \begin{equation} \dot{y}(t) = \frac{d y(t)}{dt} = y - t^2 + 1 \quad, \, \hbox{mit:} \,\, a=0 \leq t \leq b=4 \, , \,\, y(a)=y(0)=\alpha=0.3 \end{equation} $$ mittels des Runge-Kutta Ordnung vier Verfahrens generiert.
// Klasse dsolve /* Berechnung der Loesung einer Differentialgleichung der Form y'=f(t,y) * mittels Runge-Kutta Ordnung vier Verfahren * Verfahren zur Loesung der DGL ist in eine Klasse ausgelagert * Zeitentwicklung der fuer unterschiedliche t-Werte in [a,b] * Konstruktor: dsolve(Anfangszeit a, Endzeit b, Anzahl der Punkte N, Anfangswert alpha=y(a)) */ #include <stdio.h> // Standard Input- und Output Bibliothek in C, z.B. printf(...) #include <cmath> // Bibliothek für mathematisches (e-Funktion, Betrag, ...) #include <vector> // Vector-Container der Standardbibliothek using namespace std; // Benutze den Namensraum std class dsolve{ //Definition der Klasse 'dsolve' double a = 0; // Untergrenze des Zeit-Intervalls [a,b] in dem die Loesung berechnet werden soll double b = 2; // Obergrenze des Intervalls [a,b] int N = 10; // Anzahl der Punkte in die das t-Intervall aufgeteilt wird double alpha = 0.5; // Anfangswert bei t=a: y(a)=alpha vector<double> y_RungeK_4; // Deklaration eines double Vektors zum speichern der Loesung vector<double> Zeit; // Deklaration eines double Vektors zum speichern der Zeit-Werte public: // Konstruktor mit vier Argumenten (Initialisierung der Parameter, Berechnung der Loesung der DGL) dsolve(double a_, double b_, int N_, double alpha_) : a(a_),b(b_),N(N_),alpha(alpha_) { double h = (b - a)/N; // Abstand dt zwischen den aequidistanten Punkten des t-Intervalls (h=dt) double k1, k2, k3, k4; // Deklaration der vier Runge-Kutta Parameter Zeit.push_back(a_); // Zum Zeit-Vektor die Endzeit eintragen y_RungeK_4.push_back(alpha_); // Zum y-Vektor den Anfangswert alpha_=y(a) eintragen for(int i=0; i < N; ++i){ // for-Schleife ueber die einzelnen Punkte des t-Intervalls k1 = h*f(Zeit[i],y_RungeK_4[i]); // Runge-Kutta Parameter 1 k2 = h*f(Zeit[i]+h/2,y_RungeK_4[i] + k1/2); // Runge-Kutta Parameter 2 k3 = h*f(Zeit[i]+h/2,y_RungeK_4[i] + k2/2); // Runge-Kutta Parameter 3 k4 = h*f(Zeit[i]+h,y_RungeK_4[i] + k3); // Runge-Kutta Parameter 4 y_RungeK_4.push_back(y_RungeK_4[i] + (k1 + 2*k2 + 2*k3 + k4)/6); // Zum Loesungs-Vektor den neuen Wert eintragen Zeit.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 Konstruktors double f(double t, double y); // Deklaration der Member-Funktion f(t,y) (Definition findet ausserhalb der Klasse statt) double y_analytisch(double t, double alpha); // Deklaration der Member-Funktion f(t,alpha) (Definition findet ausserhalb der Klasse statt) const vector<double>& get_y() const { return y_RungeK_4; } // Definition der konstanten Member-Funktion get_y(), Rueckgabewert vector der Loesung der DGL const vector<double>& get_zeit() const { return Zeit; } // Definition der konstanten Member-Funktion get_zeit(), Rueckgabewert vector der zeit-Punkte }; // Ende der Klasse double dsolve::f(double t, double y){ // Definition der Funktion f(t,y), Gleichung der DGL dy/dt=f(t,y) double wert; wert = y - pow(t,2) + 1; // Eigentliche Definition der Funktion return wert; // Rueckgabewert der Funktion f(t,y) } // Ende der Funktion f(t,y) double dsolve::y_analytisch(double t, double alpha){ // Analytische Loesung der DGL double wert; wert = (alpha + (pow(t,2) + 2*t + 1)*exp(-t) -1)*exp(t); // Eigentliche Definition der analytische Loesung return wert; // Rueckgabewert } // Ende der Definitiom int main(){ // Hauptfunktion dsolve Loes {0,4,30,0.3}; // Benutzt Konstruktor mit a=0, b=4, N=30 und alpha=0.3 printf("# 0: Index i \n# 1: t-Wert \n# 2:Runge-Kutta Ordnung vier \n"); // Beschreibung der ausgegebenen Groessen printf("# 3: Analytische Loesung \n# 4: Fehler \n"); // Beschreibung der ausgegebenen Groessen for(int i=0; i < Loes.get_zeit().size(); ++i){ // for-Schleife zur Terminalausgabe der Loesung printf("%3d %19.15f %19.15f %19.15f %19.15e \n",i, Loes.get_zeit()[i], Loes.get_y()[i], Loes.y_analytisch(Loes.get_zeit()[i], 0.3), Loes.get_y()[i] - Loes.y_analytisch(Loes.get_zeit()[i], 0.3)); } } // Ende der Hauptfunktion
Das oben abgebildete C++ Programm zeigt zunächst eine Möglichkeit der Umformulierung des C++ Programmes DGL_1.cpp in eine Klassenstruktur, wobei als Klassenname 'dsolve' verwendet wurde. Mittels des Konstruktors werden die Daten-Member der Klasse neu initialisiert und außerdem werden die, als private Daten-Member deklarierten Standardvektoren 'vector<double> y_RungeK_4' und 'vector<double> Zeit' im Anweisungsblock des Konstruktors mit den Lösungswerten gefüllt. Der Benutzer kann dann vom Hauptprogramm mittels der public definierten Memberfunktionen 'const vector<double>& get_y()' und 'const vector<double>& get_zeit()' auf die berechneten Lösungs- und Zeitwerte zugreifen.
Nun möchten wir diese Klasse auch zum Lösen eines Systems von Differentialgleichungen benutzen. Im Unterpunkt Systeme von gekoppelten Differentialgleichungen und Differentialgleichungen zweiter Ordnung hatten wir dies für ein Systemen von zwei gekoppelten Differentialgleichungen erster Ordnung vorgestellt (siehe DGL_2.cpp). Da wir bei der schwingenden Kette ein System von $2 N$ Differentialgleichungen erster Ordnung lösen wollen, müssen wir die Klasse 'dsolve' allgemeiner gestalten und die Anzahl der zu lösenden Differentialgleichungen sollte im Programm frei wählbar sein. Das folgende C++ Programm stellt eine allgemeine Klasse 'dsolve' dar und man kann sie beliebig zum Lösen von Systemen von gekoppelten Differentialgleichungen und Differentialgleichungen zweiter Ordnung verwenden. Im unteren Programm wird sie verwendet um die Bewegung der schwingenden Kette mit drei Perlen zu simulieren (siehe SchwingendeKette_3P.cpp). Man kann sie jedoch auch für nur eine Differentialgleichung erster Ordnung benutzen (siehe dsolve.cpp) oder eine schwingende Kette mit 200 Perlen simulieren (siehe SchwingendeKette_200P.cpp).
// Die schwingende Kette mit drei Perlen /* ist ein Beispiel eines schwingenden, gekoppelten Massensystems aus dem Bereich der Mechanik * Berechnung der Lösung eines Systems von Differentialgleichungen * mittels Runge-Kutta Ordnung vier Verfahren * Verfahren zur Loesung der DGL ist in eine Klasse ausgelagert * Zeitentwicklung der fuer unterschiedliche t-Werte in [a,b] * Konstruktor: dsolve(Anfangszeit a, Endzeit b, Anzahl der Punkte N, Anfangswerte alpha=y(a), DGL als Funktion) * Ausgabe zum Plotten mittels Python Jupyter Notebook SchwingendeKette.ipynb: "./a.out > SchwingendeKette_3P.dat */ #include <iostream> // Standard Input- und Output Bibliothek in C, z.B. printf(...) #include <cmath> // Bibliothek für mathematisches (e-Funktion, Betrag, ...) #include <vector> // Vector-Container der Standardbibliothek #include <functional> // Funktionen in der Argumentenliste von Funktionen using namespace std; // Benutze den Namensraum std class dsolve{ //Definition der Klasse 'dsolve' double a = 0; // Untergrenze des Zeit-Intervalls [a,b] in dem die Loesung berechnet werden soll double b = 1; // Obergrenze des Intervalls [a,b] int N = 10; // Anzahl der Punkte in die das t-Intervall aufgeteilt wird vector<double> alpha = {1,0,0,0,0,0}; // Sechs Anfangswerte (Orte und Geschwindigkeiten der drei Perlen) bei t=a vector<vector<double>> y_RungeK_4; // Deklaration eine double Vektor-Matrix zum speichern der Loesungen vector<double> Zeit; // Deklaration eines double Vektors zum speichern der Zeit-Werte public: // Konstruktor mit fuenf Argumenten (Initialisierung der Parameter, Uebergabe der DGL als Funktion, Berechnung der Loesung der DGL) dsolve(double a_, double b_, int N_, vector<double> alpha_, function< vector<double>(double, vector<double>)> f) : a(a_),b(b_),N(N_),alpha(alpha_) { 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=6) 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 Zeit.push_back(a_); // Zum Zeit-Vektor die Anfangszeit eintragen y_RungeK_4.push_back(alpha_); // Zum Loesungs-Vektor die Anfangswerte eintragen 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*f(Zeit[i],y_RungeK_4[i])[j];} // for-Schleife ueber die sechs Runge-Kutta k1-Parameter for(int j=0; j < n; ++j){y[j] = y_RungeK_4[i][j] + k1[j]/2;} // Temporaerer Hilfsvektor for(int j=0; j < n; ++j){k2[j] = h*f(Zeit[i]+h/2,y)[j];} // for-Schleife ueber die sechs Runge-Kutta k2-Parameter for(int j=0; j < n; ++j){y[j] = y_RungeK_4[i][j] + k2[j]/2;} // Temporaerer Hilfsvektor for(int j=0; j < n; ++j){k3[j] = h*f(Zeit[i]+h/2,y)[j];} // for-Schleife ueber die sechs Runge-Kutta k3-Parameter for(int j=0; j < n; ++j){y[j] = y_RungeK_4[i][j] + k3[j];} // Temporaerer Hilfsvektor for(int j=0; j < n; ++j){k4[j] = h*f(Zeit[i]+h,y)[j];} // for-Schleife ueber die sechs Runge-Kutta k4-Parameter for(int j=0; j < n; ++j){y[j] = y_RungeK_4[i][j] + (k1[j] + 2*k2[j] + 2*k3[j] + k4[j])/6;} // Temporaerer Hilfsvektor y_RungeK_4.push_back(y); // Zum Loesungs-Vektor den neuen Wert eintragen Zeit.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 Konstruktors const vector<vector<double>>& get_y() const { return y_RungeK_4; } // Definition der konstanten Member-Funktion get_y(), Rueckgabewert Vektor-Matrix der Loesung der DGL const vector<double>& get_zeit() const { return Zeit; } // Definition der konstanten Member-Funktion get_zeit(), Rueckgabewert vector der zeit-Punkte }; // Ende der Klasse // Definition der Bewegungsgleichung der schwingenden Kette bestehend aus drei Perlen vector<double> dgls(double t, vector<double> u_vec){ vector<double> du_dt(u_vec.size()); // Die sechs DGLs erster Ordnung werden als Standard-Vektor definiert double set_T = 1; // Fadenspannung T double set_a = 1; // Aequidistante Abstaenden zwischen den Perlen double set_m_1 = 1; // Masse der ersten Perle double set_m_2 = 1; // Masse der zweiten Perle double set_m_3 = 1; // Masse der dritten Perle du_dt[0] = u_vec[3]; // Sechs DGLs erster Ordnung du_dt[1] = u_vec[4]; // ... du_dt[2] = u_vec[5]; du_dt[3] = set_T/(set_m_1*set_a)*(u_vec[1] - 2*u_vec[0]); du_dt[4] = set_T/(set_m_2*set_a)*(u_vec[0] - 2*u_vec[1] + u_vec[2]); du_dt[5] = set_T/(set_m_3*set_a)*(u_vec[1]-2*u_vec[2]); return du_dt; // Rueckgabewert der DGLs als Standard-Vektor } // Ende: Definition der Bewegungsgleichung // Analytische Loesung (siehe Python Jupyter Notebook) vector<double> y_analytisch(double t, vector<double> alpha){ // Analytische Loesung in Abhaengigkeit der sechs Anfangswerte alpha vector<double> y; // Vektor {y_1,y_2,y_3} der Loesungen y.push_back((1.0/2.0)*(alpha[0] - alpha[2])*cos(M_SQRT2*t) + (1.0/4.0)*M_SQRT2*(alpha[3] - alpha[5])*sin(M_SQRT2*t) + (1.0/4.0)*(alpha[0] - M_SQRT2*alpha[1] + alpha[2])*cos(t*sqrt(M_SQRT2 + 2)) + (1.0/4.0)*(alpha[0] + M_SQRT2*alpha[1] + alpha[2])*cos(t*sqrt(2 - M_SQRT2)) + (1.0/8.0)*sqrt(M_SQRT2 + 2)*(alpha[3]*(2 - M_SQRT2) + 2*alpha[4]*(1 - M_SQRT2) + alpha[5]*(2 - M_SQRT2))*sin(t*sqrt(M_SQRT2 + 2)) + (1.0/8.0)*sqrt(2 - M_SQRT2)*(alpha[3]*(M_SQRT2 + 2) + 2*alpha[4]*(1 + M_SQRT2) + alpha[5]*(M_SQRT2 + 2))*sin(t*sqrt(2 - M_SQRT2))); y.push_back((1.0/8.0)*(-2*pow(M_SQRT2 + 2, 2)*(-alpha[4]*(M_SQRT2 + 2) + (1 + M_SQRT2)*(alpha[3] + alpha[5]))*sin(t*sqrt(M_SQRT2 + 2)) - pow(M_SQRT2 + 2, 2)*(2*alpha[3]*(1 + M_SQRT2) - alpha[4]*(2 - M_SQRT2)*pow(M_SQRT2 + 2, 2) + 2*alpha[5]*(1 + M_SQRT2))*sin(t*sqrt(M_SQRT2 + 2)) + 2*pow(M_SQRT2 + 2, 7.0/2.0)*((-M_SQRT2*alpha[0] + 2*alpha[1] - M_SQRT2*alpha[2])*cos(t*sqrt(M_SQRT2 + 2)) + (M_SQRT2*alpha[0] + 2*alpha[1] + M_SQRT2*alpha[2])*cos(t*sqrt(2 - M_SQRT2)) + sqrt(2 - M_SQRT2)*(alpha[3]*(1 + M_SQRT2) + alpha[4]*(M_SQRT2 + 2) + alpha[5]*(1 + M_SQRT2))*sin(t*sqrt(2 - M_SQRT2))))/pow(M_SQRT2 + 2, 7.0/2.0)); y.push_back((1.0/2.0)*(-alpha[0] + alpha[2])*cos(M_SQRT2*t) - 1.0/4.0*M_SQRT2*(alpha[3] - alpha[5])*sin(M_SQRT2*t) + (1.0/4.0)*(alpha[0] - M_SQRT2*alpha[1] + alpha[2])*cos(t*sqrt(M_SQRT2 + 2)) + (1.0/4.0)*(alpha[0] + M_SQRT2*alpha[1] + alpha[2])*cos(t*sqrt(2 - M_SQRT2)) + (1.0/8.0)*sqrt(M_SQRT2 + 2)*(alpha[3]*(2 - M_SQRT2) + 2*alpha[4]*(1 - M_SQRT2) + alpha[5]*(2 - M_SQRT2))*sin(t*sqrt(M_SQRT2 + 2)) + (1.0/8.0)*sqrt(2 - M_SQRT2)*(alpha[3]*(M_SQRT2 + 2) + 2*alpha[4]*(1 + M_SQRT2) + alpha[5]*(M_SQRT2 + 2))*sin(t*sqrt(2 - M_SQRT2))); return y; // Rueckgabe des Vektors {y_1,y_2,y_3} der Loesungen } // Ende der analytische Loesung // Hauptfunktion int main(){ vector<double> u_init {1,0,0,0,0,0}; // Vektor der sechs Anfangswerte (Orte und Geschwindigkeiten der drei Perlen) bei t=a dsolve Loes1 {0,20,100000,u_init,dgls}; // Benutzt Konstruktor mit a=0, b=20, N=100000 printf("# 0: Index i \n# 1: t-Wert \n# 2:y_1 \n# 3:y_2 \n# 4:y_3 \n"); // Beschreibung der ausgegebenen Groessen printf("# 5: Analytische Loesung y_1 \n# 6: Analytische Loesung y_2 \n# 7: Analytische Loesung y_3 \n"); // Beschreibung der ausgegebenen Groessen for(int i=0; i < Loes1.get_zeit().size(); ++i){ // for-Schleife zur Terminalausgabe der Loesung printf("%3d %19.15f %19.15f %19.15f %19.15f",i, Loes1.get_zeit()[i], Loes1.get_y()[i][0], Loes1.get_y()[i][1], Loes1.get_y()[i][2]); printf("%19.15f %19.15f %19.15f \n",y_analytisch(Loes1.get_zeit()[i],u_init)[0],y_analytisch(Loes1.get_zeit()[i],u_init)[1],y_analytisch(Loes1.get_zeit()[i],u_init)[2]); } // Ende der for-Schleife } // Ende der Hauptfunktion
Im Vergleich zu unserer ersten Klasse 'dsolve' (siehe dsolve_a.cpp) ist das zugrundeliegende, zu lösende Differentialgleichungssystem in einer vektorwertigen Funktion 'vector<double> dgls(double t, vector<double> u_vec){' definiert, die an den Konstruktor der Klasse als Argument übergeben wird. Hierfür ist die Einbindung '#include <functional>' am Anfang des Programms nötig. Mittels des Konstruktors werden die Daten-Member der Klasse neu initialisiert, wobei die Anfangswerte auch als ein Standardvektor an den Konstruktor übergeben werden (hier bei drei Perlen sechs Anfangswerte). In Abhängigkeit von der Anzahl der zu lösenden Differentialgleichungen wird die Vektor-Matrix 'vector<vector<double>> y_RungeK_4;' mit den Lösungen aufgefüllt.
Obwohl der Kern-Algorithmus zur Berechnung der numerischen Lösung von $N$-Differentialgleichungen schon allgemein in der Klasse 'dsolve' implementiert ist, kann das obere Programm nur den Fall für $N=3$ berechnen und mit der analytischen Lösung vergleichen. Bei dem folgenden Programm ist das behoben und mittels dieses C++ Programms kann man die schwingende Kette mit beliebig vielen Perlen simulieren.
// Die schwingende Kette mit beliebiger Anzahl von Perlen /* ist ein Beispiel eines schwingenden, gekoppelten Massensystems aus dem Bereich der Mechanik * Berechnung der Lösung eines Systems von Differentialgleichungen * mittels Runge-Kutta Ordnung vier Verfahren * Verfahren zur Loesung der DGL ist in eine Klasse ausgelagert * Zeitentwicklung der fuer unterschiedliche t-Werte in [a,b] * Konstruktor: dsolve(Anfangszeit a, Endzeit b, Anzahl der Punkte N, Anfangswerte alpha=y(a), DGL als Funktion) * Ausgabe zum Plotten mittels Python Jupyter Notebook SchwingendeKette.ipynb: "./a.out > SchwingendeKette.dat */ #include <iostream> // Standard Input- und Output Bibliothek in C, z.B. printf(...) #include <cmath> // Bibliothek für mathematisches (e-Funktion, Betrag, ...) #include <vector> // Vector-Container der Standardbibliothek #include <functional> // Funktionen in der Argumentenliste von Funktionen using namespace std; // Benutze den Namensraum std class dsolve{ //Definition der Klasse 'dsolve' double a = 0; // Untergrenze des Zeit-Intervalls [a,b] in dem die Loesung berechnet werden soll double b = 1; // Obergrenze des Intervalls [a,b] int N = 10; // Anzahl der Punkte in die das t-Intervall aufgeteilt wird vector<double> alpha = {1,0,0,0,0,0}; // Sechs Anfangswerte (Orte und Geschwindigkeiten; hier drei Perlen) bei t=a vector<vector<double>> y_RungeK_4; // Deklaration eine double Vektor-Matrix zum speichern der Loesungen vector<double> Zeit; // Deklaration eines double Vektors zum speichern der Zeit-Werte public: // Konstruktor mit fuenf Argumenten (Initialisierung der Parameter, Uebergabe der DGL als Funktion, Berechnung der Loesung der DGL) dsolve(double a_, double b_, int N_, vector<double> alpha_, function< vector<double>(double, vector<double>)> f) : a(a_),b(b_),N(N_),alpha(alpha_) { 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 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 Zeit.push_back(a_); // Zum Zeit-Vektor die Anfangszeit eintragen y_RungeK_4.push_back(alpha_); // Zum Loesungs-Vektor die Anfangswerte eintragen 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*f(Zeit[i],y_RungeK_4[i])[j];} // for-Schleife ueber die sechs Runge-Kutta k1-Parameter for(int j=0; j < n; ++j){y[j] = y_RungeK_4[i][j] + k1[j]/2;} // Temporaerer Hilfsvektor for(int j=0; j < n; ++j){k2[j] = h*f(Zeit[i]+h/2,y)[j];} // for-Schleife ueber die sechs Runge-Kutta k2-Parameter for(int j=0; j < n; ++j){y[j] = y_RungeK_4[i][j] + k2[j]/2;} // Temporaerer Hilfsvektor for(int j=0; j < n; ++j){k3[j] = h*f(Zeit[i]+h/2,y)[j];} // for-Schleife ueber die sechs Runge-Kutta k3-Parameter for(int j=0; j < n; ++j){y[j] = y_RungeK_4[i][j] + k3[j];} // Temporaerer Hilfsvektor for(int j=0; j < n; ++j){k4[j] = h*f(Zeit[i]+h,y)[j];} // for-Schleife ueber die sechs Runge-Kutta k4-Parameter for(int j=0; j < n; ++j){y[j] = y_RungeK_4[i][j] + (k1[j] + 2*k2[j] + 2*k3[j] + k4[j])/6;} // Temporaerer Hilfsvektor y_RungeK_4.push_back(y); // Zum Loesungs-Vektor den neuen Wert eintragen Zeit.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 Konstruktors const vector<vector<double>>& get_y() const { return y_RungeK_4; } // Definition der konstanten Member-Funktion get_y(), Rueckgabewert Vektor-Matrix der Loesung der DGL const vector<double>& get_zeit() const { return Zeit; } // Definition der konstanten Member-Funktion get_zeit(), Rueckgabewert vector der zeit-Punkte }; // Ende der Klasse // Definition der Bewegungsgleichung der schwingenden Kette bestehend aus anz_p Perlen vector<double> dgls(double t, vector<double> u_vec){ int anz_p = static_cast<int>(u_vec.size()/2); // Anzahl der Perlen vector<double> du_dt; // Die 2*anz_p DGLs erster Ordnung werden als Standard-Vektor definiert double set_T = 1; // Fadenspannung T double set_a = 1; // Aequidistante Abstaenden zwischen den Perlen double set_m = 1; // Masse der ersten Perle for(int i=0; i < anz_p; ++i){ // Erster Block von anz_p DGLs erster Ordnung du_dt.push_back(u_vec[anz_p+i]); // ... } // ... du_dt.push_back(set_T/(set_m*set_a)*(u_vec[1] - 2*u_vec[0])); // Zweiter Block von anz_p DGLs erster Ordnung for(int i=anz_p+1; i < 2*anz_p-1; ++i){ // ... mit Bewegungsgleichungen du_dt.push_back(set_T/(set_m*set_a)*(u_vec[i-anz_p-1] - 2*u_vec[i-anz_p] + u_vec[i-anz_p+1])); // ... } // ... du_dt.push_back(set_T/(set_m*set_a)*(u_vec[anz_p-2]-2*u_vec[anz_p-1])); // ... return du_dt; // Rueckgabewert der DGLs als Standard-Vektor } // Ende: Definition der Bewegungsgleichung-Funktion // Hauptfunktion (hier fuer fuenf Perlen) int main(){ vector<double> u_init {1,0,0,0,0,0,0,0,0,0}; // Vektor der 10 Anfangswerte (Orte und Geschwindigkeiten der fuenf Perlen) bei t=a int anz_p = static_cast<int>(u_init.size()/2); // Anzahl der Perlen dsolve Loes1 {0,20,1000,u_init,dgls}; // Benutzt Konstruktor mit a=0, b=20, N=1000 printf("# 0: Index i \n# 1: t-Wert \n# 2:y_1 \n# 3:y_2 \n# 4:y_3 \n# 5:y_4 \n# 6:y_5 \n"); // Beschreibung der ausgegebenen Groessen for(int i=0; i < Loes1.get_zeit().size(); ++i){ // for-Schleife zur Terminalausgabe; Zeitwerte printf("%3d %19.15f ",i, Loes1.get_zeit()[i]); // Ausgabe Index und Zeitwert for(int j=0; j < anz_p; ++j){ // for-Schleife zur Terminalausgabe; n-Perlen printf("%19.15f ",Loes1.get_y()[i][j]); // Ausgabe y-Wert der Perle j } // Ende der for-Schleife; n-Perlen printf("\n"); // Neue Zeile } // Ende der for-Schleife } // Ende der Hauptfunktion
In der Hauptfunktion des Programms definiert man den Standardvektor der Anfangsbedingungen und somit legt man auch fest aus wievielen Perlen die Kette besteht (hier aus fünf Perlen). Die Bewegungsgleichung der schwingenden Kette ist allgemein für $N$-Perlen in der Vektor-Funktion 'dgls' definiert. In der Hauptfunktion führt man die Simulation durch den Aufruf des Konstruktors aus und die Simulationsergebnisse werden dann für alle Perlen mittels 'printf()' ausgegeben.
Die untere Animation zeigt eine Simulation mit $N=200$ Perlen. Die Simulationsdaten wurden mit dem C++ Programm SchwingendeKette_200P.cpp berechnet und die Visualisierung der Resultate wurde mittels des Jupyter Notebooks SchwingendeKette.ipynb angefertigt. Man kann auch die Daten direkt mit dem folgenden Python-Skript visualisieren: PythonPlot_Kette.py.
In diesem Projekt könnte man z.B. die folgenden weiteren Aufgaben betrachten:
Betrachten Sie den Fall ungleicher Massen an einem Beispiel.
Betrachten Sie den allgemeinen Fall $\vec{F}_{n-1} = - \left( T \cdot \hbox{sin}(\alpha_{n-1}) \right) \, \vec{e}_y$ und $\vec{F}_{n+1} = - \left( T \cdot \hbox{sin}(\alpha_{n+1}) \right) \, \vec{e}_y$ und beziehen zusätzlich auch die Bewegungen der Perlen in x-Richtung in Ihre Berechnungen ein. Erstellen Sie dann ein Beispiel-Simulation mit großen Perlenausschlägen und vergleichen die Ergebnisse mit den Ergebnissen der linearen Näherung. Bauen Sie auch diesen Anwendungsfall sinnvoll in die Klassenstruktur Ihres Programmes ein.