lehrkraefte:snr:informatik:glf22:python:simulationen

Simulationen

Mit Simulation (oder hier genauer Computer-Simulation) ist die Nachbildung eines in der realen Welt stattfindenden Prozesses gemeint. Einige Beispiele:

  • Wettersimulation: Auf Grund von Messwerten wird versucht, die Entwicklung des Wetters vorherzusagen. Dabei werden physikalische Modelle für die Wetterentwicklung verwendet. Meist werden viele Simulationen durchgeführt und dann im Wetterbericht das wahrscheinlichste Ergebnis präsentiert.
  • Klimasimulation
  • Simulation wirtschaftlicher Szenarien (wie entwickelt sich die Gesamtwirschaft, welchen Einfluss haben Veränderungen im Steuersystem, etc.)
  • Flug- oder Zugsimulator, etwa zu Ausbildungszwecken (gefährliche Situationen können gefahrlos trainiert werden)
  • Virtual reality (etwa bei Computerspielen, aber auch ernsthaften Anwendungen)
  • Simulationen zur Entwicklung neuer Medikamente oder Impfstoffe, etwa gegen Corona (viele Stoffe können am Computer getestet werden, so dass man aussichtsreiche Kandidation für reale Tests bekommt)
  • Simulationen von biochemischen Vorgängen (etwa Falten von Proteinen, vgl. https://www.youtube.com/watch?v=gg7WjuFs8F4, https://en.wikipedia.org/wiki/AlphaFold)
  • Simulationen in Naturwissenschaften (etwa Simulationen von Atomen und Molekülen)
  • etc. (evtl. auch künstliche Intelligenz, etwa neuronale Netzwerke, die in (sehr vereinfachter Weise) das menschliche Gehirn nachbilden und etwa sehr gut Schach oder Go spielen können oder “gut” chatten können)

Die klassischen Standbeine der Naturwissenschaften sind Experiment und Theorie. Als drittes Standbein kommt mit zunehmenden Rechenkapazitäten die Simulation hinzu.1) Bisweilen können Simulationen kostspielige oder gefährliche Experimente ersetzen. Auch lässt sich mit Simulationen der Erfolg von Experimenten oder die Gültigkeit einer Theorie abschätzen.

Kopf oder Zahl: Simulation von Münz- und Würfelexperimenten

Schätze:

  1. Wie oft muss man einen (Spiel-)Würfel im Schnitt werfen, bis zweimal direkt hintereinander eine Sechs (also 66) erscheint?
  2. Muss man einen Würfel im Schnitt gleich oft, weniger oft oder öfter werfen, bis das erste Mal eine Sechs direkt gefolgt von einer Fünf (also 65) erscheint?
  3. Wie oft muss man eine Münze im Schnitt werfen, bis zweimal direkt hintereinander Kopf (also KK) erscheint?

Zweierarbeit:

  1. Beantworte experimentell: Wie oft muss man eine Münze im Schnitt werfen, bis zweimal hintereinander Kopf erscheint? (ca. 10 Minuten, mindestens 20 Experimente)
  2. Wenn ihr fertig seid: Beschreibt handschriftlich möglichst genau auf einem Blatt Papier, was ihr gemacht habt! (Denn wir wollen dies später einem Computer beibringen.)

Gemeinsam: Experiment in Worten beschreiben, immer genauer, bis am Ende Pseudo-Code dasteht.

Beschreibung in der 2lW

Wandle den zuvor erstellten Pseudo-Code in ein Python-Programm namens muenzwurf-simulation.py um. Wähle dein Niveau:

Höchstes Niveau: Selbständig programmieren

Höchstes Niveau: Selbständig programmieren

Schreibe selbst das entsprechende Programm.

Hinweise:

  • Um Zufallszahlen2) zu erzeugen:
    • Schreibe from random import * in die erste Zeile deines Programms.
    • Der Funktionsaufruf randrange(1, 3) liefert dann zufällig die Zahl 1 oder die Zahl 2 und beide Zahlen kommen gleich häufig vor.
  • Interpretiere die zufällig erzeugten Zahlen wie folgt: 1 steht für “Kopf”, 2 für “Zahl”.

Bemerkung: Gib Zwischenergebnisse aus, damit es bei hoher Anzahl von Simulationen etwas weniger langweilig ist, das Endergebnis abzuwarten (etwa alle 100000 Simulationen ein Zwischenergebnis).

Mittleres Niveau: Programmieren nach Anweisungen (vgl. Malen nach Zahlen)

Mittleres Niveau: Programmieren nach Anweisungen (vgl. Malen nach Zahlen)

Verwende das folgende Programmgerüst: Ersetze jedes Auftreten von “???” wie direkt davor beschrieben. Alle Kommentarzeilen (beginnend mit #) können bleiben.

muenzwurf-simulation-vorlage.py
# Vereinbarung:
# 1 = Kopf
# 2 = Zahl
 
# Import der Zufallsbibliothek
from random import *
 
# Anzahl der durchzuführenden Simulationen
ANZAHL_SIMULATIONEN = 100000
 
# Vereinbare eine Variable namens "gesamtzahl_wuerfe", 
# die die Gesamtzahl aller Münzwürfe zählt und setze sie auf Null.
???
 
# Beginn einer for-Schleife, die so oft ausgeführt wird,
# wie die Variable ANZAHL_SIMULATIONEN angibt.
# Nenne die Laufvariable simulationsnummer.
???
    # Gibt die Simulationsnummer aus und die aktuelle durchschnittliche Dauer, 
    # falls diese durch 10000 teilbar und positiv ist.
    if simulationsnummer % 100000 == 0:
        print(simulationsnummer)
    #    
    # Beginn des Einzelexperiments:
    # Vereinbare zwei Variablen namens
    # "letzter_wurf" und "vorletzter_wurf"
    # und weise ihnen jeweils als Startwert einen Fantasiewert zu, 
    # etwa 'nicht-definiert' oder 9999, (aber nicht 1 oder 2).
    ???
    ???
 
    # Vereinbare eine Variable "anzahl_wuerfe", deren Wert jeweils angibt, wie oft im
    # Einzelexperiment bereits die Münze geworfen wurde.
    ???
    # Beginn einer while-Schleife:
    # Der eingerückte Code-Block nach der mit "while" beginnenden Zeile
    # wird so lange durchgeführt, wie
    # die Bedingung ??? wahr ist. Wodurch musst du ??? ersetzen?
    while not (???):
        # Erzeugt zufällig die Zahl 1 (alias Kopf) oder 2 (alias Zahl) 
        # und speichert sie in der Variablen "aktueller_wurf".
        aktueller_wurf = randrange(1,7)
        # Passe die Variable anzahl_wuerfe an.
        ???
        # Aktualisiere die beiden Variablen "letzter_wurf" und "vorletzter_wurf":
        # Der letzte Wurf wird zum vorletzten Wurf,
        # der aktuelle Wurf wird zum letzten Wurf. 
        ???
        ???
    # Erhöhe die Variable "gesamtzahl_wuerfe" um die Anzahl
    # der im aktuellen Einzelexperiment getätigten Würfe.
    ???
 
# Berechne die durchschnittliche Dauer für zweimal Kopf 
# und gib sie aus!
???

Bemerkung: Die Variable anzahl_wuerfe benötigt man nicht unbedingt: Man kann auch bei jedem Wurf direkt die Variable gesamtzahl_wuerfe um Eins erhöhen.

Niedrigstes Level: Lehrperson programmiert vor

Niedrigstes Level: Lehrperson programmiert vor

Du programmierst mit und versuchst, das Programm zu verstehen. Ausserdem vergleichst du es mit dem Pseudo-Code.

Indem du dein Programm 1'000'000 Simulationen durchführen lässt, bekommst du eine Vermutung für die Antwort auf die folgende Frage.3)

Wie oft muss man eine Münze im Schnitt werfen, bis zweimal direkt hintereinander Kopf erscheint?

Was denkst du?

Wie oft muss man eine Münze im Schnitt werfen, bis direkt hintereinander Kopf-Zahl erscheint? 
Genauso lang wie für zweimal Kopf hintereinander?

Ändere dein Programm ein wenig, um diese Frage zu erforschen.

Bevor man ein kompliziertes Problem löst (und dafür etwa ein Computerprogramm schreibt), ist es stets eine gute Idee, das Problem auf einem Blatt Papier handschriftlich möglichst genau zu analysieren (und den beabsichtigten Programmablauf schrittweise immer genauer festzulegen (etwa: welche Variablen, Schleifen, Funktionen benötige ich?)).

  • Öffne ein neues Python-Programm namens wuerfel-simulation.py und kopiere allen Code aus dem Programm muenzwurf-simulation.py in dieses Programm.
  • Ändere das Programm so, dass es per Simulation die Frage beantwortet: Wie oft muss man einen (Spiel-)Würfel im Schnitt werfen, bis zweimal direkt hintereinander eine Sechs erscheint?

Bonusaufgabe:

  • Wie oft muss man im Schnitt würfeln, bis dreimal direkt hintereinander eine Sechs erscheint?

Welche der Folgen KKK (= Kopf-Kopf-Kopf), KKZ, KZK, ZKK kommt im Schnitt am frühesten?

Es bieten sich jede Menge ähnliche Fragen an. Finde selbst eine oder suche dir eine aus der folgenden Liste aus!

Wie oft muss man einen Würfel im Schnitt werfen,

  1. bis insgesamt 10 Mal eine 6 gewürfelt wurde?
  2. bis insgesamt 20 Mal eine 6 gewürfelt wurde?
  3. bis eine 6 gewürfelt wird?
  4. bis zweimal direkt hintereinander dieselbe Zahl (also ein Pasch) erscheint?
  5. bis direkt hintereinander 1, 2 erscheint?
  6. bis direkt hintereinander 6, 6 oder direkt hintereinander 1, 2 erscheint?
  7. bis direkt hintereinander 6, 6 oder direkt hintereinander 1, 1 erscheint?
  8. bis zwei Päsche hintereinander erscheinen? (Entscheide selbst, ob dreimal hintereinander dieselbe Zahl (etwa 3, 3, 3) als zwei Päsche hintereinander zählen oder nicht.)
  9. bis 5 Mal hintereinander eine Sechs erscheint? (Empehlung, wenn bekannt: Verwende eine Liste!)
  10. bis 10 Mal (oder gar 1000 Mal) hintereinander eine Sechs erscheint? (Achtung: Nicht zu viele Simulationen durchführen.)

Welche Folge von drei Zahlen kommt im Schnitt am frühesten?

Wie viele Päsche (alias zweimal dieselbe Zahl direkt hintereinander) hat man im Schnitt geworfen, bevor man einen Sechserpasch wirft?

Wie lange dauert es im Schnitt, bis man eine “grosse Strasse” 123456 wirft? Wie viele “kleine Strassen” (also 12345 oder 23456) hat man im Schnitt vorher geworfen?

Wie lange dauert es im Schnitt, bis die Summe aller gewürfelten Zahlen 100 übersteigt?

Bei wieviel Prozent der Versuche kommt man beim wiederholten würfeln genau bei 100 an?

Würdest du darauf wetten, dass man in vier Würfen mit einem Würfel mindestens eine Sechs würfelt?

Würdest du darauf wetten, dass man in 38 Würfen mit einem Würfel mindestens zwei Sechser direkt hintereinander würfelt? (Man könnte 38 durch irgendeinen andere Zahl ersetzen.)

Wenn man mit jeweils mit zwei Würfeln gleichzeitig würfelt, wie lange muss man im Schnitt auf den Sechserpasch warten?

Bei vielen dieser Fragestellungen legen die Simulationen nahe, dass als Ergebnis eine ganze Zahl heraus kommt (also etwa 6 oder 42, aber nicht 43.5). Genauer vermute ich, dass dies bei allen Fragestellungen der Fall ist, wo man auf eine einzige Folge wartet.

Wenn das so ist (was man sich mathematisch recht leicht überlegen können sollte), gibt es dafür einen offensichtlichen Grund?

Per Zustandsdiagramm, Übergangswahrscheinlichkeiten, an Tafel. (Die wesentliche Idee: Für jeden Zustand $A$ eine Variable $a$ einführen, die angibt, wie lange man im Schnitt noch braucht (die Variablen der Zielzustände haben den Wert Null). Dann gilt etwa $a=\frac 16(1+b)+\frac 56 (1+c)=1+\frac 16b+\frac 56c$, wenn man vom Zustand $A$ mit Wahrscheinlichkeit $\frac 16$ in den Zustand $B$ gelangt und mit Wahrscheinlichkeit $\frac 56$ in den Zustand $C$. Beachte: Die beiden Einser kommen (bzw. der Einser kommt) daher, dass man genau einmal würfeln muss, um in den nächsten Zustand zu kommen. Es ergibt sich ein Gleichungssytem, das man löse.)

Bemerkungen:

  • Erst mit einer mathematisch korrekten Begründung kann man absolut sicher sein, die Antwort zu kennen (und hoffentlich stimmt sie mit der auf Grund der Simulationen erwarteten Antwort überein.)
  • Gewisse Fragen (“Wie lange dauert es im Schnitt für 100 Sechser?”) lassen sich durch Simulationen nicht in realistischer Zeit beantworten - das geht nur durch Nachdenken!

Simulation von Roulette

Wenn man im Wesentlichen weiss, wie Roulette funktioniert, muss man von den Roulette-Regeln für die folgende Aufgabe nur das Folgende wissen (französisches Roulette ohne Prison-Regel):

  • Die Zahlen von 0 bis 37 fallen gleichhäufig.
  • Die Zahl Null ist grün, die Hälfte der restlichen Zahlen ist rot, die andere Hälfte schwarz.
  • Wer seinen Einsatz (etwa 30 Jetons) auf Rot setzt, verliert ihn bei Null oder einer der 18 schwarzen Zahlen; bei einer der 18 roten Zahlen bekommt er seinen Einsatz zurück und zusätzlich dieselbe Summe von der Spielbank (also 30 Jetons zurück + 30 Jetons von der Bank).

Jemand vermutet (irrtümlich), dass er mit der folgenden Strategie langfristig beim Roulette gewinnen kann:

Er startet mit einem Anfangsvermögen von 100 Jetons.
Er beginnt mit einem Einsatz von 1 Jeton.
Solange er nicht alles Geld verloren hat oder sein Anfangsvermögen verdoppelt hat, macht er das folgende:
    Er setzt seinen Einsatz auf Rot.
    (Nun folgt der Wurf der Kugel.)
    Wenn die Kugel in einem roten Fach zu liegen kommt (er also gewinnt),
        startet er beim nächsten Mal wieder mit einem Einsatz von 1 Jeton;
    sonst (im Fall, dass die Kugel schwarz oder grün (= Null) liefert)
        verdoppelt er seinen Einsatz für das nächste Mal, falls er dafür genügend Geld übrig hat.
        Sonst setzt er beim nächsten Mal all sein verbliebenes Vermögen.        

Die Idee dahinter: Wenn er beispielsweise 5 Mal verloren hat, also 1+2+4+8+16 = 31 Jetons verloren hat, so setzt er beim nächsten Mal 32 Jetons und macht so seinen Verlust weg und gewinnt sogar einen Jeton dazu.

Schreibe ein Programm, das 10'000 Spielbankbesuche simuliert, jeweils mit einem Startvermögen von 100 Jetons. Auszugeben sind am Ende:

  • Durchschnittsgewinn pro Spielbankbesuch (dieser wird negativ sein - de facto verliert man also langfristig);
  • Anteil der Spielbankbesuche in Prozent, die mit einer Vermögensverdopplung enden.

Gerüst des fast fertigen Programms, falls benötigt

Gerüst des fast fertigen Programms, falls benötigt

Alles zwischen den Fragezeichen ist in Code umzusetzen.

roulette-simulation.py
from random import *
 
ANZAHL_SIMULATIONEN = 10000
ANFANGSVERMOEGEN = 100
 
anzahl_gewinne = 0
gesamt_gewinn = 0 # Kann und wird auch negativ sein.
 
for simulationsnummer in range(1, ANZAHL_SIMULATIONEN + 1):
    vermoegen = ANFANGSVERMOEGEN
    einsatz = 1
    while ??? weder pleite noch ANFANGSVERMOEGEN verdoppelt ???:
        if randrange(37) < 18:
            # die Zufallszahl ist eine der 18 Zahlen von 0 bis 17,
            # was wir als Rot interpretieren, 
            # d.h. der Spieler gewinnt und 
            # bekommt seinen Einsatz von der Bank.
            ??? Passe die Variable "vermoegen" an ???    
            ??? Passe die Variable "einsatz" an ??? 
        else:
            # Farbe ist Schwarz, 
            # d.h. der Spieler verliert seinen Einsatz.
            ??? Passe die Variable "vermoegen" an ???    
            einsatz = min(2 * einsatz, vermoegen)
    if ??? Vermögen verdoppelt ???:
        ??? Zähle den erzielten Gewinn zu "gesamt_gewinn" hinzu. ???
        ??? Erhöhe "anzahl_gewinne". ???
    else:
        # Alles verloren.
        ??? Passe die Variable "gesamt_gewinn" an ???
 
print(f'Anteil erfolgreicher Spielbankbesuche in Prozent: {100 * anzahl_gewinne / ANZAHL_SIMULATIONEN}')
print(f'Durchschnittsgewinn: {gesamt_gewinn / ANZAHL_SIMULATIONEN}')

Weitere mögliche Fragestellungen:

  • Wie wären die Gewinnchancen, wenn es die grüne Null nicht gäbe?
  • Was passiert, wenn man dieselbe Verdopplungsstrategie verwendet, jedoch erst bei einer Verzehnfachung des Anfangsvermögens nach Hause geht?
  • Wie viele Runden spielt man durchschnittlich im Gewinnfall?
  • Wie viele Runden spielt man durchschnittlich im Verlustfall?

Ideen

(eventuell: Listen und dann Ergebnis-Darstellung durch Säulendiagramme)

(in Schublade: Turtle-Grafik selbst schreiben, ausgehend von kantigrafik.)

Lösungsvorschlag

Simulationsprogramm für die durchschnittliche Dauer bis zweimal Kopf, zusätzlich mit Häufigkeits-Säulendiagramm

Simulationsprogramm für die durchschnittliche Dauer bis zweimal Kopf, zusätzlich mit Häufigkeits-Säulendiagramm

muenz-simulation.py
from random import *
from matplotlib import pyplot
 
gesamtzahl_wuerfe = 0
ANZAHL_SIMULATIONEN = 1000000
 
def zeichneSaeulendiagramm(xWerte, yWerte, titel, xBeschriftung, yBeschriftung, xSchrittweite):
    # xWerte: Liste der Werte auf der horizontalen Achse
    # yWerte: Liste der zugehörigen Funktionswerte
    # titel: Titel der Graphik
    # xBeschriftung: Text unter der horizontalen Achse
    # yBeschriftung: Text links der vertikalen Achse
    pyplot.figure("Säulendiagramm-Fenster")
    pyplot.title(titel)
    pyplot.xlabel(xBeschriftung)
    pyplot.ylabel(yBeschriftung)
    pyplot.bar(xWerte, yWerte, color='blue')
    # pyplot.xticks(xWerte, xWerte, rotation='horizontal')
    pyplot.xticks(range(min(xWerte), max(xWerte)+1, xSchrittweite))
    pyplot.tight_layout()
    pyplot.show()
 
haeufigkeit_bei_dauer = []
for simulationsnummer in range(ANZAHL_SIMULATIONEN):
    if simulationsnummer % 100000 == 0 and simulationsnummer > 0:
        print(simulationsnummer, gesamtzahl_wuerfe / simulationsnummer)
    letzter_wurf = 'nicht definiert'
    vorletzter_wurf = 'nicht definiert'
    anzahl_wuerfe = 0
    while not (letzter_wurf == 1 and vorletzter_wurf == 1):
        aktueller_wurf = randrange(1,3)
        anzahl_wuerfe = anzahl_wuerfe + 1
        vorletzter_wurf = letzter_wurf
        letzter_wurf = aktueller_wurf
    while anzahl_wuerfe > len(haeufigkeit_bei_dauer) - 1:
        haeufigkeit_bei_dauer.append(0)
    haeufigkeit_bei_dauer[anzahl_wuerfe] = haeufigkeit_bei_dauer[anzahl_wuerfe] + 1    
    gesamtzahl_wuerfe = gesamtzahl_wuerfe + anzahl_wuerfe
print(gesamtzahl_wuerfe / ANZAHL_SIMULATIONEN)
# print(haeufigkeit_bei_dauer)
zeichneSaeulendiagramm(range(len(haeufigkeit_bei_dauer)), haeufigkeit_bei_dauer, 'Anzahl der Experimente bei gegebener Anzahl von Würfen (Achtung, evtl. 10er-Potenzen auf vertikaler Achse)', 'Anzahl von Würfen', 'Anzahl der Experimente', 1)

Simulationsprogramm für Roulette mit Verdopplungsstrategie

Simulationsprogramm für Roulette mit Verdopplungsstrategie

roulette-simulation.py
from random import *
 
ANZAHL_SIMULATIONEN = 100000
ANFANGSVERMOEGEN = 100
 
anzahl_gewinne = 0
gesamt_gewinn = 0 
 
for simulationsnummer in range(1, ANZAHL_SIMULATIONEN + 1):
    vermoegen = ANFANGSVERMOEGEN
    einsatz = 1
    while 0 < vermoegen < 2 * ANFANGSVERMOEGEN:
        if randrange(37) < 18:
            # die Zufallszahl ist eine der 18 Zahlen von 0 bis 17,
            # was wir als Rot interpretieren, 
            # d.h. der Spieler gewinnt und 
            # bekommt seinen Einsatz von der Bank.
            vermoegen = vermoegen + einsatz
            einsatz = 1
        else:
            # Farbe ist Schwarz, 
            # d.h. der Spieler verliert seinen Einsatz.
            vermoegen = vermoegen - einsatz
            einsatz = min(2 * einsatz, vermoegen)
    if vermoegen >= 2 * ANFANGSVERMOEGEN:
        gesamt_gewinn = gesamt_gewinn + vermoegen - ANFANGSVERMOEGEN
        anzahl_gewinne = anzahl_gewinne + 1
    else:
        # Alles verloren.
        gesamt_gewinn = gesamt_gewinn - ANFANGSVERMOEGEN
 
print(f'Anteil erfolgreicher Spielbankbesuche in Prozent: {100 * anzahl_gewinne / ANZAHL_SIMULATIONEN}')
print(f'Durchschnittsgewinn: {gesamt_gewinn / ANZAHL_SIMULATIONEN}')

2)
genauer handhelt es sich um Pseudozufallszahlen
3)
Die vorsichtige Formulierung (statt “beantworte die folgende Frage!”) ist gewählt, denn: (1) Wenn auch extrem unwahrscheinlich, könnte es beispielsweise sein, dass der Computer in jeder Simulation sofort zweimal Kopf geworfen hat. (2) Es könnte sein, dass die vom Computer erzeugten Pseudozufallszahlen nicht wirklich zufällig sind und etwa Kopf ein ganz kleines bisschen öfter vorkommt als Zahl. (3) Das Programm könnte falsch sein.
  • lehrkraefte/snr/informatik/glf22/python/simulationen.txt
  • Last modified: 2024/03/20 14:01
  • by Olaf Schnürer