Springer-Lehrbuch 3 Berlin Heidelberg New York Hongkong London Mailand Paris Tokio Harald Wiedemann Numerische Physik Mit 72 Abbildungen, zahlreichen Übungen und einer CD-ROM mit Beispielprogrammen und Programmpaketen 13 Dr. Harald Wiedemann Hattsteinstr. 6 79423 Heitersheim, Deutschland e-mail: [email protected] ISBN 3-540-40774-x Springer-Verlag Berlin Heidelberg New York Bibliografische Information Der Deutschen Bibliothek. Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über <http://dnb.ddb.de> abrufbar. Dieses Werk ist urheberrechtlich geschützt. Die dadurch begründeten Rechte, insbesondere die der Übersetzung, des Nachdrucks, des Vortrags, der Entnahme von Abbildungen und Tabellen, der Funksendung, der Mikroverfilmung oder der Vervielfältigung auf anderen Wegen und der Speicherung in Datenverarbeitungsanlagen, bleiben, auch bei nur auszugsweiser Verwertung, vorbehalten. Eine Vervielfältigung dieses Werkes oder von Teilen dieses Werkes ist auch im Einzelfall nur in den Grenzen der gesetzlichen Bestimmungen des Urheberrechtsgesetzes der Bundesrepublik Deutschland vom 9. September 1965 in der jeweils geltenden Fassung zulässig. Sie ist grundsätzlich vergütungspflichtig. Zuwiderhandlungen unterliegen den Strafbestimmungen des Urheberrechtsgesetzes. Springer-Verlag ist ein Unternehmen von Springer Science+Business Media springer.de © Springer-Verlag Berlin Heidelberg 2004 Printed in Germany Die dem Buch beigelegten Programme des Autors unterliegen der GPL (Gnu General Public License). r r r Die nachfolgenden Namen sind eingetragene Warenzeichen: Linux by Linus Torvalds; Intel und Pentium by r r r by Advanced Micro Devices, Inc.; Windows by Microsoft Corp.; Visual Numerics und IMSL Intel Corp.; AMD r r r r by Visual Numerics; NAG by The Numerical Algorithms Group Ltd.; Adobe , Acrobat Reader , Postscript by r r Adobe Systems Inc.; Red Hat by Red Hat Software Inc.; SUSE by SuSE Linux AG Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Werk berechtigt auch ohne besondere Kennzeichnung nicht zu der Annahme, daß solche Namen im Sinne der Warenzeichen- und Markenschutz-Gesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften. Satz: Druckfertige Daten vom Autor erstellt unter Verwendung eines Springer LATEX2e Makropakets Einbandgestaltung: design & production GmbH, Heidelberg Gedruckt auf säurefreiem Papier SPIN: 10934104 56/3141/jl - 5 4 3 2 1 0 Meinen Eltern Vorwort Die letzten Jahre waren vom ungeheuren Siegeszug des Computers geprägt: Er hielt zunächst Einzug in große Computersäle der Konzerne, dann drang er auch in kleinere Firmen vor, eroberte unsere Arbeitsplätze und findet sich mittlerweile in fast jedem Haushalt. Er übernimmt verschiedenste Aufgaben, wie die Verwaltung von Lagerbeständen oder von Konten, die Berechnung der Sicherheit unserer Autos, dient als Schreibmaschine ebenso wie als Stereoanlage oder Fernseher – die Liste ließe sich fast beliebig fortsetzen. Schon als Computer noch die Ausmaße von Schränken hatten, wurden Sie intensiv in der Forschung eingesetzt, und das nicht nur als der schon oben erwähnte Schreibmaschinenersatz, um Diplom- oder Doktorarbeiten sowie Veröffentlichungen niederzuschreiben. Auch bei der eigentlichen Forschung, also der Gewinnung neuen Wissens, ist der Computer zu einem unentbehrlichen Hilfsmittel geworden. In der Physik wird der Computer im Wesentlichen zu drei unterschiedlichen Aufgabengebieten herangezogen: zum einen für die Erstellung von Grafiken und Texten, zum zweiten zur Steuerung und Auswertung von Messabläufen in der Experimentalphysik und zum dritten zur numerischen Auswertung von Gleichungen in der Theoretischen Physik. In jüngster Zeit gewinnt darüber hinaus der Computer eine Bedeutung als Hilfsmittel bei der algebraischen Auswertung komplizierter Ausdrücke. In diesem Buch werden wir uns lediglich mit dem dritten Aspekt, also der numerischen Physik, beschäftigen, wobei wir jedoch nicht umhin kommen, die so gewonnenen Ergebnisse auch grafisch darzustellen, so dass der an erster Stelle genannte Gesichtspunkt – wenn auch nur am Rande – ins Spiel kommt. Wenn man ein Buch über numerische Physik schreiben will, sollte man sich zunächst fragen, welche Hilfsmittel ein theoretischer Physiker in diesem Aufgabengebiet einsetzt und welche Kenntnisse er für seine Arbeit benötigt. Bei den Arbeitsmitteln ist in erster Linie neben einem möglichst leistungsfähigen Computer ein Compiler einer höheren Programmiersprache (z.B. C/C++ oder FORTRAN) zu nennen. Da der Physiker allerdings möglichst wenig seiner Zeit opfern möchte, um numerische Standardaufgaben zu programmieren, benötigt er noch eine Numerik-Bibliothek, die ihm diese Aufgabe abnimmt. Und schließlich benötigt er noch ein Grafikprogramm, mit dessen Hilfe er die gewonnenen Ergebnisse visualisieren kann. VIII Vorwort Die Frage nach speziellen Kenntnissen, die der numerische Physiker benötigt, ist weit schwieriger zu beantworten. Auf den ersten Blick scheinen Programmierkenntnisse und ein Handbuch der verwendeten Numerikbibliothek sowie des eingesetzten Grafikprogramms auszureichen. Die Realität aber zeigt, dass diese beiden Dinge zwar einen guten Programmierer, aber keinen guten Wissenschaftler ausmachen – für letzteren sind die Kenntnisse seines Fachgebietes weitaus wichtiger, auch wenn er den größeren Teil seiner Zeit mit dem Suchen von Programmierfehlern zubringt. Nun könnte man meinen, dass diese Kenntnisse doch hinreichend in den Vorlesungen zur Experimentalphysik und Theoretischen Physik vermittelt würden, aber die Erfahrung zeigt, dass die Sichtweise physikalischer Sachverhalte in der numerischen Physik oft eine andere ist: Im konventionellen Lehrplan zentrale Dinge verlieren an Bedeutung, während andere Gesichtspunkte ins Blickfeld geraten. Probleme, die vorher zu schwierig waren, sind nun verhältnismäßig einfach, während es sich bei anderen genau umgekehrt verhält. Dies ist die Lücke, die dieses Buch schließen will. Und dieses Buch möchte den Studenten einladen, die aufregende Welt der numerischen Physik selbst zu entdecken. Um dieses zu ermöglichen, müssen wir noch einmal auf die materiellen Voraussetzungen zurückkommen, die für numerische Physik benötigt werden. Computer sind seit ihrer Erfindung kontinuierlich billiger und leistungsfähiger geworden, so dass mittlerweile fast jeder Student einen besitzt oder wenigstens Zugang zu einem hat. Compiler sind zwar auch nicht unerschwinglich, aber bei der Numerik-Bibliothek erreichen wir im Allgemeinen ein Preisniveau, das für Studenten nicht mehr akzeptabel ist. Deshalb kommen bislang viele Studenten erst zum Beginn der Diplomarbeit mit einem umfangreicheren Einsatz numerischer Methoden in Berührung, oder die Erfahrungen vorher beschränken sich auf eine Vorlesung mit eventuellem Praktikum. Angesichts der zunehmenden Bedeutung der numerischen Physik ist diese Situation unbefriedigend, und eine Aufgabe dieses Buches ist, dies zu ändern. Einen Ausweg aus dieser Situation bietet mittlerweile sogenannte freie Software, also Software die umsonst ist. Neben dem weithin bekannten freien Unix-Betriebssystem Linux gibt es nämlich inzwischen für fast jede SoftwareSparte auch freie Programme, die den Vergleich mit ihren kommerziellen Konkurrenten nicht zu scheuen brauchen – von der Textverarbeitung bis hin zum von uns benötigten Compiler. Und wer ein wenig sucht, findet sogar frei verfügbare Numerikbibliotheken. Da auch für unsere Zwecke taugliche Visualisierungsprogramme frei erhältlich sind, können alle Voraussetzungen für numerische Physik auch zum Nulltarif erfüllt werden, und man hat bei allen drei Software-Paketen (Compiler, Numerikbibliothek und Grafikprogramm) sogar noch verschiedene Programme zur Auswahl – eine Auswahl, die zumindest zum Teil von mir eingeschränkt werden musste, da dieses Buch nicht alle Möglichkeiten abdecken kann. Beim Compiler haben Sie noch weitgehend freie Wahl, sofern Sie sich auf die Programmiersprachen C++ oder FORTRAN beschränken – andere Vorwort IX Programmiersprachen finden in der Numerik auch praktisch keine Anwendung. An freien Compilern stehen hier z.B. die Compiler der GNU-Familie gcc/g++ bzw. g77 zur Verfügung. Diese Compiler gibt es in Varianten für Windows und für Linux sowie andere Betriebssysteme. Wenn Sie als Betriebssystem Linux installiert haben, stehen Ihnen darüber hinaus noch z.B. die Intel-Compiler zur Verfügung (die auch auf AMD-Systemen hervorragend laufen) – aus Lizenzgründen sind diese Compiler aber nicht auf der beiliegenden CD-ROM vorhanden, sondern müssen nach einer Registrierung direkt bei Intel von deren Webseiten geholt werden. Neben diesen frei verfügbaren Compilern ist aber selbstverständlich auch jeder kommerzielle C++- oder Fortran-Compiler für unsere Zwecke geeignet. Eine Auswahl von Numerik-Bibliotheken finden Sie in Anhang C. Deren Routinen werden von unseren C++- bzw. Fortran-Programmen aufgerufen, so dass ein Programm immer nur mit einer bestimmten Bibliothek übersetzt werden kann. Hier muss also eine Festlegung erfolgen und wenn Sie eine andere Numerik-Bibliothek verwenden wollen, müssen Sie die Programme auf der beigelegten CD-ROM entsprechend anpassen. Alle Programme werden im Buch als C++-Quellcode in ihrer für die GNU Scientific Library [1], kurz GSL, ausgelegten Variante ausführlich vorgestellt und Sie finden diese Programme auf der CD-ROM im Verzeichnis programme\gsl. Darüber hinaus finden Sie im Verzeichnis programme\slatec die meisten dieser Programme als FORTRAN-Quellcode für SLATEC. Alle diese Programme wurden wie die zugehörigen Bibliotheken, unter die Gnu General Public License, kurz GPL, gestellt. Das bedeutet, dass Sie diese Programme verwenden, verändern und an Dritte weitergeben dürfen. Ein entsprechender Hinweis findet sich im Quelltext der Programme am Dateiende – diese Zeilen geben wir aus Platzgründen bei der Besprechung der Programme im Buch nicht wieder. Für die Weiterverteilung von GPL-lizensierten Programmen gibt es einige Auflagen, die Sie bitte Anhang G entnehmen. Der dort wiedergegebene Text der GPL ist Englisch, da nur dieser rechtliche Relevanz besitzt – Sie finden im Internet unter http://www.gnu.org/licenses/translations.html aber auch Übersetzungen in andere Sprachen, die allerdings keinen offiziellen Charakter haben. Als letzten Punkt müssen wir auf die Visualisierung unserer Ergebnisse eingehen. Die von uns geschriebenen Programme erzeugen zunächst einmal nur eine Menge Zahlen, die grafisch dargestellt werden müssen, damit man mit diesen etwas anfangen kann. Auch hier stehen wieder eine ganze Reihe von Programmen zur Auswahl (gnuplot, GLE, kplot), und es ist Ihnen überlassen, welches Sie benutzen wollen. Die Programme dieses Buches sind zwar insofern auf GLE oder gnuplot ausgelegt, dass Kommentarzeilen mit Parametern mit einem # beginnen – diese Zeilen lassen sich jedoch leicht entfernen oder an ein anderes Grafikprogramm anpassen. Die Gliederung des Buches entspricht dem Kursus Theoretischer Physik, wie er an Universitäten gelehrt wird: Mechanik, Elektrodynamik, Optik, Sta- X Vorwort tistische Physik sowie Quantenmechanik. Jedes Kapitel beginnt mit einem Abriss der Grundlagen – dieser soll dem Leser, der bereits über die entsprechenden Kenntnisse verfügt, die wesentlichen Punkte noch einmal in Erinnerung rufen. Sollte der Leser dabei feststellen, dass er keine ausreichenden Grundlagenkenntnisse hat, müssen wir ihn an ein konventionelles Lehrbuch verweisen, da das vorliegende Buch ein solches weder ersetzen kann noch will. Empfehlenswerte Werke, die alle wesentlichen Teilgebiete der Physik abdecken, sind [2–4]. Darüber hinaus kann der Leser auf [5] für Elektrodynamik, [6] für Optik und [7] für Quantenmechanik zurückgreifen. Durch die Fülle an detailliert besprochenen Aufgaben und Problemen zeichnet sich [8] aus – diese Aufgaben bieten sich auch für eine numerische Lösung an, wobei die analytische Lösung zur Kontrolle herangezogen werden kann. Als ergänzende bzw. weiterführende Literatur in Richtung Numerische Physik empfehle ich [9–13]. Überhaupt nicht angesprochen wird in diesem Buch der Themenbereich Dynamik von Flüssigkeiten, da dies den Rahmen einer Einführung in die Numerische Physik sprengen würde. Für diese Disziplin kann der Leser auf eine umfangreiche Spezialliteratur zurückgreifen [14–17]. Wie in einem konventionellen Lehrbuch in Theoretischer Physik werden zu jedem Kapitel eine Reihe von Problemstellungen vorgestellt und ausführlich besprochen. Jedes dieser Probleme wird zunächst in einem Grundlagenabschnitt diskutiert, in dem wir dieses soweit wie möglich analytisch lösen und damit den Grundstein für die numerische Lösung legen, die im Anschluss daran besprochen wird. Mir liegt daran, dass Sie die Numerik nicht als Alternative zur analytischen Lösung begreifen, sondern eher als Ergänzung, und Sie werden feststellen, wie diese beiden an sich grundverschiedenen Zugänge ineinander greifen. So eingesetzt, erweist sich Numerik als ein äußerst leistungsfähiges Werkzeug in der Hand des Theoretischen Physikers. Zu jedem Kapitel dieses Buches gibt es Übungsaufgaben, bei denen Sie die Programme, die im Text besprochen werden und die Sie auf der CDROM finden, an neue Aufgabenstellungen anpassen müssen. Fassen Sie diese Aufgaben als Anregungen auf – sein Ziel hat das Buch erreicht, wenn Sie über diese Aufgaben hinausgehen und Probleme numerisch angehen, die Sie interessieren, die Sie aber nicht analytisch lösen können. Zu guter Letzt möchte ich mich bei Gert-Ludwig Ingold und Michael Weingärtner für das unermüdliche Korrekturlesen und zahlreiche Vorschläge, die diesem Buch zu Gute kamen, bedanken. Darüber hinaus bin ich Fritz Haake, Holger Schanz und den Mitgliedern der GSL-Newsgroup zu Dank verpflichtet. Heitersheim, Januar 2004 Harald Wiedemann Inhaltsverzeichnis Mechanik der Massenpunkte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.1 Die Newtonschen Gesetze . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Das Fadenpendel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2.1 Verifizierung des Programms . . . . . . . . . . . . . . . . . . . . . . . 1.2.2 Graphische Darstellung und Interpretation der Ergebnisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2.3 Verbesserung des Algorithmus . . . . . . . . . . . . . . . . . . . . . 1.3 Das Doppelpendel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.4 Integrable und nicht integrable Dynamiken . . . . . . . . . . . . . . . . 1.5 Reguläre und chaotische Dynamiken . . . . . . . . . . . . . . . . . . . . . . 1.6 Das Teilchen in der Schachtel . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.7 Hamilton-Formalismus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.8 Attraktoren in dissipativen Systemen . . . . . . . . . . . . . . . . . . . . . 1.9 Das periodisch angetriebene Pendel . . . . . . . . . . . . . . . . . . . . . . . 1.10 Der schiefe Wurf mit Luftwiderstand . . . . . . . . . . . . . . . . . . . . . . Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 14 26 32 40 45 52 52 55 61 66 2 Elektrodynamik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1 Die Maxwellschen Gleichungen . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Felder von Punktladungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3 Multipole . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4 Berechnung von Feldlinien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.5 Magnetfelder stationärer Ströme . . . . . . . . . . . . . . . . . . . . . . . . . . 2.6 Hysterese . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 71 72 73 76 80 92 96 3 Optik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1 Historischer Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Grundbegriffe der Strahlenoptik . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3 Brechung und Reflektion von Licht . . . . . . . . . . . . . . . . . . . . . . . 3.4 Brechung an einer Linsenfläche . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.5 Bild durch eine Linse – Linsenfehler . . . . . . . . . . . . . . . . . . . . . . 3.6 Entstehung eines Regenbogens . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.6.1 Qualitative Erklärung des Regenbogens . . . . . . . . . . . . . 3.6.2 Quantititave Vorüberlegungen . . . . . . . . . . . . . . . . . . . . . 99 99 100 101 105 111 116 117 118 1 1 1 3 11 XII Inhaltsverzeichnis 3.6.3 Programm zur Berechnung eines Regenbogens . . . . . . . 3.6.4 Der Regenbogen bei ellipsoidförmigen Regentropfen . . . 3.7 Grundlagen der Wellenoptik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.8 Ebene Wellen und Kugelwellen . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.9 Interferenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.10 Das Huygenssche Prinzip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.11 Berechnung von Beugungsmustern . . . . . . . . . . . . . . . . . . . . . . . . 3.12 Kohärenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.13 Beugung bei endlicher Kohärenzlänge . . . . . . . . . . . . . . . . . . . . . Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 124 125 126 127 128 129 134 137 142 4 Statistische Physik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1 Grundbegriffe der Statistik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2 Bestimmung von Wahrscheinlichkeiten . . . . . . . . . . . . . . . . . . . . 4.3 Mittelwerte und Momente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.4 Bedingte Wahrscheinlichkeiten und Korrelationen . . . . . . . . . . . 4.5 Dynamik bei statistischen Problemen . . . . . . . . . . . . . . . . . . . . . 4.6 Der Random-Walk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.6.1 Stochastische Differentialgleichung des Random-Walk . 4.6.2 Mastergleichung des Random-Walks . . . . . . . . . . . . . . . . 4.6.3 Verbesserung des Random-Walk-Modells . . . . . . . . . . . . 4.7 Thermisches Hüpfen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.8 Thermalisierung in Gasen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.8.1 Energieerhaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 147 148 150 150 152 153 154 158 160 170 176 177 189 5 Quantenmechanik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.1 Die mathematische Struktur der Quantenmechanik . . . . . . . . . 5.2 Operationen im Hilbertraum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3 Eigenzustände und ihre Verwendung als Koordinatensysteme . 5.4 Orts- und Impulsdarstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.5 Die Kopenhagener Interpretation der Quantenmechanik . . . . . 5.6 Schrödingergleichung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.7 Bestimmung des Hamilton-Operators . . . . . . . . . . . . . . . . . . . . . 5.8 Das freie Teilchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.9 Eigenzustände des Hamiltonoperators . . . . . . . . . . . . . . . . . . . . . 5.10 Variationsmethoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.11 Quantentunneln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.12 Einführung in die Quantenstatistik . . . . . . . . . . . . . . . . . . . . . . . 5.13 Ein Zwei-Niveau-System mit äußerer Anregung . . . . . . . . . . . . . 5.14 Messprozess . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.15 Der Zeno-Effekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.16 Ein Ein-Atom-Laser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Übungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193 193 193 195 197 199 199 200 200 213 219 226 233 235 241 242 246 254 Inhaltsverzeichnis XIII Anhang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257 A Installation der Pakete unter Linux . . . . . . . . . . . . . . . . . . . . . . . 257 B Installation der Pakete unter Windows . . . . . . . . . . . . . . . . . . . 261 C Mathematische Bibliotheken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265 D Fortran und C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269 E Filterprogramme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275 F Fouriertransformation und FFT-Routinen . . . . . . . . . . . . . . . . 285 G Die GPL-Lizenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289 Literaturverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295 1 Mechanik der Massenpunkte Der Siegeszug der mathematischen Beschreibung physikalischer Gesetzmäßigkeiten begann vor rund 330 Jahren mit der Formulierung der Newtonschen Gesetze [18], die die Bewegung von Massenpunkten unter dem Einfluss von Kräften beschreiben. Dieser Schritt war gleichermaßen revolutionär wie ungeheuer erfolgreich. So erfolgreich, dass in der Folgezeit versucht wurde, die gesamte Physik mechanisch zu interpretieren und zu beschreiben. Auch wenn wir heute wissen, dass diese Sichtweise falsch ist und die Mechanik nur ein Teilgebiet der Physik darstellt, folgt der Unterricht in Theoretischer Physik weitgehend der geschichtlichen Chronologie und beginnt mit der Mechanik – schon wegen der Anschaulichkeit der dabei besprochenen Problemstellungen. 1.1 Die Newtonschen Gesetze Die Aufstellung der Newtonschen Gesetze bedeutete, wie eben schon ausgeführt, in der Geschichte der Physik einen entscheidenden Wendepunkt: erstmals wurden Gesetzmäßigkeiten so formuliert, dass man mit den Methoden der Mathematik daraus andere Gesetzmäßigkeiten herleiten konnte. Und auch für uns sollen die Newtonschen Gesetze am Anfang unseres Weges durch die klassische Mechanik stehen: In Ineratialsystemen gilt: F = ma . (1.1) In Worten ausgedrückt besagt dieses Gesetz, dass man, um einem Körper der Masse m eine Beschleunigung a zu geben, eine Kraft F benötigt, die gerade gleich dem Produkt aus der Masse und der Beschleunigung ist. Insbesondere ist die Beschleunigung null, wenn keine Kraft wirkt – ein Spezialfall, den Newton aufgrund seiner Bedeutung in einem eigenen Gesetz formulierte: Ein Massenpunkt, auf den keine Kraft wirkt, bewegt sich gleichförmig. Noch bevor wir uns der Frage widmen, woher Kräfte zwischen Körpern kommen, wollen wir ein weiteres – und letztes – Newtonsches Gesetz postulieren: Übt ein Körper 1 auf einen anderen Körper 2 eine Kraft F aus, so übt umgekehrt letzterer (2) auf ersteren (1) die Kraft −F aus. Dieses Gesetz fasst man häufig kurz zusammen durch: Kraft gleich Gegenkraft, oder mit lateinischen Ausdrücken: actio gleich reactio. 2 1 Mechanik der Massenpunkte Kommen wir jedoch zur Gleichung (1.1) zurück. Als erstes müssen wir uns fragen, wann dieses Gesetz gilt – und diese Frage bringt uns schon in Probleme. Die Antwort, die Sie in den Lehrbüchern finden, nämlich dass das Newtonsche Grundgesetz in Inertialsystemen gilt, führt uns zu der Frage, was ein Inertialsystem ist. Und die Antwort darauf ist, dass ein Inertialsystem dadurch definiert ist, dass in ihm das Newtonsche Grundgesetz gilt. Wir sind also in einen Zirkelschluss geraten. Wir müssen uns an dieser Stelle mit der Annahme begnügen, dass es Inertialsysteme zumindestens gibt und dass wir hinreichend gut in einem Inertialsystem sind, wenn wir (salopp gesagt) mit beiden Füßen auf der Erde stehen. Ein weiterer Punkt, den wir zu Gleichung (1.1) anmerken wollen, ist die Tatsache, dass die Beschleunigung ein Vektor ist, und deshalb auch die Kraft vektoriell sein muss. Newton macht keine Aussage darüber, woher die Kraft F kommt und wie man sie berechnet. Er impliziert aber, dass jede Kraft eine irgendwie geartete Ursache hat und für den Fall der Gravitation konnte Newton auch den mathematischen Ausdruck finden – eben das Gravitationsgesetz: m 1 m2 F = −G 3 r . (1.2) r Die Gravitationskraft zwischen zwei Körpern ist nach diesem Gesetz proportional zum Produkt der zwei beteiligten Massen m1 und m2 und umgekehrt proportional zum Quadrat des Abstandes r zwischen den Körpern. Außerdem zeigt die Kraft immer in Richtung der Verbindungslinie zwischen den Körpern und ist immer anziehend. Die Gravitionskonstante G schließlich ist eine Naturkonstante und daher für alle Körper die selbe. Mit dem Gravitationsgesetz lässt sich schon eine ganze Menge machen: Planetenbewegungen fallen ebenso unter dessen Zuständigkeit wie Pendel oder der schiefe Wurf. Um z.B. Federn in die Betrachtungen einzubeziehen, müssen wir jedoch auch empirische Gesetze zulassen, denen zufolge wir wissen, dass die Federkraft proportional zur Auslenkung s der Feder ist: F = −Ds . (1.3) Dieses Gesetz (das Hookesche Gesetz) ist empirisch und bei einer anderen Feder kann es sein, dass nicht nur die Proportionalitätskonstante D eine andere ist, sondern der Zusammenhang zwischen der Kraft F und der Auslenkung s grundsätzlich ein anderer ist (im Prinzip ist es sogar so, dass keine Feder das Gesetz (1.3) exakt befolgt). In diesem Sinne handelt es sich also bei (1.3) nicht um ein Gesetz, sondern eher um eine Regel. Zusammenfassend haben wir bis jetzt das Newtonsche Grundgesetz (1.1), das durch andere physikalische Gesetze sowie durch empirische Regeln mit Leben gefüllt wird. Zum Schluss dieses Abschnitts wollen wir uns noch überlegen, was passiert, wenn wir nicht einen Massenpunkt haben, sondern mehrere. Der Einfachheit halber nummerieren wir die Massenpunkte mit 1, 2, 3 u.s.w. durch 1.2 Das Fadenpendel 3 und bezeichnen die zugehörigen Massen mit m1 , m2 , m3 und so weiter. In dieser Situation wirkt auf jeden Massenpunkt auch eine eigene Kraft, die wir konsequenterweise F1 , F2 , . . . nennen. Und Newtons Gesetz (1.1) gilt nun für jeden einzelnen Massenpunkt, also F i = mi ai , (1.4) wobei ai die Beschleunigung des Massenpunkts mit der Nummer i ist. 1.2 Das Fadenpendel In diesem ersten Beispiel untersuchen wir einen Massenpunkt, der mittels einer Stange an einem Punkt befestigt ist (siehe Abb. 1.1). Abb. 1.1. Fadenpendel Wir setzen voraus, dass die Bewegung ausschließlich in der Zeichenebene erfolgen kann und da durch die Stange außerdem der Abstand zum Aufhängepunkt fixiert ist, ist die Position des Massenpunktes eindeutig durch den Winkel φ festgelegt. Aufgrund dieses eindimensionalen Charakters der Bewegung können wir den Vektorcharakter der Beschleunigung a und der Kraft F außer Acht lassen und stattdessen mit skalaren Größen rechnen. Unabhängig von der Position wirkt auf den Massenpunkt die Schwerkraft −mg, wobei allerdings nur die Komponente senkrecht zur Stange den Massenpunkt beschleunigen bzw. verzögern kann, der Rest wird von einer entsprechenden Gegenkraft in Richtung der Stange kompensiert: In unserem Fall ist die wirksame Komponente −mg sin φ. Nun benötigen wir noch die Beschleunigung, ausgedrückt durch die dynamische Variable φ sowie die fixe Länge l der Stange: d2 (1.5) a = l 2φ . dt Damit haben wir auch schon unsere Bewegungsgleichung ml d2 φ = −mg sin φ . dt2 (1.6) 4 1 Mechanik der Massenpunkte Nach dem Kürzen der Masse m erhalten wir eine Differentialgleichung, in die noch die Größe l/g eingeht, die, wie wir leicht feststellen, die DimensionZeit zum Quadrat hat. Diese charakteristische Zeit definieren wir als τ = l/g und skalieren die Zeit t gemäß t = t . τ (1.7) Dadurch erhalten wir die skalierte Differentialgleichung d2 φ = − sin φ . dt2 (1.8) Diese Vorgehensweise hat gleich mehrere Vorteile: – Wir haben die Zeitskala, auf der die Bewegung des Pendels stattfindet, bereits identifiziert, obwohl wir die Bewegungsgleichung noch nicht gelöst haben. – Wir haben die Parameter l und g eliminiert, so dass wir im Falle einer numerischen Lösung die Differentialgleichung nicht für mehrere Werte dieser Parameter lösen müssen. Diese Differentialgleichung sieht ziemlich einfach aus, wir werden jedoch sehen, dass sie bereits zu schwierig ist, um mit Papier und Bleistift geschlossen gelöst zu werden. Ohne Computer müssten wir uns deswegen mit mehr oder weniger guten Näherungslösungen zufrieden geben – eine Situation, die leider bei sehr vielen Problemen auftritt. Die Entwicklung leistungsfähiger Computer hat uns zu dieser klassischen Vorgehensweise noch einen zweiten Zugang zur Verfügung gestellt, um den es in diesem Buch geht: die numerische Behandlung des Problems. Vorher jedoch wollen wir uns trotzdem mit einer Näherungslösung beschäftigen, dem sogenannten mathematischen Pendel, das Sie bereits aus der Schule kennen. Beim mathematischen Pendel geht man davon aus, dass die Auslenkungen φ aus der Ruhelage sehr klein sind (das Pendel also weit davon entfernt ist, überzuschlagen). In diesem Fall können wir den Sinus auf der rechten Seite von (1.8) in guter Näherung durch dessen Argument ersetzen, wodurch sich unsere Differentialgleichung vereinfacht zu: d2 φ = −φ . dt2 (1.9) Diese Gleichung nun können wir direkt lösen, ihre Lösung lautet φ = A sin t + B cos t , (1.10) wobei A und B beliebige Werte annehmen dürfen, die wir zum Beispiel durch die Anfangsbedingungen bestimmen können. Wenn wir nun wieder zu der unskalierten Zeit t übergehen, erhalten wir 1.2 Das Fadenpendel φ = A sin ωt + B cos ωt , 5 (1.11) mit ω = g/l. Aus dieser Kreisfrequenz ω bekommen wir die Frequenz f , indem wir ω durch 2π dividieren. Die Periodendauer schließlich ist das Inverse der Frequenz f , also 2π/ω. Nach diesem kleinen Ausflug kehren wir jedoch zu unserem ursprünglichen Problem (1.8) zurück. Nachdem wir uns vielleicht eine Weile erfolglos daran versucht haben, eine Funktion zu finden, die diese Bewegungsgleichung erfüllt, geben wir diese Bemühungen auf und wenden uns einer numerischen Lösung zu. Im ersten Schritt machen wir uns klar, dass diese Differentialgleichung zweiter Ordnung (die gesuchte Größe φ tritt maximal in zweiter Ableitung nach τ auf) auch als zwei gekoppelte Differentialgleichungen erster Ordnung aufgefasst werden kann: d φ = vφ dτ d vφ = − sin φ . dτ (1.12) (1.13) In der ersten der beiden Gleichungen steht, dass die zeitliche Änderung von φ gleich einer Winkelgeschwindigkeit vφ ist; in der zweiten Gleichung wird die Änderung dieser Größe gleich der Beschleunigung − sin φ gesetzt. Wenn Sie die erste Gleichung nach τ ableiten und darin die zweite Gleichung einsetzen, erhalten Sie wieder die ursprüngliche Gleichung (1.8). Nehmen wir nun an, dass wir die Werte von φ und vφ zu einem Zeitpunkt τ0 kennen. Entwickeln wir zunächst φ(τ ) und vφ (τ ) in Potenzreihen um τ0 : φ(τ ) = φ(τ0 + ∆τ ) = φ(τ0 ) + ∞ 1 (n) φ (∆τ )n n! n=1 vφ (τ ) = vφ (τ0 + ∆τ ) = vφ (τ0 ) + (n) ∞ 1 (n) vφ (∆τ )n . n! n=1 (1.14) (1.15) Wenn wir alle Koeffizienten φ(n) und vφ kennen würden, hätten wir immerhin eine Potenzreihendarstellung der gesuchten Lösung unseres Problems gefunden. Aber was ist, wenn ∆τ sehr klein ist, sagen wir 0.001. Dann sind die weitaus größten Terme auf der rechten Seite von (1.14) und (1.15) φ(τ0 ) und vφ (τ0 ). Der nächstfolgende Term ist bereits etwa tausendmal kleiner, der nächste noch einmal tausendmal kleiner und so weiter. Diese Beobachtung ist keineswegs überraschend, besagt sie doch nur, dass der Zustand zum Zeitpunkt τ0 sich nur wenig unterscheidet von einem Zustand kurz vor und kurz nach τ0 . Wenn wir uns also auf solche kleinen Zeitschritte ∆τ beschränken, kommen wir mit sehr wenigen Termen aus den Potenzreihen (1.14) und (1.15) aus! Der Trick besteht nun darin, eine längere Zeit in entsprechend kleine Zeitschritte zu zerteilen und sich so langsam in der Zeit entlangzuhangeln. 6 1 Mechanik der Massenpunkte Nur die beiden Anfangsterme φ(τ0 ) und vφ (τ0 ) zu berücksichten, macht offensichtlich keinen Sinn, da sich dann überhaupt nichts ändern würde. Die einfachst mögliche Vorgehensweise macht also die folgende Näherung: φ(τ0 + ∆τ ) ≈ φ(τ0 ) + φ(1) ∆τ vφ (τ0 + ∆τ ) ≈ vφ (τ0 ) + (1) vφ ∆τ (1.16) . (1.17) Wenn Sie nun in einem Mathematiklehrbuch den Abschnitt über Potenzreihen konsultieren, werden Sie feststellen, dass die beiden fehlenden Terme φ(1) (1) und vφ durch die Ableitungen der betreffenden Funktionen, also durch unsere Differentialgleichungen (1.12) und (1.13), gegeben sind. Auf diese Weise erhalten wir also φ(τ0 + ∆τ ) ≈ φ(τ0 ) + vφ (τ0 )δτ vφ (τ0 + ∆τ ) ≈ vφ (τ0 ) − sin(φ(τ0 ))δτ . (1.18) (1.19) Damit haben wir – endlich – zwei Gleichungen, bei denen alle Größen auf der rechten Seite bekannt sind. Nun können wir daran gehen, das Ganze in einem Computerprogramm zu implementieren. Wie in der Einleitung bereits erläutert, werden wir die Programme jeweils in C++ vorstellen und besprechen, einfach weil diese Sprache der derzeitige Standard ist und sich – nach langen Geburtswehen – auch in der Physik durchsetzt. Alternativ stehen jedoch auf der beigefügten CD auch Quellprogramme für Fortran zur Verfügung. Doch nun zu dem Programm: 1 2 3 4 5 6 /************************************************************************** * Name: faden1.cpp * * Zweck: Simuliert die Dynamik eines Fadenpendels * * Gleichung: (dˆ2/d tˆ2) phi + sin(phi) = 0 * * Methode: 1.Ordnung * **************************************************************************/ 7 8 9 10 11 #include #include #include #include <iostream> <fstream> <stdio.h> <string> 12 13 14 //-- Definition der globalen Variablen const int n_start_max = 10; 15 16 main( int argc, char *argv[] ) 17 18 19 20 21 22 23 24 { //-- Definition der Variablen int n, m, n1, nmax, nout, n_start; double t, tend, dt, phi, phi_neu, v_phi, v_phi_neu, phi_0, v_phi_0; char* resfile; ifstream in_stream; ofstream out_stream; 25 26 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden 1.2 Das Fadenpendel 27 28 29 30 31 if (argc<3) { cout << " Aufruf: faden1 infile outfile\n"; exit(1); } 32 33 34 35 36 37 38 39 //-- Einlesen der Parameter -in_stream.open(argv[1]); in_stream >> tend; in_stream >> nmax; in_stream >> nout; in_stream >> n_start; in_stream.close(); 40 41 42 //-- Berechnung einiger benoetigter Parameter -dt = tend / nmax; 43 44 45 46 47 48 49 50 51 52 53 54 //-- Schleife ueber die Anfangsbedingungen for (n1=0; n1<n_start; n1++) { sprintf(resfile,"%s.%i",argv[2],n1); out_stream << "! Ergebnis-Datei generiert von faden1.cpp\n"; out_stream << "! tend = " << tend << "\n"; out_stream << "! nmax = " << nmax << "\n"; out_stream << "! nout = " << nout << "\n"; out_stream << "! n_start = " << n_start << "\n"; out_stream << "! Spalte 1: t Spalte 2: phi(t)" << " Spalte 3: v_phi(t)\n"; 55 56 57 58 59 60 61 62 //-- Anfangsbedingungen in_stream >> phi_0; in_stream >> v_phi_0; t = 0; phi = phi_0; v_phi = v_phi_0; out_stream << t << " " << phi << " " << v_phi; 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 //-- Zeitschleife -for (n=1; n<=nout; n++) { for (m=1; m<=nmax/nout; m++) { //-----------t = t + dt; phi_neu = phi + v_phi * dt; v_phi_neu = v_phi - sin(phi) * dt; //-----------phi = phi_neu; v_phi = v_phi_neu; //-----------} out_stream << t << " " << phi << " " << v_phi; } out_stream.close(); } 82 83 } 7 8 1 Mechanik der Massenpunkte Beginnen wir mit dem Programmkopf, der aus einigen Kommentarzeilen besteht (alles hinter einem \\ wird vom Compiler als Kommentar interpretiert, desgleichen alles zwischen \* und *\). Aus diesen Zeilen geht hervor, wie die Datei heißt, in welcher das Programm steht, und was es ganz grob macht. Danach kommt der eigentliche Programmtext, zunächst einige Zeilen, die verwendete Include-Dateien festlegen, danach die Definition einer globalen Konstante n start max, die festlegt, wie viele verschiedene Anfangsbedingungen in einem Programmlauf durchgerechnet werden können. Nach der Deklaration der für das Programm benötigten Variablen (Zeile 19–24) beginnt das eigentliche Programm. In den Zeilen 26 bis 31 wird überprüft, ob das Programm korrekt aufgerufen wurde – wir werden bei diesem und allen folgenden Programmen die Namen der Eingabe- und Ausgabedateien beim Programmaufruf als Argumente übergeben. Der Aufruf ist also sicher nicht korrekt, wenn nicht mindestens zwei Argumente übergeben wurden und da bei C/C++ immer auch der Programmname selbst zu den Argumenten mitgerechnet wird, ergibt sich die Bedingung argc<3 für einen Fehler. Nachdem nun die Eingabedatei bekannt ist, werden in den folgenden Zeilen alle für das Programm nötigen Parameter eingelesen und gleichzeitig in der Ausgabedatei festgehalten. In unserem Fall ist das die Zeit tend, bis zu der die Bewegungsgleichung gelöst werden soll, die Zahl der Zeitschritte nmax, die Zahl der Zeiten, zu denen wir das Ergebnis abspeichern wollen (nout). Schließlich kommt noch ein Parameter n start, der angibt, mit wie vielen Anfangsbedingungen die Bewegungsgleichung gelöst werden soll: 1 2 3 4 5 6 7 10 1000000 100 3 0 1. 0 2. 0 2.1 Alle diese Parameter wollen wir in der bzw. in diesem Fall den Ausgabedateien festhalten, ansonsten ist später nicht mehr rekonstruierbar zu welchen Parametern ein konkreter Datensatz gehört. Dabei müssen wir beachten, dass das Grafikprogramm, mit dessen Hilfe später die Ergebnisse graphisch umgesetzt werden sollen, diese Zeilen ignoriert – im Falle von GLE oder von gnuplot geschieht das wie hier gezeigt durch ein ! am Zeilenanfang. So erklären sich die Zeilen 48–53, in denen jeder Ausgabedatei ein Block der Form ! ! ! ! ! ! Ergebnis-Datei generiert von faden1 tend = 10 nmax = 1000000 nout = 100 n_start = 3 Spalte 1: t Spalte 2: phi(t) Spalte 3: v_phi(t) vorangestellt wird. Die Tatsache, dass wir für jede Anfangsbedingung eine eigene Ausgabedatei generieren, erfordert zwei Besonderheiten im Programm, 1.2 Das Fadenpendel 9 die Sie in den weiteren Programmen dieses Buches so nicht wiederfinden werden (auf den normalen Aufbau gehen wir dann beim nächsten Beispielprogramm faden all.cpp ein): – der Name der jeweiligen Ausgabedatei wird in der Zeile 47 zusammengesetzt, – das Schreiben des oben beschriebenen Blocks geschieht nicht unmittelbar nach dem Einlesen der Parameter, sondern erst innerhalb der Schleife über n1. Anschließend werden in Zeile 59–61 die Werte phi und v phi auf diese Anfangswerte und t auf null gesetzt. Was jetzt noch fehlt, ist lediglich die Zeitschleife, die in zwei Schleifen geteilt wurde. Der Sinn dieser Aufteilung ist, dass wir dadurch die Möglichkeit haben, nur zu einem Bruchteil der Zeiten, die wir durchlaufen, die Werte von phi und v phi abzuspeichern. Ansonsten würden wir nämlich sehr, sehr große Ausgabedateien erzeugen, die wir gar nicht benötigen. Bei diesem ersten Programm wollen wir – ausnahmsweise – auch den FORTRAN-Code besprechen, um dem FORTRAN-Programmierer einen Einblick in den Programmierstil zu geben, der in den Beispielprogrammen gewählt wurde: 1 2 3 4 5 6 *************************************************************************** * Name: faden1.f * * Zweck: Simuliert die Dynamik eines Fadenpendels * * Gleichung: (dˆ2/d tˆ2) phi + sin(phi) = 0 * * Methode: 1.Ordnung * *************************************************************************** 7 program faden1 8 9 10 * 11 -- Definition der Variablen implicit none 12 integer 13 n_start_max 14 parameter (n_start_max=10) 15 16 real*8 t, tend, dt, phi, phi_neu, v_phi, v_phi_neu, phi_0, v_phi_0 character*20 resfile(n_start_max) integer n, m, n1, nmax, nout, n_start 17 18 19 20 21 * -- Einlesen der Parameter -open(1,file="faden1.par") read(1,*) tend read(1,*) nmax read(1,*) nout read(1,*) n_start * -- Namen fuer die Ergebnisdateien resfile(1) = "faden1.1" resfile(2) = "faden1.2" resfile(3) = "faden1.3" resfile(4) = "faden1.4" 22 23 24 25 26 27 28 29 30 31 32 10 1 Mechanik der Massenpunkte 33 resfile(5) = "faden1.5" resfile(6) = "faden1.6" resfile(7) = "faden1.7" resfile(8) = "faden1.8" resfile(9) = "faden1.9" resfile(10) = "faden1.10" 34 35 36 37 38 39 40 * -- Berechnung einiger benoetigter Parameter -dt = tend / dfloat(nmax) * -- Schleife ueber die Anfangsbedingungen do n1 = 1, n_start 41 42 43 44 45 open(2,file=resfile(n1)) 46 47 48 * -- Anfangsbedingungen read(1,*) phi_0, v_phi_0 t = 0.d0 phi = phi_0 v_phi = v_phi_0 write(2,*) t, phi, v_phi * -- Zeitschleife -do n = 1, nout do m = 1, nmax / nout -t = t + dt phi_neu = phi + v_phi * dt v_phi_neu = v_phi - sin(phi) * dt -phi = phi_neu v_phi = v_phi_neu -enddo write(2,*) t, phi, v_phi enddo 49 50 51 52 53 54 55 56 57 58 * 59 60 61 62 * 63 64 65 66 67 68 * 69 70 close(2) 71 72 enddo 73 74 close(1) 75 76 77 STOP end Wie im vorher besprochenen C++-Programm haben wir dem Programm einen erklärenden Kopf mit Informationen zum Programm vorangestellt. Danach kommt zuerst eine Zeile, die den Programmnamen festlegt, dann wird in Zeile 11 festgelegt, dass keine implizite Variablendeklaration durchgeführt werden soll. Normalerweise nimmt FORTRAN nämlich bei nicht deklarierten Variablen an, dass sie – je nach deren Anfangsbuchstaben entweder integer oder real sein sollen. Dies ist auf der einen Seite zwar bequem, hat aber die fatale Folge, dass versehentlich nicht deklarierte Variablen nicht vom Compiler moniert werden, was wiederum dazu führt, dass Variablen einfach statt doppelt genau sind. Ein weiteres Problem dieser impliziten Variablendeklara- 1.2 Das Fadenpendel 11 tion ist, dass Tippfehler bei Variablennamen nicht erkannt werden. Aus diesen Gründen empfehlen wir, diese Zeile grundsätzlich einzufügen. Nun kommt in den Zeilen 13 bis 19 die Definition einiger Variablen, sowie der Konstanten n start max. Im eigentlichen Programm schließlich werden zunächst aus einer Datei faden1.par Parameter eingelesen, die das Programm benötigt – im Gegensatz zum C++-Programm ist hier also der Name der Eingabedatei, wie übrigens auch der Ausgabedatei, festgelegt, weil FORTRAN nicht standardmäßig die Übergabe von Parametern unterstützt. Es schließt sich ein Block an, in dem daraus Größen berechnet werden, die im weiteren Verlauf noch gebraucht werden – in unserem Fall ist dies nur die Größe eines Zeitschritts dt. Nun kann die Ausgabedatei geöffnet werden und eine äußere Schleife durchläuft alle Anfangsbedingungen. Innerhalb dieser Schleife wird jede Anfangsbedingung jeweils aus der Parameterdatei faden1.par gelesen. Das Lösungsverfahren, mit dem wir vom Zeitpunkt t zum Zeitpunkt t+dt kommen, ist denkbar primitiv, d.h. wir haben die grobe Näherung (1.18) und (1.19) verwendet. Wir werden jedoch sehen, dass wir uns diese Einfachheit mit einer großen Zahl benötigter Zeitschritte und damit einer langen Rechenzeit erkaufen. Bevor wir uns jedoch um eine Verbesserung des Algorithmus’ bemühen, ist es Zeit, ein bisschen mit dem Programm zu spielen. Auf der beiliegenden CD finden Sie in der Datei faden1.par einen Parametersatz, mit dem wir anfangen wollen. Wenn Sie das Programm faden1.cpp bzw. faden1.f komplilieren und mit diesen Parametern starten, sehen Sie, dass das Programm sehr schnell durchläuft – der Grund liegt nicht daran, dass es so effizient programmiert wäre, sondern schlicht und einfach daran, dass das Problem sehr einfach ist. Um nennenswerte Rechenzeiten zu bekommen, müssen wir statt zwei Trajektorien, sagen wir, 1000 anfordern und die betrachtete Zeit bis auf τ = 1000 ausdehnen, aber dies ist Gegenstand von Übung 1.1. An dieser Stelle wollen wir uns damit begnügen, dass auch dieses Programm bei geeigneter Wahl der Parameter etwas länger braucht, und es deswegen Sinn machen kann, den Algorithmus zu verbessern. Darauf kommen wir in Abschn. 1.2.3 zurück. 1.2.1 Verifizierung des Programms Zunächst jedoch müssen wir uns davon überzeugen, dass dieses Programm keine Fehler enthält; das können einfache Programmierfehler sein, oder aber auch ein Fehler im Algorithmus, den wir uns überlegt haben. Dazu müssen wir die gewonnenen Ergebnisse mit einer Referenz vergleichen. Wenn unser Problem analytisch lösbar wäre, könnten wir diese analytische Lösung als Referenz verwenden, aber leider ist das in unserem Beispiel ja nicht der Fall. Überhaupt müssen wir im Allgemeinen davon ausgehen, dass uns eine solche analytische Lösung nicht zur Verfügung steht, da wir ansonsten das Problem gar nicht numerisch gelöst hätten. Ein möglicher Ausweg besteht in der Suche 12 1 Mechanik der Massenpunkte nach Grenzfällen oder Sonderfällen, in denen wir das Problem lösen können – in unserem Fall bietet sich der Grenzfall kleiner Auslenkungen an, für den das Fadenpendel in das mathematische Pendel übergeht, dessen Lösung wir kennen (1.10). Das einzige, was wir hierfür tun müssen ist, in der Parameterdatei faden1.par z.B. eine sehr kleine Auslenkung ohne Anfangsgeschwindigkeit einzustellen und uns die Zahlenwerte für die Lösung (1.10) zu besorgen. Letztere können wir durch ein kleines, separates Programm berechnen lassen – einfacher ist es jedoch, zwei entsprechende Zeilen phi_an = phi_0 * cos(t) + v_phi_0 * sin(t); v_phi_an = v_phi_0 * cos(t) - phi_0 * sin(t); in unser bestehendes Programm einzubauen. Wir dürfen nicht vergessen, diese beiden neuen Variablen im Deklarationsblock am Anfang des Programms zu ergänzen! Und wir müssen diese Werte natürlich ausgeben lassen, was wir am Einfachsten bewerkstelligen, indem wir sie an den bestehenden Schreibbefehl anhängen: out_stream << t << " " << phi << " " << v_phi << " " << phi_an << " " << v_phi_an << " " << sqr(phi-phi_an) << " " << sqr(v_phi-v_phi_an) << "\n"; Für einen schnelleren Überblick haben wir nicht nur die analytische Lösung selbst herausschreiben lassen, sondern auch deren Abweichung von der numerischen Lösung – diese Abweichung sollte möglichst klein sein, was wir dann nach einem Programmlauf auch verifizieren können. Eine andere Möglichkeit, das Programm faden1.cpp zu testen, bestünde darin, einen Alternativalgorithmus zu entwickeln, der uns ebenfalls das gestellte Problem löst. Das hätte gegenüber dem Vergleich in einem analytisch lösbaren Grenzfall den Vorteil, dass wir uns sicher wären, dass die Lösung auch außerhalb dieses Grenzfalls richtig ist. Der Nachteil, den wir dabei in Kauf nehmen, ist, dass wir, wenn die beiden gewonnenen Lösungen nicht übereinstimmen, nicht wissen, in welchem der beiden Programme der Fehler steckt, oder ob gar beide Lösungen falsch sind. Eine weitere wichtige Verifikationsmöglichkeit bieten Erhaltungssätze. In unserem Beispiel ist die Energie eine Erhaltungsgröße, was wir leicht im Programm überprüfen können: E = 0.5 * sqr(v_phi) - cos(phi); berechnet die Energie, die wir der Ausgabe einfach anhängen können. Zeigt diese Größe eine signifikante Zeitabhängigkeit, enthält das Programm noch einen Fehler. 1.2.2 Graphische Darstellung und Interpretation der Ergebnisse Nachdem wir nun einigermaßen sicher sind, dass die gewonnene Lösung richtig ist, wollen wir sie uns ansehen, d.h. aus dem Zahlenwust, der in der Datei faden1.res enthalten ist, Diagramme machen, die uns etwas sagen. 1.2 Das Fadenpendel 13 Das Einfachste ist natürlich die Darstellung von φ bzw. vφ als Funktion der skalierten Zeit τ . Eingabedateien für die verschiedenen in der Einleitung besprochenen Grafikprogramme finden Sie auf der beiliegenden CD – da es nicht Thema dieses Buchs sein soll, diese Grafikprogramme zu erklären, werden wir hier nicht näher darauf eingehen. Worauf wir jedoch eingehen wollen, ist die geeignete Wahl von Anfangsbedingungen, um letztendlich eine aussagekräftige Graphik zu bekommen. Dazu müssen wir unser physikalisches Verständnis des Problems bemühen. Das genannte Problem hat zwei verschiedene Lösungstypen: zum einen Lösungen, bei denen der Massenpunkt um den unteren Umkehrpunkt schwingt; zum anderen Lösungen, bei denen die Energie zum Überschlagen ausreicht. Bei Anfangsbedingungen mit E = mgl befinden wir uns gerade im Grenzfall zwischen diesen beiden Lösungstypen. Für eine aussagefähige Grafik sollten wir darauf achten, dass beide Typen und nach Möglichkeit auch der Grenzfall zwischen ihnen vertreten ist. Schön ist es außerdem, wenn wir bei der Auswahl der Anfangsbedingungen eine gewisse Systematik walten lassen – in diesem Fall könnten wir z.B. Anfangsbedingungen wählen, die alle φ0 = 0 gemeinsam haben und sich nur in vφ0 unterscheiden. Das entstehende Diagramm könnte dann etwa so aussehen: (a) v 3 (b) 2 10 1 5 0 0 -1 0 2 4 6 8 10 t 0 2 4 6 8 10 t Abb. 1.2. Auslenkung φ und deren zeitliche Ableitung vφ beim Fadenpendel als Funktion der Zeit Wir erkennen im unteren Bereich von Abb. 1.2(a) die oszillatorische Lösung (durchgezogene Kurve), bei der das Fadenpendel um die Ruhelage φ = 0 pendelt. Bei der Überschlagslösung im oberen Bereich der Abbildung (gestrichelte Kurve) hingegen wächst φ kontinuierlich an. Und schließlich haben wir zwischen den beiden die Trajektorie des asymptotischen Grenzfalls (gepunktete Kurve), bei der der Massenpunkt zwar nicht zur Ruhelage zurückkehrt, allerdings auch nie den Punkt φ = π erreicht bzw. überwindet. Im rechten Diagramm (Abb. 1.2(b)) sehen wir die Winkelgeschwindigkeit als Funktion der Zeit. Die oszillatorische Lösung (wieder die durchgezogene Kurve) bietet ein ähnliches Bild wie im vorangegangenen Diagramm. Die Überschlagslösung (gestrichelt) sieht jedoch ähnlich wie die oszillatorische Lösung aus, nur dass sie nicht um vφ = 0, sondern um einen von null verschiedenen Mittelwert pendelt. Wenn man genau hinsieht, stellt man außerdem fest, dass die Periodendauer dieser Kurve kleiner ist, als bei der oszillato- 14 1 Mechanik der Massenpunkte rischen Lösung. Ein völlig anderes Bild hingegen liefert der asymptotische Grenzfall, bei dem vφ kontinuierlich abnimmt und für t → ∞ den Wert null annimmt. Das Problem aus einer etwas anderen Sicht beleuchtet das sogenannte Phasendiagramm, bei dem die Ortsvariable φ gegen die Impulsvariable vφ aufgetragen wird (Beachten Sie, dass wir in den skalierten Variablen nicht zwischen Geschwindigkeit und Impuls unterscheiden müssen). Das Phasendiagramm für die selben Trajektorien wie in der vorangegangen Abb. 1.2 sieht so aus: v 2 1 0 -1 -2 0 2 4 6 8 10 12 14 Abb. 1.3. Phasendiagramm des Fadenpendels Die geschlossene Trajektorie links unten entspricht der oszillatorischen Lösung, die Trajektorie im oberen Bereich des Diagramms der Überschlagslösung, bei der der Impuls niemals null wird. Die dazwischenliegende Kurve repräsentiert den asymptotischen Grenzfall, bei dem der Punkt φ = π erst für τ = ∞ mit der Geschwindigkeit vφ = 0 erreicht wird. Diese Trajektorie heißt Separatrix, da sie den Bereich der oszillatorischen vom Bereich der Überschlagslösung abtrennt (separiert). 1.2.3 Verbesserung des Algorithmus Wie bereits angedeutet, ist unser bisheriges Programm einfach, aber in puncto Rechengeschwindigkeit nicht sonderlich effektiv. Der Grund hierfür ist, dass unser Algorithmus mit dem wir uns von einem Zeitpunkt zum nächsten hangeln, außerordentlich kleine Zeitinkremente delta t verlangt, was wiederum zu einer sehr großen Zahl von Zeitschritten führt. Die nächstliegende Methode, den bisherigen Algorithmus zu verbessern, besteht darin, die Näherung (1.18), (1.19) um die nächsten Terme in den Potenzreihen (1.14), (1.15) zu erweitern. Die so gewonnene verfeinerte Näherung lautet φ(τ0 + ∆τ ) = φ(τ0 ) + vφ (τ0 )∆τ − − 1 sin(φ(τ0 ))(∆τ )2 2 1 vφ (τ0 ) cos(φ(τ0 ))(∆τ )3 . . . 6 (1.20) 1.2 Das Fadenpendel 15 1 vφ (τ0 + ∆τ ) = vφ (τ0 ) − sin(φ(τ0 ))∆τ − vφ (τ0 ) cos(φ(τ0 ))(∆τ )2 2 1 sin(φ(τ0 )) cos(φ(τ0 )) + vφ2 (τ0 ) sin(φ(τ0 )) (∆τ )3 . . . . + 6 (1.21) Auf diese Weise bekommen Sie nur durch den Austausch der Zeilen phi_neu = phi + v_phi * dt; v_phi_neu = v_phi - sin(phi) * dt; durch phi_neu = phi + v_phi * dt - 0.5 * sin(phi) * sqr(dt); v_phi_neu = v_phi - sin(phi) * dt - 0.5 * v_phi * cos(phi) * sqr(dt); für die 2. Ordnung bzw. durch phi_neu = phi + v_phi * dt - 0.5 * sin(phi) * sqr(dt) - 1/6 * v_phi * dcos(phi) * pow(dt,3); v_phi_neu = v_phi - sin(phi) * dt - 0.5 * v_phi * dcos(phi) * sqr(dt) + 1./6 * (sin(phi)*cos(phi)+sqr(v_phi)*sin(phi)) * pow(dt,3); für die 3. Ordnung zwei Programme faden2.cpp und faden3.cpp. Wir vereinfachen aber die weitere Vorgehensweise indem wir alle drei Algorithmen (also erster, zweiter und dritter Ordnung) in einem einzigen Programm vereinigen: 1 2 3 4 5 6 /************************************************************************** * Name: faden_all.cpp * * Zweck: Simuliert die Dynamik eines Fadenpendels * * Gleichung: (dˆ2/d tˆ2) phi + sin(phi) = 0 * * Methode: 1. bis 3.Ordnung * **************************************************************************/ 7 8 9 10 11 #include #include #include #include <iostream> <fstream> <math.h> "tools.h" 12 13 using namespace std; 14 15 16 17 //-- Definition der globalen Variablen const int n_start_max = 10; const int n_fehler = 17; 18 19 main( int argc, char *argv[] ) 20 21 22 23 24 25 26 27 28 29 { //-- Definition der Variablen int n, m, n1, n2, nmax, n_start, exp_min; double t, tend, dt, phi_0, v_phi_0, phi_ref, v_phi_ref; double phi1, phi_neu1, v_phi1, v_phi_neu1; double phi2, phi_neu2, v_phi2, v_phi_neu2; double phi3, phi_neu3, v_phi3, v_phi_neu3; double fehler1, fehler2, fehler3, fehler[n_fehler][3]; ifstream in_stream; 16 30 1 Mechanik der Massenpunkte ofstream out_stream, ref_stream; 31 32 33 34 35 36 37 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<4) { cout << " Aufruf: faden_all infile outfile reference-file\n"; exit(1); } 38 39 40 41 42 43 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); ref_stream.open(argv[3]); inout(tend,"tend"); inout(n_start,"n_start"); 44 45 46 47 48 49 //-- Berechnung einiger benoetigter Parameter -dt = tend / nmax; exp_min = 5; for (n1=0; n1<n_fehler; n1++) for (n2=0; n2<3; n2++) fehler[n1][n2] = 0; 50 51 52 53 54 55 56 //-- Schleife ueber die Anfangsbedingungen for (n1=1; n1<=n_start; n1++) { //-- Anfangsbedingungen in_stream >> phi_0; in_stream >> v_phi_0; 57 58 59 60 61 62 63 //-- Schleife ueber die Zeitinkremente (sie werden jedesmal verdoppelt) nmax = pow(2,n_fehler+exp_min) * 2; for (n2=0; n2<n_fehler; n2++) { nmax = nmax / 2; dt = tend / nmax; 64 65 66 67 68 69 70 71 t = 0; phi1 = phi_0; v_phi1 = v_phi_0; phi2 = phi_0; v_phi2 = v_phi_0; phi3 = phi_0; v_phi3 = v_phi_0; 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 //-- Zeitschleife for (n=1; n<=nmax; n++) { t = t + dt; phi_neu1 = phi1 + v_phi1 * dt; v_phi_neu1= v_phi1 - sin(phi1) * dt; phi_neu2 = phi2 + v_phi2 * dt - 0.5 * v_phi_neu2= v_phi2 - sin(phi2) * dt; - 0.5 * v_phi2 * cos(phi2) * phi_neu3 = phi3 + v_phi3 * dt - 0.5 * - 1/6 * v_phi3 * cos(phi3) * v_phi_neu3= v_phi3 - sin(phi3) * dt - 0.5 * v_phi3 * cos(phi3) * + 1/6 * (sin(phi3)*cos(phi3) + v_phi3*v_phi3*sin(phi3)) * //-- sin(phi2) * dt*dt; dt*dt; sin(phi3) * dt*dt dt*dt*dt; dt*dt dt*dt*dt; 1.2 Das Fadenpendel phi1 phi2 phi3 } 89 90 91 92 = phi_neu1; = phi_neu2; = phi_neu3; 17 v_phi1 = v_phi_neu1; v_phi2 = v_phi_neu2; v_phi3 = v_phi_neu3; 93 if (n2 == 1) { phi_ref = phi3; v_phi_ref = v_phi3; ref_stream << phi_ref << " " << v_phi_ref << "\n"; } 94 95 96 97 98 99 fehler1 = + fehler2 = + fehler3 = + 100 101 102 103 104 105 (phi1-phi_ref)*(phi1-phi_ref) (v_phi1-v_phi_ref)*(v_phi1-v_phi_ref); (phi2-phi_ref)*(phi2-phi_ref) (v_phi2-v_phi_ref)*(v_phi2-v_phi_ref); (phi3-phi_ref)*(phi3-phi_ref) (v_phi3-v_phi_ref)*(v_phi3-v_phi_ref); 106 fehler[n2][0] fehler[n2][1] fehler[n2][2] cout << n1 << } 107 108 109 110 111 = = = " fehler[n2][0] + fehler1; fehler[n2][1] + fehler2; fehler[n2][2] + fehler3; " << n2 << "\n"; 112 113 } 114 115 116 117 118 119 120 121 122 123 124 nmax = pow(2,n_fehler+exp_min); for (n1=1; n1<n_fehler; n1++) { nmax = nmax / 2; dt = tend / nmax; out_stream << log(dt)/log(10) << " " << log(nmax)/log(10) << " " << log(fehler[n1][0])/log(10) << " " << log(fehler[n1][1])/log(10) << " " << log(fehler[n1][2])/log(10) << "\n"; } 125 126 127 128 129 out_stream.close(); ref_stream.close(); in_stream.close(); } Exemplarisch für den Großteil der Programme dieses Buches ist hier die Behandlung des Einlesens der Parameter aus der Eingabedatei und des Festhaltens derselben als Kommentare in der Ausgabedatei – siehe dazu auch das vorangegangene Programm faden1.cpp. Im Gegensatz zu letzterem ist hier nun nur eine Ausgabedatei vorhanden, so dass wir für jeden Parameter eine Befehlszeile der Form in_stream >> varname; out_stream << "! varname = " << varname << "\n"; haben. Da wir diese Syntax in jedem unserer Programme mehrfach brauchen, wurde eine entsprechende Zeile in die Datei tools.h eingefügt, die in Zeile 11 eingebunden wird, so dass sich das Einlesen einer Variablen aus der Eingabedatei und das Herausschreiben derselben in die Ausgabedatei vereinfacht zu 18 1 Mechanik der Massenpunkte inout(varname,"varname"); In diese Datei tools.h können wir immer wiederkehrende einfache Aufgaben oder immer wieder gebrauchte Definitionen aufnehmen – ein Blick in diese Datei auf der CD ist sicherlich sinnvoll. Auch zu diesem Programm noch einige weitere Erläuterungen: Um den Fehler zu ermitteln, benötigen wir die exakte, oder doch zumindest eine sehr gute Lösung. Aus diesem Grund lösen wir die Bewegungsgleichung zunächst mit einer sehr großen Zahl von Zeitinkrementen und reduzieren diese dann von Schritt zu Schritt. Dies gibt uns die Möglichkeit die Lösung bei höchster Ordnung und kleinstem Zeitinkrement als Referenzlösung zu definieren und in phi ref und v phi ref abzuspeichern. Mittels dieser Referenzlösung können wir den Fehler einer beliebigen Lösung bestimmen und summieren diesen in einem Feld deviations(n1,n2) auf. Hierbei gibt n1 die Zahl der Zeitinkremente an, während n2 festlegt, ob wir den Fehler zur Lösung erster, zweiter oder dritter Ordnung aufsummieren. Da wir die Ergebnisse logarithmisch darstellen wollen, und das von uns verwendete Grafikprogramm manchmal Probleme mit dieser Darstellung von Daten hat, schreiben wir diese gleich logarithmisch in die Ausgabedatei, so dass aus der Sicht des Grafikprogramms die Darstellung linear ist. Wenn wir nun den Fehler als Funktion der Zahl der Einzelschritte auftragen, erhalten wir das Diagramm in Abb. 1.4: 1 Fehler 10 -10 -20 10 -30 10 -2 10 10 -3 -4 10 10 -5 -6 10 dt Abb. 1.4. Numerische Fehler beim Fadenpendel unter Verwendung verschiedener Ordnungen bei der Lösung der Differentialgleichung, beginnend bei erster Ordnung in der Abbildung ganz oben, dann zweiter und schließlich dritter Ordnung Wir sehen, dass wie erwartet der Fehler durch die Hinzunahme des Terms zweiter bzw. dritter Ordnung wesentlich kleiner geworden ist. Zudem stellen wir fest, dass die Abnahme des Restfehlers mit der Zahl der Schritte bei einem Algorithmus höherer Ordnung schneller erfolgt, als bei einem Algorithmus niedrigerer Ordnung (in dem Diagramm ist die Steigung beim Algorithmus dritter Ordnung am höchsten, beim Algorithmus erster Ordnung am niedrigsten). Die Tatsache, dass der Restfehler durch eine weitere Verkleinerung der Schrittweite nicht auf beliebig kleine Werte gedrückt werden kann, liegt an der endlichen Rechengenauigkeit durch das Zahlenformat – diese Grenz- 1.2 Das Fadenpendel 19 genauigkeit ließe sich nur durch den Wechsel von 16-Bit-Gleitkommazahlen auf 32-Bit usw. verbessern. Wenn wir jedoch eine einigermaßen realistische Genauigkeitsanforderung stellen, und sagen wir, Fehler von 10−7 tolerieren, kommen wir in unserem Beispiel bei Verwendung nur der ersten Ordnung mit ca. 2 Millionen Zeitschritten aus. Allein die Hinzunahme der nächsthöheren Ordnung reduziert diesen Wert auf etwa 4000 Zeitschritte und bei Verwendung des Terms dritter Ordnung reichen sogar etwa 500 Zeitschritte. Diese drastische Reduktion der Zahl nötiger Schritte macht die Tatsache, dass jeder Einzelschritt eine unwesentlich längere Rechenzeit benötigt, unerheblich. Nach dem bisher Gesagten scheint der Weg zu einem effektiven Algorithmus festzustehen: man verwende möglichst viele Terme der Potenzreihenentwicklung (1.14), (1.15). Es gibt jedoch auch Bewegungsgleichungen bei denen die höheren Ordnungen zwar ohne prinzipielle Probleme berechnet werden können, aber einen erheblichen Mehraufwand für den Computer darstellen. Ein Beispiel hierfür sind alle Systeme mit vielen gekoppelten Teilchen. Betrachten wir eine Bewegungsgleichung der Form d xn = f (x1 , . . . , xN ) , dt (1.22) wobei N die Gesamtzahl der beteiligten Teilchen ist. Die zweiten Ableitungen nach der Zeit erhalten wir durch d d2 xn = f (x1 , . . . , xN ) dt2 dt N df dxn = dxn dt n=1 = N df f (x1 , . . . , xN ) . dx n n=1 (1.23) (1.24) (1.25) Entsprechend erhalten wir die dritten Ableitungen nach der Zeit: N d2 d df xn = f (x1 , . . . , xN ) dt2 dt n=1 dxn N N d2 f df df = f+ . dxn1 dxn2 dxn1 dxn2 n =1 n =1 1 (1.26) (1.27) 2 Sie sehen, dass die erste Ableitung eine Summe über alle Teilchen enthält, die zweite Ableitung bereits eine Doppelsumme und Sie sehen vielleicht auch, dass es entsprechend weitergeht, also die dritte Ableitung nach der Zeit bereits eine Dreifachsumme enthält. In diesem Fall führt die direkte Berechnung der höheren Ableitungen zu einem sehr großen Rechenaufwand. Zum Glück gibt es jedoch auch Algorithmen höherer Ordnung, bei denen diese höheren Zeitableitungen gar nicht zur Verfügung gestellt werden müssen, sondern die 20 1 Mechanik der Massenpunkte diese durch Differenzbildung an nahe beieinanderliegenden Punkten selbst ausrechnen. Solche Algorithmen müssen wir zudem nicht selbst programmieren, sondern wir können auf fertige Bibliotheken zurückgreifen, z.B. auf die beiliegende GSL-Bibliothek. Dies ist die Vorgehensweise, die ein Wissenschaftler im Normalfall wählen wird, da sie gegenüber dem Programmieren eigener Routinen einige wichtige Vorteile hat: – eine erhebliche Reduktion der Entwicklungszeit – eigene Routinen wären fast immer langsamer – ein Wechsel des Algorithmus’ ist verhältnismäßig einfach, – die Sicherheit einer gut getesteten Routine. Alle diese Punkte werden wir auch am Beispiel des Doppelpendels bestätigt finden. Die Vorgehensweise, um das bestehende C++ bzw. FORTRAN-Programm so zu modifizieren, dass die Lösung der Differentialgleichung durch eine Bibliotheksroutine erfolgt, erläutern wir bei diesem ersten Beispiel für alle drei auf der CD-ROM beigefügten Bibliotheken GSL, MATPACK und SLATEC, da diese bei den drei vorgestellten Bibliotheken sehr verschieden ist. Beginnen wir jedoch mit einigen grundsätzlichen Überlegungen. Unabhängig von der verwendeten Bibliothek muss unser Computerprogramm folgende Dinge festlegen bzw. zur Verfügung stellen: – – – – – – die zu lösende Differentialgleichung, das numerische Verfahren zur Lösung, Anfangs- und Endzeit, den Anfangsbedingungen die Genauigkeitsanforderung an die gesuchte Lösung und Speicherplatz, der für die Lösung benötigt wird. Die verschiedenen Bibliotheken unterscheiden sich lediglich in der Art und Weise, wie dies alles definiert wird. Die GNU Scientific Library oder GSL legt diese Punkte jeweils einzeln fest – dadurch wird das Programm zwar etwas länger, dafür erlaubt dieser modulare Aufbau aber einen noch leichteren Austausch insbesondere des Lösungsalgorithmus’. Im Einzelnen sieht das dann so aus: 1 2 3 4 5 6 /************************************************************************** * Name: faden_lib.cpp * * Zweck: Simuliert die Dynamik eines Fadenpendels * * Gleichung: (dˆ2/d tˆ2) phi + sin(phi) = 0 * * verwendete Bibiliothek: GSL * **************************************************************************/ 7 8 9 10 11 12 #include #include #include #include #include <iostream> <fstream> <gsl/gsl_matrix.h> <gsl/gsl_errno.h> <gsl/gsl_odeiv.h> 1.2 Das Fadenpendel 13 14 21 #include <math.h> #include "tools.h" 15 16 using namespace std; 17 18 19 //---- globale Variablen int icount1, icount2; 20 21 //------------------------------------------------------------------------- 22 23 int f(double t, const double x[], double dxdt[], void *params) 24 25 26 27 { dxdt[0] = x[1]; dxdt[1] = -sin(x[0]); 28 29 30 31 icount1++; return GSL_SUCCESS; } 32 33 //------------------------------------------------------------------------- 34 35 36 int jac(double t, const double x[], double *dfdx, double dfdt[], void *params) 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 { double mu = *(double *)params; gsl_matrix_view dfdx_mat = gsl_matrix_view_array (dfdx, 2, 2); gsl_matrix * m = &dfdx_mat.matrix; gsl_matrix_set (m, 0, 0, 0.0); gsl_matrix_set (m, 0, 1, 1.0); gsl_matrix_set (m, 1, 0, -cos(x[0])); gsl_matrix_set (m, 1, 1, 0); dfdt[0] = 0.0; dfdt[1] = 0.0; icount2++; return GSL_SUCCESS; } 52 53 //------------------------------------------------------------------------- 54 55 int main( int argc, char *argv[] ) 56 57 { 58 59 60 61 62 63 64 65 66 //-- Definition der Variablen int n, n1, n_start, n_out, max_fct, iaux, error, normal; double t, tend, dt, t1, t2; double rtol, atol, h; double mu = 10; double x[2], x_ref[2]; ifstream in_stream, ref_stream; ofstream out_stream; 67 68 69 70 71 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: faden_lib infile referencefile outfile\n"; 22 72 73 1 Mechanik der Massenpunkte exit(1); } 74 75 76 77 78 79 80 81 //-- Einlesen der Parameter -ref_stream.open(argv[2]); in_stream.open(argv[1]); out_stream.open(argv[3]); out_stream << "! Ergebnis-Datei generiert von faden_lib.cpp\n"; inout(tend,"tend"); inout(atol,"atol"); inout(rtol,"rtol"); inout(n_start,"n_start"); 82 83 84 85 //-- Berechnung einiger benoetigter Parameter -dt = tend; h = 1.e-6; 86 87 88 89 90 //-- Schleife ueber die Anfangsbedingungen for (n1=1; n1<=n_start; n1++) { icount1 = 0; icount2 = 0; 91 92 93 94 95 //-- Anfangsbedingungen in_stream >> x[0]; ref_stream >> x_ref[0]; in_stream >> x[1]; ref_stream >> x_ref[1]; t = 0; 96 97 98 out_stream << t << " " << x[0] << " " << x[1] << " " << 0 << " " << 0 << "\n"; 99 100 101 102 103 104 105 //-- Initialisierung der GSL-Routine const gsl_odeiv_step_type *T = gsl_odeiv_step_rk4; gsl_odeiv_step *s = gsl_odeiv_step_alloc(T, 2); gsl_odeiv_control *c = gsl_odeiv_control_y_new(atol, rtol); gsl_odeiv_evolve *e = gsl_odeiv_evolve_alloc(2); gsl_odeiv_system sys = {f, jac, 2, &mu}; 106 107 108 109 110 111 112 113 114 while (t<tend) { int status = gsl_odeiv_evolve_apply(e, c, s, &sys, &t, tend, &h, x); if (status != GSL_SUCCESS) break; } out_stream << t << " " << x[0] << " " << x[1] << " " << (x[0]-x_ref[0])*(x[0]-x_ref[0]) << " " << (x[1]-x_ref[1])*(x[1]-x_ref[1]) << "\n"; 115 116 117 cout << " Aufrufe von f bzw. jac: " << icount1 << " " << icount2; cout << " Fehler : " << sqr(x[0]-x_ref[0])+sqr(x[1]-x_ref[1]) << "\n"; 118 119 120 121 122 gsl_odeiv_evolve_free(e); gsl_odeiv_control_free(c); gsl_odeiv_step_free(s); } 123 124 125 126 127 out_stream.close(); in_stream.close(); ref_stream.close(); } Die entscheidende Neuerung befindet sich in Zeile 109: die eigentliche Lösung der Differentialgleichung erfolgt nun in einer einzigen Zeile durch 1.2 Das Fadenpendel 23 Aufruf der Routine gsl odeiv evolve apply, die von GSL zur Verfügung gestellt wird. Vor dem Aufruf dieser Routine muss diese initialisiert werden, was in den Zeilen 100 bis 105 geschieht, wobei für uns vor allem die Zeilen 101, 103 und 105 interessant sind. In ersterer wird das numerische Verfahren zur Lösung der Differentialgleichung festgelegt – im Fall von gsl odeiv step rk4 ist dies ein Runge-Kutta-Verfahren 4. Ordnung. In der Zeile 103 wird die Genauigkeitsanforderung (absolute und relative Toleranz) festgelegt. Und schließlich wird in Zeile 105 das Differentialgleichungssystem selbst deklariert, wozu ein Unterprogramm f gehört, das der Programmierer schreiben muss. Dieser Routine wird ein Unterprogramm subroutine f übergeben, in der die Differentialgleichung ẋ = f (x) festgelegt wird. Sie finden dieses Unterprogramm im Programmtext ab Zeile 23. Die genaue Form dieses Unterprogramms (also die zu übergebenden Parameter und deren Reihenfolge) hängt von der verwendeten Bibliotheksroutine ab. Um mitzuzählen, wie oft dieses Unterprogramm aufgerufen wird, wurde noch ein Zähler icount eingebaut, und um ein Maß für die erreichte Genauigkeit zu bekommen, lesen wir die Referenzergebnisse von faden all.f ein und vergleichen diese mit den neu errechneten Lösungen (was natürlich voraussetzt, dass beide Programme mit identischen Eingabe-Parametern gestartet wurden). Dieser modulare Aufbau, bei dem das Lösungsverfahren und die Toleranzen jeweils in einem einzelnen Befehl festgelegt werden, eine weitere Befehlszeile den benötigten Speicherplatz zur Verfügung stellt und eine Zeile für das Ausführen eines Integrationsschrittes benötigt wird, ist eine Besonderheit der GSL-Bibliothek. Bei allen mir bekannten anderen Numerik-Bibliotheken, erfolgen alle diese Schritte in einem einzigen Befehl, z.B. unter MATPACK: error = ODE_Bulirsch(tstart,x,f,tend,h,hmax,atol,rtol,max_fct,normal, aux,iaux,daux); Der entsprechende Aufruf unter der C-Version von Visual Numerics IMSL lautet imsl_ode_runge_kutta(neq,&tstart,tend,x,state,f); während er bei der FORTRAN-Version von Visual Numerics IMSL so aussieht: call divprk(ido,4,f,tstart,tend,param,x) Zum Abschluss geben wir noch die Befehlszeile für SLATEC, eine frei verfügbare FORTRAN-Bibliothek an: & call dderkf(f,4,tstart,x,tend,info,rtol,atol,idid, rwork,lrw,iwork,liw,rpar,ipar) Während die beiden IMSL-Routinen den benötigten Speicherplatz automatisch bereitstellen, geschieht dies bei den anderen beiden Routinen durch Übergabe von Feldern (aux, iaux und daux bzw. rwork und iwork), deren Mindestgröße von der Zahl der zu integrierenden Differentialgleichungen abhängt. 24 1 Mechanik der Massenpunkte Zusätzlich müssen wir für dderkf noch Platz im Arbeitsspeicher reservieren, der während der Lösung der Differentialgleichungen benötigt wird. Dies geschieht durch Übergabe der Felder rwork und iwork, deren Dimensionierung sich aus der Beschreibung der Routine dderkf ergibt. Allen diesen Bibliotheksroutinen gemein sind die Parameter atol und rtol, die angeben welche Genauigkeitsanforderung wir an die gewonnene Lösung stellen: atol legt einen Grenzwert für den Absolutfehler, rtol einen für den relativen Fehler fest. In der Praxis ist es sinnvoll zu verifizieren, dass eine Verschärfung dieser beiden Parameter zu keiner signifikanten Änderung des Ergebnisses führt, um, falls dies doch der Fall sein sollte, kleinere Werte für diese beiden Parameter zu wählen. Desgleichen darf die Einteilung der Zeitachse in diskrete Abschnitte keinen Einfluss auf die Lösung haben, ansonsten kann der gewonnenen Lösung kein Vertrauen geschenkt werden. Setzt man einen dieser Parameter übrigens auf null, wird als alleiniges Kriterium für das Erreichen der geforderten Genauigkeit der jeweils andere Parameter herangezogen. Eine der jeweiligen Situation angemessene Wahl dieser Parameter ist nicht immer einfach, insbesondere solange man keine Informationen über die zu erwartende Lösung hat: – Legt man nur den relativen Fehler fest, bekommt man Probleme, wenn die Lösung einen Nulldurchgang hat, da an diesem Punkt ein relativer Fehler nicht definiert ist. – Legt man nur den absoluten Fehler fest, bekommt man hingegen Schwierigkeiten, wenn die Lösung sehr große Werte annimmt, da dann die Anforderungen – relativ gesehen – immer ansteigen und unter Umständen aufgrund des endlichen Zahlenformats gar nicht mehr erreicht werden können. In solchen Situationen kann man entweder versuchen, durch eine Festlegung beider Parameter Abhilfe zu schaffen, oder man tastet sich in mehreren Probeläufen mit unterschiedlicher Wahl von atol und rtol an das Verhalten der Lösung heran, wonach eine sinnvolle Wahl dieser beiden Werte möglich ist. Das Ergebnis unseres Programms ist > faden_lib faden_lib.par faden_all.ref faden_lib.res Aufrufe von f bzw. jac: 12480 0 Fehler : 9.50448e-14 Aufrufe von f bzw. jac: 16379 0 Fehler : 1.82071e-13 Aufrufe von f bzw. jac: 18972 0 Fehler : 2.05772e-13 Aufrufe von f bzw. jac: 21788 0 Fehler : 1.55255e-13 Aufrufe von f bzw. jac: 14852 0 Fehler : 2.8778e-13 Aufrufe von f bzw. jac: 15856 0 Fehler : 2.52265e-13 Aufrufe von f bzw. jac: 18116 0 Fehler : 1.34472e-13 Aufrufe von f bzw. jac: 19800 0 Fehler : 2.90902e-13 Aufrufe von f bzw. jac: 22261 0 Fehler : 3.15646e-14 Aufrufe von f bzw. jac: 23830 0 Fehler : 1.32739e-11 Im Vergleich zu den bisherigen Programmen mit selbst geschriebener Routine zur Lösung des Gleichungssystems kommt das eingesetzte Runge-KuttaVerfahren odeiv step rk4 mit weniger Stützpunkten aus und erreicht eine höhere Genauigkeit! Interessant ist auch, 1.2 Das Fadenpendel 25 Ohne großen Aufwand können wir nun eine andere der vielen zur Auswahl stehenden Bibliotheksroutinen verwenden: wir tauschen lediglich in Zeile 101 odeiv step rk4 durch odeiv step rk8pd, das eine Implementierung des Runge-Kutta-Prince-Dormand-Algorithmus’ darstellt, aus und erhalten das Programm faden lib2.cpp, das dann mit noch weniger Stützstellen auskommt: > faden_lib2 Aufrufe von Aufrufe von Aufrufe von Aufrufe von Aufrufe von Aufrufe von Aufrufe von Aufrufe von Aufrufe von Aufrufe von faden_lib.par faden_all.ref faden_lib.res f bzw. jac: 272 0 Fehler : 4.88865e-10 f bzw. jac: 169 0 Fehler : 8.7534e-12 f bzw. jac: 207 0 Fehler : 2.53785e-12 f bzw. jac: 245 0 Fehler : 8.01659e-11 f bzw. jac: 193 0 Fehler : 1.6238e-10 f bzw. jac: 169 0 Fehler : 2.65704e-10 f bzw. jac: 207 0 Fehler : 1.51987e-13 f bzw. jac: 233 0 Fehler : 7.65308e-12 f bzw. jac: 245 0 Fehler : 1.07912e-10 f bzw. jac: 307 0 Fehler : 2.82351e-10 Zum Abschluss wollen wir noch eine Routine (odeiv step bsimp) verwenden, die neben der Funktion f auch Gebrauch von jac macht – diese Routine stellt ∂(dfi /dt)/∂xj zur Verfügung. > faden_lib3 Aufrufe von Aufrufe von Aufrufe von Aufrufe von Aufrufe von Aufrufe von Aufrufe von Aufrufe von Aufrufe von Aufrufe von faden_lib.par faden_all.ref faden_lib.res f bzw. jac: 1820 13 Fehler : 1.08217e-09 f bzw. jac: 420 3 Fehler : 2.26444e-09 f bzw. jac: 420 3 Fehler : 4.64311e-09 f bzw. jac: 420 3 Fehler : 2.3433e-09 f bzw. jac: 420 3 Fehler : 2.71246e-08 f bzw. jac: 420 3 Fehler : 1.49442e-09 f bzw. jac: 420 3 Fehler : 2.50474e-09 f bzw. jac: 420 3 Fehler : 3.35529e-09 f bzw. jac: 420 3 Fehler : 3.9156e-09 f bzw. jac: 420 3 Fehler : 7.62995e-10 Gegenüber odeiv step rk8pd stellt dieses Verfahren jedoch keine Verringerung des Rechenaufwands dar. Bei komplexeren Problemen, bei denen wir noch mehr Rechenzeit einsparen wollen (oder müssen), besteht eine weitere Optimierungsmöglichkeit in der Speicherung immer wieder verwendeter Ausdrücke. Zum Beispiel berechnet das Programm faden all.cpp bei jedem Zeitschritt das Produkt (dt)3 /6 zweimal und das Produkt (dt)2 /2 sogar viermal, was insgesamt leicht mehrere tausend- oder gar millionenmal der Fall sein kann. Das ist natürlich unsinnig, und wir hätten gut daran getan, den Wert dieser Audrücke in Variablen, z.B. dt3 und dt2 zu speichern. Viele moderne Compiler erledigen diese Aufgabe selbsttätig, so dass man sich nicht weiter darum kümmern muss – in diesen Fällen genügt es, beim Compilieren eine entsprechende Option zu spezifizieren, die den Optimierer einschaltet. 26 1 Mechanik der Massenpunkte 1.3 Das Doppelpendel Hängt man an den Massenpunkt am Ende des Fadenpendels ein weiteres Pendel erhält man das sogenannte Doppelpendel (siehe Abb. 1.5), dem wir uns in diesem Abschnitt widmen wollen. Abb. 1.5. Das Doppelpendel, bestehend aus einem Fadenpendel, an dem ein weiteres Pendel angehängt ist Hier die Bewegungsgleichungen durch eine Betrachtung der wirkenden Kräfte aufzustellen, ist zwar möglich, aber sehr unübersichtlich. Der Grund hierfür ist die Tatsache, dass die Kräfte im Allgemeinen nicht in eine Richtung zeigen, in die sich die Massenpunkte bewegen können. Das hat Zwangskräfte zur Folge, die von den Stangen aufgebracht werden und die – obwohl zunächst unbekannt – in die Betrachtungen miteinbezogen werden müssen. Deswegen wollen wir an dieser Stelle den Lagrangeformalismus in Erinnerung rufen, der diese Schwierigkeiten elegant umgeht. Ausgangspunkt des Lagrangeformalismus ist ein mathematisches Verfahren zum Auffinden von Extremwerten unter Beachtung von Nebenbedingungen, das unter dem Stichwort Lagrangeparameter in der Literatur zu finden ist. Wir wollen im Rahmen dieses Buches auf eine Herleitung des Lagrangeformalismus’ verzichten, ihn aber jedoch soweit skizzieren, dass der Leser keine Schwierigkeiten bei der Anwendung dieses Verfahrens haben sollte. Der erste Schritt zu den Bewegungsgleichungen besteht im Auffinden sogenannter generalisierter Koordinaten – dabei handelt es sich um einen Satz von Koordinaten q, die so gewählt sind, dass sie bereits die Zwangsbedingungen berücksichtigen, in deren Richtung eine Verrückung also immer möglich ist. Im zweiten Schritt stellt man nun die Lagrangefunktion auf, die für ein nicht explizit zeitabhängiges Problem wie das Vorliegende gleich der Differenz aus kinetischer und potentieller Energie ist: L(q, q̇) = Ekin − Epot . (1.28) 1.3 Das Doppelpendel 27 Zu beachten ist hierbei, dass die Lagrangefunktion als Funktion der im ersten Schritt gefundenen generalisierten Koordinaten und deren Zeitableitungen zu schreiben ist. Im letzten Schritt erhält man die gesuchten Bewegungsgleichungen durch partielle Differentiation der Lagrangefunktion nach diesen Koordinaten bzw. deren Ableitungen nach der Zeit: ∂L d ∂L = . dt ∂ q̇ ∂q (1.29) Wenn Sie den Lagrangeformalismus noch nicht kennen, betrachten Sie bitte diese extrem kurze Skizzierung als Rezept und identifizieren Sie die einzelnen Schritte bei diesem und den folgenden Beispielen. Beginnen wir also mit Schritt 1, dem Auffinden generalisierter Koordinaten. Dies sind am Einfachsten die beiden Winkel φ1 und φ2 . Ganz offensichtlich unterliegen diese beiden Winkel keinen Beschränkungen durch die Zwangsbedingungen, die die beiden Stangen darstellen. Im zweiten Schritt stellen wir nun die Lagrangefunktion auf, wozu wir zunächst die kinetische und die potentielle Energie – ausgedrückt durch φ1 , φ2 , φ̇1 und φ̇2 – benötigen. Der Ausdruck für die potentiellen Energie ist verhältnismäßig einfach: Epot (φ1 , φ2 ) = −m1 gl1 cos φ1 − m2 g(l1 cos φ1 + l2 cos φ2 ) . (1.30) Für die kinetische Energie erhalten wir: Ekin = 2 1 m1 l1 φ̇1 2 1 + m2 l12 φ̇21 + l22 φ̇22 + 2l1 l2 φ̇1 φ̇2 cos(φ1 + φ2 ) . 2 (1.31) Daraus erhalten wir definitionsgemäß die Lagrangefunktion L = Ekin − Epot (1.32) 2 1 1 = m1 l1 φ̇1 + m2 l12 φ̇21 + l22 φ̇22 + 2l1 l2 φ̇1 φ̇2 cos(φ1 + φ2 ) 2 2 (1.33) +m1 gl1 cos φ1 + m2 g(l1 cos φ1 + l2 cos φ2 ) . Im dritten und letzten Schritt erhalten wir daraus die Bewegungsgleichungen: d ∂L d = m1 l12 φ˙1 + m2 l12 φ˙1 + m2 l1 l2 φ˙2 cos(φ1 + φ2 ) dt ∂ φ˙1 dt (1.34) = m1 l1 φ̈1 + m2 l12 φ̈1 + m2 l1 l2 φ̈2 cos(φ1 + φ2 ) − m2 l1 l2 φ̇1 φ̇2 cos(φ1 + φ2 ) − m2 l1 l2 φ̇22 cos(φ1 + φ2 ) ∂L = ∂φ1 (1.35) (1.36) 28 1 Mechanik der Massenpunkte = −m2 l1 l2 φ̇1 φ̇2 sin(φ1 + φ2 ) − m1 gl1 sin φ1 (1.37) −m2 gl1 sin φ1 d ∂L d = (1.38) m2 l22 φ˙2 + m2 l1 l2 φ˙1 cos(φ1 + φ2 ) dt ∂ φ˙2 dt = m2 l1 l2 φ̈2 + m2 l1 l2 φ¨1 cos(φ1 + φ2 ) − m2 l1 l2 φ̇2 sin(φ1 + φ2 ) 1 −m2 l1 l2 φ̇1 φ̇2 sin(φ1 + φ2 ) ∂L = ∂φ2 = −m2 l1 l2 φ̇1 φ̇2 sin(φ1 + φ2 ) − m2 gl2 sin φ2 . (1.39) (1.40) (1.41) Sie sehen, wie diese scheinbar harmlose Veränderung gegenüber dem Problem des vorherigen Abschnitts die Sache erheblich komplizierter macht. Um die Bewegungsgleichungen übersichtlicher zu gestalten, skalieren wir ähnlich wie beim Fadenpendel l1 τ =t (1.42) g und führen die Abkürzungen m2 m1 l2 β= l1 α= (1.43) (1.44) ein. Damit erhalten wir: (1 + α)φ̈1 + αβ φ̈2 cos(∆φ) + αβ φ̇22 sin(∆φ) = −(1 + α) sin(φ1 ) (1.45) β 2 φ̈2 + φ̈1 cos(∆φ) − φ̇21 sin(∆φ) = − sin(φ2 ) , (1.46) wobei wir die Abkürzung ∆φ = φ1 − φ2 (1.47) eingeführt haben. Da die Herleitung dieser Bewegungsgleichungen länglich und damit auch fehlerträchtig ist, wollen wir zumindest einen einfachen Test auf Richtigkeit durchführen: Im Grenzfall α → 0 hängt an der ersten Masse m1 ein quasi masseloses Teilchen, das deren Bewegung natürlich nicht beeinflussen kann. Wir sollten in diesem Grenzfall also für das erste Teilchen die Bewegungsgleichung des einfachen Fadenpendels erhalten, was tatsächlich der Fall ist. Glücklicherweise haben wir damit die Hauptarbeit getan und es sollte uns keine sonderlichen Schwierigkeiten bereiten, diese Differentialgleichungen zu implementieren. Am Einfachsten nehmen wir das bestehende Programm faden lib.cpp als Ausgangspunkt, kopieren dieses in eine neue Datei dpendel1.cpp und nehmen die nötigen Änderungen vor: 1.3 Das Doppelpendel 1 2 3 4 5 6 29 /************************************************************************** * Name: dpendel1.cpp * * Zweck: Simuliert die Dynamik eines Doppelpendels * * Gleichung: siehe Text * * verwendete Bibiliothek: GSL * **************************************************************************/ 7 8 9 10 11 12 13 14 #include #include #include #include #include #include #include <iostream> <fstream> <gsl/gsl_matrix.h> <gsl/gsl_errno.h> <gsl/gsl_odeiv.h> <math.h> "tools.h" 15 16 using namespace std; 17 18 19 //---- globale Variablen double alpha, beta; 20 21 //------------------------------------------------------------------------- 22 23 int f(double t, const double x[], double dxdt[], void *params) 24 25 26 27 28 29 30 31 32 33 { dxdt[0] = x[1]; dxdt[1] = ( - alpha*beta*x[3]*x[3] * sin(x[0]-x[2]) (1+alpha)*sin(x[0]) - alpha*x[1]*x[1]*sin(x[0] -x[2])*cos(x[0]-x[2]) + alpha*sin(x[2])*cos(x[2]-x[0]) ) / ( 1 + alpha*pow(sin(x[0]-x[2]),2) ); dxdt[2] = x[3]; dxdt[3] = ( -dxdt[1] * cos(x[0]-x[2]) +x[1]*x[1] * sin(x[0]-x[2]) - sin(x[2]) ) / beta; 34 35 36 return GSL_SUCCESS; } 37 38 //------------------------------------------------------------------------- 39 40 41 42 int jac(double t, const double x[], double *dfdx, double dxdt[], void *params) 43 44 45 46 { return GSL_SUCCESS; } 47 48 49 //------------------------------------------------------------------------- 50 51 int main( int argc, char *argv[] ) 52 53 { 54 55 56 57 58 59 //-- Definition der Variablen int n, neq, n1, n2, nout; double t, tend, dt, dt1, dtmin, dtfirst, eps, t1, t2, E, x[4]; double phi1_0, phi2_0, v_phi1_0, v_phi2_0, rtol, atol, h; double mu = 10; 30 60 61 1 Mechanik der Massenpunkte ifstream in_stream; ofstream out_stream; 62 63 64 65 66 67 68 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: dpendel1 infile outfile\n"; return 0; } 69 70 71 72 73 74 75 76 77 78 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von dpendel1.cpp\n"; inout(tend,"tend"); inout(phi1_0,"phi1_0"); inout(phi2_0,"phi2_0"); inout(v_phi1_0,"v_phi1_0"); inout(v_phi2_0,"v_phi2_0"); inout(alpha,"alpha"); inout(beta,"beta"); inout(nout,"nout"); in_stream.close(); 79 80 81 82 83 84 85 86 //-- Initialiserung der Bibliotheksroutine atol = 1.e-6; rtol = 1.e-6; const gsl_odeiv_step_type *T = gsl_odeiv_step_rkf45; gsl_odeiv_step *s = gsl_odeiv_step_alloc(T, 4); gsl_odeiv_control *c = gsl_odeiv_control_y_new(atol, rtol); gsl_odeiv_evolve *e = gsl_odeiv_evolve_alloc(4); gsl_odeiv_system sys = {f, jac, 4, &mu}; 87 88 89 90 //-- Berechnung einiger benoetigter Parameter -dt = tend / nout; h = 1.e-6; 91 92 93 x[0] = phi1_0; x[2] = phi2_0; x[1] = v_phi1_0; x[3] = v_phi2_0; 94 95 96 97 98 99 E = 0.5 * x[1]*x[1] + 0.5 * alpha * (x[1]*x[1]+ pow(beta*x[3],2) + 2 * beta * x[1] * x[3] * cos(x[0]-x[2])) - cos(x[0]) - alpha * (cos(x[0])+beta*cos(x[2])); out_stream << 0 << " " << x[0] << " " << x[1] << " " << x[2] << " " << x[3] << " " << E << "\n"; 100 101 102 103 104 105 106 107 108 109 110 111 112 t = 0; for (n=1; n<=nout; n++) { t1 = n * dt; while (t<t1) { int status = gsl_odeiv_evolve_apply(e, c, s, &sys, &t, t1, &h, x); if (status != GSL_SUCCESS) break; } E = 0.5 * x[1]*x[1] + 0.5 * alpha * (x[1]*x[1]+ pow(beta*x[3],2) + 2 * beta * x[1] * x[3] * cos(x[0]-x[2])) - cos(x[0]) - alpha * (cos(x[0])+beta*cos(x[2])); 113 114 115 116 out_stream << t << " " << x[0] << " " << x[1] << " " << x[2] << " " << x[3] << " " << E << "\n"; } 117 118 gsl_odeiv_evolve_free(e); 1.3 Das Doppelpendel 119 120 31 gsl_odeiv_control_free(c); gsl_odeiv_step_free(s); 121 122 123 out_stream.close(); } Zunächst hat sich natürlich die Zahl der Zustandsvariablen von 2 in faden lib.cpp auf 4 erhöht (zwei für jede der beiden Massen) und dementsprechend musste auch die Größe des allozierten Speicherplatzes angepasst werden. Auf die Möglichkeit, in einem Programmlauf die Trajektorien zu mehreren Anfangsbedingungen zu berechnen, haben wir hier verzichtet, um das Programm übersichtlicher zu gestalten. Und schließlich berechnen wir in den Zeilen 95–97 bzw. 110–112 als weitere Kontrolle die Energie und geben diese in der Ergebnisdatei mit aus. Die Tatsache, dass diese konstant ist, ist eine Kontrolle für die Richtigkeit sowohl der analytischen Berechnung der Bewegungsgleichung als auch der Implementierung in einem Computerprogramm. Als Ergebnis erhalten wir die vier Zustandsvariablen φ1 , φ2 , p1 = φ̇1 und p2 = φ̇2 als Funktion der Zeit (Abb. 1.6). Wenn wir die Ergebnisse für die hier gewählten Parameter (α = 1, und β = 0.8) betrachten, so erkennen wir zwar auf kurzen Zeitskalen eine Art immer wiederkehrende Struktur, aber – im Gegensatz zu den Ergebnissen beim Fadenpendel – keine Regelmäßigkeiten über lange Zeiträume. Diese Abwesenheit von Regularität wollen wir nun genauer untersuchen, wozu wir jedoch zuerst einige theoretische Grundlagen benötigen, die wir in den beiden folgenden Abschnitten legen wollen. Abb. 1.6. Zeitverlauf der beiden Auslenkungen φ1 (a) und φ2 (b) sowie der kanonisch konjugierten Impulse p1 (c) und p2 (d) beim Doppelpendel (α = 1, β = 0.8) 32 1 Mechanik der Massenpunkte 1.4 Integrable und nicht integrable Dynamiken Dieser Abschnitt enthält zwar keinerlei Formeln, erfordert dafür ein erhöhtes Maß an räumlichem Vorstellungsvermögen, vor allem in höherdimensionalen Räumen. Es ist daher erfahrungsgemäß sinnvoll, sich die Absätze langsam zu Gemüte zu führen und gegebenenfalls noch einmal zu lesen. Kommen wir zunächst zum Beispiel des einfachen Fadenpendels zurück. In diesem Fall ist der Phasenraum zweidimensional (φ und φ̇ als unabhängige Variable). Einer Trajektorie steht jedoch nicht der gesamte Phasenraum zur Verfügung, da die Energieerhaltung die Trajektorie auf eine Energiefläche1 zwingt. Diese zusätzliche Einschränkung hat zur Folge, dass der effektiv zur Verfügung stehende Unterraum eindimensional ist, das heißt dass die Trajektorie durch Erhaltungssätze vollständig festgelegt ist. Diesen einer Trajektorie zur Verfügung stehende Teilraum des Phasenraumes werden wir im Folgenden immer wieder benötigen, weswegen wir ihm ein Symbol zuordnen: U. Eine Dynamik, bei der ebenso viele von einander unabhängige Erhaltungsgrößen wie dynamische Variablen existieren, heißt integrabel . Schauen wir uns nun unter diesem Aspekt das Doppelpendel an: die Zahl dynamischer Variabler ist nun zwei, was bedeutet, dass der Phasenraum vierdimensional ist. Die einzige Erhaltungsgröße, die wir unmittelbar erkennen, ist die Energie; wir können aber zunächst nicht sicher sein, dass wirklich keine weitere Erhaltungsgröße existiert. Im Fall von zwei dynamischen Variablen gibt es jedoch eine Methode, genau dies zu überprüfen: der sogenannte Poincaré-Plot. Dazu stellen wir uns zunächst vor, es gäbe eine weitere Erhaltungsgröße, die wir jedoch nicht gefunden haben. In diesem Fall wäre der zur Verfügung stehende Unterraum des Phasenraums zweidimensional (anstatt dreidimensional im Fall von nur einer Erhaltungsgröße). Definieren wir einen beliebigen dreidimensionalen Unterraum V des Phasenraumes, so dass der Schnitt von U und V nicht leer ist und stellen uns die Frage, welche Dimension diese Schnittmenge hat. Im Fall der integrablen Dynamik ist dieser Schnitt eindimensional (die Dimension ist eins niedriger als die Dimension von U); im Fall der nicht integrablen Dynamik ist der Schnitt zweidimensional. Und diese beiden Fälle lassen sich wie folgt unterscheiden: Wir bestimmen die Schnittpunkte der Trajektorie mit V und stellen diese in einer graphischen Projektion auf die zweidimensionale Zeichenfläche dar. Im ersten Fall ordnen sich diese entlang einer Linie an, im zweiten Fall verteilen sich die Schnittpunkte auf einer Fläche. Nun wollen wir unser Programm dpendel1.cpp so erweitern, dass es statt dem Zeitverlauf einer Trajektorie die Schnittpunkte dieser Trajektorie mit einer festgelegten Hyperfläche V ausgibt. Diese definieren wir durch φ̇1 = 0 1 Der Begriff Fläche ist hier im Sinne einer Hyperfläche zu verstehen, d.h. ein Unterraum, dessen Dimension gegenüber dem Phasenraum um eins erniedrigt ist. Im hier vorliegenden Fall eines zweidimensionalen Phasenraums ist die Energiefläche also ein eindimensionales Gebilde. 1.4 Integrable und nicht integrable Dynamiken 33 (ein Wert, den φ̇1 sicher bei jeder Trajektorie immer wieder annimmt). Das Ergebnis ist das Programm dpendel2.cpp: 1 2 3 4 5 6 /************************************************************************** * Name: dpendel2.cpp * * Zweck: Poincare-Plot fuer ein Doppelpendel * * Gleichung: siehe Text * * verwendete Bibiliothek: GSL * **************************************************************************/ 7 8 9 10 11 12 13 14 15 #include #include #include #include #include #include #include #include <iostream> <fstream> <gsl/gsl_matrix.h> <gsl/gsl_errno.h> <gsl/gsl_odeiv.h> <gsl/gsl_roots.h> <math.h> "tools.h" 16 17 using namespace std; 18 19 20 21 //---- globale Variablen double alpha, beta, tstart, tend, akt_f0, akt_f1, akt_f2, akt_f3; double phi1_alt, phi2_alt, v_phi1_alt, v_phi2_alt; 22 23 //------------------------------------------------------------------------- 24 25 int f(double t, const double x[], double dxdt[], void *params) 26 27 28 29 30 31 32 33 34 35 { dxdt[0] = x[1]; dxdt[1] = ( - alpha*beta*x[3]*x[3] * sin(x[0]-x[2]) - (1+alpha)*sin(x[0]) - alpha*x[1]*x[1]*sin(x[0]-x[2])*cos(x[0]-x[2]) + alpha*sin(x[2])*cos(x[2]-x[0]) ) / ( 1 + alpha*pow(sin(x[0]-x[2]),2) ); dxdt[2] = x[3]; dxdt[3] = ( -dxdt[1] * cos(x[0]-x[2]) +x[1]*x[1] * sin(x[0]-x[2]) - sin(x[2]) ) / beta; 36 37 38 return GSL_SUCCESS; } 39 40 //------------------------------------------------------------------------- 41 42 43 44 int jac(double t, const double x[], double *dfdx, double dxdt[], void *params) 45 46 47 48 { return GSL_SUCCESS; } 49 50 //------------------------------------------------------------------------- 51 52 double funktion(double t, void *params) 53 54 55 56 { double y[4], rtol, atol, t1, t2; double mu = 10; 34 57 1 Mechanik der Massenpunkte double h = 1.e-8; 58 59 60 61 62 y[0] y[2] t1 = t2 = = phi1_alt; = phi2_alt; tstart; t; y[1] = v_phi1_alt; y[3] = v_phi2_alt; 63 64 65 66 67 68 69 atol = 1.e-7; rtol = 1.e-7; const gsl_odeiv_step_type *T2 = gsl_odeiv_step_rkf45; gsl_odeiv_step *s2 = gsl_odeiv_step_alloc(T2, 4); gsl_odeiv_control *c2 = gsl_odeiv_control_y_new(atol, rtol); gsl_odeiv_evolve *e2 = gsl_odeiv_evolve_alloc(4); gsl_odeiv_system sys2 = {f, jac, 4, &mu}; 70 71 72 73 74 75 while (t1<t2) { int status = gsl_odeiv_evolve_apply(e2,c2,s2,&sys2,&t1,t2,&h,y); if (status != GSL_SUCCESS) break; } 76 77 78 79 gsl_odeiv_evolve_free(e2); gsl_odeiv_control_free(c2); gsl_odeiv_step_free(s2); 80 81 82 83 84 akt_f0 = y[0]; akt_f2 = y[2]; return y[1]; } akt_f1 = y[1]; akt_f3 = y[3]; 85 86 //------------------------------------------------------------------------- 87 88 int main( int argc, char *argv[] ) 89 90 { 91 92 93 94 //-- Definition der Variablen int n, neq, n1, n2, nout, status; int iter, max_iter = 100; 95 96 97 98 99 100 double x[4], t, dt, dt1, dtmin, dtfirst, t1, t2; double phi1_0, phi2_0, v_phi1_0, v_phi2_0, E, rtol, atol, h, r; double mu = 10; ifstream in_stream; ofstream out_stream; 101 102 103 104 105 106 107 rtol = 1.e-10; atol = 1.e-10; const gsl_odeiv_step_type *T = gsl_odeiv_step_rkf45; gsl_odeiv_step *s = gsl_odeiv_step_alloc(T, 4); gsl_odeiv_control *c = gsl_odeiv_control_y_new(atol, rtol); gsl_odeiv_evolve *e = gsl_odeiv_evolve_alloc(4); gsl_odeiv_system sys = {f, jac, 4, &mu}; 108 109 110 111 112 const gsl_root_fsolver_type *T1; gsl_root_fsolver *s1; gsl_function F; F.function = &funktion; 113 114 115 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) 1.4 Integrable und nicht integrable Dynamiken 116 117 118 119 { cout << " Aufruf: dpendel2 infile outfile\n"; return 0; } 120 121 122 123 124 125 126 127 128 129 //-- Einlesen der Parameter -out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von dpendel2.cpp\n"; in_stream.open(argv[1]); inout(tend,"tend"); inout(phi1_0,"phi1_0"); inout(phi2_0,"phi2_0"); inout(v_phi1_0,"v_phi1_0"); inout(v_phi2_0,"v_phi2_0"); inout(alpha,"alpha"); inout(beta,"beta"); inout(nout,"nout"); in_stream.close(); 130 131 132 133 134 //-- Berechnung einiger benoetigter Parameter -t = 0; dt = tend / nout; h = 1.e-6; 135 136 137 x[0] = phi1_0; x[2] = phi2_0; x[1] = v_phi1_0; x[3] = v_phi2_0; 138 139 140 141 142 E = -cos(x[0]) + 0.5*sqr(x[1]) - alpha*(cos(x[0])+beta*cos(x[2])) + 0.5*alpha*( sqr(x[1])+sqr(beta*x[3]) + 2*beta*x[1]*x[3]*cos(x[0]+x[2]) ); cout << " E = " << E << "\n"; 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 for (n=1; n<=nout; n++) { t1 = (n-1) * dt; t2 = n * dt; phi1_alt = x[0]; v_phi1_alt = x[1]; phi2_alt = x[2]; v_phi2_alt = x[3]; while (t<t2) { status = gsl_odeiv_evolve_apply(e, c, s, &sys, &t, t2, &h, x); if (status != GSL_SUCCESS) break; } if (v_phi1_alt*x[1] < 0) { tstart = t1; tend = t2; T1 = gsl_root_fsolver_brent; s1 = gsl_root_fsolver_alloc (T1); gsl_root_fsolver_set (s1, &F, t1, t2); iter = 0; do { iter++; status = gsl_root_fsolver_iterate(s1); r = gsl_root_fsolver_root(s1); t1 = gsl_root_fsolver_x_lower(s1); t2 = gsl_root_fsolver_x_upper(s1); status = gsl_root_test_interval(t1, t2, 0, 1.e-6); } while (status == GSL_CONTINUE && iter < max_iter); out_stream << t1 << " " << akt_f0 << " " << akt_f1 << " " << akt_f2 << " " << akt_f3 << "\n"; gsl_root_fsolver_free(s1); } 35 36 175 1 Mechanik der Massenpunkte } 176 177 178 179 gsl_odeiv_evolve_free(e); gsl_odeiv_control_free(c); gsl_odeiv_step_free(s); 180 181 182 out_stream.close(); } Zunächst integriert das Programm wie dpendel1.cpp die Bewegungsgleichungen. Sobald v phi1 sein Vorzeichen wechselt, wird ein Nullstellensuchprogramm damit beauftragt, den exakten Schnittpunkt mit v phi1 = 0 zu bestimmen. Hierzu muss eine Funktion funktion implementiert werden, die für jeden Zeitpunkt den Wert von v phi1 zurückgibt. Diese Funktion arbeitet ebenfalls mit der Bibliotheksroutine zur Lösung von Differentialgleichungen, allerdings sind deren Aufrufe nicht wie bisher immer zu aufeinander folgenden Intervallen, sondern zu Intervallen mit immer demselben Startwert. Aus diesem Grund müssen wir vor jedem Aufruf deren Workarrays neu initialisieren. Außerdem achten wir auf eine strenge Trennung der Integration der Bewegungsgleichungen im Hauptprogramm (mit den Kontrollvariablen s, c, e und sys) und derjenigen in der Funktion funktion (mit den Kontrollvariablen s2, c2, e2 und sys2). Starten wir zunächst das Programm mit den Parametern > cat dpendel2.par 200000 0 1 1 0 1 1 1000000 und stellen die erhaltenen Schnittpunkte in der φ1 -φ2 -Ebene dar, so erhalten wir: Abb. 1.7. Poincaré-Plot für eine chaotische Dynamik: die Punkte füllen eine Fläche aus Dies ist das charakteristische Bild, wie wir es bei einem nicht integrablen System erwarten: die Punkte füllen (wenn auch nicht gleichmäßig) eine 1.4 Integrable und nicht integrable Dynamiken 37 Fläche. Diese Fläche weist in unserem Beispiel eine unegelmäßige Berandung und darüber hinaus Löcher auf – es stellt sich die Frage, wie die Trajektorien aussehen, die durch diese Löcher gehen. Um diese Frage zu beantworten, modifizieren wir lediglich die Anfangsbedingungen für unsere Trajektorie: > cat dpendel2a.par 200000 -0.5 0.6 0 1.4423578 1 1 1000000 Der Wert von 1.442375 für v phi2 0 ergibt sich aus der Bedingung, dass die Gesamtenergie aus (1.30) und (1.31) die selbe sein soll, wie bei der vorangegangen Parameter-Datei. Das Bild, das wir für diese Anfangsbedingungen erhalten, ist völlig anders: Abb. 1.8. Poincaré-Plot für eine integrable Dynamik: die Punkte ordnen sich entlang eines eindimensionalen Gebildes (in diesem Fall zweier Kurven) an Hier liegt eindeutig die Situation einer integrable Dynamik vor, bei der sich diese Durchstoßpunkte entlang einer oder mehrerer Kurven anordnen müssen. Wir könnten nun für eine Vielzahl von Anfangsbedingungen entsprechende Plots erstellen, um zu entscheiden, in welchen Bereichen des Phasenraumes die Bewegung regulär und in welchen die Dynamik chaotisch ist. Wir können jedoch diese etwas mühselige Vorgehensweise vereinfachen, indem wir mehrere solcher Plots in einer Darstellung vereinigen. Wir müssen dabei jedoch sicherstellen, dass durch die Projektion auf eine zweidimensionale Darstellung keine sich überlappenden Strukturen erzeugt werden – in diesem Fall könnte man dem entstehenden Diagramm keine Informationen mehr entlocken. Am Einfachsten erfüllen wir diese Forderung, indem wir Startwerte mit gleicher Gesamtenergie wählen, dann nämlich liegen alle Trajektorien auf einer gemeinsamen Hyperfläche. Wenn wir dies berücksichtigen, kommen wir durch Modifikation des Hauptprogramms von dpendel2.cpp zu dem Programm dpendel3.cpp: 38 1 Mechanik der Massenpunkte ... 118 119 120 121 122 123 124 125 126 127 //-- Einlesen der Parameter -out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von dpendel3\n"; in_stream.open(argv[1]); inout(tend,"tend"); inout(dphi1,"dphi1"); inout(dphi2,"dphi2"); inout(E_0,"E_0"); inout(alpha,"alpha"); inout(beta,"beta"); inout(nmax,"nmax"); inout(nout,"nout"); in_stream.close(); 128 129 130 131 //-- Berechnung einiger benoetigter Parameter -dt = tend / nout; h = 1.e-6; 132 133 F.function = &funktion; 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 for (n1=-nmax; n1<=nmax; n1++) for (n2=-nmax; n2<=nmax; n2++) { t = 0; x[0] = n1*dphi1; x[1] = 0; x[2] = n2*dphi2; // x[3] = 0; a = E_0 + cos(x[0]) + alpha*(cos(x[0])+beta*cos(x[2])); x[3] = sqrt(2*a / (alpha*sqr(beta))); cout << n1 << " " << n2 << "\n"; for (n=1; n<=nout; n++) { t1 = (n-1) * dt; t2 = n * dt; phi1_alt = x[0]; v_phi1_alt = x[1]; phi2_alt = x[2]; v_phi2_alt = x[3]; while (t<t2) { status = gsl_odeiv_evolve_apply(e, c, s, &sys, &t, t2, &h, x); if (status != GSL_SUCCESS) break; } if (v_phi1_alt*x[1] < 0) { tstart = t1; tend = t2; T1 = gsl_root_fsolver_brent; s1 = gsl_root_fsolver_alloc (T1); gsl_root_fsolver_set (s1, &F, t1, t2); iter = 0; do { iter++; status = gsl_root_fsolver_iterate(s1); r = gsl_root_fsolver_root(s1); t1 = gsl_root_fsolver_x_lower(s1); t2 = gsl_root_fsolver_x_upper(s1); status = gsl_root_test_interval(t1, t2, 0, 1.e-6); } while (status == GSL_CONTINUE && iter < max_iter); out_stream << t1 << " " << akt_f0 << " " << akt_f1 << " " << akt_f2 << " " << akt_f3 << "\n"; gsl_root_fsolver_free(s1); } 1.4 Integrable und nicht integrable Dynamiken } 176 177 39 } 178 179 180 181 gsl_odeiv_evolve_free(e); gsl_odeiv_control_free(c); gsl_odeiv_step_free(s); 182 183 184 185 out_stream.close(); return 0; } Das Einlesen der Eingabeparameter in Zeile 142–149 wurde dahingehend geändert, dass v phi 1 nicht mehr eingegeben werden muss, sondern fest gleich null ist. Auch v phi 2 muss nicht mehr eingegeben werden, da dieses durch die Energie E festgelegt wird. Variiert werden phi 1 und phi 2, so dass statt diesen Variablen deren Schrittweiten dphi1 und dphi2 eingelesen werden. Neu hinzugekommen ist die Festlegung der Anfangswerte aus den Schleifenzählern n1 und n2 sowie der Anfangsenergie E 0 in den Zeilen 163–168. Zu beachten ist dabei, dass es Wertekombinationen von φ1 und φ2 gibt, bei denen die vorgegebene Energie E0 nicht mehr möglich ist, weil die zugehörige potentielle Energie bereits größer ist. In diesem Fall hat die quadratische Gleichung für vφ1 keine reelle Lösung. Um solche Situationen nicht weiter zu betrachten, fragen wir in Zeile 177 das Vorzeichen des Terms des Radikanden a ab. Wir haben die Eingabeparameter geringfügig geändert, um das entstehende Bild etwas zu variieren und die regulären Inseln etwas zu vergrößern: > cat dpendel3.par 2000 0.2 0.2 -1.5403 1 1.2 5 10000 Das Ergebnis (Abb. 1.9) zeigt sowohl Bereiche, wie wir Sie bei einer integrablen Dynamik erwarten, als auch Bereiche, die dem nicht integrablen Fall Abb. 1.9. Poincaré-Plot für eine Dynamik mit chaotischen und regulären Bereichen 40 1 Mechanik der Massenpunkte entsprechen. Für eine Charakterisierung dieser beiden parallel vorliegenden Situationen ist die Unterscheidung reguläre vs. chaotische Bewegung geeignet, auf die wir im folgenden Abschnitt eingehen. 1.5 Reguläre und chaotische Dynamiken Die Unterscheidung integrabel zu nicht integrabel ist eng verwandt mit einer anderen Klassifizierung in der klassischen Mechanik. Diese nimmt als Klassifizierungskriterium die Sensitivität der Trajektorie auf geringfügige Änderungen der Startwerte. Wächst der Abstand zweier infinitesimal benachbarter Punkte im Phasenraum mit der Zeit exponentiell an, so spricht man von chaotischem Verhalten, ist dieses Anwachsen langsamer als exponentiell (also z.B. nach einem Potenzgesetz) nennt man die Dynamik regulär. In dem letzten Absatz sind einige Begriffe enthalten, die einer näheren Erläuterung bedürfen. Zunächst ist vom Abstand zwischen Trajektorien die Rede, was allerdings etwas unpräzise ist. Gemeint ist der Abstand zwischen zwei Punkten im Phasenraum und da sich diese beiden jeweils auf ihrer Trajektorie bewegen, ist auch der Abstand eine Funktion der Zeit. Wir benötigen also eine Abstandsfunktion ∆ im Phasenraum, die jeweils zwei Punkten einen Abstand zuordnet – eine solche Abstandsfunktion wird auch Metrik genannt. Dann müssen wir noch klären, was unter benachbart gemeint ist. Damit meinen wir, dass der Anfangsabstand der beiden Punkte infinitesimal sein soll, d.h. wir betrachten den Grenzwert ∆(t0 = 0) → 0. Solche Abstände verhalten sich in einem bestimmten Sinne linear: skaliert man nämlich den infinitesimalen Anfangsabstand mit einem Faktor α, so bleibt diese Skalierung erhalten: Gegeben sind drei Trajektorien x0 (t), x1 (t) und x2 (t), die sich zum Zeitpunkt t = 0 um (1.48) ∆01 (t = 0) = |x0 (0) − x1 (0)| bzw. ∆02 (t = 0) = x2 (0) − x2 (0) = α∆01 (0) (1.49) unterscheiden. Diese Abstände ändern sich nun aufgrund der Dynamik des Systems, im Limes |∆x01 (t = 0)| → 0 gilt jedoch die Beziehung ∆02 (t) = α∆01 (t) (1.50) für alle (endlichen) Zeiten t. Um Missverständnisse zu vermeiden, bedarf es einiger Erklärungen, mit deren Hilfe wir das Gesagte dann auch in einer quantitativen Größe zum Ausdruck bringen können. Der Abstand zweier Punkte im Phasenraum wird im Allgemeinen nicht kontinuierlich größer werden, sondern zwischenzeitlich auch mal abnehmen. Bei der Unterscheidung zwischen regulär und chaotisch interessiert man sich ausschließlich für das Langzeitverhalten (t → ∞), bei dem dieses intermediäre 1.5 Reguläre und chaotische Dynamiken 41 Verhalten nicht beachtet wird. Da trotz dieser langen Zeiten und des potentiell exponentiellen Anwachsens des Abstandes letzterer klein bleiben muss, ist die Reihenfolge dieser beiden Limites entscheidend: zuerst wird der Limes Abstand gegen null und dann der Limes Zeit gegen unendlich durchgeführt! Unter Berücksichtigung des bisher gesagten definieren wir den LyapunovExponenten durch 1 ln (∆(T )/∆(0)) . T →∞ ∆(t=0)→0 T λ = lim lim (1.51) Bevor wir an die numerische Berechnung des Lyapunov-Exponenten gehen, noch einige Bemerkungen: – Der Lyapunov-Exponent ist ein Maß für die Empfindlichkeit gegen kleine Änderung der Anfangsbedingungen. Ist der Lyapunov-Exponent kleiner oder gleich null, wachsen Abstände zwischen Trajektorien im Allgemeinen polynomial an, während ein Lyapunov-Exponent größer als null bedeutet, dass diese Abstände exponentiell anwachsen. Da in der Realität ein Zustand nie genau bekannt ist, sondern wir immer eine Unsicherheit z.B. durch Messungenauigkeiten haben, ist der Lyapunov-Exponent auch ein Maß dafür wie schnell diese Unsicherheit wächst und unsere Kenntnis über das System verlorengeht. – Die Wahl der Metrik ∆ hat – von pathologischen Ausnahmen abgesehen – keinen Einfluss auf den Lyapunov-Exponenten. – Auch die Wahl des Nachbarpunktes, von dem die zweite Trajektorie startet, spielt – wieder abgesehen von singulären Fällen – keine Rolle. Nun jedoch zur numerischen Berechnung des Lyapunov-Exponenten. Die Definition (1.51) mit den beiden Limites ∆(t = 0) → 0 und t → ∞ stellt numerisch ein Problem dar, da die endliche Genauigkeit, die uns der Computer zur Verfügung stellt, den ersten Grenzübergang unmöglich macht. Bei den bisherigen Anwendungen stellte dies kein Problem dar, da relative Abstände von 10−10 und kleiner durchaus aufgelöst werden können. Erst durch den zweiten Grenzübergang t → ∞ werden diese kleinen Abstände zu einem begrenzenden Faktor, da sie eine Einschränkung für die Zeiten geben, zu denen der Abstand noch klein ist. Nehmen wir beispielsweise einen Anfangsabstand von 10−10 und einen Lyapunov-Exponenten von 1 an und fragen uns, wie lange der Abstand kleiner als 10−4 bleibt, so ergibt sich diese maximale Zeit, bis zu der wir die Zeitentwicklung verfolgen dürfen: tmax = ln(10−4 /10−10 ) ≈ 16 . (1.52) Selbst unter etwas günstigeren Annahmen (kleinerer Anfangsabstand, kleinerer Lyapunov-Exponent) sind wir weit davon entfernt, den Grenzübergang t → ∞ vernünftig durchzuführen. Die Beziehung (1.50) ermöglicht uns jedoch einen Trick, mit dem wir dieses Dilemma beseitigen können. Wir beginnen mit einem moderaten Anfangsabstand, der uns nur eine kurze Zeitentwicklung ∆t erlaubt, ohne den 42 1 Mechanik der Massenpunkte Linearitätsbereich zu verlassen. Danach skalieren wir den – inzwischen angewachsenen Abstand – auf seinen ursprünglichen Wert zurück, was wegen (1.50) einer Verkleinerung des Anfangsabstands äquivalent ist: ∆0eff = ∆0 ∆1 . ∆0 (1.53) Nach einer erneuten Entwicklung über einen kurzen Zeitbereich skalieren wir erneut zurück und so weiter. Da wir jedesmal effektiv den Anfangsabstand verkleinern, erreichen wir im Prinzip beliebig kleine Werte für denselben, ohne je wirklich mit solchen winzigen Differenzen rechnen zu müssen: ∆0eff = ∆0 ∆1 ∆2 ∆N ... , ∆0 ∆0 ∆0 (1.54) wobei N die Zahl der insgesamt durchgeführten Zeitschritte ∆t ist. Diese Vorgehensweise liefert uns eine Folge von Abständen ∆1 , ∆2 , . . . , die die beiden Phasenraumpunkte am Ende des ersten, zweiten, dritten . . . Zeitintervalls haben. Den gesuchten Lyapunov-Exponenten erhalten wir dann als N ∆t ∆N λ= (1.55) ln ∆0eff ∆1 N ∆t ∆N ∆N −1 = ... (1.56) ln ∆ 0 ∆0 ∆0 N 1 λn , = N n=1 wobei wir quasi Kurzzeit-Lyapunov-Exponenten ∆n 1 ln λn = ∆t ∆0 (1.57) (1.58) eingeführt haben. Nach Gleichung (1.57) ist der Lyapunov-Exponent also einfach der Mittelwert dieser Kurzzeit-Exponenten. Nachdem wir diese Informationen vorangestellt haben, sollte das folgende Programm verständlich sein: 1 2 3 4 5 6 /************************************************************************** * Name: ljapu.cpp * * Zweck: Ljapunov-Exponent fuer ein Doppelpendel * * Gleichung: siehe Text * * verwendete Bibiliothek: GSL * **************************************************************************/ 7 8 9 10 11 #include #include #include #include <iostream> <fstream> <gsl/gsl_matrix.h> <gsl/gsl_errno.h> 1.5 Reguläre und chaotische Dynamiken 12 13 14 43 #include <gsl/gsl_odeiv.h> #include <math.h> #include "tools.h" 15 16 using namespace std; 17 18 19 20 21 22 //---double double double double globale Variablen alpha, beta; phi1_alt, phi2_alt, v_phi1_alt, v_phi2_alt; tstart, tend; akt_f0, akt_f1, akt_f2, akt_f3; 23 24 //------------------------------------------------------------------------- 25 26 int f(double t, const double x[], double dxdt[], void *params) 27 28 29 30 31 32 33 34 35 36 37 { dxdt[0] = x[1]; dxdt[1] = ( - alpha*beta*x[3]*x[3] * sin(x[0]-x[2]) - (1+alpha)*sin(x[0]) - alpha*x[1]*x[1]*sin(x[0]-x[2])*cos(x[0]-x[2]) + alpha*sin(x[2])*cos(x[2]-x[0]) ) / ( 1 + alpha*pow(sin(x[0]-x[2]),2) ); dxdt[2] = x[3]; dxdt[3] = ( -dxdt[1] * cos(x[0]-x[2]) +x[1]*x[1] * sin(x[0]-x[2]) - sin(x[2]) ) / beta; 38 39 40 return GSL_SUCCESS; } 41 42 //------------------------------------------------------------------------- 43 44 45 46 int jac(double t, const double x[], double *dfdx, double dxdt[], void *params) 47 48 49 50 { return GSL_SUCCESS; } 51 52 //------------------------------------------------------------------------- 53 54 int main( int argc, char *argv[] ) 55 56 { 57 58 59 60 61 62 63 64 //-- Definition der Variablen int n, nout, status, i; double dt, t1, tend1, t2, tend2, phi1_0, phi2_0, v_phi1_0, v_phi2_0; double rtol, atol, h1, h2; double mu = 10, x1[4], x2[4], dist0 = 1.e-6, dist1, ljapu, ljapu_1; ifstream in_stream; ofstream out_stream; 65 66 67 68 69 70 rtol = 1.e-10; atol = 1.e-10; const gsl_odeiv_step_type *T1 = gsl_odeiv_step_rkf45; gsl_odeiv_step *s1 = gsl_odeiv_step_alloc(T1, 4); gsl_odeiv_control *c1 = gsl_odeiv_control_y_new(atol, rtol); 44 71 72 73 74 75 76 1 Mechanik der Massenpunkte gsl_odeiv_evolve *e1 = gsl_odeiv_evolve_alloc(4); const gsl_odeiv_step_type *T2 = gsl_odeiv_step_rkf45; gsl_odeiv_step *s2 = gsl_odeiv_step_alloc(T2, 4); gsl_odeiv_control *c2 = gsl_odeiv_control_y_new(atol, rtol); gsl_odeiv_evolve *e2 = gsl_odeiv_evolve_alloc(4); gsl_odeiv_system sys = {f, jac, 4, &mu}; 77 78 79 80 81 82 83 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: ljapu infile outfile\n"; return 0; } 84 85 86 87 88 89 90 91 92 93 //-- Einlesen der Parameter -out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von ljapu.cpp\n"; in_stream.open(argv[1]); inout(tend,"tend"); inout(phi1_0,"phi1_0"); inout(phi2_0,"phi2_0"); inout(v_phi1_0,"v_phi1_0"); inout(v_phi2_0,"v_phi2_0"); inout(alpha,"alpha"); inout(beta,"beta"); inout(nout,"nout"); in_stream.close(); 94 95 96 97 98 //-dt = h1 = h2 = Berechnung einiger benoetigter Parameter -tend / nout; 1.e-6; 1.e-6; 99 100 101 102 103 x1[0] x1[2] x2[0] x2[2] = = = = phi1_0; phi2_0; phi1_0 + dist0; phi2_0; x1[1] x1[3] x2[1] x2[3] = = = = v_phi1_0; v_phi2_0; v_phi1_0; v_phi2_0; 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 ljapu = 0; for (n=1; n<=nout; n++) { tend1 = n * dt; tend2 = n * dt; while (t1<tend1) { status = gsl_odeiv_evolve_apply(e1,c1,s1,&sys,&t1,tend1,&h1,x1); if (status != GSL_SUCCESS) break; } h2 = 1.e-6; while (t2<tend2) { status = gsl_odeiv_evolve_apply(e2,c2,s2,&sys,&t2,tend2,&h2,x2); if (status != GSL_SUCCESS) break; } dist1 = sqrt( sqr(x1[0]-x2[0]) + sqr(x1[1]-x2[1]) + sqr(x1[2]-x2[2]) + sqr(x1[3]-x2[3]) ); ljapu_1 = log( dist1 / dist0 ) / dt; ljapu = ( (n-1)*ljapu + ljapu_1 ) / n; if (n%100==0) cout << " " << n << t1 << " " << ljapu << "\n"; out_stream << t1 << " " << ljapu << "\n"; for (i=0; i<4; i++) x2[i] = x1[i] + (x2[i]-x1[i]) * dist0 / dist1; gsl_odeiv_evolve_reset (e2); 1.6 Das Teilchen in der Schachtel 130 131 45 gsl_odeiv_step_reset (s2); } 132 133 134 135 136 137 138 gsl_odeiv_evolve_free(e1); gsl_odeiv_control_free(c1); gsl_odeiv_step_free(s1); gsl_odeiv_evolve_free(e2); gsl_odeiv_control_free(c2); gsl_odeiv_step_free(s2); 139 140 141 out_stream.close(); } Die entscheidende Neuerung ist in den Zeilen 121–124, wo zum einen der in Gleichung (1.58) eingehende Kurzzeit-Lyapunov-Exponent ljapu1 und zum anderen der gleitende Mittelwert ljapu aus allen bislang ermittelten ljapu1 berechnet wird. Dieser Mittelwert ist – wenn wir über eine hinreichend lange Zeit mitteln – gleich dem gesuchten Lyapunov-Exponenten. In Abb. 1.10 haben wir diese Berechnung eines Lyapunov-Exponenten zeitlich für eine reguläre und eine chaotische Dynamik verfolgt, wobei dieser Unterschied lediglich durch verschiedene Anfangsbedingungen erreicht wurde (d.h. wir haben hier gleichzeitig ein Beispiel für einen gemischten Phasenraum). Die Parameter waren α = 1 und β = 1, die Anfangsbedingungen φ1 (t = 0) = 0, φ2 (t = 0) = 0.4 bzw. 0.8, φ2 = 0.8 bzw. 1.6 sowie vφ2 = 0. Abb. 1.10. Der Lyapunov-Exponent als Funktion der Zeit, über die die Zeitentwicklung verfolgt wird, für reguläre (durchgezogene Kurve) und chaotische Dynamik (gestrichelte Kurve) 1.6 Das Teilchen in der Schachtel In diesem Beispiel wollen wir uns mit einem Teilchen beschäftigen, das sich wie eine Billardkugel auf einer Fläche frei bewegen kann und an den Rändern dieser Fläche reflektiert wird. Dieses an und für sich sehr einfache Beispiel wird zu unserem Erstaunen noch eine erstaunliche Vielfalt an Phänomenen zeigen! Als weiterführende Literatur in Bezug auf Billards ist [19] empfehlenswert. 46 1 Mechanik der Massenpunkte Als Umrandung wählen wir zum einen ein Rechteck, in dessen Mitte ein kreisförmiges Hindernis ist, und zum anderen ein Rechteck ohne dieses Hindernis. Da bei letzterem die Bewegung sehr viel einfacher ist, beginnen wir mit diesem. Wir legen den Ursprung in die linke, untere Ecke des Rechtecks und bezeichnen dessen Kantenlängen mit a und b (Abb. 1.11). Um die Bezeichnungen so einfach wie möglich zu halten, wählen wir den Zeitpunkt t = 0 so, dass sich unser Teilchen in diesem Moment irgendwo auf der Kante 2 in Abb. 1.11 befindet. y b 4 3 1 2 a x Abb. 1.11. Rechteck-Billard Um die Anfangsbedingungen zu komplettieren, benötigen wir noch die xKoordinate sowie die Geschwindigkeiten in x- und y-Richtung. Damit ist das Problem vollständig beschrieben und wir müssen uns der Frage zuwenden, welche Größen wir überhaupt berechnen wollen. Im Normalfall wären die xund y-Koordinaten als Funktion der Zeit eine gute Wahl, und wir könnten den Zeitverlauf dieser Koordinaten wie in dem vorigen Beispiel des Fadenpendels durch eine Aufteilung in sehr kleine Zeitschritte gewinnen (Wenn Sie Interesse an dieser Übung haben, wollen wir Sie an dieser Stelle sogar ausdrücklich dazu ermuntern, dann können Sie später diesen Algorithmus mit dem vergleichen, den wir für dieses Beispiel entwickeln werden). Die Tatsache, dass die Bewegung zwischen zwei Wandberührungen so einfach ist, dass sie analytisch beschrieben werden kann, können wir ausnützen, um einen Algorithmus zu entwerfen, der wesentlich schneller als eine Iteration mit vielen, kleinen Zeitschritten ist. Die Idee besteht darin, den Zeitpunkt und den Ort des nächsten Zusammenpralls mit einer Wand zu berechnen, dann die Geschwindigkeit entsprechend des Stoßes mit der Wand zu ändern, und diese Prozedur zu wiederholen. Man betrachtet also nur Zeitpunkte, bei denen das Teilchen gerade eine Wand trifft, weswegen man diese Betrachtungsweise stroboskopisch nennt – ein Stroboskop ist eine Lampe, die kurze Lichtblitze aussendet, und den Raum nur zu diesen Zeitpunkten ausleuchtet, während er ansonsten im Dunkeln bleibt. Um diese Vorgehensweise zu realisieren, berechnen wir zunächst, nach welcher Zeit ∆t unser Massenpunkt mit jeder einzelnen Wand zusammenstoßen würde, wenn die anderen Wände nicht da wären. Diese Zeiten können 1.6 Das Teilchen in der Schachtel 47 auch negativ sein, dann hat der Zusammenstoß bereits stattgefunden. Außerdem wird diese Zeit für eine Wand null sein, nämlich für die Wand, an der sich das Teilchen gerade befindet. Beginnen wir mit dem Zusammenstoß mit der Wand 1, die durch die Gleichung y = 0 beschrieben wird. Wenn gar keine Wand vorhanden wäre, wäre der Ort unseres Körpers gegeben durch: x(t) = x(t0 + ∆t1 ) = x(t0 ) + vx (t0 )∆t1 y(t) = y(t0 + ∆t1 ) = y(t0 ) + vy (t0 )∆t1 . (1.59) (1.60) Hierbei soll der Index 1 bei ∆t1 anzeigen, dass dies die Zeit bis zum Zusammenstoß mit Wand 1 ist – vorausgesetzt, es kommt nicht vorher zu einer Berührung mit einer anderen Wand. Wenn wir die erste dieser beiden Gleichungen gleich null setzen, erhalten wir eine Bestimmungsgleichung für ∆t und daraus x(t0 ) . (1.61) ∆t1 = − vx (t0 ) Entsprechend erhalten wir für die Wand mit x = a ∆t3 = x(t0 ) − a , vx (t0 ) (1.62) y(t0 ) , vy (t0 ) (1.63) y(t0 ) − b vy (t0 ) (1.64) für die Wand mit y = 0 ∆t4 = und ∆t2 = für die Wand mit y = b. Der kleinste positive Wert von ∆t bestimmt nun die Wand, an der die nächste Reflexion stattfindet. Ist dies eine Wand parallel zur x-Achse (beispielsweise die Wand mit y = b), so wird bei der Reflexion die y-Komponente der Geschwindigkeit invertiert, andernfalls die x-Komponente. Damit haben wir alle Bestandteile, die wir zur numerischen Bearbeitung des Problems benötigen. 1 2 3 4 5 /************************************************************************** * Name: box1.cpp * * Zweck: Simuliert die Dynamik eines Teilchens in einer quadr. Box * * Methode: stroboskopische Abbildung * **************************************************************************/ 6 7 8 9 10 11 12 #include #include #include #include #include #include <iostream> <fstream> <gsl/gsl_matrix.h> <gsl/gsl_errno.h> <gsl/gsl_odeiv.h> <math.h> 48 13 1 Mechanik der Massenpunkte #include "tools.h"; 14 15 using namespace std; 16 17 //------------------------------------------------------------------------- 18 19 int main( int argc, char *argv[] ) 20 21 { 22 23 24 25 26 27 //-- Definition der Variablen int n1, n, n_start, nmax, n_wand; double t, tend, dt, t1, t2, t3, t4, x, y, v_x, v_y, a, b, eps; ifstream in_stream; ofstream out_stream; 28 29 30 31 32 33 34 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: box1 infile outfile\n"; return 0; } 35 36 37 38 39 40 41 42 //-- Einlesen der Parameter in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von box1.cpp\n"; inout(tend,"tend"); inout(a,"a"); inout(b,"b"); inout(n_start,"n_start"); in_stream.close(); 43 44 45 46 //-- Berechnung einiger benoetigter Parameter nmax = 1000000; eps = 1.e-6; 47 48 49 50 //-- Schleife ueber die Anfangsbedingungen for (n1=1; n1<=n_start; n1++) { 51 52 53 54 55 56 57 58 59 //-- Anfangsbedingungen in_stream >> x; in_stream >> in_stream >> v_x; in_stream >> t = 0; dt = 0; n_wand = 0; out_stream << t << " " << x << " << v_x << y; v_y; " << y << " " " " << v_y << "\n"; 60 61 62 63 64 65 66 67 68 69 70 71 //-- Zeitschleife for (n=1; n<=nmax; n++) { dt = 1.e10; //-- Kollision mit Wand 1 t1 = - x / v_x; if ((t1>eps) && (t1<dt)) { dt = t1; n_wand = 1; } //-- Kollision mit Wand 2 t2 = (b-y) / v_y; if ((t2>eps) && (t2<dt)) { dt = t2; n_wand = 2; } //-- Kollision mit Wand 3 1.6 Das Teilchen in der Schachtel 49 t3 = (a-x) / v_x; if ((t3>eps) && (t3<dt)) { dt = t3; n_wand = 3; } //-- Kollision mit Wand 4 t4 = -y / v_y; if ((t4>eps) && (t4<dt)) { dt = t4; n_wand = 4; } //-- die neuen Werte von x, y und t x = x + v_x*dt; y = y + v_y*dt; t = t+dt; //-- Reflexion if ((n_wand==1) || (n_wand==3)) v_x = -v_x; else v_y = -v_y; //-if (t>tend) break; out_stream << t << " " << x << " " << y << " " << v_x << " " << v_y << "\n"; 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 } 90 91 92 } 93 94 95 out_stream.close(); } Einige Teile dieses Programms bedürfen einer näheren Erklärung. Zum einen ist da diese ominöse Variable eps, was für steht. Der Hintergrund ist, dass wir bei jedem Stoß unter den Variablen t1, t2, t3 und t4 die größte positive finden müssen – das ist dann der Zeitpunkt des nächsten Aufpralls auf eine Umrandung der Schachtel. Dafür müssen wir jeweils einen Vergleich mit null durchführen, und genau dieser Vergleich stellt ein Problem dar: eine der Zeiten muss theoretisch exakt null sein, aber durch Rundungsfehler kann sie auch einen kleinen positiven Wert annehmen. Um nun zu verhindern, dass dadurch diese Zeit statt der nächstgrößeren gewählt wird, fordern wir statt ti > 0 die etwas schärfere Bedingung ti > , wobei eine sehr kleine, positive Größe ist. Die Variable nmax gibt an, wie viele Stöße für eine Anfangsbedingung ausgerechnet werden sollen, falls vorher nicht die Endzeit tend erreicht wird. Dies stellt lediglich einen Mechanismus dar, um zu verhindern, dass die Rechenzeit durch ungeeignete Parameterwahl ins Unermessliche steigt. Wenn Sie dieses Programm laufen lassen, werden Sie feststellen, dass die Dynamik ziemlich langweilig ist. Eine typische Bahn unseres Massenpunktes finden Sie in Abb. 1.12. Abb. 1.12. Bewegung eines Teilchens auf einem rechteckigen Billardtisch 50 1 Mechanik der Massenpunkte Sie sehen, dass das Teilchen nach 2 Reflektionen immer antiparallel zur ursprünglichen Richtung läuft – eine Tatsache, die nach einigem Nachdenken selbstverständlich ist. Aus diesem Grund wollen wir nun eine Veränderung vornehmen, die diese Situation drastisch ändert – drastischer, als Sie es sich wahrscheinlich im Moment vorstellen können. Die Änderung besteht darin, dass wir in die Mitte des Rechtecks ein kreisförmiges Hindernis stellen, oder in anderen Worten, in der Mitte einen Teil der vorher rechteckigen Fläche des Billardtisches herausschneiden. Dadurch erhalten wir das so genannte Sinai-Billard: Abb. 1.13. Das so genannte Sinai-Billard, bei dem sich ein Teilchen auf einer rechteckigen Fläche bewegt, aus dem in der Mitte ein kreisförmiger Teil herausgeschnitten wurde Zunächst vereinfachen wir die Situation, indem wir uns auf ein Viertel des in Abb. 1.13 dargestellten Billards beschränken. Überzeugen Sie sich, dass wir dabei die Bahnkurve qualitativ nicht ändern, sondern diese nur in das betreffende Viertel umklappen. Zur konkreten Berechnung der Trajektorie müssen wir zunächst bestimmen, mit welcher Wand die nächste Kollision erfolgt. Das geschieht am Einfachsten, indem wir für alle Berandungen (vier gerade Wände und der Kreisabschnitt) berechnen, wann und wo diese erreicht werden. Von diesen fünf Paaren an Zeiten und Koordinaten des Berührungspunktes ist dann dasjenige relevant, bei dem die berechnete Zeit den kleinsten positiven Wert annimmt. Diese Vorgehensweise bereitet keine sonderlichen Schwierigkeiten und Sie finden das entsprechende Programm auf der beiliegenden CD-ROM als sinai1.cpp. Die typischen Ergebnisse sehen völlig anders aus als beim RechteckBillard. Während dort eine Bahnkurve einen sehr übersichtlichen, geordneten Eindruck machte (Abb. 1.12) – die einzelnen Abschnitte waren immer parallel oder senkrecht zueinander – läuft die Bahn beim Sinai-Billard (Abb. 1.14) kreuz und quer. Dieser visuelle Eindruck legt nahe, dass die Dynamik chaotisch sein könnte – um dies jedoch zu verifizieren, stehen uns nun zwei Möglichkeiten zur Verfügung: – – Bestimmung des Poincaré-Plots Berechnung des Lyapunov-Exponenten. 1.6 Das Teilchen in der Schachtel 51 Abb. 1.14. Typische Bahn im Sinai-Billard, wobei diese auf das rechte obere Viertel projeziert wurde Für die Berechnung des Lyapunov-Exponenten verweisen wir auf die Übungsaufgabe 1.5. Den Poincaré-Plot hingegen werden wir hier im Text etwas näher unter die Lupe nehmen. Dazu wiederholen wir zunächst an diesem Beispiel die Dimensionsbetrachtung, die wir bereits in Abschn. 1.4 angestellt haben: Der gesamte Phasenraum ist vierdimensional (zwei Orts- und zwei Impulsvariablen). Da die Energie jedoch eine Erhaltungsgröße ist – was in diesem Fall gleichbedeutend ist mit der Tatsache, dass der Betrag der Geschwindigkeit konstant ist, findet die Dynamik auf einer dreidimensionalen Hyperfläche im Phasenraum statt. Indem wir uns nun auf Durchstoßpunkte durch eine weitere solche Hyperfläche, die wir frei wählen können, beschränken, verbleiben wir mit einem Gebilde in einem zweidimensionalen Raum. Am Einfachsten realisieren wir das, indem wir die Punkte nehmen, an denen unser Teilchen auf eine bestimmte der fünf Berandungen trifft – diese Auftreffpunkte berechnen wir ohnehin – und x, y, vx und vy nach jedem Stoß abspeichern (eigentlich benötigen wir nur zwei Werte, die die Lage des Auftreffpunktes und die Richtung der Geschwindigkeit festlegen; die anderen beiden Werte dienen lediglich der Kontrolle). Wir könnten natürlich auch die entsprechenden vier Werte vor dem Stoß nehmen, wir dürfen aber nicht vor und nach dem Stoß mischen. Das Ergebnis (Abb. 1.15) zeigt, dass die Dynamik nicht integrabel ist: Abb. 1.15. Poincaré-Plot beim Sinai-Billard 52 1 Mechanik der Massenpunkte 1.7 Hamilton-Formalismus In diesem Abschnitt wollen wir den Hamilton-Formalismus kurz skizzieren, da wir im nächsten Abschnitt die Hamiltonschen Bewegungsgleichungen benötigen werden. Für eine ausführliche Herleitung verweisen wir auf [20] oder [21]. Wir gehen von einem System aus, dessen Dynamik wir in Form der Lagrangefunktion L(qi , q̇i ) kennen. Das impliziert, dass wir einen Satz von generalisierten Koordinaten qi haben, deren zeitlichen Ableitungen q̇i die generalisierten Geschwindigkeiten sind. Der Übergang zur Hamiltonfunktion erfolgt durch eine Legendre-Transformation. Die Rolle der Lagrangefunktion L übernimmt die Hamiltonfunktion H(qi , pi ) = pi q̇i − L(qi , q̇i ) . (1.65) i Die darin auftretenden neuen Variablen pi sind die zu qi kanonisch konjugierten Impulse: ∂L pi = , (1.66) ∂ q̇i die die qi als unabhängige Variablen ersetzen. Die Bewegungsgleichungen sind dann die Hamiltonschen Gleichungen: q̇i = ∂H ∂pi ṗi = − ∂H . ∂qi (1.67) Abschließend wollen wir noch erklären, welche anschauliche Bedeutung die Hamiltonfunktion hat. Dazu betrachten wir ein System ohne explizite Zeitabhängigkeit: ∂L =0 (1.68) ∂t und berechnen die zeitliche Änderung der Hamiltonfunktion H: ∂H ∂H d ∂H q̇i + ṗi + H(qi , pi ) = dt ∂q ∂p ∂t i i i i =0. (1.69) (1.70) Die Hamiltonfunktion ist also eine Erhaltungsgröße. Man kann zeigen, dass es sich im Fall konservativer Kräfte, wenn sich also die Kräfte durch ein Potential darstellen lassen, um die Energie handelt [22]. In diesem Fall spricht man von skleronomen Zwangsbedingungen. 1.8 Attraktoren in dissipativen Systemen Bislang haben wir uns ausschließlich mit sogenannten Hamiltonschen Systemen beschäftigt – darunter versteht man Systeme, bei denen der Energieerhaltungssatz gilt. Wenn man jedoch Situationen betrachtet, bei denen von 1.8 Attraktoren in dissipativen Systemen 53 außen Energie zugeführt bzw. nach außen Energie abgeführt wird, bleibt die Gesamtenergie im System selbst nicht konstant. Eine ähnliche Situation erhalten wir, wenn zwar keine Energie zu- bzw. abgeführt wird, aber Energie in eine nicht betrachtete Energieform umgewandelt wird. Dies ist zum Beispiel bei einem System mit Reibung der Fall, solange wir die thermische Energie in unserer (rein mechanischen) Betrachtung nicht berücksichtigen. Um diese qualitativen Aussagen auf eine exakte theoretische Basis zu stellen, betrachten wir die Zeitentwicklung von (kleinen) Phasenraumvolumina ∆V in einem 2N -dimensionalen Phasenraum der von N Orts- und N Impulskoordinaten aufgespannt wird: Abb. 1.16. Entwicklung eines Phasenraumelements, das durch die Punkte 0, 1 und 2 aufgespannt wird. Zur besseren Übersichtlichkeit sind nur die Ortsvektoren des Punktes 0 beschriftet In Abb. 1.16 sehen wir ein solches Volumen, das von den Punkten 0, 1 und 2 aufgespannt wird. Zu einem beliebigen Zeitpunkt τ sind die zugehörigen Phasenraumvektoren x0 (τ ), x1 (τ ) und x2 (τ ). Außerdem führen wir die Differenzvektoren ∆xn = xn − x0 (1.71) ein. Der Einfachheit halber wollen wir annehmen, dass diese Differenzvektoren an den Koordinatenachsen ausgerichtet sind: ∆xn = (0, . . . , ∆xn , . . . , 0, 0, . . . , 0 , . . . , 0) für n ≤ N (1.72) ∆xn = (0, . . . , 0, . . . , 0, 0, . . . , ∆pn , . . . , 0) für n > N , (1.73) so dass sich das Phasenraumvolumen ∆V einfach durch Multiplikation ergibt: ∆V = N ∆xn ∆pn . (1.74) n=1 Die Zeitableitung dieses Volumenelements erhalten wir gemäß der Produktregel 54 1 Mechanik der Massenpunkte d∆V = ∆V dt N N 1 d∆xn 1 d∆pn + x dt p dt n=1 n n=1 n . (1.75) Wenn die Zeitentwicklung durch die Hamiltonschen Bewegungsgleichungen d∆xn d∆Hn = dt dpn d∆Hn d∆pn =− dt dxn (1.76) mit der Abkürzung ∆Hn = H(x + ∆xn ) − H(x) (1.77) gegeben ist, erhalten wir für diese Volumenänderung für kleine ∆x, ∆p den Wert null, da dH dH 1 dxn 1 d 1 dpn 1 d ∆xn − ∆pn = 0 + = xn dt pn dt xn dpn dxn pn dxn dpn (1.78) ist. Diese Erhaltung von Phasenraumvolumina in Hamiltonschen Systemen wird als Liouvillescher Satz bezeichnet. Nimmt die Zeitableitung von Phasenraumvolumina also einen Wert verschieden von null an, so ist die Dynamik mit Sicherheit nicht Hamiltonsch, was nur möglich ist, wenn dem System Energie zugeführt bzw. abgeführt wird. Ein System, bei dem diese Zeitableitung negativ ist, wird dissipativ genannt. Wir sehen also, dass ein Phasenraumvolumen in einem dissipativen System immer weiter schrumpft, bis es im Grenzfall t → ∞ verschwindet. Im Einfachsten Fall heißt das, dass alle Trajektorien auf einen einzigen stabilen Punkt laufen, in dem das System dann zur Ruhe kommt. Selbstverständlich gibt es auch die Möglichkeit, dass mehrere solcher Fixpunkte existieren – in diesem Fall hängt es von den Anfangsbedingungen ab, in welchen der Fixpunkte die Trajektorie hineinläuft. Bei einem System, bei dem die einzige Orts- oder Impulsvariable nicht in die Bewegungsgleichungen eingeht und der Phasenraum daher quasi eindimensional ist, ist dies die einzige Möglichkeit. Eine weitere Möglichkeit kommt hinzu, wenn ein zweidimensionaler Phasenraum vorliegt: der Grenzzyklus, darunter versteht man eine Trajektorie, der sich alle benachbarten Trajektorien immer mehr annähern. Das heißt, dass sich das System nach einem transienten Einschwingvorgang, in dem die Trajektorie in den Grenzzyklus läuft, periodisch verhält. Fixpunkt und Grenzzyklus sind zwei spezielle Formen von Attraktoren, von denen es in höherdimensionalen Phasenräumen noch weitere Ausbildungen gibt. Ein Attraktor ist eine Untermenge des Phasenraumes mit folgenden Eigenschaften: – Der Attraktor ist bezüglich der Dynamik des Systems abgeschlossen, d.h. startet man auf einem beliebigen Punkt des Attraktors, verläuft die gesamte Dynamik auf dem Attraktor und verlässt diesen nicht. 1.9 Das periodisch angetriebene Pendel – 55 Startet man an einem Punkt genügend nahe am aber nicht auf dem Attraktor so nähert sich die Trajektorie für t → ∞ dem Attraktor immer mehr an. Insbesondere kann die Dimension eines solchen Attraktors auch nicht ganzzahlige Werte annehmen – in diesem Fall spricht man von einem seltsamen Attraktor. 1.9 Das periodisch angetriebene Pendel In diesem Abschnitt wollen wir ein Beispiel besprechen, das in manchen Bereichen des Phasenraums dissipativ ist, während in anderen das Phasenraumvolumen expandiert. Als Ausgangspunkt wählen wir das gedämpfte Fadenpendel mit der Bewegungsgleichung d2 d (1.79) x + β x + sin x = 0 . dt2 dt Das Programm getr pendel1.cpp, das diese Bewegungsgleichung löst, finden Sie auf der CD-ROM. Neu gegenüber der Bewegungsgleichung (1.8) ist lediglich der Dämpfungsterm β dx/dt, der zur Folge hat, dass Phasenraumvolumina nicht mehr erhalten bleiben, sondern immer kleiner werden. Diesen rein dissipativen Charakter der Bewegungsgleichung ändern wir durch das Hinzufügen einer periodischen, treibenden Kraft: d2 d (1.80) x + β x + sin x = α cos(Ωt) . dt2 dt Durch Hinzufügen dieses explizit zeitabhängigen Terms verlassen wir zunächst den Rahmen der zeitunabhängigen Dynamiken, auf die wir uns bisher beschränkt haben. Wir können dieses Problem jedoch elegant lösen, indem wir den zweidimensionalen Phasenraum des ursprünglichen Problems formal um eine Dimension erweitern. Die zusätzliche Variable nennen wir f und ihre Bewegungsgleichung soll durch d f =Ω (1.81) dt gegeben sein. Die Lösung dieser Differentialgleichung zur Anfangsbedingung f (t = 0) = 0 ist trivial: f = Ωt (1.82) und erlaubt die obigen Differentialgleichung (1.80) in das folgende zeitunabhängige Differentialgleichungssystem umzuwandeln: d x=v dt d v = −βv − sin x + α cos f dt d f = Ω. dt (1.83) (1.84) (1.85) 56 1 Mechanik der Massenpunkte Beachten Sie bitte, dass sowohl x als auch f den Charakter eines Winkels haben und daher modulo 2π zu betrachten sind. Eine direkte Konsequenz ist, dass die zusätzlich eingeführte Variable f periodisch ist und deren Periodendauer durch T0 = 2π/Ω gegeben ist. Wenn wir später nach der Periodendauer des Gesamtsystems fragen, kommen hierfür nur Vielfache dieses Wertes in Frage. Da wir nun sehen, dass die Dimension des Phasenraums größer als zwei ist und das System außerdem dissipativ ist, wissen wir aus den Überlegungen des vorigen Abschnitts, dass mindestens ein Attraktor existieren muss, wissen aber nicht, ob dieser vom Typ eines Fixpunkts, eines Grenzzyklus oder eines seltsamen Attraktors ist. Die erste Möglichkeit können wir jedoch verhältnismäßig einfach ausschließen: Im Grenzpunkt müsste die Zeitableitung aller drei Koordinaten des Phasenraums null sein – was jedoch (1.85) widerspricht. Es bleiben die Möglichkeiten eines Grenzzyklusses und eines seltsamen Attraktors. Um dies zu entscheiden, konstruieren wir das Programm zur Lösung der Bewegungsgleichung so, dass es eine eventuelle Periodizität erkennt und die zugehörige Periodendauer ausgibt: 1 2 3 4 5 6 7 8 /************************************************************************** * Name: getr_pendel2.cpp * * Zweck: Berechnet die Periodendauer eines getriebenen, gedaempften * * Fadenpendels * * Gleichung: (dˆ2/d tˆ2) phi + beta * (d/dt) phi + sin(phi) = * * alpha * cos(Omega*t) * * verwendete Bibiliothek: GSL * **************************************************************************/ 9 10 11 12 13 14 15 16 17 #include #include #include #include #include #include #include #include <iostream> <fstream> <gsl/gsl_matrix.h> <gsl/gsl_errno.h> <gsl/gsl_odeiv.h> <gsl/gsl_roots.h> <math.h> "tools.h" 18 19 using namespace std; 20 21 22 23 //---- globale Variablen double alpha, beta, Omega; double rtoleranz, atoleranz, h; 24 25 //------------------------------------------------------------------------- 26 27 int f(double t, const double x[], double dxdt[], void *params) 28 29 30 31 { dxdt[0] = x[1]; dxdt[1] = -beta*x[1]-sin(x[0])+alpha*cos(Omega*t); 32 33 34 return GSL_SUCCESS; } 35 36 //------------------------------------------------------------------------- 1.9 Das periodisch angetriebene Pendel 57 37 38 39 int jac(double t, const double x[], double *dfdx, double dfdt[], void *params) 40 41 42 43 { return GSL_SUCCESS; } 44 45 //------------------------------------------------------------------------- 46 47 int check_period(double x0[],double x1[], double eps, int *n) 48 49 50 { int n1, n2, n_min, found; 51 52 53 54 55 56 57 58 59 60 61 62 63 64 found = 0; n_min = 1000; for (n1=0; n1<=*n; n1++) { for (n2=1; n2<=*n-n1; n2++) { if ((fabs(x0[n1]-x0[n1+n2])<eps) && (fabs(x1[n1]-x1[n1+n2])<eps) && (n2<n_min)) {n_min = n2; found = 1; break;} } if (found == 1) break; } *n = n_min; 65 66 67 return found; } 68 69 //------------------------------------------------------------------------- 70 71 int main( int argc, char *argv[] ) 72 73 { 74 75 76 77 78 79 80 81 82 //-- Definition der Variablen int n, n1, n_alpha, n_alpha_max, status, nmax, found, n_assumed; double t, tend, dt, t_start, T_0, T_period; double x[2], x_start[2], delta[2], x0[10000], x1[10000]; double alpha_min, alpha_max, dalpha, eps; double mu = 10; ifstream in_stream; ofstream out_stream; 83 84 85 86 87 88 89 //-- Fehlermeldung, wenn Input- oder Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: getr_pendel2 infile outfile\n"; exit(0); } 90 91 92 93 94 95 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von getr_pendel2.cpp\n"; inout(dt,"dt"); inout(nmax,"nmax"); 58 96 97 98 99 100 101 1 Mechanik der Massenpunkte inout(alpha_min,"alpha_min"); inout(n_alpha_max,"n_alpha_max"); inout(Omega,"Omega"); inout(rtoleranz,"rtoleranz"); inout(x_start[1],"x_start[1]"); in_stream.close(); inout(alpha_max,"alpha_max"); inout(beta,"beta"); inout(atoleranz,"atoleranz"); inout(x_start[0],"x_start[0]"); 102 103 104 105 106 107 //-- Berechnung einiger benoetigter Parameter -eps = 1.e-7; h = 1.e-6; dalpha = (alpha_max-alpha_min) / n_alpha_max; T_0 = 2 * pi / Omega; 108 109 110 111 112 113 114 out_stream.precision(7); //-- Schleife ueber alpha for (n_alpha=0; n_alpha<=n_alpha_max; n_alpha++) { alpha = alpha_min + n_alpha * dalpha; found = 0; 115 116 117 118 119 //-- Anfangsbedingungen t = 0; x[0] = x_start[0]; x[1] = x_start[1]; 120 121 122 123 124 125 126 //-- Initialisierung der GSL-Routine const gsl_odeiv_step_type *T = gsl_odeiv_step_rk8pd; gsl_odeiv_step *s = gsl_odeiv_step_alloc(T, 2); gsl_odeiv_control *c = gsl_odeiv_control_y_new(atoleranz, rtoleranz); gsl_odeiv_evolve *e = gsl_odeiv_evolve_alloc(2); gsl_odeiv_system sys = {f, jac, 2, &mu}; 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 for (n=1; n<=nmax; n++) { //-- Zeitentwicklung bis zum Ende der n-ten Periode tend = n*T_0; while (t<tend) { status = gsl_odeiv_evolve_apply(e, c, s, &sys, &t, tend, &h, x); if (status != GSL_SUCCESS) break; } n1 = int(fabs(x[0] / 2 / pi) + 0.5); x[0] = x[0] - sign(x[0]) * 2*n1*pi; x0[n] = x[0]; x1[n] = x[1]; for (n1=n-1; n1>0; n1--) { delta[0] = x[0] - x0[n1]; delta[1] = x[1] - x1[n1]; if (fabs(delta[0]) > pi-eps) delta[0] = fabs(delta[0]) - pi; if ((fabs(delta[0])<eps) && (fabs(delta[1])<eps)) {found=1; break;} } if (found==1) break; } 150 151 152 153 154 n_assumed = n-n1; if ((found==1) && (n_assumed<=64)) { x0[0] = x[0]; x1[0] = x[1]; 1.9 Das periodisch angetriebene Pendel 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 59 t_start = t; for (n=1; n<=5*n_assumed; n++) { tend = t_start + n*T_0; while (t<tend) { int status = gsl_odeiv_evolve_apply(e, c, s, &sys, &t, tend, &h, x); if (status != GSL_SUCCESS) break; } x0[n] = x[0]; x1[n] = x[1]; } n--; if ( check_period(x0, x1, 5*eps, &n) ) { T_period = n*T_0; out_stream << alpha << " " << T_period << " " << 1/T_period << "\n"; } } 174 175 176 177 178 gsl_odeiv_evolve_free(e); gsl_odeiv_control_free(c); gsl_odeiv_step_free(s); } 179 180 181 out_stream.close(); } In den Zeilen 25 bis 35 finden wir die Funktion f, die zur Lösung der Bewegungsgleichung benötigt wird, wobei wir auf die einfachere Form eines zweidimensionalen Phasenraumes zurückgreifen und dafür die explizite Zeitabhängigkeit der Differentialgleichung in Kauf nehmen. Nach dem Einlesen der Parameter und der Berechnung einiger benötigter Hilfsvariablen finden wir in den Zeilen 111 bis 178 eine Schleife, mit der wir einen der Parameter (hier α) variieren können. Für jeden Wert von α bestimmen wir die Lösung der Bewegungsgleichung zu den Zeitpunkten, die Vielfache von T0 sind, und speichern die Werte von x und v in x0[n] und x1[n]. Nach jedem solchem Schritt vergleichen wir in den Zeilen 140 bis 147 diese neuen Werte mit denen zu den vorangegangen Zeitpunkten und entscheiden so, ob wahrscheinlich Periodizität vorliegt und – wenn dies erfüllt ist – welche Periodendauer wir vermuten. Die so ermittelte Periodendauer kann jedoch noch falsch sein – insbesondere könnte die tatsächliche Periodendauer ein Bruchteil des ermittelten Wertes sein, wie wir uns an dem folgenden Beispiel klarmachen können: nehmen wir an, die gesuchte Periodizität ist T0 . Wenn wir nun die Dynamik an einem beliebigen Punkt starten, nähert sich die Trajektorie dem gesuchten Grenzzyklus und nähert sich ihm irgendwann soweit, dass die Prüfung in Zeile 145 zu dem Schluss kommt, dass Periodizität vorliegt. Dies kann bedeuten, dass die Werte von x und v ihren jeweiligen Vorgängern so nahe kommen, dass der Abstand unter den Schwellwert fällt – es kann aber auch sein, dass dieser Abstand geringfügig größer ist, aber der Abstand zum Zustand vor 2T0 klein genug war. In diesem Fall scheint die Periode – fälschlicherweise – 2T0 sein. 60 1 Mechanik der Massenpunkte Dieser erste Schritt garantiert also nur, dass wir uns dem Grenzzyklus hinreichend genähert haben und wahrscheinlich keine Periodendauer größer als der gefundene Wert vorliegt. Wir müssen also an diesen ersten Schritt noch eine Prüfung anschließen, welche Periodendauer die wahrscheinlich richtige ist. Zu diesem Zweck verfolgen wir die Trajektorie weiter – in unserem Fall bis zum fünffachen der vermuteten Periodendauer und ermitteln aus den gefundenen Werten von x und v die Periodendauer T . Diesen letzten Schritt übernimmt das Unterprogramm check period in den Zeilen 47 bis 67. Schließlich schreiben wir, wenn wir für einen Wert von α eine Periodendauer gefunden haben, diese sowie ihren Kehrwert in Zeile 171 in die Ausgabedatei. Nach so vielen Details zum Programm getr pendel2.cpp können wir uns nun den so gewonnenen Ergebnissen widmen: Abb. 1.17. Periodenverdopplung beim getriebenen gedämpften Pendel. Die Stärke α der treibenden Kraft wird variiert, während die Parameter Ω = 2/3 und β = 0.5 konstant gehalten werden Abbildung 1.17 zeigt die gefundene Periodendauer sowie ihren Kehrwert als Funktion von α, wobei die anderen beiden Parameter Ω und β festgehalten wurden. Bis zu α ≈ 1.0625 liegt die für Ω = 2/3 kürzestmögliche Periodendauer 2π/ω ≈ 9.425 vor. Dann springt die Periodendauer plötzlich auf den doppelten Wert, auf dem sie bis etwa α ≈ 1.078 bleibt. Bei diesem Wert verdoppelt sich die Periodendauer ein weiteres Mal, um sich dann bereits bei α ≈ 1.082 wieder zu verdoppeln. Diese Periodenverdopplungen treten dann immer wieder auf und folgen zunehmend dichter aufeinander. Schließlich wird die Periodendauer unendlich – das heißt mit anderen Worten, dass die Bewegung nicht mehr periodisch ist. Wie Sie sehen, ist eine einigermaßen zuverlässige Bestimmung der Periodendauer gar nicht so einfach, wie man dies vielleicht zunächst vermuten sollte. Es stellt sich also die Frage, ob man auf einfacherem Weg die selbe Information über das physikalische Verhalten hätte bekommen können. Diese Frage führt uns zum sogenannten Attraktordiagramm. Für dieses sammeln wir für jeden Wert des Kontrollparameters α die Werte einer beliebigen dynamischen Variablen zu den Zeiten nT0 , (n + 1)T0 , . . . (n + N )T0 . Dabei wählen wir die Startzeit nT0 groß genug um sicherzugehen, dass sich unsere 1.10 Der schiefe Wurf mit Luftwiderstand 61 Trajektorie nahe genug an den Attraktor angenähert hat. Wenn wir nun all diese Werte der dynamischen Variable in Abhängigkeit des Kontrollparameters (hier α) auftragen, erhalten wir das Attraktordiagramm in Abb. 1.18 (das Programm sollte keinerlei Probleme darstellen und Sie finden es auf der CD als getr pendel3.cpp). Abb. 1.18. Attraktordiagramm des getriebenen gedämpften Pendels. Auch in diesem Diagramm sehen wir die Kaskade der Periodenverdopplungen bis hin zum Chaos. Der Parameter α wird variiert, während die Parameter Ω = 2/3 und β = 0.5 konstant gehalten werden Vergleichen Sie dieses Diagramm mit Abb. 1.17 so sehen Sie, dass sich die erste Periodenverdopplung im Attraktordiagramm durch eine Gabelung, eine Bifurkation manifestiert. Bei der zweiten Periodenverdopplung gabeln sich beide so entstandenen Linien noch einmal und so weiter bis im chaotischen Bereich keine einzelnen Linien mehr ausgemacht werden können, sondern ein Band entsteht. Mit dem Wissen aus den Abbildungen 1.17 und 1.18 können wir nun einige interessante Werte von α auswählen und die Trajektorien des Grenzzyklus für diese Werte berechnen. An dieser Stelle nun kommen wir auf das Programm getr pendel1.cpp zurück. Die Werte von Ω = 2/3 und β = 0.5 übernehmen wir und wählen für α die Werte 1.06 (vor der ersten Periodenverdopplung), 1.075 (nach der ersten Periodenverdopplung), 1.082 (nach der zweiten Periodenverdopplung) und 1.085 (chaotischer Bereich). Die Ergebnisse sehen Sie in Abb. 1.19. 1.10 Der schiefe Wurf mit Luftwiderstand Beim schiefen Wurf handelt es sich um die Bewegung eines Körpers im – homogen genäherten – Schwerefeld der Erde. Unter Vernachlässigung der Luftreibung sind die Bewegungsgleichungen schnell aufgestellt: in x-Richtung haben wir eine freie Bewegung d2 x(t) = 0 dt2 (1.86) 62 1 Mechanik der Massenpunkte Abb. 1.19. Trajektorien des getriebenen gedämpften Pendels für einige ausgewählte Werte von α für feste Parameter Ω = 2/3 und β = 0.5. Die Werte von α sind 1.06 (a), 1.075 (b), 1.082 (c) und 1.085 (d). Die Periodenverdopplung von (a) nach (b) manifestiert sich in dieser Darstellung in einer Aufspaltung der Trajektorie von einer Einfachschleife in eine Doppelschleife. Entsprechend erhalten wir nach einer weiteren Periodenverdopplung (c) eine Vierfachschleife. Im chaotischen Fall (d) schließlich ist die Trajektorie flächenfüllend und in z-Richtung den freien Fall d2 z(t) = −g . dt2 (1.87) Hierbei haben wir unser Koordinatensystem so gewählt, dass in y-Richtung keine Bewegung stattfindet, so dass wir diese Koordinate nicht zu berücksichtigen brauchen. Diese Bewegungsgleichungen sind einfach zu lösen: x(t) = x0 + vx0 t (1.88) 1 z(t) = z0 + vz0 t − gt2 . 2 (1.89) Nun müssen wir in die Bewegungsgleichungen (1.86) und (1.87) die Luftreibung einbauen, von der wir annehmen wollen, dass sie proportional zum Quadrat der Geschwindigkeit des Körpers ist (eine Annahme, die für die nicht übermäßig schnelle Bewegung in Gasen sehr gut erfüllt ist). Diese Reibungskraft ist natürlich der Geschwindigkeit entgegengerichtet, so dass wir als modifizierte Bewegungsgleichungen d2 x(t) = −γ vx2 (t) + vz2 (t)vx 2 dt d2 z(t) = −g − γ vx2 (t) + vz2 (t)vz 2 dt (1.90) (1.91) 1.10 Der schiefe Wurf mit Luftwiderstand 63 erhalten. Wie sie sehen, koppelt der Luftwiderstand die bisher unabhängigen Bewegungsgleichungen von x(t) und z(t). Am Einfachsten bekommen wir das benötigte Programm, indem wir unser bestehendes Programm faden1.cpp nach wurf.cpp kopieren und dann die nötigen Änderungen vornehmen. So bekommen wir 1 2 3 4 5 6 7 /************************************************************************** * Name: wurf.cpp * * Zweck: Simuliert den schiefen Wurf mit Luftwiderstand * * Gleichung: (dˆ2/d tˆ2) x = - eta sqrt(v_xˆ2+v_zˆ2) v_x * * (dˆ2/d tˆ2) z = -g - eta sqrt(v_xˆ2+v_zˆ2) v_x * * verwendete Bibiliothek: GSL * **************************************************************************/ 8 9 10 11 12 13 14 15 #include #include #include #include #include #include #include <iostream> <fstream> <gsl/gsl_matrix.h> <gsl/gsl_errno.h> <gsl/gsl_odeiv.h> <math.h> "tools.h" 16 17 using namespace std; 18 19 20 //---- globale Variablen double g, eta; 21 22 //------------------------------------------------------------------------- 23 24 int f(double t, const double x[], double dxdt[], void *params) 25 26 27 28 29 30 { dxdt[0] dxdt[1] dxdt[2] dxdt[3] = x[2]; = x[3]; = - eta * sqrt(x[2]*x[2]+x[3]*x[3]) * x[2]; = - g - eta * sqrt(x[2]*x[2]+x[3]*x[3]) * x[3]; 31 32 33 return GSL_SUCCESS; } 34 35 //------------------------------------------------------------------------- 36 37 38 int jac(double t, const double x[], double *dfdx, double dxdt[], void *params) 39 40 41 42 { return GSL_SUCCESS; } 43 44 //------------------------------------------------------------------------- 45 46 main( int argc, char *argv[] ) 47 48 { 49 50 51 52 //-- Definition der Variablen int n, n1, n_start, n_out, max_fct, iaux, error, normal; double t, tend, dt, t1, t2, rtol, atol, h, x[4]; 64 53 54 55 1 Mechanik der Massenpunkte double mu = 10; ifstream in_stream; ofstream out_stream; 56 57 58 59 60 61 62 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: wurf infile outfile\n"; exit(1); } 63 64 65 66 67 68 69 70 71 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von wurf1.cpp\n"; inout(tend,"tend"); inout(n_out,"n_out"); inout(g,"g"); inout(eta,"eta"); inout(rtol,"rtol"); inout(atol,"atol"); inout(n_start,"n_start"); 72 73 74 75 76 77 //-- Berechnung einiger benoetigter Parameter -dt = tend / n_out; rtol = 1.e-8; atol = 1.e-8; h = 1.e-6; 78 79 80 81 //-- Schleife ueber die Anfangsbedingungen for (n1=1; n1<=n_start; n1++) { 82 83 84 85 86 //-- Anfangsbedingungen in_stream >> x[0]; in_stream >> x[1]; in_stream >> x[2]; in_stream >> x[3]; t = 0; 87 88 89 out_stream << t << " " << x[0] << " " << x[1] << " " << x[2] << " " << x[3] << "\n"; 90 91 92 93 94 95 96 //-- Initialisierung der GSL-Routine const gsl_odeiv_step_type *T = gsl_odeiv_step_rkf45; gsl_odeiv_step *s = gsl_odeiv_step_alloc(T, 4); gsl_odeiv_control *c = gsl_odeiv_control_y_new(atol, rtol); gsl_odeiv_evolve *e = gsl_odeiv_evolve_alloc(4); gsl_odeiv_system sys = {f, jac, 4, &mu}; 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 //-- Zeitschleife -t = 0; for (n=1; n<=n_out; n++) { t1 = n * dt; while (t<t1) { int status = gsl_odeiv_evolve_apply(e, c, s, &sys, &t, t1, &h, x); if (status != GSL_SUCCESS) break; } out_stream << t << " " << x[0] << " " << x[1] << " " << x[2] << " " << x[3] << "\n"; } gsl_odeiv_evolve_free(e); 1.10 Der schiefe Wurf mit Luftwiderstand 112 113 114 65 gsl_odeiv_control_free(c); gsl_odeiv_step_free(s); } 115 116 117 118 out_stream.close(); in_stream.close(); } Zum Überprüfen können wir zwei Grenzfälle betrachten: zum einen den schiefen Wurf ohne Luftreibung, zum anderen die vertikale Bewegung des Körpers (vx (t) = 0) mit Luftreibung, aber ohne Schwerefeld. Auf diese Weise testen wir sowohl die richtige Implementation des Schwerefeldes als auch der Luftreibung. Für den freien Fall haben wir die Lösung bereits angegeben, es fehlt uns also nur noch der Fall der eindimensionalen Bewegung mit vx (t) = 0. Die Differentialgleichung für die Geschwindigkeit vz (t) ist d vz (t) = −γ|vz (t)|vz (t) . dt (1.92) Ohne Beschränkung der Allgemeinheit können wir vz (0) positiv wählen. Unsere Anschauung sagt uns, dass in diesem Fall die Geschwindigkeit allein durch Reibung nie negative Werte annimmt (eine Tatsache, die wir anschließend am Ergebnis verifizieren können), so dass wir die einfachere Differentialgleichung d vz (t) = −γvz2 (t) . (1.93) dt zu lösen haben. Diese Differentialgleichung ist separierbar, d.h. wir können sie in die folgende Form bringen: − 1 dvz = dt . γvz2 Integration auf beiden Seiten führt auf die Gleichung 1 1 1 − =t. 3γ vz (t)3 vz (0)3 (1.94) (1.95) Diese Gleichung können wir ohne Problem nach vz (t) auflösen und erhalten 1/3 vz (t) = vz (0)3 − 3γt . (1.96) Der letzte Punkt schließlich ist die Visualisierung der Ergebnisse, wobei es dabei darauf ankommt, unter welchem Aspekt man den schiefen Wurf betrachten möchte. Sehr lehrreich sind die Trajektorien bei gleichen Anfangsbedingungen aber verschiedenen Werten des Luftwiderstandskoeffizienten γ, wie in Abb. 1.20 dargestellt. In allen drei dargestellten Fällen beginnt das Teilchen seine Bahn im Ursprung. Seine Geschwindigkeit ist sehr hoch (etwa 69 m/s oder knapp 250 66 1 Mechanik der Massenpunkte Abb. 1.20. Bahnkurve beim schiefen Wurf für verschiedene Luftwiderstandswerte, wobei alle übrigen Parameter – Abwurfwinkel und -geschwindigkeit festgehalten wurden km/h und bildet einen Winkel von 45 Grad zum Horizont. Die Werte für den Luftwiderstand γ waren null (durchgezogene Kurve), 10−4 1/m (gestrichelte Linie), 10−3 1/m (gepunktet-gestrichelte Linie) sowie 10−2 1/m (gepunktete Linie). Vergleichen Sie die verschiedenen Flugbahnen qualitativ mit Ihren Erfahrungen: wie fliegt ein Wattebausch, wie fliegt ein Stein? Übungen 1.1 Numerische Lösung von Differentialgleichungen Lösen Sie direkt durch Anwendung der Entwicklung (1.14), (1.15), d.h. ohne Verwendung einer Numerik-Bibliothek, das Kepler-Problem mit N Teilchen, wobei Sie sowohl die Ordnung m der Entwicklung als auch die Zahl N der beteiligten Körper variieren. Was stellen Sie insbesondere für große N und große m fest? 1.2 Dreieckspotential Lösen Sie die Bewegungsgleichung eines Teilchens im Potential (x − [x]) für [x] gerade V (x) = V0 × . (1 − x + [x]) für [x] ungerade (1.97) Dabei ist die Gaußklammer [x] definiert als die größte ganze Zahl, die kleiner gleich x ist, also z.B. [2] = 2 [1.2] = 1 (1.98) (1.99) [−1.2] = −2 . (1.100) Übungen 67 Dieses Potential hat die folgende Form: Abb. 1.21. Dreieckspotential Versuchen Sie für die Lösung der Bewegungsgleichung verschiedene Bibliotheksroutinen, insbesondere solche, die auf Verfahren niedriger bzw. höherer Ordnung beruhen. 1.3 Stadionbillard Wir betrachten ein Teilchen in einer stadionförmigen Umrandung (Abb. 1.22): Abb. 1.22. Stadionbillard Schreiben Sie ein Programm, das – analog zum Programm box1.cpp – die Bewegung eines Massenpunkts in diesem sogenannten Stadionbillard simuliert. Ausgehend von diesem Programm können Sie nun einen PoincarèSchnitt oder ein Attraktordiagramm anfertigen und dann für ausgewählte Parameter den Lyapunov-Exponenten berechnen. 1.4 Dreikörperproblem Die Entdeckung des klassischen Chaos durch Poincaré geschah im Rahmen eines Wettbewerbs zur Berechenbarkeit von Planetenbewegungen. Im Rahmen seiner Abhandlung konnte Poincaré zeigen, dass bereits bei drei Köpern unter dem Einfluss ihrer gegenseitigen Anziehung eine Voraussage über lange Zeiten nicht möglich ist. Um eine solche Situation zu untersuchen, wählen 68 1 Mechanik der Massenpunkte Sie bitte drei gleiche Massen m an den Ecken eines gleichschenkligen Dreiecks und spielen verschiedene Anfangsgeschwindigkeiten durch – von Interesse sind hierbei allerdings nur Situationen, bei denen alle drei Massen gebunden bleiben. Da der Phasenraum 18-dimensional ist (für jeden Massenpunkt drei Orts- und drei Geschwindigkeitskoordinaten) und nach Festlegung des Schwerpunkts und seines Impulses davon immer noch 12 Dimensionen übrig bleiben, ist ein Poincarè-Plot nicht ohne weiteres möglich. Zur Entscheidung, ob chaotisches Verhalten vorliegt oder nicht, ziehen Sie deshalb bitte den Lyapunov-Exponenten heran. 1.5 Lyapunov-Exponent beim Sinai-Billard Berechnen Sie den Lyapunov-Exponent des in Abschn. 1.6 vorgestellten SinaiBillards. Betrachten Sie dazu zwei Bahnen, die an der selben Wand entweder parallel und nahe beeinander oder am selben Punkt mit etwas unterschiedlicher Richtung beginnen. Versuchen Sie zunächst eine Bestimmung des Lyapunov-Exponenten ohne der im Text beschriebenen sukzessiven Rückskalierung. Welche tend sind bei dieser Vorgehensweise noch sinnvoll möglich? Und wie ändert sich das, wenn Sie in bestimmten Zeitabständen den Abstand auf einen festen Wert skalieren? 1.6 Flug einer Diskusscheibe Das Programm wurf1.cpp aus Abschn. 1.10 kann verhältnismässig einfach so modifiziert werden, dass die wesentlichen Punkte beim Flug einer Diskusscheibe in einem einfachen Modell erfasst werden. Gegenüber dem schiefen Wurf eines ’normalen’ Körpers kommt bei der Diskusscheibe eine Auftriebskraft proportional zur Relativgeschwindigkeit Diskus–Luft hinzu: F Auftr = α(vx,Diskus − vx,Luft )ez . (1.101) Hierbei sind vx,Diskus bzw. vx,Luft die x-Komponente der Geschwindigkeit des Diskus bzw. der Luft und ez ist der Einheitsvektor in z-Richtung. Der Einfachheit halber haben wir angenommen, daß der Diskus während des Fluges eben liegt – in Wirklichkeit steht der Diskus üblicherweise leicht schräg in der Luft, wobei diese Neigung aufgrund der schnellen Rotation während des Fluges annähernd konstant ist. Der Wert der Konstanten α hängt von der Form des Diskus ab; ein guter Diskus sollte ein möglichst großes α aufweisen, da dies viel Auftrieb und damit eine große Flugweite bedeutet. Beachten Sie, dass die Einbeziehung von Luftbewegung auch eine Modifikation beim Reibungsterm erfordert. Im ersten Schritt müssen Sie den Wert von α abschätzen – überlegen Sie sich dazu einen realistischen Wert der Anfangsgeschwindigkeit und ermitteln Sie für verschiedene Werte von α die maximalen Wurfweiten bei Windstille (vx,Luft = 0). Im Folgenden verwenden Sie das α, bei dem die so ermittelte Übungen 69 maximale Wurfweite mit dem Weltklasseergebnis von 70 m übereinstimmt. Nun können Sie durch Variation von vx,Luft studieren, inwieweit ein günstiger Wind noch eine Verbesserung erlaubt. Ist eher Rückenwind oder Gegenwind günstig? 2 Elektrodynamik 2.1 Die Maxwellschen Gleichungen Nach dem Studium des letzten Kapitels beherrschen wir mechanische Systeme, d.h. Systeme, die durch die Angabe von Orten und Impulsen vollständig beschrieben werden. Noch Anfang des letzten Jahrhunderts nahm man an, dass dies – zumindest im Prinzip – bei allen physikalischen Fragestellungen der Fall sei. Dieses mechanistische Weltbild, das zur Erklärung elektrischer und magnetischer Phänomene den Äther benötigte, wurde durch die Aufstellung der speziellen Relativitätstheorie [23] zerstört. In Einsteins Weltbild ist eine mechanische Interpretation des elektrischen und magnetischen Feldes nicht mehr möglich, und die Bewegungsgleichungen dieser Felder können nicht als eine Folge der Newtonschen Axiome betrachtet werden. Vielmehr nehmen diese Bewegungsgleichungen, die Maxwellschen Gleichungen, denselben Status ein, wie die Newtonschen Axiome selbst. Im Rahmen dieses Buches ist die Elektrodynamik die erste Feldtheorie, die wir kennen lernen, d.h. die dynamischen Variablen sind nicht mehr skalare oder vektorielle Größen, sondern sind Funktionen von Ort und Zeit. Das hat zur Folge, dass die Bewegungsgleichungen keine gewöhnlichen Differentialgleichungen, sondern partielle Differentialgleichungen sind: div B = 0 div D = ∂D =j ∂t ∂B rotE + = 0, ∂t rotH − (2.1) (2.2) wobei ρ die Ladungsdichte und j die Stromdichte sind. Es wird Sie vielleicht verwundern, dass statt zwei Feldern (dem elektrischen und dem magnetischen) in den Maxwellschen Gleichungen vier Felder eingehen. Der Grund ist praktischer Natur: auf diese Weise drückt man sich quasi – zumindest für’s erste – um die Frage, wie stark das elektrische und magnetische Feld innerhalb von Materie ist, wenn man die Felder außerhalb kennt. Bis auf historisch durch das Einheitensystem bedingte Vorfaktoren ist nämlich D nichts anderes als das elektrische Feld E innerhalb von Materie und entsprechend verhält es sich mit B und H. Letztendlich kann man sich dieser Problematik aber nicht entziehen, so dass die Maxwell-Gleichungen 72 2 Elektrodynamik alleine noch unvollständig sind und erst durch – Im allgemeinen empirische – Beziehungen zwischen D und E sowie zwischen B und H ergänzt werden müssen. Diese Beziehungen können außerordentlich kompliziert sein und sogar von der Vorgeschichte abhängen (Hysterese), in vielen Fällen wird die Wirklichkeit aber durch einen linearen Ansatz hinreichend genau beschrieben: D = ε0 ε r E B = µ0 µr H . (2.3) (2.4) Dabei sind ε0 und µ0 die oben erwähnten historischen Faktoren (sie heißen Dielektrizitäts- und Permeabilitätskonstante des Vakuums), während εr und µr die Dielektrizitäts- und Permeabilitätskonstante des jeweiligen Mediums sind. Im Vakuum nehmen die letzteren beiden Konstanten den Wert eins an. Transparenter wird die Unterscheidung von E und D einerseits und B und H andererseits durch die Aufspaltung D = ε0 E + P (2.5) B = µ0 (H + M ) . (2.6) wobei P die Polarisation und M die Magnetisierung des Mediums ist. 2.2 Felder von Punktladungen Die Maxwellschen Gleichungen sind in Ihrer vollen Allgemeinheit außerordentlich kompliziert – was Sie unter anderem daran ermessen können, welche Vielzahl von Phänomenen grundsätzlich auf dieses System partieller Differentialgleichungen zurückgeführt werden kann. Man beschränkt sich daher in den Vorlesungen darauf, mehr oder weniger eingeschränkte Spezialfälle des allgemeinen Problems zu betrachten. Der erste Spezialfall, den wir nun eingehender untersuchen wollen, ist der eines Systems von sich nicht bewegenden Punktladungen qn an den Orten xn im Vakuum: qn δ(x − xn ) (2.7) (x, t) = n j(x, t) = 0 . (2.8) In diesem Fall vereinfachen sich die Maxwellgleichungen zu div B = div E = 0 1 ε0 n qn δ(x 1 µ0 rotB − xn ) =0 rotE = 0 , (2.9) (2.10) wobei wir bereits alle Terme mit einer Zeitableitung weggelassen haben, da die Lösung dieses statischen Problems keine zeitabhängigen Anteile enthalten kann. 2.3 Multipole 73 Die Lösung dieser Differentialgleichungen lässt sich direkt angeben: E(x, t) = qn 1 (x − xn ) 4πε0 n |x − xn |3 B(x, t) = 0 . (2.11) (2.12) Die Lösung für das elektrische Feld sollte uns sofort an das Newtonsche Gravitationsgesetz (1.2) erinnern, das genau die selbe Form und die selbe Abstandsabhängigkeit besitzt. 2.3 Multipole Auch wenn das Newtonsche Gravitationsgesetz, das die Anziehung zwischen Massen beschreibt, und das Grundgesetz der Elektrostatik, das die Anziehung und Abstoßung von Ladungen wiedergibt, mathematisch völlig äquivalent sind, gibt es doch einen entscheidenden physikalischen Unterschied: während Massen immer positiv sind, sich diese also immer anziehen, können Ladungen sowohl positiv als auch negativ sein. Eine direkte Konsequenz ist, dass das Feld einer positiven Punktladung durch eine negative Ladung in unmittelbarer Nähe nahezu aufgehoben werden kann — eine Möglichkeit, die beim Gravitationsgesetz natürlich nicht gegeben ist. Auf diese Weise erhalten wir einen so genannten Dipol, dessen Fernfeld nur vom Quotienten q/∆ abhängt (q bzw. −q sind die beiden Punktladungen und ∆ ist deren Abstand) und kubisch mit dem Abstand zum Dipol abnimmt (anstatt quadratisch wie bei einer Einzelladung, dem Monopol). Durch Hinzufügen eines zweiten Dipols, dessen Ladungen gegenüber dem ersten wieder vertauscht sind, erhalten wir einen Quadrupol, dessen Feld nun quartisch mit zunehmenden Abstand abnimmt usw. In diesem Abschnitt wollen wir die Felder dieser Multipole berechnen und die bereits erwähnte Abhängigkeit der Feldstärke vom Abstand zum Multipol bestätigen. Das Programm multipol.cpp konstruiert im ersten Schritt (Zeile 60–71) einen Multipol vorgegebener Ordnung. Dabei wird ausgenutzt, dass man aus zwei gegeneinander versetzten Ladungsanordnungen, die jeweils einen Multipol n-ter Ordnung darstellen und bei denen beim einen Multipol die Ladungen gegenüber dem anderen alle invertiert sind, zusammen einen Multipol (n+1)-ter Ordnung erhält. Anschließend (Zeile 73–86) wird die elektrische Feldstärke entlang einer Geraden, deren Richtung durch die Variable alpha festgelegt wird, berechnet und ausgegeben. 1 2 3 4 5 /************************************************************************** * Name: multipol.cpp * * Zweck: Berechnet eletrostatische Multipol-Felder * * Gleichung: E(x) = sum_n q_n (x-x_n) / (x-x_n)ˆ3 * **************************************************************************/ 6 7 #include <iostream> 74 8 9 10 2 Elektrodynamik #include <fstream> #include <math.h> #include "tools.h" 11 12 using namespace std; 13 14 main( int argc, char *argv[] ) 15 16 17 18 19 20 21 22 23 24 { //-- Definition der Variablen const int ord_max = 10; const int xmax = 1024; int n, n_left, ord, nr, nr_max, npos, npos1, n_ord; double x_multipol[xmax], q_multipol[xmax], alpha, x, y, r, dr, rmax; double E, E_x, E_y, delta, distance; ifstream in_stream; ofstream out_stream; 25 26 27 28 //-- Initialisierung for (n=0; n<xmax; n++) x_multipol[n] = 0; for (n=0; n<xmax; n++) q_multipol[n] = 0; 29 30 31 32 33 34 35 //-- Fehlermeldung, wenn Input- oder Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: multipol infile outfile\n"; return 0; } 36 37 38 39 40 41 42 43 44 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ausgabe-File generiert von multipol.cpp \n"; inout(ord,"ord"); inout(alpha,"alpha"); inout(rmax,"rmax"); inout(nr_max,"nr_max"); inout(delta,"delta"); in_stream.close(); 45 46 47 //-- Berechnung einiger benoetigter Parameter -dr = rmax / nr_max; 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 //-- Generation des Multipols aus Einzelladungen if (ord > ord_max) { cout << " Maximal zulaessige Ordnung ueberschritten!\n"; return 0; } if (ord < 1) { cout << " Nur positive Ordnungen sind physikalisch sinnvoll!\n"; return 0; } n_left = -pow(2,ord-1); x_multipol[0] = n_left * delta; q_multipol[0] = 1; npos = 0; for (n_ord=1; n_ord<=ord; n_ord++) for (n=1; n<=pow(2,n_ord-1); n++) { 2.3 Multipole 67 68 69 70 71 75 npos++; npos1 = npos - pow(2,n_ord-1); x_multipol[npos] = (npos+n_left) * delta; q_multipol[npos] = -q_multipol[npos1]; } 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 //-- Berechnung der elektrischen Feldstaerke entlang einer Linie for (nr=1; nr<=nr_max; nr++) { r = nr*dr; x = r * cos(alpha); y = r * sin(alpha); E_x = 0; E_y = 0; for (n=0; n<=npos; n++) { distance = sqr(x-x_multipol[n]) + sqr(y); E_x = E_x + q_multipol[n] / pow(distance,1.5) * ( x-x_multipol[n]); E_y = E_y + q_multipol[n] / pow(distance,1.5) * y; } E = sqrt( sqr(E_x)+sqr(E_y) ); out_stream << r << " " << log(r) / log(10) << " " << E << " " << log(fabs(E)) / log(10) << "\n"; } out_stream.close(); } Das Ergebnis dieses Programms für Ordnungen bis n ord = 10 zeigt das folgende Diagramm: Abb. 2.1. Abhängigkeit der elektrischen Feldstärke vom Abstand bei Multipolen 1. (oberste Linie) bis 10. Ordnung (unterste Linie) Da die Multipolfelder als kleine Größen durch Differenzbildung der wesentlich größeren Monopolfelder gebildet werden, bekommen wir nur numerisches Rauschen, wenn das Größenverhältnis zwischen den beiden zu groß wird, was hier etwa bei Multipolfeldern der Größenordnung 10−25 der Fall ist. Bei kleinen Abständen r überschneiden sich die Kurven teilweise, weil wir noch nicht im Fernfeld sind – durch unser Konstruktionsverfahren sind insbesondere Multipole höherer Ordnung N ziemlich ausgedehnt: der Abstand zwischen den beiden außen liegenden Ladungen ist (2N − 1)∆, wobei ∆ der Abstand zweier benachbarter Einzelladungen ist. Dieses unschöne Detail ließe sich entschärfen, indem man die hier gewählte lineare Anordnung der Punkt- 76 2 Elektrodynamik ladungen durch eine räumliche Anordnung ersetzt. Abgesehen von diesen beiden Artefakten erhalten wir jedoch die bei einer doppelt-logarithmischen Auftragung erwarteten Geraden verschiedener Steigung. 2.4 Berechnung von Feldlinien Nachdem wir im vorangegangenen Abschnitt elektrische Felder berechnet haben, stellt sich nun die Frage, wie wir diese am besten graphisch darstellen. Sie kennen sowohl aus Lehrbüchern als auch aus dem Physikunterricht an der Schule die Darstellung mittels Feldlinien: Linien, die immer in Richtung des elektrischen Feldes zeigen. Der Berechnung solcher Feldlinien bei vorgegebenem elektrischen Feld, z.B. dem Feld eines Multipols, wollen wir uns in diesem Abschnitt widmen. Was wir suchen, ist also eine Kurve x(s), wobei s ein Parameter ist, der einen jeweils festzulegenden Bereich durchläuft. Die Tatsache, dass die Feldlinie immer in Richtung des elektrischen Feldes zeigt, heißt nun, dass d x(s) = α(s)E(x) . dt (2.13) Den Proportionalitätsfaktor α(s) können wir hierbei frei wählen, da er lediglich angibt, wie schnell die Kurve x(s) mit s durchlaufen wird. Gleichung (2.13) stellt bei gegebenem elektrischen Feld E(x) eine Differentialgleichung für die Parameterdarstellung der elektrischen Feldlinien dar. Wenn man noch berücksichtigt, dass Feldlinien immer bei positiven Ladungen beginnen und bei negativen Ladungen enden, steht einem Computerprogramm zur Berechnung von Feldlinien nichts mehr im Wege: 1 2 3 4 5 6 /************************************************************************** * Name: linien1.cpp * * Zweck: Berechnet Feldlinien elektrostatischer Multipol-Felder * * Gleichung: (d/dt)x(t) = E(x) / |E(x)| * * verwendete Bibiliothek: GSL * **************************************************************************/ 7 8 9 10 11 12 13 14 #include #include #include #include #include #include #include <iostream> <fstream> <math.h> <gsl/gsl_matrix.h> <gsl/gsl_errno.h> <gsl/gsl_odeiv.h> "tools.h" 15 16 using namespace std; 17 18 19 20 21 22 //-- Definition der globalen Variablen const int ord_max = 10; const int nq_max = 1024; int npos; double x_multipol[nq_max], q_multipol[nq_max]; 2.4 Berechnung von Feldlinien 77 23 24 //------------------------------------------------------------------------- 25 26 int f(double s, const double x[], double dxds[], void *params) 27 28 29 30 { int n; double E_x, E_y, E, distance; 31 32 33 34 35 36 37 38 E_x = 0; E_y = 0; for (n=0; n<=npos; n++) { distance = sqr(x[0]-x_multipol[n]) + sqr(x[1]); E_x = E_x + q_multipol[n] / pow(distance,1.5) * ( x[0]-x_multipol[n]); E_y = E_y + q_multipol[n] / pow(distance,1.5) * x[1]; } 39 40 41 42 E = sqrt( sqr(E_x) + sqr(E_y) ); dxds[0] = E_x / E; dxds[1] = E_y / E; 43 44 45 return GSL_SUCCESS; } 46 47 //------------------------------------------------------------------------- 48 49 50 int jac(double s, const double x[], double *dfdx, double dxds[], void *params) 51 52 53 54 { return GSL_SUCCESS; } 55 56 //------------------------------------------------------------------------- 57 58 main( int argc, char *argv[] ) 59 60 61 62 63 64 65 66 { //-- Definition der Variablen int n, n1, n_left, ord, npos1, n_ord, nline, nline_max, status; double x[2], s, send, delta, eps, rtol, atol; double dist, dist1, deltax, deltay, h, mu = 10; ifstream in_stream; ofstream out_stream; 67 68 69 70 71 72 73 //-- Fehlermeldung, wenn Input- oder Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: linien1 infile outfile\n"; return 0; } 74 75 76 77 //-- Initialisierung for (n=0; n<nq_max; n++) x_multipol[n] = 0; for (n=0; n<nq_max; n++) q_multipol[n] = 0; 78 79 80 81 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); 78 82 83 84 85 86 2 Elektrodynamik out_stream << "! Ausgabe-File generiert von linien1.cpp\n"; inout(ord,"ord"); inout(delta,"delta"); inout(nline_max,"nline_max"); inout(atol,"atol"); inout(rtol,"rtol"); inout(eps,"eps"); in_stream.close(); 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 //-- Generation des Multipols aus Einzelladungen if (ord > ord_max) { cout << " Maximal zulaessige Ordnung ueberschritten!\n"; return 0; } if (ord < 1) { cout << " Nur echt positive Ordnungen sind physikalisch sinnvoll!\n"; return 0; } n_left = -pow(2,ord-1); x_multipol[0] = n_left * delta; q_multipol[0] = 1; npos = 0; for (n_ord=1; n_ord<=ord; n_ord++) for (n=1; n<=pow(2,n_ord-1); n++) { npos++; npos1 = npos - pow(2,n_ord-1); x_multipol[npos] = x_multipol[npos-1] + delta; q_multipol[npos] = -q_multipol[npos1]; } 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 //-- Berechnung der elektrischen Feldlinien for (n=0; n<=npos; n++) { if (q_multipol[n] > 0) for (nline=1; nline<=nline_max; nline++) { deltax = eps * cos(2*3.141*(nline-0.5)/nline_max); deltay = eps * sin(2*3.141*(nline-0.5)/nline_max); x[0] = x_multipol[n] + deltax; x[1] = deltay; dist = eps * 2; const gsl_odeiv_step_type *T1 = gsl_odeiv_step_rkf45; gsl_odeiv_step *s1 = gsl_odeiv_step_alloc(T1, 2); gsl_odeiv_control *c1 = gsl_odeiv_control_y_new(atol, rtol); gsl_odeiv_evolve *e1 = gsl_odeiv_evolve_alloc(2); gsl_odeiv_system sys = {f, jac, 2, &mu}; h = 1.e-6; while (dist >= eps) { s = 0; send = eps; while (s < send) { status = gsl_odeiv_evolve_apply(e1,c1,s1,&sys,&s,send,&h,x); if (status != GSL_SUCCESS) break; } out_stream << x[0] << " " << x[1] << "\n"; dist = 1.e6; for (n1=0; n1<=npos; n1++) { 2.4 Berechnung von Feldlinien 141 142 143 144 145 146 147 148 149 150 151 152 79 dist1 = sqrt(sqr(x[0]-x_multipol[n1])+sqr(x[1])); if (dist1<dist) dist = dist1; } if (dist<eps) break; } gsl_odeiv_evolve_free(e1); gsl_odeiv_control_free(c1); gsl_odeiv_step_free(s1); } } out_stream.close(); } In diesem Programm haben wir die Generierung eines Multipols aus dem Programm multipol.cpp des vorigen Abschnitts übernommen (Zeile 99–110). Rings um jede positive Ladung im Abstand eps werden die Startpunkte für die Feldlinien gewählt (Zeile 118–121), die Feldlinien werden dann als Lösung des Differentialgleichungssystems (2.13) berechnet (Zeile 123–136). In Zeile 144 finden Sie die Abbruchbedingung: Eine Feldlinie gilt als beendet, wenn ihr Abstand zu einer der Punktladungen kleiner als eps geworden ist. Das Ergebnis dieses Programms für Multipole erster bis vierter Ordnung zeigt Abb. 2.2. Was wir an diesen Diagrammen sehr schön sehen, ist die Symmetrie der Multipolfelder. Während das hier nicht gezeigte Feld eines Monopols kugelsymmetrisch ist, wird diese Symmetrie beim Dipol gebrochen und es ist symmetrisch bzgl. Spiegelung an der Verbindungslinie zwischen den beiden Einzelladungen sowie bzgl. Drehungen um π. Das Quadrupolfeld Abb. 2.2. Feldlinien für Multipole 1. Ordnung (Dipol, oben links), 2. Ordnung (Quadrupol, oben rechts), 3. Ordnung (Oktopol, unten links) sowie 4. Ordnung (unten rechts) 80 2 Elektrodynamik sollte invariant gegenüber Drehungen um beliebige Vielfache von π/2 sein, aufgrund des endlichen Abstands der Einzelladungen ist diese Symmetrie jedoch bereits leicht gebrochen. Noch deutlicher wird diese Symmetriebrechung beim Oktopolfeld, das eigentlich invariant gegenüber Drehungen um Vielfache von π/3 sein sollte, und beim Multipol 4. Ordnung, dessen Feld invariant gegenüber Drehungen um Vielfache von π/4 sein sollte. Der Grund für diese zunehmende Verzerrung des elektrischen Feldes ist die Tatsache, dass die Ausdehnung unseres linear aufgebauten Multipols mit höherer Ordnung rasch zunimmt und deshalb nicht mehr vernachlässigbar ist. Beachten Sie hierzu Übungsaufgabe 2.1, in der die Multipole in einer räumlichen Anordnung realisiert werden. 2.5 Magnetfelder stationärer Ströme Einen weiteren interessanten – und auch technisch sehr bedeutsamen Spezialfall – stellt das elektromagnetische Feld dar, das von stationären Strömen erzeugt wird: (x, s) = 0 j(x, s) = I (2.14) ds dl(s) δ(x − x(s)) . ds (2.15) Der zweite Ausdruck beschreibt einen auf einen Draht beschränkten Strom. Dabei ist I die Stromstärke, und s ist ein Parameter, der den Leiter parametrisiert, d.h. jedem Wert von s entspricht einem Punkt l(s) auf der Leiterbahn. In diesem Fall reduzieren sich die Maxwell-Gleichungen auf: dl(s) divB = 0 rotH = I ds δ(x − x(s)) (2.16) ds divD = 0 rotE = 0 . (2.17) Diese Gleichungen lassen sich direkt lösen: E=0 j(x ) × (x − x ) . B = d3 x |x − x |3 (2.18) (2.19) Da Ströme häufig in dünnen Leiterbahnen fließen, ist diese Situation von besonderer Bedeutung. In diesem Fall reduziert sich das Dreifachintegral aus (2.19) zu einem Einfachintegral entlang der Leiterbahn(en): I(x ) × (x − x ) B = dx . (2.20) |x − x |3 Das ist das Biot-Savart-Gesetz, mit dem das Magnetfeld um beliebige Leiteranordnungen berechnet werden kann, z.B. das Magnetfeld einer Spule, für das Sie aus der Schule die Näherungsformel 2.5 Magnetfelder stationärer Ströme B = NI A l 81 (2.21) kennen (N ist die Zahl der Windungen, l und A ist die Länge bzw. die Querschnittsfläche der Spule). Berechnungen von Feldern zu verschiedenen Stromverteilungen finden Sie z.B. in [24]. Doch zurück zum Biot-Savart-Gesetz in seiner allgemeinen Formulierung (2.19), die wir auf eine beliebig geformte Leiterbahn anwenden können, z.B. einen Ringstrom: Abb. 2.3. Kreisstrom und das von ihm erzeugte Magnetfeld Die dargestellten Feldlinien wurden mittels dieses Programms berechnet: 1 2 3 4 5 6 /************************************************************************** * Name: kreisstrom.cpp * * Zweck: Berechnet das Magnetfeld eines stromdurchflossenen Rings * * Gleichung: Biot-Savart-Gesetz * * verwendete Bibiliothek: GSL * **************************************************************************/ 7 8 9 10 11 12 13 14 15 #include #include #include #include #include #include #include #include <iostream> <fstream> <math.h> <gsl/gsl_matrix.h> <gsl/gsl_errno.h> <gsl/gsl_odeiv.h> <gsl/gsl_integration.h> "tools.h" 16 17 using namespace std; 18 19 20 21 //-- Definition der globalen Variablen const double epsabs = 1.e-12; const double epsrel = 1.e-12; 22 23 24 int double npos; R, x[3]; 25 26 //------------------------------------------------------------------------- 27 28 29 int dI(double s, double x0[3], double dx0_ds[3], double *r, double *dl) 30 31 32 { x0[0] = R * cos(s); dx0_ds[0] = - R * sin(s); 82 33 34 2 Elektrodynamik x0[1] = R * sin(s); x0[2] = 0; dx0_ds[1] = dx0_ds[2] = R * cos(s); 0; 35 36 37 *dl = sqrt(sqr(dx0_ds[0])+sqr(dx0_ds[1])+sqr(dx0_ds[2])); *r = sqrt(sqr(x[0]-x0[0])+sqr(x[1]-x0[1])+sqr(x[2]-x0[2])); 38 39 40 return 1; } 41 42 //------------------------------------------------------------------------- 43 44 double dB_x(double s, void *params) 45 46 47 48 { int result; double x0[3], dx0_ds[3], r, dl; 49 50 result = dI(s, x0, dx0_ds, &r, &dl); 51 52 53 return (dx0_ds[1] * (x[2]-x0[2]) - dx0_ds[2] * (x[1]-x0[1]))/pow(r,3); } 54 55 //------------------------------------------------------------------------- 56 57 double dB_y(double s, void *params) 58 59 60 61 { int result; double x0[3], dx0_ds[3], r, dl; 62 63 result = dI(s, x0, dx0_ds, &r, &dl); 64 65 66 return (dx0_ds[2] * (x[0]-x0[0]) - dx0_ds[0] * (x[2]-x0[2]))/pow(r,3); } 67 68 //------------------------------------------------------------------------- 69 70 double dB_z(double s, void *params) 71 72 73 74 { int result; double x0[3], dx0_ds[3], r, dl; 75 76 result = dI(s, x0, dx0_ds, &r, &dl); 77 78 79 return (dx0_ds[0] * (x[1]-x0[1]) - dx0_ds[1] * (x[0]-x0[0]))/pow(r,3); } 80 81 //------------------------------------------------------------------------- 82 83 double B_x(double x, double y, double z) 84 85 86 87 88 { const size_t limit = 10000; const int key = GSL_INTEG_GAUSS21; double result, abserr; 89 90 91 gsl_integration_workspace * w = gsl_integration_workspace_alloc(limit); 2.5 Magnetfelder stationärer Ströme 83 92 93 94 gsl_function F; F.function = &dB_x; 95 96 97 gsl_integration_qag(&F, -pi, pi, epsabs, epsrel, limit, key, w, &result, &abserr); 98 99 gsl_integration_workspace_free(w); 100 101 102 return result; } 103 104 //------------------------------------------------------------------------- 105 106 double B_y(double x, double y, double z) 107 108 109 110 111 { const int limit = 1000; const int key = GSL_INTEG_GAUSS61; double result, abserr; 112 113 114 gsl_integration_workspace * w = gsl_integration_workspace_alloc(limit); 115 116 117 gsl_function F; F.function = &dB_y; 118 119 120 gsl_integration_qag(&F, -pi, pi, epsabs, epsrel, limit, key, w, &result, &abserr); 121 122 gsl_integration_workspace_free(w); 123 124 125 return result; } 126 127 //------------------------------------------------------------------------- 128 129 double B_z(double x, double y, double z) 130 131 132 133 134 { const int limit = 1000; const int key = GSL_INTEG_GAUSS61; double result, abserr; 135 136 137 gsl_integration_workspace * w = gsl_integration_workspace_alloc(limit); 138 139 140 gsl_function F; F.function = &dB_z; 141 142 143 gsl_integration_qag(&F, 0, 2*pi, epsabs, epsrel, limit, key, w, &result, &abserr); 144 145 gsl_integration_workspace_free(w); 146 147 148 return result; } 149 150 //------------------------------------------------------------------------- 84 2 Elektrodynamik 151 152 int f(double s, const double x[], double dxds[], void *params) 153 154 155 { double B; 156 157 158 159 160 161 B = sqrt( + dxds[0] = dxds[1] = dxds[2] = sqr(B_x(x[0],x[1],x[2])) + sqr(B_y(x[0],x[1],x[2])) sqr(B_z(x[0],x[1],x[2])) ); B_x(x[0],x[1],x[2]) / B; B_y(x[0],x[1],x[2]) / B; B_z(x[0],x[1],x[2]) / B; 162 163 164 return GSL_SUCCESS; } 165 166 //------------------------------------------------------------------------- 167 168 169 int jac(double s, const double x[], double *dfdx, double dxds[], void *params) 170 171 172 173 { return GSL_SUCCESS; } 174 175 //------------------------------------------------------------------------- 176 177 178 main( int argc, char *argv[] ) 179 180 181 182 183 184 185 186 187 188 { //-- Definition der Variablen double dxds[3], x_start[3], s, send, ds; double delta, eps, rtol, atol, dist, dist1; double mu = 10; double h, x2old; int n, n1, N, N_res, nline, nline_max; ifstream in_stream; ofstream out_stream; 189 190 191 192 193 194 195 //-- Fehlermeldung, wenn Input- oder Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: kreisstrom infile outfile\n"; return 0; } 196 197 198 199 200 201 202 203 204 205 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ausgabe-File generiert von kreisstrom.cpp \n"; inout(R,"R"); inout(x_start[0],"x_start[0]"); inout(x_start[1],"x_start[1]"); inout(x_start[2],"x_start[2]"); inout(ds,"ds"); inout(atol,"atol"); inout(rtol,"rtol"); inout(eps,"eps"); in_stream.close(); 206 207 208 209 //-- Berechnung der elektrischen Feldlinien x[0] = x_start[0]; x[1] = x_start[1]; 2.5 Magnetfelder stationärer Ströme 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 85 x[2] = x_start[2]; const gsl_odeiv_step_type *T1 = gsl_odeiv_step_rkf45; gsl_odeiv_step *s1 = gsl_odeiv_step_alloc(T1, 3); gsl_odeiv_control *c1 = gsl_odeiv_control_y_new(atol, rtol); gsl_odeiv_evolve *e1 = gsl_odeiv_evolve_alloc(3); gsl_odeiv_system sys = {f, jac, 3, &mu}; h = 1.e-6; s = 0; send = ds; dist = 1.e6; x2old = 1.e6; while ((x2old >=0) || (x[2]<=0)) { x2old = x[2]; while (s < send) { int status = gsl_odeiv_evolve_apply(e1,c1,s1,&sys,&s,send,&h,x); if (status != GSL_SUCCESS) break; } out_stream << x[0] << " " << x[1] << " " << x[2] << "\n"; cout << s << " " << x[0] << " " << x[1] << " " << x[2] << "\n"; dist = sqrt(sqr(x[0]-x_start[0])+sqr(x[1]-x_start [1]) +sqr(x[2]-x_start[2])); send = send + ds; } gsl_odeiv_evolve_free(e1); gsl_odeiv_control_free(c1); gsl_odeiv_step_free(s1); 238 239 240 out_stream.close(); } Die Generierung der Feldlinien bei gegebenem Feld können wir aus dem Programm linien1.cpp übernehmen, was bleibt ist die Berechnung des magnetischen Feldes gemäß (2.19), die zur besseren Übertragung auf andere Leitergeometrien in drei Teile aufgeteilt ist: – Das Unterprogramm dI liefert für ein s die zugehörigen Koordinaten des Punktes x0 (s) auf dem Leiter, die Richtung der Leiterbahn sowie der Einfachheit halber den Abstand vom gerade betrachteten Punkt x und das Längenelement dl = |dx0 (s)/ds|. – Die Funktionen dB x, dB y und dB z stellen die drei Komponenten des Integranden in (2.19) zur Verfügung. – Die Unterprogramme B x, B y und B z schließlich berechnen die jeweiligen Komponenten des magnetischen Feldes durch Integration der Funktionen dB x, dB y und dB z. Den Vorteil dieser Strukturierung sehen Sie, wenn Sie das Programm spule1.cpp weiter unten studieren. Abgesehen von einer Anpassung der einzulesenden Parameter, musste lediglich das Unterprogramm dI modifiziert werden. Bei diesem Programm haben wir von einer nützlichen Symmetrie nicht Gebrauch gemacht: das Problem ist nämlich symmetrisch bzgl. beliebigen Drehungen um die z-Achse. Aus diesem Grund müssen Feldlinien immer in 86 2 Elektrodynamik der Ebene liegen, die durch den Startpunkt und die z-Achse festgelegt wird. Der Grund, warum wir uns den Luxus leisten, von dieser Einschränkung auf zwei Dimensionen nicht Gebrauch zu machen, liegt wieder in der Wiederverwertbarkeit für andere Probleme, bei denen diese Symmetrie nicht mehr vorliegt. Womit wir nun endgültig zum Programm spule1.cpp kommen, das ebenfalls die Feldlinien des Magnetfelds berechnet – diesmal jedoch für eine Spule: Abb. 2.4. Stromführung durch eine Spule inklusive Zuleitungen 1 2 3 4 5 6 /********************************************************************* * Name: spule1.cpp * * Zweck: Berechnet das Magnetfeld einer Spule * * Gleichung: Biot-Savart-Gesetz * * verwendete Bibiliothek: GSL * *********************************************************************/ 7 8 9 10 11 12 13 14 15 #include #include #include #include #include #include #include #include <iostream> <fstream> <math.h> <gsl/gsl_matrix.h> <gsl/gsl_errno.h> <gsl/gsl_odeiv.h> <gsl/gsl_integration.h> "tools.h" 16 17 using namespace std; 18 19 //-- Definition der globalen Variablen 20 21 22 23 24 25 26 const const const const const const double double double double double double epsabs = 1.e-12; epsrel = 1.e-12; s0 = 0; const double s1 = 1; s2 = 2; const double s3 = 3; s4 = 4; const double s5 = 5; s6 = 6; 27 28 int npos, dir; 2.5 Magnetfelder stationärer Ströme 29 double R, l, L1, L2, phi_min, phi_max, dphids, x[3]; 30 31 //-------------------------------------------------------------------- 32 33 34 int dI(double s, double x0[3], double dx0_ds[3], double *r, double *dl) 35 36 37 { double phi = phi_min + dphids*s; 38 39 40 41 42 43 44 if (s<1) { x0[0] = R * cos(phi); x0[1] = R * sin(phi); x0[2] = l * s; } dx0_ds[0] = - R * sin(phi) * dphids; dx0_ds[1] = R * cos(phi) * dphids; dx0_ds[2] = l; 45 46 47 48 49 50 51 if ((s>=s1) && (s<s2)) { x0[0] = R; dx0_ds[0] = 0; x0[1] = 0; dx0_ds[1] = 0; x0[2] = l + (L2-l) * (s-1); dx0_ds[2] = (L2-l); } 52 53 54 55 56 57 58 if ((s>=s2) && (s<s3)) { x0[0] = R + (L1-R)*(s-2); x0[1] = 0; x0[2] = L2; } dx0_ds[0] = L1-R; dx0_ds[1] = 0; dx0_ds[2] = 0; 59 60 61 62 63 64 65 if ((s>=s3) && (s<s4)) { x0[0] = L1; x0[1] = 0; x0[2] = L2 - 2*L2*(s-3); } dx0_ds[0] = 0; dx0_ds[1] = 0; dx0_ds[2] = -2*L2; 66 67 68 69 70 71 72 if ((s>=s4) && (s<=s5)) { x0[0] = L1 + (R-L1)*(s-4); x0[1] = 0; x0[2] = -L2; } dx0_ds[0] = R-L1; dx0_ds[1] = 0; dx0_ds[2] = 0; 73 74 75 76 77 78 79 if ((s>=s5) && (s<=s6)) { x0[0] = R; x0[1] = 0; x0[2] = -L2+L2*(s-5); } dx0_ds[0] = 0; dx0_ds[1] = 0; dx0_ds[2] = L2; 80 81 82 *dl = sqrt(sqr(dx0_ds[0])+sqr(dx0_ds[1])+sqr(dx0_ds[2])) / 100.; *r = sqrt(sqr(x[0]-x0[0])+sqr(x[1]-x0[1])+sqr(x[2]-x0[2])); 83 84 85 return 1; } 86 87 //-------------------------------------------------------------------- 87 88 2 Elektrodynamik 88 89 double dB_x(double s, void *params) 90 91 92 93 { int result; double x0[3], dx0_ds[3], r, dl; 94 95 result = dI(s, x0, dx0_ds, &r, &dl); 96 97 98 99 return (dx0_ds[1] * (x[2]-x0[2]) - dx0_ds[2] * (x[1]-x0[1])) / pow(r,3) * dl; } 100 ... 126 127 //-------------------------------------------------------------------- 128 129 double B_x(double x, double y, double z) 130 131 132 133 134 { const size_t limit = 10000; const int key = GSL_INTEG_GAUSS21; double result1, result2, result3, result4, result5, result6, abserr; 135 136 137 138 gsl_integration_workspace * w = gsl_integration_workspace_alloc(limit); 139 140 141 gsl_function F; F.function = &dB_x; 142 143 144 145 146 147 148 149 150 151 152 153 154 gsl_integration_qag(&F, s0, s1, epsabs, &result1, &abserr); gsl_integration_qag(&F, s1, s2, epsabs, &result2, &abserr); gsl_integration_qag(&F, s2, s3, epsabs, &result3, &abserr); gsl_integration_qag(&F, s3, s4, epsabs, &result4, &abserr); gsl_integration_qag(&F, s4, s5, epsabs, &result5, &abserr); gsl_integration_qag(&F, s5, s6, epsabs, &result6, &abserr); epsrel, limit, key, w, epsrel, limit, key, w, epsrel, limit, key, w, epsrel, limit, key, w, epsrel, limit, key, w, epsrel, limit, key, w, 155 156 gsl_integration_workspace_free(w); 157 158 159 return result1+result2+result3+result4+result5+result6; } 160 ... 226 227 //------------------------------------------------------------------------- 228 229 int f(double s, const double x[], double dxds[], void *params) 230 231 { 2.5 Magnetfelder stationärer Ströme 232 89 double B; 233 234 235 236 237 238 B = sqrt( + dxds[0] = dxds[1] = dxds[2] = sqr(B_x(x[0],x[1],x[2])) + sqr(B_y(x[0],x[1],x[2])) sqr(B_z(x[0],x[1],x[2])) ); dir * B_x(x[0],x[1],x[2]) / B; dir * B_y(x[0],x[1],x[2]) / B; dir * B_z(x[0],x[1],x[2]) / B; 239 240 241 return GSL_SUCCESS; } 242 243 //------------------------------------------------------------------------- 244 245 246 int jac(double s, const double x[], double *dfdx, double dxds[], void *params) 247 248 249 250 { return GSL_SUCCESS; } 251 252 //------------------------------------------------------------------------- 253 254 255 main( int argc, char *argv[] ) 256 257 258 259 260 261 262 263 264 265 { //-- Definition der Variablen int n, n1, N, nline, nline_max; double dxdt[3], x_start[3], s, s_max, send, ds, delta, rtol, atol; double dist, dist1, Bx, By, Bz, kruemmung, h; double tx, ty, tz, txold, tyold, tzold, txp, typ, tzp, kx, ky, kz; double mu = 10; ifstream in_stream; ofstream out_stream; 266 267 268 269 270 271 272 273 //-- Fehlermeldung, wenn Input- und Outputfilename nicht // uebergeben wurden if (argc<3) { cout << " Aufruf: spule1 infile outfile\n"; return 0; } 274 275 276 277 278 279 280 281 282 283 284 285 286 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ausgabe-File generiert von spule1.cpp \n"; inout(N,"N"); inout(l,"l"); inout(L1,"L1"); inout(L2,"L2"); inout(R,"R"); inout(s_max,"s_max"); inout(ds,"ds"); inout(x_start[0],"x_start[0]"); inout(x_start[1],"x_start[1]"); inout(x_start[2],"x_start[2]"); inout(atol,"atol"); inout(rtol,"rtol"); inout(dir,"dir"); in_stream.close(); 287 288 289 290 //-- Berechnung einiger benoetigter Parameter -phi_min = 0; phi_max = 2 * pi * N; 90 291 2 Elektrodynamik dphids = phi_max - phi_min; 292 293 294 295 296 297 298 299 300 301 302 303 304 305 //-- Berechnung der elektrischen Feldlinien x[0] = x_start[0]; x[1] = x_start[1]; x[2] = x_start[2]; const gsl_odeiv_step_type *T1 = gsl_odeiv_step_rkf45; gsl_odeiv_step *s1 = gsl_odeiv_step_alloc(T1, 3); gsl_odeiv_control *c1 = gsl_odeiv_control_y_new(atol, rtol); gsl_odeiv_evolve *e1 = gsl_odeiv_evolve_alloc(3); gsl_odeiv_system sys = {f, jac, 3, &mu}; h = 1.e-6; s = 0; send = ds; dist = 1.e6; 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 while (s < s_max) { while (s < send) { int status = gsl_odeiv_evolve_apply(e1,c1,s1,&sys,&s,send,&h,x); if (status != GSL_SUCCESS) break; } out_stream << s << " " << x[0] << " " << x[1] << " " << x[2] << " "; Bx = B_x(x[0],x[1],x[2]); By = B_y(x[0],x[1],x[2]); Bz = B_z(x[0],x[1],x[2]); tx = Bx / sqrt( sqr(Bx)+sqr(By)+sqr(Bz) ); ty = By / sqrt( sqr(Bx)+sqr(By)+sqr(Bz) ); tz = Bz / sqrt( sqr(Bx)+sqr(By)+sqr(Bz) ); txp = ( tx - txold ) / ds; txold = tx; typ = ( ty - tyold ) / ds; tyold = ty; tzp = ( tz - tzold ) / ds; tzold = tz; kx = ty * tzp - tz * typ; ky = tz * txp - tx * tzp; kz = tx * typ - ty * txp; kruemmung = sqrt( sqr(kx)+sqr(ky)+sqr(kz) ); out_stream << Bx << " " << By << " " << Bz << " " << sqrt(sqr(Bx)+sqr(By)+sqr(Bz)) << " " << kruemmung << "\n"; cout << x[0] << " " << x[1] << " " << x[2] << "\n"; dist = sqrt(sqr(x[0]-x_start[0])+sqr(x[1]-x_start [1]) +sqr(x[2]-x_start[2])); send = send + ds; } gsl_odeiv_evolve_free(e1); gsl_odeiv_control_free(c1); gsl_odeiv_step_free(s1); 338 339 340 out_stream.close(); } Etwas länglich ist die Funktion dI, die bei gegebenem Parameter t die dazugehörige Position x0, das Linienelement dx0 ds, sowie dessen Betrag dl und den Abstand r vom Punkt x berechnet. Das liegt an der notwendigen Fallunterscheidung für die verschiedenen Abschnitte der Leiterbahn: 2.5 Magnetfelder stationärer Ströme 91 für t ∈ [0, 1] befindet man sich auf der Spulenwicklung, für t ∈ [1, 2] befindet man sich auf dem daran anschließenden Abschnitt in z-Richtung, – für t ∈ [2, 3] befindet man sich auf dem nächsten Abschnitt, der in xRichtung verläuft, – usw. – – Von den Funktionen dB x, dB y und dB z haben wir hier nur die erste wiedergegeben – die beiden anderen sind völlig analog. Diese Funktionen stellen die Implementierung des Integranden im Biot-Savart-Gesetz (2.20) dar und sind gegenüber dem Programm kreisstrom.cpp unverändert. Das selbe gilt für die Funktionen B x, B y und B z – Sie finden im Buchtext wieder nur B x. Diese berechnen aus dB x, dB y und dB z durch Integration das magnetische Feld. Die Routine f schließlich baut aus diesen Funktionen das Differentialgleichungssystem, dessen Lösung – im Hauptprogramm implementiert – die Feldlinien sind. Abb. 2.5. Magnetisches Feld einer (gestrichelt eingezeichneten) Spule – man beachte, dass die Feldlinien am Rand der Spule wegen der einzelnen Wicklungen onduliert sind und dann aus dem Innenbereich der Spule ausbrechen Das Bild der Feldlinien in Abb. 2.5 entspricht unseren Erwartungen: innerhalb der schematisch eingezeichneten Spule ist das Feld einigermaßen homogen – je näher man aber den Spulendrähten kommt, desto mehr weicht das Feld von dieser Vorstellung ab, weil es die Diskretheit der Drahtwicklungen sieht. Interessanterweise verlassen die äußeren beiden Feldlinien die Spule nicht an den Enden sondern nutzen den Zwischenraum zwischen zwei Drahtwicklungen. Wenn Sie die Feldlinien weiter in den Außenbereich verfolgen – was Sie zur Übung unbedingt tun sollten! – werden Sie feststellen, dass sich auch dort die Feldlinien ungefähr so verhalten, wie man dies erwartet: sie machen einen weiten Bogen um die Spule, treten wieder in diese ein, und die Feldlinie schließt sich – naja, jedenfalls fast – wieder. Soweit so gut, nun wollen wir an den Parametern ein wenig drehen, und schauen, ob wir vielleicht etwas Interessanteres entdecken können. Die Spule, die Abb. 2.6 zugrunde liegt, ist gleich lang wie die aus Abb. 2.5, jedoch hat sie nur den halben Radius und doppelt so viele Wicklungen (20 statt 10). Gezeigt ist nur eine einzelne Feldlinie, die in etwa in der Mitte 92 2 Elektrodynamik 10 5 0 -10 -5 0 5 Abb. 2.6. Eine Feldlinie des magnetischen Felds einer Spule – man beachte, dass sich die Feldlinie nicht schließt der Spule beginnt, wobei wir die ganze Anordnung gedreht haben, um eine maßstabsgerechte Darstellung zu ermöglichen. Im Innenbereich der Spule sehen wir wieder einen weitgehend geradlinigen Verlauf (entsprechend dem hier nahezu homogenen Magnetfeld). Wenn wir diesen Bereich getrennt darstellen und den Maßstab der z-Achse stark vergrößern, stellen wir wieder eine leichte Welligkeit der Feldlinien fest, die von den einzelnen Spulenwicklungen herrührt. Interessant wird es aber vor allem, wenn wir die Feldlinie im Außenbereich verfolgen: die Feldlinie ist hier bei weitem nicht mehr eben und der Weg an das andere Ende der Spule verläuft in unerwarteten Schleifen. Die Tatsache, dass die Feldlinien nicht in einer Ebene verlaufen, sollte uns allerdings nicht sonderlich überraschen, da die Wicklungen der Spule keine entsprechende Symmetrie aufweisen. Dieser Verlauf der Feldlinie im Außenbereich der Spule führt dazu, dass sich die Feldlinie nicht schließt, sondern beim zweiten Durchlauf durch die Spule parallel zum ersten verläuft. 2.6 Hysterese Wir haben bereits in Abschn. 2.1 angemerkt, dass der lineare Charakter der Maxwellschen Gleichungen durch nichtlineare Materialgleichungen zu einem komplexen nichtlinearen Gleichungssystem führen kann. Dort hatten wir auch festgehalten, dass in manchen Fällen überhaupt kein funktionaler Zusammenhang zwischen Polarisation P und elektrischem Feld E bzw. zwischen Magnetisierung M und magnetischem Feld H existiert, sondern Gedächtniseffekte eine Rolle spielen. Dieses Phänomen wird Hysterese genannt und wir wollen in diesem Abschnitt anhand eines einfachen Modells für einen Ferromagneten (siehe [25]) aufzeigen, wie es zu diesem Effekt kommt. Wir gehen von einem System von N Spin- 12 -Teilchen (siehe Abschn. 5.13) aus, die sich in einem äußeren Magnetfeld Hext jeweils entweder parallel oder 2.6 Hysterese 93 antiparallel zu Hext ausrichten können. Wir bezeichnen die Zahl der parallel orientierten Spins mit N1 und die der antiparallel orientierten mit N2 . Wenn wir diese beiden Zahlen im thermischen Gleichgewicht wissen möchten, müssen wir einen Vorgriff auf Kap. 4 machen: N1 E1 − E 2 = exp − . (2.22) N2 kT Dabei sind k die Boltzmann-Konstante und E1 und E2 die jeweiligen Energien der beiden möglichen Spinzustände: E1 = −µH E2 = +µH . (2.23) (2.24) Das magnetische Feld H setzt sich nun aus dem externen Feld Hext und dem durch die Spins selbst erzeugten Feld zusammen. Letzteres ist für jeden Spin verschieden, wenn wir dieses lokale Feld jedoch durch dessen Mittelwert ersetzen (Mean-Field-Näherung) erhalten wir: H = Hext + α(N1 − N2 ) . (2.25) Der zweite Term auf der rechten Seite ist die Magnetisierung M . Der entscheidende Schritt zur Komplettierung unseres Modells ist nun die Aufstellung von Bewegungsgleichungen für N1 und N2 , die bei statischem äußerem Feld für t → ∞ in das durch (2.22) beschriebene Gleichgewicht läuft. Darüber hinaus haben wir zu beachten, dass die Bewegungsgleichungen bzgl. Invertierung des äußeren Feldes Hext bei gleichzeitigem Vertauschen von N1 und N2 symmetrisch sein müssen. Überzeugen Sie sich bitte, dass die geforderten Bedingungen von d N1 = −ξ [(N1 exp(−βH) − N2 exp(βH)] dt d N2 = +ξ [(N1 exp(−βH) − N2 exp(βH)] dt (2.26) (2.27) erfüllt werden. Dabei haben wir zum einen den Parameter ξ eingeführt, der angibt, wie schnell die Spins auf eine Änderung des magnetischen Feldes reagieren. Zum anderen haben wir die Abkürzung β= αµ kT (2.28) verwendet. β setzt die Wechselwirkungsenergie eines einzelnen Spins mit der Magnetisierung ins Verhältnis zur thermischen Energie pro Freiheitsgrad. Durch Subtraktion von (2.26) und (2.27) und Multiplikation mit µ erhält man eine einzige Bewegungsgleichung für die Magnetisierung M = α(N1 − N2 ): d M = −2µξ [N1 exp(βH) − N2 exp(βH)] . dt (2.29) 94 2 Elektrodynamik Die darin auftretenden Größen N1 , N2 und H müssen dabei durch M ausgedrückt werden: M 1 N1,2 = 1± (2.30) 2 α H = Hext + M (2.31) Die Implementierung dieser Bewegungsgleichung stellt uns vor keine besonderen Probleme: 1 2 3 4 5 6 /************************************************************************** * Name: hysterese.cpp * * Zweck: Berechnet eine Hysterese-Kurve * * Gleichung: siehe Buch * * verwendete Bibiliothek: GSL * **************************************************************************/ 7 8 9 10 11 12 13 #include #include #include #include #include #include <iostream> <fstream> <gsl/gsl_odeiv.h> <gsl/gsl_errno.h> <math.h> "tools.h" 14 15 using namespace std; 16 17 18 //-- globale Variablen double mu, kT, xi, H_0, omega, alpha, beta; 19 20 //------------------------------------------------------------------------- 21 22 int f(double t, const double x[], double dxdt[], void *params) 23 24 25 { double H_ext, H, N1, N2; 26 27 28 29 30 H_ext = H_0 * sin(omega*t); H = H_ext + x[0]; N1 = 0.5 * (1. + x[0] / alpha); N2 = 0.5 * (1. - x[0] / alpha); 31 32 dxdt[0] = -2*alpha*xi*(N1*exp(-beta*H)-N2*exp(beta*H)); 33 34 35 return GSL_SUCCESS; } 36 37 //------------------------------------------------------------------------ 38 39 40 int jac(double t, const double x[], double *dfdx, double dxdt[], void *params) 41 42 43 44 { return GSL_SUCCESS; } 45 46 47 //------------------------------------------------------------------------- 2.6 Hysterese 48 95 main( int argc, char *argv[] ) 49 50 { 51 52 53 54 55 //-- Definition der Variablen int n, nout; double x[1], t, t1, dt, atol, rtol, h, tend; double mu1 = 10; 56 57 58 ifstream in_stream; ofstream out_stream; 59 60 61 62 63 64 65 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: hysterese infile outfile\n"; exit(1); } 66 67 68 69 70 71 72 73 74 75 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von hysterese.cpp\n"; inout(mu,"mu"); inout(kT,"kT"); inout(xi,"xi"); inout(alpha,"alpha"); inout(H_0,"H_0"); inout(omega,"omega"); inout(tend,"tend"); inout(nout,"nout"); in_stream.close(); 76 77 78 79 //-- Berechnung einiger benoetigter Parameter -dt = tend / nout; beta = exp(alpha*mu/(kT)); 80 81 82 83 //-- Anfangsbedingungen t = 0; x[0] = 0; atol = 1.e-6; rtol = 1.e-6; 84 85 86 87 88 89 90 //-- Initialisierung der GSL-Routine const gsl_odeiv_step_type *T = gsl_odeiv_step_rkf45; gsl_odeiv_step *s = gsl_odeiv_step_alloc(T, 1); gsl_odeiv_control *c = gsl_odeiv_control_y_new(atol, rtol); gsl_odeiv_evolve *e = gsl_odeiv_evolve_alloc(1); gsl_odeiv_system sys = {f, jac, 1, &mu1}; 91 92 93 94 95 96 97 98 99 100 101 102 for (n=1; n<=nout; n++) { t1 = n * dt; while (t<t1) { int status = gsl_odeiv_evolve_apply(e, c, s, &sys, &t, t1, &h, x); if (status != GSL_SUCCESS) break; } cout << t << " " << H_0*sin(omega*t) << " " << x[0] << "\n"; out_stream << t << " " << H_0*sin(omega*t) << " " << x[0] << "\n"; } 103 104 105 out_stream.close(); } 96 2 Elektrodynamik In dem Programm ist die Zeitabhängigkeit des äußeren Feldes auf Hext = H0 sin(ωt) (2.32) festgelegt (Zeile 27). Für kleine Frequenzen ω stellt sich zu jedem Zeitpunkt das durch (2.22) beschriebene Gleichgewicht ein. Wenn jedoch ω in der Größenordnung von ξ oder darüber liegt, hinkt die Magnetisierung ihrem Gleichgewichtswert immer hinterher und wir beobachten Hysterese: Abb. 2.7. Magnetisierung M in Abhängigkeit vom äußeren Feld Hext , das einen sinusoidalen Zeitverlauf hat. Nach dem transienten Verhalten in der ersten Halbperiode sieht man deutlich die typische Hysterese-Kurve Dieser Abbildung lagen die Parameter µ = kT = ξ = 1, α = 0.6, H0 = 2 sowie ω = 2 zu Grunde. Wir überlassen es dem Leser, zu untersuchen, inwieweit sich die in Abb. 2.7 dargestellte Kurve ändert, wenn man die einzelnen Parameter verändert. Übungen 2.1 Räumlich angeordnete Multipole Sie können die Ausdehnung der Multipole aus Abschn. 2.3 reduzieren, indem Sie statt einer linearen Anordnung der Einzelladungen eine räumliche Anordnung wählen. Berechnen Sie die Abhängigkeit der Feldstärke vom Abstand r und vergleichen Sie die Ergebnisse mit Abb. 2.1. Wie fällt dieser Vergleich für kleine Abstände und für große Abstände aus? Interessant ist es auch, für diese Anordnung die Feldlinien zu berechnen und mit Abb. 2.2 zu vergleichen. 2.2 Feld von Helmholtz-Spulen In Abschn. 2.5 haben wir das Magnetfeld eines Kreisstroms betrachtet. Sie können nun zwei solcher Ringströme koaxial anordnen und erhalten eine Helmholtz-Spule (Abb. 2.8). Typischerweise wird der Abstand der beiden Spulen gleich deren Radius gewählt, wodurch das Magnetfeld in der Mitte der Anordnung sehr homogen Übungen 97 Abb. 2.8. Helmholtz-Spule, bestehend aus zwei Ringströmen. Gestrichelt eingezeichnet wurde die Symmetrieachse der Anordnung wird. Berechnen Sie dieses Feld in der Nähe der Mitte für diesen Abstand, wobei Sie auch Variationen senkrecht zur Symmetrieachse betrachten sollen. Wie sieht das magnetische Feld aus, wenn der Stromfluß in den beiden Ringen nicht wie dargestellt gleichsinnig sondern gegensinnig erfolgt? 2.3 Diamagnetismus Das in Abschn. 2.6 entworfene Modell beschreibt für positive µ – je nach Größe von µ und α ein paramagnetisches oder ferromagnetisches Material, bei dem sich die Spins parallel zum magnetischen Feld ausrichten möchten. Für negative µ ist die energetisch günstige Orientierung hingegen antiparallel zum äußeren Feld, was der Situation bei einem Diamagneten entspricht. Untersuchen Sie zunächst analytisch das Verhalten bei statischem äußeren Feld und anschließend numerisch das bei einem Magnetfeld mit sinusoidalem Zeitverlauf (2.32). 2.4 Magnetisierung und Entmagnetisierung Durchläuft man die Hystereseschleife in Abb. 2.7 und hört bei Hext = 0 auf, verbleibt das Material mit einer endlichen Magnetisierung, die man Remaneszenz nennt. Möchte man dies vermeiden, muss man das äußere Feld langsam abklingen lassen (z.B. in Form einer gedämpften Sinusschwingung). Schreiben Sie – aufbauend auf hysterese.cpp – ein Programm, das diese Entmagnetisierung modelliert. 3 Optik In diesem Kapitel wollen wir einige ausgewählte Probleme der Optik mit Verfahren der numerischen Physik bearbeiten. Da Licht nichts anderes als eine elektromagnetische Welle ist, handelt es sich hierbei streng genommen um ein Unterkapitel zu 2 – aufgrund der Bedeutung der Optik ist es jedoch gerechtfertigt, hierfür ein eigenes Kapitel vorzusehen. Während die ersten Abschnitte sich mit der Strahlenoptik beschäftigen, werden wir uns ab Abschn. 3.7 der Wellenoptik widmen. 3.1 Historischer Überblick Beginnen wir unseren Streifzug durch die Optik mit einem kleinen historischen Rückblick auf deren Geschichte: Nachdem lange Zeit über das Wesen von Licht nur wilde Spekulationen existierten, kristallisierten sich im 17. Jahrhundert zwei unvereinbare Theorien heraus. Die erstere wurde von dem größten Physiker dieser Epoche vertreten, von Isaac Newton. Er war der Ansicht, dass Licht ein Strom von Teilchen ist, deren Flugbahn bei Lichtstrahlen besonders deutlich wird. Aus dieser Vorstellung entwickelte sich die Strahlenoptik. Der Hauptvertreter der zweiten Theorie war Huygens – nach dieser Theorie sollte es sich bei Licht um Schwingungen eines Mediums handeln, das jeden Stoff durchdringt und das er Äther nannte (der nichts mit dem Äther aus der Chemie zu tun hat). Aus dieser Theorie entwickelte sich später die Wellenoptik. Zu Lebzeiten dieser beiden Physiker wurde allgemein die Newtonsche Theorie bevorzugt, zum einen, weil es keinerlei sonstige Hinweise auf den Äther gab, den Huygens’ Theorie benötigte, zum anderen aber auch wegen der Berühmtheit Newtons. Später jedoch häuften sich Hinweise, dass Newton in diesem Punkt doch irrte und die Huygenssche Theorie korrekt sei. Als Mitte des 19. Jahrhunderts die Maxwellschen Gleichungen aufgestellt wurden, die als spezielle Lösungen die elektromagnetischen Wellen und damit Licht beinhalten, schien die Entscheidung über die wahre Natur des Lichts gefallen. Aber das Blatt wendete sich noch einmal – gegen Ende des vorletzten Jahrhunderts gab es die ersten Experimente, die nicht ins Wellenbild des Lichts passten und schließlich zur Entwicklung der Quantentheorie führten: nach unserer heutigen Auffassung sind die beiden oben dargestellten Theorien über das Licht zwei Facetten, von denen je nach Experiment die eine oder 100 3 Optik die andere zum Vorschein kommen. Die Theorie, die beide Aspekte in sich vereinigt und das Licht korrekt beschreibt, ist folgerichtig die Quantenoptik. Es stellt sich natürlich die Frage, warum sich so viele – sich gegenseitig ausschließende – Auffassungen über die Natur des Lichts entwickeln konnten. Die Antwort ist, dass jede dieser Betrachtungsweisen ihre Berechtigung hat, und die Frage, ob die geometrische Optik, die Wellenoptik oder gar nur die Quantenoptik die geeignete Beschreibung für ein konkretes Problem ist, von der Art des Problems abhängt. Wenn alle relevanten Längen groß gegen die Wellenlänge des Lichts (ca 400–800 nm) sind, spielen die Welleneigenschaften keine Rolle und die geometrische Optik kommt zu korrekten Resultaten. Ist dies nicht der Fall, z.B. weil Wegdifferenzen in dieser Größenordnung vorhanden sind, tritt die Wellennatur des Lichts zutage und eine korrekte Beschreibung erfordert deren Berücksichtigung. Die Quantennatur des Lichts schließlich wird relevant, wenn die Energie eines einzelnen Photons (h̄ω, wobei h̄ das Plancksche Wirkungsquantum und ω = 2πf die Frequenz des Lichts ist) nicht mehr klein gegen die alle anderen betrachteten Energien ist. An dieser Stelle ist auch ein Blick auf die Weise interessant, in der Optik gelehrt und gelernt wird. Diese ist nämlich äquivalent zur historischen Entwicklung und beginnt in der gymnasialen Mittelstufe mit der Strahlenoptik und geht dann erst zur Wellenoptik über. Quantenoptik schließlich bleibt, wenn überhaupt, den höheren Semestern im Physikstudium vorbehalten. Und an diese Reihenfolge wollen wir uns auch in diesem Kapitel halten und beginnen daher mit Problemen aus der Strahlenoptik. 3.2 Grundbegriffe der Strahlenoptik Der entscheidende Begriff der Strahlenoptik ist – naheliegenderweise – der des Lichtstrahls. Dieser wird zum einen durch seine Bahn und zum anderen in jedem Punkt seiner Bahnkurve durch seine Intensität, seine Wellenlänge und gegebenenfalls durch seine Polarisation charaktierisiert. Mit der letzten Eigenschaft verlassen wir allerdings streng genommen schon die Strahlenoptik, da die Polarisation die Schwingungsebene des elektrischen und magnetischen Feldes, also eine typische Welleneigenschaft, angibt – näheres hierzu werden wir in Abschn. 3.7 diskutieren. Beginnen wir jedoch mit der Bahnkurve: diese ist in homogenen Ausbreitungsmedien geradlinig, so dass der geometrische Verlauf des Lichtstrahls hier durch eine Geradengleichung (z.B. einen Punkt und einen Richtungsvektor) beschrieben wird. Hierbei bezieht sich die Forderung der Homogenität auf die Brechzahl n und damit die Ausbreitungsgeschwindigkeit des Lichts in diesem Medium. Interessant wird es, wenn wir mehrere Medien mit verschiedenen Brechzahlen haben: in diesem Fall erfolgt an der Grenzfläche zwischen diesen beiden Materialien Brechung und Reflektion (siehe Abschn. 3.3). In solchen Situationen ist es also unsere Aufgabe, eine Abfolge von Geradengleichungen 3.3 Brechung und Reflektion von Licht 101 für den Verlauf des Lichtstrahls aufzustellen und gegebenenfalls die Intensität und Polarisation entlang dieses Strahls zu berechnen. 3.3 Brechung und Reflektion von Licht In der Strahlenoptik geht es um die Berechnung von Lichtstrahlen. Diese verlaufen im Allgemeinen geradlinig, jedoch mit zwei Ausnahmen, die auftreten, wenn ein Lichtstrahl auf die Grenzfläche zwischen zwei Medien mit unterschiedlichen Brechzahlen trifft: x’ n2 x n1 n x’’ Abb. 3.1. Brechung und Reflektion an einer Grenzfläche – Ein Teil des Lichts dringt in das andere Medium ein, wobei sich dessen Richtung ändert. Dieses Phänomen bezeichnet man als Brechung. – Der verbleibende Teil des Lichts wird an der Grenzfläche reflektiert. Diesen Vorgang bezeichnet man als Reflektion. Dies sind also die beiden Elementarprozesse, die wir zunächst beherrschen müssen, bevor wir uns schwierigeren, darauf aufbauenden Problemen zuwenden. Wie in Abb. 3.1 wollen wir den Normalenvektor der Grenzfläche so wählen, dass sein Skalarprodukt mit dem Richtungsvektor des einfallenden Strahls positiv ist. Beginnen wir mit der Darstellung des einfallenden Lichtstrahls: x = x0 + µxt , (3.1) wobei x0 ein beliebig gewählter Punkt auf der Einfallslinie des Lichtstrahls ist. Es ist sinnvoll, den Richtungsvektor xt in eine Komponente in Richtung der Flächennormalen n (3.2) xt = (xt n)n und die dazu senkrechte Komponente xt⊥ = xt − (xt n)n (3.3) 102 3 Optik aufzuspalten. Dann erhalten wir den Richtungsvektor des reflektierten Strahls als xt = xt⊥ − xt = x − 2xt . (3.4) (3.5) Etwas komplizierter ist die Berechnung der Richtung des gebrochenen Strahls. Zunächst müssen wir uns an das Snelliussche Brechungsgesetz erinnern: n1 sin α1 = n2 sin α2 . (3.6) Dabei sind n1 und n2 die Brechungsindizes im Bereich des einfallenden bzw. gebrochenen Lichtrahls. Aus Symmetriegründen muss der gebrochene Lichtstrahl in der Ebene verlaufen, die durch den eingehenden Lichtstrahl und den Normalenvektor der Ebene aufgespannt wird: x = λ1 x + λ2 n . (3.7) Da die in (3.6) auftretenden Terme sin α1 und sin α2 schwer zu bestimmen sind, quadrieren wir die gesamte Gleichung, drücken die sin2 -Terme durch den Kosinus aus und schreiben cos α0 = xt n cos α1 = xt n . (3.8) (3.9) Wir müssen nun die zwei Faktoren λ1 und λ2 in (3.7) so berechnen, dass das Snelliussche Brechungsgesetz (3.6) erfüllt ist: sin2 α1 1 − cos2 α1 = 1 − cos2 α2 sin2 α2 = 1 − (xn)2 = 1 − (x n)2 (3.10) n2 n1 2 . (3.11) Dabei haben wir vorausgesetzt, dass alle Richtungsvektoren normiert sind, was zu der zusätzlichen Bedingung λ21 + λ22 + 2λ1 λ2 xt n = 1 (3.12) führt. Setzen wir in (3.11) die Zerlegung (3.7), so können wir die entstehenden Terme mit λ1 unter Verwendung der Normierungsbedingung (3.12) eliminieren und erhalten n1 . (3.13) λ1 = n2 Die negative Lösung λ1 = −n1 /n2 können wir ausschließen, da der gesuchte Richtungsvektor in Richtung des Normalenvektors zeigen muss. Setzen wir 3.3 Brechung und Reflektion von Licht 103 diesen Ausdruck für λ1 in (3.12) ein und berücksichtigen, dass auch λ2 positiv sein muss (siehe Abb. 3.1), so erhält man (3.14) λ2 = 1 − λ1 (1 − ξ 2 ) − ξλ1 . Dabei haben wir die Abkürzung ξ = nxt (3.15) eingeführt. Wenn wir den Ausdruck für λ2 betrachten, stellen wir fest, dass dieser nicht notwendigerweise reell ist – und nur reellwertige Koeffizienten λ1 und λ2 geben einen physikalisch sinnvollen Richtungsvektor. Wenn eine solches reelles λ2 nicht existiert, wenn also 1 − λ1 (1 − ξ 2 ) < 0 n2 sin α12 > n1 (3.16) (3.17) ist, tritt Totalreflektion ein, d.h. das gesamte Licht wird an der Grenzfläche reflektiert und kein Licht wird in das andere Medium gebrochen. Bei genügend streifendem Einfall wird also jede Grenzfläche zwischen zwei Medien aus der Richtung des optisch dichteren Mediums (das Material mit dem größerem Brechungsindex) totalreflektierend – eine Tatsache, die man in Glasfasern ausnutzt, um den Lichtstrahl in denselben zu halten. Abschließend wollen wir uns noch der Frage zuwenden, welche Intensitäten der reflektierte und der gebrochene Strahl haben – eine Frage, die durch die Fresnelschen Formeln (von denen wir hier nur einen Teil benötigen) beantwortet wird: sin2 (α1 − α2 ) sin2 (α1 + α2 ) tan2 (α1 − α2 ) R⊥ = . tan2 (α1 + α2 ) R = (3.18) (3.19) Der hier angegebene Reflektionskoeffizient R = I2 /I0 hängt von der Polarisationsebene des Lichts ab: Bei Polarisation in der durch Einfallsrichtung und Normalenvektor definierten Ebene gilt (3.18), in der dazu senkrechten Ebene gilt (3.19). Zum Abschluss dieses Abschnitts implementieren wir dessen Inhalt in einem Unterprogramm, das ein wichtiger Bestandteil aller Programme zum Bereich Strahlenoptik sein wird – es ist daher das Einfachste, diesen Teil separat zu kompilieren und bei den folgenden Programmen hinzuzulinken: 1 2 3 4 5 /************************************************************************** * Name: refract.cpp * * Zweck: Unterprogramm zur Berechnung des gebrochenen und * * reflektierten Strahls * * Gleichung: Snelliussches Brechungsgesetz * 104 6 3 Optik **************************************************************************/ 7 8 9 10 11 12 int refract(double x_t, double y_t, double z_t, double n_x, double n_y, double n_z, double* xs_t, double* ys_t, double* zs_t, double* xss_t, double* yss_t, double* zss_t, double I, double* Is, double* Iss, double n_0, double n_1) 13 14 15 { double norm, xi, xi2, lambda_1, lambda_2, alpha_1, alpha_2, R_1, R_2; 16 17 // Berechnung der Richtung des reflektierten Strahls 18 19 20 21 22 23 24 25 26 xi = x_t*n_x + y_t*n_y + z_t*n_z; *xs_t = x_t - 2.*xi* n_x; *ys_t = y_t - 2.*xi* n_y; *zs_t = z_t - 2.*xi* n_z; norm = 1. / sqrt( sqr(*xs_t)+sqr(*ys_t)+sqr(*zs_t) ); *xs_t = *xs_t * norm; *ys_t = *ys_t * norm; *zs_t = *zs_t * norm; 27 28 29 // Berechnung der Richtung des gebrochenen Strahls xi2 = sqr(xi); 30 31 32 lambda_1 = n_0 / n_1; lambda_2 = sqrt( 1. - lambda_1*(1.-xi2) ) - xi*lambda_1; 33 34 35 36 37 38 39 40 *xss_t *yss_t *zss_t norm = *xss_t *yss_t *zss_t = lambda_1 = lambda_1 = lambda_1 1. / sqrt( = *xss_t * = *yss_t * = *zss_t * * x_t + lambda_2 * n_x; * y_t + lambda_2 * n_y; * z_t + lambda_2 * n_z; sqr(*xss_t)+sqr(*yss_t)+sqr(*zss_t) ); norm; norm; norm; 41 42 43 44 45 46 // Berechnung der Intensitaeten alpha_1 = acos(xi); alpha_2 = acos(*xss_t*n_x + *yss_t*n_y + *zss_t*n_z); R_1 = sqr(sin(alpha_1-alpha_2)/sin(alpha_1+alpha_2)); R_2 = sqr(tan(alpha_1-alpha_2)/tan(alpha_1+alpha_2)); 47 48 49 *Is = 0.5 * I * (R_1+R_2); *Iss = I - *Is; 50 51 52 return 1; } Zu beachten ist, dass dieses Programm normierte Richtungsvektoren als Eingabeparameter voraussetzt und auch normierte Richtungsvektoren für den refelektierten und den gebrochenen Strahl liefert (siehe Zeile 20–26 und 37– 47). Auch dass der Richtungsvektor des einfallenden Strahls und der Normalenvektor der Grenzfläche nicht in entgegengesetzte Richtung zeigen, muss man beim Aufruf von refract sicherstellen, da eine entsprechende Korrektur in der Routine selbst nicht vorgenommen wird. Der Rückgabewert von refract ist im Fall von Totalreflektion null und ansonsten eins. 3.4 Brechung an einer Linsenfläche 105 Der Einfachheit halber haben wir angenommen, dass das einfallende Licht unpolarisiert ist, so dass wir für den Reflektionskoeffizienten den Mittelwert der beiden Werte aus den Fresnelschen Formeln nehmen konnten. Beachten Sie, dass selbst unter dieser Annahme der reflektierte und gebrochene Strahl teilweise polarisiert sind, so dass die sukzessive Anwendung von refract eigentlich nicht korrekt ist – ein Detail, das wir im Folgenden unberücksichtigt lassen werden. Die Korrektur dieser Ungenauigkeit wäre nicht schwierig – sie erfordert lediglich eine Auftrennung in die beiden Polarisationsebenen – führt jedoch zu längeren und unübersichtlicheren Programmen, so dass wir in diesem Rahmen darauf verzichtet haben. Der Leser, der jedoch ein vertieftes Interesse an der Optik hat, sollte diese Lücke durch eine entsprechende Erweiterung des Programms durchführen. 3.4 Brechung an einer Linsenfläche Nachdem wir im vorangegangenen Abschnitt die Grundlagen von Reflektion und Brechung besprochen haben, können wir diese jetzt benutzen, um die Brechung von Licht an einer Linsenoberfläche zu berechnen. Wir beschränken uns dabei auf Grenzflächen, die Teile von Kugeln sind – diese Bedingung ist bei den meisten Linsenoberflächen erfüllt, da diese sphärischen Linsen einfacher und damit billiger herzustellen sind als die sogenannten asphärischen Linsen. Sie können die im Folgenden verwendeten Bezeichnungen Abb. 3.2 entnehmen, wobei wir – im Vorgriff auf die folgenden Abschnitte – uns dabei nicht auf eine einzelne Linsenoberfläche beschränkt haben, sondern eine komplette Linse mit einem Objektpunkt und dem dazugehörigen Bildpunkt betrachten. Die Linse hat zwei Linsenflächen – alle Größen, die zur linken Linsenoberfläche gehören tragen den Index 1, während die entsprechenden Größen zur rechten Linsenfläche am Index 2 erkennbar sind. Die Position der Linsenfläche auf der optischen Achse (die in z-Richtung orientiert ist) kennzeichnen wir durch deren Scheitelpunkt z1 bzw. z2 . Die Krümmung der Linsenflächen wird durch den Radien r1 und r2 beschrieben. Den Linsendurchmesser – der für beide Linsenflächen im Normalfall identisch sein muss – schließlich bezeichnen wir mit 2R. Die Position des Objekt- sowie des Bildpunkts (die wir Abb. 3.2. Schematischer Aufbau einer Linse inklusive einem Objektpunkt (links im Bild) und dem dazugehörigen Bildpunkt (rechts) 106 3 Optik allerdings erst in den späteren Abschnitten benötigen) charakterisieren wir durch ihre Lage auf der optischen Achse z0 bzw. z0 sowie ihre Objekthöhe l bzw. (d.h. den Abstand von der optischen Achse). Bevor wir in die Rechnung einsteigen, wollen wir Ihnen noch einen kurzen Überblick über die Vorgehensweise zur Konstruktion des Strahlengangs geben: – Wir beginnen mit einem Strahl, der durch einen Punkt und durch einen Richtungsvektor gegeben ist, – dann berechnen wir den ersten Schnittpunkt mit der Linsenoberfläche und den Normalenvektor in diesem Punkt, – anschließend verwenden wir die Routine des vorigen Abschnitts um die Richtung des gebrochenen und/oder des reflektierten Strahls zu berechnen. Beginnen wir also mit der Berechnung des Schnittpunktes von Linsenoberfäche und einfallendem Lichstrahl. Die Linsenoberfläche wird durch x2 + y 2 + (z − zc )2 = r2 (3.20) beschrieben, wobei zc die z-Koordinate des Kugelmittelpunktes und r der Kugelradius ist. Der Lichtstrahl hingegen durchläuft alle Punkte entlang der Bahn (3.21) x = x0 + µxt . Setzen wir dies in (3.20) ein, erhalten wir eine quadratische Bestimmungsgleichung für µ und damit Kandidaten für potentielle Punkte, bei denen der Lichtstrahl in die Linse eindringt: (x0 + µxt )2 + (y0 + µyt )2 + (z0 + µzt − zc )2 = r2 . (3.22) Wir multiplizieren aus, fassen die Terme gleicher Ordnung in µ zusammen und erhalten so eine quadratische Gleichung in der Standardform µ2 + 2aµ + b = 0 (3.23) mit a = x0 xt + y0 yt + (z0 − zc )zt b= x20 + y02 + z02 + zc2 (3.24) − 2z0 zc − r . 2 (3.25) Hat diese quadratische Gleichung keine Lösung, verfehlt der Lichtstrahl die Linse, aber auch wenn es eine reelle Lösung gibt, ist nicht gesagt, dass der Lichtstrahl tatsächlich die Linse trifft – dazu müssen noch zwei weitere Bedingungen erfüllt sein: – Das gefundene µ muss positiv sein, ansonsten hat der Lichtstrahl nämlich die Linse verlassen, bevor er unseren Objektpunkt durchlaufen hat. 3.4 Brechung an einer Linsenfläche – 107 Der Auftreffpunkt muss zwischen dem linken (zl ) und dem rechten Rand der Linse (zr ) liegen. Die Lösung der quadratischen Gleichung (3.23) lautet: µ1,2 = −a ± a2 − b . (3.26) Durch Betrachten der Determinante a2 − b können wir entscheiden, ob überhaupt reelle Lösungen existieren, und wenn ja, entscheiden wir anhand der beiden gewonnenen Lösungen, welche davon die relavante ist. Den so gewonnenen Eintrittspunkt bezeichnen wir im Weiteren mit x1 = (x1 , y1 , z1 ). Um dies zu überprüfen, bauen wir das daraus resultierende Unterprogramm gleich in ein kleines Testprogramm ein: 1 2 3 4 5 6 /************************************************************************** * Name: strahl1.cpp * * Zweck: Brechung an einer Linsenoberflaeche * * Gleichung: Snelliussches Brechungsgesetz * * Methode: Konstruktion von Lichtstrahlen * **************************************************************************/ 7 8 9 10 11 #include #include #include #include <iostream> <fstream> <math.h> "tools.h" 12 13 using namespace std; 14 15 #include "refract.cpp" 16 17 //------------------------------------------------------------------------- 18 19 20 21 22 23 int hit(double x_0, double y_0, double z_0, double x_t, double y_t, double z_t, double* x_1, double* y_1, double* z_1, double* n_x, double* n_y, double* n_z, double r, double z_l, double z_r, double z_c) 24 25 26 27 28 { double t_1, t_2, a, b, d, diskri; int ret_val; 29 30 ret_val = 0; 31 32 33 a = x_0*x_t + y_0*y_t + (z_0-z_c)*z_t; b = sqr(x_0) + sqr(y_0) + sqr(z_0) + sqr(z_c) - 2*z_0*z_c - sqr(r); 34 35 36 diskri = sqr(a) - b; if (diskri < 0) return 0; 37 38 39 t_1 = -a + sqrt(diskri); t_2 = -a - sqrt(diskri); 40 41 42 43 if (t_2 > 0) { *z_1 = z_0 + t_2 * z_t; 108 3 Optik if ((*z_1 >= z_l) && (*z_1 <= z_r)) { ret_val = 1; *x_1 = x_0 + t_2 * x_t; *y_1 = y_0 + t_2 * y_t; } else return 0; } else if (t_1 > 0) { *z_1 = z_0 + t_1 * z_t; if ((*z_1 > z_l) && (*z_1 < z_r)) { ret_val = 1; *x_1 = x_0 + t_1 * x_t; *y_1 = y_0 + t_1 * y_t; } else return 0; } else return 0; 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 d = sqrt(sqr(*x_1) + sqr(*y_1) + sqr(z_c-*z_1)); 65 66 *n_x = -*x_1 / d; *n_y = -*y_1 / d; *n_z = (z_c - *z_1) / d; 67 68 69 70 if (r < 0) { *n_x = -*n_x; *n_y = -*n_y; *n_z = -*n_z; } 71 72 73 74 75 76 77 return ret_val; 78 79 } 80 81 //------------------------------------------------------------------------- 82 83 int main( int argc, char *argv[] ) 84 85 86 87 88 89 90 91 { int n, hit_result; double x0, y0, z0, x1, y1, z1, xs_t, ys_t, zs_t, xss_t, yss_t, zss_t; double Is, Iss, z_l, z_r, z_c, z_1, R, n_x, n_y, n_z, r1, length; double n0, n1, phi_start, phi_end; ifstream in_stream; ofstream out_stream; 92 93 94 95 96 97 98 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: strahl1 infile outfile\n"; return 0; } 99 100 101 102 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); 3.4 Brechung an einer Linsenfläche 103 104 105 106 107 109 out_stream << "! GLE-Datei generiert von strahl1.cpp\n"; inout(z_1,"z_1"); inout(R,"R"); inout(r1,"r1"); inout(n0,"n0"); inout(n1,"n1"); in_stream.close(); 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 if (r1 > 0) { z_l = z_1; z_c = z_l + z_r = z_c phi_start = phi_end = } else { z_r = z_1; z_c = z_r + z_l = z_c phi_start = phi_end = } r1; r1 * cos(asin(R/r1)); 180 * acos((z_r-z_c)/r1)/pi; 180 * (2*pi-acos((z_r-z_c)/r1))/pi; r1; r1 * cos(asin(R/r1)); -180 * acos(-(z_l-z_c)/r1)/pi; +180 * acos(-(z_l-z_c)/r1)/pi; 125 126 y0 = 0; z0 = 0; 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 out_stream out_stream out_stream out_stream out_stream << "size 22*0.2 10*0.5\n"; << "set lwidth 0.1\n"; << "begin scale 0.2 0.2\n"; << "amove " << 1+z_c << " " << 12 << "\n"; << "arc " << fabs(r1) << " " << phi_start << " " << phi_end << "\n"; for (n = -10; n <= 10; n++) { x0 = n; out_stream << "amove 1 " << x0+12 << "\n"; hit_result = hit(x0,y0,z0,0,0,1,&x1,&y1,&z1,&n_x,&n_y, &n_z,r1,z_l,z_r,z_c); if (hit_result == 1) { refract(0,0,1,n_x,n_y,n_z,&xs_t,&ys_t,&zs_t,&xss_t, &yss_t,&zss_t,1,&Is,&Iss,n0,n1); length = min( (20-z1) / zss_t, fabs((fabs(x1)+10)/xss_t) ); out_stream << "aline " << z1+1 << " " << x1+12 << "\n"; out_stream << "rline " << length*zss_t << " " << length*xss_t << "\n"; } else out_stream << "rline 20 0\n"; } out_stream << "end scale\n"; out_stream.close(); } Zunächst eine Bemerkung zur Funktion hit, die den Auftreffpunkt auf die Linsenoberfläche und den zugehörigen Normalenvektor berechnet. Die Funktion ist eins, wenn die Oberfläche getroffen wird und ansonsten null. Wenn sie den Wert eins hat liefert sie in den Variablen x 1, y 1 und z 1 die Koordinaten des Auftreffpunktes und in den Variablen n x bis n z die Komponenten des Normalenvektors, der bereits in Richtung des einfallenden 110 3 Optik Lichtstrahls normiert ist (Zeile 67–76). Da wir uns an dieser Stelle lediglich für den geometrischen Aspekt der Strahlverläufe interessieren, sind die Intensitäten irrelevant. Das Testprogramm berechnet die Strahlverläufe für einen Satz parallel einlaufender Strahlen und schreibt die Ergebnisse in ein GLEFile strahl1.gle, so dass wir uns diese Strahlverläufe direkt anschauen und entscheiden können, ob wir mit dem Ergebnis zufrieden sind. Typische Bilder für n0 < n1 bzw n0 > n1 sehen etwa so aus: Abb. 3.3. Strahlverlauf beim Übergang vom optisch dünneren ins optisch dichtere Medium (a) und umgekehrt (b) Nachdem wir uns davon überzeugt haben, dass die Unterprogramme refract und hit funktionieren und vernünftige Ergebnisse liefern, ist der nächste Schritt verhältnismäßig einfach: wir müssen nach dem Eintritt in die Linse noch den Austritt aus der Linse berechnen, wozu wir – abgesehen von Deklaration und Einlesen einiger neuer Parameter – nur den letzten Teil des Programms ändern müssen: ... 153 154 155 156 157 158 159 160 161 162 out_stream out_stream out_stream out_stream out_stream << << << << << << out_stream << out_stream << << "size 27*0.2 10*0.5\n"; "set lwidth 0.1\n"; "begin scale 0.2 0.2\n"; "amove " << 1+z1_c << " " "arc " << fabs(r1) << " " " " << phi1_end << "\n"; "amove " << 1+z2_c << " " "arc " << fabs(r2) << " " " " << phi2_end << "\n"; << 12 << "\n"; << phi1_start << 12 << "\n"; << phi2_start 163 164 165 166 167 168 169 170 171 172 173 174 175 176 for (n = -10; n <= 10; n++) { x0 = n+0.01; out_stream << "amove 1 " << x0+12 << "\n"; hit_result = hit(x0,y0,z0,0,0,1,&xa,&ya,&za,&n_x,&n_y, &n_z,r1,z1_l,z1_r,z1_c); if (hit_result == 1) { refr_result = refract(x_t,y_t,z_t,n_x,n_y,n_z, &dummy_1,&dummy_2,&dummy_3, &xs_t,&ys_t,&zs_t,1.,&dummy_4,&Is,n0,n1); hit_result = hit(xa,ya,za,xs_t,ys_t,zs_t,&xb,&yb,&zb,&n_x,&n_y,&n_z, r2,z2_l,z2_r,z2_c); 3.5 Bild durch eine Linse – Linsenfehler 111 refr_result = refract(xs_t,ys_t,zs_t,n_x,n_y,n_z, &dummy_1,&dummy_2,&dummy_3, &xss_t,&yss_t,&zss_t,Is,&dummy_4,&Iss,n1,n0); out_stream << "aline " << za+1 << " " << xa+12 << "\n"; out_stream << "aline " << zb+1 << " " << xb+12 << "\n"; length = min( (25-zb) / zss_t, fabs((fabs(xb)+10)/xss_t) ); out_stream << "rline " << length*zss_t << " " << length*xss_t << "\n"; } else out_stream << "rline 25 0\n"; 177 178 179 180 181 182 183 184 185 186 187 } out_stream << "end scale\n"; out_stream.close(); 188 189 190 191 192 } Das geänderte Programm finden Sie als strahl2.cpp bzw. strahl2.f auf der CD-ROM. Mit dessen Hilfe erhalten wir nun den typischen Strahlengang durch eine Linse in Abb. 3.4: Abb. 3.4. Strahlverlauf beim Durchgang durch eine sphärische Linse Bei genauem Hinsehen stellen Sie fest, dass sich die Strahlen nicht alle in einem Punkt – dem Brennpunkt – kreuzen, wie dies für eine perfekte Linse der Fall sein sollte. Vielmehr schneiden die achsfernen Strahlen die optische Achse näher an der Linse als dies bei den achsnahen Strahlen der Fall ist. Dieser Linsenfehler rührt daher, dass die Linsenoberflächen Sphären – also Teile von Kugeln – sind, was nicht der idealen Oberflächenform entspricht. Aus diesem Grund bezeichnet man diesen Linsenfehler als sphärische Aberration. 3.5 Bild durch eine Linse – Linsenfehler Nach diesem Testprogramm können wir davon ausgehen, dass das Unterprogramm refract korrekte Ergebnisse liefert. Dieses werden wir für unsere nächste Aufgabe – der Konstruktion des Bildes eines Objektpunkts in endlicher Entfernung von der Linse – weiterverwenden. Bevor wir uns dem Umbau unseres bisherigen Programms zuwenden können, müssen wir uns zunächst überlegen, wie die Lichtintensität an einem Punkt in der Bildebene zustande kommt und wie sie demzufolge zu berechnen ist 112 3 Optik In der Strahlenoptik sendet die Lichtquelle Licht in den Raum hinaus, wobei bei einer isoptropen Lichtquelle die Intensität des ausgestrahlten Lichts unabhängig von der Richtung ist. In diesem Falle ist der Energiefluss in einen Raumwinkel dΩ = sin(θ)dθdφ proportional zum Raumwinkel selbst. Im freien Raum ist der Strahlverlauf aller Strahlen in einem solchen Raumwinkel sehr einfach und man erkennt sofort, dass die Fläche, die dieses Raumwinkelelement aus einer Kugelfläche im Abstand r ausschneidet, proportional zu r2 ist. Auf diese Fläche verteilt sich nun die Energie, die von der Lichtquelle ausgestrahlt wurde, was bedeutet, dass die Intensität einer Lichtquelle proportional zum Quadrat des Abstands von ihr abnimmt. Eine optische Vorrichtung wie z.B. eine Linse verändert nun den weiteren Strahlverlauf und führt dazu, dass die Fläche auf der Bildebene, auf der sich das Winkelelement verteilt, eine völlig andere ist. Was müssen wir also tun, um die Intensität an einem bestimmten Punkt (x1 ,y1 ) in der Bildebene zu bestimmen? Eine einfache Möglichkeit ist es, eine Reihe von Lichtstrahlen, die vom Punkt in der Objektebene ausgehen, zu betrachten und ihren Strahlverlauf zu berechnen. Jeder dieser Lichtstrahlen trägt zur Intensitätsverteilung in der Bildebene bei, und entsprechend summieren wir diese Einzelintensitäten auf: 1 2 3 4 5 6 /************************************************************************** * Name: strahl3.cpp * * Zweck: Brechung an zwei Linsenoberflaechen * * Gleichung: Snelliussches Brechungsgesetz * * Methode: Konstruktion von Lichtstrahlen * **************************************************************************/ 7 8 9 10 11 12 #include #include #include #include #include <iostream> <fstream> <math.h> <gsl/gsl_matrix.h> "tools.h" 13 14 using namespace std; 15 16 //-- globale Variablen 17 18 double r1, r2, n0, n1, z1_l, z1_r, z1_c, z2_l, z2_r, z2_c, Iss; 19 20 #include "refract.cpp" 21 22 //------------------------------------------------------------------------- 23 24 25 26 27 28 int hit(double x_0, double y_0, double z_0, double r_x, double r_y, double r_z, double* x_1, double* y_1, double* z_1, double* n_x, double* n_y, double* n_z, double r, double z_l, double z_r, double z_c) 29 30 31 32 33 { int hit_result; double t_1, t_2, a, b, diskri, xi, d; 3.5 Bild durch eine Linse – Linsenfehler 34 113 hit_result = 0; 35 36 37 a = x_0*r_x + y_0*r_y + (z_0-z_c)*r_z; b = sqr(x_0) + sqr(y_0) + sqr(z_0) + sqr(z_c) - 2*z_0*z_c - sqr(r); 38 39 40 diskri = sqr(a) - b; if (diskri < 0) return 0; 41 42 43 t_1 = -a + sqrt(diskri); t_2 = -a - sqrt(diskri); 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 if (t_2 > 0) { *z_1 = z_0 + t_2 * r_z; if ((*z_1 >= z_l) && (*z_1 <= z_r)) { hit_result = 1; *x_1 = x_0 + t_2 * r_x; *y_1 = y_0 + t_2 * r_y; } else return 0; } else if (t_1 > 0) { *z_1 = z_0 + t_1 * r_z; if ((*z_1 >= z_l) && (*z_1 <= z_r)) { hit_result = 1; *x_1 = x_0 + t_1 * r_x; *y_1 = y_0 + t_1 * r_y; } else return 0; } else return 0; 69 d = sqrt(sqr(*x_1) + sqr(*y_1) + sqr(z_c-*z_1)); 70 71 *n_x = -*x_1 / d; *n_y = -*y_1 / d; *n_z = (z_c - *z_1) / d; 72 73 74 75 xi = (*n_x)*r_x + (*n_y)*r_y + (*n_z)*r_z; if (xi < 0) { *n_x = -(*n_x); *n_y = -(*n_y); *n_z = -(*n_z); } 76 77 78 79 80 81 82 83 return hit_result; 84 85 } 86 87 //------------------------------------------------------------------------- 88 89 int main( int argc, char *argv[] ) 90 91 92 { double x0, y0, z0, z1, x_t, y_t, z_t, z1_1, z2_1, R, Is, Iss, I_max, I; 114 93 94 95 96 97 98 99 100 3 Optik double xs_t, ys_t, zs_t, x1s, y1s, xss_t, yss_t, zss_t; double dummy_1, dummy_2, dummy_3, dummy_4, n_x, n_y, n_z; double theta, theta_max, dtheta, phi, phi_max, dphi; double xmin, xmax, dx, ymin, ymax, dy, xa, ya, za, xb, yb, zb, t; ifstream in_stream; ofstream out_stream; int hit_result, refr_result, nx, nx_max, ny, ny_max; int n_phi, n_phi_max, n_theta, n_theta_max; 101 102 103 104 105 106 107 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: strahl3 infile outfile\n"; exit(1); } 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von strahl3.cpp\n"; inout(z1_1,"z1_1"); inout(r1,"r1"); inout(z2_1,"z2_1"); inout(r2,"r2"); inout(R,"R"); inout(n0,"n0"); inout(n1,"n1"); inout(xmin,"xmin"); inout(xmax,"xmax"); inout(ymin,"ymin"); inout(ymax,"ymax"); inout(nx_max,"nx_max"); inout(ny_max,"ny_max"); inout(phi_max,"phi_max"); inout(theta_max,"theta_max"); inout(n_phi_max,"n_phi_max"); inout(n_theta_max,"n_theta_max"); inout(x0,"x0"); inout(y0,"y0"); inout(z0,"z0"); inout(z1,"z1"); out_stream << "! Spalte 1: x Spalte 2: y Spalte 3: I(x,y)\n"; in_stream.close(); 125 126 gsl_matrix *I_ges = gsl_matrix_alloc(nx_max,ny_max); 127 128 129 dx = (xmax-xmin) / nx_max; dy = (ymax-ymin) / ny_max; 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 //-- Berechnung einiger benoetigter Parameter dphi = phi_max / n_phi_max; dtheta = (theta_max-pi/2.) / n_theta_max; if (r1 > 0) { z1_l = z1_1; z1_c = z1_l + r1; z1_r = z1_c - r1 * cos(asin(R/r1)); } else { z1_r = z1_1; z1_c = z1_r + r1; z1_l = z1_c - r1 * cos(asin(R/r1)); } 146 147 148 149 150 151 if (r2 { z2_l z2_c z2_r > 0) = z2_1; = z2_l + r2; = z2_c - r2 * cos(asin(R/r2)); 3.5 Bild durch eine Linse – Linsenfehler 152 153 154 155 156 157 158 115 } else { z2_r = z2_1; z2_c = z2_r + r2; z2_l = z2_c - r2 * cos(asin(R/r2)); } 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 //-- Intensitaeten in der Bildebene bestimmen for (n_phi = -n_phi_max; n_phi<=n_phi_max; n_phi++) { if ((n_phi % 100) == 0) cout << n_phi << " von " << n_phi_max << "\n"; for (n_theta = -n_theta_max; n_theta<=n_theta_max; n_theta++) { phi = n_phi * dphi; theta = n_theta * dtheta + pi/2.; I = cos(theta); x_t = cos(phi) * cos(theta); y_t = sin(phi) * cos(theta); z_t = sin(theta); hit_result = hit(x0,y0,z0,x_t,y_t,z_t,&xa,&ya,&za, &n_x,&n_y,&n_z,r1,z1_l,z1_r,z1_c); if (hit_result == 1) { refr_result = refract(x_t,y_t,z_t,n_x,n_y,n_z,&dummy_1,&dummy_2, &dummy_3,&xs_t,&ys_t,&zs_t,1.,&dummy_4,&Is,n0,n1); hit_result = hit(xa,ya,za,xs_t,ys_t,zs_t,&xb,&yb,&zb, &n_x,&n_y,&n_z,r2,z2_l,z2_r,z2_c); refr_result = refract(xs_t,ys_t,zs_t,n_x,n_y,n_z,&dummy_1,&dummy_2, &dummy_3,&xss_t,&yss_t,&zss_t,Is,&dummy_4,&Iss,n1,n0); t = (z1-zb) / zss_t; x1s = xb + t * xss_t; y1s = yb + t * yss_t; nx = int((x1s-xmin)/dx); ny = int((y1s-ymin)/dy); if ((nx>=0) && (nx<nx_max) && (ny>=0) && (ny<ny_max)) gsl_matrix_set(I_ges,nx,ny,gsl_matrix_get(I_ges,nx,ny)+Iss); } } } 192 193 194 195 196 197 198 199 200 201 //-- Intensitaeten in der Bildebene abspeichern und Maximum bestimmen I_max = 0; for (nx = 0; nx<nx_max; nx++) for (ny = 0; ny<ny_max; ny++) { I = gsl_matrix_get(I_ges,nx,ny); out_stream << nx << " " << ny << " " << I << "\n"; if (I > I_max) I_max = I; } 202 203 204 205 out_stream << "! Max. Intensity = " << I_max <<"\n"; out_stream.close(); } Die beiden Unterprogramme refract und hit konnten wir ungeändert übernehmen, so dass wir diese nicht erneut besprechen müssen. Der Rest des Programms ist verhältnismäßig einfach: eine große Zahl von Strahlen wird 116 3 Optik ausgehend vom Objektpunkt verfolgt und trägt zur Intensitätsverteilung in der Bildebene bei. Um Abb. 3.5 zu erzeugen haben wir Parameter gewählt, die für eine gute Abbildungsleistung ziemlich ungünstig sind: – einen kleinen Abstand des Objektes von der Linse, – eine große Linse, – einen Objektpunkt weit weg von der optischen Achse. Abb. 3.5. Intensitätsverteilung in der Bildebene durch den Linsenfehler Koma. Die Intensität des korrekten Lichtpunktes (rechts im Bild) wurde reduziert, um den Linsenfehler deutlicher hervortreten zu lassen Sie sehen, dass ein Lichtpunkt – wie erwartet – durch die Linsenfehler nicht auf einen Punkt in der Bildebene abgebildet wird. Hingegen ist vielleicht die Tatsache überraschend, dass wir nicht einfach einen diffusen Fleck erhalten, sondern ein Gebilde, das durchaus eine gewisse Ästhetik ausstrahlt. Der Hauptteil des Lichtes konzentriert sich in dem Lichtpunkt auf der rechten Seite, das Streulicht verteilt sich in dem ’Schweif’ nach links, wobei hier insbesondere der begrenzende Rand ganz links und der Punkt genau in der Mitte ausgeleuchtet wird. Dieser Linsenfehler kommt vor allem daher, dass unser Objektpunkt sehr weit von der optischen Achse entfernt gewählt wurde und dass wir eine große Linse betrachten. Aufgrund seiner optischen Erscheinung wird dieser Linsenfehler Koma genannt. Andere Linsenfehler lassen sich mit dem Programm durch Variation der Parameter (siehe Übungsaufgabe 3.1) erzeugen. 3.6 Entstehung eines Regenbogens Die vielleicht eindrucksvollste Demonstration der spektralen Zerlegung von weißem Licht in der Natur ist der Regenbogen. Man sieht ihn, wenn hinter 3.6 Entstehung eines Regenbogens 117 einem die Sonne scheint und es gleichzeitig vor einem regnet. Unter besonders günstigen Bedingungen sieht man sogar zwei Regenbögen, wobei der äußere schwächer ist und die Reihenfolge der Spektralfarben gegenüber dem äußeren invertiert ist. Hier wollen wir zunächst qualitativ erklären, wie es zum Regenbogen kommt, und dann das Problem numerisch angehen, wobei die entstehende Theorie in der Lage sein sollte, sowohl die Existenz mehrerer Regenbögen zu erklären als auch Größe und Reihenfolge der Farben vorherzusagen. 3.6.1 Qualitative Erklärung des Regenbogens Das Licht, das von der Sonne die Erde erreicht, trifft auf einen Regentropfen, den wir zunächst einmal kugelförmig annehmen. Am Auftrittspunkt A (siehe Abb 3.6) an der Grenzfläche Luft–Wasser wird ein Teil dieses Lichts reflektiert, während der Rest in den Wassertropfen eintritt. Dieser Rest trifft nach Durchqueren des Wassertropfens bei B auf die Grenzfläche Wasser–Luft, wo wieder ein Teil austritt, während der Rest reflektiert wird und also im Wassertropfen verbleibt und ihn abermals durchquert. Danach tritt bei C wiederum ein Teil aus, während der übrige Teil reflektiert wird – und immer so weiter. Abb. 3.6. Strahlverlauf beim Durchgang durch einen Wassertropfen. Dargestellt ist der Strahlverlauf, der für den Hauptregenbogen verantwortlich ist Es gibt also eine Vielzahl möglicher Wege, die zum einen durch die Zahl der Reflektionen an der Grenzschicht Wasser–Luft gekennzeichnet sind, zum anderen werden diese Wege aber auch von dem Winkel α zwischen der usprünglichen Richtung des Sonnenstrahls und der Verbindungslinie des Wassermittelpunkts und dem ersten Auftrittspunkt A bestimmt. Interessant ist nun, in welche Richtungen besonders viel Licht vom Regentropfen gestreut wird. Um dies zu verstehen, betrachten wir Abb. 3.7, bei der wir statt einem Lichtstrahl eine ganze Reihe parallel einfallender Lichtstrahlen betrachten, allerdings haben wir zwecks Übersichtlichkeit auf den auslaufenden Teil jedes Lichtstrahls nach einer Reflektion beschränkt und alle anderen Teile weggelassen. Wir sehen, dass das ganze Licht mehr oder weniger zurückgestreut wird, nichts wird in Vorwärtsrichtung gestreut. Wir bezeichnen den Winkel 118 3 Optik Abb. 3.7. Richtung des rückgestreuten Lichts nach Passieren eines Regentropfens (bei einfacher Reflektion im Regentropfen). Der einfallende Lichtstrahl kommt wie in Abb. 3.6 von links zwischen einfallendem Licht und rückgestreutem Licht mit α. Dieser variiert zwischen einem minimalen Ablenkungswinkel −αmax und einem Maximum αmax . Wir werden sehen, dass diese Extrema die Richtungen sind, in die besonders viel Licht gestreut wird. Nicht berücksichtigen werden wir die Interferenz von Strahlen, die in die gleiche Richtung gestreut werden – ein Effekt, der in der Wirklichkeit zu einer, wenn auch geringen Abhängigkeit der genauen Struktur des Regenbogens von der Größe der Regentropfen führt. 3.6.2 Quantititave Vorüberlegungen Wie wir den gerade beschriebenen Strahlverläufen entnehmen, treten nur zwei Elementarprozesse auf: Brechung (jeweils beim Ein- und Austritt) und Reflektion. Wenn wir ein Unterprogramm haben, das für einen beliebigen Lichtstrahl den nächsten Auftreffpunkt auf die Grenzfläche Wasser–Luft berechnet und sowohl die Intensität als auch die Richtung des reflektierten und des gebrochenen Strahls ausgibt, können wir den gesamten Strahlverlauf durch wiederholte Aufrufe dieses Unterprogramms erhalten. An dieser Stelle wollen wir die ursprüngliche Annahme eines kugelförmigen Wassertropfens durch die vielleicht wirklichkeitsgetreuere Beschreibung durch ein Rotationsellipsoid ersetzen. Wenn wir den Wassertropfen in den Ursprung des Koordinatensystems setzen, wird seine Oberfläche durch die Gleichung x2 y2 z2 + 2 + 2 =1 (3.27) 2 a a b beschrieben. Hierbei sind a bzw. b die Radien des Wassertropfens in x- und y- bzw. in z-Richtung. Alternativ können wir auch eine Parameterdarstellung durch zwei Winkel ϑ und φ wählen: ⎛ ⎞ ⎛ ⎞ x a cos φ sin ϑ ⎝ y ⎠ = ⎝ a sin φ sin ϑ ⎠ . (3.28) z b cos ϑ 3.6 Entstehung eines Regenbogens Wenn wir den Lichtstrahl in der Parameterdarstellung ⎛ ⎞ ⎛ ⎞ ⎛ ⎞ x x0 xt ⎝ y ⎠ = ⎝ y0 ⎠ + α ⎝ yt ⎠ . z z0 zt 119 (3.29) gegeben haben, erhalten wir eventuelle Schnittpunkte mit der Oberfläche des Wassertropfens durch Lösen der quadratischen Gleichung (x0 + αxt )2 + (y0 + αyt )2 + β 2 (z0 + αzt )2 = a2 . (3.30) Hierbei haben wir den Parameter β = a/b eingeführt, der für einen kugelförmigen Wassertropfen den Wert eins annimmt. Diese quadratische Gleichung hat entweder – – – keine Lösung, wenn der Lichtstrahl den Wassertropfen verfehlt, genau eine Lösung, wenn der Lichtstrahl den Wassertropfen berührt, oder zwei Lösungen, wenn der Lichtstrahl durch den Wassertropfen durchgeht. Die Situation, die uns im Zusammenhang mit dem Regenbogen natürlich am meisten interessiert, ist die letztgenannte, bei der der Lichtstrahl eine Chance hat, in den Wassertropfen einzudringen. Wir führen die Abkürzungen A = x20 + y02 + β 2 z02 − a2 2 B = x0 xt + y0 yt + β z0 zt C = x2t + yt2 + β 2 zt2 (3.31) (3.32) (3.33) ein, wonach wir die Lösung des quadratischen Gleichungssystems verhältnismäßig leicht hinschreiben können: √ B ± B 2 − AC . (3.34) α1,2 = A Nachdem wir den Schnittpunkt des Lichtstrahls mit der Oberfläche des Wassertropfens ermittelt haben, benötigen wir nun den Normalenvektor zu dieser Oberfläche in diesem Punkt. Dazu gehen wir in die Parameterdarstellung (3.28), wo wir durch Differentiation nach φ bzw. ϑ die (nicht normierten) Tangentialvektoren t1 und t2 an die Wasseroberfläche erhalten: ⎛ ⎞ ⎛ ⎞ a cos φ sin ϑ a cos φ cos ϑ ∂ ⎝ a sin φ sin ϑ ⎠ = ⎝ a sin φ cos ϑ ⎠ (3.35) t1 = ∂φ b cos ϑ −b sin ϑ ⎛ ⎞ ⎛ ⎞ a cos φ sin ϑ −a sin φ sin ϑ ∂ ⎝ a sin φ sin ϑ ⎠ = ⎝ a cos φ sin ϑ ⎠ . t2 = (3.36) ∂ϑ b cos ϑ 0 120 3 Optik Durch Bildung des Vektorprodukts erhalten wir daraus den gesuchten (immer noch nicht normierten) Normalenvektor: ⎞ ⎛ ab cos φ sin2 ϑ (3.37) n = t1 × t2 = ⎝ ab sin φ sin2 ϑ ⎠ . a2 sin ϑ cos ϑ Der Faktor sin θ ist allen drei Komponenten gemein, so dass wir diesen ohne Änderung der Richtung des Vektors n streichen können, ebenso extrahieren wir den Faktor ab, wonach ⎛ ⎞ x n=⎝ y ⎠ (3.38) βz verbleibt. 3.6.3 Programm zur Berechnung eines Regenbogens Das Programm, das nun die Quintessenz der vorangegangen Überlegungen darstellt, ist gar nicht so verschieden von den Programmen zur Berechnung des Strahlengangs durch eine Linse und zur Konstruktion eines Punktes in der Objektebene, wie wir sie im vorhergehenden Abschnitt kennengelernt haben. 1 2 3 4 5 6 /************************************************************************** * Name: regen1.cpp * * Zweck: Beschreibung eines Regenbogens * * Gleichung: Snelliussches Brechungsgesetz, Fresnelsche Formeln * * Methode: Konstruktion von Lichtstrahlen * **************************************************************************/ 7 8 9 10 11 12 #include #include #include #include #include <iostream> <fstream> <gsl/gsl_matrix.h> <math.h> "tools.h" 13 14 using namespace std; 15 16 17 const int nmax = 10; double r1, r2; 18 19 #include "refract.cpp" 20 21 //------------------------------------------------------------------------- 22 23 24 25 26 int hit(double x_0, double y_0, double z_0, double x_t, double y_t, double z_t, double* x_1, double* y_1, double* z_1, double* n_x, double* n_y, double* n_z) 27 28 29 { int hit_result; 3.6 Entstehung eines Regenbogens 30 121 double t_1, t_2, diskri, eps, A, B, C, beta, xi; 31 32 33 hit_result = 0; eps = 1.e-8; 34 35 36 37 38 39 // Berechnung des Eintrittspunktes beta = r2/r1; A = sqr(x_t) + sqr(y_t) + sqr(beta*z_t); B = x_0*x_t + y_0*y_t + sqr(beta)*(z_0*z_t); C = sqr(x_0) + sqr(y_0) + sqr(beta*z_0) - sqr(r1); 40 41 42 diskri = sqr(B) - A*C; if (diskri < 0) return 0; 43 44 45 t_1 = (-B + sqrt(diskri)) / A; t_2 = (-B - sqrt(diskri)) / A; 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 if (t_2 > eps) { hit_result = *x_1 = x_0 + *y_1 = y_0 + *z_1 = z_0 + } else if (t_1 { hit_result *x_1 = x_0 *y_1 = y_0 *z_1 = z_0 } else return 1; t_2 * x_t; t_2 * y_t; t_2 * z_t; > eps) = + + + 1; t_1 * x_t; t_1 * y_t; t_1 * z_t; 0; 62 63 64 65 66 67 68 69 70 71 72 73 // Berechnung des Normalenvektors *n_x = *x_1; *n_y = *y_1; *n_z = beta * (*z_1); xi = x_t*(*n_x) + y_t*(*n_y) + z_t*(*n_z); if (xi<0) { *n_x = - (*n_x); *n_y = - (*n_y); *n_z = - (*n_z); } 74 75 76 return hit_result; } 77 78 //------------------------------------------------------------------------ 79 80 int main( int argc, char *argv[] ) 81 82 83 84 85 86 87 88 { int n_1, n_2, ny, nz, hit_result, n_refl, n_refl_max; int m_1, m_2, my, mz, m1start, m1stop, m2start, m2stop; double Intensity[nmax], Intensity_max, angle_max; double x[nmax], y[nmax], z[nmax], rx[nmax], ry[nmax], rz[nmax]; double y_min, y_max, z_min, z_max, n0, n1, n_x, n_y, n_z; ifstream in_stream; 122 89 3 Optik ofstream out_stream; 90 91 92 93 94 95 96 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: regen1 infile outfile\n"; return 0; } 97 98 99 100 101 102 103 104 105 106 107 108 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); in_stream >> x[0]; in_stream in_stream >> nz; in_stream in_stream >> y_max; in_stream in_stream >> z_max; in_stream in_stream >> mz; in_stream in_stream >> r1; in_stream in_stream >> n0; in_stream in_stream.close(); >> >> >> >> >> >> >> ny; y_min; z_min; my; n_refl_max; r2; n1; 109 110 111 112 gsl_matrix *Intensity_result = gsl_matrix_alloc(my, mz); Intensity_max = 0; angle_max = 0; 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 for (n_1 = 0; n_1<=ny; n_1++) { if (n_1 % 100 == 0) cout << n_1 << " von " << ny << "\n"; for (n_2 = 0; n_2<=nz; n_2++) { y[0] = y_min + (y_max-y_min) * (n_1) / (ny-1); z[0] = z_min + (z_max-z_min) * (n_2) / (nz-1); Intensity[0] = 1; hit_result = hit(x[0],y[0],z[0],1.,0.,0., &(x[1]),&(y[1]),&(z[1]),&n_x,&n_y,&n_z); if (hit_result == 1) { hit_result = refract(1.,0.,0.,n_x,n_y,n_z, &(rx[1]),&(ry[1]),&(rz[1]), &(rx[2]),&(ry[2]),&(rz[2]), Intensity[0],&(Intensity[1]),&(Intensity[2]),n0,n1); for (n_refl=1; n_refl<=n_refl_max+1; n_refl++) { hit_result = hit(x[n_refl],y[n_refl],z[n_refl], rx[n_refl*2],ry[n_refl*2],rz[n_refl*2],&(x[n_refl+1]), &(y[n_refl+1]),&(z[n_refl+1]),&n_x,&n_y,&n_z); hit_result = refract(rx[n_refl*2],ry[n_refl*2],rz[n_refl*2], n_x,n_y,n_z,&(rx[n_refl*2+2]),&(ry[n_refl*2+2]), &(rz[n_refl*2+2]),&(rx[n_refl*2+1]),&(ry[n_refl*2+1]), &(rz[n_refl*2+1]),Intensity[n_refl*2], &(Intensity[n_refl*2+2]),&(Intensity[n_refl*2+1]),n1,n0); if ((rx[n_refl*2+1]<0) && (n_refl==n_refl_max+1)) //Rueckstreuung { m_1 = int((ry[n_refl*2+1]/2.+0.5)*my); m_2 = int((rz[n_refl*2+1]/2.+0.5)*mz); gsl_matrix_set(Intensity_result,m_1,m_2, gsl_matrix_get(Intensity_result,m_1,m_2) +Intensity[n_refl*2+1]); // Richtung, in welche am meisten Licht gestreut wird if (gsl_matrix_get(Intensity_result,m_1,m_2) > Intensity_max) 3.6 Entstehung eines Regenbogens { Intensity_max = gsl_matrix_get(Intensity_result,m_1,m_2); angle_max = atan( sqrt( sqr(ry[n_refl*2+1]) +sqr(rz[n_refl*2+1]))); } 148 149 150 151 152 } 153 } 154 } 155 } 156 157 123 } 158 159 160 161 162 163 164 165 166 167 168 169 170 171 m1start = int(my*0.1); m1stop = int(my*0.9); m2start = int(mz*0.6); m2stop = int(mz*1.0); for (m_1=m1start; m_1<m1stop; m_1++) for (m_2=m2start; m_2<m2stop; m_2++) { out_stream << (m_1/float(my)-0.5) * 2. << " " << (m_2/float(mz)-0.5) * 2. << " " << gsl_matrix_get(Intensity_result,m_1,m_2) << "\n"; } cout << " Zahl der m_1 : " << m1stop-m1start << "\n"; cout << " Zahl der m_2 : " << m2stop-m2start << "\n"; cout << " Maximale Intensitaet : " << Intensity_max << "\n"; cout << " Winkel maximaler Intensitaet : " << angle_max*180./pi << "\n"; 172 173 174 175 out_stream << "! GLE-Eingabe-Datei generiert von regen1.cpp\n"; out_stream.close(); } Die Funktion hit wurde gemäß (3.31) bis (3.38) an eine ellipsoidförmige Grenzfläche angepasst. Im Hauptprogramm wird mit Hilfe dieser Funktion für eine Vielzahl paralleler Lichtstrahlen, die von der Sonne kommen, geprüft, ob sie den Regentropfen treffen. Wenn dies der Fall ist, wird der Weg dieses Lichtstrahles mit n refl max Reflektionen innerhalb des Tropfens verfolgt, bis er dann bei der (n refl max+1)-ten Berührung mit der Grenzfläche Wasser–Luft den Tropfen verlässt. Jeder dieser Strahlen trägt zur Gesamtintensität bei, weswegen wir ihre jeweiligen Einzelintensitäten aufsummieren. Mit der Abfrage in Zeile 139 beschränken wir uns auf rückgestreutes Licht (den Regenbogen sehen wir immer nur, wenn wir die Sonne im Rücken haben!) und können uns zusätzlich mit dem zweiten Teil der Bedingung auf Licht beschränken, das genau n refl max Reflektionen im Regentropfen erfahren hat. Diese Einschränkung ist hilfreich, um den ersten und zweiten Regenbogen getrennt zu untersuchen, da diese stark unterschiedliche Intensitäten aufweisen (siehe auch Übungsaufgabe 3.4). Im letzten Teil des Programms erkennen Sie unschwer das Herausschreiben der gefundenen Intensitäten, allerdings nur in einem eingeschränkten Bereich, der bewußt die Richtung genau entgegengesetzt zur Sonne ausspart. In dieser Richtung würden Sie nämlich ein starkes Maximum der Intensität finden, das in der Realität jedoch ohne Bedeutung ist, weil diese Richtung bei Tag unterhalb des Horizontes liegt. Anschließend werden noch die Zahl der Punkte in horizontaler und vertikaler Richtung, das Intensitätsmaximum, sowie der Öffnungswinkel des Regenbogens (Winkel maximaler Intensitaet) 124 3 Optik Abb. 3.8. Regenbogen, wie er durch kugelförrmige Wassertropfen hervorgerufen wird. Da Lichtstreuung in der Atmosphäre nicht berücksichtigt wird, ist der Himmel schwarz und nicht blau auf dem Bildschirm ausgegeben. Die Öffnungswinkel können wir mit den Werten in der Literatur vergleichen, während wir die anderen Zahlen zur Generierung der graphischen Darstellung benötigen. In Abb. 3.8 sehen Sie die so generierte Helligkeitsverteilung, in der Sie sehr schön den Regenbogen erkennen. Die Öffnungswinkel betragen ca 42.6 Grad für das rote Licht, 42.4 Grad beim grünen Licht und 42.2 Grad für blaues Licht. Es erstaunt Sie vielleicht, dass die Breite des Regenbogens lediglich 0.4 Grad beträgt – generell neigt der Mensch dazu, die Größe von Himmelserscheinungen zu überschätzen, beispielsweise beträgt die scheinbare Größe der Sonnenscheibe lediglich 0.5 Grad, wesentlich weniger als die meisten Menschen schätzen würden. 3.6.4 Der Regenbogen bei ellipsoidförmigen Regentropfen Bis zu diesem Punkt haben wir kugelförmige Regentropfen betrachtet – obwohl unser Programm regen1.cpp so konstruiert ist, dass es auch Regentropfen in Betracht zieht, die die Form eines Rotationsellipsoids haben. Indem wir die beiden Radien a und b in der Eingabedatei verschieden wählen, erhalten wir diesen allgemeineren Fall, wobei a<b (β < 1) ein abgeflachtes Ellipsoid (eine Art Diskusscheibe) bedeutet und r1>r2 (β > 1) ein langgestrecktes Ellipsoid (eine Art Zigarre). Für eine verhältnismäßig schwache Abplattung (r1=1 und r2=1.2) erhalten wir den Regenbogen in Abb. 3.9, der bereits stark von der uns bekannten Kreisform abweicht. Der Grund für diese starke Veränderung der Form des Regenbogens bei nur geringfügig verformten Wassertropfen liegt in dem komplizierten Weg, den der Lichtstrahl im Regentropfen nimmt, und bei dem er dreimal von der Oberfläche des Regentropfens beeinflusst wird: zweimal bei Brechung und zweimal in Reflektion. Dadurch verstärken sich eventuelle Abweichungen vom Strahlverlauf bei kugelförmigen Regentropfen. Aus der Tatsache, dass Regenbögen – zumindest für das bloße Auge – praktisch kreisförmig sind, kann man also umgekehrt schließen, dass dies noch in ausgepägterem Maß für die Regentropfen der Fall sein muss, auch wenn sie wohl nicht völlig kugelförmig sind. 3.7 Grundlagen der Wellenoptik 125 Abb. 3.9. Regenbogen, wie er durch Wassertropfen hervorgerufen wird, die die Form eines abgeflachten Rotationsellipsoids haben. Da Lichtstreuung in der Atmosphäre nicht berücksichtigt wird, ist der Himmel schwarz und nicht blau 3.7 Grundlagen der Wellenoptik In der Wellenoptik wird – im Gegensatz zur Strahlenoptik – berücksichtigt, dass Licht eine Wellenlösung der Maxwellschen Gleichungen ist, also eine Schwingung von elektrischem und magnetischem Feld darstellt. Wir wollen an dieser Stelle die genaue Form dieser Lösung nicht herleiten, sondern lediglich einige ihrer Eigenschaften ins Gedächtnis rufen: – Licht ist eine transversale Welle, d.h. elektrisches und magnetisches Feld stehen immer senkrecht auf der Ausbreitungsrichtung. – Das elektrische und magnetische Feld stehen außerdem senkrecht aufeinander. – Die Knoten des elektrischen Feldes sind Maxima des magnetischen Feldes und umgekehrt. Eine entscheidende Größe in der vorhin besprochenen Strahlenoptik war die Intensität des Lichtes, die wir auch hier angeben können: I= 1 c 1 cε0 εr E 2 + B2 . 2 2 µ0 µr (3.39) c ist die Vakuumlichtgeschwindigkeit, ε0 bzw µ0 sind die Dielektrizitätskonstante bzw. die Permeabilität des Vakuums und εr bzw. µr sind Materialkonstanten, die die Dielektrizität bzw. Permeabilität relativ zum Vakuumwert festlegen. An (3.39) sieht man sehr schön, dass die Energie sowohl im elektrischen als auch im magnetischen Feld stecken kann. Im Fall von Licht wechselt die Energie zwischen diesen beiden Formen hin und her, so wie dies bei einer mechanischen Schwingung eines Pendels zwischen potentieller und kinetischer Energie der Fall ist. Die etwas unschönen Vorfaktoren in Gleichung (3.39) kann man durch Einführung skalierter Variabler √ E = cε0 εr E (3.40) c B = B (3.41) µ0 µr 126 3 Optik loswerden, was zudem den Vorteil hat, dass danach elektrisches und magnetisches Feld die selbe Dimension haben und die Amplituden dieser beider Felder im Falle von Licht identisch sind. Wir werden im Weiteren diese skalierten Größen verwenden, der Übersichtlichkeit halber allerdings die Striche wieder weglassen. Besonders bequem ist es, das elektrische und magnetische Feld zu einer komplexen Größe A = E + iB (3.42) zusammenzufassen. Dies hat zum einen den Vorteil, dass sich die Formel für die Intensität I weiter vereinfacht zu I= 1 |A|2 , 2 (3.43) vor allem aber schreiben sich sowohl ebene als auch Kugelwellen besonders einfach in dieser Darstellung. 3.8 Ebene Wellen und Kugelwellen Ein wichtiges Konzept ist die Zerlegung eines beliebigen Lichtfeldes nach einer Basis, wobei wir hier die zwei wichtigsten solcher Elementarwellen vorstellen wollen: zum einen ebene Wellen und zum anderen Kugelwellen. Von beiden Zerlegungen werden wir im weiteren Verlauf Gebrauch machen. Die ebene Welle wird am Besten durch eine sehr weit entfernte Lichtquelle, die ununterbrochen leuchtet, realisert. In diesem Fall sind das elektrische und magnetische Feld gegeben durch E = E0 cos(kz − ωt + φ0 )ex (3.44) B = B0 sin(kz − ωt + φ0 )ey . (3.45) Dabei haben wir die Freiheit bei der Wahl des Koordinatensystems so ausgenutzt, dass die Fortpflanzung der Welle in z-Richtung erfolgt und E und B in der x- bzw. y-Achse liegen. Mit der komplexen Schreibweise (3.42) sieht das ganze etwas übersichtlicher aus: A = A0 exp (i(kz − ωt + φ0 )) . (3.46) Die Phase φ0 können Sie auch in die komplexe Amplitude A0 einbeziehen. Bei der Kugelwelle handelt es sich im Gegensatz dazu um ein Lichtfeld, wie es von einer punktförmigen Lichtquelle erzeugt wird, die das Licht isotrop in alle Raumrichtungen ausstrahlt. Um die Abnahme der Lichtintensität mit wachsender Entfernung richtig zu bestimmen, betrachten wir die Energie in einer Kugelschale mit Radius R um den Ausgangspunkt der Kugelwelle. Aus der Forderung, dass die Abstrahlung isotrop ist, folgt, dass die Intensität I über die Kugelfläche konstant sein muss, d.h. der Energiestrom durch diese 3.9 Interferenz 127 Kugelfläche ist proportional zu IR2 . Da dieser Energiestrom für alle diese Kugelflächen gleich sein muss – es kann ja nirgendwo Energie verloren gehen – haben wir (3.47) I ∝ 1/R2 , woraus wiederum A ∝ 1/R folgt. Damit erhalten wir für die Kugelwelle 1 A = A0 exp(i(kR − ωt + φ0 )) . R (3.48) (3.49) 3.9 Interferenz Die unmittelbarste Konsequenz der Tatsache, dass Licht eine Schwingung darstellt, ist Interferenz. Überlagern sich nämlich zwei solcher Schwingungen, so heißt das, dass diese beiden Felder vektoriell addiert werden müssen, um die Gesamtsituation zu beschreiben. Betrachten wir zur besseren Übersicht zunächst zwei monochromatische, ebene Lichtwellen, die sich in identischer Richtung (entlang der z-Achse) ausbreiten und die selbe Wellenlänge haben. Außerdem sollen beiden Lichtwellen so polarisiert sein, dass das elektrische Feld in x-Richtung und das magnetische Feld in y-Richtung zeigt. Letzteres hat zur Folge, dass wir den vektoriellen Charakter von A außer acht lassen können. A1 = A0,1 exp(i(kz − ωt + φ1 )) A2 = A0,2 exp(i(kz − ωt + φ2 )) . (3.50) (3.51) Dabei haben wir die Phasen φ1 und φ2 so gewählt, dass A1 und A2 reell und positiv sind. Überlagern wir nun diese beiden Wellen, erhalten wir als Summe für A: A = A1 + A2 = (A0,1 + A0,2 ) exp(i(kz − ωt)) . (3.52) (3.53) Die Intensität schließlich ist proportional zum Betragsquadrat also erhalten wir |A|2 = A21 + A22 + 2A1 A2 cos(φ2 − φ1 ) , (3.54) Iges = I1 + I2 + 2 I1 I2 cos(φ2 − φ1 ) . (3.55) Das Interessante an diesem Ergebnis ist, dass die Gesamtintensität nicht gleich der Summe der Einzelintensitäten ist, sondern ein sogenannter Interferenzterm 2A1 A2 cos(φ2 − φ1 ) hinzukommt. Dieser Term kann positiv (konstruktive Interferenz) oder negativ (destruktive Interferenz) sein, so dass die Intensität sowohl größer als auch kleiner als erwartet sein kann. Im Extremfall (A1 = A2 und φ2 − φ1 = π) können sich die beiden Wellen sogar komplett auslöschen und die Gesamtintensität ist null. 128 3 Optik 3.10 Das Huygenssche Prinzip Das Huygenssche Prinzip besagt, dass man sich jeden Punkt einer Wellenfront als Ausgangspunkt einer kugelförmigen Elementarwelle vorstellen kann und dass diese Elementarwellen so interferieren, dass sie alle zusammen die weitere Zeitentwicklung des gesamten Lichtwellenfeldes beschreiben. Wir wollen uns diesen Sachverhalt an einer ebenen Welle verdeutlichen, deren Fortpflanzungsrichtung wir in Richtung der z-Achse wählen. Um nun das Huygenssche Prinzip anzuwenden, benötigen wir eine Wellenfront, z.B. die xy-Ebene. Jeder Punkt A dieser Ebene ist Ausgangspunkt einer Kugelwelle und trägt so zum Lichtfeld im gesamten Raum bei. Berechnen wir nun konkret das Feld an einem Punkt B im Abstand L von der Wellenfront. Die Aufsummation (in diesem Fall eine Integration) des Huygensschen Prinzip ergibt das Integral (3.56) A ∝ A0 dxdy exp(ikl)/l , wobei l der Abstand des betrachteten Punktes vom Punkt (Ax , Ay , 0) ist. Wenn wir den Punkt, an dem wir das Lichtfeld berechnen wollen, auf die z-Achse legen, ist dieser Abstand (3.57) l = A2x + A2y + L2 . Offensichtlich hängt l nur vom Abstand r = A2x + A2y des Punktes A vom Ursprung ab – daher ist der Beitrag aller Punkte auf dem Kreis in Abb 3.10 identisch. Abb. 3.10. Schematische Darstellung zur Konstruktion einer ebenen Welle aus Kugelwellen. Links zu sehen ist die Ebene, die Ausgangspunkt der Elementarwellen ist. Eingezeichnet in diese Ebene ist ein Kreis konstanten Abstands zum betrachteten Punkt A Fassen wir diese Punkte zusammen, verbleibt von dem Doppelintegral in (3.56) nur noch ein Einfachintegral: 3.11 Berechnung von Beugungsmustern ∞ A ∝ 2πA0 129 r dr exp(ikl) l (3.58) dl exp(ikl) (3.59) 0 ∞ = 2πA0 L = 2πiA0 exp(ikL) . k (3.60) Für den letzten Schritt mussten wir die Konvergenz des Integrals durch einen Konvergenz erzeugenden Faktor erzwingen – am einfachsten indem man k einen kleinen Imaginärteil zuweist, den man dann gegen null gehen lässt. Das Endergebnis (3.60) sagt uns nicht nur, dass sich in der Tat die Kugelwellen zu einer ebenen Welle (von der wir ja ausgegangen sind) aufsummieren, sondern liefert auch den fehlenden Proportionalitätsfaktor −ik/(2π). 3.11 Berechnung von Beugungsmustern Wir können das Huygenssche Prinzip dazu verwenden, das Beugungsmuster hinter einer Blende (z.B. hinter einem Spalt oder einem Doppelspalt) zu berechnen. Gegenüber der vorangegangen Berechnung bedeutet es lediglich dass der Integrationsbereich in (3.56) auf den offenen Teil der Blende eingeschränkt werden muss. Besonders übersichtlich gestaltet sich die Berechnung des Beugungsmusters bei einer kreisförmigen Blende, mit der wir uns in diesem Abschnitt befassen werden. Betrachten wir dazu zunächst Abb 3.11: Abb. 3.11. Schematische Darstellung einer kreisförmigen Blende (im Hintergrund) und der Bildebene (im Vordergrund). Dargestellt ist ein beliebig herausgegriffener Lichtweg von der Blende zu einem Punkt auf der Bildebene, der x0 von der optischen Achse entfernt ist 130 3 Optik Die Einführung einer Blende bricht die Translationssymmetrie gegenüber Verschiebungen in x- und y-Richtung und führt eine optische Achse ein (die Gerade, die senkrecht durch den Mittelpunkt der Kreisblende geht). Während es vorher naheliegend war, die z-Achse so zu legen, dass sie durch den Punkt A geht, an dem wir das Licht berechnen wollten, müssen wir uns nun entscheiden, ob es nicht sinnvoller ist, sie in die optische Achse zu legen. Wir entscheiden uns für diese Vorgehensweise und bezeichnen mit x0 den Abstand von A von der optischen Achse. Die Kreisblende ist dann ein Kreis mit Radius R um die z-Achse, während jener Kreis, der alle Punkte gleichen Abstands l von A enthält, gegenüber der optischen Achse um x0 versetzt ist. Dieser letztere Kreis hat Radius r und Sie sehen in Abb 3.11, dass er unter Umständen nur teilweise innerhalb der Öffnung der Blende liegt (dieser Teil, den wir h nennen wollen, ist in der Abbildung mit einer durchgezogenen Linie gezeichnet, während der Rest des Kreises punktiert ist). Dieser Tatsache müssen wir nun Rechnung tragen, wobei wir drei Fälle unterscheiden müssen: – Wenn r + x0 < R ist, liegt dieser Kreis vollständig innerhalb der Öffnung der Blende und h ist durch 2πr gegeben. – Wenn r ∈ [R−x0 , R+x0 ], liegt der Kreis teilweise innerhalb dieser Öffnung und wir müssen noch berechnen, wie groß dieser Teil h(r) ist. – Wenn r > R+x0 ist, liegt der Kreis gar nicht mehr innerhalb der Öffnung, h ist null und es gibt keinen Beitrag zum Integral (3.56). Wenn wir für jeden Wert von r das zugehörige Bogenstück h kennen, ergibt sich die komplexe Amplitude A des Gesamtfeldes als Verallgemeinerung des Integrals (3.59): A = 2πA0 dl exp(ikl)h(r(l)) . (3.61) Beachten Sie bitte, dass der erste der oben genannten Fälle nur auftritt, wenn x0 < R ist, was bedeutet dass der Punkt x0 in dem Bereich ist, der nach den Gesetzen der Strahlenoptik ausgeleuchtet wird. Im Folgenden bezeichnen wir den Beitrag dieses ersten Falles zu A mit A1 , den des zweiten Falles mit A2 , während der dritte Fall offensichtlich keinen Beitrag zu A liefert. A1 können wir analytisch berechnen, hingegen werden wir das aus A2 resultierende Integral im Computer bestimmen müssen. Für die numerische Implementation benötigen wir eine kleine Nebenrechnung. Am Einfachsten ist es, wenn wir den Kreis mit dem Winkel φ zur x-Achse parametrisieren und den Winkel φ0 am Schnittpunkt mit der Kreisblende berechnen: x20 + R2 + 2x0 R cos φ0 = r2 r2 − x20 − R2 cos φ0 = . 2x0 R (3.62) (3.63) Wenn x0 im Intervall [R −x0 , R +x0 ] liegt, ist der Bruch auf der rechten Seite von (3.63) zwischen −1 und 1, so dass ein zugehöriger Winkel φ0 existiert. 3.11 Berechnung von Beugungsmustern 131 Die Länge des relevanten Kreisbogens (innerhalb der Kreisblende) ist nun 2(π − φ0 ) , (3.64) so dass wir schließlich 1 A2 = ik 1 = ik R+x 0 exp(ikl) l (3.65) exp(ikl) . l (3.66) dr (r − φ0 (r)) r=R−x0 l2 dl (r(l) − φ0 (l)) l=l1 erhalten. Dabei sind l1 und l2 definiert durch 2 l12 = (R − x0 ) + L2 l22 2 2 = (R + x0 ) + L . (3.67) (3.68) Nun benötigen wir noch A1 , von dem wir ja oben behauptet haben, dass wir es analytisch berechnen können. Im Wesentlichen können wir diese Berechnung aus Abschn. 3.10 übernehmen: R−x 0 1 A1 = ik 1 = ik dr r r=0 l1 dl l l=L exp(ikl) l exp(ikl) l = exp(ikl1 ) − exp(ikL) . 1 2 3 4 5 6 7 (3.69) (3.70) (3.71) /************************************************************************** * Name: kreisbl1.cpp * * Zweck: Berechnet das Beugungsmuster hinter einer kreisfoermigen * * Blende ( Kohaerenzlaenge unendlich ) * * Gleichung: siehe Buch * * verwendete Bibiliothek: GSL * **************************************************************************/ 8 9 10 11 12 13 #include #include #include #include #include <iostream> <fstream> <gsl/gsl_integration.h> <math.h> "tools.h" 14 15 using namespace std; 16 17 18 //-- globale Variablen double R, R2, x_02, x_0, L, k; 19 20 //------------------------------------------------------------------------- 132 3 Optik 21 22 double f(double dist, void *params) 23 24 25 { double r, r2, phi, res, arg; 26 27 28 29 30 31 32 33 34 35 36 r2 = sqr(dist)-sqr(L); r = sqrt(r2); res = 0; if (r<R-x_0) res = 2.*pi; else if (r<R+x_0) { arg = (r2-R2+x_02)/(2.*x_0*r); if (fabs(arg)<1) { phi = acos(arg); res = 2.*phi; } } 37 38 39 return res; } 40 41 //------------------------------------------------------------------------- 42 43 main( int argc, char *argv[] ) 44 45 { 46 47 48 49 50 51 52 53 54 //-- Definition der Variablen int limit = 1000; int n1, nmax; double epsabs = 1.e-10; double epsrel = 1.e-4; double A_real, A_imag, A_1_real, A_1_imag, A_2_real, A_2_imag; double l_0, l_start, l_end, delta_l, x_max, dx, abserr, Intensity; double mu = 10; 55 56 57 gsl_integration_workspace *w; gsl_integration_qawo_table *wf; 58 59 60 ifstream in_stream; ofstream out_stream; 61 62 63 64 65 66 67 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: kreisbl1 infile outfile\n"; exit(1); } 68 69 70 71 72 73 74 75 76 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von kreisbl1.cpp\n"; inout(R,"R"); inout(L,"L"); inout(k,"k"); inout(nmax,"nmax"); inout(x_max,"x_max"); in_stream.close(); 77 78 79 //-- Berechnung einiger benoetigter Parameter -dx = x_max / nmax; 3.11 Berechnung von Beugungsmustern 80 R2 = sqr(R); 81 82 83 84 for (n1 = nmax-1; n1>=0; n1--) { x_0 = (n1+0.5)*dx; x_02 = sqr(x_0); 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 if (R>x_0) { l_0 = sqrt(sqr(R-x_0)+sqr(L)); l_start = l_0; l_end = sqrt(sqr(R+x_0)+sqr(L)); A_1_real = 2.*pi / k * (sin(k*l_0)-sin(k*L)); A_1_imag = 2.*pi / k * (cos(k*l_0)-cos(k*L)); } else { A_1_real = 0; A_1_imag = 0; l_start = sqrt(sqr(x_0-R)+sqr(L)); l_end = sqrt(sqr(x_0+R)+sqr(L)); } delta_l = l_end-l_start; 102 103 104 105 w = gsl_integration_workspace_alloc(100000); wf = gsl_integration_qawo_table_alloc(k, delta_l, GSL_INTEG_COSINE, 100000); 106 107 108 109 gsl_function F; F.function = &f; F.params = &mu; 110 111 112 gsl_integration_qawo(&F, l_start, epsabs, epsrel, limit, w, wf, &A_2_real, &abserr); 113 114 115 gsl_integration_workspace_free(w); gsl_integration_qawo_table_free(wf); 116 117 118 119 w = gsl_integration_workspace_alloc(100000); wf = gsl_integration_qawo_table_alloc(k, delta_l, GSL_INTEG_SINE, 100000); 120 121 122 gsl_integration_qawo(&F, l_start, epsabs, epsrel, limit, w, wf, &A_2_imag, &abserr); 123 124 125 gsl_integration_workspace_free(w); gsl_integration_qawo_table_free(wf); 126 127 128 129 130 131 132 A_real = A_1_real + A_2_real; A_imag = A_1_imag + A_2_imag; Intensity = (sqr(A_real)+sqr(A_imag)) * sqr(k/2./pi); out_stream << x_0 << " " << Intensity << "\n"; cout << x_0 << " " << Intensity << "\n"; } 133 134 135 out_stream.close(); } 133 134 3 Optik Die Real- und Imaginärteile der Integrale A1 und A2 finden Sie im Programm als A 1 real und A 1 imag bzw A 2 real und A 2 imag. Es fällt Ihnen vielleicht auf, dass die Funktion f in diesem Programm nicht direkt der Integrand ist, sondern von diesem der schnell oszillierende Anteil exp(iωl) abgespalten wurde. Integrale mit solchen schnell oszillierenden Anteilen stellen nämlich für die Standard-Algorithmen zur Integration eine besondere Schwierigkeit dar, so dass hierfür eine speziell angepasste Routine (gsl integration qawo) verwendet wurde. 2 I / I0 1 0 0 0.5 10 -3 -3 10 1.5 10 -3 x0 Abb. 3.12. Beugungsmuster hinter einer kreisförmigen Blende Der Bereich bis x0 = 10−3 in Abb 3.12 entspricht dem Teil der Bildebene, der in der geometrischen Optik ausgeleuchtet ist, während der verbleibende Teil der Bildebene im Schatten läge. Sie sehen, dass die Wellennatur des Lichtes diese scharfe Trennung aufhebt, so dass zum einen auch etwas Licht in den Rand des Schattenbereiches eindringt und zum anderen auch im ausgeleuchteten Teil dunkle Stellen (in diesem Fall wegen der Rotationssymmetrie dunkle Kreise) auftreten. Beachten Sie außerdem, dass im Bereich konstruktiver Interferenz die Intensität höher ist als in der geometrischen Optik zu erwarten wäre (I/I0 = 1), so dass die über die gesamte Bildebene gemittelte Intensität die selbe ist. Letzteres bringt zum Ausdruck, dass auch in der Wellenoptik konstruktive und destruktive Interferenz zusammen die Energieerhaltung nicht verletzen dürfen. Interessant ist auch, dass die Mitte des ausgeleuchteten Bereichs dunkel bleibt: I(x0 = 0) = 0. 3.12 Kohärenz Bis jetzt haben wir ausschließlich die Interferenz von völlig monochromatischem Licht betrachtet, was eine Lichtquelle impliziert, die schon immer geleuchtet hat und auch in Zukunft immer leuchten wird! Diese Annahme ist natürlich irrealistisch, jede Lichtwelle muss einen zeitlichen Anfang und ein zeitliches Ende haben. Im Falle thermischer Lichtquellen (also z.B. einer Glühbirne oder der Sonne) haben wir es mit einer statistischen Überlagerung sehr vieler und sehr kurzer solcher Lichtwellenzüge zu tun, so dass der zeitliche Verlauf des elektrischen Feldes in etwa so aussieht, wie es in Abb. 3.13 3.12 Kohärenz 135 Abb. 3.13. Schematische Darstellung des elektrischen Feldes von Licht mit endlicher Kohärenzlänge dargestellt ist. Die Frage, wie es mit der Interferenz solchen Lichts aussieht, wird uns zum Begriff der Kohärenz führen. Wir beginnen bei (3.55), wobei wir jedoch den Phasen φ1 und φ2 statistischen Charakter zuweisen, wonach wir als Ausdruck für die Intensität (3.72) Iges = I1 + I2 + 2 I1 I2 cos(φ1 − φ2 ) . erhalten. Die eckigen Klammern auf der rechten Seite von (3.72) bedeuten den statistischen Mittelwert des innerhalb der Klammern stehenden Ausdrucks – für die Grundlagen der Statistik, soweit nicht vorhanden, verweisen wir den Leser auf das folgende Kap. 4. Gegenüber der geometrischen Optik, in der Intensitäten einfach addiert werden, erhalten wir einen zusätzlichen Term, den Interferenzterm 2 I1 I2 cos(φ1 − φ2 ) . (3.73) Im Fall von unterschiedlichen Lichtquellen ist die Phasendifferenz völlig zufällig und gleichverteilt, so dass sich der Interferenzterm im Mittel weghebt und wir das Ergebnis der geometrischen Optik erhalten. Licht aus unterschiedlichen Quellen kann also nicht miteinander interferieren. Wie sieht es nun aus mit Licht, das aus einer Quelle kommt, aber auf unterschiedlichen Wegen auf die Bildebene gelangt? Ein Beispiel hierfür ist die Kreisblende aus dem vorhergehenden Abschnitt, aber auch im Regenbogen gibt es mehrere mögliche Wege des Lichtstrahls, die zum selben Ausfallswinkel führen. Der Weglängendifferenz ∆l entspricht ein Laufzeitunterschied ∆t = ∆l/c: die beiden Lichtwellen sind also zu verschiedenen Zeiten bei der Lichtquelle gestartet! In diesem Fall ist der Mittelwert cos(φ1 − φ2 ) eine Funktion der Laufzeitdifferenz ∆t und eine charakteristische Eigenschaft der Lichtquelle. Bei einer rein monochromatischen Lichtquelle ist die Phasendifferenz (3.74) φ1 − φ2 = ω(t1 − t2 ) fest, so dass die Mittelung trivial ist: cos(φ1 − φ2 ) = cos(ω(t1 − t2 )) . (3.75) Diesen Faktor, der nur von der Frequenz des Lichtes herrührt, spaltet man vom Mittelwert cos(φ1 − φ2 ) ab und definiert den verbleibenden Teil als 136 3 Optik Kohärenzfunktion: C(∆t) = cos(φ(t) − φ(t + ∆t))/ cos(ω∆t) . (3.76) Dabei bringt diese Schreibweise schon implizit zum Ausdruck, dass diese Kohärenzfunktion aus Symmetriegründen nur von der Laufzeitdifferenz ∆t abhängen kann. Betrachten wir Abb. 3.13, so wird klar, dass die Kohärenzfunktion für sehr kleine Zeiten den Wert 1 annehmen muss (auf kleinen Skalen ist der Verlauf sinusoidal), während umgekehrt im Limes ∆t → ∞ die Kohärenzfunktion auf null abfallen muss, da die einzelnen Wellenzüge voneinander unabhängig sind. Darüberhinaus können wir zunächst keine Aussage über die Kohärenzfunktion machen und tatsächlich kann sie von Fall zu Fall sehr verschieden aussehen, häufig jedoch hat sie die Form einer Exponentialfunktion: |∆t| C(∆t) = exp − , (3.77) tcoh wobei die Kohärenzzeit tcoh eine für den Prozess der Lichtentstehung charakteristische Konstante ist. Die in der Literatur ebenfalls oft verwendete Kohärenzlänge lcoh ist definiert durch lcoh = ctcoh . (3.78) Setzen wir die Kohärenzfunktion (3.76) in den Ausdruck für die Intensität (3.72) ein, so erhalten wir I = I1 + I2 + 2 I1 I2 C(t1 − t2 ) cos(ω(t1 − t2 )) (3.79) = A1 A1 C(0) cos(0) + A2 A2 C(0) cos(0) +2Re (A1 A2 ) C(t1 − t2 ) cos(ω(t1 − t2 )) . (3.80) Dabei haben wir davon Gebrauch gemacht, dass sowohl Kohärenzfunktion als auch der Kosinus symmetrisch sind und bei null den Wert eins haben. Die Darstellung (3.80) hat den Vorteil, dass sie leicht auf mehrere sich überlagernde Wellen oder sogar auf den kontinuierlichen Fall verallgemeinert werden kann: I= An Am C(tn − tm ) cos(ω(tn − tm )) (3.81) n,m bzw. I= dx1 dx2 A(x1 )A (x2 )C (t(x1 ) − t(x2 )) × cos [ω(t(x1 ) − t(x2 ))] , (3.82) wobei wir jedoch nicht implizieren wollen, dass die Integrationsvariablen x1 bzw. x2 Ortscharakter haben müssen, sondern einfach verschiedene mögliche Wege parametrisieren. 3.13 Beugung bei endlicher Kohärenzlänge 137 3.13 Beugung bei endlicher Kohärenzlänge Die Berechnung von Beugungsmustern unter Berücksichtigung der endlichen Kohärenzlänge lcoh diskutieren wir zunächst an einem Beispiel, das wir – allerdings für lcoh = ∞ – bereits besprochen haben: die Beugung hinter einer kreisförmigen Blende. Das hat den Vorteil, dass wir die gewonnenen Ergebnisse vergleichen können und so eine einfache Überprüfungsmöglichkeit für deren Richtigkeit haben. Wenn wir die Ausdrücke für die Intensität bei unendlicher ((3.54) mit (3.61)) und endlicher Kohärenzlänge (3.82) vergleichen, so stellen wir fest, dass aus dem Einfachintegral, das das Programm kreisbl1.cpp zu lösen hatte, nun ein Doppelintegral wird: (3.83) I = dl1 dl2 h(l1 )h(l2 ) exp(ik(l1 − l2 ))C(|x1 − x2 |) . Das erste Problem mit diesem Integral erkennen wir, wenn wir für die Kohärenzfunktion C(|x1 − x2 |) = exp(−α|x1 − x2 |) (3.84) die Normierung berechnen wollen, was uns (h(l) = 2π) auf das Integral ∞ ∞ dl1 dl2 exp(ik(l1 − l2 )) exp(−α|l1 − l2 |) L (3.85) L führt, welches divergent ist. Das kann daran liegen, dass die Koheränzfunktion zumindest für große Argumente unphysikalisch ist oder unsere Annahme einer ebenen Welle zur Berechnung der Normierungskonstante das Problem darstellt, da ebene Wellen nicht realisierbar sind und unendlich viel Energie enthalten würden. Welcher der beiden Gründe hier auch vorliegt, es ist in beiden Fällen legitim, die Normierung auf später zu verschieben und nach der Berechnung des Beugungsmusters aus der Energieerhaltung zu bestimmen. Damit sollte die Implementation des Doppelintegrals (3.83) eigentlich kein Problem darstellen: 1 2 3 4 5 6 7 /************************************************************************** * Name: kreisbl2.cpp * * Zweck: Berechnet das Beugungsmuster hinter einer kreisfoermigen * * Blende (endliche Kohaerenzlaenge) * * Gleichung: siehe Buch * * verwendete Bibiliothek: GSL * **************************************************************************/ 8 9 10 11 12 13 #include #include #include #include #include <iostream> <fstream> <gsl/gsl_integration.h> <math.h> "tools.h" 138 3 Optik 14 15 using namespace std; 16 17 18 19 //-- globale Variablen double R, R2, x_0, x_02, L, k, l2_global, alpha; gsl_integration_qawo_table *wf; 20 21 //------------------------------------------------------------------------- 22 23 double f_inner(double l1_shifted, void *params) 24 25 26 27 { double r, r2, res, phi, arg, l1; res = 0; 28 29 l1 = l1_shifted + L; 30 31 32 33 34 if (l1 < L) return 0; r2 = sqr(l1_shifted)+2.*L*l1_shifted; r = sqrt(r2); arg = 0; 35 36 37 if (fabs(x_0+r) < R) res = res + 2.*pi; 38 39 40 41 42 43 else if (fabs(x_0-r) < R) { arg = (r2+x_02-R2)/(2.*x_0*r); if (fabs(arg)<=1) res = res + 2.*acos(arg); } 44 45 return res*exp(-alpha*fabs(l1-l2_global)); 46 47 } 48 49 //------------------------------------------------------------------------- 50 51 double f_outer_real(double l2_shifted, void *params) 52 53 54 55 56 57 58 59 { int limit = 1000; double r, r2, phi, arg, res, l1_start, l2, Int_inner_real_1, abserr; double mu = 10; double epsabs = 1.e-17; double epsrel = 1.e-5; gsl_integration_workspace *w; 60 61 res = 0; 62 63 64 l2 = l2_shifted + L; l2_global = l2; 65 66 67 68 if (l2 < L) return 0; r2 = sqr(l2_shifted)+2.*L*l2_shifted; r = sqrt(r2); 69 70 71 72 if (fabs(x_0+r) < R) res = res + 2.*pi; 3.13 Beugung bei endlicher Kohärenzlänge 73 74 75 76 77 139 else if (fabs(x_0-r) < R) { arg = (r2+x_02-R2)/(2.*x_0*r); if (fabs(arg)<=1) res = res + 2.*acos(arg); } 78 79 80 81 gsl_function F; F.function = &f_inner; F.params = &mu; 82 83 84 85 86 if (R>x_0) l1_start = 0; else l1_start = sqrt(sqr(x_0-R)+sqr(L))-L; 87 88 w = gsl_integration_workspace_alloc(100000); 89 90 91 gsl_integration_qawo(&F, l1_start, epsabs, epsrel, limit, w, wf, &Int_inner_real_1, &abserr); 92 93 gsl_integration_workspace_free(w); 94 95 96 return Int_inner_real_1*res; } 97 98 //------------------------------------------------------------------------- 99 100 double f_outer_imag(double l2_shifted, void *params) 101 102 103 104 105 106 107 108 { int limit = 1000; double r, r2, phi, res, arg, l1_start, l2, Int_inner_real_1, abserr; double mu = 10; double epsabs = 1.e-17; double epsrel = 1.e-5; gsl_integration_workspace *w; 109 110 res = 0; 111 112 113 l2 = l2_shifted + L; l2_global = l2; 114 115 116 117 if (l2 < L) return 0; r2 = sqr(l2_shifted)+2.*L*l2_shifted; r = sqrt(r2); 118 119 120 if (fabs(x_0+r) < R) res = res + 2.*pi; 121 122 123 124 125 126 else if (fabs(x_0-r) < R) { arg = (r2+x_02-R2)/(2.*x_0*r); if (fabs(arg)<=1) phi = res + 2.*acos(arg); } 127 128 129 130 131 gsl_function F; F.function = &f_inner; F.params = &mu; 140 132 133 134 135 3 Optik if (R>x_0) l1_start = 0; else l1_start = sqrt(sqr(x_0-R)+sqr(L))-L; 136 137 w = gsl_integration_workspace_alloc(100000); 138 139 140 gsl_integration_qawo(&F, l1_start, epsabs, epsrel, limit, w, wf, &Int_inner_real_1, &abserr); 141 142 gsl_integration_workspace_free(w); 143 144 145 return Int_inner_real_1*res; } 146 147 //------------------------------------------------------------------------- 148 149 main( int argc, char *argv[] ) 150 151 { 152 153 154 155 156 157 158 159 160 //-- Definition der Variablen int limit = 1000; int n1, nmax; double epsabs = 1.e-12; double epsrel = 1.e-3; double Int1_real, Int1_imag, Intensity; double l2_start, l2_end, delta_l2, x_max, dx, abserr, delta; double mu = 10; 161 162 163 gsl_integration_workspace *w; gsl_function F; 164 165 166 ifstream in_stream; ofstream out_stream; 167 168 169 170 171 172 173 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: kreisbl2 infile outfile\n"; exit(1); } 174 175 176 177 178 179 180 181 182 183 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von kreisbl2.cpp\n"; inout(R,"R"); inout(L,"L"); inout(k,"k"); inout(nmax,"nmax"); inout(x_max,"x_max"); inout(delta,"delta"); inout(alpha,"alpha"); in_stream.close(); 184 185 186 187 //-- Berechnung einiger benoetigter Parameter -dx = x_max / nmax; R2 = sqr(R); 188 189 190 for (n1=0; n1<=nmax; n1++) { 3.13 Beugung bei endlicher Kohärenzlänge 191 141 x_0 = (n1+0.5)*dx; x_02 = sqr(x_0); 192 193 194 195 196 197 198 199 200 201 202 203 if (R>x_0) { l2_start l2_end } else { l2_start l2_end } delta_l2 = = 0; = sqrt(sqr(R+x_0)+sqr(L))-L; = sqrt(sqr(x_0-R)+sqr(L))-L; = sqrt(sqr(x_0+R)+sqr(L))-L; l2_end-l2_start; 204 205 206 207 w = gsl_integration_workspace_alloc(10000); wf = gsl_integration_qawo_table_alloc(k, delta_l2, GSL_INTEG_COSINE, 10000); 208 209 210 F.function = &f_outer_real; F.params = &mu; 211 212 213 gsl_integration_qawo(&F, l2_start, epsabs, epsrel, limit, w, wf, &Int1_real, &abserr); 214 215 216 gsl_integration_workspace_free(w); gsl_integration_qawo_table_free(wf); 217 218 219 220 w = gsl_integration_workspace_alloc(10000); wf = gsl_integration_qawo_table_alloc(k, delta_l2, GSL_INTEG_SINE, 10000); 221 222 223 F.function = &f_outer_imag; F.params = &mu; 224 225 226 gsl_integration_qawo(&F, l2_start, epsabs, epsrel, limit, w, wf, &Int1_imag, &abserr); 227 228 229 gsl_integration_workspace_free(w); gsl_integration_qawo_table_free(wf); 230 231 232 233 234 Intensity = (Int1_real+Int1_imag) * sqr(k/2./pi); out_stream << x_0 << " " << Intensity << "\n"; cout << x_0 << " " << Intensity << "\n"; } 235 236 237 out_stream.close(); } Wenn Sie den Programmcode jedoch aufmerksam studieren, werden Sie einige Feinheiten bemerken, die für die erfolgreiche Berechnung des Interferenzmusters ausschlaggebend sind: Zunächst fällt auf, dass zur Berechnung des Doppelintegrals (3.83) dieses umgeformt wurde: I = dl1 dl2 h(l1 )h(l2 ) exp(ik(l1 − l2 )) exp(−α|l1 − l2 |) 142 3 Optik I / I0 2 1 0 0 0.5 10 -3 -3 10 -3 1.5 10 x0 Abb. 3.14. Beugungsmuster einer 1 mm großen, kreisförmigen Blende im Abstand von 10 cm. Die durchgezogene Linie entspricht der Situation unendliche Kohärenzlänge, während die gestrichelte Linie einer Koherärenzlänge von 1 µm entspricht = dl1 dl2 h(l1 )h(l2 ) (cos(kl1 ) cos(kl2 ) + sin(kl1 ) sin(kl2 )) × exp(−α|l1 − l2 |) (3.86) Dies hat den Vorteil, dass sowohl für das innere als auch das äußere Integral die uns schon bekannte Routine gsl integration qawo verwendet werden kann. Außerdem wurden die Integrationsvariablen l1 bzw l2 um L verschoben, um zu vermeiden, dass bei der Berechnung von r zwei fast gleich große Zahlen subtrahiert werden müssen, was auf Kosten der Genauigkeit gehen würde. Bei den Genauigkeitsanforderungen zur Berechnung des inneren bzw. äußeren Integrals – festgelegt durch die Variablen epsabs und epsrel – stellen Sie fest, dass diese Anforderungen für die innere Integrale um zwei Zehnerpotenzen höher angesetzt wurden als bei den beiden äußeren Integralen. Dies ist nötig, da sonst die Ungenauigkeiten im inneren Integral eine Konvergenz des äußeren Integrals verhindern. Ein Zusatzprogramm kreisbl2 norm.cpp bewerkstelligt abschließend die Normierung. Diese beiden Programme stellen keine besonderen Probleme dar, weswegen wir sie hier nicht im Detail vorstellen – Sie finden Sie jedoch selbstverständlich auf der CD zum Buch. In Abb. 3.14 erkennen Sie, dass das Beugungsmuster bei unendlicher Kohärenzlänge (durchgezogene Kurve) genau Abb. 3.12 enspricht. Bei endlicher Kohärenzlänge wird das Beugungsmuster hingegen geglättet (gestrichelte Linie) – ein Effekt, der mit kleiner werdender Kohärenzlänge immer ausgeprägter wird. Übungen 3.1 Strahlengang durch an einer Linse Das Programm strahl3.cpp konstruiert den Strahlengang durch eine Linse für ein Strahlenbündel, das parallel zur optischen Achse auf die Linse fällt. Dies entspricht einem unendlich weit entfernten Objekt mit Objekthöhe null. Übungen 143 Modifizieren Sie dieses Programm dahingehend, dass die Strahlen zwar parallel zueinander aber in einem beliebigen Winkel zur optischen Achse einfallen – diese Situation entspricht einem Objekt, das weiterhin unendlich weit entfernt ist, aber nicht auf der optischen Achse liegt. 3.2 Linsenfehler Es gibt eine ganze Reihe weiterer Linsenfehler, die Sie mit strahl5.cpp untersuchen können – eine Aufstellung gibt Tabelle 3.1, wobei statt der Objekthöhe l der Objektwinkel φ zur Charakterisierung benutzt wird. tan φ = l/d . (3.87) Tabelle 3.1. Linsenfehler Bezeichnung Größenordn. Bemerkung Öffnungsfehler ∝ R3 Koma Astigmatismus ∝ R2 tan φ ∝ R tan2 φ Bildfeldwölbung ∝ R tan2 φ Verzeichnung ∝ tan3 φ auch sphärische Aberration, Strahlen am Linsenrand werden zu stark gebrochen Asymmetrie der Kaustik es gibt statt einem Brennpunkt zwei Brennstriche die Bildfläche ist nicht mehr eben, sondern gewölbt der Abbildungsmaßstab hängt von der Entfernung zur optischen Achse ab Sie können durch Variation der Parameter (insbesondere des Linsenradius R und der Objekthöhe l) die einzelnen Linsenfehler betonen und so getrennt untersuchen. Wer Kenntnisse in geometrischer Optik besitzt, kann an geeigneter Stelle des Strahlengangs eine Blende einbringen und dadurch die Linsenfehler reduzieren. 3.3 Regenbogen von tropfenförmigen Regentropfen Sie können das Regenbogenprogramm so modifizieren, dass es statt Tropfen in Form von Rotationsellipsoiden z.B. tropfenförmige Regentropfen annimmt, z.B. etwas in der Form r = r0 + r1 /(ϑ − ϑ0 ) , (3.88) wobei θ der Winkel gegen die Vertikale ist. Für r0 = 0.8 und ϑ0 = 0.2 ergibt sich ein schöner (wenn auch falscher) Tropfen (Abb. 3.15): 144 3 Optik Abb. 3.15. Typische, aber falsche Tropfenform 3.4 Der zweite Regenbogen Wie Sie sicherlich schon einmal gesehen haben, können unter günstigen Bedingungen auch zwei Regenbögen beobachtet werden. Um den sogenannten zweiten Regenbogen zu bekommen, müssen Sie in unserem Programm die Variable n ray max auf 2 (statt 1 beim ersten Regenbogen) setzen. Ermitteln Sie die Intensität dieses zweiten Regenbogens im Vergleich zum ersten und bestimmen Sie den Öffnungswinkel. Wenn Sie einen Ausschnitt der beiden Regenbögen betrachten (Abb. 3.16), stellen Sie zudem fest, dass die Reihenfolge der Farben bei den beiden verschieden ist. Abb. 3.16. Ausschnittsvergrößerung vom ersten (linkes Bild) und zweiten Regenbogen (rechtes Bild). Deutlich zu sehen ist, dass die Reihenfolge der Farben beim zweiten Regenbogen gegenüber dem ersten gerade invertiert ist – also rot innen und blau außen liegt. (Eine Farbversion dieser Abbildung finden Sie übrigens auf der Buch-CD im Verzeichnis figures) 3.5 Interferenz hinter einer Kreisscheibe Das Interferenzmuster hinter einer Kreisscheibe ist quasi das Komplementär zu dem der Kreisblende. Dieses Beugungsmuster direkt durch numerische Integration über den Außenbereich auszurechnen ist schwierig, weil dieses Übungen 145 Integral bis ins Unendliche ausgedehnt ist. Sie können aber die Tatsache ausnutzen, dass sich die Felder (also nicht die Intensitäten!) von der Kreisblende (siehe Abschn. 3.11) und die des gesuchten Lichtfeldes zu den Feldern einer ebenen Welle addieren müssen. Damit können Sie direkt das Interferenzmuster hinter einer Kreisscheibe angeben. 4 Statistische Physik Bei einer Vielzahl physikalischer Probleme ist der Zustand aus mikroskopischer Sicht nicht bekannt – ein typisches Beispiel hierfür ist ein Gas, bei dem der Zustand durch die Koordinaten und Impulse aller beteiligten Atome gegeben wäre. In diesem Fall helfen uns die mikroskopischen Bewegungsgleichungen alleine also nicht weiter und es stellt sich die Frage, warum sich ein Gas überhaupt immer gleich verhält. Die Antwort lautet, dass wir ein Gas nicht mikroskopisch betrachten, sondern dieses außerordentlich komplexe System mit einer Vielzahl von Freiheitsgraden durch einige wenige Zustandsgrößen wie Druck oder Temperatur beschreiben, bei denen sich unsere Unkenntnis durch die Vielzahl beteiligter Teilchen quasi herausmittelt. Die Aufgabe der statistischen Physik ist es, diese Zusammenhänge herzustellen und die makroskopischen Zustandsgleichungen mit den Methoden der Statistik auf die mikroskopischen Bewegungsgleichungen zurückzuführen. 4.1 Grundbegriffe der Statistik Bevor wir zur Anwendung von statistischen Verfahren auf physikalische Probleme kommen, müssen wir zunächst einige mathematische Grundbegriffe aus der Statistik klären. Der erste dieser Begriffe ist der des Zufallsexperiments. Dabei handelt es sich um ein Experiment, dessen Ausgang wir nicht vorhersagen können und das wir – zumindest im Prinzip – beliebig oft wiederholen können. Ein typisches Beispiel ist das Werfen einer Münze, bei dem wir vorher nicht sagen können, welche Seite anschließend nach oben zeigen wird. In diesem Fall wären die Menge der möglichen Ereignisse gleich der Menge mit den Elementen Kopf und Zahl. Ebensogut kann dieser Ereignisraum aber auch natürliche Zahlen (beim Werfen eines Würfels) oder reelle Zahlen (zum Beispiel Zeiten) enthalten. Insbesondere kann die Ergebnismenge endlich oder abzählbar unendlich sein oder sogar ein Kontinuum bilden. Das Ergebnis x eines konkreten solchen Zufallsexperiments wird Ereignis genannt. Macht man nun viele identische Zufallsexperimente, kann man die Wahrscheinlichkeit für ein bestimmtes Ereignis feststellen, indem man die Zahl nx der Zufallsexperimente, bei denen dieses Ereignis eintrat, durch die Zahl aller Zufallsexperimente N teilt: 148 4 Statistische Physik Px = lim N →∞ nx . N (4.1) Ist der Ereignisraum ein Kontinuum, so wird in den meisten Fällen dieser Grenzwert null liefern, so dass man hier statt der Wahrscheinlichkeit selbst die Wahrscheinlichkeitsdichte p definiert: p(x) = lim lim ∆x→0 N →∞ n(x, ∆x) , N (4.2) wobei n(x, ∆x) die Zahl der Ereignisse im Intervall [x − ∆x/2, x + ∆x/2] ist. Da wir wissen, dass in jedem Zufallsexperiment irgendein Ereignis eintritt, muss die Summe aller Wahrscheinlichkeiten gleich eins sein: Px = 1 , (4.3) x bzw. im Fall eines Kontinuums: dx p(x) = 1 . (4.4) In den nachfolgenden Abschnitten werden wir zur besseren Übersichtlichkeit nur die Ausdrücke für diskrete Ereignisräume angeben, sofern der entsprechende Ausdruck für kontinuierliche Räume offensichtlich ist. 4.2 Bestimmung von Wahrscheinlichkeiten An dieser Stelle wollen wir die empirische Bestimmung einer Wahrscheinlichkeit p durch Wiederholung des betreffenden Zufallsexperimentes genauer unter die Lupe nehmen. Gegeben sei ein Zufallsexperiment mit zwei möglichen Ausgängen, die wir positiv und negativ nennen. Die Wahrscheinlichkeit für den postiven Ausgang sei p und entsprechend die Wahrscheinlichkeit für den negativen Ausgang 1 − p. Wenn wir dieses Experiment N -mal wiederholen, so erhalten wir n+ positive und n− negative Ausgänge, woraus wir empirisch die Wahrscheinlichkeit p abschätzen: pemp = n+ . N (4.5) Im Extremfall können wir jedoch immer den negativen Ausgang erhalten, obwohl p nicht null ist, d.h. diese Bestimmung ist mit einer Unsicherheit behaftet, die daher rührt, dass wir N zwar groß wählen können, aber nie den Grenzübergang N → ∞ wirklich vollziehen können. Um dies zu quantifizieren, definieren wir eine Wahrscheinlichkeit PN (n) (wir lassen im Folgenden den Index + der Übersichtlichkeit wegen weg) dafür, dass wir bei N Zufallsexperimenten genau n positive Ereignisse beobachten. 4.2 Bestimmung von Wahrscheinlichkeiten 149 Sofern unsere Zufallsexperimente alle voneinander unabhängig sind (Näheres zu diesem Begriff siehe Abschn. 4.4), berechnet sich diese Wahrscheinlichkeit zu N (4.6) PN (n) = pn (1 − p)N −n , n wobei N n = N! n! (N − n)! (4.7) die Zahl der Möglichkeiten angibt, die n positiven Ereignisse auf die N Zufallsexperimente zu verteilen. Uns interessieren nun der Mittelwert und die Varianz der empirisch bestimmten Wahrscheinlichkeit, also benötigen wir n (4.8) P1 = PN (n) N n n 2 P2 = PN (n) . (4.9) N n Um diese Größen zu berechnen, betrachten wir zunächst die Funktion fN (α, β) = N N n=0 n αn β N −n = (α + β)N . (4.10) Wie wir sehen, liefert der erste Ausdruck für fN (α = p, β = 1 − p) gerade den Mittelwert von eins und konsequenterweise liefert die rechte Seite das korrekte Ergebnis (p + 1 − p)N = 1. Entsprechend erhalten wir α ∂ (4.11) P1 = fN (α, β) α=p N ∂α β=1−p p = N (p + 1 − p)N −1 (4.12) N =p (4.13) α ∂ α ∂ fN (α, β) (4.14) P2 = α=p N ∂α N ∂α β=1−p p (1 + p(N − 1)) . = N (4.15) Daraus erhalten wir den Mittelwert der empirisch bestimmten Wahrscheinlichkeit pemp zu (4.16) pemp = p und deren Varianz zu p2emp − pemp 2 = 1 p − p2 . N (4.17) 150 4 Statistische Physik Beachten Sie bitte, dass die Varianz proportional zu 1/N abfällt, das heißt √ die Standardabweichung fällt nur mit 1/ N ab – um also einen doppelt so genauen Wert von p zu erhalten müssen Sie viermal so viele Zufallsexperimente durchführen. Interessant ist außerdem, dass die Unsicherheit für p = 0 und für p = 1 verschwindet (hier ist man sich sicher, dass alle Zufallsexperimente negativ bzw. positiv ausgehen) und für p = 1/2 ihr Maximum 1/4N annimmt. 4.3 Mittelwerte und Momente Wenn auf dem Ergebnisraum eine Addition erklärt ist, so macht es Sinn, den Mittelwert zu definieren: x = xPx . (4.18) x Statt dem Mittelwert von x selbst können wir natürlich auch nach dem Mittelwert von jeder Funktion von x fragen: f (x)Px , (4.19) f (x) = x insbesondere nach den Mittelwerten von x2 , x3 . . . Diese Mittelwerte heißen zweites, drittes, viertes Moment und so weiter. Das erste Moment ist gleich dem Mittelwert (4.18), das nullte Moment ist immer identisch eins (4.3). An dieser Stelle muss betont werden, dass der Mittelwert f (x) im Allgemeinen verschieden von der Funktion des Mittelwertes f (x) ist. 4.4 Bedingte Wahrscheinlichkeiten und Korrelationen Die konzeptionell schwierigsten Begriffe und deswegen auch die am häufigsten missinterpretierten Grundbegriffe, die wir für dieses Kapitel benötigen, sind die der bedingten Wahrscheinlichkeit und der Korrelation. Betrachten wir hierzu ein Zufallsexperiment, dessen Ergebnisse als N -Tupel von Zahlen dargestellt werden können. Dabei könnte es sich zum Beispiel um das Werfen von N unterscheidbaren Würfel handeln, bei denen wir die Augenzahl des n-ten Würfel an die n-te Stelle des N -Tupels schreiben. Andere Beispiele wären die Energien zweier in einem Experiment erzeugter Photonen oder zwei Komponenten des Geschwindigkeitsvektors eines Teilchens. Die beiden Begriffe bedingte Wahrscheinlichkeit und Korrelation dienen der Klärung der statistischen Unabhängigkeit von solchen Einzelereignissen. Neben den Wahrscheinlichkeiten für die Einzelergebnisse einer konkreten Komponenten des N -Tupels können wir auch sogenannte Verbundwahrscheinlichkeiten bilden, dass z.B. der erste Würfel Ergebnis a und der zweite Würfel Ergebnis b liefert. Diese Verbundwahrscheinlichkeit können wir uns 4.4 Bedingte Wahrscheinlichkeiten und Korrelationen 151 nun zusammensetzen aus der Wahrscheinlichkeit Pa für Ergebnis a beim ersten Würfel mal der Wahrscheinlichkeit Pb|a dafür, dass der zweite Würfel b liefert, unter der Bedingung, dass das Ergebnis des ersten Würfels a ist. Diese Zusatzbedingung ist enorm wichtig, denn es ist nicht a priori selbstverständlich, dass die Ergebnisse der beiden Würfel voneinander unabhängig sind. Wenn diese Zusatzbedingung zu keiner Änderung der Wahrscheinlchkeiten führt, wenn also Pb|a = Pb für alle a, b (4.20) gilt, dann spricht man von unabhängigen Ereignissen. Bei zwei normalen Würfeln muss beispielsweise das Ergebnis beim einen Würfel unabhängig vom Ergebnis beim zweiten Würfel sein. Ist die Bedingung (4.20) für irgendein Paar a, b nicht erfüllt, sprechen wir hingegen von korrelierten Ereignissen. Der Nachteil dieser Formel ist, dass wir sie im Zweifelsfall für alle a und alle b verifizieren müssen, bevor wir sicher sein können, dass die beiden Ereignisse voneinander unabhängig sind. Aus diesem Grund ist die Berechnung der Korrelation (4.21) Cxy = xy − xy eine schöne Alternative, auch wenn deren Aussage streng genommen nicht so weit geht wie die Forderung (4.20). Um die Bedeutung von Cxy zu verstehen, berechnen wir die Korrelation unter der Voraussetzung, dass die beiden Ereignisse x und y voneinander unabhängig sein sollen: xypx py|x − xpx ypy Cxy = x,y = x,y x xypx py − x xpx y ypy (4.22) y =0. Wenn dieser Ausdruck also nicht verschwindet, können wir sicher sein, dass die beiden betrachteten Ereignisse miteinander korreliert sind – umgekehrt können wir leider aus Cxy = 0 nicht sicher auf statistische Unabhängigkeit schließen. Als letzten Begriff in diesem Abschnitt wollen wir die Korrelationsfunktion einführen. Dazu stellen wir uns vor, dass unsere Zufallsvariablen x und y von einem Parameter, z.B. der Zeit t, abhängen. Als Beispiel können Ort und Geschwindigkeit eines Teilchens dienen, dessen Bewegung auf Grund von Stößen mit anderen, nicht beobachteten Teilchen zufälliger Natur ist. Die Korrelationsfunktion ist nun definiert durch Cxy (t, ∆t) = x(t)y(t + ∆t) − x(t)y(t + ∆t) . (4.23) In stationären Situationen, z.B. im thermischen Gleichgewicht, kann diese Korrelationsfunktion nicht von der Zeit t, sehr wohl aber von der Zeitdifferenz ∆t abhängen. 152 4 Statistische Physik 4.5 Dynamik bei statistischen Problemen Eine stochastische Beschreibung physikalischer Probleme wird notwendig, wenn die Anfangsbedingung nicht genau bekannt ist oder die Dynamik selbst stochastischer Natur ist, d.h. die Bewegungsgleichung eine Zufallskomponente enthält. Letzteres bedarf einer Erklärung, denn klassisch liegt die Dynamik eindeutig fest, wenn alle Zustandsvariablen bekannt sind – es besteht also gar kein Spielraum für einen statistischen Charakter der Bewegung. Der einzige Ausweg aus diesem logischen Dilemma, ist, dass wir nur einen Teil der Zustandsvariablen erfasst haben und einen relevanten Teil des Systems unterschlagen haben. Als Illustration dessen, was wir damit meinen, ziehen wir die Brownsche Bewegung heran. Dabei handelt es sich um die Bewegung von kleinen, aber im Mikroskop noch wahrnehmbaren Teilchen (z.B. Bärlappsporen) in einer Flüssigkeit. Der Botaniker Robert Brown stellte 1827 fest, dass diese Teilchen eine Zitterbewegung vollführen, konnte jedoch keinen Grund für dieses Zittern finden.1 Die Erklärung lieferte Einstein im Jahre 1905: die Bärlappsporen werden von den Flüssigkeitsmolekülen, die im Mikroskop nicht optisch aufgelöst werden können, gestoßen. In diesem Fall sind also die Moleküle der Flüssigkeit der Teil, den wir nicht berücksicht haben und auch gar nicht können, ohne ein nicht handhabbares System von 1020 gekoppelten Differentialgleichungen zu bekommen. Wie sieht nun die Struktur der Bewegungsgleichung eines solchen Teilchens aus? Sie hat zum einen einen deterministischen und zum anderen einen stochastischen Anteil: d x = ẋdet + ẋstoch . (4.24) dt Hierbei umfasst der Vektor x alle Zustandsvariablen, d.h. z.B. bei einem mechanischen System alle Orte und Impulse. Eine solche Differentialgleichung mit einem stochastischen Anteil heißt stochastische Differentialgleichung [26] und sie macht nur Sinn, wenn man die statistischen Eigenschaften dieses Terms kennt. Im folgenden Abschnitt werden wir sehen, wie mit einer stochastischen Differentialgleichung gerechnet werden kann und wie man sie numerisch implementiert. Einen völlig anderen Zugang erhält man, wenn man sich bereits bei der Bewegungsgleichung auf Mittelwerte – bzw. allgemeiner Momente – der Zustandsvariablen beschränkt. Auf diese Weise erhält man aus (4.24) formal z.B. für den Mittelwert der Variablen xn : d xn = ẋn,det + ẋn,stoch . dt 1 (4.25) Tatsächlich war Brown nicht der erste, der eine solche Beobachtung machte, aber der erste, der zum Schluss kam, dass es sich dabei nicht um ein Anzeichen von Leben handelt, sondern jedes Material, sofern es nur zu einem sehr feinen Pulver verrieben wird, dieses Phänomen zeigt. 4.6 Der Random-Walk 153 Ob die beiden Terme auf der rechten Seite ausgewertet werden können und ob sie auf ein geschlossenes Differentialgleichungssystem führen, ist von Fall zu Fall unterschiedlich. In einigen Fällen hilft auch die Näherung f (x) ≈ f (x) , (4.26) die allerdings voraussetzt, dass die Wahrscheinlichkeitsverteilung im Phasenraum gut lokalisiert ist. Der dritte und letzte Zugang, den wir an dieser Stelle ansprechen wollen, ist eine Bewegungsgleichung für die Wahrscheinlichkeitsverteilung P , die sogenannte Mastergleichung. Alle drei Zugänge sowie deren numerische Implementation werden wir im folgenden Abschnitt am Beispiel des Random-Walk diskutieren. 4.6 Der Random-Walk Als Einstieg in die statistische Physik beginnen wir mit einem einfachen Modell für die Brownsche Bewegung, dem sogenannten Random-Walk, was soviel wie Zufallsmarsch bedeutet. Dazu betrachten wir Teilchen, die sich entlang einer Achse bewegen können. Der Einfachheit halber nehmen wir zunächst an, dass diese Bewegung in diskreten (und äquidistanten) Schritten erfolgt: zum Zeitpunkt 1 bewegt sich das Teilchen um eine Einheit nach links oder rechts, desgleichen zum Zeitpunkt 2, 3 und so weiter. Der entscheidende Punkt ist nun, dass die Frage, in welche Richtung die Bewegung erfolgt, zufällig entschieden wird. Bevor wir dieses Problem numerisch angehen werden, wollen wir zunächst an seinen physikalischen Hintergrund erinnern und es außerdem analytisch lösen. Beginnen wir mit der physikalischen Rechtfertigung dieses eigentlich recht seltsamen Modells (das so wichtig ist, dass jeder Physikstudent mehrmals während seines Studiums damit belästigt wird). Das Teilchen, dessen Bewegung wir betrachten, ist ein relativ großes Partikel in einer Flüssigkeit. Die Zufallsschritte der Größe ±1 entsprechen der Zitterbewegung, die durch Stöße mit kleineren, nicht mehr wahrnehmbaren Teilchen (den Flüssigkeitsmolekülen) herrühren (siehe Abschn. 4.5). Kommen wir nun zur analytischen Behandlung des Problems. Aufgrund des stochastischen Charakters der Bewegungsgleichung können wir die Bahn des Teilchens nicht vorhersagen. Das einzige, was wir angeben können sind Wahrscheinlichkeiten oder daraus resultierende Mittelwerte. Beginnen wir mit dem Mittelwert m1 = x. Dieser Mittelwert hängt zunächst davon ab, wo sich das Teilchen zum Zeitpunkt n = 0 aufgehalten hat und wie viele Zeiteinheiten (sprich Sprünge) inzwischen vergangen sind. Ohne Beschränkung der Allgemeinheit setzen wir das Teilchen zu Anfang in den Ursprung und berechnen (4.27) m1 (n) = m1 (n − 1) + ∆x . 154 4 Statistische Physik Hierbei ist ∆x der Sprung, den das Teilchen bei einem Zeitschritt macht. Da die Wahrscheinlichkeit für einen Sprung nach rechts (∆x = +1) genauso groß ist wie für einen Sprung nach links (∆x = −1) ist der Mittelwert ∆x gleich null. Also gilt m1 (n) = m1 (n − 1) = m1 (n − 2) = . . . = m0 = 0 . (4.28) Das Teilchen bewegt sich also im Mittel nicht von der Stelle. Zur Beantwortung der Frage, wie weit sich das Teilchen nach n Schritten vom Ausgangspunkt entfernt hat, ist der Mittelwert x also ungeeignet. Die Tatsache, dass dieser null ist, drückt ja nur aus, dass das Teilchen mit der gleichen Wahrscheinlichkeit links vom Ursprung wie rechts vom Ursprung ist. Zur Berechnung eines mittleren Abstandes müssen wir den Mittelwert m2 = x2 berechnen. m2 (n) = x(n)2 = (x(n − 1) + ∆x(n))2 = x(n − 1)2 + x(n − 1)∆x(n) + ∆x(n)2 . (4.29) (4.30) (4.31) Der erste Ausdruck ist identisch mit m2 (n − 1). Der zweite ist wegen der Unabhängigkeit von aktueller Position und nächstem Sprung identisch mit dem Produkt der Mittelwerte x(n − 1)∆x(n). Da ∆x(n) null ist, verschwindet dieser Term. Der letzte Ausdruck ist der Mittelwert von eins, denn egal ob ∆x(n) den Wert 1 oder -1 annimmt, das Quadrat ist in beiden Fällen 1. Insgesamt erhalten wir also m2 (n) = m2 (n − 1) + 1 = m2 (n − 2) + 2 = . . . = m2 (0) + n = n . (4.32) √ Das bedeutet, dass der mittlere Abstand d = m2 nur mit der Wurzel der Zeit anwächst – im Gegensatz zu einer linearen Zunahme bei einer deterministischen Bewegung (mit Ausnahme singulärer Punkte). 4.6.1 Stochastische Differentialgleichung des Random-Walk Schreiten wir nun zu einer ersten (noch auszubauenden) numerischen Implementierung des Random-Walks: 1 2 3 4 5 /************************************************************************** * Name: rw1.cpp * * Zweck: Simuliert den Random-Walk - ohne Skalierung * * verwendete Bibliothekl: GSL * **************************************************************************/ 6 7 8 9 10 11 12 #include #include #include #include #include #include <iostream> <fstream> <gsl/gsl_rng.h> <gsl/gsl_randist.h> <math.h> "tools.h" 4.6 Der Random-Walk 155 13 14 using namespace std; 15 16 int main( int argc, char *argv[] ) 17 18 19 20 { const int N_max = 1000000; const int n_bins = 100; 21 22 23 24 25 26 27 28 //-- Definition der Variablen int histo[2*n_bins+1], n, N, n_step, n_step_max; double x[N_max], xmean, xvar, sum1, sum2, dx, d_bins, rand; ifstream in_stream; ofstream out_stream1, out_stream2; const gsl_rng_type * T; gsl_rng * r; 29 30 31 32 33 34 35 36 37 //-- Fehlermeldung, if (argc<4) { cout << " Aufruf: cout << " cout << " exit(1); } wenn Input- und Outputfilename nicht uebergeben wurden rw1 infile outfile1 outfile2\n"; outfile1: Zeitentwickl. von Mittelwert und Varianz\n"; outfile2: Histogramm der Schlussverteilung\n"; 38 39 40 41 42 43 44 45 46 47 48 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream1.open(argv[2]); out_stream2.open(argv[3]); out_stream1 << "! Ergebnis-Datei out_stream2 << "! Ergebnis-Datei in_stream >> n_step_max; out_stream1 << "! n_step_max = " out_stream2 << "! n_step_max = " in_stream.close(); generiert von rw1\n"; generiert von rw1\n"; << n_step_max << "\n"; << n_step_max << "\n"; 49 50 51 52 53 //-- Initialisierung des Zufallszahlengenerators gsl_rng_env_setup(); T = gsl_rng_default; r = gsl_rng_alloc (T); 54 55 56 //-- Anfangsbedingungen for (N=0; N<N_max; N++) x[N] = 0; 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 //-- Zeitschleife -for (n_step=1; n_step<=n_step_max; n_step++) { sum1 = 0; sum2 = 0; for (N=0; n<N_max; N++) { rand = gsl_rng_uniform (r); if (rand > 0.5) dx = 1; else dx = -1; x[N] = x[N] + dx; sum1 = sum1 + x[N]; sum2 = sum2 + sqr(x[N]); } xmean = sum1 / N_max; 156 72 73 74 4 Statistische Physik xvar = sum2 / N_max - sqr(xmean); out_stream1 << n_step << " " << xmean << " " << xvar << "\n"; } 75 76 77 78 79 80 81 82 83 84 85 86 for (n=0; n<=2*n_bins+1; n++) histo[n] = 0; d_bins = sqrt(xvar) * 5 / n_bins; for (N=0; N<N_max; N++) { n = int( x[N] / d_bins + n_bins + 0.5); if ((n>=0) && (n<=2*n_bins+1)) histo[n]++; } for (n=0; n<=2*n_bins+1; n++) out_stream2 << (n-n_bins) * d_bins << " " << histo[n] / N_max / d_bins << " " << exp( -(sqr(n-n_bins)*d_bins) / 2 / xvar ) / sqrt( 2 * pi * xvar ); 87 88 89 90 out_stream1.close(); out_stream2.close(); } Aufgrund des statistischen Charakters des Problems hat die Bewegung eines einzelnen Teilchens nur begrenzte Aussagekraft. Deswegen simulieren wir im Programm rw1.cpp gleich die Dynamik einer Vielzahl von Teilchen (ein Ensemble mit identischen Anfangsbedingungen, dessen Größe durch die Variable n max festgelegt wird. Die zufälligen Stöße sorgen nun dafür, dass sich diese Teilchen voneinander wegbewegen und eine immer weiter ausgedehnte Verteilung bilden. Dieser Zufallsprozess spiegelt sich in den Zeilen 64–66 wider: gsl rng uniform liefert eine gleichverteilte Zufallszahl im Intervall [0, 1]; die nachfolgende Fallunterscheidung gewährleistet, dass mit gleicher Wahrscheinlichkeit ein Schritt nach links bzw. nach rechts gemacht wird. In der Ergebnisdatei rw1.res speichern wir den Mittelwert und die Varianz des Aufenthaltsortes ab. Wenn sowohl unsere analytische Berechnung als auch unsere numerische Implementierung korrekt sind, sollte ersterer bei etwa 0 bleiben, und die zweite linear ansteigen. Zusätzlich legen wir eine weitere Ergebnisdatei rw1 1.res an, die ein Histogramm des Endzustandes abspeichert: Dazu definieren wir ein Feld histo, dessen Einträge der Häufigkeit von Endwerten in einem bestimmten x-Intervall entsprechen. Das heißt, dass histo(0) gleich der Teilchen ist, die sich am Ende der Simulation im Intervall [-d bins/2,d bins/2] befunden haben, und allgemeiner ist histo(n) die Zahl der Teilchen im Intervall [(n-1/2)*d bins, (n+1/2)*d bins]. Doch zunächst zum zeitlichen Verlauf des Mittelwertes und der Varianz des Aufenthaltsortes. Wir sehen an der Skala in Abb. 4.1(a), dass der Mittelwert ungefähr bei null bleibt. Dieser Mittelwert ließe sich auf noch niedrigere Werte drücken, indem man die Zahl n max der Teilchen erhöht. Entscheidend ist auch eigentlich nicht, dass der ermittelte Mittelwert klein (gegen die typischen Einzelwerte) ist, sondern die Tatsache, dass dieser Mittelwert im Grenzfall unendlich großer Ensembles n max→ ∞ verschwindet – davon sollte sich der Leser überzeugen, indem er Ergebnisse von dem auf der CD befindlichen Programm rw1.cpp für mehrere Werte von n max vergleicht. 4.6 Der Random-Walk 0.01 100 (a) x (b) 2 x 0.0 157 50 -0.01 0 0 25 50 75 n 100 0 25 50 75 100 n Abb. 4.1. Mittelwert (a) und Varianz (b) des Aufenthaltsortes eines Teilchens beim Random-Walk. Das Ensemble umfasst 106 Teilchen Der lineare Verlauf (mit der vorhergesagten Steigung von 1) im rechten Diagramm (Abb. 4.1(b)) entspricht genau unseren Erwartungen, so dass wir uns eigentlich sicher sein können, die Grundform des Random-Walk richtig implementiert zu haben. Schauen wir uns jedoch das Histogramm das Endzustandes an: Abb. 4.2. Histogramm des Endzustandes nach einem RandomWalk (n=100, n max=106 ), wobei die Schrittweite beim Einzelschritt ±1 beträgt. Zum Vergleich wurde eine Gaußfunktion mit gleicher Varianz eingezeichnet Wenn wir genau hinsehen, stellen wir fest, dass das Histogramm keine glatte Kurve ist, sondern eine Reihe von Spitzen bei den geraden Zahlen, was vielleicht nicht das ist, was Sie erwartet hätten. Das muss aber so sein, weil – die Wahrscheinlichkeitsverteilung nur auf der Menge der ganzen Zahlen definiert ist, eine glatte Funktion also gar nicht im Bereich des möglichen liegt, – unser Teilchen vom Ursprung startend eine gerade Zahl von Sprüngen um eine Einheit gemacht hat und sich deshalb nicht an einem ungeraden n befinden kann. Aus diesem Grund wollen wir unser Modell für den Random-Walk später verbessern, bevor wir es zur Analyse physikalischer Fragestellungen verwenden werden. Zunächst jedoch werden wir an diesem einfachsten Beispiel noch 158 4 Statistische Physik die Mastergleichung erarbeiten und in einem Computerprogramm implementieren. 4.6.2 Mastergleichung des Random-Walks Die Mastergleichung ist, wie schon gesagt, die Bewegungsgleichung für die Wahrscheinlichkeitsverteilung P , die angibt mit welcher Wahrscheinlichkeit man das Teilchen an einem bestimmten Ort findet. Da sich das Teilchen in unserem Fall aufgrund der festgeschriebenen Schrittweite von ±1 immer nur bei 0, ±1, ±2, . . . aufhalten kann, ist auch die Wahrscheinlichkeitsverteilung P nur an diesen diskreten Punkten definiert. Da außerdem die Schritte nur zu festgelegten Zeiten 1, 2, . . . erfolgen, ist auch die Zeit diskret. Zusammengefasst hat die Wahrscheinlichkeitsverteilung P also zwei diskrete Argumente, wovon eines der Zeit und das andere dem Ort entspricht. Die Mastergleichung muss nun angeben, wie man aus allen P (n, m) zu einem festen Zeitpunkt n die Verteilung P (n + 1, m) zum nächsten Zeitpunkt ausrechnet. In unserem Fall ist das verhältnismäßig einfach, da sich ein Teilchen nur dann am Ort m befinden kann, wenn es zum vorhergehenden Zeitpunkt am Ort m − 1 war und es einen Schritt nach rechts gemacht hat, oder – es zum vorhergehenden Zeitpunkt am Ort m + 1 war und es einen Schritt nach links gemacht hat. – Da die Wahrscheinlichkeiten für einen Schritt nach links bzw. nach rechts beide 1/2 sind, ergibt sich die gesuchte Wahrscheinlichkeitsverteilung zum Zeitpunkt n + 1 als: P (n + 1, m) = 1 [P (n, m − 1) + P (n, m + 1)] . 2 (4.33) Das ist bereits die gesuchte Mastergleichung, deren Umsetzung in ein Computerprogramm keine besonderen Schwierigkeiten bereitet: 1 2 3 4 5 /************************************************************************** * Name: master1.cpp * * Zweck: Loest die Mastergleichung des Random-Walk - ohne Skalierung * * verwendete Bibliothek: keine * **************************************************************************/ 6 7 8 9 10 #include #include #include #include <iostream> <fstream> <math.h> "tools.h" 11 12 using namespace std; 13 14 int main( int argc, char *argv[] ) 15 16 17 { const int n_max = 100; 4.6 Der Random-Walk 159 18 19 20 21 22 23 //-- Definition der Variablen int n, n_step, n_step_max; double P[n_max], P1[n_max], x, xmean, xvar, sum1, sum2; ifstream in_stream; ofstream out_stream; 24 25 26 27 28 29 30 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<4) { cout << " Aufruf: master1 infile outfile\n"; exit(1); } 31 32 33 34 35 36 37 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von master1.cpp\n"; inout(n_step_max,"n_step_max"); in_stream.close(); 38 39 40 41 //-- Anfangsbedingungen for (n=0; n<n_max; n++) P[n] = 0; P[n_max/2] = 1; 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 //-- Zeitschleife -for (n_step=1; n_step<=n_step_max; n_step++) { P1[0] = 0.5 * P[1]; P1[n_max-1] = 0.5 * P[n_max-2]; for (n=1; n<n_max-1; n++) P1[n] = 0.5 * (P[n-1]+P[n+1]); sum1 = 0; sum2 = 0; for (n=0; n<n_max; n++) { x = n-n_max/2; P[n] = P1[n]; sum1 = sum1 + x * P[n]; sum2 = sum2 + sqr(x) * P[n]; } xmean = sum1; xvar = sum2 - sqr(xmean); cout << n_step << " " << xmean << " " << xvar << "\n"; out_stream << n_step << " " << xmean << " " << xvar << "\n"; } 62 63 out_stream.close(); 64 65 66 67 68 69 70 71 out_stream.open(argv[3]); for (n=0; n<n_max; n++) out_stream << n-n_max/2 << " " << P[n] << " " << exp( -(sqr(n-n_max/2)) / 2 / xvar ) / sqrt( 2 * pi * xvar ) << "\n"; out_stream.close(); } Wenn wir dieses Programm rechnen lassen, stellen wir fest, dass der lineare Anstieg der Varianz mit der Zeit perfekt, d.h. ohne statistisches Rauschen wiedergegeben wird. Der Grund liegt natürlich darin, dass die Bewegungsglei- 160 4 Statistische Physik chung für die Wahrscheinlichkeitsverteilung P kein stochastisches Element enthält, sondern der stochastische Charakter des Random-Walks lediglich darin zum Ausdruck kommt, dass die Verteilung P nicht scharf sondern verschmiert ist. Zumindest wenn wir die Mastergleichung numerisch lösen, haben wir diesen enormen Vorteil mit zwei Nachteilen erkauft: – Anstatt der Lösung einer Bewegungsgleichung haben wir es nun mit einem System gekoppelter Gleichungen zu tun. Insbesondere bei Systemen mit mehreren dynamischen Variablen steigt der numerische Aufwand enorm an, weil die Wahrscheinlichkeitsverteilung ein mehrdimensionales Feld darstellt. – Die numerisch gewonnene Lösung ist nur richtig, solange die Wahrscheinlichkeitsverteilung P nicht den Rand des berücksichtigten räumlichen Bereiches erreicht. Im vorliegenden Fall ist das der Fall, solange n step < n max ist. Neben der Entwicklung des Mittelwertes und der Varianz liefert das Programm master1.cpp auch die Wahrscheinlichkeitsverteilung P selbst, die unserem Histogramm in Abb. 4.2 entspricht: Abb. 4.3. Wahrscheinlichkeitsverteilung des Endzustandes nach einem RandomWalk, wobei die Schrittweite beim Einzelschritt ±1 beträgt. Die durchgezogene Linie ist eine Gaußfunktion mit der selben Varianz Der Vorteil der Lösung der Mastergleichung statt der stochastischen Differentialgleichung liegt darin, dass man nicht gezwungen ist, sehr große Ensembles zu benutzen, um zu genauen Ergebnissen zu gelangen. Man löst eine Bewegungsgleichung und erhält ein im Rahmen der Rechengenauigkeit exaktes Ergebnis. 4.6.3 Verbesserung des Random-Walk-Modells Sowohl in Abb. 4.2 als auch in Abb. 4.3 spiegelt sich eine Unzulänglichkeit unseres bisherigen Modells wieder, die wir bereits angesprochen haben: Zu einem gegebenen Zeitpunkt hält sich das Teilchen entweder nur an einem 4.6 Der Random-Walk 161 geraden oder nur an einem ungeraden Ort auf – das heißt, dass die Wahrscheinlichkeitsverteilung P diesen untypischen, gezackten Verlauf hat, der in den beiden Abbildungen zum Ausdruck kommt. Als Verbesserung bietet sich an, neben den beiden Sprüngen um ±1 auch zu erlauben, dass das Teilchen an seinem Ort verweilt. Dies ist Gegenstand der Übungsaufgabe 4.1. Hier wollen wir sofort den Übergang zu Gauß-förmig verteilten Sprungweiten machen. Dies bedeutet, dass wir lediglich die Zeilen ... 65 66 67 rand = gsl_rng_uniform (r); if (rand > 0.5) dx = 1; else dx = -1; ... im Programm rw1.cpp durch ... 65 dx = gsl_rng_gaussian(r,dv_0); ... ersetzen müssen. Sie finden das so entstandene Programm als rw2.cpp auf der beigelegten CD-ROM. Dabei haben wir gleichzeitig die Größe unseres Ensembles drastisch reduziert (von 106 auf 104 Teilchen), um im zugehörigen Histogramm des Endzustandes (Abb. 4.4) überhaupt einen Unterschied zwischen dem Ergebnis der Simulation (durchgezogene Kurve) und der theoretischen Vorhersage (eine Gaußfunktion, gestrichelte und dünner gezeichnete Kurve) zu sehen. Abb. 4.4. Histogramm des Endzustandes nach einem Random-Walk, wobei die Schrittweite beim Einzelschritt Gauß-förmig verteilt ist. Zum Vergleich wurde gestrichelt die vorhergesagte Gaußfunktion eingezeichnet Zu einer kompletten Implementierung ist es jedoch noch nötig, dass wir die Verbindung zu einer physikalischen Problemstellung herstellen – dabei werden wir feststellen, dass wir uns noch Gedanken über die korrekte Skalierung der Sprungweite mit dem Zeitintervall ∆t machen müssen, die wir bisher einfach zu 1 gesetzt hatten. 162 4 Statistische Physik Als naheliegendste Realisation wählen wir die Diffusion, genauer gesagt die Bewegung eines freien Teilchens, bei dem nicht wie bisher dessen Ort sondern dessen Geschwindigkeit einer Diffusion gemäß v 2 = Dt (4.34) unterliegt. Unser Teilchen bekommt nun also Stöße, bei denen sich dessen Geschwindigkeit stochastisch ändert. Diese führen zu einem nicht deterministischen Term in der Bewegungsgleichung d v = ξ(t) , dt (4.35) für die Geschwindigkeit v, die wir der Einfachheit halber skalar ansetzen, was physikalisch bedeutet, dass wir die Bewegung auf eine Raumdimension reduzieren. In (4.35) haben wir uns darüber hinaus auf eine freie Bewegung eingeschränkt, so dass die Bewegungsgleichung keinen determinstischen Anteil hat. Der stochastische Term ξ(t) ist mittelwertsfrei ξ(t) = 0 (4.36) und hängt streng genommen von den Details beim Stoß ab, in vielen Fällen ist jedoch die Markov-Näherung erlaubt: ξ(t)ξ(t ) = D δ(t − t ) . (4.37) Integrieren wir (4.35) über ein kleines Zeitintervall ∆t so erhalten wir: t+∆t dt ξ(t ) v(t + ∆t) = v(t) + (4.38) t = v(t) + Λ(∆t) . (4.39) Die neu definierte Größe Λ enthält die über das Zeitintervall [t, t + ∆t] akkumulierten Stöße und ist selbst eine Zufallszahl mit Gauß-förmiger Wahrscheinlichkeitsverteilung. Für die Implementierung im Computerprogramm benötigen wir den Mittelwert t+∆t dt ξ(t ) Λ = (4.40) t =0 (4.41) sowie die Varianz t+∆t Λ = 2 t+∆t dt ξ(t )ξ(t ) dt t = D∆t . (4.42) t (4.43) 4.6 Der Random-Walk 163 Da die Standardabweichung die Wurzel der Varianz ist, bedeutet (4.43), dass die Schrittweite nur mit der Wurzel von ∆t zunimmt – und nicht proportional zu ∆t, wie es bei deterministischen Prozessen der Fall ist. Das entsprechende Programm finden Sie als rw3.cpp auf der CD: 1 2 3 4 5 /************************************************************************** * Name: rw3.cpp * * Zweck: Simuliert den Random-Walk - mit Skalierung * * verwendete Bibliothekl: GSL * **************************************************************************/ 6 7 8 9 10 11 12 #include #include #include #include #include #include <iostream> <fstream> <gsl/gsl_rng.h> <gsl/gsl_randist.h> <math.h> "tools.h" 13 14 using namespace std; 15 16 int main( int argc, char *argv[] ) 17 18 19 20 { const int N_max = 1000000; const int n_bins = 100; 21 22 23 24 25 26 27 28 29 //-- Definition der Variablen int histo[2*n_bins+1], N, n, n_step, n_step_max; double x[N_max][2], xmean, xvar, vmean, vvar, D, t_max, t, dt; double sum1, sum2, sum3, sum4, dx, dv, dv_0, d_bins; ifstream in_stream; ofstream out_stream1, out_stream2; const gsl_rng_type * T; gsl_rng * r; 30 31 32 33 34 35 36 37 38 //-- Fehlermeldung, if (argc<4) { cout << " Aufruf: cout << " cout << " exit(1); } wenn Input- und Outputfilename nicht uebergeben rw3 infile1 outfile1\n"; outfile1: Zeitentwickl. von Mittelwert und Varianz\n"; outfile2: Histogramm der Schlussverteilung\n"; 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 wurden //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream1.open(argv[2]); out_stream2.open(argv[3]); in_stream >> D; in_stream >> t_max; in_stream >> dt; in_stream.close(); out_stream1 << "! Ergebnis-Datei generiert von rw3.cpp\n"; out_stream1 << "! D = " << D << "\n"; out_stream1 << "! t_max = " << t_max << "\n"; out_stream1 << "! dt = " << dt << "\n"; out_stream2 << "! Ergebnis-Datei generiert von rw3.cpp\n"; out_stream2 << "! D = " << D << "\n"; out_stream2 << "! t_max = " << t_max << "\n"; 164 54 55 56 4 Statistische Physik out_stream2 << "! dt = " << dt << "\n"; n_step_max = t_max / dt; dv_0 = D * sqrt(dt); 57 58 59 60 61 //-- Initialisierung des Zufallszahlengenerators gsl_rng_env_setup(); T = gsl_rng_default; r = gsl_rng_alloc (T); 62 63 64 65 //-- Anfangsbedingungen t = 0; for (n=0; n<N_max; n++) {x[n][0] = 0; x[n][1] = 0;} 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 //-- Zeitschleife -for (n_step=1; n_step<=n_step_max; n_step++) { t = t+dt; sum1 = 0; sum2 = 0; sum3 = 0; sum4 = 0; for (N=0; N<N_max; N++) { dx = x[N][1] * dt; dv = gsl_ran_gaussian(r,dv_0); x[N][0] = x[N][0] + dx; x[N][1] = x[N][1] + dv; sum1 = sum1 + x[N][0]; sum2 = sum2 + sqr(x[N][0]); sum3 = sum3 + x[N][1]; sum4 = sum4 + sqr(x[N][1]); } xmean = sum1 / N_max; xvar = sum2 / N_max - sqr(xmean); vmean = sum3 / N_max; vvar = sum4 / N_max - sqr(vmean); out_stream1 << t << " " << xmean << " " << xvar << " " << vmean << " " << vvar << "\n"; } 87 88 89 90 91 92 93 94 95 96 97 98 for (n=0; n<=2*n_bins+1; n++) histo[n] = 0; d_bins = sqrt(xvar) * 5 / n_bins; for (N=0; N<N_max; N++) { n = int( x[N][0] / d_bins + n_bins + 0.5); if ((n>=0) && (n<=2*n_bins+1)) histo[n]++; } for (n=0; n<=2*n_bins+1; n++) out_stream2 << (n-n_bins) * d_bins << " " << histo[n] / N_max / d_bins << " " << exp( -(sqr(n-n_bins)*d_bins) / 2 / xvar ) / sqrt( 2 * pi * xvar ); 99 100 101 102 out_stream1.close(); out_stream2.close(); } Die Ergebnisse dieses Programms sind in Abb. 4.5 dargestellt: Unter Umständen überraschen Sie zwei Details in Abb. 4.5: – die lineare Drift im Mittelwert x des Ortes (Abb. 4.5(a)), – das quadratische Ansteigen der Varianz x2 mit der Zeit t (Abb. 4.5(b)). Um die lineare Drift des Ortsmittelwertes zu verstehen, betrachten Sie bitte die Zeitentwicklung von v in Abb. 4.5(c): Durch die endlichen Größe des 4.6 Der Random-Walk 0.0 x (a) x 165 2 (b) 2000 -0.02 1000 -0.04 0 0 5 10 0 5 t (c) 0.0 v 10 t 2 v (d) 75 50 -0.005 25 -0.01 0 0 5 10 0 t 5 10 t Abb. 4.5. Mittelwert und Varianz von Ort und Geschwindigkeit eines Teilchens, das eine Geschwindigkeitsdiffusion erfährt. betrachteten Ensembles ist dieser Mittelwert nicht exakt null, sondern fluktuiert, wobei er in unserem Beispiel überwiegend negative Werte annimmt. Da sich der Ort durch Integration t x(t) = dt v(t ) (4.44) 0 ergibt, driftet der Ortsmittelwert langsam in den negativen Bereich. Sie sollten anhand der Beispielprogramme überprüfen, dass bei einer anderen Initialisierung des Zufallszahlengenerators diese Drift auch in Richtung positiver Werte erfolgen kann und dass deren Steigung mit größer werdendem Ensemble abnimmt. Um die quadratische Zunahme von x2 zu verstehen, muss man sich klar machen, dass Ort und Geschwindigkeit des Teilchens nicht statistisch unabhängig sind, sondern sich auf der positiven x−Achse überwiegend Teilchen mit positiver Geschwindigkeit befinden, während auf der negativen x−Achse überwiegend Teilchen mit negativer Geschwindigkeit sind. Diese Teilchen behalten im Mittel ihre Geschwindigkeit bei, wandern also nach rechts bzw. links und führen zu einem quadratischen Anwachsen der Varianz des Ortes. Um diese Argumentation zu untermauern, fügen wir einen Programmabschnitt ein, der eben diese Korrelation vx − v x zwischen Ort und Geschwindigkeit berechnet. Dazu führen wir zusätzlich zu den Variablen sum1 bis sum4 eine Variable sum5 ein, die entsprechend sum5 = sum5 + x(n,1) * x(n,2) 166 4 Statistische Physik aufsummiert wird. Daraus erhalten wir die gesuchte Korrelation corr corr = sum5 / dfloat(n_max) - xmean*vmean Betrachten wir diese Korrelation in Abhängigkeit von der Zeit, so erhalten wir Abb. 4.6, der wir entnehmen, dass v und x tatsächlich stark korreliert sind. Die Tatsache, dass die Korrelation positiv ist, bedeutet, dass Teilchen vx 40 20 0 0 5 10 t Abb. 4.6. Korrelation zwischen Ort und Geschwindigkeit eines freien Teilchens, das einer Geschwindigkeitsdiffusion unterliegt. mit positiver Geschwindigkeit am wahrscheinlichsten rechts vom Ursprung und Teilchen mit negativer Geschwindigkeit am wahrscheinlichsten links vom Ursprung anzutreffen sind. Ein Problem stellt die Tatsache dar, dass die Varianz der Geschwindigkeit ohne Beschränkung linear anwächst, denn diese Größe ist direkt mit der mittleren kinetischen Energie verknüpft: 1 1 Ekin = mv 2 = mv 2 2 2 (4.45) Dass die mittlere Energie immer weiter anwächst, ist natürlich nicht akzeptabel und stellt einen Fehler unseres Modells dar, den wir erst korrigieren müssen, bevor wir den gewonnenen Ergebnissen vertrauen können. Zu diesem Zweck erinnern wir uns an die Einsteinsche Interpretation der Brownschen Bewegung: die relativ großen, sichtbaren Teilchen erhalten Stöße durch andere, kleine und deswegen nicht wahrnehmbare Teilchen. Nach heutigem Sprachgebrauch bilden diese kleinen Teilchen ein Reservoir, das als so groß angenommen wird, dass es durch die sichtbaren Teilchen nicht beeinflusst wird. Wir müssen nun einige Eigenschaften dieses Reservoirs kennen, ohne dass wir uns jedoch für die Dynamik der einzelnen Teilchens des Reservoirs zu interessieren brauchen. Grundsätzlich geht man davon aus, dass das Reservoir im thermischen Gleichgewicht ist, was bedeutet, dass die Geschwindigkeitsverteilung in ihm Gauß-förmig ist. Wenn wir darüber hinaus annehmen, dass sich das Reservoir in Ruhe befindet und nur aus einer Teilchensorte besteht, wird es durch nur zwei Parameter beschrieben: seiner Temperatur und der Masse seiner Teilchen. 4.6 Der Random-Walk 167 Betrachten wir nun etwas genauer die Stöße zwischen den sichtbaren Teilchen, die uns interessieren, und den nicht mehr wahrnehmbaren Teilchen. Wie stark dieser Stoß ist und welche Richtung der Impulsübertrag hat, hängt sowohl von der relativen Geschwindigkeit als auch vom sogenannten Stoßparameter ab. Dieser Stoßparameter ist der Abstand, in dem der Schwerpunkt des einen Teilchens an dem des anderen vorbeifliegen würde, wenn es keine Wechselwirkung zwischen den beiden Teilchen gäbe (es also zu gar keinem Stoß kommen würde). In einer Dimension gibt es diesen Stoßparameter natürlich nicht, dort ist per definitionem jeder Stoß zentral. Um diese Stöße korrekt abzubilden, müssten wir streng genommen für jede Relativgeschwindigkeit eine Wahrscheinlichkeitsverteilung angeben, die angibt, mit welcher Wahrscheinlichkeit ein Stoß mit einem bestimmten Impulsübertrag stattfindet – bzgl. dieser Vorgehensweise verweisen wir auf Abschn. 4.8. Wir vereinfachen das Modell jedoch dahingehend, dass die Wahrscheinlichkeit für einen Stoß unabhängig von der Relativgeschwindigkeit ist und der Impulsübertrag ein bestimmter Prozentsatz der Relativgeschwindigkeit ist. Die erste Annahme ist sicherlich nie erfüllt und auch die zweite Annahme ist nur in eindimensionalen Situationen erfüllt, in denen jeder Stoß zentral ist. Trotzdem wollen wir dieses stark vereinfachte Modell analysieren und werden feststellen, dass es eine erstaunlich gute Beschreibung der Realität liefert. Zunächst jedoch müssen wir uns überlegen, wie wir den obigen Annahmen in einem Algorithmus gerecht werden können: Als erstes bestimmt ein Zufallszahlengenerator die Geschwindigkeit v1 des stoßenden Teilchens. Da sich das Reservoir im thermischen Gleichgewicht befindet und keine zusätzliche Driftgeschwindigkeit aufweist, müssen diese Zufallszahlen Gauß-förmig um den Ursprung verteilt sein. – Durch Subtraktion der Geschwindigkeit v2 des gestoßenen Teilchens erhalten wir daraus die Relativgeschwindigkeit: – vrel = v2 − v1 . – (4.46) Da der Impulsübertrag proportional zu vrel sein soll, müssen wir jetzt nur noch mit einem festen Faktor multiplizieren, der zum einen von der Häufigkeit von Stößen und zum anderen vom Massenverhältnis des (sichtbaren) gestoßenen und der (unsichtbaren) stoßenden Teilchen abhängt. Wir erkennen die Implementation dieser Vorgehensweise beim Programm rw4.cpp in Zeile 70: 1 2 3 4 5 /************************************************************************** * Name: rw4.cpp * * Zweck: Simuliert den Random-Walk - mit Skalierung * * verwendete Bibliothekl: GSL * **************************************************************************/ 6 7 #include <iostream> 168 8 9 10 11 12 4 Statistische Physik #include #include #include #include #include <fstream> <gsl/gsl_rng.h> <gsl/gsl_randist.h> <math.h> "tools.h" 13 14 using namespace std; 15 16 int main( int argc, char *argv[] ) 17 18 19 20 { const int N_max = 1000000; const int n_bins = 100; 21 22 23 24 25 26 27 28 29 //-- Definition der Variablen int histo[2*n_bins+1], N, n, n_step, n_step_max; double x[N_max][2], xmean, xvar, vmean, vvar, sum1, sum2, sum3, sum4; double t_max, t, dt, prob, v_therm, kick_strength, d_bins, dx, dv; ifstream in_stream; ofstream out_stream, out_stream2; const gsl_rng_type * T; gsl_rng * r; 30 31 32 33 34 35 36 37 38 39 //-- Fehlermeldung, if (argc<4) { cout << " Aufruf: cout << " cout << cout << " exit(1); } wenn Input- und Outputfilename nicht uebergeben wurden rw4 infile1 outfile1\n"; outfile1: Zeitentwicklung von Mittelwert"; " und Varianz\n"; outfile2: Histogramm der Schlussverteilung\n"; 40 41 42 43 44 45 46 47 48 49 50 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream2.open(argv[2]); inout(v_therm,"v_therm"); inout(kick_strength,"kick_strength"); inout(prob,"prob"); inout(t_max,"t_max"); inout(dt,"dt"); in_stream.close(); n_step_max = t_max / dt; prob = prob * dt; 51 52 53 54 55 //-- Initialisierung des Zufallszahlengenerators gsl_rng_env_setup(); T = gsl_rng_default; r = gsl_rng_alloc (T); 56 57 58 59 //-- Anfangsbedingungen t = 0; for (N=0; N<N_max; N++) {x[N][0] = 0; x[N][1] = 0;} 60 61 62 63 64 65 66 //-- Zeitschleife -for (n_step=1; n_step<=n_step_max; n_step++) { t = t+dt; sum1 = 0; sum2 = 0; sum3 = 0; sum4 = 0; 4.6 Der Random-Walk 67 68 69 70 71 72 73 74 75 76 77 78 79 80 169 for (n=0; n<N_max; n++) { dx = x[N][1] * dt; dv = (gsl_ran_gaussian(r,v_therm) - x[N][1]) * kick_strength; x[N][0] = x[N][0] + dx; x[N][1] = x[N][1] + dv; sum1 = sum1 + x[n][0]; sum2 = sum2 + sqr(x[N][0]); sum3 = sum3 + x[N][1]; sum4 = sum4 + sqr(x[N][1]); } xmean = sum1 / N_max; xvar = sum2 / N_max - sqr(xmean); vmean = sum3 / N_max; vvar = sum4 / N_max - sqr(vmean); out_stream << t << " " << xmean << " " << xvar << " " << vmean << " " << vvar << "\n"; } 81 82 83 84 85 86 87 88 89 90 91 for (n=0; n<=2*n_bins+1; n++) histo[n] = 0; d_bins = sqrt(xvar) * 5 / n_bins; for (N=0; N<N_max; N++) { n = int( x[N][0] / d_bins + n_bins + 0.5); if ((n>=0) && (n<=2*n_bins+1)) histo[n]++; } out_stream.close(); out_stream2.close(); } Betrachten wir nun in Abb. 4.7 wieder die Zeitentwicklung von Ort und Impuls (jeweils Mittelwert und Varianz) so sehen wir, dass nach einer Übergangszeit, in der die Varianz der Geschwindigkeit linear anwächst, diese dann 100 (a) 0.002 x x (b) 2 0.0 50 -0.002 0 0 5 10 0 5 t v t (c) 0.002 10 2 3 (d) v 0.0 2 -0.002 1 -0.004 0 0 5 10 t 0 5 10 t Abb. 4.7. Zeitentwicklung von Mittelwert und Varianz des Aufenthaltsortes und der Geschwindigkeit eines Teilchens mit einer – einigermaßen – realistischen Modellierung der Brownschen Bewegung. 170 4 Statistische Physik einen stabilen Wert annimmt (Abb. 4.7(d)). Außerdem nimmt danach die Varianz des Ortes (Abb. 4.7(b)) nur noch linear und nicht mehr quadratisch zu. Das ist das Verhalten, wie es ein Brownsches Teilchen in der Realität wirklich zeigt! 4.7 Thermisches Hüpfen Im vorangegangenen Abschnitt haben wir den Grundstein für nichttriviale Probleme aus der statistischen Physik gelegt, auf dem wir nun aufbauen wollen. Während die Beispiele des letzten Abschnittes jedoch noch analytisch lösbar waren (also eigentlich gar keiner numerischen Behandlung bedurften), wenden wir uns nun Problemen zu, die ohne Hilfe des Computers nicht mehr gelöst werden können. Dabei werden wir feststellen, dass wir unsere Grundprogramme nur geringfügig verändern müssen! Das Problem, dem wir uns nun zuwenden wollen, ist thermisches Hüpfen. Stellen wir uns dazu ein Teilchen vor, das sich unter dem Einfluss einer Kraft bewegt, die wir als Gradient eines Potentials beschreiben können. Dieses Potential soll mehrere Minima (also stabile Gleichgewichtszustände) besitzen. Wenn das Teilchen nun in einem dieser Minima ruht, wird es dort für immer verharren. Wenn wir uns jedoch vorstellen, dass es durch andere Teilchen gestoßen wird, kann es durch diese Stöße dieses Minimum verlassen und eventuell in einen anderen stabilen Zustand gelangen. Die Modifikation gegenüber rw3.cpp besteht lediglich darin, dass wir zu der statistischen Bewegung eine deterministische hinzufügen – wir ersetzen also nur die Zeilen, in denen dv berechnet wird, durch: dv = (gsl_ran_gaussian(r,v_therm) - x[N][1]) * kick_strength + dv_det(x[N][0]); Der deterministische Anteil hängt nur vom Ort des Teilchens ab, da wir die Bewegung in einem zeitunabhängigen Potential zugrunde legen wollen – die Verallgemeinerung auf zeitabhängige Potentiale oder auf Kräfte, die nicht durch ein Potential beschrieben werden können, stellt jedoch kein größeres Problem dar. Nun müssen wir noch das Potential festlegen, in dem sich das Teilchen bewegen soll. Grundsätzlich sind alle Potentiale geeignet, die mehrere Minima haben – wir wählen hier ein Polynom 4. Ordnung mit zwei unterschiedlich tiefen Minima (siehe Abb. 4.8): V (x) = 4 n=0 mit an xn . (4.47) 4.7 Thermisches Hüpfen 171 Abb. 4.8. Potentialverlauf für ein Brownsches Teilchen. a2 = −0.5 (4.48) a3 = −0.01 a4 = 0.005 . (4.49) (4.50) Die beiden verbleibenden Koeffizienten a0 und a1 sind null. Als letztes müssen wir noch einige Gedanken darauf verwenden, was wir als Anfangsverteilung P (x, t = 0) wählen wollen und welche Variablen wir ausgeben wollen, um das Hüpfen zu verfolgen. Als Ausgangssituation wählen wir sinnvollerweise, dass das Teilchen in einem der beiden Minima ruht. Um zu erreichen, dass das Teilchen mit einer möglichst hohen Wahrscheinlichkeit seine Ausgangsposition verlässt und in die andere Potentialmulde hüpft, wählen wir als Anfangsort das flachere der beiden Potentialminima, also das linke. Unser Programm soll dann zum einen für jeden Zeitpunkt die Wahrscheinlichkeit ausgeben, das Teilchen links bzw. rechts zu finden. Zum anderen möchten wir wenigstens am Ende der Zeitentwicklung auch die komplette Wahrscheinlichkeitsverteilung P (x, tend ) in einer getrennten Ausgabedatei abspeichern. Damit steht unser erstes Programm zum thermischen Hüpfen: 1 2 3 4 5 /************************************************************************** * Name: th1.cpp * * Zweck: Simuliert thermisches Huepfen zwischen zwei Potentialminima * * verwendete Bibliothekl: GSL * **************************************************************************/ 6 7 8 9 10 11 12 #include #include #include #include #include #include <iostream> <fstream> <gsl/gsl_rng.h> <gsl/gsl_randist.h> <math.h> "tools.h" 13 14 using namespace std; 15 16 17 18 //-- globale Variablen double dt; double a1, a2, a3, a4; 19 20 //------------------------------------------------------------------------- 172 4 Statistische Physik 21 22 double dv_det(double x) 23 24 25 26 27 { double a = -2*a2*x - 3*a3*sqr(x) - 4*a4*pow(x,3); return a * dt; } 28 29 //------------------------------------------------------------------------- 30 31 int main( int argc, char *argv[] ) 32 33 34 35 { const int N_max = 1000000; const int n_bins = 100; 36 37 //-- Definition der Variablen 38 39 40 41 42 43 44 45 int histo[2*n_bins+1], n, N, n_step, n_step_max; double x[N_max][2], xmean, xvar, sum1, sum2, sum3, sum4, dx, dv; double v_therm, kick_strength, prob_links, prob_rechts, t_max, t, d_bins; ifstream in_stream; ofstream out_stream; const gsl_rng_type * T; gsl_rng * r; 46 47 48 49 50 51 52 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<4) { cout << " Aufruf: th1 infile outfile1 outfile2\n"; exit(1); } 53 54 55 56 57 58 59 60 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von th1.cpp\n"; inout(v_therm,"v_therm"); inout(kick_strength,"kick_strength"); inout(t_max,"t_max"); inout(dt,"dt"); in_stream.close(); 61 62 63 64 65 //-- Berechnung einiger benoetigter Parameter n_step_max = t_max / dt; v_therm = v_therm / sqrt(dt); kick_strength = kick_strength * dt; 66 67 68 69 70 71 //-a1 = a2 = a3 = a4 = Festlegung des Potentials 0; -0.5; -0.01; 0.005; 72 73 74 75 76 //-- Initialisierung des Zufallszahlengenerators gsl_rng_env_setup(); T = gsl_rng_default; r = gsl_rng_alloc (T); 77 78 79 //-- Anfangsbedingungen t = 0; 4.7 Thermisches Hüpfen 80 81 82 83 84 for (N=0; N<N_max; N++) { x[N][0] = (-3*a3-sqrt(9*a3*a3-32*a2*a4))/(8*a4); x[N][1] = 0; } 173 // Potentialmin. // in Ruhe 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 //-- Zeitschleife -for (n_step=1; n_step<=n_step_max; n_step++) { t = t+dt; sum1 = 0; sum2 = 0; sum3 = 0; sum4 = 0; prob_links = 0; prob_rechts = 0; for (N=0; N<N_max; N++) { dx = x[N][1] * dt; dv = (gsl_ran_gaussian(r,v_therm) - x[N][1]) * kick_strength + dv_det(x[N][0]); x[N][0] = x[N][0] + dx; x[N][1] = x[N][1] + dv; sum1 = sum1 + x[N][0]; sum2 = sum2 + sqr(x[N][0]); if (x[N][0]<0) prob_links++; else prob_rechts++; } prob_links = prob_links / float(N_max); prob_rechts = prob_rechts / float(N_max); xmean = sum1 / float(N_max); xvar = sum2 / float(N_max) - sqr(xmean); out_stream << t << " " << prob_links << " " << prob_rechts << "\n"; } 111 112 out_stream.close(); 113 114 115 116 117 118 119 120 121 122 123 124 125 out_stream.open(argv[3]); for (n=0; n<2*n_bins+1; n++) histo[n] = 0; d_bins = sqrt(xvar) * 5. / float(n_bins); cout << xvar << " " << d_bins << " " << N_max << "\n"; for (N=0; N<N_max; N++) { n = int( x[N][0] / d_bins + n_bins + 0.5); if ((n>=0) && (n<2*n_bins+1)) histo[n]++; } for (n=0; n<2*n_bins+1; n++) out_stream << (n-n_bins) * d_bins << " " << histo[n] / float(N_max) / d_bins << "\n"; 126 127 128 out_stream.close(); } Das Programm th1.cpp liefert uns zum einen in der ersten Ausgabedatei den Zeitverlauf der Aufenthaltswahrscheinlichkeit links bzw. rechts (Zeile 109) und zum anderen in der zweiten Ausgabedatei die Wahrscheinlichkeitsdichte P (x) (Zeile 114–127). Wenn wir uns nun zunächst die beiden Aufenthaltswahrscheinlichkeiten rechts und links als Funktion der Zeit anschauen (Abb. 4.9), stellen wir fest, dass wie erwartet die Wahrscheinlichkeit links immer mehr abnimmt, während sie rechts entsprechend zunimmt. Allerdings 174 4 Statistische Physik sind im stationären Zustand, der sich für t → ∞ einstellt, nicht alle Teilchen im tieferen Minimum, vielmehr befinden sich ca. 26% weiterhin auf der rechten Seite. Der Grund hierfür ist der geringe Unterschied in der Tiefe der beiden Potentialminima, was dazu führt, dass – bei effektiv endlicher Temperatur des Reservoirs – Teilchen auch vom tieferen Minimum in das höhere zurückhüpfen können. Abb. 4.9. Aufenthaltswahrscheinlichkeit im linken (durchgezogene Linie) bzw. rechten Potentialminimum (gestrichelte Linie) als Funktion der Zeit Für t → ∞ stellt sich das thermische Gleichgewicht ein, bei dem die beiden Potentialmulden so besetzt sind, dass sich das thermische Hüpfen von links nach rechts sowie in der umgekehrten Richtung die Waage halten. Bei t = 300 ist dies bereits sehr gut erfüllt – Abb. 4.10 zeigt uns diese Gleichgewichtsverteilung, wie sie in der zweiten Ausgabedatei von th1.cpp abgespeichert wurde: Abb. 4.10. Aufenthaltswahrscheinlichkeit P (x), die sich bei t = 300 eingestellt hat Interessant ist es, das Potential so zu variieren, dass der Potentialunterschied zwischen den beiden Mulden kontrolliert werden kann. Dies ist Gegenstand der Übungsaufgabe 4.2. Hier jedoch wollen wir das Potential in einer anderen Weise variieren, um uns einer anderen Fragestellung zuzuwenden. Uns interessiert an dieser Stelle die Zeitskala, die das System braucht, um den Gleichgewichtszustand zu erreichen. Dazu betrachten wir Potentiale der Form 4.7 Thermisches Hüpfen 175 1 V (x) = −ax20 x2 + ax4 . (4.51) 2 Unabhängig von a hat dieses Potential zwei gleich tiefe Minima bei x = ±x0 , an denen das Potential V den Wert −ax40 /2 annimmt. Der Parameter a erlaubt es uns, die Höhe der Potentialbarriere zwischen diesen beiden Minima zu variieren, denn bei null hat V unabhängig von a den Wert null. Wenn wir für die Wahscheinlichkeit P1 , das Teilchen links anzutreffen, ein exponentielles Zeitverhalten der Form t 1 1 exp − (4.52) P1 (t) = + P1 (0) − 2 τ 2 ansetzen und die Anfangssituation so wählen, dass P1 (0) = 1 ist, erhalten wir die für das Hüpfen charakterische Zeit τ aus der Bedingung, dass 1 1 1+ (4.53) P1 (τ ) = 2 e und entsprechend P2 (τ ) = 1 2 1− 1 e (4.54) ist. Die Modifikationen, die wir – ausgehend vom Programm th1.cpp – machen müssen, sind also – Einschränkung der betrachteten Potentiale auf Potentiale vom Typ (4.51). – Die stochastische Differentialgleichung wird nicht mehr bis zu einer vorgegebenen Endzeit tend integriert, sondern bis zum ersten Mal P2 den durch (4.54) festgelegten Wert überschreitet. Sie finden das entsprechend modifizierte Programm als th2.cpp auf der CDROM zum Buch. Das Ergebnis (Abb. 4.11) zeigt das erwartete starke Ansteigen der Hüpfzeit τ mit der Höhe der Potentialbarriere, also mit a. Abb. 4.11. Charakteristische Hüpfzeit τ als Funktion des Parameters a, der die Höhe der Potentialbarriere charakterisiert Bei genauem Hinsehen erkennen Sie in Abb. 4.11, dass die Thermalisierung für a → 0 nicht verschwindet, was die Tatsache widerspiegelt, dass auch 176 4 Statistische Physik ohne Barriere das Brownsche Teilchen Zeit braucht, um von seinem Startpunkt in die rechte Hälfte des Ortsbereichs zu gelangen. Erstaunlicher ist die Tatsache, dass τ mit wachsendem a zunächst abnimmt und erst ab etwa a = 0.1 ansteigt. 4.8 Thermalisierung in Gasen In diesem Abschnitt werden wir uns genauer mit dem Übergang ins thermische Gleichgewicht beschäftigen und ein mikroskopisches Modell auf der Basis einer Master-Gleichung dafür aufstellen und behandeln. Dabei werden wir uns auf einatomige Gase beschränken. Bei ihnen gibt es nur translatorische Freiheitsgrade und die kinetische Energie ist eine Funktion der Geschwindigkeit: 1 (4.55) Ekin = mv 2 . 2 Da wir uns außerdem auf ein freies, homogenes Gas beschränken, reduziert sich unsere Aufgabe darauf, die Geschwindigkeitsverteilung P (v) zu einem bestimmten Zeitpunkt zu kennen. Der einzige Prozess, der zu Änderungen in dieser Geschwindigkeitsverteilung – und damit zur Thermalisierung – führt, sind Stöße der Teilchen untereinander. Da wir rotatorische Freiheitsgrade ausgeschlossen haben und darüber hinaus annehmen wollen, dass die inneren Freiheitsgrade von Atomen zu hohe Energien erfordern, um angeregt zu werden, sind diese Stöße elastisch. Bezeichnen wir mit p(v1 , v2 , ∆v) die Häufigkeit für Stöße zwischen zwei Teilchen mit den Geschwindigkeiten v 1 und v 2 , bei denen der Geschwindigkeitsübertrag vom zweiten auf das erste Teilchen ∆v ist, so erhalten wir für P (v) die folgende Master-Gleichung: d P (v) = dv 1 dv 2 p(v 1 , v2 , v − v 1 ) dt − dv 2 d∆v p(v, v 2 , ∆v) . (4.56) Diese spezielle Master-Gleichung für die Geschwindigkeitsverteilung in einem Gas wird Boltzmann-Gleichung genannt [27]. Der erste Term beschreibt Stöße, bei denen eines der beiden Teilchen nach dem Stoß die Geschwindigkeit v hat; der zweite Term enthält die Stöße, bei denen ein Teilchen vor dem Stoß die Geschwindigkeit v hatte. Die Häufigkeit von Stößen ist proportional zur Wahrscheinlichkeit, Teilchen mit den erforderlichen Geschwindigkeiten anzutreffen. Der Proportionalitätsfaktor – der differentielle Wirkungsquerschnitt σ – kann aus Symmetriegründen nur von der Relativgeschwindigkeit v 1 − v 2 und dem Geschwindigkeitsübertrag ∆v abhängen: p(v 1 , v 2 , ∆v) = P (v 1 )P (v 2 ) σ(v 1 − v 2 , ∆v) . Wenn wir dies in (4.56) einsetzen, erhalten wir: (4.57) 4.8 Thermalisierung in Gasen d P (v) = dt 177 dv 1 dv 2 P (v 1 )P (v 2 )σ(v 1 − v 2 , v − v 1 ) − dv 2 d∆v P (v)P (v 2 )σ(v − v 2 , ∆v) = dv 1 dv 2 d∆vP (v 1 )P (v 2 )σ(v 1 − v 2 , ∆v) × [δ(v − v 1 − ∆v) − δ(v 1 − v)] . (4.58) (4.59) Dieses Doppelintegral (4.59) können wir durch Übergang zu den Fouriertransformierten Funktionen P̃ (u) = dv P (v) exp(iuv) (4.60) σ̃(u12 , ∆u) = dv 12 d∆v σ(v 12 , ∆v) exp(iu12 v 12 + i∆u∆v) .(4.61) auf ein Einfachintegral reduzieren: d 1 du1 P̃ (u1 )P̃ (u − u1 ) P̃ (u) = dt (2π)3 × (σ̃(u − u1 , u) − σ̃(u − u1 , 0)) . (4.62) Zur besseren Übersichtlichkeit verzichten wir im weiteren auf die Tilde zur Kennzeichnung, dass die Fouriertransformierten Funktionen gemeint sind. 4.8.1 Energieerhaltung Bevor wir uns an die numerische Implementierung der Bewegungsgleichung (4.62) machen können, müssen wir noch den Wirkungsquerschnitt σ festlegen. Hierzu müssen wir uns jedoch zunächst überlegen, ob dieser differentielle Wirkungsquerschnitt irgendwelchen Einschränkungen durch einzuhaltende Erhaltungssätze unterliegt. Die Geschwindigkeiten vor dem Stoß sind v1 bzw v2 vor dem Stoß und v1 + ∆v bzw. v2 − ∆v nach dem Stoß. Wir sehen, dass die Impulserhaltung automatisch erfüllt ist, die Energieerhaltung müssen wir jedoch durch eine zusätzliche Bedingung sicherstellen: v12 + v22 = (v1 + ∆v)2 + (v2 − ∆v)2 0 = 2(v1 − v2 )∆v + 2∆v 2 2 2 v2 − v1 v2 − v1 = . ∆v − 2 2 (4.63) ∆v liegt also auf einem Kreis um (v2 − v1 )/2, der durch den Ursprung und durch v2 −v1 geht – ersteres besagt, dass kein Impulsübertrag keinen Konflikt mit der Energieerhaltung darstellt, und letzteres besagt, dass auch Stöße 178 4 Statistische Physik erlaubt sind, bei denen nachher Teilchen 1 die Geschwindigkeit hat, die vorher Teilchen 2 hatte, und umgekehrt. Wenn wir uns der Anschaulichkeit halber auf zwei Dimensionen beschränken, so können wir das Fourierintegral von (4.61) auf diesen Kreis beschränken (bei drei Dimensionen würde Bedingung 4.63 zu einer Integration über eine Kugeloberfläche führen). Dazu führen wir die folgenden Winkel ein, wobei wir die x-Achse in Richtung des Vektors u12 legen: Abb. 4.12. Schematische Darstellung zur Berechnung des Wirkungsquerschnittes im Fourierraum Damit ergibt sich u12 v 12 = u12 v12 cos φ12 (4.64) 1 (4.65) ∆u∆v = ∆u (v 12 + v12 eφ ) 2 1 = ∆uv12 (cos(φ12 − φ∆u ) + cos(φ − φ∆u )) . (4.66) 2 Für das Integral (4.61) erhalten wir: σ (u12 , ∆u) = dv12 dφ dφ12 σ(v 12 , ∆v(φ12 − φ∆u ), φ12 − φ∆u ) ∆uv12 × exp i u12 v12 cos φ12 (cos(φ12 − φ∆u ) + cos(φ − φ∆u )) 2 dφ dθ σ(v12 , φ ) = dv12 (4.67) ∆uv12 × exp i u12 v12 cos(θ + φ∆u ) + (cos θ + cos(φ + θ)) , 2 wobei wir die Winkel und φ = φ − φ12 θ = φ12 − φ∆u (4.68) (4.69) 4.8 Thermalisierung in Gasen 179 eingeführt haben. Zunächst überzeugen wir uns davon, dass der Imaginärteil von σ(u12 , ∆u) null ist: Im(σ(u12 , ∆u)) = dv12 dφ dθ σ(v12 , φ ) sin ξ , (4.70) mit ξ = u12 v12 cos(θ + φ∆u ) + ∆uv12 (cos θ + cos(φ + θ)) . 2 (4.71) Wir müssen lediglich die Integration über θ betrachten. ξ lässt sich mittels der Additionstheoreme in die Form ξ = A cos(θ + B) (4.72) bringen, wobei uns die Werte von A und B nicht zu interessieren brauchen. Da die Integration über θ ohnehin über alle Werte von −π bis π geht, können wir die Verschiebung B eliminieren und erhalten einen Integranden, der antisymmetrisch in θ ist. Aufgrund des symmetrischen Integrationsbereiches [−π, π] muss das Integral also null ergeben. Schwieriger ist der Realteil von σ(u12 , ∆u), dessen Wert wir numerisch berechnen müssen. Dazu müssen wir zunächst den Wirkungsquerschnitt im Geschwindigkeitsraum, dessen genaue Form wir bisher offen gelassen haben, festlegen, z.B.: 2 cos2 (∆φ) (4.73) σ = σ0 exp −αv12 Wenn wir versuchen, das Dreifach-Integral (4.67) direkt numerisch für viele Werte von u12 und ∆u zu berechnen, stellen wir schnell fest, dass die benötigten Rechenzeiten enorm sind. Glücklicherweise können wir für die Abhängigkeit (4.73) des Wirkungsquerschnitts von v12 die Integration über v12 analytisch ausführen, so dass nur ein Doppelintegral verbleibt: 2 a σ0 π dφ σ(u12 , ∆u) = dθ exp , (4.74) 2 α 4α wobei wir die Abkürzung 1 a = u12 cos(θ + φ∆u ) + ∆u (cos θ + cos(φ + θ)) 2 (4.75) eingeführt haben. Das Programm sigma.cpp berechnet dieses Doppelintegral für n3 ∆u n1 ∆u ∆u = ni = −nmax , . . . nmax . (4.76) u12 = n2 ∆u n4 ∆u 180 1 2 3 4 5 6 4 Statistische Physik /************************************************************************** * Name: sigma.cpp * * Zweck: Berechnet den Wirkungsquerschnitt im Fourierraum * * Gleichung: siehe Buch * * verwendete Bibiliothek: GSL * **************************************************************************/ 7 8 9 10 11 12 13 14 15 #include #include #include #include #include #include #include #include <iostream> <fstream> <gsl/gsl_vector.h> <gsl/gsl_integration.h> <gsl/gsl_sort_vector.h> <gsl/gsl_sort.h> <math.h> "tools.h" 16 17 using namespace std; 18 19 20 21 //--globale Variablen double u_12, delta_u, phi_delta, alpha; int count_outer; 22 23 24 25 26 27 //--Parameter fuer die innere bzw. mittlere Integration struct param_type { double theta; }; 28 29 //------------------------------------------------------------------------ 30 31 double f_inner(double phi_s, void *params) 32 33 34 35 { double a; double theta = ((struct param_type *) params) -> theta; 36 37 38 39 40 a = u_12 * cos(theta+phi_delta) + 0.5 * delta_u * (cos(theta)+cos(phi_s+theta)); return 0.5 * sqrt(pi/alpha) * exp(-0.25*sqr(a)/alpha); } 41 42 //------------------------------------------------------------------------ 43 44 double f_outer(double theta, void *params) 45 46 47 48 49 50 { int limit = 1000; double epsabs = 1.e-8, epsrel = 1.e-8; double result, abserr; param_type params_inner; 51 52 53 gsl_integration_workspace *w_inner = gsl_integration_workspace_alloc(10000); 54 55 56 57 gsl_function F_inner; F_inner.function = &f_inner; F_inner.params = &params_inner; 58 59 gsl_integration_qag(&F_inner, -pi, pi, epsabs, epsrel, limit, 4.8 Thermalisierung in Gasen 181 2, w_inner, &result, &abserr); 60 61 62 gsl_integration_workspace_free(w_inner); 63 64 65 return result; } 66 67 //------------------------------------------------------------------------ 68 69 main( int argc, char *argv[] ) 70 71 { 72 73 74 75 76 77 78 79 //-- Definition der Variablen int limit = 1000; int n, n1, n2, n3, nmax, nmax1; double epsabs = 1.e-5, epsrel = 1.e-5; double result, abserr; double u1, u2, du, dphi, delta_u_array[1001*1001], delta_u_2[1001*1001]; double mu = 10; 80 81 82 ifstream in_stream; ofstream out_stream; 83 84 85 86 87 88 89 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: sigma infile outfile\n"; exit(1); } 90 91 92 93 94 95 96 97 98 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von sigma\n"; inout(alpha,"alpha"); inout(du,"du"); inout(nmax1,"nmax1"); in_stream.close(); out_stream.precision(12); 99 100 101 102 103 104 105 106 107 108 109 110 111 n = 0; gsl_vector *delta_u_array_sorted = gsl_vector_alloc((nmax1+2)*(nmax1+1)/2); for (n1 = 0; n1<=nmax1; n1++) for (n2 = 0; n2<=n1; n2++) { u1 = n1 * du; u2 = n2 * du; delta_u_array[n] = sqrt( sqr(u1)+sqr(u2) ); gsl_vector_set(delta_u_array_sorted,n,delta_u_array[n]); n++; } nmax = n; 112 113 114 115 116 117 118 size_t nsize = delta_u_array_sorted->size; gsl_permutation * perm = gsl_permutation_alloc(nsize); gsl_permutation * rank = gsl_permutation_alloc(nsize); gsl_sort_vector_index(perm, delta_u_array_sorted); gsl_permutation_inverse(rank,perm); 182 119 120 121 122 123 124 125 4 Statistische Physik delta_u_2[0] = 0; n = 1; for (n1=1; n1<(nmax1+2)*(nmax1+1)/2 ; n1++) if (delta_u_array[perm->data[n1]]!=delta_u_array[perm->data[n1-1]]) { delta_u_2[n] = delta_u_array[perm->data[n1]]; n++; } nmax = n; cout << " nmax = " << nmax << "\n"; 126 127 128 129 130 131 132 133 for (n1=0; n1<nmax; n1++) { for (n2=0; n2<nmax; n2++) { u_12 = delta_u_2[n1]; delta_u = delta_u_2[n2]; count_outer = 0; 134 135 136 gsl_integration_workspace *w_outer = gsl_integration_workspace_alloc(10000); 137 138 139 140 gsl_function F_outer; F_outer.function = &f_outer; F_outer.params = &mu; 141 142 143 gsl_integration_qags(&F_outer, -pi, pi, epsabs, epsrel, limit, w_outer, &result, &abserr); 144 145 gsl_integration_workspace_free(w_outer); 146 147 148 149 150 151 result = 0.5 * result; out_stream << u_12 << " " << delta_u << " " << result << "\n"; } cout << n1 << " " << result << "\n"; } 152 153 154 out_stream.close(); } In den Funktionen f inner bzw. f outer erkennen Sie den Integranden für die innere bzw. äußere Integration, wobei letztere eine Integration über f inner beinhaltet. Beachten Sie, dass der Integrand nur von den Beträgen von u12 und ∆u abhängt. Das Programm macht davon insofern Gebrauch, als es in den Zeilen 100–111 die vorkommenden Beträge sortiert und eventuell mehrfach vorkommende Werte streicht. Im konkreten Fall von nmax = 35 reduziert dies den Aufwand von ca. 1.68 Millionen Berechnungen des Doppelintegrals auf 287296. Nachdem wir nun den Wirkungsquerschnitt im u-Raum kennen, können wir die Zeitentwicklung (4.62) selbst implementieren: 1 2 3 4 5 /************************************************************************** * Name: therm.cpp * * Zweck: Berechnet die Thermalsierung von Gasen im Fourierraum * * Gleichung: siehe Buch * **************************************************************************/ 6 7 8 #include <iostream> #include <fstream> 4.8 Thermalisierung in Gasen 9 10 183 #include <math.h> #include "tools.h" 11 12 using namespace std; 13 14 15 16 17 //-- globale const int const int const int Variablen nmax = 35; nmax_sorted = 536; nges = 2*nmax+1; 18 19 //------------------------------------------------------------------------- 20 21 main( int argc, char *argv[] ) 22 23 { 24 25 26 27 28 29 30 31 32 33 34 35 //-- Definition der Variablen int n1, n2, n3, n4, m1, m2, m3, m4, hit, n_t, n_t_max; double s, sum_real, sum_imag, dt, dv, v1, v2, du, u1, u2, t_max, arg; double N_1, v_1, sigma_1, a_1, N_2, v_2, sigma_2, a_2, v_quer, v2_quer; double min_real, max_imag, sigma[nges][nges][nges][nges]; double P_u_real[nges][nges], P_u_imag[nges][nges]; double P_v_real[nges][nges], P_v_imag[nges][nges]; double dP_dt_real[nges][nges], dP_dt_imag[nges][nges], P_1d[nges]; const double eps = 1.e-4; ifstream in_stream, in_stream2; ofstream out_stream; 36 37 38 39 40 41 42 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<4) { cout << " Aufruf: therm infile1 infile2 outfile\n"; exit(1); } 43 44 45 46 47 48 49 50 51 52 //-- Einlesen der Parameter -in_stream.open(argv[1]); in_stream2.open(argv[2]); out_stream.open(argv[3]); out_stream << "! Ergebnis-Datei generiert von therm.cpp\n"; inout(N_1,"N_1"); inout(v_1,"v_1"); inout(sigma_1,"simga_1"); inout(N_2,"N_2"); inout(v_2,"v_2"); inout(sigma_2,"sigma_2"); inout(t_max,"t_max"); inout(n_t_max,"n_t_max"); in_stream.close(); 53 54 55 56 57 58 59 //-- Berechnung einiger benoetigter Parameter -du = 0.4; dt = t_max / n_t_max; dv = pi / du / nmax; a_1 = 0.5 / sqr(sigma_1); a_2 = 0.5 / sqr(sigma_2); 60 61 62 63 64 65 66 67 //-- Zustand im Geschwindigkeitsraum for (n1=-nmax; n1<=nmax; n1++) for (n2=-nmax; n2<=nmax; n2++) { v1 = n1*dv; v2 = n2*dv; P_v_real[n1+nmax][n2+nmax] = N_1 * exp(-a_1*(sqr(v1-v_1)+sqr(v2))) 184 68 69 70 71 72 73 74 75 76 77 78 4 Statistische Physik + N_2 * exp(-a_2*(sqr(v1-v_2)+sqr(v2))); P_v_imag[n1+nmax][n2+nmax] = 0; P_u_imag[n1+nmax][n2+nmax] = 0; sum_real = sum_real + P_v_real[n1+nmax][n2+nmax]; } for (n1=-nmax; n1<=nmax; n1++) for (n2=-nmax; n2<=nmax; n2++) { P_v_real[n1+nmax][n2+nmax] = P_v_real[n1+nmax][n2+nmax] / sum_real; P_u_real[n1+nmax][n2+nmax] = P_v_real[n1+nmax][n2+nmax]; } 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 //-- Transformation in den u-Raum sum_real = 0; for (n1=0; n1<nges; n1++) for (n2=0; n2<nges; n2++) { u1 = (n1-nmax)*du; u2 = (n2-nmax)*du; P_u_real[n1][n2] = 0; P_u_imag[n1][n2] = 0; for (n3=0; n3<nges; n3++) for (n4=0; n4<nges; n4++) { v1 = (n3-nmax)*dv; v2 = (n4-nmax)*dv; arg = u1*v1 + u2*v2; P_u_real[n1][n2] = P_u_real[n1][n2] + P_v_real[n3][n4]*cos(arg); P_u_imag[n1][n2] = P_u_imag[n1][n2] + P_v_real[n3][n4]*sin(arg); } sum_real = sum_real + sqr(P_u_real[n1][n2]) + sqr(P_u_imag[n1][n2]); } for (n1=0; n1<nges; n1++) for (n2=0; n2<nges; n2++) { P_u_real[n1][n2] = P_u_real[n1][n2] / sum_real; P_u_imag[n1][n2] = P_u_imag[n1][n2] / sum_real; } 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 //-- Einlesen von sigma im u-Raum cout << "Einlesen!\n"; hit = 0; for (n1 = 0; n1<nmax_sorted; n1++) { cout << n1 << "\n"; for (n2 = 0; n2<nmax_sorted; n2++) { in_stream2 >> u1; in_stream2 >> u2; in_stream2 >> s; for (m1 = 0; m1<=nmax; m1++) { if (fabs(m1*du) > u1+eps) break; for (m2 = sqrt(sqr(u1/du)-sqr(m1)); m2<=nmax; m2++) if (fabs(sqrt(sqr(m1*du)+sqr(m2*du)) - u1) < eps) { for (m3 = 0; m3<=nmax; m3++) { if (fabs(m3*du) > u2+eps) break; 4.8 Thermalisierung in Gasen for (m4 = sqrt(sqr(u2/du)-sqr(m3)); m4<=nmax; m4++) if (fabs(sqrt(sqr(m3*du)+sqr(m4*du)) - u2) < eps) { sigma[m1+nmax][m2+nmax][m3+nmax][m4+nmax] = s; sigma[-m1+nmax][m2+nmax][m3+nmax][m4+nmax] = s; sigma[m1+nmax][-m2+nmax][m3+nmax][m4+nmax] = s; sigma[-m1+nmax][-m2+nmax][m3+nmax][m4+nmax] = s; sigma[m1+nmax][m2+nmax][-m3+nmax][m4+nmax] = s; sigma[-m1+nmax][m2+nmax][-m3+nmax][m4+nmax] = s; sigma[m1+nmax][-m2+nmax][-m3+nmax][m4+nmax] = s; sigma[-m1+nmax][-m2+nmax][-m3+nmax][m4+nmax] = s; sigma[m1+nmax][m2+nmax][m3+nmax][-m4+nmax] = s; sigma[-m1+nmax][m2+nmax][m3+nmax][-m4+nmax] = s; sigma[m1+nmax][-m2+nmax][m3+nmax][-m4+nmax] = s; sigma[-m1+nmax][-m2+nmax][m3+nmax][-m4+nmax] = s; sigma[m1+nmax][m2+nmax][-m3+nmax][-m4+nmax] = s; sigma[-m1+nmax][m2+nmax][-m3+nmax][-m4+nmax] = s; sigma[m1+nmax][-m2+nmax][-m3+nmax][-m4+nmax] = s; sigma[-m1+nmax][-m2+nmax][-m3+nmax][-m4+nmax] = s; hit++; break; } } break; } 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 } 152 153 154 155 156 185 } } cout << " hit = " << hit << " ( Sollwert: " << pow(nmax+1,4) << ")\n"; in_stream2.close(); 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 //-- Zeitschleife for (n_t = 1; n_t <= n_t_max; n_t++) { sum_real = 0; sum_imag = 0; for (n1=-nmax; n1<=nmax; n1++) for (n2=-nmax; n2<=nmax; n2++) { dP_dt_real[n1+nmax][n2+nmax] = 0; dP_dt_imag[n1+nmax][n2+nmax] = 0; for (m1=max(-nmax,n1-nmax); m1<=min(nmax,n1+nmax); m1++) for (m2=max(-nmax,n2-nmax); m2<=min(nmax,n2+nmax); m2++) { dP_dt_real[n1+nmax][n2+nmax] = dP_dt_real[n1+nmax][n2+nmax] + P_u_real[n1-m1+nmax][n2-m2+nmax] * P_u_real[m1+nmax][m2+nmax] * (sigma[n1-m1+nmax][n2-m2+nmax][n1+nmax][n2+nmax] - sigma[n1-m1+nmax][n2-m2+nmax][0+nmax][0+nmax]) - P_u_imag[n1-m1+nmax][n2-m2+nmax] * P_u_imag[m1+nmax][m2+nmax] * (sigma[n1-m1+nmax][n2-m2+nmax][n1+nmax][n2+nmax] - sigma[n1-m1+nmax][n2-m2+nmax][0+nmax][0+nmax]); dP_dt_imag[n1+nmax][n2+nmax] = dP_dt_imag[n1+nmax][n2+nmax] + P_u_real[n1-m1+nmax][n2-m2+nmax] * P_u_imag[m1+nmax][m2+nmax] * (sigma[n1-m1+nmax][n2-m2+nmax][n1+nmax][n2+nmax] - sigma[n1-m1+nmax][n2-m2+nmax][0+nmax][0+nmax]) + P_u_imag[n1-m1+nmax][n2-m2+nmax] * P_u_real[m1+nmax][m2+nmax] * (sigma[n1-m1+nmax][n2-m2+nmax][n1+nmax][n2+nmax] - sigma[n1-m1+nmax][n2-m2+nmax][0+nmax][0+nmax]); } 186 186 187 188 189 190 191 192 193 194 195 196 197 4 Statistische Physik sum_real = sum_real + sqr(dP_dt_real[n1+nmax][n2+nmax]); sum_imag = sum_imag + sqr(dP_dt_imag[n1+nmax][n2+nmax]); } cout << n_t << " " << sum_real << " " << sum_imag << "\n"; for (n1=-nmax; n1<=nmax; n1++) for (n2=-nmax; n2<=nmax; n2++) { P_u_real[n1+nmax][n2+nmax] = P_u_real[n1+nmax][n2+nmax] + dP_dt_real[n1+nmax][n2+nmax] * dt; P_u_imag[n1+nmax][n2+nmax] = P_u_imag[n1+nmax][n2+nmax] + dP_dt_imag[n1+nmax][n2+nmax] * dt; } 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 //-- Ruecktransformation in den v-Raum sum_real = 0; v_quer = 0; v2_quer = 0; min_real = 1.e6; max_imag = 0; for (n1=0; n1<nges; n1++) for (n2=0; n2<nges; n2++) { v1 = (n1-nmax)*dv; v2 = (n2-nmax)*dv; P_v_real[n1][n2] = 0; P_v_imag[n1][n2] = 0; for (n3=0; n3<nges; n3++) for (n4=0; n4<nges; n4++) { u1 = (n3-nmax)*du; u2 = (n4-nmax)*du; arg = -(u1*v1 + u2*v2); P_v_real[n1][n2] = P_v_real[n1][n2] + P_u_real[n3][n4]*cos(arg) - P_u_imag[n3][n4]*sin(arg); P_v_imag[n1][n2] = P_v_imag[n1][n2] + P_u_real[n3][n4]*sin(arg) + P_u_imag[n3][n4]*cos(arg); } sum_real = sum_real + P_v_real[n1][n2]; } //-- Projektion auf P_1d for (n1=0; n1<nges; n1++) { P_1d[n1] = 0; for (n2=0; n2<nges; n2++) { v1 = (n1-nmax)*dv; v2 = (n1-nmax)*dv; P_v_real[n1][n2] = P_v_real[n1][n2] / sum_real; P_v_imag[n1][n2] = P_v_imag[n1][n2] / sum_real; v_quer = v_quer + v1 * P_v_real[n1][n2]; v2_quer = v2_quer + (sqr(v1)+sqr(v2)) * P_v_real[n1][n2]; if (P_v_real[n1][n2]<min_real) min_real = P_v_real[n1][n2]; if (fabs(P_v_imag[n1][n2])>max_imag) max_imag = fabs(P_v_imag[n1][n2]); P_1d[n1] = P_1d[n1] + P_v_real[n1][n2]; } out_stream << n1 << " " << P_1d[n1] << "\n"; } cout << " v_quer = " << v_quer << "\n"; cout << " v2_quer = " << v2_quer << "\n"; cout << " min_real = " << min_real << "\n"; cout << " max_imag = " << max_imag << "\n"; } 4.8 Thermalisierung in Gasen 187 245 246 247 out_stream.close(); } Hierbei haben wir uns, was den Anfangszustand anbelangt, auf zwei additiv überlagerte Gaußfunktionen festgelegt, deren Mittelwerte und Standardabweichungen (v 1 und sigma 1 bzw. v 2 und sigma 2) aus der Eingabedatei eingelesen werden. Diese Situation deckt bereits zwei physikalisch interessante Problemstellungen ab: – ein Gasstrom trifft auf einen anderen bzw. auf ein ruhendes Gas, wobei die kinetische Energie in Wärme umgewandelt wird, – ein Teil des Gases hat eine andere Temperatur als der Rest und das Gemisch thermalisiert. Nachdem wir in Zeile 61–78 den Ausgangszustand im Geschwindigkeitsraum konstruiert haben, müssen wir diesen in den u-Raum transformieren. Da dies eine Fouriertransformation darstellt, bietet sich hierfür eigentlich eine FFT (siehe Anhang F) an. Man stellt jedoch fest, dass diese bei einem so kleinen Wert von nmax kein befriedigendes Ergebnis liefert, so dass wir eine zwar langsamere aber etwas genauere Alternative direkt, d.h. ohne Bibliotheksroutine, implementieren (Zeile 80–105). Besondere Aufmerksamkeit verlangt auch das Einlesen des vorher berechneten Wirkungsquerschnitts σ (Zeile 107–154). Da das Programm sigma.cpp diese Werte nach den Beträgen der beiden Argumente u12 und ∆u sortiert abspeichert, müssen diese nun nach dem Einlesen von therm.cpp wieder umsortiert werden. Die hier gewählte Methode ist sicherlich nicht effektiv, aber überschaubar und einigermaßen sicher: Im Prinzip werden für jeden eingelesenen Wert für alle Kombinationen von u12 und ∆u überprüft, ob deren Beträge mit den eingelesenen übereinstimmt. Etwas beschleunigt wird dieses Verfahren durch einige einfache Bedingungen, unter denen diese Suche abgebrochen werden kann. Zur Sicherheit wird zum Schluss (Zeile 155) überprüft, ob jedes Element des Arrays sigma gefüllt wurde. Nun folgt ab Zeile 158 die eigentliche Berechnung der Zeitentwicklung. An dieser Stelle müssen wir uns fragen, was wir ausgeben wollen. Auf der einen Seite sind dies Kontrollparameter, die wir auf den Bildschirm ausgeben, und anhand derer wir vorab kontrollieren können, ob das Ergebnis korrekt sein kann. Auf der anderen Seite sind das die Größen, an denen wir letztendlich interessiert sind und die uns einen guten Einblick in die ablaufende Physik geben. Zur Kontrolle eignen sich folgende unmittelbar einsichtige Gesetzmäßigkeiten Impulserhaltung: Die Mittelwerte von vx und vy müssen erhalten bleiben dabei ist insbesondere die x-Komponente interessant, da deren Mittelwert in der Ausgangsverteilung nicht notwendigerweise null ist. – Energieerhaltung: der Mittelwert von v 2 ist proportional zur Energie und ist deshalb ebenfalls eine Erhaltungsgröße. – 188 – – 4 Statistische Physik Im Geschwindigkeitsraum muss P reell sein. Im Geschwindigkeitsraum darf kein Wert von P negativ werden. Die letzten beiden Aussagen ergeben sich aus der Wahrscheinlichkeitsinterpretation von P (v). Im Programm überprüfen wir dies durch die Variablen v quer (Impulserhaltung), v2 quer (Energieerhaltung), den betragsmäßig größten Imaginärteil von P max imag, sowie den kleinsten Realteil von P min real. Schwieriger fällt die Entscheidung, was man letztlich in der Ergebnisdatei haben möchte. In unserem Fall wollen wir einfach die Verteilung P (vx ) zu verschiedenen Zeitpunkten ausgeben. Dazu projezieren wir die usprünglich zweidimensionale Verteilung P (v), die im Feld P v real gespeichert ist, auf P 1d. Auf der CD finden Sie ein kleines Hilfsprogramm extract therm.cpp, das aus der Ergebnisdatei von therm.cpp einzelne Geschwindigkeitsverteilungen P (vx ) extrahiert. P 0.06 (a) P 0.03 0.06 (b) P 0.03 0.0 0 5 vx (c) 0.03 0.0 -5 0.06 0.0 -5 0 5 vx -5 0 5 vx Abb. 4.13. Zeitentwicklung der Wahrscheinlichkeitsverteilung P (vx ) bei der Thermalisierung eines Gases, bei dem ein Teil nach rechs und ein Teil nach links strömt. Links dargestellt ist der Ausgangszustand, in der Mitte die Verteilung bei t = 50 und rechts die Geschwindigkeitsverteilung bei t = 500 In Abb. 4.13 sehen Sie zunächst ganz links die Ausgangsverteilung, die aus zwei getrennten Gaußpaketen besteht: Das linke davon ist der Teil des Gases, der nach links strömt, während umgekehrt das rechte Gaußpaket zu den Gasmolekülen gehört, die nach rechts strömen. Im mittleren Bild (in den gewählten Zeiteinheiten bei t = 50) sind beide Gaußpakete etwas verbreitert, ihre Position scheint sich aber nicht verändert zu haben. In der Abbildung ganz rechts finden Sie schließlich den Zustand bei t = 500. Die usprüngliche Separation in zwei Gaußpakete ist nun verschwunden, und wir haben einen Zustand ähnlich dem, der sich als Endzustand einstellen wird: eine Gaußverteilung um den Urspung, die jedoch breiter ist als jedes der Anfangspakete ganz links war, da kinetische Energie in Wärme umgewandelt wurde. Aus der Tatsache, dass die Verteilung auch bei t = 500 noch deutlich von einer Gaußfunktion abweicht, entnehmen wir, dass das thermische Gleichgewicht noch nicht erreicht wurde. Sie werden sich sicherlich fragen, warum wir den Endzustand, das sich einstellende thermische Gleichgewicht in Form einer Gauß-Verteilung, hier nicht zeigen: Der Grund ist, dass einer unserer Kontrollparameter, nämlich v2 quer, der proportial zur Energie Übungen 189 ist, eine langsame Drift zu größeren Werten hat. Mit anderen Worten: Die Energie in unserem System ist nicht genau erhalten, sondern steigt durch numerische Fehler langsam an. Da der Grund hierfür in der Tatsache liegt, dass der von uns berücksichtigte Teil des Geschwindigkeitsraumes nur sehr geringe Geschwindigkeiten berücksichtigt, kann dieses Problem etwas entschärft werden, indem man die Anfangsverteilungen im Geschwindigkeitsraum schärfer lokalisiert, also größere a 1 und a 2 wählt. Da dies jedoch den Nachteil hat, dass die Anfangsverteilung im u-Raum ausgedehnter wird und wir dann dort mit denselben Problem konfrontiert werden, verspricht das keine wirkliche Lösung dieses Problems. Wenn wir uns hingegen einer anderen Fragestellung zuwenden, die ebenfalls in die Kategorie Thermalisierung in Gasen fällt, kann sich die Situation zu unseren Gunsten verändern. Betrachten wir z.B. die Dynamik in einem ruhenden Gasgemisch, in dem eine chemische Reaktion stattfindet, z.B. 2 H2 + O2 → 2 H2 O , dann wird unsere Bewegungsgleichung zwar zunächst durch zwei neue Aspekte komplizierter: – statt einer Teilchensorte haben wir es nun mit drei verschiedenen Gasmolekülen zu tun, was zur Folge hat, dass wir drei Wahrscheinlichkeitsverteilungen P1 , P2 und P3 unterscheiden müssen; – wir müssen auch rotatorische und vibratorische Freiheitsgrade berücksichten; – die chemische Reaktion führt zu zusätzlichen Termen in der Mastergleichung. Im Gegenzug kommen wir jedoch in den Genuss einer nicht zu unterschätzenden Vereinfachung: aus Symmetriegründen müssen alle Wahrscheinlichkeitsverteilungen Pi (v) radialsymmetrisch sein, können also nur von |v| abhängen. Übungen 4.1 Brownsche Bewegung mit diskreten Schritten Im Text betrug die Schrittweite bei der Brownschen Bewegung zunächst ±1 und wurde dann durch Gauß-förmig verteilte Schrittweiten ersetzt, um die in Abb. 4.2 zu erkennende ‘Kammstruktur’ zu eliminieren. Alternativ können Sie Schritte mit den Schrittweiten ±1 (jeweils mit Wahrscheinlichkeit p < 1/2) und null (mit Wahrscheinlichkeit 1−2p) wählen. Das bedeutet, dass dem Teilchen neben der Möglichkeit, nach links und nach rechts zu springen, auch die Möglichkeit eingeräumt wird, bei der aktuellen Position zu verharren. Untersuchen Sie die sich ergebende Wahrscheinlichkeitsverteilung für 190 4 Statistische Physik – sehr kleine p, – p = 1/3, – Werte von p, die nur geringfügig kleiner als 1/2 sind. 4.2 Brownsche Bewegung mit Reibungsterm In Abschn. 4.6 haben wir die Annahme gemacht, dass bei jedem Stoß der Impulsübertrag proportional zur Relativgeschwindigkeit ist. Durch diese Annahme haben wir ein realistisches Verhalten inklusive dem Erreichen einer thermischen Gleichgewichtsverteilung im Geschwindigkeitsraum erreicht. Alternativ hätten wir die unsichtbaren Teilchen auch als viskose Flüssigkeit auffassen können, was zu einem Reibungsterm im deterministischen Teil der Bewegungsgleichung führt: dv = −µv . (4.77) dt det Gehen Sie vom Programm rw4.cpp aus, implementieren Sie die nötigen Änderungen und untersuchen Sie das so beschriebene System. 4.3 Random-Walk Eine weitere Annahme in Abschn. 4.6 war, dass die Einzelschritte beim Random-Walk statistisch unabhängig voneinander erfolgen. Diese Bedingung können Sie jedoch fallen lassen und stattdessen annehmen, dass die Wahrscheinlichkeit nach rechts zu wandern, davon abhängt, ob der vorangegangene Schritt ebenfalls nach rechts erfolgte oder nicht. Gehen Sie dazu der Einfachheit halber von rw1.cpp aus und setzen für die Wahrscheinlichkeit eines Sprunges nach links (1/2) + α wenn Sprung n nach rechts erfolgte Pn+1,links = (4.78) (1/2) − α wenn Sprung n nach links erfolgte. Untersuchen Sie insbesondere Fälle mit positivem und negativem α. 4.4 Thermisches Gleichgewicht In Abschn. 4.7 haben wir gesehen, dass sich für t → ∞ ein Gleichgewicht in der Besetzung der beiden Potentialmulden einstellt. Diese Gleichgewichtsverteilung hängt von dem Potentialunterschied der beiden Minima ab. Schreiben Sie ein Programm, das diese Potentialdifferenz in einer Schleife variiert und jeweils die Besetzung Plinks und Prechts im Gleichgewichtszustand ermittelt. Übungen 191 4.5 Thermisches Hüpfen In Abschn. 4.7 haben wir das thermisches Hüpfen zwischen zwei Potentialminima betrachtet. Das dort vorgestellte Programm (je nach Fragestellung th1.cpp oder th2.cpp) lässt sich aber leicht auf Potentiale erweitern, die mehr Minima aufweisen, z.B: V (x) = −V0 cos(αx) . (4.79) Beachten Sie, dass nun die Unterscheidung links ⇔ rechts zur Positionsbestimmung des Teilchens nicht mehr ausreicht. Untersuchen Sie, wie das Teilchen (ausgehend vom Minimum bei x = 0) zuerst in die beiden benachbarten Minima bei x = ±2π/α wandert, dann zu den übernächsten Minima bei x = ±4π/α und so weiter. Beschreiben Sie den Vorgang durch einen Random Walk mit endlicher, fixer Sprungweite, wie wir es bei rw1.cpp getan haben. 4.6 Boltzmann-Verteilung Beim thermischen Hüpfen in Abschn. 4.7 haben wir die sich für t → ∞ einstellende Gleichgewichtsverteilung bestimmt (Abb. 4.10). Diese Verteilung sollte durch die Formel P (x) = 1 exp(−mβV (x)) N (4.80) (Boltzmann-Verteilung) beschrieben werden. N ist dabei ein Normierungsfaktor, der Parameter β = 1/kT (k ist die Boltzamnn-Konstante und T die Temperatur) kann aus der Tatsache bestimmt werden, dass im thermischen Gleichgewicht die mittlere Energie in jedem Freiheitsgrad durch kT /2 gegeben ist: 1 1 mv 2 = kT . (4.81) 2 2 Fügen Sie in das Programm th1.cpp die Zeilen ein, um v 2 zu berechnen und auszugeben. Bestimmen Sie dann mβ und damit die Boltzmann-Verteilung (4.80). Wenn Sie dieses Ergebnis zusammen mit der Endverteilung P (x, tend ) in einem Diagramm darstellen, sollten die beiden Kurven nicht mehr voneinander zu trennen sein. 5 Quantenmechanik In diesem Kapitel wollen wir in eine Welt eintauchen, die sich erst im letzten Jahrhundert aufgetan hat: der Welt der Quanten. Diese Welt blieb deshalb so lange unentdeckt, weil sie sich meistens erst erschließt, wenn man extrem kleine Objekte betrachtet bzw. es mit extrem niedrigen Energien zu tun hat. Diese Welt des Allerkleinsten scheint sich oft dem gesunden Menschenverstand zu entziehen – was darauf zurückzuführen ist, dass letzterer aus unserer Erfahrungswelt mit normalen Dimensionen entstammt. Trotz seiner scheinbaren Widersprüche existiert ein – einigermaßen – sauberes mathematisches Modell, das in der Lage ist, alle Experimente mit ihren scheinbaren Widersprüchen zu beschreiben: die Quantenmechanik. 5.1 Die mathematische Struktur der Quantenmechanik Die mathematische Struktur der Quantenmechanik könnte bereits ein Buch für sich füllen – aus diesem Grund beschränken wir uns an dieser Stelle auf das Nötigste. Ein quantenmechanischer Zustand wird mathematisch durch einen sogenannten Zustandsvektor |ψ repräsentiert. Hierbei haben wir gleich die in der Quantenmechanik übliche Schreibweise mit einem senkrechten Strich auf der einen Seite und einem nach außen gerichteten Winkel auf der anderen Seite eingeführt. Der zu diesen Zustandsvektoren zugehörige Vektorraum heißt Hilbertraum H und kann je nach Problem sehr unterschiedlich aussehen. Seine mathematische Struktur kann zum Beispiel diejenige des C N sein, also des Raumes der Vektoren mit N komplexen Zahlen; sie kann aber auch äquivalent zu der der normierbaren komplexen Funktionen über den reellen Zahlen sein. 5.2 Operationen im Hilbertraum In diesem Abschnitt wollen wir in Erinnerung rufen, welche Operationen mit Zustandsvektoren definiert sind. Zum einen kann man zwei beliebige Vektoren des Hilbertraums addieren und diese Summe ist dann wieder ein Vektor des Hilbertraums. Diese Addition ist kommutativ, es gilt also: 194 5 Quantenmechanik |ψ1 + |ψ2 = |ψ2 + |ψ1 . (5.1) Ebenso kann man jeden Vektor des Hilbertraumes mit komplexen Zahlen multiplizieren, und das Ergebnis dieser Multiplikation liegt wieder im Hilbertraum. α|ψ ∈ H . (5.2) Diese Multiplikation ist ebenfalls kommutativ und darüber hinaus assoziativ, d.h. bei aufeinanderfolgenden Multiplikationen ist die Reihenfolge irrelevant und wir können auch alternativ zuerst die beiden Multiplikatoren miteinander multiplizieren und anschließend das Produkt dieses Ergebnisses mit dem Zustandsvektor bilden: αβ|ψ = βα|ψ = (αβ)|ψ . (5.3) Wir können aber auch zwei Vektoren miteinander multiplizieren und erhalten auf diese Weise eine komplexe Zahl, das sogenannte Skalarprodukt. An dieser Stelle möchten wir die in Abschn. 5.1 eingeführte Schreibweise um die für den dazugehörigen hermitesch transponierten Vektor erweitern: ψ|. Hiermit schreibt sich das Skalarprodukt zweier Vektoren besonders einfach: ψ1 |ψ2 ∈ C . (5.4) Mit der Einführung des Skalarproduktes können wir zwei Begriffe aus der Linearen Algebra übertragen: – Die Länge l (oder auch der Betrag) eines Vektors |ψ definiert sich durch l2 = ψ|ψ . (5.5) – Zwei Vektoren, deren Längen von null verschieden sind und deren Skalarprodukt null ist, bezeichnen wir als orthogonal. An dieser Stelle nun können wir eine Einschränkung bezüglich Zustandsvektoren im Hilbertraum machen: Es ist nämlich nicht jeder Vektor des Hilbertraums ein erlaubter Zustandsvektor – vielmehr sind ausschließlich normierte Vektoren als Zustandsvektoren erlaubt. Abschließend wollen wir nun noch lineare Abbildungen innerhalb des Hilbertraumes einführen. Eine solche Abbildung lässt sich durch einen (linearen) Operator  beschreiben, der auf einen beliebigen Vektor des Hilbertraums angewendet einen neuen Vektor ebenfalls aus dem Hilbertraum ergibt: Â|ψ ∈ H . (5.6) Der Operatorcharakter wird durch das kleine Hütchen über dem A explizit zum Ausdruck gebracht. Linear bedeutet in diesem Fall, dass für beliebige λ Â(λ|ψ) = λ(Â|ψ) Â(|ψ + |χ) = Â|ψ + Â|χ gilt. (5.7) (5.8) 5.3 Eigenzustände und ihre Verwendung als Koordinatensysteme 195 Für das Folgende müssen wir außerdem festhalten, dass die Operatoren im Hilbertraum nicht unbedingt kommutativ sind, d.h. dass zwischen dem Produkt ÂB̂ und B̂  unterschieden werden muss, wie man es z.B. auch von Matrizen kennt. Diese Nichtvertauschbarkeit von Operatoren spielt in der Quantenmechanik eine große Rolle, und weil derartige Ausdrücke sehr oft vorkommen, bekommt die Differenz [Â, B̂] = ÂB̂ − B̂  (5.9) ein eigenes Symbol – eben die eckigen Klammern auf der linken Seite der Gleichung – und wird Kommutator genannt. Bis zu diesem Punkt haben wir gegenüber der klassischen Physik noch nichts Neues eingeführt, da wir noch keinerlei Aussagen über diese Operatoren getroffen haben. Der entscheidende Punkt und der endgültige Schritt zur Quantenmechanik wird nun durch ein Postulat (also eine prinzipiell nicht beweisbare Behauptung) gemacht: Der Kommutator von Operatoren, die in der klassischen Dynamik kanonisch konjugierte Größen sind, ist ih̄, also z.B. [x̂, p̂x ] = ih̄ . (5.10) Der entsprechende Kommutator bei nicht kanonisch konjugierten Größen hingegen soll null sein: [x̂, p̂y ] = 0 . (5.11) Abschließend wollen wir noch in Erinnerung rufen, was unter der Funktion eines Operators, z.B. unter sin(d/dx) zu verstehen ist. Die Festlegung ist die, dass solche Funktionen über ihre Potenzreihen definiert sind. In unserem Beispiel des Sinus benötigen wir also zunächst die Potenzreihe der Sinusfunktion ∞ 1 (−1)n z n . (5.12) sin(z) = n! n=0 Wenn wir nun für z den Operator d/dx einsetzen, erhalten wir den gesuchten Sinus der ersten Ableitung nach x. Beachten Sie bitte, dass der so gewonnene Operator beliebig hohe Ableitungen nach x enthält! 5.3 Eigenzustände und ihre Verwendung als Koordinatensysteme Operatoren und Zustände sind zunächst sehr abstrakte Begriffe – zumindest wenn wir konkrete Probleme numerisch lösen wollen, müssen wir diese in eine in Zahlen gegossene Form bringen. Hierzu wollen wir zunächst auf spezielle Zustände im Hilbertraum eingehen, nämlich die Eigenzustände zu einem vorgegebenen Operator Â. Dabei handelt es sich um Zustände, die sich bei Anwendung dieses Operators bis auf einen Faktor reproduzieren, also: 196 5 Quantenmechanik Â|λ = λ|λ . (5.13) Der Faktor λ wird in diesem Zusammenhang Eigenwert und (5.13) Eigenwertgleichung genannt, und wir haben die in der Quantenmechanik übliche Schreibweise benutzt, in der der Eigenzustand |λ durch seinen Eigenwert charakterisiert wird. Trotz der ähnlichen Schreibweise ist genau zwischen der Zahl λ und dem Zustand |λ zu unterscheiden! Die Situation ist übrigens vollkommen analog zu Eigenvektoren und Eigenwerten zu einer Matrix M , falls Ihnen dies aus der Linearen Algebra vertraut ist. Zunächst ohne Beweis ein paar Fakten über Eigenzustände und Eigenwerte, die wir kennen müssen, um das folgende nachvollziehen zu können. Wenn Sie an den Beweisen dieser Behauptungen interessiert sind, finden Sie diese z.B. in [7] oder [28]. Wenn Sie einen Eigenzustand |λ zu einem Operator gefunden haben, erhalten Sie durch Multiplikation mit einer beliebigen (von null verschiedenen) Zahl α weitere Eigenzustände α|λ. – Hat man zu einem Eigenwert zwei Eigenvektoren, so ist auch die Summe dieser beiden wieder ein Eigenvektor zum selben Eigenwert. – für selbstadjungierte Operatoren stehen Eigenzustände zu verschiedenen Eigenwerte stehen immer senkrecht aufeinander, d.h. ihr Produkt ist null: – λ1 |λ2 = 0 . – (5.14) Die Eigenzustände einer großen Klasse von Operatoren, nämlich der Hermiteschen Operatoren, bilden ein vollständiges System, d.h. jeder beliebige Zustand lässt sich als Linearkombination dieser Eigenzustände schreiben: ψλ |λ . (5.15) |ψ = λ – Die Operatoren zu allen physikalischen Größen sind Hermitesch. An dieser Stelle wollen wir noch kurz erklären, was unter Entartung zu verstehen ist. Die ersten beiden der oben angeführten Punkte besagen, dass die Eigenvektoren zu einem Eigenwert unter Hinzunahme des Nullvektors einen (mindestens eindimensionalen) Untervektorraum des Hilbertraums bilden. Falls dieser Raum nun eine Dimension größer als eins hat, spricht man von Entartung. Da die Berücksichtigung von Entartung in den meisten Fällen nur einen erhöhten Schreibaufwand darstellt, werden wir dieses Phänomen, wenn nicht explizit erforderlich, vernachlässigen. Wieviele Eigenvektoren (und zugehörige Eigenwerte) gibt es nun zu einem vorgegebenen Operator? Das hängt ganz vom Operator ab – es gibt Operatoren, zu denen überhaupt keine Eigenvektoren existieren, es gibt aber auch Operatoren, die sozusagen die maximal mögliche Zahl von Eigenvektoren haben, deren Eigenvektoren nämlich eine Basis bezüglich des Hilbertraums 5.4 Orts- und Impulsdarstellung 197 darstellen. Insbesondere gehören alle Hermiteschen Operatoren in diese Kategorie. Auf diese Weise liefern uns Hermitesche Operatoren eine Basis, in die wir alle Vektoren des Hilbertraums entwickeln können. Gegeben sei ein Hermitescher Operator  mit Eigenwerten a1 , a2 , . . . Da die zugehörigen Eigenvektoren eine Basis bilden ist |ψ = ai |ψ|ai (5.16) i = ψi |ai . (5.17) i Hierbei haben wir die Komponenten ψi durch ψi = ai |ψ (5.18) definiert. Wir können jedoch nicht nur Zustände nach einer Basis entwickeln, sondern auch Operatoren: ai |Ô|aj |ai aj | (5.19) Ô = ij = Oij |ai aj | . (5.20) ij Zusammenfassend sind wir nun in der Lage, sowohl Zustände als auch Operatoren des Hilbertraums durch Zahlenwerte, nämlich durch Koordinaten bzgl. einer Basis, darzustellen. 5.4 Orts- und Impulsdarstellung Ein wichtiger Spezialfall des letzten Abschnittes ist die Darstellung bzgl. der Eigenzustände des Orts- bzw. des Impulsoperators. Im Vorgriff auf den nächsten Abschnitt stellen wir hier fest, dass die Eigenwerte der beiden Operatoren die Orts- bzw. die Impulsvektoren sind und damit ein dreidimensionales Kontinuum bilden. Unter Anwendung des vorhergehenden Abschnitts können wir einen beliebigen Zustand also nach Ortseigenzuständen (5.21) |ψ = d3 x x|ψ|x = d3 x ψ(x) |x (5.22) oder Impulseigenzuständen |ψ = = d3 p p|ψ|p (5.23) d3 p ψ(p) |p (5.24) 198 5 Quantenmechanik entwickeln, wobei wir jedoch die Summation über die Eigenzustände durch entsprechende Integrale ersetzen müssen, da diese ein Kontinuum bilden. Wie sieht nun der Ortsoperator in der Ortsdarstellung aus? Dazu entwickeln wir x1 |x|x2 = x2 x1 |x2 = x2 δ(x1 − x2 ) . Der Ortsoperator selber ist also 3 x̂ = d x1 d3 x2 x2 δ(x1 − x2 )|x1 x2 | = d3 x x|xx| . (5.25) (5.26) (5.27) (5.28) Wenn wir dies nun auf einen ebenfalls in Ortsdarstellung gegebenen Zustand ψ(x) anwenden, erhalten wir x|x̂|ψ = d3 x x x|x x |ψ (5.29) = x ψ(x) . (5.30) Der Ortsoperator x̂ wird also lediglich zu einem Faktor x. Entsprechendes gilt natürlich für den Impulsoperator in Impulsdarstellung. Wie aber sieht der Impulsoperator in Ortsdarstellung aus? Das einzige, was wir über die Beziehung zwischen Orts- und Impulsoperator wissen, ist der Kommutator (5.10). Wenn dieser erfüllt sein soll, muss der Impulsoperator durch −ih̄d/dx gegeben sein, denn für jede beliebige Funktion ψ(x) gilt: d d + ih̄ x ψ(x) = ih̄ψ(x) . −xih̄ (5.31) dx dx Entsprechend ist der Ortsoperator in Impulsdarstellung ih̄d/dp. Aus der Darstellung des Impulsoperators in Ortsdarstellung und umgekehrt kann man auch entnehmen, dass die Transformation von der einen in die andere Darstellung eine Fouriertransformation (siehe auch Anhang F) sein muss: 3 i 1 d3 x ψ(x) exp xp (5.32) ψ(p) = √ h̄ 2πh̄ und entsprechend ψ(x) = √ 1 2πh̄ 3 i d3 p ψ(p) exp − xp . h̄ (5.33) 5.6 Schrödingergleichung 199 5.5 Die Kopenhagener Interpretation der Quantenmechanik Bis zu diesem Punkt haben wir jede Menge mathematischer Begriffe eingeführt (Zustandsvektor, Skalarprodukt, Operator, Kommutator, . . . ), aber noch keinen physikalischen Bezug hergestellt. Letztendlich aber muss die Quantenmechanik wie jede andere physikalische Theorie den Ausgang von Experimenten vorhersagen. Diesen Zusammenhang zwischen dem mathematischen Formalismus und dem Ergebnis von Messvorgängen liefert die sogenannte Kopenhagener Interpretation, die sich in drei Postulate zusammenfassen lässt. Das erste Postulat macht eine Aussage darüber, welche Ergebnisse die Messung haben kann. Wird an einem quantenmechanischen System die Messung einer Observablen O vorgenommen, so wird (Messfehler außer Acht gelassen) immer ein Eigenwert o des zugehörigen Operators Ô gemessen. Das zweite Postulat konkretisiert nun das Gesagte, indem es eine Aussage über die Wahrscheinlichkeit für das Eintreffen der möglichen Ergebnisse macht: Die Wahrscheinlichkeit P (o) für einen konkreten Eigenwert o ist gleich dem Absolutbetrag der Projektion des Zustandsvektors auf den entsprechenden Eigenvektor: P (o) = |o|ψ|2 . (5.34) An dieser Stelle führen wir also eine Zufallskomponente ein – auch wenn wir den Zustand des Systems genau kennen, können wir den Ausgang eines konkreten Experiments nicht vorhersagen – es sei denn, dieser Zustand ist ein Eigenzustand der Messobservablen. Dies hat eine andere Qualität als der statistische Charakter aus der klassischen Physik (z.B. bei der Brownschen Bewegung), der daher rührt, dass wir das System eben nicht vollständig kennen. Das letzte Postulat schließlich legt fest, in welchem Zustand sich das System nach der Messung befindet, nämlich in der Projektion des ursprünglichen Zustands |ψ auf den Eigenraum zum Eigenwert o. Mittels dieser Postulate lässt sich der Mittelwert einer Observablen berechnen: P (on )on (5.35) O = n = ψ|on on on |ψ (5.36) n = ψ|Ô|ψ . (5.37) 5.6 Schrödingergleichung Wodurch wird nun die Dynamik in der Quantenmechanik festgelegt? Die Grundgleichung – also das, was in der klassischen Mechanik die Newtonschen 200 5 Quantenmechanik Axiome und in der Elektrodynamik die Maxwellschen Gleichungen sind – ist hier die Schrödingergleichung, die festlegt, wie sich ein Zustand mit der Zeit verändert: d (5.38) ih̄ |ψ(t) = Ĥ|ψ(t) . dt Hierbei ist Ĥ der Hamiltonoperator, das quantenmechanische Pendant zur Hamiltonfunktion, die in der klassischen Mechanik – im skleronomen Fall – die Energie eines Systems als Funktion aller Orte und Impuls angibt. Dabei haben wir uns auf das sogenannte Schrödingerbild beschränkt, in dem die Zustandsvektoren zeitabhängig sind und die Operatoren nur eine explizite Zeitabhängigkeit haben können. Auf die umgekehrte Sichtweise des Heisenbergbildes werden wir in diesem Buch nicht eingehen. 5.7 Bestimmung des Hamilton-Operators Wie wir aus (5.38) entnehmen, müssen wir zunächst den Hamiltonoperator eines Problems bestimmen, ehe wir uns daran machen können, seine Bewegungsgleichung zu lösen. Dieser Schritt entspricht dem Bestimmen der Bewegungsgleichungen im Fall der klassischen Physik. Für Probleme, die eine klassische Entsprechung haben, also zum Beispiel die Bewegung eines Teilchens in einem Potential, kann der Hamiltonoperator der Quantenmechanik aus der Hamiltonfunktion der klassischen Physik bestimmt werden – daher auch die Namensgebung Hamiltonoperator. Dies geschieht einfach dadurch, dass man die klassischen Größen durch die entsprechenden Operatoren ersetzt – also die Ortsvariable x durch den Ortsoperator x̂, die Impulsvariable p durch den Impulsoperator p̂ und so weiter. 5.8 Das freie Teilchen Bevor wir uns komplizierteren Problemen zuwenden, wollen wir das in den beiden vorangegangenen Abschnitten Zusammengefasste festigen, indem wir es auf ein sehr einfaches Problem anwenden. Das einfachste Problem der klassischen Physik ist ein freies Teilchen ohne äußere Kräfte, das sich in einem Inertialsystem bekanntlich gleichförmig bewegt. Seine Hamiltonfunktion ist durch 1 2 1 2 px + p2y + p2z p = (5.39) H= 2m 2m gegeben. Daraus ergibt sich der Hamiltonoperator des entsprechenden quantenmechanischen Problems: Ĥ = 1 2 p̂x + p̂2y + p̂2z , 2m (5.40) 5.8 Das freie Teilchen 201 das heißt, sowohl H als auch die Impulskomponenten px , py und pz haben nun Operatorcharakter. Das gleiche gilt für die Komponenten des Ortsvektors x, y und z, die in (5.40) aber nicht auftreten. Da der Hamiltonoperator eine Funktion des Impulsoperators ist, ist dieses Problem am Einfachsten in der Impulsdarstellung zu lösen. Dazu entwickeln wir die Schrödingergleichung nach den Impulseigenzuständen |px , py , pz = |p (die analytische Betrachtung können wir dreidimensional durchführen, während wir uns bei der nachfolgenden numerischen Behandlung auf eine Dimension beschränken müssen): d 1 d3 p p|ψ(t)|p = d3 p p2 p|ψ(t)|p . ih̄ (5.41) dt 2m Da die Impulseigenzustände |p linear unabhängig sind, kann dies nur erfüllt sein, wenn die beiden Integranden gleich sind: ih̄ d d p|ψ(t) = ih̄ ψ(p, t) dt dt 1 2 p ψ(p, t) . = 2m (5.42) (5.43) Diese Differentialgleichung lässt sich direkt lösen und wir erhalten als Lösung: 1 2 ψ(p, t) = ψ(p, 0) exp −i p t . (5.44) 2mh̄ Nachdem wir die Lösung im Impulsraum haben, können wir diese mittels (5.33) in den Ortsraum transformieren: ψ(x, t) = 1 √ 2πh̄ 3 i d p ψ(p, t) exp − xp h̄ 3 . (5.45) Schreiten wir nun zur numerischen Implementation des Problems und beginnen mit der vielleicht naheliegendsten Lösung: Die Schrödingergleichung ist im Ortsraum eine partielle Differentialgleichung, die Ableitungen erster Ordnung nach der Zeit enthält: ih̄ ∂ h̄2 ∂ 2 ψ(x) = − ψ(x) . ∂t 2m ∂x2 (5.46) Diese Differentialgleichung können wir direkt einer entsprechenden Bibliotheksroutine übergeben. Das Ergebnis könnte dann in ungefähr so aussehen (im Folgenden setzen wir h̄ = m = 1): 1 2 3 4 5 /************************************************************************** * Name: frei1.cpp * * Zweck: Simuliert ein quantenmechanisches freies Teilchen * * Gleichung: Schroedingergleichung ohne Potential * * verwendete Bibiliothek: GSL * 202 6 5 Quantenmechanik **************************************************************************/ 7 8 9 10 11 12 13 #include #include #include #include #include #include <iostream> <fstream> <gsl/gsl_errno.h> <gsl/gsl_odeiv.h> <math.h> "tools.h" 14 15 using namespace std; 16 17 18 //-- Zahl der Stuetzstellen const int nmax = 8192; 19 20 21 //-- globale Variablen double dx; 22 23 //------------------------------------------------------------------------- 24 25 int f(double t, const double psi[], double dpsi_dt[], void *params) 26 27 28 29 { int n; double norm, norm1, norm2; 30 31 32 33 //-- Realteil for (n=1; n<nmax-1; n++) dpsi_dt[n] = -(psi[nmax+n+1]+psi[nmax+n-1]-2*psi[nmax+n]) / sqr(dx); 34 35 36 dpsi_dt[0] = -(psi[nmax+1]+psi[2*nmax-1]-2*psi[nmax]) / sqr(dx); dpsi_dt[nmax-1] = -(psi[nmax]+psi[2*nmax-2]-2*psi[2*nmax-1])/ sqr(dx); 37 38 39 40 //-- Imaginaerteil for (n=nmax+1; n<2*nmax-1; n++) dpsi_dt[n] = +(psi[n+1-nmax]+psi[n-1-nmax]-2*psi[n-nmax]) / sqr(dx); 41 42 43 dpsi_dt[nmax] = +(psi[1]+psi[nmax-1]-2*psi[0]) / sqr(dx); dpsi_dt[2*nmax-1] = +(psi[0]+psi[nmax-2]-2*psi[nmax-1]) / sqr(dx); 44 45 46 return GSL_SUCCESS; } 47 48 //-------------------------------------------------------------------- 49 50 51 int jac(double t, const double x[], double *dfdx, double dxdt[], void *params) 52 53 54 55 { return GSL_SUCCESS; } 56 57 //------------------------------------------------------------------------- 58 59 main( int argc, char *argv[] ) 60 61 62 63 64 { //-- Definition der Variablen int n, n1, n_start, n_out, max_fct, status; double psi[2*nmax], dpsi_dt[2*nmax], t, t1, t2, tend, dt, rtol, atol; 5.8 Das freie Teilchen 65 66 67 68 203 double alpha, p_0, x, h, hmax, daux, sum; double mu = 10; ifstream in_stream; ofstream out_stream; 69 70 71 72 73 74 75 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: frei1 infile outfile\n"; exit(1); } 76 77 78 79 80 81 82 83 84 85 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von frei1.cpp\n"; inout(tend,"tend"); inout(dx,"dx"); inout(n_out,"n_out"); inout(alpha,"alpha"); inout(p_0,"p_0"); inout(rtol,"rtol"); inout(atol,"atol"); in_stream.close(); 86 87 88 89 //-- Berechnung einiger benoetigter Parameter -dt = tend / n_out; h = 1.e-6; 90 91 92 93 94 95 96 97 98 99 100 //-- Anfangsbedingungen t = 0; max_fct = 1000000; for (n=0; n<nmax; n++) { x = (n-nmax/2) * dx; psi[n] = exp(-sqr(x/alpha)/2); sum = sum + sqr(psi[n]); } sum = 1 / sqrt(sum); 101 102 103 104 105 106 107 for (n=0; n<nmax; n++) { x = (n-nmax/2) * dx; psi[n+nmax] = -psi[n] * sum * sin(p_0*x); psi[n] = psi[n] * sum * cos(p_0*x); } // Imaginaerteil // Realteil 108 109 110 111 out_stream << t << "\n"; for (n=0; n<nmax; n++) out_stream << psi[n] << " " << psi[n+nmax] << "\n"; 112 113 114 115 116 117 const gsl_odeiv_step_type *T = gsl_odeiv_step_rkf45; gsl_odeiv_step *s = gsl_odeiv_step_alloc(T, 2*nmax); gsl_odeiv_control *c = gsl_odeiv_control_y_new(atol, rtol); gsl_odeiv_evolve *e = gsl_odeiv_evolve_alloc(2*nmax); gsl_odeiv_system sys = {f, jac, 2*nmax, &mu}; 118 119 120 121 122 123 //-- Zeitschleife -t = 0; for (n=1; n<=n_out; n++) { t1 = n * dt; 204 124 125 126 127 128 129 130 131 132 133 5 Quantenmechanik cout << n << " " << t << "\n"; while (t<t1) { status = gsl_odeiv_evolve_apply(e, c, s, &sys, &t, t1, &h, psi); if (status != GSL_SUCCESS) break; } out_stream << t << "\n"; for (n1=0; n1<nmax; n1++) out_stream << psi[n1] << " " << psi[n1+nmax] << "\n"; } 134 135 136 137 gsl_odeiv_evolve_free(e); gsl_odeiv_control_free(c); gsl_odeiv_step_free(s); 138 139 140 out_stream.close(); } Besonderes Augenmerk verdient hier die Berechnung der zweiten Ableitung der Wellenfunktion ψ nach der sogenannten Dreipunktformel in Zeilen 33 und 40: d2 1 ψ(xn ) ≈ (ψ(xn+1 ) − 2ψ(xn ) + ψ(xn−1 )) . d2 x (∆x)2 (5.47) Am Rand des abgedeckten Intervalls, also bei x0 und xnmax−1 , lässt sich diese Formel nicht direkt anwenden, da ψ(x−1 ) und ψ(xnmax ) nicht bekannt sind. Für’s erste haben wir hier periodische Randbedingungen angenommen, bei denen diese beiden fehlenden Werte durch ψ(xnmax−1 ) und ψ(x0 ) ersetzt werden. Wir werden diesen Punkt noch eingehender beleuchten und verschiedene Alternativen zu dieser Wahl erläutern. Ein zweiter Punkt, der der Erläuterung bedarf, ist die Konstruktion des Ausgangszustandes in Zeile 91 bis 107. Wir könnten diesen Ausgangszustand zwar auch direkt aus der – entsprechend modifizierten – Eingabedatei einlesen; wir werden uns aber an dieser Stelle auf eine kleine Klasse von Ausgangszuständen beschränken, die durch lediglich zwei Parameter festgelegt werden. Dabei handelt es sich um Gauß-förmige Wellenpakete, die sich zum Zeitpunkt t = 0 am Ursprung befinden und sich mit einem mittleren Impuls p̄ bewegen. Außer diesem mittleren Impuls braucht man nur noch die anfängliche Breite α des Wellenpakets im Ortsraum. Zunächst aber wollen wir uns die Frage stellen, ob unser Programm vom Standpunkt der Performance und der Stabilität eine gute Implementation des gegebenen Problems darstellt. Die Antwort auf diese Frage muss – zumindest in der gegenwärtigen Form – eindeutig nein lauten; wir wollen jedoch hier schon anmerken, dass wir auf das Programm frei1.cpp noch zurückgreifen werden. Der Grund dafür, dass frei1.cpp in puncto Performance nicht zufriedenstellen kann, ist die Tatsache, dass wir keinerlei Gebrauch davon gemacht haben, dass uns im Impulsraum die analytische Lösung bekannt ist (5.44), weswegen wir eine hohe Zahl von Teilschritten ∆t in Kauf nehmen, in die die Bibliotheksroutine zur Lösung der Differentialgleichung die 5.8 Das freie Teilchen 205 Zeit unterteilt. Ein auf der analytischen Lösung im Impulsraum aufbauendes Programm könnte folgendermaßen vorgehen: – Transformation des Ausgangszustandes im Ortsraum ψ(x, t = 0) in den Impulsraum ψ(p, t = 0) durch Fouriertransformation. – Berechnung des Zustandes ψ(p, tn ) zu den Zeiten tn , zu denen die Lösung erwünscht ist mittels (5.44). – Rücktransformation dieser Zustände in den Ortsraum durch inverse Fouriertransformation. Diese Vorgehensweise ist in dem folgenden Programm realisiert: 1 2 3 4 5 6 /************************************************************************** * Name: frei2.cpp * * Zweck: Simuliert ein quantenmechanisches freies Teilchen * * Gleichung: Schroedingergleichung ohne Potential * * verwendete Bibiliothek: GSL * **************************************************************************/ 7 8 9 10 11 12 #include #include #include #include #include <iostream> <fstream> <math.h> <gsl/gsl_fft_complex.h> "tools.h" 13 14 using namespace std; 15 16 17 //-- Zahl der Stuetzstellen const int nmax = 8192; 18 19 //------------------------------------------------------------------------- 20 21 main( int argc, char *argv[] ) 22 23 24 25 26 27 28 29 30 31 32 { //-- Definition der Variablen int n, n1, n_start, n_out, max_fct, status; double psi[2*nmax], psi_p[2*nmax], t, t1, t2, tend, dt, alpha; double x, dx, p, dp, p_0, arg, sum; double mu = 10; ifstream in_stream; ofstream out_stream; gsl_fft_complex_wavetable * wavetable; gsl_fft_complex_workspace * workspace; 33 34 35 36 37 38 39 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: frei2 infile outfile\n"; exit(1); } 40 41 42 43 44 45 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von frei2.cpp\n"; inout(tend,"tend"); inout(dx,"dx"); 206 46 47 48 5 Quantenmechanik inout(n_out,"n_out"); inout(p_0,"p_0"); in_stream.close(); inout(alpha,"alpha"); 49 50 51 52 //-- Berechnung einiger benoetigter Parameter -dt = tend / n_out; dp = 2 * pi / dx / nmax; 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 //-- Anfangsbedingungen t = 0; for (n=0; n<nmax; n++) { x = (n-nmax/2) * dx; psi[2*n] = exp(-sqr(x/alpha)/2); sum = sum + sqr(psi[2*n]); } sum = 1 / sqrt(sum); for (n=0; n<nmax; n++) { x = (n-nmax/2) * dx; psi[2*n+1] = -psi[2*n] * sum * sin(p_0*x); psi[2*n] = psi[2*n] * sum * cos(p_0*x); } // Imaginaerteil // Realteil 69 70 71 72 //--Anfangszustand im Impulsraum for (n=0; n<=2*nmax; n++) psi_p[n] = psi[n]; gsl_fft_complex_radix2_forward (psi_p, 1, nmax); 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 //-- Zeitschleife -t = 0; for (n=1; n<=n_out; n++) { t1 = n * dt; cout << n << " " << t1 << " " << t1*dp*nmax/2 << "\n"; for (n1=0; n1<nmax/2; n1++) { p = n1 * dp; arg = sqr(p)*t1/2; psi[2*n1] = psi_p[2*n1] * cos(arg) - psi_p[2*n1+1] * sin(arg); psi[2*n1+1] = psi_p[2*n1] * sin(arg) + psi_p[2*n1+1] * cos(arg); } for (n1=nmax/2; n1<nmax; n1++) { p = (n1-nmax) * dp; arg = sqr(p)*t1/2; psi[2*n1] = psi_p[2*n1] * cos(arg) - psi_p[2*n1+1] * sin(arg); psi[2*n1+1] = psi_p[2*n1] * sin(arg) + psi_p[2*n1+1] * cos(arg); } gsl_fft_complex_radix2_inverse (psi, 1, nmax); out_stream << t << "\n"; for (n1=0; n1<nmax; n1++) out_stream << (n1-nmax/2)*dx << " " << psi[2*n1] << " " << psi[2*n1+1] << " " << sqr(psi[2*n1])+sqr(psi[2*n1+1]) << "\n"; } 100 101 102 out_stream.close(); } 5.8 Das freie Teilchen 207 Betrachten wir nun die Lösungen, die uns dieses oder das vorangegangene Programm liefert, so scheint sich das Wellenpaket zunächst wie erwartet zu verhalten: 2 | | 0.003 (a) 2 | | 0.002 0.001 0.0 -100 -50 0.003 (b) 2 | | 0.002 0.001 0 50 100 x 0.0 -100 -50 0.003 (c) 0.002 0.001 0 50 100 0.0 -100 -50 x 0 50 100 x Abb. 5.1. Zeitentwicklung eines Wellenpakets berechnet mit frei2.cpp. Zunächst wandert das Paket nach rechts, wenn es den Rand des abgedeckten Ortsbereichs erreicht, verschwindet es dort und taucht am entgegengesetzten Ende wieder auf Das Wellenpaket wandert aufgrund seines positiv gewählten Anfangsimpulses p0 nach rechts. Gleichzeit läuft das Wellenpaket auseinander, d.h. es verliert an Höhe und wird breiter. Interessant wird es, wenn es den Rand des betrachteten Ortsbereichs erreicht: Während ein Teil des Pakets scheinbar nach rechts das Intervall verlässt, kommt ein Double unseres Wellenpakets am anderen Ende des Intervalls ins Spiel. Diese implizite Periodizität können wir schon am Programmcode an einigen Punkten festmachen: – Im Programm frei1.cpp werden die Randbedingungen in den Zeilen 35/36 sowie in 42/43 explizit festgelegt. Indem wir z.B. das eigentlich benötigte psi[-1] (links vom x-Bereich) durch psi[nmax-1] (am rechten Rand des x-Bereichs) ersetzt haben, haben wir uns für periodische Randbedingungen entschieden. An dieser Stelle lassen sich also die Randbedingungen verhältnismäßig einfach ändern. – Im Programm frei2.cpp sehen wir, dass die Zeitentwicklung sich in Impulsdarstellung lediglich in einer Phase exp(−ip2 t/2) äußert (Zeile 84/85 und 91/92). Es ist also offensichtlich, dass die Norm der Wellenfunktion erhalten bleibt und ein Wellenpaket nicht einfach an den Enden des Ortsbereichs verschwinden kann. – Die Periodizität im Ortsbereich liegt im Programm frei2.cpp in der Verwendung der FFT, die eigentlich keine Fouriertransformation berechnet, sondern eine Fourierreihenentwicklung (siehe Anhang F). Versuchen wir es zunächst mit einer Änderung der Randbedingungen in den Zeilen 35/36 und 42/43 von frei1.cpp, indem wir diese durch dpsi_dt[0] = - (psi[nmax+1]-2*pis[nmax]) / sqr(dx); dpsi_dt[nmax-1] = - (psi[2*nmax-2]-2*psi[2*nmax-1]) / sqr(dx) bzw. durch dpsi_dt[nmax] = + (psi[1]-2*psi[0]) / sqr(dx); dpsi_dt[2*nmax-1] = + (psi[nmax-2]-2*psi[nmax-1]) / sqr(dx) 208 5 Quantenmechanik ersetzen. Dadurch dass wir ψ jenseits des Intervalls ersatzlos haben wegfallen lassen, implizieren wir, dass die Wellenfunktion außerhalb des betrachteten Ortsbereichs null ist – was bei einem unendlich tiefen Potentialtopf der Fall ist. Entsprechend bekommen wir durch diese Änderung ein Verhalten analog zu dem beim Potentialtopf: das Wellenpaket wird beim Erreichen des Randes des Potentialtopfs an demselben reflektiert – auch nicht das Verhalten, das wir im Normalfall haben wollen. Vielmehr wünschen wir in der Mehrheit der Fälle, dass das Teilchen nach dem Erreichen des Randes den betrachteten Ortsbereich verlässt ohne irgendwo anders wiederaufzutauchen. Um zu verstehen, wie wir ein solches Verhalten realisieren können, müssen wir uns zunächst Gedanken über die Normerhaltung machen – denn genau diese müssen wir aushebeln, wenn wir wollen, dass das Teilchen irgendwo verschwindet. Betrachten wir hierzu die Dynamik eines Teilchens in einem Potential V (x). Die Schrödingergleichung in Ortsdarstellung lautet in skalierten Einheiten i 1 d2 d ψ(x, t) = − ψ(x, t) + V (x)ψ(x) . dt 2 dx2 Damit können wir bestimmen, ob sich die Norm der Wellenfunktion d d N = dx ψ (x)ψ(x) dt dt d2 i dx ψ (x) 2 ψ(x) − c.c. = 2 dx − (ψ (x)(V (x) − V (x))ψ(x)) . (5.48) ändert: (5.49) (5.50) (5.51) Hierbei bedeutet c.c. den zum Vorausgegangenen konjugierten Ausdruck. Durch partielle Integration sieht man, dass der erste Term zu keiner Änderung der Norm führen kann. Bei normalen, also reellen Potentialen verschwindet auch der zweite Term – wie es auch sein muss, da in Wirklichkeit kein Teilchen einfach verschwinden kann. Gleichzeitig liefert der Ausdruck (5.51) aber einen Hinweis, was man tun muss, um absorbierende Randbedingungen zu implementieren: Man muss in diesem Bereich ein imaginäres Potential V (x) hinzufügen. Da solche Potentiale zuerst in der klassischen Optik verwendet wurden, werden diese auch als optische Potentiale bezeichnet. Ein solches – zwangsweise ortsabhängiges Potential – führt jedoch dazu, dass das Problem nicht mehr durch Transformation in den Impulsraum gelöst werden kann. Das Programm frei2.cpp kann also so nicht mehr verwendet werden. Andererseits haben wir festgestellt, dass frei1.cpp sehr viele Teilschritte und damit enorme Rechenzeiten benötigt. Wir müssen also nach einem Weg suchen, die analytische Lösung für das Ausgangsproblem (ohne Potential) mit einem ortsabhängigen Potential zu verknüpfen. Eine Möglichkeit besteht in der Näherung exp(i(Ĥ1 + Ĥ2 )∆t) ≈ exp(iĤ1 dt) exp(iĤ2 ∆t) , (5.52) 5.8 Das freie Teilchen 209 die für beliebige Operatoren H1 und H2 möglich ist. In unserem Fall wählen wir 1 H1 = − d2 dx2 2 H2 = V (x) (5.53) (5.54) und trennen die Dynamik damit in zwei Abschnitte, die diagonal im Impulsraum bzw. im Ortsraum sind. Den Fehler, den wir dabei machen, ist von der Ordnung [Ĥ1 , Ĥ2 ](∆t)2 , (5.55) d.h. im Gegensatz zu der Situation ohne Potential müssen wir die Zeitentwicklung in viele kleine Zeitschritte unterteilen, um den Fehler gering zu halten, was zunächst keine Verbesserung gegenüber frei1.cpp verspricht. Außerdem stellen wir mit etwas Enttäuschung fest, dass der Fehler wesentlich größer ist als bei den Verfahren zur Integration der Schrödingergleichung im Ortsraum, die wir im Programm frei1.cpp eingesetzt haben (dort war der Fehler in der Größenordnung (∆t)5 ). Zunächst ist also nicht einzusehen, warum ein auf dieser Näherung basierendes Programm eine Verbesserung darstellen sollte. Einen Punkt wollen wir jedoch noch erwähnen, bevor wir daran gehen, die Näherung (5.52) in ein konkretes Computerprogramm umzusetzen: Eine wichtige Eigenschaft bleibt dieser Näherung nämlich exakt erhalten: wenn Ĥ1 und Ĥ2 hermitesch sind, bleibt die Norm der Wellenfunktion erhalten. 1 2 3 4 5 6 /************************************************************************** * Name: frei3.cpp * * Zweck: Simuliert ein quantenmechanisches freies Teilchen * * Gleichung: Schroedingergleichung mit optischem Potential * * verwendete Bibiliothek: GSL * **************************************************************************/ 7 8 9 10 11 12 #include #include #include #include #include <iostream> <fstream> <math.h> <gsl/gsl_fft_complex.h> "tools.h" 13 14 using namespace std; 15 16 17 //-- Zahl der Stuetzstellen const int nmax = 8192; 18 19 20 21 22 23 //-- globale Variablen double psi[2*nmax], V[nmax]; double dp; gsl_fft_complex_wavetable * wavetable; gsl_fft_complex_workspace * workspace; 24 25 //------------------------------------------------------------------------- 26 27 int timestep(double dt) 28 29 { 210 30 31 5 Quantenmechanik int n, n1; double psi_p[2*nmax], arg, p; 32 33 34 35 //-- Zustand im Impulsraum for (n=0; n<=2*nmax; n++) psi_p[n] = psi[n]; gsl_fft_complex_radix2_forward (psi_p, 1, nmax); 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 //-- Zeitentwicklung (freie Dynamik) for (n1=0; n1<nmax/2; n1++) { p = n1 * dp; arg = sqr(p)/2 * dt; psi[2*n1] = psi_p[2*n1] * cos(arg) psi[2*n1+1] = psi_p[2*n1] * sin(arg) } for (n1=nmax/2; n1<nmax; n1++) { p = (n1-nmax) * dp; arg = sqr(p)/2 * dt; psi[2*n1] = psi_p[2*n1] * cos(arg) psi[2*n1+1] = psi_p[2*n1] * sin(arg) } - psi_p[2*n1+1] * sin(arg); + psi_p[2*n1+1] * cos(arg); - psi_p[2*n1+1] * sin(arg); + psi_p[2*n1+1] * cos(arg); 52 53 54 //-- Ruecktransformation in den Ortsraum gsl_fft_complex_radix2_inverse (psi, 1, nmax); 55 56 57 58 59 60 61 62 //-- Zeitentwicklung (optisches Potential) for (n1=0; n1<nmax; n1++) { arg = - V[n1] * dt; psi[2*n1] = psi[2*n1] * exp(arg); psi[2*n1+1] = psi[2*n1+1] * exp(arg); } 63 64 65 return 0; } 66 67 //-------------------------------------------------------------------- 68 69 main( int argc, char *argv[] ) 70 71 72 73 74 75 76 77 78 { //-- Definition der Variablen int n, n1, n_start, n_out, n_interm, max_fct, status; double t, t1, t2, tend, dt, rtol, atol; double alpha, x, dx, p, p_0, a_opt, b_opt, arg, sum; double mu = 10; ifstream in_stream; ofstream out_stream; 79 80 81 82 83 84 85 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: frei3 infile outfile\n"; exit(1); } 86 87 88 //-- Einlesen der Parameter -in_stream.open(argv[1]); 5.8 Das freie Teilchen 89 90 91 92 93 94 95 96 211 out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von frei3.cpp\n"; inout(tend,"tend"); inout(dx,"dx"); inout(n_out,"n_out"); inout(n_interm,"n_interm"); inout(alpha,"alpha"); inout(p_0,"p_0"); inout(rtol,"rtol"); inout(atol,"atol"); inout(a_opt,"a_opt"); inout(b_opt,"b_opt"); in_stream.close(); 97 98 99 100 //-- Berechnung einiger benoetigter Parameter -dt = tend / n_out / n_interm; dp = 2 * pi / dx / nmax; 101 102 103 104 105 106 107 108 //-- Berechnung des optischen Potentials for (n=0; n<nmax; n++) { if (n < nmax/2) x = n * dx; else x = (nmax-n) * dx; V[n] = a_opt*exp( -sqr(b_opt*x) ); } 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 //-- Anfangsbedingungen for (n=0; n<nmax; n++) { x = (n-nmax/2) * dx; psi[2*n] = exp(-sqr(x/alpha)/2); sum = sum + sqr(psi[2*n]); } sum = 1 / sqrt(sum); for (n=0; n<nmax; n++) { x = (n-nmax/2) * dx; psi[2*n+1] = -psi[2*n] * sum * sin(p_0*x); psi[2*n] = psi[2*n] * sum * cos(p_0*x); } // Imaginaerteil // Realteil 124 125 126 127 128 t = 0; out_stream << n*dt << "\n"; for (n1=0; n1<nmax; n1++) out_stream << psi[2*n1] << " " << psi[2*n1+1] << "\n"; 129 130 131 132 133 134 135 136 137 138 139 //-- Zeitschleife -t = 0; for (n=1; n<=n_out; n++) { for (n1=0; n1<n_interm; n1++) status = timestep(dt); cout << n << " " << n_out << "\n"; out_stream << n*dt << "\n"; for (n1=0; n1<nmax; n1++) out_stream << psi[2*n1] << " " << psi[2*n1+1] << "\n"; } 140 141 142 out_stream.close(); } Zu den bisherigen Parametern kommen noch die Höhe a opt und die inverse Breite b opt des (Gaußschen) optischen Potentials hinzu, sowie n interm, das angibt, in wieviele intermediäre Zeitschritte jedes Zeitinter- 212 5 Quantenmechanik vall zwischen zwei Zeiten, zu denen der Zustand abgespeichert wird, eingeteilt wird. Bei der Wahl der beiden Parameter a opt und b opt ist ein wenig Fingerspitzengefühl gefragt: – Ein zu kleines b opt schränkt den Bereich ein, in dem sich das Teilchen tatsächlich wie ein freies Teilchen verhält. – Ein zu großes b opt hingegen wirkt wie eine Wand: das Teilchen dringt nur zu einem Teil in den Potentialwall ein, wo es absorbiert wird, während der Rest reflektiert wird. – Ein zu großes a opt hat ebenfalls den Effekt, dass ein nennenswerter Teil der Wellenfunktion am optischen Potential reflektiert wird, anstatt einzudringen. – Ein zu kleines a opt schließlich erlaubt dem Teilchen, das Potential zu durchdringen, und so auf der anderen Seite des Ortsbereichs wieder aufzutauchen. Zur Verdeutlichung illustrieren wir die Zeitentwicklung für einige Parameter von a opt in Abb. 5.2. Sie sehen, dass in der oberen Reihe der Wert von 2 | | 0.1 (a) 2 | | 0.05 0.0 -100 -50 0 0.1 (b) | | 0.05 0.0 -100 -50 50 100 0 x 2 | | 0.1 2 | | 0.0 -100 -50 0 0.1 | | 0.1 2 | | 0 50 100 x 0 | | 0 0.1 2 0.1 (f) 0.05 0.0 -100 -50 50 100 0 0.0 -100 -50 | | 0 50 100 x 50 100 x (h) 0.05 50 100 x x (g) 0.0 -100 -50 0.0 -100 -50 50 100 0.05 0.0 -100 -50 50 100 0.05 (c) 0.05 (e) x 2 0.1 x (d) 0.05 2 2 0.1 (i) 0.05 0.0 -100 -50 0 50 100 x Abb. 5.2. Zeitentwicklung eines Wellenpakets und dessen Absorption sowie Reflektion an einem optischen Potential, das an den beiden Enden des dargestellten Ortsbereichs lokalisiert ist. In der oberen Bildreihe ist a opt = 0.1, in der mittleren Reihe ist a opt = 0.8 und in der unteren Reihe ist a opt = 3. Gezeigt wird jeweils der Ausgangszustand des Wellenpakets (linkes Bild), das Wellenpaket im Bereich des optischen Potentials (mittleres Bild) und das Wellenpaket nach Durchlaufen des optischen Potentials (rechtes Bild) 5.9 Eigenzustände des Hamiltonoperators 213 a opt zu klein ist – das Teilchen durchdringt rechts den Bereich des optischen Potentials, erreicht den Rand und taucht auf der linken Seite auf. In der mittleren Reihe ist der Parameter a opt so gewählt, dass das Teilchen das optische Potential nicht durchdringen kann, aber auch nicht an demselben reflektiert wird. In der unteren Reihe schließlich ist a opt zu groß gewählt, so dass das Wellenpaket nur zu einem Teil absorbiert wird, während der verbleibende Teil reflektiert wird. Ein vorteilhafter Aspekt des nun entwickelten Programms frei3.cpp ist, dass es durch minimale Modifikationen auch die Entwicklung einer Wellenfunktion in einem beliebigen und sogar zeitabhängigen Potential berechnen kann. Dazu müssen lediglich die Zeilen 56–62 auf ein reelles Potential angepasst und die Zeilen 102–105 entsprechend geändert werden. Dies werden wir uns im übernächsten Abschnitt zu Nutze machen, vorher jedoch müssen wir noch lernen, wie wir zu einem vorgegebenen Potential die zugehörigen Eigenzustände und deren Energieniveaus berechnen können. 5.9 Eigenzustände des Hamiltonoperators Die zeitabhängige Schrödingergleichung (5.38) ist besonders leicht zu lösen, wenn wir als Anfangszustand einen Eigenzustand |ψ(t = 0) = |E des Hamiltonoperators H wählen, d.h. einen Zustand für den H|E = E|E (5.56) gilt. In diesem Fall ist der Zustandsvektor zu einem beliebigen Zeitpunkt t durch |ψ(t) = exp(−iEt)|E (5.57) gegeben. Die Zeitentwicklung äußert sich also lediglich in einem skalaren Faktor vom Betrag 1. Das bedeutet auch, dass sich der Erwartungswert eines beliebigen, nicht explizit zeitabhängigen Operators nicht mit der Zeit ändert, sondern konstant bleibt: ψ(t)|Ô|ψ(t) = exp(iEt)E|Ô|E exp(iEt) = E|Ô|E . (5.58) Sie sehen also, dass den Eigenzuständen des Hamiltonoperators eine besondere Bedeutung zukommt. In diesem Abschnitt wollen wir uns deshalb mit der numerischen Berechnung dieser Eigenzustände bei beliebig vorgegebenem äußeren Potential beschäftigen. Die Grundlage dafür haben wir bereits in (5.47) gelegt, denn wenn wir die Dreipunktsformel für die zweite Ableitung in Ĥ einsetzen, erhalten wir einen diskretsieren Hamiltonoperator in Matrixform: 1 + V (x) (∆x)2 1 1 = Hn,n+1 = − . 2 (∆x)2 Hn,n = Hn,n−1 (5.59) (5.60) 214 5 Quantenmechanik Um die Eigenzustände des Hamiltonoperators zu erhalten, müssen wir also die Eigenvektoren dieser Matrix berechnen, wofür zum Glück in jeder Standardbibliothek fertige Routinen zur Verfügung stehen. Betrachten wir das Programm eigen1.cpp: 1 2 3 4 5 6 /************************************************************************** * Name: eigen1.cpp * * Zweck: Berechnet Eigenzustaende zu vorgegebenem Potential * * Gleichung: Schroedingergleichung * * verwendete Bibiliothek: GSL * **************************************************************************/ 7 8 9 10 11 12 13 #include #include #include #include #include #include <iostream> <fstream> <math.h> <gsl/gsl_math.h> <gsl/gsl_eigen.h> "tools.h" 14 15 using namespace std; 16 17 18 //-- Zahl der Stuetzstellen const int nmax = 512; 19 20 //------------------------------------------------------------------------- 21 22 main( int argc, char *argv[] ) 23 24 25 26 27 28 29 { //-- Definition der Variablen int n, n1, n2; double x, dx, x2[nmax], psi[nmax], V[nmax]; ifstream in_stream; ofstream out_stream; 30 31 32 33 34 35 36 37 38 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<4) { cout << " Aufruf: eigen1 infile outfile1 outfile2\n"; cout << " outfile1 enthaelt das Potential\n"; cout << " outfile2 enthaelt die Eigenwerte und Eigenzustaende\n"; return 0; } 39 40 41 42 43 44 45 46 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von eigen1.cpp\n"; inout(dx,"dx"); in_stream.close(); out_stream << "! Spalte 1: n Spalte 2: x_n Spalte 3: V(x_n)\n"; 47 48 49 50 51 52 53 54 //-- Berechnung des reellen Potentials for (n=0; n<nmax; n++) { x = (n-nmax/2) * dx; V[n] = 0.5 * sqr(x); out_stream << n << " " << x << " " << V[n] << "\n"; } 5.9 Eigenzustände des Hamiltonoperators 55 out_stream.close(); 56 57 58 59 60 61 62 63 64 out_stream.open(argv[3]); out_stream << "! Ergebnis-Datei generiert von eigen1.cpp\n"; out_stream << "! nmax = " << nmax << "\n"; out_stream << "! dx = " << dx << "\n"; out_stream << "! Erster Teil: Spalte 1: n Spalte 2: E_n" << " Spalte 3: <xˆ2>_n\n"; out_stream << "! Zweiter Teil: Spalte 1: n Spalte 2: x" << " Spalte 3: psi_n(x)\n"; 65 66 67 68 69 70 //-- Speicherplatz allokieren gsl_vector *eval = gsl_vector_alloc(nmax); gsl_matrix *evec = gsl_matrix_alloc(nmax, nmax); gsl_matrix *H = gsl_matrix_alloc(nmax, nmax); gsl_eigen_symmv_workspace * w = gsl_eigen_symmv_alloc(nmax); 71 72 73 74 75 //-- Hamiltonoperator konstruieren for (n1=0; n1<nmax; n1++) for (n2=0; n2<nmax; n2++) gsl_matrix_set(H, n1, n2, 0); 76 77 78 79 80 81 82 83 84 85 86 87 88 for (n1=0; n1<nmax; n1++) gsl_matrix_set(H, n1, n1, 1/sqr(dx) + V[n1]); for (n1=1; n1<nmax; n1++) { gsl_matrix_set(H, n1-1, n1, -0.5/sqr(dx)); gsl_matrix_set(H, n1, n1-1, -0.5/sqr(dx)); } for (n1=0; n1<nmax-1; n1++) { gsl_matrix_set(H, n1+1, n1, -0.5/sqr(dx)); gsl_matrix_set(H, n1, n1+1, -0.5/sqr(dx)); } 89 90 91 92 93 //-- Eigenwerte und Eigenvektoren berechnen und sortieren gsl_eigen_symmv(H, eval, evec, w); gsl_eigen_symmv_free(w); gsl_eigen_symmv_sort (eval, evec, GSL_EIGEN_SORT_VAL_ASC); 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 //-- Berechnung von <x*x> for (n1=0; n1<nmax; n1++) { x2[n1] = 0; for (n2=0; n2<nmax; n2++) { x = (n2-nmax/2) * dx; x2[n1] = x2[n1] + sqr(gsl_matrix_get(evec,n2,n1)*x); } } //-- Ausgabe der Eigenwerte und Eigenvektoren for (n1=0; n1<nmax; n1++) out_stream << n1 << " " << gsl_vector_get(eval, n1) << " " << x2[n1] << "\n"; for (n1=0; n1<nmax; n1++) for (n2=0; n2<nmax; n2++) { x = (n2-nmax/2) * dx; out_stream << n1 << " " << x << " " 215 216 5 Quantenmechanik << gsl_matrix_get(evec, n2, n1) << "\n"; 114 115 } 116 117 118 119 gsl_vector_free(eval); gsl_matrix_free(evec); gsl_matrix_free(H); 120 121 122 out_stream.close(); } Nach dem Einlesen der Parameter wird das Potential V (x) an den Stützstellen berechnet (Zeile 48–54) – das ist die einzige Stelle, die wir modifizieren müssen, wenn wir die Eigenzustände zu anderen Potentialtypen berechnen wollen. Danach wird Speicherplatz für die Matrix des Hamiltonoperators, für deren Eigenwerte und Eigenvektoren, sowie für die Berechnung dieser Eigenvektoren alloziert. Dann erfolgt die Aufstellung des Hamiltonoperators nach (5.59), die Berechnung der Eigenwerte und -vektoren, sowie die Ausgabe der Ergebnisse. Zur Überprüfung des Programms eignen sich besonders Potentiale, bei denen die Eigenzustände analytisch berechnet werden können, z.B. das Potential des harmonischen Oszillators: V (x) = 1 mω 2 x2 . 2 (5.61) Dessen Eigenzustände finden sich in jedem Lehrbuch zur Quantenmechanik und lauten: 1 2 2 ψn (x) = Nn exp − k x Hn (k 2 x) (5.62) 2 mit k2 = Nn = mω h̄ (5.63) k2 √ 2n n! π . (5.64) Die dabei auftretenden Hermiteschen Polynome Hn geben wir für die niedrigsten Ordnungen n explizit an: H0 (ξ) = 1 H1 (ξ) = 2ξ (5.65) (5.66) H2 (ξ) = 4ξ − 2 . 2 Die zu diesen Eigenzuständen gehörenden Energieniveaus sind: 1 . En = h̄ω n + 2 (5.67) (5.68) 5.9 Eigenzustände des Hamiltonoperators 217 Tabelle 5.1. Numerisch bestimmte Energieeigenwerte des harmonischen Oszillators n E n E 0 0.49999 7 7.49985 1 1.49994 8 8.50359 2 2.49984 9 9.51658 3 3.49969 10 10.5515 4 4.49949 11 11.6277 5 5.49927 12 12.7666 6 6.49918 13 13.9861 Dieses analytische Ergebnis können wir nun mit dem numerischen Resultat von eigen1.cpp vergleichen. Für nmax=512 und dx = 0.02 erhalten wir die Eigenwerte: Die Energieniveaus werden also für diese Parameter bis etwa n = 10 ganz gut wiedergegeben und weichen dann immer mehr von ihrem Sollwert n+1/2 ab. Was bei diesem Wert geschieht sehen wir, wenn wir die zugehörigen Eigenfunktionen betrachten (Abb. 5.3): Wir sehen, dass die Wellenfunktion ψ für kleine n gut beim Ursprung lokalisiert ist, ihre Ausdehnung aber mit wachsendem n zunimmt. Für die- 1.0 (a) (b) 1 0.5 0 0.0 0.5 -0.5 0.0 -4 -2 0 2 4 -4 -2 0 2 x (c) (d) 2 0.5 10 0.5 0.0 0.0 -0.5 -0.5 -4 4 x -2 0 2 4 x -4 -2 0 2 4 x Abb. 5.3. Eigenzustände des harmonischen Oszillators: Vergleich des numerisch gewonnenen Ergebnisses (durchgezogene Linie) mit dem analytischen Ergebnis (gestrichelt) für n = 0, n = 1, n = 2 und n = 10. Nur bei n = 10 kann ein kleiner Unterschied zwischen diesen beiden Kurven ausgemacht werden 218 5 Quantenmechanik se n ist in Abb. 5.3 das numerische vom analytischen Ergebnis nicht mehr zu trennen. Bei etwa n = 10 erreicht die Wellenfunktion den Rand des abgedeckten Ortsbereichs, so dass verständlich ist, dass für höhere n unsere numerische Berechnung nicht mehr richtig sein kann. Trotzdem ist die Übereinstimmung der gewonnenen Eigenfunktion (in Abb. 5.3 rechts unten) mit dem analyitschen Resultat (5.62) erstaunlich. Nachdem wir nun verifiziert haben, dass das Programm korrekt funktioniert, ist es verhältnismäßig einfach, das Potential beliebig zu variieren und jeweils Energieniveaus und die zugehörigen Eigenfunktionen zu berechnen. Da wir für den übernächsten Abschnitt die Eigenzustände zum Potential (5.69) V (x) = −a exp −b(x − x0 )2 benötigen, werden wir die Eigenzustände dieses Potentials kurz diskutieren – darüber hinaus kann dieses Potential auch gut als eindimensionales Modell eines Atoms herangezogen werden. Das Programm zur Berechnung der Eigenzustände finden Sie auf der CD als eigen2.cpp. Wie schon oben erwähnt müssen nur die Zeilen geändert werden, in denen das Potential V berechnet wird: ... 55 56 57 58 59 60 //-- Berechnung des reellen Potentials for (n=0; n<nmax; n++) { x = (n-nmax/2) * dx; V[n] = -a*exp(-b*sqr(x-x0)); } ... Wenn wir die Eigenzustände und zugehörigen Eigenwerte zu diesem Potential für a = 1.5 b = 0.5 x0 = 0 (5.70) berechnen, stellen wir zunächst fest, dass das Spektrum der Eigenwerte aus zwei negativen und sehr vielen positiven Werten besteht. Die negativen Energieeigenwerte gehören zu gebundenen Zuständen – d.h. die Energie des Elektrons reicht nicht aus, den Atomkern zu verlassen – während die positiven Energien zu ungebundenen Zuständen gehören. Diese Aussage können wir auf zwei Weisen untermauern: Zum einen können wir die Eigenzustände direkt anschauen und werden sehen, dass tatsächlich die Eigenzustände zu den negativen Energien am Potential lokalisiert sind, was bei den übrigen Eigenzuständen nicht zutrifft. Da wir dies aber nicht für alle Eigenzustände (in unserem Beispiel immerhin 512 Stück) durchführen können, können wir alternativ für jeden Eigenzustand den Mittelwert x2 berechnen und ausgeben lassen. Die beiden gebundenen Zustände haben einen kleinen Wert von x2 , der sich zudem bei einer Vergrößerung des betrachteten Ortsbereichs nur wenig ändert, während diese Größe bei den freien Zuständen sehr groß ist und mit anwachsendem Ortsbereich divergiert. 5.10 Variationsmethoden 219 5.10 Variationsmethoden Alternativ zur vorangegangenen Bestimmung der Eigenwerte über die Aufstellung des Hamiltonoperators im Ortsraum kann jede beliebige Darstellung des Hilbertraumes verwendet werden. Dabei ist es auch nicht notwendig erforderlich, dass der Basissatz |φi , nach dem man einen beliebigen Zustand entwickelt, aus lauter zueinander orthogonalen Funktionen besteht – wichtig für analytische Berechnungen ist lediglich die Vollständigkeit des Funktionensatzes, d.h. jeder beliebige Zustand |ψ muss sich als Linearkombination ψi |φi (5.71) |ψ = i darstellen lassen. Bei den meisten Problemen ist diese Forderung nur durch einen unendlich großen Satz an Entwicklungsfunktionen erfüllbar. Für numerische Berechnungen ist es dann leider notwendig, sich auf einen endlichen Teilsatz zu beschränken. Diese Notwendigkeit und der damit verbundene Fehler lässt die Auswahl geeigneter Entwicklungsfunktionen unter einem ganz neuen Licht erscheinen: eine günstige Wahl kann eine sehr genaue Berechnung der Energieniveaus und Eigenzustände mit einem sehr kleinen Satz an Entwicklungsfunktionen erlauben. In diesem Zusammenhang ist natürlich jede Kenntnis über die erwarteten Eigenfunktionen (z.B. deren Verhalten in der Nähe kritischer Punkte oder für große x bzw. p) hilfreich. Beginnen wir mit einer Betrachtung des Grundzustandes |ψ0 , von dem wir voraussetzen wollen, dass er nicht entartet ist. Unter allen normierten Zuständen ist er derjenige mit dem niedrigsten Energieerwartungswert ψ0 |H|ψ0 , (5.72) da alle anderen Zustände Anteile mit höherer Energie haben – dies ist übrigens auch der Grundgedanke des Ritzschen Variationsprinzips. Wir können den Grundzustand also als eine Lösung des Variationsproblems δ (ψ0 |H|ψ0 ) = 0 (5.73) erhalten, wobei die Variation allerdings auf normierte Zustände beschränkt ist. Diese Beschränkung können wir fallenlassen, wenn wir statt des Ausdruckes (5.72) die leicht modifizierte Größe E= ψ0 |H|ψ0 ψ0 |ψ0 (5.74) variieren. Wir können den Grundzustand also anstatt als Lösung der zeitunabhängigen Schrödingergleichung auch als Lösung des Variationsproblems δE = 1 2 (ψ|ψ) (ψ|ψδψ|H|ψ − ψ|H|ψδψ|ψ +ψ|ψψ|H|δψ − ψ|H|ψψ|δψ) =0 (5.75) (5.76) 220 5 Quantenmechanik erhalten. Auch die anderen Energieniveaus erhält man als Lösung dieses Variationsproblems. Beschränkt man sich nun auf einen endlichen Satz an Funktionen, nach denen man die gesuchten Zustände entwickelt, erstreckt sich die Suche nach dem Zustand minimaler Energie nur auf einen Teilraum des tatsächlichen Hilbertraums. Aus diesem Grund ist die gefundene Grundzustandsenergie entweder zu hoch oder korrekt, kann aber niemals zu niedrig sein. Die Variationsaufgabe (5.75) kann auf die Form einer verallgemeinerten Eigenwertgleichung HA = EDA (5.77) gebracht werden. Dabei enthält die Matrix H die Elemente Hnm = φn |E|φm (5.78) und die Überlappmatrix D setzt sich aus Dnm = φn |φm (5.79) zusammen. Als konkretes Beispiel zur Berechnung von Eigenzuständen und Energieniveaus mittels (5.75) betrachten wir das Modellatom mit dem Potential V (x) = − √ β . 1 + αx2 (5.80) Die Symmetrie des Potentials gewährleistet, dass alle Eigenfunktionen entweder symmetrisch oder antisymmetrisch zu x = 0 sind. Aus diesem Grund ist es sinnvoll, zwei Sätze von Testfunktionen aufzustellen, wobei der eine nur symmetrische und der andere nur antisymmetrische Entwicklungsfunktionen enthält. Der Übersichtlichkeit halber beschränken wir uns hier auf symmetrischen Entwicklungsfunktionen. Bei der Auswahl des Funktionensatzes fn ist es von Vorteil, wenn möglichst viele der für Dnm und Hnm benötigten Erwartungswerte analytisch berechnet werden können. In unserem Fall wählen wir 1 (5.81) φn = √ x2n exp −µx2 /2 . n! √ Der Faktor 1/ n! soll bewirken, dass die Matrixelemente ähnliche Größenordnung haben – andernfalls handelt man sich an dieser Stelle vermeidbare numerische Fehler ein. Der Übersichtlichkeit spalten wir die Matrix H in den kinetischen Anteil T und den vom Potential herrührenden Teil V auf. Zunächst benötigen wir die Überlappintegrale Dnm = φn |φm ∞ 1 dx x2N exp −µx2 , = √ n!m! −∞ (5.82) (5.83) 5.10 Variationsmethoden 221 die Matrixelemente Tnm = φn |T̂ |φm 1 =− √ 2 n!m! (5.84) ∞ dx 2N (2N − 1)x2N −2 − (4N + 1)µx2N −∞ 2 2N +2 +α x exp −µx2 , (5.85) sowie Vnm = φn |V̂ |φm ∞ x2N = −β dx √ exp −µx2 . 2 1 + αx (5.86) (5.87) −∞ Hierbei haben wir die Abkürzung N = n + m eingeführt. Bis auf den letzten Integraltyp (5.87) können wir alle anderen Integrale auf Ausdrücke der Form ∞ In = dx x2n exp −µx2 −∞ 1 · 3 · . . . · (2n − 1) = µn π µ (5.88) (5.89) zurückführen: IN (5.90) n!m! 1 2N (2N − 1)IN −1 − (4N + 1)µIN + µ2 IN +1 .(5.91) = −√ 2n!m! Dnm = √ Tnm Entsprechend definieren wir ∞ dx √ Jn = −∞ x2n exp −µx2 , 1 + αx2 (5.92) welches wir jedoch numerisch bestimmen müssen. Aus dieser Hilfsgröße erhält man die fehlenden Matrixelemente für die potentielle Energie Vnm = −βJN . (5.93) Wenn wir nun zur Umsetzung des Gesagten in einem Programm kommen, stoßen wir auf ein weiteres Hindernis: die Numerik-Bibliothek GSL verfügt über keine Routine, die die verallgemeinerte Eigenwertgleichung (5.77) löst. Wir könnten nun eine weitere Bibliothek zu Hilfe nehmen (z.B. CLAPACK), 222 5 Quantenmechanik was jedoch wegen der verschiedenen Realisierung von Matrizen in GSL und CLAPACK etwas unschön ist. Alternativ können die Eigenvektoren der – offensichtlich symmetrischen – Matrix D dazu verwendet werden, (5.77) auf die Form einer gewöhnlichen Eigenwertgleichung zu bringen. Hierzu multiplizieren wir (5.77) von rechts mit der Matrix T, deren Spalten die Eigenvektoren von D sind, und von links mit der adjungierten Matrix Tt . Verwendet man √ hierbei statt auf eins normierter Eigenvektoren, solche, die auf 1/ an (an ist der jeweilige Eigenwert) normiert sind, so erhält man: H̃ = Tt HT. (5.94) Die Eigenwerte dieser Matrix H̃ sind identisch mit den Lösungen der verallgemeinerten Eigenwertgleichung (5.77). 1 2 3 4 5 6 /************************************************************************** * Name: var1.cpp * * Zweck: Berechnet Eigenzustaende zu vorgegebenem Potential * * Methode : Variationsmethode * * verwendete Bibiliothek: GSL * **************************************************************************/ 7 8 9 10 11 12 13 14 15 16 17 #include #include #include #include #include #include #include #include #include #include <iostream> <fstream> <math.h> <gsl/gsl_math.h> <gsl/gsl_vector.h> <gsl/gsl_matrix.h> <gsl/gsl_linalg.h> <gsl/gsl_eigen.h> <gsl/gsl_integration.h> "tools.h" 18 19 using namespace std; 20 21 22 23 //-- globale Variablen int n; double alpha, beta, mu; 24 25 //------------------------------------------------------------------------- 26 27 double integr1(double x, void *params) 28 29 30 31 { return 2. * pow(x,n) / sqrt(1.+alpha*sqr(x)) * exp(-mu*sqr(x)); } 32 33 //------------------------------------------------------------------------- 34 35 main( int argc, char *argv[] ) 36 37 38 39 40 41 42 { //-- Definition der Variablen int n1, n2, n3, nmax, limit=10000, N, m, signum; double x, dx, a1, b1, x1, res, abserr, sum, sum1, sum2, sum3; double nu = 10; double epsabs = 0; 5.10 Variationsmethoden 43 44 45 46 47 223 double epsrel = 1.e-6; ifstream in_stream; ofstream out_stream; gsl_function F; gsl_integration_workspace *w1; 48 49 50 51 52 53 54 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: var1 infile outfile\n"; return 0; } 55 56 57 58 59 60 61 62 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von var1.cpp\n"; inout(alpha,"alpha"); inout(beta,"beta"); inout(nmax,"nmax"); inout(mu,"mu"); in_stream.close(); 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 //-- Speicherplatz allokieren gsl_vector *eval = gsl_vector_alloc(nmax); gsl_vector *eval_D = gsl_vector_alloc(nmax); //gsl_vector *evec1 = gsl_vector_alloc(nmax); gsl_vector *I = gsl_vector_alloc(2*nmax+2); gsl_vector *J = gsl_vector_alloc(2*nmax+2); gsl_vector *fac = gsl_vector_alloc(2*nmax+2); gsl_matrix *evec = gsl_matrix_alloc(nmax, nmax); gsl_matrix *evec_D = gsl_matrix_alloc(nmax, nmax); gsl_matrix *D = gsl_matrix_alloc(nmax, nmax); gsl_matrix *D_copy = gsl_matrix_alloc(nmax, nmax); gsl_matrix *H = gsl_matrix_alloc(nmax, nmax); gsl_matrix *M = gsl_matrix_alloc(nmax, nmax); gsl_permutation *p = gsl_permutation_alloc(nmax); 78 79 80 81 82 83 84 85 86 87 //-- Hilfsintegrale I und J berechnen gsl_vector_set_zero(I); gsl_vector_set_zero(J); gsl_vector_set(I,0,sqrt(pi/mu)); gsl_vector_set(fac,0,1); for (n1=1; n1<2*nmax+2; n1++) { gsl_vector_set(I,n1,gsl_vector_get(I,n1-1)*(2.*n1-1)/2./mu); gsl_vector_set(fac,n1,n1*gsl_vector_get(I,n1-1)); } 88 89 90 91 92 93 94 95 96 97 98 for (n1=0; n1<2*nmax+2; n1++) { n = 2*n1; F.function = &integr1; F.params = &nu; w1 = gsl_integration_workspace_alloc(10000); gsl_integration_qagiu(&F, 0, epsabs, epsrel, limit, w1, &res, &abserr); gsl_vector_set(J,n1,res); gsl_integration_workspace_free(w1); } 99 100 101 //-- Ueberlappmatrix konstruieren gsl_eigen_symmv_workspace *w2 = gsl_eigen_symmv_alloc(nmax); 224 102 103 104 105 106 107 108 109 5 Quantenmechanik for (n1=0; n1<nmax; n1++) for (n2=0; n2<nmax; n2++) gsl_matrix_set(D, n1, n2, gsl_vector_get(I,n1+n2) / gsl_vector_get(fac,n1) / gsl_vector_get(fac,n2)); gsl_matrix_memcpy(D_copy,D); gsl_eigen_symmv(D_copy, eval_D, evec_D, w2); gsl_eigen_symmv_free(w2); gsl_matrix_free(D_copy); 110 111 112 113 114 115 116 117 118 119 120 121 122 123 //-- Energiematrix konstruieren for (n1=0; n1<nmax; n1++) for (n2=0; n2<nmax; n2++) { N = n1+n2; res = -beta*gsl_vector_get(J,N) + 0.5*mu*(4.*n2+1.)*gsl_vector_get(I,N) - 0.5*sqr(mu)*gsl_vector_get(I,N+1); if (n2>=1) res = res - n2*(2.*n2-1.)*gsl_vector_get(I,N-1); res = res / gsl_vector_get(fac,n1) / gsl_vector_get(fac,n2); gsl_matrix_set(H, n1, n2, res); } gsl_vector_free(I); gsl_vector_free(J); gsl_vector_free(fac); 124 125 126 127 128 129 // H durch Tˆ{-1} H T ersetzen for (n1=0; n1<nmax; n1++) for (n2=0; n2<nmax; n2++) gsl_matrix_set(evec_D,n1,n2,gsl_matrix_get(evec_D,n1,n2) / sqrt(gsl_vector_get(eval_D,n2))); 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 for (n1=0; n1<nmax; n1++) for (n2=0; n2<nmax; n2++) { sum = 0; for (n3=0; n3<nmax; n3++) sum = sum + gsl_matrix_get(H,n2,n3) * gsl_matrix_get(evec_D,n3,n1); gsl_matrix_set(M,n1,n2,sum); } for (n1=0; n1<nmax; n1++) for (n2=0; n2<nmax; n2++) { sum = 0; for (n3=0; n3<nmax; n3++) sum = sum + gsl_matrix_get(M,n2,n3) * gsl_matrix_get(evec_D,n3,n1); gsl_matrix_set(H,n1,n2,sum); } 147 148 149 gsl_matrix_free(D); gsl_vector_free(eval_D); gsl_matrix_free(evec_D); gsl_matrix_free(M); 150 151 152 153 154 155 //-- Eigenwerte und Eigenvektoren berechnen und sortieren gsl_eigen_symmv_workspace *w = gsl_eigen_symmv_alloc(nmax); gsl_eigen_symmv(H, eval, evec, w); gsl_eigen_symmv_free(w); gsl_eigen_symmv_sort (eval, evec, GSL_EIGEN_SORT_VAL_ASC); 156 157 158 159 160 //-- Ausgabe der Eigenwerte und Eigenvektoren for (n1=0; n1<nmax; n1++) out_stream << n1 << " " << gsl_vector_get(eval, n1) << "\n"; 5.10 Variationsmethoden 161 162 gsl_matrix_free(H); gsl_vector_free(eval); 225 gsl_matrix_free(evec); 163 164 165 out_stream.close(); } Im Programm erkennen wir zunächst die Definition des Integranden aus (5.92) in Zeile 27–31. Im Hauptprogramm wird dieses Integral in den Zeilen 89–98 für verschiedene Werte von n berechnet. Für die Bestimmung der In wird zunächst in Zeile 81 I0 berechnet und dann in den Zeilen 83–87 auf die Rekursionsformel 2n − 1 In = In−1 (5.95) 2µ zurückgegriffen. Anschließend konstruieren wir aus diesen Hilfsgrößen die Überlappmatrix D und die Energiematrix H. Nun müssen wir die Transformation (5.94) durchführen, wofür wir zunächst die Eigenwerte und Eigenvektoren von D benötigen (Zeile 106–109). Beachten Sie bitte, dass bei der Eigenwert- und Eigenvektorberechnung die jeweilige Matrix überschrieben wird, so dass Sie vorher eine Kopie erzeugen müssen, falls Sie die ursprüngliche Matrix noch benötigen. Die Transformation auf H̃ (das in unserem Programm einfach die ursprüngliche Matrix H ersetzt) geschieht dann in den Zeilen 125–146. Nach all dieser Vorarbeit ist die Berechnung der gesuchten Energieeigenwerte (Zeile 151–155) verhältnismäßig einfach. Die Effizienz unseres Programms überprüfen wir für α = 1 und β = 2, indem wir die Eigenenergien E0 und E2 des Grundzustands und des zweiten angeregten Zustands mit verschieden großen Sätzen an Eigenfunktionen (jeweils mit µ = 1) bestimmen (beachten Sie, dass der erste angeregte Zustand antisymmetrisch sein muss und deswegen hier nicht auftaucht): Tabelle 5.2. Energieeigenwerte eines Modellatoms, bestimmt mit der Variationsmethode n E0 E2 2 -1.476462496 0.0388704779 3 -1.482897851 -0.1979509319 4 -1.483099040 -0.3236953490 6 -1.483407203 -0.4120002016 8 -1.483432501 -0.4424870560 10 -1.483435439 -0.4548390169 12 -1.483435876 -0.4603066238 16 -1.483435970 -0.4640635221 226 5 Quantenmechanik Sie erkennen, dass die gewonnenen Energien mit wachsendem n Grenzwerten zustreben. Wenn unser Funktionensatz nicht so gewählt wurde, dass selbst im Grenzfall n → ∞ noch relevante Bereiche des Hilbertraums nicht abgedeckt werden, sind diese Grenzwerte gleich den gesuchten Eigenenergien Für die Grundzustandsenegie ist der gefundene Wert auch für verhältnismäßig kleine n außerordentlich genau. Vergleichen Sie die Größe des hierbei verwendeten Funktionensatzes mit der Zahl an Ortspunkten, die wir im vorangegangen Abschnitt benötigt haben (oder noch besser mit der entsprechenden Zahl bei Übungsaufgabe 5.2)! Bei dem Energieeigenwert für den zweiten angeregten Zustand stellen wir fest, dass wir hierfür mehr Testfunktionen benötigen – ein Sachverhalt, der ganz analog auch in Tabelle 5.1 zutage trat. 5.11 Quantentunneln Das Problem, das wir in diesem Abschnitt untersuchen wollen, ist eines jener Phänomene, die so erstaunlich sind, dass sie immer wieder diskutiert und auch experimentell untersucht werden, da sich an ihnen sowohl die Richtigkeit als auch die Unanschaulichkeit der Quantenmechanik eindrucksvoll dokumentieren lässt. Beim sogenannten Quantentunneln ist ein Teilchen zunächst in einem Bereich, den es klassisch nicht verlassen kann, weil seine Energie nicht ausreicht, die es umgebenden Potentialbarrieren zu überwinden. Die Quantenmechanik erlaubt es dem Teilchen aber trotzdem zu entkommen – als ob es einen Tunnel durch die Barriere finden würde, daher der Name des Effekts. Um dieses Phänomen zu diskutieren, konstruieren wir uns zunächst ein Potential, das über zwei Minima verfügt, z.B. V (x) = −a1 exp(−b1 (x + x0 )2 ) − a2 exp(−b2 (x − x0 )2 ) . (5.96) Dieses Potential verfügt über zwei Minima in der Nähe von ±x0 , deren Tiefe und Breite wir mit a1,2 und b1,2 festlegen können. Im folgenden interessieren wir uns für zwei nur geringfügig verschiedene Parametersätze zur Festlegung des Potentials: a1 = a2 = 1.5 b1 = b2 = 4 x0 = 2 (5.97) x0 = 2 . (5.98) bzw. a1 = 1.5 a2 = 1.6 b 1 = b2 = 4 Während im ersten Fall die beiden Potentialmulden identisch sind, ist die rechte Potentialmulde im zweiten Fall ein bisschen tiefer (siehe Abb. 5.4). Das Programm qt1.cpp setzt nun den im vorigen Abschnitt 5.9 berechneten Grundzustand für eine einzelne Potentialmulde in das linke Minmum des Potential ein und löst die zeitabhängige Schrödingergleichung – beachten Sie, dass der Ausgangszustand durch die Hinzunahme der rechten Potentialmulde kein Eigenzustand mehr ist, so dass die Dynamik nicht einfach durch (5.57) gegeben ist: 5.11 Quantentunneln 227 Abb. 5.4. Doppeltopfpotential mit zwei Minima. 1 2 3 4 5 6 /************************************************************************** * Name: qt1.cpp * * Zweck: Simuliert quantenmechanisches Tunneln * * Gleichung: Schroedingergleichung mit optischem und reellem Potential * * verwendete Bibiliothek: GSL * **************************************************************************/ 7 8 9 10 11 12 13 #include #include #include #include #include #include <iostream> <fstream> <math.h> <gsl/gsl_errno.h> <gsl/gsl_fft_complex.h> "tools.h" 14 15 using namespace std; 16 17 18 //-- Zahl der Stuetzstellen const int nmax = 8192; 19 20 21 22 23 24 25 //-- globale Variablen double psi[2*nmax]; double V[nmax], V_opt[nmax]; double t, dt, dp; gsl_fft_complex_wavetable * wavetable; gsl_fft_complex_workspace * workspace; 26 27 //------------------------------------------------------------------------- 28 29 int timestep(double dt) 30 31 32 33 { int n, n1; double psi_p[2*nmax], arg, p, a, b; 34 35 36 37 //-- Zustand im Impulsraum for (n=0; n<=2*nmax; n++) psi_p[n] = psi[n]; gsl_fft_complex_radix2_forward (psi_p, 1, nmax); 38 39 40 41 42 43 44 45 46 //-- Zeitentwicklung (freie Dynamik) for (n1=0; n1<nmax/2; n1++) { p = n1 * dp; arg = sqr(p)/2 * dt; psi[2*n1] = psi_p[2*n1] * cos(arg) - psi_p[2*n1+1] * sin(arg); psi[2*n1+1] = psi_p[2*n1] * sin(arg) + psi_p[2*n1+1] * cos(arg); } 228 47 48 49 50 51 52 53 5 Quantenmechanik for (n1=nmax/2; n1<nmax; n1++) { p = (n1-nmax) * dp; arg = sqr(p)/2 * dt; psi[2*n1] = psi_p[2*n1] * cos(arg) - psi_p[2*n1+1] * sin(arg); psi[2*n1+1] = psi_p[2*n1] * sin(arg) + psi_p[2*n1+1] * cos(arg); } 54 55 56 //-- Ruecktransformation in den Ortsraum gsl_fft_complex_radix2_inverse (psi, 1, nmax); 57 58 59 60 61 62 63 64 //-- Zeitentwicklung (optisches Potential) for (n1=0; n1<nmax; n1++) { arg = - V_opt[n1] * dt; psi[2*n1] = psi[2*n1] * exp(arg); psi[2*n1+1] = psi[2*n1+1] * exp(arg); } 65 66 67 68 69 70 71 72 73 74 75 //-- Zeitentwicklung (reelles Potential) for (n1=0; n1<nmax; n1++) { arg = V[n1] * dt; a = psi[2*n1] * cos(arg) - psi[2*n1+1] * sin(arg); b = psi[2*n1] * sin(arg) + psi[2*n1+1] * cos(arg); psi[2*n1] = a; psi[2*n1+1] = b; } t = t+dt; 76 77 78 return 0; } 79 80 //------------------------------------------------------------------------- 81 82 main( int argc, char *argv[] ) 83 84 85 86 87 88 89 90 91 { //-- Definition der Variablen int n, n1, n2, n_start, n_out, n_interm, max_fct, status, n_offset; double tend, x, dx, p, a, a1, b1, x1, a2, b2, x2, E, x_min, alpha; double arg, sum, a_opt, b_opt, prob_links, prob_rechts, rtol, atol; double mu = 10; ifstream in_stream; ofstream out_stream; 92 93 94 95 96 97 98 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: qt1 infile outfile\n"; return 0; } 99 100 101 102 103 104 105 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von qt1.cpp\n"; out_stream << "! nmax = " << nmax << "\n"; inout(tend,"tend"); inout(dx,"dx"); 5.11 Quantentunneln 106 107 108 109 110 111 112 inout(n_out,"n_out"); inout(rtol,"rtol"); inout(a_opt,"a_opt"); inout(a1,"a1"); inout(x1,"x1"); inout(b2,"b2"); in_stream.close(); inout(n_interm,"n_interm"); inout(atol,"atol"); inout(b_opt,"b_opt"); inout(b1,"b1"); inout(a2,"a2"); inout(x2,"x2"); 113 114 115 116 //-- Berechnung einiger benoetigter Parameter -dt = tend / n_out / n_interm; dp = 2 * pi / dx / nmax; 117 118 119 120 121 122 123 124 125 126 //-- Berechnung des optischen Potentials for (n=0; n<nmax; n++) { if (x < nmax/2) x = n * dx; else x = (nmax-n) * dx; V_opt[n] = a_opt*exp( -sqr(b_opt*x) ); } 127 128 129 130 131 132 133 //-- Berechnung des reellen Potentials for (n=0; n<nmax; n++) { x = (n-nmax/2) * dx; V[n] = -a1*exp(-b1*sqr(x-x1)) -a2*exp(-b2*sqr(x-x2)); } 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 //-- Anfangsbedingungen for (n=0; n<nmax; n++) psi[2*n] = 0; sum = 0; in_stream.open("eigen1.res"); n_offset = nmax/2 - 512/2 + x2 / dx; cout << " n_offset : " << n_offset << "\n"; for (n=0; n<512; n++) {in_stream >> n1; in_stream >> E;} for (n=0; n<512; n++) { in_stream >> n1; in_stream >> n2; in_stream >> a; psi[2*(n+n_offset)] = a; sum = sum + sqr(psi[2*(n+n_offset)]); } in_stream.close(); sum = 1 / sqrt(sum); for (n=0; n<nmax; n++) { psi[2*n] = psi[2*n] * sum; // Realteil psi[2*n+1] = 0; // Imaginaerteil } prob_links = 0; prob_rechts = 0; for (n1=0; n1<nmax; n1++) prob_links = prob_links + sqr(psi[n1]); for (n1=nmax; n1<2*nmax; n1++) prob_rechts = prob_rechts + sqr(psi[n1]); cout << "0 0 " << " " << prob_links << " " << prob_rechts << "\n"; 162 163 164 t = 0; out_stream << n*dt << "\n"; 229 230 165 166 5 Quantenmechanik for (n1=0; n1<nmax; n1++) out_stream << psi[2*n1] << " " << psi[2*n1+1] << "\n"; 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 //-- Zeitschleife -t = 0; for (n=1; n<=n_out; n++) { for (n1=0; n1<n_interm; n1++) timestep(dt); //-- Wahrscheinlichkeit, dass das Teilchen in der linken/rechten // Haelfte ist prob_links = 0; prob_rechts = 0; for (n1=0; n1<nmax; n1++) prob_links = prob_links + sqr(psi[n1]); for (n1=nmax; n1<2*nmax; n1++) prob_rechts = prob_rechts + sqr(psi[n1]); cout << n << " " << t << " " << prob_links << " " << prob_rechts << "\n"; out_stream << n*dt << "\n"; for (n1=0; n1<nmax; n1++) out_stream << psi[2*n1] << " " << psi[2*n1+1] << "\n"; } 183 184 185 out_stream.close(); } Als Ergebnis liefert das Programm zum einen die Zustände zu den Zeitpunkten 0, dt, 2dt, . . . N dt und zum anderen die Wahrscheinlichkeit, das Teilchen zu diesen Zeitpunkten in der linken oder in der rechten Hälfte des Ortsbereiches zu finden. Diese beiden Wahrscheinlichkeiten – im Programmtext prob links und prob rechts genannt – werden auf dem Bildschirm ausgegeben, können jedoch auch in eine gesonderte Datei umgelenkt werden. Wie wir feststellen, ist dieser Zeitverlauf, der das Wechseln des Teilchens vom einen in das andere Potentialminimum wiedergibt, für die beiden sehr ähnlichen Potentialverläufe sehr unterschiedlich. Beginnen wir mit dem symmetrischen Potentialverlauf, bei dem a1 = a2 = 1.5 ist (Abb. 5.5): Abb. 5.5. Aufenthaltswahrscheinlichkeit des Teilchens in der linken (durchgezogene Linie) bzw. rechten Potentialmulde (gestrichelt dargestell) beim symmetrischen Doppelmuldenpotential Das Teilchen pendelt also zwischen den beiden Potentialminima hin und her. Dieses Verhalten steht in krassem Gegensatz zu dem, was wir vom thermischen Tunneln her kennen, bei dem die Aufenthaltswahrscheinlichkeit sich monoton dem thermischen Gleichgewicht (für zwei gleich tiefe Minima also der Gleichverteilung) nähert. 5.11 Quantentunneln 231 Die bisher besprochene Situation von zwei gleich tiefen Potentialmulden, die gut voneinander getrennt sind, lässt sich in guter Näherung auch analytisch beschreiben. Dazu nehmen wir zunächst an, wir würden die Eigenfunk(0) tionen ψi (x) einer einzelnen Potentialmulde V0 (x) kennen. Nun suchen wir die Eigenfunktionen des Doppelmuldenpotentials V (x) = V0 (x − x0 ) + V0 (x + x0 ) . (5.99) Da dieses Potential symmetrisch ist, müssen alle Eigenfunktionen entweder symmetrisch (ψ(x) = ψ(−x)) oder antisymmetrisch (ψ(x) = −ψ(−x)) sein. In niedrigster Ordnung Störungstheorie mit Entartung müssen wir diese Eigenfunktionen aus den Eigenfunktionen gleicher Energie zum linken bzw. rechten Potential zusammensetzen (siehe z.B. [29] oder [30]): ψ0,1 (x) = αi (ψ0 (x − x0 ) ± ψ0 (x + x0 )) . (5.100) Der Normierungsfaktor α bestimmt sich für gut separierte Potentialmulden durch N = dx ψ0,1 (x)ψ0,1 (x) (5.101) = αi2 2 ± 2Re dx ψ0 (x + x0 )ψ(x − x0 ) (5.102) ≈ 2αi2 , woraus folgt, dass (5.103) √ αi = 1/ 2 (5.104) ist. Näherungen für die zugehörigen Energieniveaus erhalten wir durch Berechnung der entsprechenden Energieerwartungswerte: E0,1 = αi2 dx ψ0,1 (x) d2 (5.105) × − 2 + V0 (x − x0 ) + V (x − x0 ) ψ0,1 (x) dx 1 (0) dx ψ0 ψ0 . (5.106) = 2E0 ± 2Re 2 Wenn wir nun den entsprechenden Zeitverlauf bei dem links etwas vertieften Potential (a1 = 1.6, a2 = 1.5) gegenüberstellen (Abb. 5.6), so stellen wir fest, dass der Zeitabhnagigkeit beider Aufenthaltswahrscheinlichkeiten zwar wieder sinusoidal ist, die Wahrscheinlichkeit, das Teilchen rechts anzutreffen, allerdings nie größer als etwa 52% ist. Abschließend möchten wir in diesem Abschnitt noch die Situation ansprechen, dass das Teilchen in einen ungebundenen Zustand tunneln kann. 232 5 Quantenmechanik Abb. 5.6. Aufenthaltswahrscheinlichkeit des Teilchens in der linken (durchgezogene Linie) bzw. rechten Potentialmulde (gestrichelt dargestellt) beim leicht asymmetrischen Doppelmuldenpotential Dies erreichen wir, indem wir das Potential so wählen, dass zu gebundenen Zuständen auf der rechten Seite des Potentialwalls freie Zustände vergleichbarer Energie auf der linken Seite existieren. Dies ist z.B. der Fall, wenn wir das Potentialminimum nach links beliebig verlängern: f ür x ≤ x0 −a1 exp b1 (x + x0 )2 − a2 exp b2 (x − x0 )2 . V (x) = −a1 exp b1 (2x0 )2 − a2 f ür x > x0 (5.107) Abb. 5.7. Aufenthaltswahrscheinlichkeit des Teilchens in der linken (durchgezogene Linie) bzw. rechten (gestrichelt dargestellt) Hälfte des Ortsbereichs Zunächst fällt in Abb. 5.7 auf, dass für t → ∞ beide Wahrscheinlichkeiten gegen null gehen, was daran liegt, dass die ungebundenen Teilchen den betrachteten Ortsbereich verlassen und dabei vom optischen Potential absorbiert werden. Da dies ausschließlich links passiert, müssen diese fehlenden Teilchen eigentlich zu P2 hinzugerechnet werden. Trotzdem verbleibt die Tatsache, dass das Tunneln nicht mehr periodisch ist wie in den vorangegangen Beispielen, sondern irreversiblen Charakter hat: Ist das Teilchen erst einmal auf der linken Seite, ist die Wahrscheinlichkeit für ein Zurücktunneln vernachlässigbar. 5.12 Einführung in die Quantenstatistik 233 5.12 Einführung in die Quantenstatistik In den folgenden Abschnitten wollen wir etwas expliziter auf die Dynamik von Atomen eingehen. Wir haben bereits an unserem eindimensionalen Modellatom aus Abschn. 5.9 gesehen, dass ein Atom über diskrete Energieniveaus im negativen Energiebereich und ein darüberliegendes Kontinuum verfügt. Die diskreten Energieniveaus gehören zu gebundenen Zuständen, während bei den Zuständen aus dem Kontinuum das Elektron nicht an den Atomkern gebunden ist. Einen Aspekt, den wir jedoch in unserem bisherigen Modell überhaupt nicht verstehen können, ist wie ein Atom spontan von einem Energieniveau auf ein anderes (niedrigeres) springt und die überschüssige Energie in Form eines Photons abgibt. Denn wenn sich das Elektron in einem Eigenzustand des vom Atomkern erzeugten Potentials befindet, sollte es für immer und ewig in diesem Eigenzustand bleiben. Dieser spontane Zerfall wurde erst im Rahmen der Quantenfeldtheorie verständlich, als neben dem Atom auch das Vakuum mit seinen Nullpunktsfluktuationen in das Modell einbezogen wurde. Zum Glück ist es in vielen Fällen nicht nötig, dieses Vakuum explizit mitzubetrachten, sondern man befindet sich in einer Situation, die ganz ähnlich der in Abschn. 4.6 ist: die Vakuumflukationen sind eine Störung, die die ansonsten Hamiltonsche Dynamik des Atoms stört, und wie dort kann man dieser Störung durch eine statistische Beschreibung gerecht werden, ohne diese explizit miteinzubeziehen. Dieser Grundgedanke ist die Basis der Quantenstatistik. Auf eine detaillierte Herleitung des Formalismusses müssen wir im Rahmen dieses Buches zwar verzichten, wir möchten allerdings die wichtigsten Punkte wiedergeben, so dass Sie die nachfolgenden Abschnitte verstehen können, ohne zu einem anderen Buch greifen zu müssen. Letzteres können wir Ihnen allerdings nicht ersparen, wenn Sie wissen möchten, wie man zu den dargestellten Gleichungen kommt. Beginnen wir mit der Beschreibung eines Zustandes in der Quantenstatistik. Während dieser Zustand bisher durch einen Zustandsvektor |ψ beschrieben wurde, tritt nun an dessen Stelle der Dichteoperator ˆ – statt eines Vektors haben wir es nun also mit einer Matrix zu tun. Die bisher betrachteten Zustandsvektoren |ψ sind als sogenannte reine Zustände in dieser erweiterten Beschreibung enthalten, da Sie durch die Dichtematrix ˆ = |ψψ| , (5.108) dargestellt werden Können. Umgekehrt kann nicht jeder zulässige Dichteoperator in dieser Weise durch einen Zustandsvektor ausgedrückt werden, doch darauf gehen wir später noch genauer ein. Diese nicht reinen Zustände heißen gemischt. Mittelwerte erhält man aus der Dichtematrix durch Spurbildung (5.109) Ô = Tr Ôˆ , 234 5 Quantenmechanik dabei ist die Spur Tr(M̂ ) einer Matrix M̂ definiert durch die Summe ihrer Diagonalelemente: Mii . (5.110) Tr(M̂ ) = i Diese Spur ist im Gegensatz zu den einzelnen Diagonalelementen unabhängig von der Wahl der Basis, nach der man den Operator M̂ entwickelt. Sie können sich davon überzeugen, dass wir im Fall von reinen Zuständen wieder zu Gleichung (5.37) aus Abschn. 5.5 zurückkommen: (5.111) Ô = Tr Ôˆ (5.112) = Tr Ô|ψψ| = ψn On,m ψm (5.113) n,m = ψ|Ô|ψ . (5.114) Schließlich benötigen wir noch die Bewegungsgleichung für die Dichtematrix , also den Ersatz für die Schrödingergleichung, die ja die Bewegungsgleichung für die Zustandsvektoren |ψ ist. Diese Bewegungsgleichung heißt in Anlehnung an ihr Analogon aus der klassischen Statistischen Physik ebenfalls Mastergleichung und spaltet sich in einen Hamiltonschen (reversiblen) Anteil und einen nicht-Hamiltonschen (irreversiblen) Anteil auf: D d d (t) = (t) (t) + . (5.115) dt dt dt rev irrev Ohne Spezifizierung auf ein konkretes Problem lässt sich allerdings nur der Hamiltonsche Anteil explizit angeben: d i = − [H, ] . (t) (5.116) dt h̄ rev Auch hier können wir uns wieder davon überzeugen, dass die Beschreibung der Quantenstatistik für reine Zustände und für eine rein Hamiltonsche Dynamik wieder in die gewöhnliche Quantenmechanik übergeht: 1 ∂ |ψ = H|ψ ⇒ ∂t ih̄ 1 ∂ |ψψ| = ((H|ψ) ψ| − |ψ (Hψ|)) ∂t ih̄ i = − [H, |ψψ|] . h̄ (5.117) (5.118) (5.119) Als letzten Punkt wollen wir das quantenmechanische Analogon zur Entropie einführen: 5.13 Ein Zwei-Niveau-System mit äußerer Anregung S = −Tr ln . 235 (5.120) Dabei ist der Logarithmus einer Matrix im Sinne von Abschn. 5.2 zu verstehen, d.h. der Logarithmus wird über seine Potenzreihe [31] definiert: ln(1 + ) = ∞ (−1)n n=1 n . n (5.121) In der Praxis wird man den Logarithmus von jedoch berechnen, indem man in Diagonaldarstellung (5.122) = TDT−1 bringt. Hierbei ist D eine Diagonalmatrix und die Zeilenvektoren von T sind die Eigenvektoren von . Dann ist (ln)nm = Tnl lnDll Tml . (5.123) l Dabei haben wir schon davon Gebrauch gemacht, dass die Eigenvektoren von senkrecht aufeinander stehen und folglich T−1 = Tt (5.124) gilt. Im Fall reiner Zustände = |ψψ| erhalten wir S=0, (5.125) wovon man sich am Leichtesten überzeugt, indem man eine Basis wählt, in der |ψ einer der Basisvektoren ist. Dann reduziert sich die Spurbildung in (5.120) auf diesen Vektor: S = −1 ln 1 = 0 . (5.126) Aus der Tatsache, dass es Dichtematrizen gibt, bei denen der Wert der Entropie (5.120) von null verschieden ist, ergibt sich automatisch, dass diese Zustände nicht in der Form (5.108) geschrieben werden können und daher nicht rein sind. 5.13 Ein Zwei-Niveau-System mit äußerer Anregung Das System, dem wir uns zunächst zuwenden wollen, hat einen denkbar einfachen, nämlich nur zweidimensionalen Hilbertraum. Die naheliegendste Realisierung eines solchen Systems ist ein Atom, bei dem nur zwei gebundene Zustände betrachtet werden müssen – aber auch der innere Freiheitsgrad des Spins führt bei Spin 1/2 und einem einzelnen, freien Teilchen auf zwei diskrete Niveaus mit den Spineinstellungen ±1/2. 236 5 Quantenmechanik Wenn wir die äußere Anregung für einen Moment ausser Acht lassen, wird der Hamiltonsche Anteil der Dynamik durch den Hamiltonoperator E1 0 H= (5.127) 0 E2 mit den Energieniveaus E1 und E2 beschrieben. Eine äußere Anregung, z.B. durch Einstrahlen von Licht, führt nun zu einem Zusatzterm im Hamiltonoperator: ∆E1 α . (5.128) Hext = α ∆E2 Interessant sind hierbei insbesondere die Nichtdiagonalelemente, die zu einer Kopplung der beiden Eigenzustände des ungestörten Systems führen. Der Einfachheit halber setzen wir die beiden Diagonalelemente ∆E1 und ∆E2 null, und haben zusammen den Hamiltonoperator E1 α Hges = . (5.129) α E2 Ohne irreversible Terme in der Bewegungsgleichung würde dieses Problem auf die Diagonalisierung des Hamiltonoperators (5.129), also einer 2×2-Matrix hinauslaufen, und die Lösung wäre eine Überlagerung der beiden so gewonnenen Eigenzustände. Dass dies nicht die ganze Wahrheit sein kann, sehen wir bereits an dem Problem ohne äussere Anregung: Bei dem Hamiltonoperator (5.127) müsste ein Atom immer im angeregten Zustand verbleiben – die Wirklichkeit lehrt uns jedoch, dass jedes angeregte Atom mit einer für diesen Anregungszustand typischen Zerfallszeit τ in den Grundzustand zurückkehrt. Diese Diskrepanz mit der Realität wird behoben, wenn wir das Atom an ein Wärmebad ankoppeln, d.h. ein System mit vielen Freiheitsgraden, dessen Dynamik wir nicht kennen und uns auch gar nicht interessiert. Eine solche Ankopplung an ein Wärmebad führt zu einem nicht-Hamiltonschen Zusatzterm in der Bewegungsgleichung: ∂ i γ21 = − [Ĥ, ] + [Ŝ− , Ŝ+ ] + [Ŝ− , Ŝ+ ] ∂t h̄ 2 γ12 + [Ŝ+ , Ŝ− ] + [Ŝ+ , Ŝ− ] , 2 wobei die sogenannten Umklappoperatoren 00 01 S+ = sowie S− = 10 00 (5.130) (5.131) das System aus dem Grundzustand in den angeregten Zustand befördern (S+ ) bzw. den umgekehrten Übergang bewirken (S− ). Neu sind in (5.130) die beiden Terme proportional zu γ21 bzw. γ12 . Der erste beschreibt den Übergang vom angeregten in den Grundzustand, während 5.13 Ein Zwei-Niveau-System mit äußerer Anregung 237 der Term in der zweiten Zeile den umgekehrten Vorgang beschreibt, der bei einem Wärmebad endlicher Temperatur auch möglich ist – schließlich muss sich im thermischen Gleichgewicht ein Zustand einstellen, bei dem die Besetzung der beiden Niveaus durch den Boltzmannfaktor wiedergegeben wird: N1 = exp {−(E1 − E2 )/kT } . N2 (5.132) Daraus lässt sich herleiten, dass γ12 = exp {−(E2 − E1 )/kT } . γ21 (5.133) gelten muss. Hierbei ist k die Boltzmann-Konstante, die Sie in der Literatur auch als kB finden. Der Vollständigkeit halber müssen wir noch anmerken, dass die quantenmechanische Mastergleichung (5.130) nur gilt, wenn – der Grenzfall schwacher Dämpfung vorliegt: γ12 , γ21 (E2 − E1 )/h̄ , – die Markov-Approximation gerechtfertigt ist, was bedeutet, dass kT /h̄ > γ12 , γ21 sein muss. Abschließend wollen wir noch darauf hinweisen, dass im Grenzfall niedriger Temperaturen kT E2 −E1 der letzte Term in (5.130) vernachlässigt werden kann. Ausgeschrieben lautet die Differentialgleichung (5.130) ∂ 11 12 i α(21 − 12 ) −∆E12 + α(22 − 11 ) =− α(12 − 21 ) ∂t 21 22 h̄ ∆E21 + α(11 + 22 ) γ21 222 −12 γ12 −211 −12 + + (5.134) −21 −222 −21 211 2 2 Die Implementation dieser Bewegungsgleichung in einem Programm stellt uns vor keine besonderen Schwierigkeiten: 1 2 3 4 5 6 7 /************************************************************************** * Name: zweiniveau.cpp * * Zweck: Dynamik eines Zweiniveau-Atoms mit aeusserer Anregung und * * Daempfung * * Gleichung: Quanten-Mastergleichung * * verwendete Bibliothekl: GSL * **************************************************************************/ 8 9 10 11 12 13 14 #include #include #include #include #include #include <iostream> <fstream> <gsl/gsl_errno.h> <gsl/gsl_odeiv.h> <math.h> "tools.h" 15 16 17 using namespace std; 238 18 19 5 Quantenmechanik //-- Definition von Konstanten const int imax_max = 100; 20 21 22 //-- Definition globaler Variablen double omega_12, alpha, gamma_down; 23 24 //------------------------------------------------------------------------- 25 26 int f(double t, const double x[], double dxdt[], void *params) 27 28 29 30 31 32 33 34 { int double double double double double n, m, i, j; rho_11_real, rho_12_real, rho_21_real, rho_22_real; rho_11_imag, rho_12_imag, rho_21_imag, rho_22_imag; drhodt_11_real, drhodt_12_real, drhodt_21_real, drhodt_22_real; drhodt_11_imag, drhodt_12_imag, drhodt_21_imag, drhodt_22_imag; mu = *(double *)params; 35 36 37 38 39 rho_11_real rho_21_real rho_12_real rho_22_real = = = = x[0]; x[2]; x[4]; x[6]; rho_11_imag rho_21_imag rho_12_imag rho_22_imag = = = = x[1]; x[3]; x[5]; x[7]; 40 41 42 43 44 45 46 47 //-Terme durch drhodt_11_real drhodt_21_real drhodt_21_imag drhodt_12_real drhodt_12_imag drhodt_22_real Eigenenergien = 0; drhodt_11_imag = 0; = - omega_12 * rho_21_imag; = omega_12 * rho_21_real; = omega_12 * rho_12_imag; = - omega_12 * rho_12_real; = 0; drhodt_22_imag = 0; //-Terme durch drhodt_11_real drhodt_11_imag drhodt_21_real drhodt_21_imag drhodt_12_real drhodt_12_imag drhodt_22_real drhodt_22_imag Daempfung = drhodt_11_real = drhodt_11_imag = drhodt_21_real = drhodt_21_imag = drhodt_12_real = drhodt_12_imag = drhodt_22_real = drhodt_22_imag //-Terme durch drhodt_11_real drhodt_11_imag drhodt_12_real drhodt_12_imag drhodt_21_real drhodt_21_imag drhodt_22_real drhodt_22_imag Kopplung (z.B. durch Pumpen) = drhodt_11_real + alpha * (rho_21_imag-rho_12_imag); = drhodt_11_imag - alpha * (rho_21_real-rho_12_real); = drhodt_12_real + alpha * (rho_22_imag-rho_11_imag); = drhodt_12_imag - alpha * (rho_22_real-rho_11_real); = drhodt_21_real + alpha * (rho_11_imag-rho_22_imag); = drhodt_21_imag - alpha * (rho_11_real-rho_22_real); = drhodt_22_real + alpha * (rho_12_imag-rho_21_imag); = drhodt_22_imag - alpha * (rho_12_real-rho_21_real); 48 49 50 51 52 53 54 55 56 57 + gamma_down + gamma_down -0.5*gamma_down -0.5*gamma_down -0.5*gamma_down -0.5*gamma_down gamma_down gamma_down * * * * * * * * rho_22_real; rho_22_imag; rho_21_real; rho_21_imag; rho_12_real; rho_12_imag; rho_22_real; rho_22_imag; 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 dxdt[0] dxdt[2] dxdt[4] dxdt[6] = = = = drhodt_11_real; drhodt_21_real; drhodt_12_real; drhodt_22_real; 73 74 75 76 return GSL_SUCCESS; } dxdt[1] dxdt[3] dxdt[5] dxdt[7] = = = = drhodt_11_imag; drhodt_21_imag; drhodt_12_imag; drhodt_22_imag; 5.13 Ein Zwei-Niveau-System mit äußerer Anregung 77 239 //------------------------------------------------------------------------ 78 79 80 int jac(double t, const double x[], double *dfdx, double dxdt[], void *params) 81 82 83 84 { return GSL_SUCCESS; } 85 86 //------------------------------------------------------------------------ 87 88 int main( int argc, char *argv[] ) 89 90 { 91 92 93 94 95 96 97 98 //-- Definition der Variablen int n, n1, n_out, neq; double t, t_end, t1, t2, dt, E_1, E_2, rtol, atol, h, hmax, daux; double x[6*imax_max]; double mu = 10; ifstream in_stream; ofstream out_stream; 99 100 101 102 103 104 105 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: zweiniveau infile outfile\n"; return 0; } 106 107 108 109 110 111 112 113 114 115 116 117 118 119 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei generiert von zweiniveau.cpp\n"; inout(t_end,"t_end"); inout(n_out,"n_out"); inout(atol,"atol"); inout(rtol,"rtol"); inout(E_1,"E_1"); inout(E_2,"E_2"); inout(alpha,"alpha"); inout(gamma_down,"gamma_down"); inout(x[0],"x[0]"); inout(x[1],"x[1]"); inout(x[2],"x[2]"); inout(x[3],"x[3]"); inout(x[4],"x[4]"); inout(x[5],"x[5]"); inout(x[6],"x[6]"); inout(x[7],"x[7]"); in_stream.close(); 120 121 122 123 124 //-- Berechnung einiger benoetigter Parameter -dt = t_end / n_out; neq = 8; omega_12 = E_2-E_1; 125 126 127 //-- Anfangsbedingungen t = 0; 128 129 130 131 132 133 134 135 //-- Initialisierung der GSL-Routine const gsl_odeiv_step_type *T = gsl_odeiv_step_rkf45; gsl_odeiv_step *s = gsl_odeiv_step_alloc(T, neq); gsl_odeiv_control *c = gsl_odeiv_control_y_new(atol, rtol); gsl_odeiv_evolve *e = gsl_odeiv_evolve_alloc(neq); gsl_odeiv_system sys = {f, jac, neq, &mu}; 240 136 137 138 139 140 141 142 143 144 145 146 147 148 5 Quantenmechanik for (n=1; n<=n_out; n++) { t1 = n * dt; while (t<t1) { int status = gsl_odeiv_evolve_apply(e, c, s, &sys, &t, t1, &h, x); if (status != GSL_SUCCESS) break; } cout << t << "\n"; out_stream << t; for (n1=0; n1<8; n1++) out_stream << " " << x[n1]; out_stream << "\n"; } 149 150 151 152 gsl_odeiv_evolve_free(e); gsl_odeiv_control_free(c); gsl_odeiv_step_free(s); 153 154 155 out_stream.close(); } Wir haben die Real- und Imaginärteile der vier Komponenten der Dichtematrix zu einem Vektor x der Länge 8 zusammengefasst, wobei wir nicht davon Gebrauch gemacht haben, dass hermitesch sein muss. Damit ließe sich die Zahl der wirklich unabhängigen Variablen auf vier halbieren – andererseits ist die Hermitizität von auch eine gute Möglichkeit, das Programm zu verifizieren, wenn der Programmcode von dieser Eigenschaft nicht explizit Gebrauch macht. In der Funktion f ab Zeile 26 schreiben wir und dessen zeitliche Ableitung in einer Matrix-ähnlichen Form, um die Übereinstimmung mit der Bewegungsgleichung (5.134) deutlich zu machen. Abb. 5.8. Aufenthaltswahrscheinlichkeit des Teilchens im Grund- (durchgezogene Kurve) bzw. im angeregten Zustand (gestrichelte Kurve) eines 2-Niveau-Systems mit äußerer Anregung und Dämpfung Wir sehen in Abb. 5.8, dass die Aufenthaltswahrscheinlichkeit im angeregten Zustand abnimmt, während die Besetzung des Grundzustands entsprechend zunimmt – dies ist der Effekt der Dämpfung. Überlagert sehen wir die Oszillationen, die wir schon aus den Abb. 5.5 und 5.6 kennen und die von der Kopplung der beiden Niveaus, also der Anregung herrührt. Diese hat auch zur Folge, dass für t → ∞ die Besetzung des angeregten Niveaus nicht gegen null, sondern gegen einen endlichen Wert strebt. 5.14 Messprozess 241 5.14 Messprozess An dem Beispiel des Zweiniveausystems können wir nun den Einfluss des Messprozesses auf die Dynamik untersuchen. Zu diesem Zweck stellen wir uns vor, dass wir zu beliebigen Zeitpunkten eine Messung vornehmen, ob sich das System im Grund- oder angeregten Zustand befindet. Eine solche Messung hat also nur zwei mögliche Ergebnisse, deren Wahrscheinlichkeiten aufgrund der Kopenhagener Interpretation der Quantenmechanik (siehe Abschn. 5.5 bzw. 5.12) durch die Diagonalelemente 11 bzw. 22 gegeben sind. Auch den Zustand nach der Messung liefert uns die Kopenhagener Interpretation: Je nach Ausgang der Messung liegt der Zustand 10 00 oder (5.135) 00 01 vor. Wir implementieren diesen Vorgang im Programm zeno1.cpp mit Hilfe eines Zufallszahlengenerators, der entscheidet, welchen Ausgang die Messung nimmt: ... 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 for (n=1; n<=n_out; n++) { t1 = n * dt; while (t<t1) { int status = gsl_odeiv_evolve_apply(e, c, s, &sys, &t, t1, &h, x); if (status != GSL_SUCCESS) break; } rand = gsl_rng_uniform (r1); if (rand < x[0]) { x[0] = 1; x[1] = 0; x[2] = 0; x[3] = 0; x[4] = 0; x[5] = 0; x[6] = 0; x[7] = 0; } else { x[0] = 0; x[1] = 0; x[2] = 0; x[3] = 0; x[4] = 0; x[5] = 0; x[6] = 1; x[7] = 0; } cout << t << " " << x[0] << "\n"; out_stream << t << " " << x[0] << " " << x[6] << "\n"; } 166 167 168 169 gsl_odeiv_evolve_free(e); gsl_odeiv_control_free(c); gsl_odeiv_step_free(s); 170 171 172 out_stream.close(); } Es überrascht Sie hoffentlich nicht, dass das Ergebnis dieses Programms (Abb. 5.9) völlig anders aussieht als das des Programms zweiniveau.cpp. 242 5 Quantenmechanik Abb. 5.9. Typischer Ausgang von aufeinanderfolgenden Messungen im zeitlichen Abstand ∆t = 0.1 an einem Zweiniveausystem. Null bedeutet, dass das System im Grundzustand ist – eins bedeutet, dass es sich im angeregten Zustand befindet 5.15 Der Zeno-Effekt Wir müssen uns auch klar darüber sein, dass das dargestellte Ergebnis zwar typisch ist, aber aufgrund des statistischen Charakters des Messvorgangs nicht das einzig mögliche. Um zu – zumindest im Prinzip – vergleichbaren Ergebnissen zu kommen, müssen wir viele solcher typischen Kurven sammeln und überlagern – ganz ähnlich zu unserer Vorgehensweise in Kap. 4. Das Hauptprogramm des so entstandenen zeno2.cpp sieht dann so aus: ... 86 //------------------------------------------------------------------------- 87 88 int main( int argc, char *argv[] ) 89 90 { 91 92 93 94 95 96 97 98 99 100 //-- Definition der Variablen int m, n, n1, n_out, neq, N_ensemble; double t, t_end, t1, t2, dt, E_1, E_2, rand, rtol, atol, h, hmax, daux; double x[8], x_0[8]; double mu = 10; ifstream in_stream; ofstream out_stream; const gsl_rng_type * T1; gsl_rng * r1; 101 102 103 104 105 106 107 //-- Fehlermeldung, wenn Input- und Outputfilename nicht uebergeben wurden if (argc<3) { cout << " Aufruf: zeno2 infile outfile\n"; return 0; } 108 109 110 111 112 113 114 115 116 117 118 //-- Einlesen der Parameter -in_stream.open(argv[1]); out_stream.open(argv[2]); out_stream << "! Ergebnis-Datei inout(t_end,"t_end"); inout(atol,"atol"); inout(E_1,"E_1"); inout(alpha,"alpha"); inout(N_ensemble,"N_ensemble"); inout(x[0],"x_0[0]"); generiert von zeno2.cpp\n"; inout(n_out,"n_out"); inout(rtol,"rtol"); inout(E_2,"E_2"); inout(gamma_down,"gamma_down"); inout(x_0[1],"x[1]"); 5.15 Der Zeno-Effekt 119 120 121 inout(x[2],"x_0[2]"); inout(x[4],"x_0[4]"); inout(x[6],"x_0[6]"); inout(x_0[3],"x[3]"); inout(x_0[5],"x[5]"); inout(x_0[7],"x[7]"); 122 123 124 125 126 //-- Berechnung einiger benoetigter Parameter -dt = t_end / n_out; neq = 8; omega_12 = E_2-E_1; 127 128 129 gsl_vector *results = gsl_vector_alloc(n_out); for (n=1; n<=n_out; n++) gsl_vector_set(results,n-1,0); 130 131 132 133 134 //-- Initialisierung des Zufallszahlengenerators gsl_rng_env_setup(); T1 = gsl_rng_default; r1 = gsl_rng_alloc (T1); 135 136 137 138 139 140 141 //-- Initialisierung der GSL-Routine const gsl_odeiv_step_type *T = gsl_odeiv_step_rkf45; gsl_odeiv_step *s = gsl_odeiv_step_alloc(T, neq); gsl_odeiv_control *c = gsl_odeiv_control_y_new(atol, rtol); gsl_odeiv_evolve *e = gsl_odeiv_evolve_alloc(neq); gsl_odeiv_system sys = {f, jac, neq, &mu}; 142 143 144 for (m=0; m<N_ensemble; m++) { 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 //-- Anfangsbedingungen t = 0; x[0] = x_0[0]; x[1] = x_0[1]; x[2] = x_0[2]; x[3] = x_0[3]; x[4] = x_0[4]; x[5] = x_0[5]; x[6] = x_0[6]; x[7] = x_0[7]; for (n=1; n<=n_out; n++) { t1 = n * dt; while (t<t1) { int status = gsl_odeiv_evolve_apply(e, c, s, &sys, &t, t1, &h, x); if (status != GSL_SUCCESS) break; } rand = gsl_rng_uniform (r1); if (rand < x[0]) // Messergebnis: Niveau 1 { x[0] = 1; x[1] = 0; x[2] = 0; x[3] = 0; x[4] = 0; x[5] = 0; x[6] = 0; x[7] = 0; } else // Messergebnis: Niveau 2 { x[0] = 0; x[1] = 0; x[2] = 0; x[3] = 0; x[4] = 0; x[5] = 0; x[6] = 1; x[7] = 0; } gsl_vector_set(results,n-1,gsl_vector_get(results,n-1)+x[0]); } if (m % 100 == 0) cout << m << "\n"; } 169 170 171 172 for (n=0; n<n_out; n++) out_stream << n*dt+dt << " " << gsl_vector_get(results,n) / N_ensemble << "\n"; 173 174 175 176 177 gsl_odeiv_evolve_free(e); gsl_odeiv_control_free(c); gsl_odeiv_step_free(s); 243 244 178 179 5 Quantenmechanik out_stream.close(); } Das Ergebnis dieses Programms (die durchgezogene Linie in Abb. 5.10) sieht nun dem Resultat von Abschn. 5.13 schon etwas ähnlicher, es fällt jedoch sofort auf, dass die Wahrscheinlichkeit im Vergleich zu Abb. 5.8 viel langsamer und auch ohne die charakteristischen Oszillationen abnimmt: Abb. 5.10. Wahrscheinlichkeit, ein Zweiniveausystem bei fortlaufenden Messungen im Grundzustand zu finden. Bei der durchgezogenen Kurve erfolgten die Messungen im zeitlichen Abstand von 1, bei der gestrichelten Kurve von 10−1 und bei der gepunkteten Kurve im Abstand von 10−2 Der Grund ist, dass wir durch die Messungen die Dynamik des Systems beeinflussen – ein Effekt, den wir im vorangegangenen Abschnitt nicht berücksichtigt haben, da wir dort lediglich die Mastergleichung des Systems gelöst haben. Diese Lösung lieferte zwar die Wahrscheinlichkeit, mit der man das System nach einer Zeit t in einem gegebenen Zustand findet – jedoch unter der Voraussetzung, dass bis zu diesem Zeitpunkt keine Messungen am System stattgefunden haben. Besonders drastisch wird dieser Unterschied, wenn wir sehr viele, kurz aufeinander folgende Messungen am System vornehmen. In diesem Fall hat das System fast keine Zeit, aus dem Zustand, in den es durch die vorhergehende Messung projiziert wurde, in den anderen überzugehen und das System wird – lediglich durch die Messungen – quasi eingefroren. Diesen Sachverhalt spiegeln die beiden weiteren Kurven in Abb. 5.10 wieder, bei denen die Frequenz der Messungen gegenüber der durchgezogenen Kurve verzehnfacht (gestrichelte Kurve) bzw. verhundertfacht (gepunktet dargestellt) wurde. Wir wollen diesen Effekt – den sogenannten Zeno-Effekt – etwas näher beleuchten, und dabei eine analytische Lösung für den Grenzfall sehr kleiner Zeitabstände zwischen zwei aufeinander folgender Messungen herleiten. Wir bezeichnen mit n den Zustand nach und mit ˜n den Zustand unmittelbar vor der n-ten Messung. Unser erstes Ziel ist eine stroboskopische Beschreibung, die den Zusammenhang von n und n+1 angibt. Beginnen wir mit dem etwas langwierigeren Teil: dem Zusammenhang zwischen n und ˜n+1 . Dazu müssen wir die Mastergleichung über das – kurze – Zeitintervall ∆t lösen. Dabei berücksichtigen wir zunächst jedoch nur den Hamiltonschen Anteil der Bewegung – aus Gründen, die im Folgenden 5.15 Der Zeno-Effekt 245 sichtbar werden. ⇒ i ∂ = − [H, ] ∂t h̄ ∂ i 11 = + α (21 − 12 ) ∂t h̄ i ∂ 12 = − (∆E21 − α (22 − 11 )) ∂t h̄ i ∂ 21 = − (−∆E12 + α (22 − 11 )) . ∂t h̄ (5.136) (5.137) (5.138) (5.139) Wir differenzieren (5.137) nach der Zeit und setzen (5.138) und (5.139) ein: ∂2 2α2 α∆E = (12 + 21 ) . 11 2 (22 − 11 ) − ∂t2 h̄ h̄2 (5.140) Unter der Voraussetzung, dass die Nichtdiagonalelemente von nicht besetzt sind (was wir weiter unten klären werden), ist die Lösung dieses Differentialgleichungssystems für kleine Zeiten δt gegeben durch: α2 (n,22 + n,11 ) (δt)2 h̄2 α2 ˜n+1,22 (δt) = n,22 + 2 (n,22 + n,11 ) (δt)2 h̄ ˜n+1,11 (δt) = n,11 − (5.141) (5.142) Der entscheidende Punkt an dieser Stelle ist, dass die Änderung der Dichtematrixelemente von der Größenordnung (δt)2 ist. Der Einfluss der Messung liefert uns schließlich den Zusammenhang zwischen ˜n+1 und n+1 . Dieser ist zum Glück sehr einfach: n+1 setzt sich aus den Dichtematrizen 10 00 und (5.143) 00 01 zusammen, wobei wir deren statistische Gewichte ˜n+1,11 bzw. ˜n+1,22 berücksichtigen müssen. Wir erhalten also 10 00 ˜n+1,11 0 n+1 = ˜n+1,11 + ˜n+1,22 = (5.144) 00 01 0 ˜n+1,22 Die Messung vernichtet also quasi die Nichtdiagonalelemente, während die Diagonalelemente unangetastet bleiben. An dieser Stelle wird klar, warum wir bei der Zeitentwicklung davon ausgehen konnten, dass 12 und 21 beide anfänglich null sind, und warum wir uns bei der Lösung nicht für deren Werte nach der kurzen Zeit δt interessiert haben: ersteres garantiert die vorangegange Messung und die nachfolgende Messung macht diese beiden Matrixelemente ohnehin zu null. Fassen wir nun die beiden Teile – die Entwicklung über kurze Zeiten δt ((5.142) und (5.142)) und den Effekt der Messung (5.144) zusammen, erhalten wir: 246 5 Quantenmechanik n+1 = n,11 + ξ(22 − 11 ) 0 0 n,22 − ξ(22 − 11 ) . (5.145) Dabei haben wir die Abkürzung ξ= 2α2 2 (δt) h̄2 (5.146) eingeführt. Was passiert nun, wenn wir im Grundzustand starten und – wie in Abb. 5.10 – ein endliches Zeitintervall ∆t in sehr viele (N ) kleine Einheiten δt = ∆t/N zerteilen, nach denen jeweils eine Messung erfolgt? Nach der ersten Messung ist die Besetzung des angeregten Zustands ξ, nach der zweiten Messung 2ξ und so weiter. Entsprechend nimmt die Besetzung des Grundzustands ab. Nach N Messungen sind die beiden Diagonalmatrixelemente also 2α2 (∆t)2 h̄2 N 2 2α (∆t)2 = . h̄2 N 11 (∆t) = 1 − N ξ = 1 − (5.147) 22 (∆t) (5.148) = Nξ Wir sehen, dass in der Tat die Dynamik im Grenzfall N → ∞ eingefroren wird. Kommen wir nun noch zu der Frage, warum wir uns bei dieser Berechnung auf eine rein Hamiltonsche Zeitentwicklung beschränkt habe und eventuelle dissipative Terme in der Mastergleichung ausgeschlossen haben. Der Grund ist einfach: wenn Dämpfung vorliegt, gibt es im allgemeinen den Zeno-Effekt nicht. Berechnet man nämlich wie vorhin die Änderung von zwischen zwei Messungen, erhält man Terme, die proportional zu δt sind (und nicht wie in (5.146) proportional zu (δt)2 ). Diese führen dazu, dass die zu (5.147) und (5.148) entsprechenden Ausdrücke nicht für N → ∞ in 11 (t = 0) und 22 (t = 0) übergehen. Diese Terme proportional zu δt verschwinden jedoch, wenn der Ausgangszustand von der dissipativen Dynamik nicht berührt wird, wenn also ∂ (t = 0) =0 (5.149) ∂t irrev ist; und genau diese Situation liegt Abb. 5.10 zugrunde. 5.16 Ein Ein-Atom-Laser In diesem Beispiel wollen wir einen einfachen Laser betrachten, wobei wir sowohl das Lichtfeld als auch das lichtemittierende Material quantenmechanisch behandeln wollen. Um das Problem in einem handhabbaren Rahmen zu halten, beschränken wir uns bei letzterem auf ein einziges Atom – und reißen dabei interessanterweise ein Themengebiet an, das in den letzten Jahren ein 5.16 Ein Ein-Atom-Laser 247 aktuelles Forschungsgebiet war: der Ein-Atom-Laser [32, 33]. Wir beginnen unsere Überlegungen, indem wir das Zwei-Niveau-System aus Abschn. 5.13 durch Hinzunahme eines weiteren Niveaus zu einem Drei-Niveau-System erweitern: Abb. 5.11. Energieschema eines Drei-Niveau-Systems, bei dem vom Grundzustand in den höchsten Zustand angeregt wird und das System durch Ankopplung an ein Wärmebad von den angeregten Zuständen in niedrigere Zustände übergehen kann Die zugehörige Mastergleichung ist zwar länglich, wird jedoch durch den Vergleich mit ihrem Pendant bei zwei Energieniveaus (5.130) verständlich: ∂ i = − [Ĥ, ] ∂t h̄ γ21 + [Ŝ21 , Ŝ12 ] + [Ŝ21 , Ŝ12 ] + 2 γ31 + [Ŝ31 , Ŝ13 ] + [Ŝ21 , Ŝ12 ] + 2 γ23 + [Ŝ23 , Ŝ32 ] + [Ŝ23 , Ŝ32 ] + 2 (5.150) γ12 [Ŝ12 , Ŝ21 ] + [Ŝ12 , Ŝ21 ] 2 γ13 [Ŝ13 , Ŝ31 ] + [Ŝ12 , Ŝ21 ] 2 γ32 [Ŝ32 , Ŝ23 ] + [Ŝ32 , Ŝ23 ] , 2 wobei der Hamiltonoperator H durch die Eigenenergien und das Pumpen vom Grundzustand (1) in das oberste Energieniveau (2) bestimmt wird: ⎞ ⎛ E1 α 0 (5.151) Ĥ = ⎝ α E2 0 ⎠ . 0 0 E3 Die letzten drei Zeilen in (5.150) beinhalten zunächst den spontanen Übergang von Niveau 2 nach 1, dann von Niveau 3 nach 1 und schließlich von Niveau 2 nach 3. Die Operatoren Sij definieren wir analog zu (5.131): (Sij )nm = δim δjn . (5.152) Wenn Sie (5.150) ausschreiben und in einem Computerprogramm (auf der CD dreiniveau.cpp) implementieren, erhalten Sie für die Funktion f, die die Differentialgleichung festlegt, die folgenden Programmzeilen: 248 5 Quantenmechanik ... 23 //------------------------------------------------------------------------- 24 25 int f(double t, const double x[], double dxdt[], void *params) 26 27 28 29 30 { int n1, n2, n3, n, m, i, j; double rho[3][3][2], drho_dt[3][3][2]; double sum; 31 32 33 34 35 n = 0; for (n1=0; n1<3; n1++) for (n2=0; n2<3; n2++) for (n3=0; n3<2; n3++) {rho[n1][n2][n3] = x[n]; n++;} 36 37 38 39 40 41 42 43 //-Terme durch Eigenenergien for (n1=0; n1<3; n1++) for (n2=0; n2<3; n2++) { drho_dt[n1][n2][0] = -omega[n1][n2] * rho[n1][n2][1]; drho_dt[n1][n2][1] = omega[n1][n2] * rho[n1][n2][0]; } 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 for (n1=0; n1<2; n1++) { //-Terme durch Daempfung 2->1 drho_dt[0][0][n1] = drho_dt[0][0][n1] drho_dt[1][0][n1] = drho_dt[1][0][n1] drho_dt[0][1][n1] = drho_dt[0][1][n1] drho_dt[1][1][n1] = drho_dt[1][1][n1] drho_dt[2][1][n2] = drho_dt[2][1][n1] drho_dt[1][2][n1] = drho_dt[1][2][n1] //-Terme durch Daempfung 3->1 drho_dt[0][0][n1] = drho_dt[0][0][n1] drho_dt[2][0][n1] = drho_dt[2][0][n1] drho_dt[0][2][n1] = drho_dt[0][2][n1] drho_dt[2][2][n1] = drho_dt[2][2][n1] drho_dt[1][2][n1] = drho_dt[1][2][n1] drho_dt[2][1][n1] = drho_dt[2][1][n1] //-Terme durch Daempfung 2->3 drho_dt[2][2][n1] = drho_dt[2][2][n1] drho_dt[1][2][n1] = drho_dt[1][2][n1] drho_dt[2][1][n1] = drho_dt[2][1][n1] drho_dt[1][1][n1] = drho_dt[1][1][n1] drho_dt[0][1][n1] = drho_dt[0][1][n1] drho_dt[1][0][n1] = drho_dt[1][0][n1] } + gamma_12 -0.5*gamma_12 -0.5*gamma_12 gamma_12 -0.5*gamma_12 -0.5*gamma_12 * * * * * * rho[1][1][n1]; rho[1][0][n1]; rho[0][1][n1]; rho[1][1][n1]; rho[2][1][n1]; rho[1][2][n1]; + gamma_13 -0.5*gamma_13 -0.5*gamma_13 gamma_13 -0.5*gamma_13 -0.5*gamma_13 * * * * * * rho[2][2][n1]; rho[2][0][n1]; rho[0][2][n1]; rho[2][2][n1]; rho[1][2][n1]; rho[2][1][n1]; + gamma_32 -0.5*gamma_32 -0.5*gamma_32 gamma_32 -0.5*gamma_32 -0.5*gamma_32 * * * * * * rho[1][1][n1]; rho[1][2][n1]; rho[2][1][n1]; rho[1][1][n1]; rho[0][1][n1]; rho[1][0][n1]; 69 70 71 72 73 74 75 76 77 78 79 //-Terme durch Kopplung (z.B. durch drho_dt[0][0][0] = drho_dt[0][0][0] drho_dt[0][0][1] = drho_dt[0][0][1] drho_dt[0][1][0] = drho_dt[0][1][0] drho_dt[0][1][1] = drho_dt[0][1][1] drho_dt[1][0][0] = drho_dt[1][0][0] drho_dt[1][0][1] = drho_dt[1][0][1] drho_dt[1][1][0] = drho_dt[1][1][0] drho_dt[1][1][1] = drho_dt[1][1][1] drho_dt[0][2][0] = drho_dt[0][2][0] Pumpen) + alpha - alpha + alpha - alpha + alpha - alpha + alpha - alpha + alpha * * * * * * * * * (rho[1][0][1]-rho[0][1][1]); (rho[1][0][0]-rho[0][1][0]); (rho[1][1][1]-rho[0][0][1]); (rho[1][1][0]-rho[0][0][0]); (rho[0][0][1]-rho[1][1][1]); (rho[0][0][0]-rho[1][1][0]); (rho[0][1][1]-rho[1][0][1]); (rho[0][1][0]-rho[1][0][0]); rho[1][2][1]; 5.16 Ein Ein-Atom-Laser 80 81 82 83 84 85 86 drho_dt[0][2][1] drho_dt[1][2][0] drho_dt[1][2][1] drho_dt[2][0][0] drho_dt[2][0][1] drho_dt[2][1][0] drho_dt[2][1][1] = = = = = = = drho_dt[0][2][1] drho_dt[1][2][0] drho_dt[1][2][1] drho_dt[2][0][0] drho_dt[2][0][1] drho_dt[2][1][0] drho_dt[2][1][1] + + + alpha alpha alpha alpha alpha alpha alpha * * * * * * * 249 rho[1][2][0]; rho[0][2][1]; rho[0][2][0]; rho[2][1][1]; rho[2][1][0]; rho[2][0][1]; rho[2][0][0]; 87 88 89 90 91 n = 0; for (n1=0; n1<3; n1++) for (n2=0; n2<3; n2++) for (n3=0; n3<2; n3++) {dxdt[n] = drho_dt[n1][n2][n3]; n++;} 92 93 94 return GSL_SUCCESS; } 95 96 //------------------------------------------------------------------------- ... Gegenüber dem Programm zweiniveau.cpp haben wir die Darstellung der Dichtematrix geändert: der besseren Übersichtlichkeit fassen wir alle Elemente von in einem Feld zusammen, dessen erste beiden Indizes die Quantenzahlen angeben, während der letzte Index angibt, ob es sich um den Realteil oder den Imaginärteil handelt. Zunächst bestimmen wir in Zeile 37– 43 die Terme in den Bewegungsgleichungen, die von den Eigenenergien der atomaren Niveaus herrühren. Dazu definieren wir im Hauptprogramm eine Matrix omega, die die Energiedifferenzen zweier beliebiger Niveaus bereithält: ... 150 151 for (n1=0; n1<3; n1++) for (n2=0; n2<3; n2++) omega[n1][n2] = E[n2]-E[n1]; ... Anschließend finden Sie in den Zeilen 47 bis 68 alle Dämpfungsterme und schließlich die Terme durch den Pumpprozess. Beachten Sie, dass die Dämpfung z.B. von Niveau 2 nach 1 auch zu Termen in den Bewegungsgleichungen für 23 oder 32 führt (siehe Zeile 52/53) und entsprechendes gilt natürlich auch für die Dämpfung von Niveau 3 nach 1 bzw. von Niveau 2 nach 3. Um einen Laser zu realisieren, benötigen wir eine sogenannte Inversion, d.h. ein höheres Niveau ist – im Gegensatz zur Besetzung im thermischen Gleichgewicht – stärker besetzt als ein niedrigeres Niveau. In unserem Beispiel kommen hierfür nur die Niveaus 3 und 1 in Frage: Wir regen das System durch Pumpen vom Grundzustand in den angeregten Zustand 2 an. Bevor dieser Zustand (durch Pumpen oder durch die Dämpfung) wieder in den Grundzustand übergeht, kann das Atom durch den zweiten Dämpfungsterm in den Zustand 3 übergehen. Dieses Bevölkern des Zustandes 3 auf dem Umweg über 2 ist umso effektiver, je – – stärker das Pumpen von 1 nach 2 ist, stärker die Dämpfung von 2 nach 3 ist (erlaubter Übergang) 250 – 5 Quantenmechanik schwächer die Dämpfung von 3 nach 1 und von 2 nach 1 ist (verbotene Übergänge). Diese Bedingungen sind bei γ21 = γ31 = 0.01 γ23 = 1 (5.153) (5.154) gut erfüllt und ergeben das folgende Resultat: Abb. 5.12. Besetzung der drei Niveaus eines gedämpften Dreiniveau-Systems mit äußerer Anregung. Die Parameter sind so gewählt, dass sich im stationären Zustand eine Inversion zwischen Zustand 1 (Grundzustand, durchgezogene Kurve) und Zustand 3 (gestrichelt dargestellt) einstellt Zu Beginn befinden sich die Atome alle im Grundzustand (durchgezogene Linie) und werden – wie in Abschn. 5.13 durch das Pumpen in Niveau 2 (gepunktete Kurve) befördert. Neu hinzugekommen ist die Möglichkeit, von dort zum Niveau 3 (gestrichelte Kurve) zu gelangen, wo sie – bedingt durch die Wahl einer kleinen Dämpfungskonstante γ31 lange bleiben. Auf diese Weise sammeln sich die Atome quasi in Niveau 3 und wir erhalten die gewünschte Inversion relativ zum Grundzustand. Was uns nun noch zu einem Laser fehlt, ist die Ankopplung an eine Lichtmode. Mathematisch ist das elektromagnetische Feld des Lichts äquivalent zum harmonischen Oszillator, weswegen wir die Eigenzustände des Lichtfeldes wie beim harmonischen Oszillator in Abschn. 5.9 durch Angabe einer Quantenzahl n beschreiben können und die zugehörigen Eigenenergien durch 1 h̄ω (5.155) En = n + 2 gegeben sind. Verwenden wir diese Zustände als Basis, werden die Elemente der Dichtematrix außer durch die zwei Atomniveaus noch durch zwei Quantenzahlen für das Lichtfeld indiziert: ij ⇒ i,n;j,m . (5.156) 5.16 Ein Ein-Atom-Laser 251 i und j nummerieren die atomaren Niveaus und laufen bei unserem DreiNiveau-Atom von null bis drei, während n und m die Photonenzahl angeben und deswegen jede ganze Zahl größer gleich null annehmen können. An dieser Stelle führen wir Erzeugungs- und Vernichtungsoperatoren ein, die einen Zustand mit n Photonen in einen mit n + 1 bzw. n − 1 Photonen überführen (also ein Photon erzeugen bzw. vernichten): √ b+ |n = n + 1 |n + 1 (5.157) √ b|n = n |n − 1 . (5.158) Um die Bewegungsgleichung nun entsprechend zu erweitern, müssen wir drei verschiedene Terme berücksichtigen: – – – den Term durch die Energieniveaus des Lichtfelds (5.155), den Term durch Absorbtion / Emission eines Photons durch das Atom, die Terme durch die Dämpfung des Lichtfeldes. Beginnen wir mit der Dynamik durch die Energieniveaus (5.155) – wie immer in skalierten Einheiten, wobei wir die bislang noch offene Skalierung der Zeit so wählen, daß ω = 1 ist: ∂ i,n;j,m = i(n − m)i,n;j,m . ∂t (5.159) Der zweite Teil, der die Absorbtion bzw. Emission eines Photons beschreibt, ist ebenfalls Hamiltonsch: H = β S − b+ + S + b (5.160) √ ∂ 1,n;j,m = iβ n 3,n−1;j,m ⇒ (5.161) ∂t √ ∂ 3,n;j,m = iβ n + 1 1,n+1;j,m (5.162) ∂t √ ∂ i,n;3,m = −iβ m + 1 i,n;1,m+1 (5.163) ∂t √ ∂ i,n;1,m = −iβ m i,n;3,m−1 . (5.164) ∂t Der neu eingeführte Parameter β beschreibt die Kopplung des Atoms an das Lichtfeld, d.h. je größer dieser Wert ist, umso wahrscheinlicher ist der Übergang von Niveau 3 nach 1 (bzw. umgekehrt) unter Emission (bzw. Absorbtion) eines Photons. Abschließend benötigen wir noch die Mastergleichung für einen gedämpften harmonischen Oszillator, die dem eines atomaren Systems sehr ähnlich ist: ∂ (5.165) = γphot 2b+ b − b+ b − bb+ . ∂t Dabei haben wir die Dämpfungskonstante γphot für das Lichtfeld eingeführt. Im Programm sieht das Ganze dann so aus: 252 5 Quantenmechanik ... 42 43 44 45 46 47 48 49 50 51 52 53 //-Terme durch Eigenenergien for (n1=0; n1<3; n1++) for (n2=0; n2<3; n2++) for (n3=0; n3<nmax; n3++) for (n4=0; n4<nmax; n4++) { n = n3-n4; drho_dt[n1][n2][n3][n4][0] = -(n + omega[n1][n2]) * rho[n1][n2][n3][n4][1]; drho_dt[n1][n2][n3][n4][1] = (n + omega[n1][n2]) * rho[n1][n2][n3][n4][0]; } ... 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 //-Terme durch Emission/Absorbtion eines Photons for (n1=0; n1<nmax; n1++) for (n3=0; n3<3; n3++) { for (n2=0; n2<nmax-1; n2++) { drho_dt[2][n3][n2][n1][0] = drho_dt[2][n3][n2][n1][0] - beta * sqrt(n2+1.) * rho[0][n3][n2+1][n1][1]; drho_dt[2][n3][n2][n1][1] = drho_dt[2][n3][n2][n1][1] + beta * sqrt(n2+1.) * rho[0][n3][n2+1][n1][0]; drho_dt[n3][2][n1][n2][0] = drho_dt[n3][2][n2][n1][0] + beta * sqrt(n2+1.) * rho[n3][0][n1][n2+1][1]; drho_dt[n3][2][n1][n2][1] = drho_dt[n3][2][n2][n1][1] - beta * sqrt(n2+1.) * rho[n3][0][n1][n2+1][0]; } for (n2=1; n2<nmax; n2++) { drho_dt[0][n3][n2][n1][0] = drho_dt[0][n3][n2][n1][0] - beta * sqrt(float(n2)) * rho[2][n3][n2-1][n1][1]; drho_dt[0][n3][n2][n1][1] = drho_dt[0][n3][n2][n1][1] + beta * sqrt(float(n2)) * rho[2][n3][n2-1][n1][0]; drho_dt[n3][0][n1][n2][0] = drho_dt[n3][0][n2][n1][0] + beta * sqrt(float(n2)) * rho[n3][2][n1][n2-1][1]; drho_dt[n3][0][n1][n2][1] = drho_dt[n3][0][n2][n1][1] - beta * sqrt(float(n2)) * rho[n3][2][n1][n2-1][0]; } } 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 //-Terme durch Daempfung der Lichtmode for (n1=0; n1<3; n1++) for (n2=0; n2<3; n2++) for (n3=0; n3<nmax-1; n3++) for (n4=0; n4<nmax-1; n4++) for (n5=0; n5<2; n5++) drho_dt[n1][n2][n3][n4][n5] = drho_dt[n1][n2][n3][n4][n5] + gamma_phot*2.*sqrt(float((n3+1)*(n4+1)))*rho[n1][n2][n3+1][n4+1][n5]; for (n1=0; n1<3; n1++) for (n2=0; n2<3; n2++) for (n3=0; n3<nmax; n3++) for (n4=0; n4<nmax; n4++) for (n5=0; n5<2; n5++) drho_dt[n1][n2][n3][n4][n5] = drho_dt[n1][n2][n3][n4][n5] - gamma_phot*(n3+n4)*rho[n1][n2][n3][n4][n5]; ... 5.16 Ein Ein-Atom-Laser 253 Im Gegensatz zum Dreiniveau-Atom ohne Lichtfeld haben wir es nun mit einem unendlich-dimensionalen Hilbertraum zu tun, den das Programm laser.cpp natürlich beschneiden muss, was sich in der Beschränkung auf ein maximales nmax für die betrachtete Photonenzahl äußert. Dies birgt die Gefahr, dass man diesen Wert zu niedrig ansetzt und merkliche Anteile der Dichtematrix vernachlässigt werden. Um dies auszuschließen bauen wir in den Hauptteil zwei Kontrollen ein: Zum einen berechnen wir eine Größe n edge, die angibt wie stark der künstlich eingeführte Rand der Dichtematrix besetzt ist; zum anderen berechnen wir die Spur der Dichtematrix. Wenn alles korrekt ist, muss die erste Größe sehr klein bleiben, während die Spur der Dichtematrix eins sein muss: ... 251 252 253 254 255 256 257 258 259 260 261 262 263 264 n_phot = 0; n_edge = 0; n_1 = 0; n_2 = 0; n_3 = 0; for (n1=0; n1<nmax; n1++) { n_1 = n_1 + rho[0][0][n1][n1][0]; n_2 = n_2 + rho[1][1][n1][n1][0]; n_3 = n_3 + rho[2][2][n1][n1][0]; n_phot = n_phot + n1 * (rho[0][0][n1][n1][0]+rho[1][1][n1][n1][0] +rho[2][2][n1][n1][0]); } trace = n_1 + n_2 + n_3; out_stream << t << " " << n_1 << " " << n_2 << " " << n_3 << " " << n_phot << " " << n_edge << " " << trace << "\n"; } ... Als weitere Bedingung, die wir überprüfen sollten, bevor wir einem Ergebnis Glauben schenken, bietet sich die Tatsache an, dass alle Diagonalelemente der Dichtematrix positiv sein müssen, da sie als Wahrscheinlichkeiten interpretiert werden können. Abschließend müssen wir noch den Parametersatz für das DreiniveauAtom um Werte für die Kopplungskonstante β und die Dämpfung γphot ergänzen. Für (5.166) β=1 γphot = 0.05 (die restlichen Parameter sind gegenüber dem Drei-Niveau-Atom unverändert) erhalten wir Abb. 5.13. Beginnend bei t = 0 pumpen wir zunächst das Atom vom Grundzustand (dessen Besetzung in Abb. 5.13 gestrichelt dargestellt ist) über das Niveau 2 in das Niveau 3 (gepunktete Kurve in Abb. 5.13). Erst wenn dieses Niveau besetzt ist, erhalten wir durch spontane Emission Photonen (deren mittlere Zahl ist die durchgezogene Kurve in Abb. 5.13), was dann allerdings wieder zu einer Entvölkerung des Niveaus 3 und zu einer stärkeren Besetzung des Grundzustands führt. An dieser Stelle können wir auch qualitativ erklären, warum der Ein-Atom-Laser so viel wissenschaftliches Interesse gewonnen hat: Während in einem normalen Laser mit vielen Atomen zwei Photonen unmittelbar hintereinander in die Lichtmode eingekoppelt werden können, ist dies 254 5 Quantenmechanik Abb. 5.13. Besetzung der Niveaus 1 und 3 (gestrichelte bzw. gepunktete Linie), eines Drei-Niveau-Atoms, das an ein Lichtfeld gekoppelt ist, sowie die Photonenzahl im Lichtfeld (durchgezogene Kurve) bei einem Ein-Atom-Laser nicht möglich, da sich das einzige Atom nach der ersten Emission im Grundzustand befindet. Man hat also guten Grund, anzunehmen, dass die Statistik der Photonen beim Ein-Atom-Laser grundlegend anders ist als beim konventionellen Laser. Um dies genauer zu beleuchten, müssten wir leider tief in die Quantenoptik eintauchen – den Leser, der über entsprechende Kenntnisse verfügt, verweisen wir auf Übung 5.7. Übungen 5.1 Eigenzustände des Delta-Potentials Die Eigenzustände zu einem δ-förmigen Potential V (x) = aδ(x) können analytisch bestimmt werden. Gerade deswegen ist es aber ein gute Übung, diese numerisch zu berechnen und mit dem analytischen Ergebnis zu vergleichen. Verwenden Sie als Ausgangspunkt das Programm eigen1.cpp aus Abschnitt 5.9 und modifizieren Sie den Programmabschnitt, in dem das Potential festgelegt wird. Studieren Sie insbesondere die Abhängigkeit des Ergebnisses von den Parametern xmax und dx. 5.2 Eindimensionales Modell eines Atoms √ Das Potential V (x) = β/ 1 + αx2 kann als einfaches Modell für ein Atom dienen, wobei die Parameter α und β die Breite und die Tiefe des vom Atomkern erzeugten Potentials festlegen. Berechnen Sie für einige Parametersätze die gebundenen Zustände und deren Bindungsenergien. Letztere können Sie mit den Ergebnissen aus Abschn. 5.10 vergleichen. Interessant ist es auch, die freien Zustände für niedrige und für große Energien zu betrachten. Übungen 255 5.3 Eindimensionales Modell für H+ 2 Fügen Sie zwei der Einzelpotentiale aus der vorangegangenen Aufgabe zum Potential zweier Atomkerne im Abstand a zusammen und berechnen Sie wieder die Energieniveaus der gebundenen Zustände. Variieren Sie nun den Abstand a und bestimmen Sie die Grundzustandsenergie als Funktion dieses Parameters. Was Sie erhalten ist das effektive Potential, in dem sich (aufgrund der großen Masse der Atomkerne auf einer viel größeren Zeitskala) die beiden Kerne bewegen. Aus dem Potentialverlauf kann also sowohl der Abstand der beiden Kerne im Grundzustand als auch deren Schwingungsfrequenz entnommen werden. 5.4 Berechnung von Energieniveaus nach der Variationsmethode In Abschn. 5.10 haben wir die Eigenenergien eines Modellatoms (siehe auch Aufgabe 5.2) nach der Variationsmethode berechnet. Als Testfunktionen dienten uns dabei Funktionen vom Typ 1 fn = √ xn exp(−µx2 /2) , n! (5.167) wobei der Parameter µ fest gewählt wurde. Untersuchen Sie, was passiert, wenn Sie diesen Parameter verändern – insbesondere, wenn Sie ein sehr großes oder ein sehr kleines µ nehmen. Interessant ist es auch, mehrere solche Funktionensätze zu verschiedenen µ zu kombinieren und so einen größeren Satz an Testfunktionen zu haben, aus denen man die Eigenzustände und die dazugehörigen Eigenenergien berechnet. 5.5 Quantenmechanische Entropie Überlegen Sie sich, wie groß die in (5.120) definierte Entropie bei einem Zwei-Niveau- bzw. Drei-Niveau-Atom werden kann. Implementieren Sie nun eine Berechnung der Entropie in die Programme zweiniveau.cpp sowie dreiniveau.cpp und untersuchen Sie den Zeitverlauf der Entropie. 5.6 Zeno-Effekt Wenn Sie bei den Parametern zum Programm zeno2.cpp (auf der CD-ROM) die Zahl der Messungen im Zeitintervall [0 : 100] auf 20 herabsetzen, werden Sie feststellen, dass die Abnahme der Besetzung des angeregten Zustandes wieder langsamer erfolgt – entgegen dem allgemeinen Trend. Woran liegt das? Betrachten Sie zum Vergleich noch einmal Abb. 5.8! 256 5 Quantenmechanik 5.7 Ein-Atom-Laser Durch Spurbildung über die atomaren Freiheitsgrade kommen Sie von der Dichtematrix i,n;j,m aus Abschn. 5.16 auf eine Dichtematrix nur für das Atom: i,n;i,m . (5.168) n;m = i Aus dieser können Leser mit Kenntnissen in Quantenoptik (siehe z.B. [34]) die Quasiwahrscheinlichkeit Q(α) = 1 α||α π (5.169) berechnen. Dabei machen wir Gebrauch von den kohärenten Zuständen 1 2 αn √ |n . (5.170) |α = exp − |α| 2 n! n A Installation der Pakete unter Linux Diesem Buch liegt eine CD-ROM bei, auf der Sie alle für die numerische Physik nötigen Softwarepakete finden, so dass Sie nach deren Installation alle Programme dieses Buches ausprobieren und weiterentwickeln können. Dabei müssen je nach der von Ihnen getroffenen Software-Auswahl noch Anpassungen an den Programmen vorgenommen werden. Wenn Sie Linux installiert haben – und davon geht dieser Abschnitt aus – benötigen Sie noch folgende Programmpakete: – – – – – einen Compiler einer höheren Programmiersprache, einen Editor, in dem Sie die Quellprogramme schreiben können, eine Numerik-Bibliothek, die mathematische Standardaufgaben (z.B. die Lösung einer Differentialgleichung) übernimmt, ein Grafikprogramm zur Visualisierung der Ergebnisse, und ein Programm, mit dessen Hilfe die vom Grafikprogramm erzeugten Grafiken auf dem Bildschirm dargestellt werden können. Für jeden dieser Punkte finden Sie auf der CD-ROM mehrere zur Auswahl stehende Programme, z.B. entspricht jedes Verzeichnis in /linux/editors einem unter Linux lauffähigen Editor. Wenn Sie sich nun für einen Editor entschieden haben (selbstverständlich können Sie aber auch mehrere Editoren installieren) und sich den Inhalt des entsprechenden Verzeichnisses anschauen, so finden Sie dort typischerweise: – ein Verzeichnis src, in dem Sie die Quelltexte des betreffenden SoftwarePaketes finden, – ein Verzeichnis bin mit bereits kompilierten Binärversionen, – ein Verzeichnis doc mit dazu verfügbarer Dokumentation. Installation von rpm-Paketen Am Einfachsten gestaltet sich die Installation, wenn Sie eine auf dem Red Hat Package Manager (kurz rpm) aufbauende Linux-Distribution haben, wie z.B. Red Hat oder SUSE. In diesem Fall können Sie für die meisten Pakete auf ein passendes rpm-Paket im Unterverzeichnis bin zurückgreifen, z.B. gsl-1.3-155.i586.rpm Der Dateiname setzt sich aus dem Namen des Pakets (hier gsl), der Versionsund der Releasenummer (hier 1.3 und 155) und einem Zusatz zusammen, der hier anzeigt, dass es sich um die Binärversion für die Plattform i586 (IntelProzessoren ab dem Pentium oder kompatible Prozessoren wie der AMD Athlon) handelt. Zunächst sollten Sie überprüfen, ob das betreffende Paket bereits installiert ist, z.B.: 258 A Installation der Pakete unter Linux > rpm -q gsl gsl-1.3-0 Das Kommando rpm dient allgemein der Verwaltung der gleichnamigen Pakete. Mit der Option -q liefert es entweder eine Meldung, dass das Paket nicht installiert ist, oder es zeigt den Namen des installierten Pakets mit Versions- und Revisionsnummer (hier 1.3 und 0). Wenn das Paket nicht installiert oder die installierte Version veraltet ist, können Sie das Paket installieren, indem Sie als root das Kommando > rpm --install XXX bzw. > rpm --freshen XXX ausführen, wobei XXX der Dateiname des betreffenden Pakets ist. Alternativ können Sie die Installation aus dem Installationsmanager der jeweiligen Distribution heraus ausführen, z.B. yast2 bei SUSE. Sowohl bei dieser Vorgehensweise als auch bei der oben vorgestellten Installation aus der Kommandozeile heraus, wird die Installation direkt ausgeführt, sofern dies möglich ist. Unter Umständen kann es vorkommen, dass ein von dem zu installierenden Programm benötigtes Paket nicht installiert ist bzw. das zu installierende und ein bereits installiertes Paket nicht koexistieren können, was jeweils zu einer entsprechenden Fehlermeldung führt. In diesem Fall müssen Sie vor der Installation des gewünschten Software-Paketes das fehlende Paket installieren, bzw. ein störendes Paket deinstallieren (für das Entfernen eines Pakets ersetzen Sie die Option --install durch --erase). Installation von Debian-Paketen Die Installation bei Debian-orientierten Linux-Distributionen verläuft sehr ähnlich wie bei den eben besprochenen rpm-basierten Linux-Varianten. Allerdings kommt hier ein anderes Paket-Management zum Einsatz, so dass sowohl die Pakete als auch die Kommandos zur Installation ein anderes Format haben. Die Debian-Pakete erkennen Sie an der Endung .deb – ansonsten ist der Dateiname analog zu denen bei den rpm-Paketen aufgebaut, z.B. ist gsl-bin_1.1.1-1_i386.deb ein Debian-Paket der GSL-Library Version 1.1.1, Revision 1. Vor der Installation eines Paketes überprüfen Sie bitte, ob das betreffende Paket bereits installiert ist: dpkg --status gsl-bin A Installation der Pakete unter Linux 259 Das Kommando dpkg ist das Analogon zum Befehl rpm im vorhergehenden Abschnitt. Es dient der Installation, Deinstallation und Verwaltung der Programmpakete. Die Optionen für Installation bzw. Deinstallation lauten --install und --purge. Für Details verweisen wir auf die im pdfFormat beigelegte Einführung Dwarf ’s Guide to Debian GNU/Linux von Dale Scheetz. Installation durch Übersetzen der Quellprogramme Ist kein passendes rpm- bzw. Debian-Paket vorhanden, können Sie auf die Quelltexte im Unterverzeichnis src ausweichen. Dort finden Sie im Allgemeinen eine Datei mit einem Namen wie README, READ.ME oder LIESMICH. Diese enthält detaillierte Anweisungen, wie die Quelltexte entpackt, kompiliert und installiert werden müssen. In vielen Fällen liegen die Quelltexte zu einer neueren Version bereits vor, während sich die rpm-Pakete noch auf eine Vorgängerversion beziehen. Dokumentation Zu jedem Programmpaket existiert Dokumentation, die Sie im Unterverzeichnis doc finden. Diese kann in verschiedenen Formaten vorliegen, die Sie jeweils an Ihrer Endung erkennen können: – Bei rpm liegt die Dokumentation selbst im rpm-Format vor und muss entsprechend installiert werden. Nach der Installation befindet sich die Dokumentation in /usr/share/doc/packages/. – Die Endung ps kennzeichnet eine Postscript-Datei. Sie kann auf einem Postscript-fähigen Drucker direkt ausgedruckt oder z.B. mit ghostview am Bildschirm betrachtet werden. Wenn Ihr System sauber installiert ist, sollte ein Ausdruck auch dann möglich sein, wenn der Drucker nicht Postscript-fähig ist – in diesem Fall übersetzt ghostview die PostscriptBefehle in eine für den Drucker verständliche Form. – Ebenfalls mit ghostview oder z.B. mit dem Acrobat Reader von Adobe können die pdf-Dateien (Portable Data Format) betrachtet werden. – Bei htm bzw. html handelt es sich um Dokumente in der Hypertext Markup Language, die bei Internet-Seiten verwendet wird. Zur Darstellung ist daher jeder Browser geeignet. – dvi ist das Ausgabeformat von TEX/LATEX und kann, wenn diese Pakete installiert sind, entweder mit xdvi auf dem Bildschirm betrachtet oder mittels dvips, dvipdf oder dvihtml in Postscript, pdf bzw. html umgewandelt werden. – Die Endung txt kennzeichnet eine Text-Datei, die mit jedem Editor betrachtet werden kann. 260 A Installation der Pakete unter Linux Bei manchen Software-Paketen ist die Dokumentation nicht vom eigentlichen Programm getrennt, in diesem Fall erfolgt die Installation der Dokumentation in /usr/share/doc/packages/ automatisch, wenn Sie das Programm installieren. Hilfe im Internet Auf den Internetseiten des jeweiligen Software-Paketes finden Sie meistens weitergehende Hilfen. Interessant sind hierbei insbesondere die FAQs (Frequently Asked Questions), die Probleme behandeln, über die schon mehrere Anwender gestolpert sind und deswegen schon häufig gestellt wurden. Zu vielen umfangreicheren Paketen gibt es auch eine Newsgroup, in der über email ein reger Gedankenaustausch zu allen möglichen Aspekten der betreffenden Software stattfindet. Wenn Sie an dieser Newsgroup teilnehmen wollen, müssen Sie sich bei dieser anmelden – Hinweise dazu finden Sie im Allgemeinen auf der Homepage des Software-Paketes. An diese Newsgroup können Sie sich auch mit einem konkreten Problem wenden, das Sie trotz Zuhilfenahme der Dokumentation nicht lösen konnten. Es wird jedoch erwartet, dass man zunächst die bereits erwähnten FAQs durchforstet, bevor man die Hilfe der Newsgroup-Mitglieder in Anspruch nimmt. Da Newsgroup-Beiträge meistens archiviert werden, ist dieses Archiv eine weitere nützliche Informtionsquelle, die man vorab konsultieren sollte. B Installation der Pakete unter Windows Diesem Buch liegt eine CD-ROM bei, auf der Sie alle für die numerische Physik nötigen Softwarepakete finden, so dass Sie nach deren Installation alle Programme dieses Buches ausprobieren und weiterentwickeln können. In diesem Anhang gehen wir von einer lauffähigen Windows-Version aus, so dass Sie noch folgende Programmpakete benötigen: – – – – – – einen Compiler einer höheren Programmiersprache, einen Editor, in dem Sie die Quellprogramme schreiben können, eine Numerik-Bibliothek, die mathematische Standardaufgaben (z.B. die Lösung einer Differentialgleichung) übernimmt, ein Grafikprogramm zur Visualisierung der Ergebnisse, und ein Programm, mit dessen Hilfe die vom Grafikprogramm erzeugten Grafiken auf dem Bildschirm dargestellt werden können. Da viele Programmpakete in gepackter Form vorliegen, brauchen Sie darüber hinaus ein Programm, das diese Archive entpackt. Solche Archivierprogramme finden Sie im Verzeichnis \windows\archiver. Sie benötigen eigentlich nur unzip oder pkunzip, die Archive mit der Endung .zip entpacken können. Ich empfehle jedoch auch die Installation der entsprechenden Programme zip bzw. pkzip, die umgekehrt Dateien in ein Archiv packen können. Für jeden dieser Punkte finden Sie auf der CD-ROM eines oder mehrere zur Auswahl stehende Programme, z.B. entspricht jedes Verzeichnis in \windows\editors einem unter Windows lauffähigen Editor. Wenn Sie sich nun für einen Editor entschieden haben (selbstverständlich können Sie aber auch mehrere Editoren installieren) und sich den Inhalt des entsprechenden Verzeichnisses anschauen, so finden Sie dort typischerweise: – ein Verzeichnis src, in dem Sie die Quelltexte des betreffenden SoftwarePaketes finden, – ein Verzeichnis bin mit bereits kompilierten Binärversionen, – ein Verzeichnis doc mit dazu verfügbarer Dokumentation. Installation durch Entpacken Am Einfachsten gestaltet sich die Installation, wenn das zu installierende Programm nur aus einer ausführbaren Datei besteht. Wenn Sie dieses in ein Verzeichnis kopieren, das in der Umgebungsvariablen PATH enthalten ist, ist die Installation damit bereits abgeschlossen. In vielen Fällen besteht das Software-Paket aber aus mehreren Dateien, z.B. dem ausführbaren Programm, einer Bibliothek und zum Programm gehörenden Hilfetexten. In diesem Fall sind normalerweise alle diese Dateien in einem Archiv (meistens im 262 B Installation der Pakete unter Windows zip-Format) enthalten, das Sie auspacken müssen. Beachten Sie dabei, dass beim Entpacken jede Datei in das richtige Verzeichnis kommt – detaillierte Anweisungen finden Sie in der dazugehörigen Installationsanleitung, die Sie in einer Datei mit dem Namen README, READ.ME, LIESMICH o.Ä. finden. In diesen Anleitungen steht auch, ob und welche Umgebungsvariablen Sie deklarieren müssen. Besonders komfortabel gestaltet sich die Installation, wenn das Anlegen der Verzeichnisse und das Entpacken der Dateien von einem Installationsprogramm durchgeführt wird, das Sie interaktiv durch eine Installationsprozedur führt. Ein solches Programm nimmt auch die Deklaration von benötigten Umgebungsvariablen in der Systemkonfiguration vor. Installation von djgpp Am kniffligsten ist die Installation der GNU-Compiler und der dazugehörigen Numerik-Bibliotheken. Wenn Sie auf Probleme stoßen, sind die Homepage von djgpp (http://www.delorie.com/djgpp/) und die der betreffenden Bibliothek sicherlich hilfreich. Diese Compiler sind ursprünglich für Unix entwickelt worden und erfordern die Installation der Unix-ähnlichen Laufzeitumgebung djgpp. Eine Anleitung finden Sie auf der CD in der Datei \windows\djgpp\install.txt. Um Ihnen diese etwas langwierige Prozedur weitgehend zu ersparen, befindet sich auf der CD eine zip-Datei mit einer Art Komplettinstallation: djgpp komplett.zip. Diese Komplettinstallation umfasst die Basispakete von djgpp, nützliche Filterprogramme wie grep, Compiler für C/C++ sowie FORTRAN und die dazugehörigen NumerikBibliotheken. Legen Sie zunächst ein Verzeichnis an, in dem die kompletten djgpp-Pakete abgelegt werden sollen, z.B. C:\djgpp. Wechseln Sie in dieses Verzeichnis und entpacken Sie die zip-Datei mittels > pkunzip D:\windows\djgpp\bin\djgpp_komplett Hierbei haben wir angenommen, dass Ihrem CD-Laufwerk der Laufwerksbuchstabe D zugeordnet ist. Nun sollten Sie unter anderem im Verzeichnis C:\djgpp\bin sehr viele EXE-Dateien und namensgleiche Dateien ohne Endung haben, z.B. ls.exe und ls. Abschließend müssen Sie nur noch eine Umgebungsvariable setzen, bei Windows 95/98/ME können Sie das bewerkstelligen, indem Sie in der Datei autoexec.bat die Zeile set DJGPP=C:\djgpp\djgpp.env eintragen – wobei Sie den dort angegeben Pfad entsprechend modifizieren müssen, wenn das Hauptverzeichnis von djgpp bei Ihnen nicht C:\djgpp ist. Wenn djgpp erfolgreich installiert wurde, können Sie mit > bash B Installation der Pakete unter Windows 263 in einen Kommandomodus wechseln, der die Funktionalität der bourne-Shell aus der Unix-Welt bietet. Wenn Sie in diesem Modus ls eingeben, wird das oben genannte Programm C:\windows\djgpp\bin\ls ausgeführt. Dieses Programm erzeugt eine Auflistung aller Dateien im Verzeichnis, ganz ähnlich zu dem Ihnen bekannten Programm dir. Sie können aber auch direkt in der DOS-Box ls eingeben – in diesem Fall wird das Programm C:\windows\djgpp\bin\ls.exe aufgerufen, das die selbe Auflistung von Dateien ergibt. Wie schon oben erwähnt, gehören zum Gesamtpaket djgpp die GNUCompiler, unter denen sich neben dem C-Compiler auch ein C++- und ein FORTRAN-Compiler findet. Für den, der seine Programme lieber in einer Entwicklungsumgebung als im gewöhnlichen Editor schreibt, ist RHIDE interessant. Installation von Cygwin Alternativ zu djgpp, bietet sich unter Windows Cygwin an, das ebenfalls die Tools der Unix-Welt dem Windows-Anwender in einer Shell zur Verfügung stellt. Sofern man diese bei der Installation ausgewählt hat, gehören dazu auch die von uns benötigten GNU-Compiler, numerische Bibliotheken und Grafikprogramme. Letztlich bietet cygwin die selbe Funktionalität wie djgpp und offeriert zudem die Möglichkeiten einer graphischen Schnittstelle zum Anwender, indem es das X-Protokoll in der Windows-Umgebung umsetzt. Sehr angenehm ist die Tatsache, dass für Cygwin ein Installationsprogramm setup.exe existiert. Installation durch Übersetzen der Quellprogramme Auch unter Windows liegen nicht alle Software-Pakete fertig übersetzt vor, insbesondere für die von uns benötigten Numerik-Bibliotheken ist dies nicht der Fall. In diesem Fall müssen Sie auf die Quelltexte im Unterverzeichnis src ausweichen. Dort finden Sie im Allgemeinen eine Datei mit einem Namen wie README, READ.ME oder LIESMICH. Diese enthält detaillierte Anweisungen, wie die Quelltexte entpackt, kompiliert und installiert werden müssen. Dokumentation Die Dokumentation im Unterverzeichnis doc kann in verschiedenen Formaten vorliegen, die Sie jeweils an Ihrer Endung erkennen können: – Die Endung .ps kennzeichnet eine Postscript-Datei. Sie kann auf einem Postscript-fähigen Drucker direkt ausgedruckt oder z.B. mit ghostview am Bildschirm betrachtet werden. 264 B Installation der Pakete unter Windows – Ebenfalls mit ghostview oder z.B. mit dem Acrobat Reader von Adobe können die pdf-Dateien (Portable Data Format) betrachtet werden. – Bei .htm bzw. .html handelt es sich um die Hypertext Markup Language, die bei Internet-Seiten verwendet wird. Zur Darstellung ist daher jeder Browser geeignet. – Die Endung .txt kennzeichnet eine Text-Datei, die mit jedem Editor betrachtet werden kann. Bei manchen Software-Paketen ist die Dokumentation nicht vom eigentlichen Programm getrennt, in diesem Fall erfolgt die Installation der Dokumentation automatisch, wenn Sie das Programm installieren. Hilfe im Internet Wie bei Linux-Software finden Sie auch zu freier Windows-Software auf den Internetseiten des jeweiligen Software-Paketes weitergehende Hilfen. Hervorzuheben sind hier die sogenannten Newsgroups, in denen über e-mail ein reger Gedankenaustausch zu allen möglichen Aspekten der betreffenden Software stattfindet. Zu vielen umfangreicheren Paketen gibt es auch eine Newsgroup, in der über e-mail ein reger Gedankenaustausch zu allen möglichen Aspekten der betreffenden Software stattfindet. Insbesondere können Sie – nachdem Sie sich bei der betreffenden Newsgroup angemeldet haben – an andere Teilnehmer Fragen zu einem konkreten Problem stellen, das Sie trotz Zuhilfenahme der Dokumentation nicht lösen konnten. Um zu vermeiden, dass dabei immer wieder die selben Fragen gestellt werden, wurden die sogenannten FAQs (Frequently Asked Questions) zusammengstellt, die häufig gestellte Fragen und kompetente Antworten dazu beinhalten. Es wird erwartet, dass man zunächst diese FAQs durchforstet, bevor man die Hilfe der Newsgroup-Mitglieder in Anspruch nimmt. C Mathematische Bibliotheken Für die Lösung mathematischer Standardprobleme auf dem Computer gibt es — zum Glück — fertige Routinen, die in wir in unsere Programme einbinden können. Diese Routinen werden in Bibliotheken zusammengefasst, aus denen der Compiler sich beim Linken (dem Erzeugen der ausführbaren Datei) die jeweils benötigten Unterprogramme bzw. Funktionen holt. Dazu müssen wir beim Aufruf des Compilers diese Bibliothek explizit mit der Option -l angeben, z.B. verwendet > gpp -o faden_lib faden_lib.cpp -lgsl -lgslcblas -lm die Bibliotheken GSL, GSLCBLAS und M. Unter Unix haben die zugehörigen Dateien die Namen libgsl.a, libgslcblas.a und libm.a und befinden sich im Verzeichnis /usr/lib. Von diesen Bibliotheken wollen wir in diesem Anhang einige vorstellen — ohne Anspruch auf Vollständigkeit. Am Anfang dieser Zusammenstellung stehen Bibliotheken, die mehr oder weniger alle numerischen Standardprobleme abdecken, während auf einen bestimmten Problemkreis spezialisierte Bibliotheken im Anschluss vorgestellt werden. IMSL IMSL steht für International Mathematics Standard Library und es gibt diese Bibliothek für alle erdenklichen Plattformen (auch für Linux und in einer für FORTRAN und einer für C gedachten Variante). Sie finden in IMSL praktisch für jedes Problem eine geeignete Routine, die Vielfalt kann durchaus schon verwirrend sein. Da IMSL ein kommerzielles Produkt von Visual Numerics ist, kann es nicht auf der CD zur Verfügung gestellt werden. NAG NAG steht für Numerical Arithmetics Group, und bei deren gleichnamiger Numerik-Bibliothek handelt es sich ebenfalls um ein kommerzielles Produkt, das das gesamte Spektrum numerischer Probleme abdeckt. Auch diese Bibliothek gibt es in einer FORTRAN- und einer C-Variante: NAG bzw CNAG. GSL GSL steht für GNU Scientific Library und hat den Anspruch, eine frei verfügbare Alternative zu IMSL zu sein. Im Gegensatz zu IMSL steht GSL 266 C Mathematische Bibliotheken allerdings nur für C++ zur Verfügung. Konzeptionell geht GSL eigene Wege, so dass beim Umstieg von einer anderen Bibliothek eine gewisse Umgewöhnungsphase nötig ist. Danach wird man jedoch dieses modulare Konzept zu schätzen wissen. Obwohl GSL bereits sehr umfangreich ist und kaum mehr Wünsche offen lässt, wird an dieser Bibliothek noch sehr eifrig weiterentwickelt. Schon aus diesem Grund lohnt sich ein Blick auf die Homepage http://sources.redhat.com/gsl/, wo man auch Links zu den Mailinglisten findet. SLATEC SLATEC ist eine zwar schon etwas ältere, aber sehr umfangreiche FortranBibliothek, die ebenfalls alle Gebiete abdeckt. Aufgrund seines Umfangs ist SLATEC die erste Wahl, wenn man eine frei verfügbare Fortran-Bibliothek sucht. Wie viele andere frei verfügbaren Bibliotheken finden Sie sie auf http://gams.nist.gov/. MATPACK MATPACK ist ein sehr junges Projekt der Technischen Universität München. Wie GSL ist MATPACK für den Einsatz in C++-Programmen vorgesehen. Im Gegensatz zu GSL verfolgt MATPACK einen eher konventionellen Ansatz und ist daher für Umsteiger einfacher zu handhaben. Da MATPACK erst am Anfang seiner Entwicklung steht, ist der Umfang der Bibliothek eher gering. Wer an dieser Bibliothek interessiert ist, sollte die Weiterentwicklung auf der Internetseite http://www.matpack.de/ verfolgen. CERNLIB CERNLIB ist in dieser Liste die letzte Bibliothek, die alle numerischen Problemstellungen abdeckt. Ursprünglich wurde CERNLIB für die numerischen Problemstellungen im Zusammenhang mit Hochenergiephysik entwickelt – das Ergebnis ist eine ausgesprochen umfangreiche Bibliothek, die für alle physikalischen Disziplinen geeignet ist. Auf der Internetseite http://wwwinfo.cern.ch/cernlib/overview.html. finden Sie die jeweils aktuelle Version der CERNLIB sowie Dokumentation. BLAS und CBLAS BLAS steht für Basic Linear Algebra System und beinhaltet Routinen rund um Matrizen- und Vektorrechnung. Solche Routinen werden auch in einer C Mathematische Bibliotheken 267 ganzen Reihe anderer Probleme benötigt (z.B. zur Lösung nichtlinearer Gleichungssysteme) und da BLAS einen de-facto-Standard darstellt, greifen die meisten Mathematikbibliotheken auf BLAS zurück. In diesem Zusammenhang ist es interessant, dass es mehrere Implementationen von BLAS (mit einem definierten FORTRAN-Interface) und CBLAS (mit einem definierten C-Interface) gibt, die sich in der Performance erheblich unterscheiden können. Die Verwendung einer gut optimierten BLASbzw. CBLAS-Bibliothek kann die Geschwindigkeit eines Programms deutlich erhöhen, selbst wenn man direkt keine Routinen zur Linearen Algebra aufruft. Dem Leser sei insbesondere ein Blick auf das ATLAS-Projekt (http://math-atlas.sourceforge.net/) und – sofern ein Intel-Prozessor eingesetzt wird – die Intel Math Kernel Library (zu finden auf der InternetSeite http://developer.intel.com/software/products/mkl/index.htm) ans Herz gelegt. FFTPACK, FITPACK, HOMPACK, ITPACK, MINPACK, . . . Hier handelt es sich um eine ganzen Reihe von Einzelbibliotheken, deren Name fast immer mit PACK endet und die frei verfügbar sind (MATPACK gehört nicht in diese Kategorie, dieses ist ein Paket, das das gesamte Spektrum der Numerik abdecken will). Sie finden diese Pakete auf http://www.netlib.org/XXX/index.html, wobei XXX durch den Namen des Pakets (klein geschrieben) ersetzt werden muss. In diesem Zusammenhang ist auch die Internetseite http://gams.nist.gov/ interessant, die eine Zusammenstellung von Numerikbibliotheken mit einer Kurzbeschreibung bietet. Alle diese Bibliotheken liegen als eine Sammlung von Quellprogrammen vor, die erst übersetzt und dann zu einer Bibliothek zusammengebaut werden müssen. LAPACK LAPACK dient der Lösung von Aufgaben aus der Linearen Algebra. Im Gegensatz zu den vorher besprochenen Bibliotheken, die ebenfalls auf PACK enden, gibt es von LAPACK fertige rpm- und Debian-Pakete. Neben der ursprünglichen, für FORTRAN ausgelegten Variante von LAPACK gibt es eine C- und eine C++-Version namens CLAPACK bzw. LAPACK++. D Fortran und C Wie bereits in der Einleitung dieses Buches festgestellt wurde, werden in der numerischen Physik hauptsächlich zwei Programmiersprachen eingesetzt: C/C++ und FORTRAN. Für welche dieser beiden Sprachen man sich entscheidet, hängt von einer Vielzahl von Faktoren ab: – Die Betreuungssituation am jeweiligen Institut: welche Programmiersprache verwenden der Betreuer und die übrigen Institusmitglieder? – Der Fundus an bereis vorhandenen Quellprogrammen, auf die zurückgegriffen werden kann. – Die Bedeutung der Programmiersprache im späteren Berufsleben spricht für C/C++. – Die Implementierung von komplexen Zahlen ist ein klarer Vorteil von FORTRAN. – C/C++ hat dafür Vorteile bei systemnahen Aufgaben. Dazu gehören das Anlegen und Löschen von Dateien und Vereichnissen ebenso wie Eingaben über die Tastatur und Ausgabe auf den Bildschirm. In den meisten Situationen wird es Argumente sowohl für C/C++ als auch für FORTRAN geben. In diesem Fall ist es interessant, dass Programmteile in C/C++ und solche in FORTRAN miteinander verbunden werden können. Das ist nicht weiter schwierig, wenn man einige kleinere Fallstricke kennt. Der erste Fallstrick besteht darin, dass FORTRAN-Compiler an alle Namen von Programmen oder Funktionen einen Unterstrich anhängen, während C das nicht macht. Wenn wir also eine C-Routine aus FORTRAN aufrufen wollen, müssen wir im C-Programm diesen Unterstrich explizit an den Namen der Routine anhängen, z.B. void routine1 (). Der nächste Fallstrick liegt in der Übergabe von Parametern, wobei wir zunächst mit einfachen Datentypen, also ganzen oder reellen Zahlen beginnen wollen. FORTRAN übergibt solche Parameter grundsätzlich by reference, d.h. wenn Fortran eine Routine gemäß call unterprogramm(a) aufruft, bekommt das Unterprogramm nicht den Wert der Variablen a, sondern dessen Adresse im Speicherbereich. Das Unterprogramm kann dann nicht nur den Wert der Variablen vor dem Aufruf verwenden (in diesem Fall handelt es sich um eine Eingabevariable), sondern auch deren Wert verändern (in diesem Fall spricht man von einer Ausgabevariablen). Springt man dann aus dem Unterprogramm wieder ins Hauptprogramm zurück, bleiben alle Änderungen von Variablen, die im Unterprogramm vorgenommen wurden, erhalten. C hingegen übergibt Variablen grundsätzlich by value, d.h. die Unterprogramme erhalten nur eine lokale Kopie der Übergabeparameter. Ändert die C-Routine nun den Wert dieser lokalen Kopie, sind solche Änderungen nach 270 D Fortran und C der Rückkehr ins Hauptprogramm verloren. Jetzt stellt sich natürlich die Frage, wie man es in C überhaupt realisiert, dass Übergabeparameter dauerhaft geändert werden (so etwas muss ja möglich sein!). Der Trick besteht darin, der Routine nicht die Variable selbst zu übergeben, sondern explizit die Speicheradresse (also das, was FORTRAN automatisch macht). Mit diesen beiden Punkten im Hinterkopf lässt sich ein einfaches Beispiel realisieren. Zunächst das aufrufende FORTRAN-Programm: 1 2 3 ****************************************************************** * FORTRAN-Programm, das eine C-Routine aufruft * ****************************************************************** 4 5 program fc1_f 6 7 real*8 x 8 9 10 11 x = 0.d0 call fc1c(x) write(6,*) x 12 13 14 STOP end Und nun die C-Routine: 1 2 3 /*------------------------------------------------------------C-Routine, die von einem FORTRAN-Programm aufgerufen wird -------------------------------------------------------------*/ 4 5 void fc1c_(double *x) 6 7 8 9 10 11 { printf(" %f\n", *x); *x = 1.; return; } Das Hauptprogramm in FORTRAN definiert eine doppelt genaue Zahl x und setzt diese auf null. Danach ruft es eine C-Routine namens fc1c auf. Im C-Programm sehen wir, dass hier der Name fc1c lautet und der Übergabeparameter nicht einfach x wie im FORTRAN-Programm ist, sondern der Zeiger auf x. Die C-Routine gibt den anfänglichen Wert von x auf den Bildschirm aus und setzt ihn danach auf 1. Nach der Rückkehr ins Hauptprogramm wird wieder der Wert von x ausgegeben. Um dieses Programm zu testen, übersetzen wir zunächst die C-Routine: > gcc -c fc1_c.c Die Option -c bewirkt, dass das Programm nur übersetzt, nicht aber gelinkt wird (also kein ausführbarer Code erzeugt wird). Nach diesem Kompiliervorgang haben wir eine Object-Datei fc1 c.o, die wir bei der Übersetzung des FORTRAN-Programms angeben müssen: > g77 -o fc1_f fc1_f.f fc1_c.o D Fortran und C 271 Dies erzeugt nun ein ausführbares Programm fc1 f, und wenn wir dieses aufrufen, erhalten wir das erwartete Resultat: > fc1_f 0.000000 1. Wie sieht es nun aus, wenn wir keine einfachen Datentypen übergeben, sondern Arrays (also Vektoren oder Matrizen)? Erstaunlicherweise ist das sogar einfacher, weil in C ein Array ein Zeiger auf das erste Element dieses Arrays ist. Dadurch ist das Verhalten von FORTRAN und C in diesem Fall identisch. Aufpassen muss man lediglich bei der Indizierung der einzelnen Elemente: – In FORTRAN hat das erste Element standardmäßig den Index 1, was jedoch auf jeden beliebigen anderen Wert geändert werden kann – in C hingegen hat das erste Element immer den Index 0. – Bei Arrays mit mehreren Indizes ändert FORTRAN zunächst den ersten Index, während C mit dem letzten Index anfängt. Ein Feld x mit 2 × 2 Elementen wird also bei FORTRAN in der Reihenfolge x(1,1), x(2,1), x(1,2), x(2,2) und in C in der Reihenfolge x[0][0], x[0][1], x[1][0], x[1][1] abgespeichert. Auch hier sagt ein Beispielprogramm mehr als längliche Ausführungen: 1 2 3 ****************************************************************** * FORTRAN-Programm, das eine C-Routine aufruft * ****************************************************************** 4 5 program fc1_f 6 7 real*8 x(2,2) 8 9 10 11 12 13 do n1 = 1, 2 do n2 = 1, 2 x(n1,n2) = -n1-2*n2 enddo enddo 14 15 call fc2c(x) 16 17 18 19 20 21 do n1 = 1, 2 do n2 = 1, 2 write(6,*) n1, n2, x(n1,n2) enddo enddo 22 23 24 STOP end Dieses FORTRAN-Programm ruft eine in C geschriebene Routine auf: 1 2 3 /*------------------------------------------------------------C-Routine, die von einem FORTRAN-Programm aufgerufen wird -------------------------------------------------------------*/ 272 D Fortran und C 4 5 6 void fc2c_(x) double x[2][2]; 7 8 9 { int n1, n2; 10 11 12 13 14 15 for (n1=0; n1<2; n1++) for (n2=0; n2<2; n2++) { printf(" %i %i %f\n", n1, n2, x[n1][n2]); x[n1][n2] = n1+2*n2; } 16 17 18 return; } Die Übersetzung der beiden Programmteile erfolgt analog zum vorhergehenden Beispiel und die Ausführung des Gesamtprogramms liefert: > fc2_f 0 0 -3.000000 0 1 -4.000000 1 0 -5.000000 1 1 -6.000000 1 1 0. 1 2 1. 2 1 2. 2 2 3. Die ersten vier Zeilen sind die Ausgabe der C-Routine, d.h. die Indizierung beginnt C-gemäß jeweils bei null, die Werte sind jedoch noch diejenigen, die der Matrix im Hauptprogramm zugewiesen wurden, also z.B. für das erste Element −1 − 2 × 1 = −2 (die Indizes sind bei FORTRAN ja jeweils 1). Bei den letzten vier Zeilen verhält es sich genau umgekehrt: die Indizes entsprechen dem FORTRAN-Standard (beginnen also jeweils bei 1), die Werte sind jedoch noch aus der C-Routine, beginnen also mit 0 + 2 × 0 = 0. Außerdem erkennen sie in den mittleren beiden Zeilen dieses Blocks die Verdrehung, die durch die bereits erwähnte bei FORTRAN und C verschiedene Reihenfolge im Abspeichern kommt. Ansonsten würde man ja bei n1=1 und n2=2 als Wert (1 − 1) + 2 × (2 − 1) = 2 erwarten, und entsprechend sollte in der vorletzten Zeile (2 − 1) + 2 × (1 − 1) = 1 stehen. Bis jetzt haben wir uns auf C-Funktionen vom Typ void beschränkt, die aus FORTRAN-Sicht wie eine subroutine aufgerufen werden. C-Funktionen hingegen, die einen Wert zurückgeben, entsprechen in FORTRAN einer function und werden dementsprechend eingesetzt. Auch hierzu wieder ein kleines Beispiel: 1 2 3 ****************************************************************** * FORTRAN-Programm, das eine C-Routine aufruft * ****************************************************************** 4 5 program fc3_f 6 7 real*8 x, y, func D Fortran und C 273 8 9 10 11 x = 1.d0 y = func(x) write(6,*) x, y, sin(x) / x 12 13 14 STOP end Dieses Programm ruft eine C-Funktion func auf, sin(x)/x berechnet: 1 2 3 /*------------------------------------------------------------C-Routine, die von einem FORTRAN-Programm aufgerufen wird -------------------------------------------------------------*/ 4 5 #include <math.h> 6 7 double func_(double *x) 8 9 10 { double y; 11 12 13 14 y = sin(*x) / (*x); return y; } Zum Vergleich gibt das aufrufende FORTRAN-Programm in Zeile 11 nicht nur den Wert von f(x) aus, sondern berechnet selbst sin(x)/x für x = 1. Anhand der Ausgabe > fc3_f 1. 0.841470985 0.841470985 können wir uns davon überzeugen, dass beide Werte übereinstimmen. E Filterprogramme Filter nennt man in der Informatik einfache, kleine Programme, die eine immer wieder auftretende Aufgabe so lösen, dass das Programm ein möglichst breites Einsatzgebiet hat. Besondere Bedeutung haben solche Filter unter Unix gewonnen, da hier auf der Kommandozeile die Ausgabe eines Kommandos direkt als Eingabe für ein neues Kommando verwendet werden kann, was man als Verkettung bezeichnet. Am Verständlichsten wird das an dem folgenden Beispiel. Das Filterprogramm head gibt die ersten Zeilen der übergebenen Datei an: > head faden_lib.cpp /************************************************************************** * Name: faden_lib.cpp * * Zweck: Simuliert die Dynamik eines Fadenpendels * * Gleichung: (dˆ2/d tˆ2) phi + sin(phi) = 0 * * verwendete Bibiliothek: GSL * **************************************************************************/ #include <iostream> #include <fstream> #include <gsl/gsl_matrix.h> Die oben erwähnte Möglichkeit der Verkettung von Kommandos erlaubt auch, die Ausgabe eines beliebigen Programms auf die ersten Zeilen zu beschränken, z.B. zeigt der folgende Aufruf die zehn neuesten Dateien: ls -t | head Der vordere Teil ls -t liefert eine chronologische Liste der Dateien, die mittels des sogenannten Pipe-Symbols | in den zweiten Teil head umgelenkt wird. Dieser zweite Teil filtert nun aus der Dateiliste die ersten zehn Zeilen heraus, die – wegen deren chronologischer Ordnung – die zehn neuesten Dateien enthalten. Hinter diesen Filtern steht eine Philosophie, die konträr zur Philosophie von DOS-Kommandos ist, nämlich dass solche Aufgaben nicht durch Optionen für das eigentliche Kommando (in diesem Fall ls) zu lösen sind, sondern durch universell einsetzbare Filter. Es gibt zwei Gründe, warum wir diesen Filtern einen speziellen Abschnitt widmen: Zum einen gibt es eine ganze Reihe von Standardfiltern, deren Kenntnis einem auch bei der Auswertung von Ergebnisdateien helfen kann. In diesem Zusammenhang ist es sicherlich hilfreich, dass wir diese Filter hier – im Gegensatz zu Informatik-Lehrbüchern – speziell im Umfeld zu numerischer Physik vorstellen. Zum anderen finden Sie auf der CD einige spezielle Filter für numerische Probleme, die ich Ihnen zur Verfügung stellen möchte. Sollte Ihnen bei einem der Standardfilter entfallen sein, wie er verwendet wird, erhalten Sie bei den meisten eine Hilfestellung, wenn Sie den Filter mit der Option --help aufrufen. UNIX-Anwendern stehen darüber hinaus die manual pages zur Verfügung, die man durch den Aufruf 276 E Filterprogramme man head angezeigt bekommt. Bei den von mir selbst entwickelten Filtern erfolgt eine Kurzerklärung der Syntax, wenn Sie den Filter nicht mit der richtigen Zahl an Argumenten, also z.B. ganz ohne Argumente, aufrufen. Die Standardfilter more und less Der Filter more wird sehr häufig gebraucht – nämlich immer dann, wenn die Ausgabe eines Kommandos nicht auf eine Bildschirmseite passt und man verhindern muss, dass die Ausgabe vorbeirauscht, bevor man Zeit hatte sie zu lesen. Genau dies leistet nämlich dieser Filter: die Ausgabe wird angehalten, sobald eine Bildschirmseite voll ist, bis man durch Drücken der Leertaste signalisiert, dass man den Inhalt zur Kenntnis genommen hat und man zur nächsten Seite wechseln möchte. Das zuerst entwickelte more hatte einen Nachteil: man konnte in der Bildschirmanzeige immer nur vorwärts blättern, nicht aber wieder zurück. Dieses Manko behebt der Nachfolger, der ironischerweise less getauft wurde: mittels der Tasten Page Up und Page Down lässt sich beliebig in der Ausgabe hin und herblättern. Darüber hinaus ermöglicht less auch die Suche nach einer Zeichenkette. Die Standardfilter head und tail Den Filter head haben wir bereits in der Einleitung zu diesem Abschnitt kennen gelernt. Er hat eine Reihe von Optionen, von denen aber zumindest ich immer nur eine gebraucht habe: > head -8 gibt nur die ersten 8 (statt standardmäßig 10) Zeilen des Arguments aus. Alternativ hat head --lines=8 denselben Effekt. Der Filter tail ist das Pendant zu head und konzentriert sich anstatt auf den Anfang des Arguments auf dessen Ende. Der Aufruf ls -t | tail liefert also die 10 ältesten Dateien. Die Optionen von tail sind zu denen von head völlig analog, so dass z.B. tail -50 die letzten 50 Zeilen liefert. Beide Filter, head und tail, können nicht nur dazu herangezogen werden, den Anfang bzw. das Ende großer Ergebnisdateien schnell anzuschauen (ohne die gesamte Datei erst in einen Editor zu laden), sondern können auch dazu verwendet werden, ohne großen Aufwand die Ausgabe eines Programms auf deren Anfang bzw. Ende zu beschränken, was insbesondere bei der Fehlersuche hilfreich ist. E Filterprogramme 277 Der Standardfilter grep Der Filter grep durchsucht sein Argument auf Zeilen, die eine bestimmte Zeichenkette enthalten, z.B. liefert > grep "#" faden_lib.res alle Zeilen der Ergebnisdatei faden lib.res, die ein # enthalten – also jene Zeilen, die die Programmparameter enthalten. Dieses Programm können sie z.B. zur Aufbereitung von Datensätzen verwenden, bei denen die Bedeutung der einzelnen Werte durch Schlüsselworte festgelegt wird, z.B. T=0.0000 X=0.0000 V=1.0000 A=0.0000 ============= T=1.0000 X=1.5000 V=2.0000 A=1.0000 ============= ... In diesem Fall liefert Ihnen > grep "T=" result.dat > result_t.dat aus der ursprünglichen Datei result.dat eine neue Datei result t.dat, die nur noch die Zeitinformationen enthält. Interessant ist die Option -v, die die Suchbedingung quasi invertiert. > grep -v "#" faden_lib.res liefert also alle Zeilen der Ergebnisdatei faden lib.res, die kein # enthalten. Der Standardfilter cut Kommen wir auf das Beispiel im vorigen Abschnitt zurück, in dem wir mittels grep aus der Datei result.dat die Datei result t.dat erzeugt haben. Diese sieht nun etwa so aus: T=0.0000 T=1.0000 T=2.0000 ... Störend bei der Weiterverarbeitung sind für die meisten Grafikprogramme die Zeilenanfänge T=; eigentlich brauchen wir nur die Zahlen selbst. Diese Aufgabe können wir mit dem Filter cut lösen, der aus Zeilen einen bestimmten Teil herausschneidet. Ein sogenannter Begrenzer (englisch Delimiter) legt fest, wo eine Spalte endet und die nächste anfängt: in unserem Beispiel können wir das Gleichheitszeichen = als Delimiter festlegen, dann steht in der ersten Spalte immer der Buchstabe T und in der zweiten Spalte (auf der anderen Seite des Delimiters) die numerischen Werte der Zeiten. Der Aufruf 278 E Filterprogramme > cut -d"=" -f2 result_t.dat > result2_t.dat liefert uns also eine Datei, die nur noch die Zeiten selbst – ohne voranstehenden Bezeichner – enthält. Die Option -d legt dabei den Delimiter fest, während die Option -f2 besagt, dass lediglich die zweite Spalte ausgegeben werden soll. Der Standardfilter wc Das Kürzel wc steht für word count, also Wortzähler. Entgegen dieser Bedeutung liefert dieser Filter jedoch nicht nur die Zahl der Wörter, sondern auch die der Buchstaben und die der Zeilen: > wc faden_lib.cpp 127 399 3505 faden_lib.cpp Die erste Zahl ist die Zahl der Zeilen, die zweite die Zahl der Wörter und schließlich folgt die Zahl der Buchstaben. Durch die Optionen -m (für Buchstaben), -w (für Wörter) und -l (für Zeilen) lässt sich die Ausgabe auf einen der drei Werte einschränken. Dieser Filter ist zum einen hilfreich, wenn Sie sich schnell einen Überblick über den Umfang eines Datenbestandes machen wollen. In dem Beispiel von oben können wir mittels dieses Kommandos auch überprüfen, ob der Ausgangsdatensatz vollständig ist, und ob unsere Verarbeitung bis hin zur Datei result t2.dat korrekt war. Für die Überprüfung der Vollständigkeit geben wir die Kommandos > grep "T=" 1000 > grep "X=" 1000 > grep "V=" 1000 > grep "A=" 999 result.dat | wc -l result.dat | wc -l result.dat | wc -l result.dat | wc -l ein. Wenn – wie hier – die Ergebnisse nicht alle übereinstimmen, ist mindestens einer der Blöcke mit Zeit, Ort, Geschwindigkeit und Beschleunigung nicht komplett. In diesem Beispiel fehlt in einem Block die Beschleunigung. Um zu überprüfen, ob die Weiterverarbeitung korrekt verlief, verwenden wir wieder den Filter wc: > wc -l result_t2.dat 1000 Das Ergebnis muss mit dem entsprechenden Ergebnis der Ausgangsdatei result.dat übereinstimmen, sonst sind irgendwo Zeilen verlorengegangen. E Filterprogramme 279 Der Standardfilter sort In vielen Fällen sind Ergebnisdateien nicht so sortiert, wie dies für eine bestimmte Darstellung erforderlich wäre. In einem Beispiel, das mit dem bisher besprochenen eng verwandt ist, haben wir eine Datei res.dat mit vier Spalten, in denen Zeit, Ort, Geschwindigkeit und Beschleunigung eines Teilchens festgehalten sind: 0.0000 0.0000 1.0000 0.0000 1.0000 1.5000 2.0000 1.0000 ... Diese Datei hat bereits das Format, mit dem jedes Grafikprogramm zurechtkommen müsste, so dass wir ohne Probleme z.B. den Ort als Funktion der Zeit darstellen können. Wenn wir aber die Beschleunigung als Funktion des Ortes haben möchten (z.B. um die an diesen Orten wirkende Kraft festzulegen), müssen wir die Datei nach aufsteigenden Orten x sortieren. Dies erledigt das Kommando sort: sort -k2 -n res.dat > res_sorted.dat Die Option -k2 legt fest, dass nach der zweiten Spalte sortiert werden soll. Dabei wird standardmäßig angenommen, dass Spalten durch Leerzeichen voneinander getrennt sind, sie können diesen Delimiter jedoch durch die Option -t ändern. Die zweite oben verwendete Option ist -n, die bewirkt, dass die Sortierung numerisch (und nicht alphabetisch) erfolgt – andernfalls wäre nämlich 10 vor 1 statt dahinter. Die Filter concat und enumerate Im Folgenden werden wir einige Filter vorstellen, die nicht Bestandteil von Unix sind, sondern die speziell für die Anforderungen der Numerik geschrieben und auf die Struktur unserer Ergebnisdateien abgestimmt wurde. Sie finden diese Filter im Quellcode und als lauffähige Binaries auf der CD im Verzeichnis filter. Alle Filter dienen der Weiterverarbeitung von Ausgabedateien, von denen angenommen wird, dass sie folgendermaßen aufgebaut sind: – – Am Programmanfang stehen einige Kommentarzeilen, die mit einem ! oder mit einem # beginnen. Diese Kommentarzeilen werden von den Filtern unverändert übernommen. Um die Änderung durch den Filter selbst nachvollziehen zu können wird eine Kommentarzeile hinzugefügt, die den Filteraufruf mit allen Argumenten protokolliert. Im eigentlichen Datenteil sind die Werte in Spalten angordnet. Dieser Bereich wird durch den Filter bearbeitet und eventuell in modifizierter Form in einer Ausgabedatei abgespeichert. 280 E Filterprogramme Im Gegensatz zu den oben vorgestellten Standardfiltern erfolgt bei diesen numerischen Filtern die Ausgabe nicht auf dem Bildschirm sondern in eine Datei – da die Filter jedoch im Quellcode vorliegen, können Sie das leicht ändern, was insbesondere für Linux-Anwender interessant ist, da dann mehrere Filter aneinander gekettet werden können. Aber auch wenn Sie die bash-Shell von djgpp benutzen, können Sie von dieser Möglichkeit Gebrauch machen. Bevor wir zu den spezifisch numerischen Filtern kommen, stelle ich Ihnen an dieser Stelle zwei eigene Filter vor, die zwar eigentlich keine numerischen Aufgaben ausführen, aber wohl nur im numerischen Umfeld benötigt werden, und deswegen nicht zu den unter Unix standardmäßig verfügbaren Filtern gehören. Die Aufgabe von concat ist sehr einfach. Es liest jeweils eine Zeile aus zwei Eingabedateien ein und gibt diese durch ein Leerzeichen getrennt in einer einzigen Zeile wieder aus. Aus den Dateien Fest gemauert in der Erden Steht die Form, aus Lehm gebrannt. Heute muss die Glocke werden. Frisch Gesellen, seid zur Hand. und Sein oder nicht sein? das ist hier die Frage... ob’s edler im Gemuet, die Pfeil’ und Schleudern würde dieser Filter also Fest gemauert in der Erden Sein oder nicht sein? Steht die Form, aus Lehm gebrannt. das ist hier die Frage... Heute muss die Glocke werden. ob’s edler im Gemuet, Frisch Gesellen, seid zur Hand. die Pfeil’ und Schleudern generieren, was ziemlichen Unsinn darstellt. Im Fall von Zahlen kann eine solche Zusammenziehung aus zwei getrennten Dateien jedoch durchaus sinnvoll sein. Als Beispiel greifen wir wieder auf die Ergebnisdatei der vorangegangen Abschnitte zurück, aus der wir mittels verschiedener Filter eine Datei result t2.dat generiert haben, die nur noch die Zeiten enthält. Auf demselben Weg können wir uns natürlich auch eine zweite Datei result x2.dat erzeugen, die nur noch die Orte enthält. Unsere Grafikprogramme erwarten jedoch eine Datei, in der Zeit und Ort jeweils paarweise enthalten sind. Dies erzeugt uns nun der Filter concat: > concat result_t2.dat result_x2.dat > result_t_x.dat Es gibt unter Unix den Standardfilter paste, der praktisch die selbe Funktion wie concat hat. Allerdings behandelt er die Kommentarzeilen genauso wie den Datenbereich und fügt nicht in die Ausgabe den Vermerk ein, dass diese Datei aus zwei Ergebnisdateien zusammengefügt wurde. E Filterprogramme 281 Der Filter enumerate überspringt wie concat die Kommentarzeilen und stellt jeder nachfolgenden Zeile eine fortlaufende Nummer, beginnend bei eins, voran. Der numerische Filter minmax Das kleine Programm minmax gibt einen Schnellüberblick über eine Zahlenreihe, die als Spalte in einer Datei steht. Als Argumente erwartet minmax die Datei, in der die Daten stehen, die Zahl der Spalten und die Nummer der Spalte, in der die zu analysierenden Daten stehen. Die Ausgabe von minmax umfasst die Zahl der Werte, den Wertebereich (also den minimalen und maximalen Wert), den Mittelwert und die Standardabweichung: > minmax hysterese.res 3 2 Zahl der Datensaetze : 10000 Minimum : -2 Maximum : 2 Mittelwert : 0.00504067 Standardabw.: 1.41576 Die numerischen Filter translate und scale Die Filter in diesem und den beiden folgenden Abschnitten werden von geübten awk-Programmierern nicht benötigt, da sie die entsprechenden Modifikationen ebenso schnell in awk ausgeführt haben. Mit Hilfe von translate und awk können Sie zu allen Werten einer bestimmten Spalte eine feste Zahl hinzuaddieren bzw. alle Werte einer Spalte mit einer Zahl multiplizieren. Wenn z.B. in Spalte 2 einer Ausgabedatei result1.res mit insgesamt 5 Spalten die Temperatur in Grad Celsius steht, erzeugt > translate result1.res result2.res 5 2 -273.2 eine Datei result2.res, bei der diese Temperatur in Kelvin angegeben wird. Die anderen Spalten, sowie die Kommentarzeilen bleiben unverändert. Ganz analog erzeugt > scale result1.res result2.res 5 3 1/3600 eine Datei, bei der die Zeit in der dritten Spalte von Sekunden in Stunden umgerechnet wurde. Unter DOS müssen Sie den Wert von 1/3600 zunächst berechnen und dann dezimal angeben. 282 E Filterprogramme Die numerischen Filter accumulate und differentiate In vielen Fällen erhalten wir Daten, die wir vor der Auswertung noch akkumulieren bzw. differenzieren müssen. Wenn wir die Werte der auszuwertenden Spalte mit (E.1) x1 , x2 , x3 , . . . , xi , . . . , xN bezeichnen, liefert das Programm accumulate i xn , (E.2) x2 − x1 , x3 − x2 , . . . , xi − xi−1 , . . . , xN − xN −1 (E.3) x1 , x1 + x2 , x1 + x2 + x3 , . . . , n=1 xn , . . . N n=1 während differentiate daraus macht. Beachten Sie, dass differentiate die Zahl der Datensätze um eins verringert. Als Argumente erwarten sowohl accumulate als auch differentiate die Namen der Ein- und Ausgabedatei, die Zahl der Spalten, sowie die Nummer der zu bearbeitenden Spalte – alle anderen Spalten bleiben unverändert. Die numerischen Filter add, subtract, multiply und divide Diese vier Filter addieren, subtrahieren, multiplizieren oder dividieren die Werte aus zwei Spalten und hängen das Ergebnis an die jeweilige Zeile an. Bei diesen vier Filtern kommt also eine neue Spalte zu unserer Datei hinzu. Der Aufruf ist analog zu accumulate oder differentiate, nur dass bei diesen Filtern zwei zu bearbeitende Spalten angegeben werden müssen, z.B. > subtract result1.res result2.res 5 2 1 Bei der so erzeugten Datei result2.res enthält die neu hinzugekommene sechste Spalte den Wert x2 − x1 , wobei x1 und x2 die Zahlen in der ersten bzw. zweiten Spalte sind. Die numerischen Filter polyfit und polynom Besonders bei der Weiterverarbeitung von Messwerten müssen diese häufig an ein bestimmtes Modell gefittet werden, d.h. alle freien Parameter des Modells müssen so bestimmt werden, dass die Abweichung zwischen Modell und Messwerten möglichst klein ist. Bei einem Polynomfit ist das Modell ein Polynom definierter Ordnung N E Filterprogramme fModell (x) = N an xn 283 (E.4) n=0 und die Parameter a0 , a1 , . . . , aN sind die freien Parameter. Unser Filter polyfit erledigt diese Aufgabe und erwartet als Argumente eine Datei mit den Wertepaaren, den Namen der Ausgabedatei und die Ordnung N . Als Ergebnis liefert der Filter die Koeffizienten a0 bis aN . > poly_fit result.res mod_koeff.dat 3 Während polyfit aus Wertepaaren die (bestmöglichen) Koeffizienten berechnet, macht polynom den umgekehrten Schritt: bei gegebenen Koeffizienten a0 bis aN berechnet es die Modellkurve. Dazu braucht es neben der Datei mit den Koeffizienten noch die Parameter xmin , xmax und die Zahl M der Stützstellen, an denen der Polynomwert berechnet werden soll. Als Ergebnis erhält man M Wertepaare mit x und f (x). > polynom mod_koeff.dat result.res -10 10 101 erzeugt eine Datei result.res, in der die Werte des Polynoms im Intervall [−10, 10] an 101 äquidistanten Punkten, also jeweils im Abstand 0.2, zu finden sind. Der numerische Filter mod diff Der Filter mod diff vergleicht einen Datensatz mit einem Polynom-Modell f (x) = N an xn (E.5) n=0 und berechnet für jedes Wertepaar (x, y) die Abweichung y−f (x) vom Modell. Als Parameter erwartet mod diff eine Datei, die die Koeffizienten ai des Modells beginnend bei a0 enthält, eine Datei mit den Wertepaaren (x, y), den Namen der Ausgabedatei und die Ordnung N des Polynommodells: > mod_diff mod_koeff.dat result.res 3 Der numerische Filter reduce Der letzte Filter, den ich hier vorstellen möchte, dient der Reduzierung großer Datensätze. Häufig enthalten Ergebnisdateien eine Datenfülle, die letztlich nicht gebraucht wird. Wenn wir diese Datensätze vor der graphischen Darstellung verkleinern, sparen wir Rechenzeit und vor allem Speicherplatz, da die entstehenden Grafiken wesentlich kleiner sind, ohne an Aussagekraft zu verlieren. Der Filter reduce erledigt diese Aufgabe, indem er vom ursprünglichen Datensatz nur jeden n-ten übernimmt, wobei der Parameter n beim Programmaufruf übergeben wird. 284 E Filterprogramme > reduce result1.res result2.res 10 reduziert den Datenbestand der Datei result1.res also auf ein Zehntel und schreibt das Ergebnis in result2.res. F Fouriertransformation und FFT-Routinen Eine gute mathematische Darstellung von Fourierreihen und der Fouriertransformation finden Sie in [35, 36] sowie in Teil II von [37]. Die meisten der in C vorgestellten mathematischen Bibliotheken stellen sogenannte FFT-Routinen (von Fast Fourier Transform, also schnelle Fouriertransformation) zur Verfügung. In diesem Anhang wollen wir zeigen, wie Sie diese Routinen einsetzen müssen, um die Fouriertransformierte einer Funktion zu erhalten. Als Ausgangssituation betrachten wir eine Funktion f , die wir an N äquidistanten Stützstellen xk kennen. Wie dies in vielen Anwendungen der Fall ist, wollen wir annehmen, dass diese Stützstellen symmetrisch um null verteilt liegen: x−N/2 , x−N/2+2 , . . . , xN/2−1 . Die Funktionswerte an diesen Stützstellen bezeichnen wir mit fk = f (xk ) = f (k ∆x). Was wir benötigen, ist die Fouriertransformierte fˆ(y) = dxf (x) exp(−ixy), (F.1) (F.2) oder wenigstens die Werte der Fouriertransformierten an einem endlichen Satz von Stützstellen y−N/2 , y−N/2+1 , . . . yN/2−1 : fˆj = fˆ(yj ) = fˆ(j∆y) . (F.3) Da wir die Ausgangsfunktion nur an diskreten Stützstellen kennen, müssen wir das Integral (F.2) durch eine Summe ersetzen – eine Näherung die nur sinnvoll ist, wenn die Stützstellen hinreichend dicht liegen, so dass sich die Funktion f zwischen zwei Stützstellen nur wenig ändert, und wenn die Funktion f außerhalb des von den Stützstellen abgedeckten Bereichs annähernd null ist. Unter diesen Voraussetzungen erhalten wir: N/2−1 fˆj ≈ ∆x fk exp(−iyj xk ) (F.4) k =−N/2 = N −1 f (xk−N/2 ) exp(−ij(k − N/2)∆y∆x) (F.5) k=0 = (−1)j N −1 f (xk−N/2 ) exp(−ijk∆y∆x) (F.6) k=0 Dies ist im Wesentlichen das, was eine FFT-Routine liefert, nämlich 286 F Fouriertransformation und FFT-Routinen gj = N −1 fk exp(−2πijk/N ) , (F.7) k=0 wobei die fk die Werte sind, die wir an die Routine als Eingangsdatensatz übergeben, während die gj das Ergebnis der FFT darstellen. Wenn wir dies mit (F.6) vergleichen, so erhalten wir Übereinstimmung, wenn und fˆj = gj 2π ∆y = N ∆x (F.8) (F.9) ist und wir den Faktor (−1)j berücksichtigen. Abschließend müssen wir uns noch überlegen, wie wir die Fouriertransformierte bei negativen y bekommen. Der Schlüssel hierzu ist die Tatsache, dass für alle ganzen n exp(−2πin) = 1 (F.10) gilt. Wir können also diesen Faktor in (F.6) einfügen, wobei wir 2π mittels (F.9) als 2π = N ∆x∆y (F.11) schreiben: fˆj = (−1)j N −1 f (xk−N/2 ) exp(−ijk∆y∆x) exp(−inN ∆x∆x) (F.12) k=0 = fˆj+nN . (F.13) Die Tatsache, dass wir die Ausgangsfunktion nur an diskreten und äquidistanten Stützstellen kennen, führt also zu einer (scheinbaren) Periodizität der Fouriertransformierten. Wir gehen jedoch davon aus, dass letztere in Wirklichkeit nur im Intervall [−N/2∆y, N/2∆y] merklich von null verschieden ist, und wir die Werte im negativen Teil dieses Intervalls in den fN/2 , fN/2+1 , . . . fN finden, wozu wir n = 1 in (F.13) einsetzen müssen. Fouriertransformierte in mehreren Dimensionen Liegt eine Funktion von mehreren unabhängigen Variablen vor, z.B. f (x1 , x2 , x3 ) , (F.14) so kann man die Fouriertransformation für alle Variablen durchführen: 1 ˆ f (y1 , y2 , y3 ) = dx1 dx2 dx3 f (x1 , x2 , x3 ) (F.15) (2π)3/2 (F.16) × exp (i(x1 y1 + x2 y2 + x3 y3 ) . F Fouriertransformation und FFT-Routinen 287 Es ist offensichtlich, dass man diese Fouriertransformation nach x1 , x2 und x3 als eine Verkettung von Transformationen auffassen kann, die jeweils nur eine der drei Variablen betrifft: 1 f1 (y1 , x2 , x3 ) = √ dx1 f (x1 , x2 , x3 ) exp (ix1 y1 ) (F.17) 2π 1 dx2 f1 (y1 , x2 , x3 ) exp (ix2 y2 ) (F.18) f2 (y1 , y2 , x3 ) = √ 2π 1 dx3 f2 (y1 , y2 , x3 ) exp (ix3 y3 ) . (F.19) fˆ(y1 , y2 , y3 ) = √ 2π Diese Sichtweise hilft uns eine FFT in mehreren Dimensionen durchzuführen, auch wenn die verwendete Numerik-Bibliothek nur FFT-Routinen für eine Dimension zur Verfügung stellt. Zunächst führen wir die Transformation (F.17), dann (F.18) und schließlich (F.19) durch, die uns die gesuchte Fouriertransformierte fˆ liefert. G Die GPL-Lizenz Aus lizenzrechtlichen Gründen geben wir hier den Text der GNU General Public License in Englisch wieder. Übersetzungen in andere Sprachen finden Sie auf der Internetseite http://www.gnu.org/licenses/translations.html; diese haben jedoch im Zweifelsfall keinerlei rechtliche Relevanz. The GNU General Public License Version 2, June 1991 c 1989, 1991 Free Software Foundation, Inc. Copyright 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software—to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation’s software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author’s protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors’ reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone’s free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. Terms and Conditions For Copying, Distribution and Modification 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The “Program”, below, refers to any such program or work, and a “work based on the Program” means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term “modification”.) Each licensee is addressed as “you”. 290 G Die GPL-Lizenz Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program’s source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same G Die GPL-Lizenz 4. 5. 6. 7. 8. 9. 10. 291 place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients’ exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and “any later version”, you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. No Warranty 11. Because the program is licensed free of charge, there is no warranty for the program, to the extent permitted by applicable law. Except when otherwise stated in writing the copyright holders and/or other parties provide the program “as is” without warranty of any kind, either expressed or implied, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. The entire risk as 292 G Die GPL-Lizenz to the quality and performance of the program is with you. Should the program prove defective, you assume the cost of all necessary servicing, repair or correction. 12. In no event unless required by applicable law or agreed to in writing will any copyright holder, or any other party who may modify and/or redistribute the program as permitted above, be liable to you for damages, including any general, special, incidental or consequential damages arising out of the use or inability to use the program (including but not limited to loss of data or data being rendered inaccurate or losses sustained by you or third parties or a failure of the program to operate with any other programs), even if such holder or other party has been advised of the possibility of such damages. End of Terms and Conditions Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. one line to give the program’s name and a brief idea of what it does. Copyright (C) yyyy name of author This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) yyyy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type ‘show w’. This is free software, and you are welcome to redistribute it under certain conditions; type ‘show c’ for details. The hypothetical commands show w and show c should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than show w and show c; they could even be mouse-clicks or menu items—whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a “copyright disclaimer” for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program ‘Gnomovision’ (which makes passes at compilers) written by James Hacker. signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. Literaturverzeichnis 1. M. Galassi et al: GNU Scientific Library Reference Manual, 2. Auflage (Network Theory Ltd, Bristol 2003) 2. T. Fließbach: Lehrbuch zur Theoretischen Physik, Band 1–4 3.–4. Auflage (Spektrum Akademischer Verlag, Heidelberg 1999–2003) 3. W. Nolting: Grundkurs Theoretische Physik Band 1-7, 4.–5. Auflage (Springer, Berlin Heidelberg New York 2001–2004) 4. R. J. Jelitto: Theoretische Physik, Band 1-6, 2.–4. Auflage (Aula Verlag, Wiesbaden 1987–1995) 5. J. D. Jackson: Klassische Elektrodynamik 3. Auflage (de Gruyter Verlag, Berlin New York 2001) 6. M. Born, E. Wolf und A. B. Bhatia: Principles of Optics, 7. Auflage (Cambridge University Press, Cambridge 1999) 7. H. Rollnik: Quantentheorie, 2. Auflage (Springer, Berlin Heidelberg New York 2003) 8. W. Greiner et al: Theoretische Physik, Band 1–11 1. –7. Auflage (Verlag Harri Deutsch, Frankfurt a. Main 1991–2004) 9. J. M. Thijssen: Computational Physics, 3. Auflage (Cambridge University Press, Cambridge 2001) 10. R. H. Landau und M. J. Paez: Computational Physics: Problem Solving with Computers, 1. Auflage (John Wiley & Sons, New York 1997) 11. P. L. DeVries: A First Course in Computational Physics, 1. Auflage (John Wiley & Sons, New York 1994) 12. S. E. Koonin und D. C. Meredith: Computational Physics, 1. Auflage (AddisonWesley, Bonn 1997) 13. Marvin L. Dejong: Introduction to Computational Physics, 1. Auflage (Addison-Wesley, Bonn 1991) 14. R. Peyret: Handbook of Computational Fluid Mechanics 1. Auflage (Academic Press, Orlando 2000) 15. D. A. Anderson, J. C. Tannehill und R. H Pletcher: Computational Fluid Mechanics and Heat Transfer 1. Auflage (Taylor & Francis Press, London New York Philadelphia Singapur 1984) 16. J. D. Anderson: Computational Fluid Dynamics 2. Auflage (McGraw-Hill, 1997) 17. T. J. Chung: Computational Fluid Dynamics 1. Auflage (Cambridge University Press, Cambridge 2002) 18. I. Newton: Principia (1687) 19. K. H. Hoffmann, M. Schreiber (eds): Computational Statistical Physics 1. Auflage (Springer, Berlin Heidelberg New York 2002) 294 Literaturverzeichnis 20. W. Nolting: Grundkurs Theoretische Physik 2, 6. Auflage (Springer, Berlin Heidelberg New York 2004) 21. F. Scheck: Theoretische Physik 1, 7. Auflage (Springer, Berlin Heidelberg New York 2003) 22. W. Greiner: Mechanik, Teil 2, 6. Auflage (Verlag Harri Deutsch, Frankfurt a. Main 1998) 23. A. Einstein: Annalen der Physik 17, 891 (1905) 24. E. Durand: Electrostatique et magnétostatique 1. Auflage (Masson, Paris 1953) 25. Ch. Kittel: Einführung in die Festkörperphysik 12. Auflage (R. Oldenbourg Verlag, München Wien 1999) 26. B. Œksendal: Stochastic Differential Equations, 5. Auflage (Springer, Berlin Heidelberg New York 1998) 27. G. G. Emch und C. Liu: The Logic of Thermostatistical Physics, 1. Auflage (Springer, Berlin Heidelberg New York 2002) 28. W. Nolting: Grundkurs Theoretische Physik 5/1, 6. Auflage (Springer, Berlin Heidelberg New York 2004) 29. W. Nolting: Grundkurs Theoretische Physik 5/2, 5. Auflage (Springer, Berlin Heidelberg New York 2004) 30. C. Cohen-Tannoudji, B. Diu, F. Laloë: Quantenmechanik, Teil 2 2. Auflage (Walter de Gruyter, Berlin 1999) 31. Teubner-Taschenbuch der Mathematik, 1. Auflage (B. G. Teubner Verlag, Stuttgart Leipzig 1996) 32. C. Ginzel et al: Physical Review A 48, 732 (1993) 33. W. P. Schleich: Quantum Optics in Phase Space, 1. Auflage (Wiley-VCH Berlin 2001) 34. M. O. Scully und M. S. Zubairy: Quantum Optics 1. Auflage (Cambridge University Press, Cambridge 1997) 35. R. V. Churchill: Fourier Series and Boundary Value Problems, 5. Auflage (McGraw-Hill, New-York 1993) 36. F. B. Hildebrand: Advanced Calculus for Applications, 2. Auflage (PrenticeHall, Englewood Cliffs 1976) 37. W. I. Smirnow: Lehrgang der höheren Mathematik, Teil I–V, 1. Auflage (Deutscher Verlag der Wissenschaften, Berlin 1967) Index Aberration 111 Absorbtion 251 Astigmatismus 143 Äther 71, 99 Attraktor 54 Attraktor seltsamer 55 Attraktordiagramm 60 Basis 196 Beschreibung stroboskopische 244 Beugungsmuster 129 Bewegungsgleichung 3 Bifurkation 61 Bildfeldwölbung 143 BLAS 266 Boltzmann-Konstante 237 Boltzmann-Verteilung 191 Boltzmannfaktor 237 Brechung 101, 105 Brechungsgesetz Snelliussches 102 Brownsche Bewegung 152 CBLAS 266 CERNLIB 266 Delta-Potential 254 Diamagnetismus 97 Dichtematrix 233 Dichteoperator 233 Dielektrizitätskonstante 72 Dipol 73 Dokumentation unter Linux 259 unter Windows 263 Drei-Niveau-System 247 Dreipunktformel 204 Dynamik integrable 32 Eigenwert 196 Eigenwertgleichung 196 Eigenwertgleichung verallgemeinerte 220 Eigenzustände des Hamiltonoperators Eigenzustand 195 Ein-Atom-Laser 246 Emission 251 Entartung 196 Entropie 234 213 FAQ 260, 264 Feldlinien 76 Fernfeld 73 Freies Teilchen 200 Fresnelsche Formeln 103 Gegenkraft 1 Gesetz Hookesches 2 von Biot-Savart 80 Gesetze Newtonsche 1 Gnu Scientific Library 20 GNU Scientific Library 265 Gravitationsgesetz 2 Grenzzyklus 54 Hamilton-Formalismus 52 Hamiltonfunktion 200 Hamiltonoperator 200 Hamiltonsche Gleichungen 52 Hamiltonsches System 52 Heisenbergbild 200 296 Index Mastergleichung 153 Mastergleichung quantenmechanische 234 Mathematisches Pendel 4 MATPACK 20, 266 Maxwellsche Gleichungen 71 Mean-Field-Näherung 93 Messprozess 241 Messung 199 Metrik 40 Moment 152 Monopol 73 Helmholtz-Spule 96 Hermitesche Polynome 216 Hilbertraum 193 Huygenssches Prinzip 128 Hysterese 72, 92 Impulsdarstellung 197 Impulsoperator 197, 198 IMSL 265 Inertialsystem 2 Intensität von Licht 125 Interferenz 127 Interferenz destruktive 127 konstruktive 127 Interferenzterm 127 Inversion 249 NAG 265 Newsgroup 260, 264 Newtonsche Gesetze 1 Normerhaltung 208 Normierungsbedingung 194 Nullpunktsfluktuationen 233 Kohärenz 134 Kohärenzfunktion 136 Koma 143 Kommutator 195 konjugiert kanonisch 52, 195 Koordinaten generalisierte 26 Kopenhagener Interpretatation Korrelation 150 Korrelationsfunktion 151 Lagrangeformalismus 26 Lagrangefunktion 26 Lagrangeparameter 26 Laser 246 Legendre-Transformation 52 Lichttheorie Huygenssche 99 Newtonsche 99 Linse asphärische 105 sphärische 105 Linsenfehler 111 Liouvillescher Satz 54 Lyapunov-Exponent 41 Magnetisierung 72 Markov-Approximation Massenpunkt 1 237 199 Objekthöhe 106 Observable 199 Öffnungsfehler 143 Operator 194 Operator Erzeugungs- 251 Hermitescher 196 Vernichtungs- 251 Ortsdarstellung 197 Ortsoperator 197, 198 Oszillator harmonischer 216 Pendel Doppel- 26 Faden- 3 mathematisches 4 Permeabilitätskonstante 72 Photon 100, 251 Plancksches Wirkungsquantum Poincaré-Plot 32 Polarisation 72 Potential optisches 208 Potentialbarriere 226 Potentialmulde 226 Potentialtopf 208 Potenzreihe 5 Projektion 199 100 Index Umklappoperatoren Pumpen 249 Punktladung 72 Variable dynamische 32 Variationsmethoden in der Quantenmechanik Variationsprinzip Ritzsches 219 Verbundwahrscheinlichkeit Verzeichnung 143 Quadrupol 73 Quantenfeldtheorie 233 Quantenoptik 100 Quantenstatistik 233 Quantentunneln 226 Randbedingungen periodische 204 Random Walk 153 Reflektion 101 Remaneszenz 97 Reservoir 166 rpm-Paket 257 Runge-Kutta-Verfahren 23 Schrödingerbild 200 Schrödingergleichung 199 Separatrix 14 Sinai-Billard 50 skleronom 52 SLATEC 20, 266 Spin 235 Spur 233 Stoßparameter 167 Strahlenoptik 100 Überlappmatrix 220 236 Wärmebad 236 Wahrscheinlichkeit Wahrscheinlichkeit bedingte 150 Wellen ebene 126 Kugel- 126 Wellenpaket 204 147 Zeno-Effekt 242, 244 Zerfall spontaner 233 Zufallskomponente 199 Zustand 193 Zustand gemischter 233 reiner 233 Zustandsvektor 193 Zwei-Niveau-System 235 219 150 297