Quelle: https://beruhmte-zitate.de/werk/per-anhalter-durch-die-galaxis-6743/
Heute ist die Aufgabe, eine Gravitationssimulation zu entwickeln, welche die Planeten unseres Sonnensystems darstellt und simuliert.
Ihr könnt euch hier eine in JavaScript geschriebene 3D Version davon anschauen: https://mgvez.github.io/jsorrery/. Wir werden uns jedoch auf eine 2D Version beschränken.
Für diese Aufgabe verwenden wir wieder eine lokale Python-Installation, wie sie auch schon für die Aufgabe "Breakout" verwendet wurde. Die Installationsanleitung haben wir noch einmal hier angehängt:
Möglicherweise funktioniert Python 3 auf deinem System bereits. Du musst Python 3 für diese Aufgabe verwenden, denn der Umgang mit Sonderzeichen (Umlaute etc.) ist in Python 2 nicht besonders komfortabel. Überprüfe zunächst deine Python Version; dazu musst du zunächst ein Terminal öffnen:
Führe dann den folgenden Befehl aus: "python3 --version" (oder wenn dies eine Fehlermeldung wirft, versuche es mit "python --version"). Wenn du bei einem der Befehle keine Fehlermeldung bekommst und in der Konsole etwas ähnliches steht wie "Python 3.7.5", kannst du den dazugehörigen Befehl ab nun zum Starten von Python 3 verwenden. Wichtig: wenn hier etwas wie "Python 2.7.17" steht, hast du eine Installation der Version 2, welche für die Aufgabe nicht verwendet werden kann. Wenn du beidesmal eine Fehlermeldung bekommst, gehe direkt zur Installation.
Ab hier wird die Aufgabe mit dem Befehl "python3" gestellt, auf deinem System kann aber auch der Befehl "python" die korrekte Installation der Version 3 starten (wie zuvor beschrieben).
Führe nun den folgenden Befehl aus, um zu testen, ob du das GUI-Toolkit Tkinter installiert hast: "python3 -m tkinter". Wenn du ein kleines Testfenster siehst, hast du Python und Tkinter bereits korrekt installiert.
Wenn du eine Fehlermeldung bekommen hast, lade dir Python (mit Tkinter) kostenfrei hier herunter:
Jetzt sollte deine Python 3-Installation mit Tkinter funktionieren. Um dies zu testen, kopiere die folgende Vorlage in eine neue leere Datei "gravitationssimulation.py". Achte darauf, dass du in deinem Editor zum Abspeichern "UTF-8" als Encoding verwendest, sonst funktionieren die Umlaute und Sonderzeichen in der deutschen Sprache nicht.
Mögliche Editoren zum Bearbeiten von Python-Skripten sind etwa: PyCharm, Notepad++, Sublime Text, Atom Editor, Eclipse mit Python Plugins, vim (Linux und MacOS), gedit (Linux). Aber die Wahl deines Lieblingseditors steht dir frei.
Auf Windows solltest du die Datei mit einem Doppelklick ausführen können. Auf anderen Systemen musst du mit dem Terminal in das Verzeichnis wechseln, in dem diese neue Datei liegt. Das kannst du mit dem Befehl "cd </vollständiger/Pfad/zu/der/Datei>" machen. Dann kannst du die Datei mit "python3 breakout.py" ausführen. Achte darauf, dass du Python 3 verwendest.
Wenn alles richtig installiert ist, erscheint ein Fenster, in dem die Simulation Form annehmen soll.
Du musst den Code in der Datei "gravitationssimulation.py" erweitern.
Nach Wikipedia ist die Gravitation (von lateinisch gravitas für "Schwere") eine der vier Grundkräfte der Physik. Erstmals beschrieben wurde die Gravitationskraft von Sir Isaac Newton. (https://de.wikipedia.org/wiki/Gravitation, https://de.wikipedia.org/wiki/Newtonsches_Gravitationsgesetz).
Nach dem Newtonsche Gravitationsgesetz lässt sich die auf einen Körper wirkende Gravitationskraft $F_1$ in $N$(ausgelöst durch einen anderen Körper) folgendermaßen bestimmen:
$$F_1 = G * \frac{m_1 * m_2}{r^2}$$Dabei ist $G$ die Gravitationskonstante ($G \approx 6.67 * 10 ^{-11} \frac{m^3}{kg * s^2}$).
$m_1$ ist die Masse des Körpers in $kg$, für den die Kraft berechnet wird
$m_2$ ist die Masse des anderen Körpers in $kg$
$r$ ist der Abstand der beiden Körper in $m$
Auf den anderen Körper, der die Masse $m_2$ besitzt, wirkt dieselbe Kraft, nur ist ihre Richtung entgegengesetzt. Das Newtonsche Gravitationsgesetz ist ausreichend, um eine Simulation unseres Sonnensystems zu entwickeln. Jedoch gibt es noch einige Punkte, auf die wir achten müssen:
Da wir unsere Simulation komplett mit SI-Einheiten entwickeln, können wir die Einheiten einfach weglassen (https://de.wikipedia.org/wiki/Internationales_Einheitensystem).
Die Körper, von denen in der Formel gesprochen wird, sind die Planeten (und Zwergplaneten und die Sonne natürlich) des Sonnensystems.
Möchte man nun mehr als zwei Körper simulieren, wie dies für unser Sonnensystem nötig ist, so müssen für die Kraft, die auf einen Körper wirkt, auch alle anderen Körper des Systems betrachtet werden. Die Kräfte, die durch die anderen Körper auf den betrachteten Körper ausgeübt werden, werden dann einfach addiert.
Wir betrachten in einer solchen Simulation nur einzelne Zeitpunkte, die Zeit schreitet in diskreten Schritten von einem Simulationszeitpunkt zum nächsten voran.
Für unsere Simulation müssen wir auch Startbedingungen vorgeben: die Sonne ist in der Mitte, und die Positionen der Planeten auf ihren Umlaufbahnen, die Massen und Bahngeschwindigkeiten sind bekannt.
Beispielsweise wollen wir nun die Gesamtkraft, die zum aktuellen Simulationszeitpunkt auf die Erde wirkt, berechnen. Am meisten Einfluss hat die Sonne, aber auch die anderen Planeten wirken eine Gravitationskraft auf die Erde aus. Daher wird zunächst jeweils der Betrag dieser Kräfte nach der Formel bestimmt.
Wichtig dabei ist nun, dass die Kraft in eine bestimmte Richtung zeigt. Die Gravitationskraft der Sonne wirkt in Richtung Sonne, die des Jupiter in Richtung des Jupiter, usw.. Der Betrag, den wir zuvor berechnet haben, muss also noch mit einem normalisierten Vektor multipliziert werden, der in Richtung des anderen Körpers zeigt. Normalisierte Vektoren haben die Länge 1.
Nun haben wir die Beträge und Richtungen der Kräfte auf die Erde berechnet, also die Kraftvektoren zu jedem anderen Körper bestimmt. Daraus müssen wir nun die Position bestimmen, um die Körper in der Simulation anzuzeigen.
Die Position $pos_t$ zum Zeitpunkt $t$ lässt sich aus der vorherigen Position $pos_{t-1}$ (oder beim ersten Zeitschritt auch Startposition) und einer Positionsänderung $d pos$ bestimmen.
$$pos_t = pos_{t-1} + d pos$$Die Positionsänderung $d pos$ kann man über die Geschwindigkeit des Körpers $v$ und den vorgegebenen Zeitschritt $dt$ bestimmen.
$$ d pos = v * dt$$Die Geschwindigkeit $v$ kann wiederum über die Beschleunigung $a$ berechnet werden.
$$ v = a * dt$$Und es gibt noch eine weitere Formel, welche die Beschleunigung $a$ aus der Kraft $F_1$ und der Masse $m_1$ des betrachteten Körpers angibt.
$$ F_1 = m_1 * a$$Wichtig: Kraft, Beschleuningung, Geschwindigkeit und Positionen sind 2D Vektoren!
Wir erhalten also folgende Berechnungskette für die Simulation der Erde im Sonnensystem:
berechne den Kraftvektor -> berechne daraus den Beschleunigungsvektor -> dieser aufsummiert ergibt die aktuelle Geschwindigkeit -> diese aufsummiert ergibt die aktuelle Position
Diese Kette muss auch für alle anderen Körper ausgeführt werden.
Nun habt ihr alle Formeln, die ihr benötigt, um die Simulation zu implementieren. Diese Aufgabe ist eher eine Knobelaufgabe, ihr bekommt weniger Anleitung, wie sie zu lösen ist und könnt selber kreativ werden.
Im Folgenden ist die Vorlage, die euch eine Benutzeroberfläche zur Verfügung stellt. Außerdem werden die Startbedingungen der Planeten darin vorgegeben und eine Klasse "Vektor2D", mit der ihr Kraft-, Geschwindigkeits-, Beschleunigungs- und Positionsvektoren abbilden könnt. Zudem findet ihr eine Klasse "Planet", die alle nötigen Attribute eines Körpers beinhaltet. Ihr könnt auch mit den Startbedingungen experimentieren: was wäre z. B., wenn der Pluto dieselbe Masse wie die Sonne hätte? Spoiler: das würde unser Sonnensystem ziemlich durcheinanderbringen...
# Das ist die Vorlage, die du in die neue leere Datei "gravitation.py" kopieren kannst.
# Unter http://particlesandbox.com/ kannst du eine Planetensimulation im Browser ausprobieren.
# Das Encoding muss UTF-8 sein wegen der Sonderzeichen in der deutschen Sprache:
# -*- coding: utf-8 -*-
# importiere benötigte Module
from tkinter import *
import random
import math
# Konstanten für das Fenster
fenster_breite = 850
fenster_höhe = 850
# die Planeten des Sonnensystems
planeten = []
# Gravitationskonstante G
G = 6.67428e-11
# es soll ein Tag pro Zeitschritt vergehen
zeitschritt = 24 * 3600
# eine astronomische Einheit sind 149,6 Millionen km,
# das ist der Abstand von der Sonne zur Erde
AE = 149.6e6 * 1000
# lege einen Skalierungsfaktor für die Simulation fest,
# so dass die Anzeige im Fenster möglich ist
skalierungsfaktor = 1
"""
Ein Vektor, der zwei Elemente beinhaltet.
Attribute:
x: erstes Element (Kommazahl)
y: zweites Element (Kommazahl)
"""
class Vektor2D:
def __init__(self, x, y):
self.x = x
self.y = y
def addiereElementweise(self, anderer):
return Vektor2D(self.x + anderer.x, self.y + anderer.y)
def multipliziereSkalar(self, skalar):
return Vektor2D(self.x * skalar, self.y * skalar)
def dividiereSkalar(self, skalar):
return Vektor2D(self.x / skalar, self.y / skalar)
def distanzQuadrat(self, anderer):
distanz_x = abs(self.x - anderer.x)
distanz_y = abs(self.y - anderer.y)
return distanz_x ** 2 + distanz_y ** 2
def richtungNormalisiert(self, anderer):
richtung = Vektor2D(anderer.x - self.x, anderer.y - self.y)
länge = math.sqrt(richtung.x ** 2 + richtung.y ** 2)
return Vektor2D(richtung.x / länge, richtung.y / länge)
"""
Ein Planet.
Attribute:
name: Name des Planets (Zeichenkette)
masse: Masse in kg (Kommazahl)
position: position in m (Vektor2D)
geschwindigkeit: geschwindigkeit in m/s (Vektor2D)
"""
class Planet:
def __init__(self, name, masse, position, geschwindigkeit, farbe):
self.name = name
self.masse = masse
self.position = position
self.geschwindigkeit = geschwindigkeit
self.farbe = farbe
self.kraft = Vektor2D(0, 0)
self.spur = []
def berechneAktuelleKraft(self, anderer):
kraftBetrag = G * self.masse * anderer.masse / self.position.distanzQuadrat(anderer.position)
return self.position.richtungNormalisiert(anderer.position).multipliziereSkalar(kraftBetrag)
# initialisiere die Planeten
def initialisiere():
# lösche alle Planeten, um sie dann zu reinitialisieren
planeten.clear()
# füge alle Planeten des Sonnensystems und Pluto hinzu
sonne = Planet('Sonne', 1.98892 * 10**30, Vektor2D(0, 0), Vektor2D(0, 0), 'yellow')
planeten.append(sonne)
merkur = Planet('Merkur', 3.301 * 10**23, Vektor2D(0.3871*AE, 0), Vektor2D(0, 47.36 * 1000), 'khaki')
planeten.append(merkur)
venus = Planet('Venus', 4.8685 * 10**24, Vektor2D(0.723*AE, 0), Vektor2D(0, 35.02 * 1000), 'salmon1')
planeten.append(venus)
erde = Planet('Erde', 5.9742 * 10**24, Vektor2D(1*AE, 0), Vektor2D(0, 29.783 * 1000), 'blue')
planeten.append(erde)
mars = Planet('Mars', 6.417 * 10**23, Vektor2D(1.524*AE, 0), Vektor2D(0, 24.07 * 1000), 'red')
planeten.append(mars)
jupiter = Planet('Jupiter', 1.899 * 10**27, Vektor2D(5.204*AE, 0), Vektor2D(0, 13.06 * 1000), 'gold')
planeten.append(jupiter)
saturn = Planet('Saturn', 5.683 * 10**26, Vektor2D(9.582*AE, 0), Vektor2D(0, 9.68 * 1000), 'orange')
planeten.append(saturn)
uranus = Planet('Uranus', 8.681 * 10**25, Vektor2D(19.201*AE, 0), Vektor2D(0, 6.81 * 1000), 'slate blue')
planeten.append(uranus)
neptun = Planet('Neptun', 1.024 * 10**26, Vektor2D(30.047*AE, 0), Vektor2D(0, 5.43 * 1000), 'cornflower blue')
planeten.append(neptun)
pluto = Planet('Pluto', 1.303 * 10**22, Vektor2D(39.482*AE, 0), Vektor2D(0, 4.67 * 1000), 'red')
planeten.append(pluto)
# diese Funktion wird jeden Zeitschritt aufgerufen,
# das Programm ist also eine Simulation
def nächsterZeitschritt():
# die Zeichenfläche leeren
zeichenfläche.delete("all")
# für jeden Planeten die auf ihn wirkende Kraft berechnen
for p in planeten:
kraftSumme = Vektor2D(0, 0)
for anderer in planeten:
if not p is anderer:
kraft = p.berechneAktuelleKraft(anderer)
kraftSumme = kraftSumme.addiereElementweise(kraft)
p.kraft = kraftSumme
# aktualisiere die Geschwindigkeit und Position jedes Planeten
for p in planeten:
p.geschwindigkeit.x += p.kraft.x / p.masse * zeitschritt
p.geschwindigkeit.y += p.kraft.y / p.masse * zeitschritt
p.position.x += p.geschwindigkeit.x * zeitschritt
p.position.y += p.geschwindigkeit.y * zeitschritt
p.spur.append(Vektor2D(p.position.x, p.position.y))
if len(p.spur) > 50:
del p.spur[0];
# alle Planeten zeichnen
for p in planeten:
r = 1.5 * math.log(math.log(p.masse / 10**22))
x = fenster_breite / 2 + p.position.x * skalierungsfaktor / (AE * 0.1)
y = fenster_höhe / 2 + p.position.y * skalierungsfaktor / (AE * 0.1)
zeichenfläche.create_circle(x, y, r, fill=p.farbe, outline=p.farbe)
for alte_position in p.spur:
x = fenster_breite / 2 + alte_position.x * skalierungsfaktor / (AE * 0.1)
y = fenster_höhe / 2 + alte_position.y * skalierungsfaktor / (AE * 0.1)
zeichenfläche.create_circle(x, y, 1, fill=p.farbe, outline=p.farbe)
zeichenfläche.create_text(x, y + 10, text=p.name, font=('Helvetica', 7, 'bold'), justify=LEFT, fill=p.farbe)
# stelle einen Text dar
nachricht = "<Leertaste> für Neustart, <+> und <-> für Zoom"
zeichenfläche.create_text(fenster_breite / 2, 20, text=nachricht, font=('Helvetica', 10, 'bold'), justify=CENTER, fill='white')
# zeichne die Zeichenfläche neu
zeichenfläche.update()
# einen Timer starten, so dass die Funktion "nächsterZeitschritt()" nach einigen Millisekunden
# erneut aufgerufen wird; das ist das Herzstück der Simulation
fenster.after(10, nächsterZeitschritt)
# verschiebe das Fenster in die Bildschirmmitte
def zentriereFenster(fenster):
fenster.update_idletasks()
b = fenster.winfo_width()
h = fenster.winfo_height()
x = (fenster.winfo_screenwidth() // 2) - (b // 2)
y = (fenster.winfo_screenheight() // 2) - (h // 2)
fenster.geometry('{}x{}+{}+{}'.format(b, h, x, y))
# mit der Leertaste kann man die Simulation reinitialisieren
def tastatur_event(event):
# mache die benötigten Variablen sichtbar
global skalierungsfaktor
# wertet die gedrückte Taste aus
taste = event.keysym
if taste == 'space':
initialisiere()
if taste == 'plus':
skalierungsfaktor += 1
if taste == 'minus':
skalierungsfaktor -= 1
skalierungsfaktor = max(skalierungsfaktor, 1)
skalierungsfaktor = min(skalierungsfaktor, 10)
# öffne das Fenster, in dem die Simulation dargestellt wird
fenster = Tk(className = ' Gravitationssimulation ')
fenster.configure(background='black')
zeichenfläche = Canvas(fenster, width=fenster_breite, height=fenster_höhe, highlightthickness=0)
zeichenfläche.configure(background='black')
zeichenfläche.pack()
zentriereFenster(fenster)
# füge die Funktion "create_circle()" zur Zeichenfläche hinzu,
# da Tkinter normalerweise nur Ovale zeichnen kann
def _create_circle(self, x, y, r, **kwargs):
r = int(r)
return self.create_oval(int(x) - r, int(y) - r, int(x) + r, int(y) + r, **kwargs)
Canvas.create_circle = _create_circle
# binde die Eventhandler-Funktion an das Fenster
fenster.bind('<Key>', tastatur_event)
# initialisiere
initialisiere()
# starte die Simulation
nächsterZeitschritt()
# Tkinter behandelt Timer- und Tastaturevents in der Funktion "mainloop()"
mainloop()
Wenn ihr Fragen oder Anregungen zu der Aufgabe (oder Lösung) habt, dann tauscht euch gerne im Chat darüber aus oder schreibt uns eine E-Mail (an info@bw-ki.de).
Wir freuen uns auch immer über Feedback (auch unter info@bw-ki.de):