Kapitel 2: Zufallszahlen Anwendungsgebiete: stochastische Algorithmen, z.b. Monte-Carlo-Integration Simulation von Ereignissen mit bekannten Wahrscheinlichkeitsverteilungen, in der Physikalischen Chemie insbesondere: (Metropolis-)Monte-Carlo-Verfahren zur Simulation verschiedener Ensembles in der statistischen Thermodynamik Eine Sequenz von echten Zufallszahlen wiederholt sich nicht, sowohl innerhalb einer Sequenz als auch bei Neustart der Sequenz (auch bei sonst gleichem input, sofern vorhanden); ist nicht vorhersagbar, und dabei insbesondere unkorreliert (Wahrscheinlichkeit für jeden Wert immer gleich, unabhängig von allen vorigen Werten). Problem: Computer ist deterministisch (immer gleicher output bei gleichem input) bestenfalls Pseudozufallszahlen möglich: wiederholen sich nach (möglichst sehr langer) Sequenz; haben keine offensichtlichen Korrelationen; sind aber exakt vorhersagbar, da nach deterministischer Vorschrift erzeugt. Vorgefertigte Zufallszahlen-Generatoren: bis inkl. Fortran77 gab es keinen Generator im Standard; alle Compiler-Hersteller boten Generatoren an, mit einigen Ähnlichkeiten aber auch subtilen Unterschieden im interface und großen Unterschieden in der Qualität; ab Fortran90 gibt es ein standardisiertes interface im Standard; die eigentlichen Generatoren sind aber immer noch hersteller-abhängig. Problem: das interface ist in einem entscheidenden Aspekt unverständlich; portable Zufallszahlengene- natürlich gibt es auch direkt in Fortran geschriebene, ratoren. Hier: Algorithmus eines portablen Generators selbst programmieren und Resultate mit eingebautem Generator vergleichen.
Verschiedenste Algorithmen als Zufallszahlen-Generatoren denkbar. Praktisch wichtig: Erzeugung von Pseudozufallszahlen muß schnell gehen, da meist in großer Menge benötigt. möglichst kurze, einfache Formel nötig. Eine von mehreren Standard-Möglichkeiten: linear-kongruente Zufallszahlen: Iteration mit modulo-division: I j+1 = (a I j + c) mod m (1) liefert (für geeignete Werte a, c, m, I 0 ) lange, weitgehend unkorrelierte, integer-zahlenfolge, gleichverteilt zwischen 0 und m. Typische Standardisierung: Normierung auf Intervall [0, 1[ (und Erzeugung einer real-zahl): Tips zur Programmierung: alle Größen integer (bis auf r) r = I j+1 /m (2) Vorsicht bei Division von integer-werten (wie in Gl. 2): integer-division wird in Fortran ganzzahlig abgeschnitten. Das eigentlich gewünschte mathematische Verhalten bei r = i/j ergibt sich durch: r = real(i)/real(j) Initialisierung = Wahl von I 0 (seed): bei gutem Generator Sequenzlänge unabhängig von I 0, daher: Wahl von I 0 eigtl. beliebig; I 0 legt nur den Einstiegspunkt in einer durch die anderen Parameter vorgegebenen, sich immer wiederholenden Sequenz fest; aber wichtiger Praxisaspekt: Prozedur ist streng deterministisch: selber I 0 -Wert ergibt bei erneutem Programmlauf exakt denselben output! gewünschtes Verhalten bei reproduzierbaren Tests; bei echten Anwendungsrechnungen pseudo-zufällige seed-werte konstruierbar aus z.b. Tagesdatum und Uhrzeit oder hardware-countern. Parameterwahl = gute Werte für m, a, c: nicht trivial: Sequenzlänge m Korrelationen: Plot von k Zufallszahlen in k-dimensionalem Raum ergibt keine Gleichverteilung, sondern Konzentration auf (k 1)-dimensionale Ebenen. Selbst bei optimaler Wahl von m, a, c gibt es maximal m 1/k Ebenen. Faustregel: große m-werte sind besser selbst bei gutem m sind Korrelationen und Sequenzlänge auch bestimmt von a und c schwarze Magie..., siehe Tabelle. (Warnung: in Maschinensprache (assembler) sind größere integer-werte möglich als in Fortran eigener Generator sollte eigentlich schlechter sein als kommerzielle Generatoren in assembler.)
Standardtrick zur Zerstörung von Korrelationen und damit zur Verbesserung eines existierenden Zufallszahlengenerators: Initialisierung: generiere N Zufallszahlen speichere sie in array r(i) bei allen folgenden Aufrufen: skaliere vorherige Zufallszahl auf integer-wert j im Intervall [1, N] r(j) wird als Zufallszahl-output ausgegeben generiere eine neue Zufallszahl speichere sie in r(j)
Verwendung eingebauter Zufallszahlengeneratoren nicht-standard-generatoren in Fortran77: typische Befehle: seed auswählen, z.b. call srand(iseed), wobei der Wert von iseed selbst gewählt werden muß (z.b. konstruiert aus Tagesdatum und Uhrzeit) nachfolgend beliebig oft neue Zufallszahlen erzeugen, z.b. mit x = rand(); die x-werte sind dann meist gleichverteilt im Intervall [0, 1[. immer Manual für genauen Gebrauch konsultieren! Dabei achten auf: Funktions-/Subroutinen-Namen, Argumente single/double precision (Funktionen, Argument(e)) Verteilung und Intervall der Zufallszahlen seed nötig oder nicht Verhalten bei Programm-Neustart stehen diese Routinen direkt zur Verfügung oder sind compiler-direktiven oder Einbinden von Bibliotheken nötig? Erwartete Funktionsweise des Generators vor eigentlichem Einsatz immer mit einem kleinen Testprogramm verifizieren! Standard-Generator in Fortran90/95: Der Aufruf des eigentlichen Generators erfolgt wie hier gezeigt:... real::r... call random_number(r)... Dadurch wird eine Zufallszahl in der Variablen r gespeichert. Nachfolgende Aufrufe derselben Art legen die nachfolgenden Zahlen der Pseudozufallssequenz in r ab. Dabei handelt es sich um gleichverteilte Zufallszahlen im Intervall [0, 1[. Soweit ist alles unproblematisch. Will man bei sukzessiven Programmläufen unterschiedliche Pseudozufallssequenzen haben, muß man die Sequenz zunächst neu initialisieren. Dafür gibt es ein Unterprogramm random_seed, das aber dummerweise im Standard sehr verwirrend implementiert ist: Die seed-zahl ist kein einfacher integer-wert, sondern ein integer-array. Die Länge dieses seed-arrays ist nicht festgelegt. In der Praxis variiert sie tatsächlich stark von compiler zu compiler.
Ein Aufruf von random_seed ohne jegliche Argumente ist möglich. Dadurch wird der seed-array auf einen implementationsabhängigen Wert gesetzt dieser kann jedoch bei jedem Programmlauf derselbe sein. Korrekterweise müßte daher eigentlich folgendermaßen verfahren werden: Testprogramm laufen lassen, in dem per call random_seed(size=n) die Länge n des seed-arrays abgefragt wird. Im eigentlichen Programm den seed-array vereinbaren als integer,dimension(n)::seed (auch wenn n=1 gelten sollte!). seed-array zu Beginn auf einen gewünschten Wert setzen und an random_seed übergeben; dabei ist jedoch völlig unklar, ob alle Elemente des seed-arrays gesetzt werden müssen, mit denselben oder unterschiedliche Werten, usw. Im Absoft-Compiler ist n=1. Das folgende kleine Testprogramm zeigt, wie man das eingebaute Unterprogramm date_and_time verwenden kann, um einen quasi-zufälligen seed zu erzeugen, der bei jedem Programmlauf unterschiedlich sein sollte, und wie der Fortran-Standardgenerator danach anzusteuern ist. Beachten Sie daß trotz des ersten Aufrufs von random_seed die erste Zufallszahl bei mehrfachem Ausführen dieses Programms immer wieder dieselbe ist. daß der seed-wert und die nachfolgenden Zufallszahlen sich aber von Programmlauf zu Programmlauf unterscheiden. program random_test implicit none real::a integer,dimension(8)::date_time integer,dimension(1)::seed integer::n,i call date_and_time(values=date_time) write(*,*) year =,date_time(1) write(*,*) month =,date_time(2) write(*,*) day =,date_time(3) write(*,*) zone =,date_time(4) write(*,*) hours =,date_time(5) write(*,*) minutes =,date_time(6) write(*,*) seconds =,date_time(7) write(*,*) milliseconds =,date_time(8) call random_seed call random_number(a) write(*,*)a call random_seed(size=n) write(*,*) size =,n seed(1)=date_time(6)*date_time(7)+date_time(8) write(*,*) seed =,seed(1) call random_seed(put=seed) do i=1,10 call random_number(a) write(*,*)a end do end
Einfaches Programmieren mit Zufallszahlen Verschiedene Verteilungen Gleichverteilung von random_number(r) in [0, 1[ direkt verwenden gleichverteiltes r im Intervall [x 1, x 2 [: r = r(x 2 x 1 ) + x 1 (3) andere Verteilungen (exponentiell, Gaußförmig, Poisson,...) leicht aus Gleichverteilung konstruierbar; siehe Numerical Recipes Münzwurf-Simulation 0 1 0.5 i = { 1 für r 0.5 0 für r < 0.5 (4) (direkte Erzeugung zufälliger bits wäre effizienter, siehe Numerical Recipes) Zufallsereignisse bekannter Wahrscheinlichkeit z.b.: Ereignis a: 60%, Ereignis b: 30%, Ereignis c: 10% 0 1 0.6 0.9 a b c wähle c für r 0.9 b für 0.6 r < 0.9 a für r < 0.6 (5) Im allgemeinen Fall mit n Ereignissen mit Wahrscheinlichkeiten w i, i = 1,...,n, 0 w i 1 sowie i w i = 1, wird diese Implementation wegen vieler if-abfragen unnötig langsam. Alternative: call random_number(r) s = 0.d0 j = 1 do s = s + w(j) if (s > r) exit j = j + 1 end do i = j Dieses Programmfragment wählt integer-wert i mit Wahrscheinlichkeit w(i), unter obigen Voraussetzungen.
Monte-Carlo-Berechnung von π r Fläche Fläche = πr2 /4 = 1 r 2 4 π (6) 0 r Algorithmus zur Berechnung von π: 1. generiere zwei Zufallszahlen x i, y i (gleichverteilt in [0, 1[; implizite Annahme: r = 1) 2. teste, ob Punkt (x i, y i ) im Viertelkreis liegt (trifft zu, wenn x 2 i + y 2 i 1) 3. π = 4 {(Anzahl Punkte im Viertelkreis)/(Anzahl aller Punkte)} 4. wenn noch nicht konvergiert, gehe zu (1) Konvergenz: Vergleich zu exaktem Wert von π wenig sinnvoll: wäre in einer realistischen Rechnung nicht bekannt davon unabhängiges Konvergenzkriterium nötig!: Vergleiche Resultate für π von einer Iteration zur nächsten; Abbruch wenn Differenz unterhalb der gewünschten Genauigkeit. generell wichtig: Anstieg der Genauigkeit mit der Anzahl N der Iterationen, z.b. linear, quadratisch, kubisch,..., exponentiell; je stärker, desto besser! Hier allerdings nur ca. wie N.
Monte-Carlo-Integration zwei mögliche Sichtweisen / Verfahren: mittlere Fläche: wie MC-Berechnung von π: y <f> a r r b a b r2 1 3 b a f(x) dx = f (b a) (7) mit f = 1 N N f(r i ) (8) i=1 b a f(x) dx = x,y-punkte unter f(x) y(b a) alle x,y-punkte (9) Offensichtlich ist Verfahren 1 einfacher: nur eine statt zwei Zufallszahlen pro Funktionsberechnung. Konvergenz allerdings ebenso schlecht wie bei MC-π-Berechnung.
Zufallszahlen in der PC/TC: Monte-Carlo-Integration: Nachteil: schlechte Konvergenz ( N) Vorteile: sehr robust und allgemein einfache Implementation keine Probleme mit kompliziert geformten Integrationsgebieten Daher Verwendung hauptsächlich bei höherdimensionalen Integralen, bei denen andere Integrationsverfahren schwieriger werden. Monte-Carlo(-Simulation/Verfahren), MC: Berechnung von statistisch-thermodynamischen Ensemblemittelwerten Metropolis-Methode zur Erzeugung eines Ensembles bei vorgegebener Temperatur T: Zufalls-Variation der Struktur, Annahme mit Wahrscheinlichkeit exp( E/kT) etablierte Varianten für verschiedene Ensembles (mikrokanonisch, kanonisch, großkanonisch; konstanter Druck; usw.) Alternative zur Moleküldynamik (MD): Simulation von Molekülen, Clustern, Gasen, Flüssigkeiten, Grenzflächen, Festkörpern,... globale Optimierung: MC-Varianten und Evolutionäre Algorithmen für Cluster-Struktur-Optimierung molecular docking Proteinfaltung