EINFÜHRUNG |
Im täglichen Leben sind viele Phänomene durch den Zufall bestimmt und du musst oft Entscheide auf der Basis von Wahrscheinlichkeiten fällen, beispielsweise ob du auf Grund einer Unfallwahrscheinlichkeit das eine oder andere Verkehrsmittel wählst. Der Computer kann in solchen Situationen ein wichtiges Hilfsmittel sein, dann mit ihm kannst du das Gefahrenpotential als Simulation untersuchen, ohne selbst gefährdet zu werden. Im Folgenden gehst du davon aus, dass du dich verirrt hast und den Heimweg nicht kennst. Dabei betrachtest du eine Bewegung, Random Walk oder Irrfahrt genannt, die in einzelnen Zeitschritten erfolgt, wobei die Schritte in zufälliger Richtung gewählt werden. Obschon eine solche Bewegung nicht genau der Realität entspricht, lernst du wichtige Eigenschaften kennen, die sich nachher auf reale Systeme, beispielsweise in der Finanzmathematik zur Modellierung von Aktienkursen oder auf die Brownsche Molekularbewegung, anwenden lassen. |
EINDIMENSIONALER RANDOM WALK |
Wiederum eignet sich die Turtlegrafik hervorragend, um die Simulation grafisch zu untermalen. Die Turtle soll sich zur Zeit 0 an der Stelle x = 0 befinden und sich mit gleich langen Schritten auf der x-Achse bewegen. Zu jedem Zeitschritt "überlegt" sie, ob sie einen Rechts- oder Linksschritt macht. In deinem Programm fällt sie den Entscheid mit der gleichen Wahrscheinlichkeit p = ½. (symmetrischer Random Walk).
from gturtle import * from gpanel import * from random import randint makeTurtle() makeGPanel(-50, 550, -480, 480) windowPosition(880, 10) drawGrid(0, 500, -400, 400) title("Mean distance versus time") lineWidth(2) setTitle("Random Walk") t = 0 while t < 500: if randint(0, 1) == 1: setHeading(90) else: setHeading(-90) forward(10) x = getX() draw(t, x) t += 1 print("All done") |
MEMO |
Es erstaunt dich sicher, dass sich in den meisten Fällen die Turtle mit der Zeit vom Startpunkt wegbewegt, obschon sie bei jedem Schritt mit gleicher Wahrscheinlichkeit einen Rechts- oder Linksschritt macht. Allerdings kann sie sich dabei nach rechts oder links wegbewegen. Um dieses wichtige Ergebnis genauer zu untersuchen, bestimmst du im nächsten Programm für je 1000 Laufversuche den Mittelwert d der Distanz vom Startpunkt nach t Schritten. |
DAS WURZEL-AUS-T-GESETZ |
Du führst die Simulation für eine bestimmte feste Schrittzahl t an fester y-Position 1000 Mal durch und zeichnest die Endposition als Punkt ein. Damit du nicht zu lange auf die Resultate warten musst, versteckst du die Turtle und zeichnest auch keine Spuren. Bei jedem Simulationsversuch bestimmst du den Abstand r der Endposition vom Startpunkt. Für jede Versuchsreihe mit einen bestimmten t ergibt sich so der Mittelwert d der Abtände r, den du in einer GPanel-Grafik einträgst. from gturtle import * from gpanel import * from math import sqrt from random import randint makeTurtle() makeGPanel(-100, 1100, -10, 110) windowPosition(850, 10) drawGrid(0, 1000, 0, 100) title("Mean distance versus time") ht() pu() for t in range(100, 1100, 100): setY(250 - t / 2) label(str(t)) count = 0 repeat 1000: repeat t: if randint(0, 1) == 1: setHeading(90) else: setHeading(-90) forward(2.5) dot(3) r = abs(getX()) count += r setX(0) d = count / 1000 print("t =", t, "d =", d, "q = d / sqrt(t) =", d / sqrt(t)) draw(t, d) fillCircle(5) print("all done") |
MEMO |
Wie du in der grafischen Darstellung siehst, nimmt der Mittelwert der Distanz zum Startpunkt mit zunehmender Schrittzahl bzw. Zeit t zu. In der Konsole berechnest du noch den Quotienten q = d / sqrt(t). Da dieser nahezu konstant ist, liegt die Vermutung nahe, dass d = q * sqrt(t) exakt gilt, oder: Die mittlere Distanz zum Startpunkt nimmt mit der Wurzel aus der Zeit zu. |
DRUNKEN MAN'S WALK |
Noch etwas spannender wird es, wenn sich die Turtle in zwei Dimensionen bewegen kann. Man spricht dann von einem zweidimensionalen Random Walk. Dabei macht die Turtle immer noch gleich lange Schritte, wählt aber bei jedem Schritt eine zufällige Richtung.
from gturtle import * from gpanel import * from math import sqrt makeTurtle() makeGPanel(-100, 1100, -10, 110) windowPosition(850, 10) drawGrid(0, 1000, 0, 100) title("Mean distance versus time") ht() pu() for t in range(100, 1100, 100): count = 0 clean() repeat 1000: repeat t: fd(2.5) setRandomHeading() dot(3) r = math.sqrt(getX() * getX() + getY() * getY()) count += r home() d = count / 1000 print("t =", t, "d =", d, "q = d / sqrt(t) =", d / sqrt(t)) draw(t, d) fillCircle(5) delay(2000) print("all done") |
MEMO |
Auch in zwei Dimensionen gilt das Wurzel-aus-t-Gesetz. Für Gasteilchen hat kein geringerer als Albert Einstein diese Gesetzmässigkeit 1905 in seiner berühmten Abhandlung "Über die von der molekularkinetischen Theorie der Wärme geforderte Bewegung von in ruhenden Flüssigkeiten suspendierten Teilchen" bewiesen und damit eine theoretische Erklärung für die Brownsche Bewegung gegeben. Ein Jahr später hat M. Smoluchowski mit einer anderen Überlegung dasselbe Resultat gefunden. |
BROWNSCHE BEWEGUNG |
Bereits 1827 hatte der Biologe Robert Brown beim Mikroskopieren beobachtet, dass Pollenkörner in einem Wassertropfen unregelmässige zuckende Bewegungen machen. Er vermutete als Ursache eine in den Pollen innewohnende Lebenskraft. Erst mit der Entdeckung der molekularen Struktur der Materie wurde klar, dass die thermische Bewegung der Wassermoleküle, die mit dem Pollen zusammenstossen, für das Phänomen verantwortlich ist. Die Brownsche Bewegung lässt sich sehr schön in einem Computerexperiment demonstrieren, wo die Moleküle als kleine Kugeln modelliert werden, die beim Zusammenstoss ihre Geschwindigkeit austauschen. Mit JGameGrid lässt sich die Simulation einfach realisieren, da du eine Teilchenkollision als Ereignis auffassen kannst. Dazu leitest du die Klasse CollisionListener von GGActorCollisionListener ab und implementierst den Callback collide(). Jedem Partikel fügst du mit addActorCollisionListener() den Listener hinzu. Die Art und die Grösse der Kollisionsarea kannst du mit setCollisionCircle() einstellen. Du teilst die 40 Teilchen zu Beginn in 4 Geschwindigkeitsgruppen ein. from gamegrid import * # =================== class Particle ==================== class Particle(Actor): def __init__(self): Actor.__init__(self, "sprites/ball.gif", 2) # Called when actor is added to gamegrid def reset(self): self.oldPt = self.gameGrid.toPoint(self.getLocationStart()) def advance(self, distance): pt = self.gameGrid.toPoint(self.getNextMoveLocation()) dir = self.getDirection() # Left/right wall if pt.x < 5 or pt.x > w - 5: self.setDirection(180 - dir) # Top/bottom wall if pt.y < 5 or pt.y > h - 5: self.setDirection(360 - dir) self.move(distance) def act(self): self.advance(3) if self.getIdVisible() == 1: pt = self.gameGrid.toPoint(self.getLocation()) self.getBackground().drawLine(self.oldPt.x, self.oldPt.y, pt.x, pt.y) self.oldPt.x = pt.x self.oldPt.y = pt.y # =================== class CollisionListener ========= class CollisionListener(GGActorCollisionListener): # Collision callback: just exchange direction and speed def collide(self, a, b): dir1 = a.getDirection() dir2 = b.getDirection() sd1 = a.getSlowDown() sd2 = b.getSlowDown() a.setDirection(dir2) a.setSlowDown(sd2) b.setDirection(dir1) b.setSlowDown(sd1) return 10 # Wait a moment until collision is rearmed # =================== Global section ==================== def init(): collisionListener = CollisionListener() for i in range(nbParticles): particles[i] = Particle() # Put them at random locations, but apart of each other ok = False while not ok: ok = True loc = getRandomLocation() for k in range(i): dx = particles[k].getLocation().x - loc.x dy = particles[k].getLocation().y - loc.y if dx * dx + dy * dy < 300: ok = False addActor(particles[i], loc, getRandomDirection()) # Select collision area particles[i].setCollisionCircle(Point(0, 0), 8) # Select collision listener particles[i].addActorCollisionListener(collisionListener) # Set speed in groups of 10 if i < 10: particles[i].setSlowDown(2) elif i < 20: particles[i].setSlowDown(3) elif i < 30: particles[i].setSlowDown(4) # Define collision partners for i in range(nbParticles): for k in range(i + 1, nbParticles): particles[i].addCollisionActor(particles[k]) particles[0].show(1) w = 400 h = 400 nbParticles = 40 particles = [0] * nbParticles makeGameGrid(w, h, 1, False) setSimulationPeriod(10) setTitle("Brownian Movement") show() init() doRun() |
MEMO |
Die Moleküle sind in der Klasse Particle modelliert, die von Actor abgeleitet ist. Sie haben zwei Spritebilder, ein rotes und ein grünes für ein besonders ausgezeichnetes Molekül, dessen Bahn du verfolgst. Das grüne Bild entspricht der Sprite-ID = 1, auf die du in act() testest, um mit drawLine() die Spur zu zeichnen. |
AUFGABEN |
|