Kapitel 10 · Pendelkette – klack, klack, klack

Pendelkette in Python – klack, klack, klack

Fünf Kugeln an Fäden: Zwei schwingen links los, prallen auf, und rechts fliegen zwei davon. Die Pendelkette vom Schreibtisch, gebaut aus einer Formel für die gedämpfte Schwingung und einem cleveren Trick.

Aus dem Buch · Kapitel 10 Einsteiger PhysikSimulation Python 3 · läuft im Browser
Bereit · Python startet beim ersten Klick
# Pendelkette – Kapitel 10 »Pendelkette – klack, klack, klack«
# aus »Coding for Fun mit Python«, portiert auf Python 3 und c4f.
# pendulum.py (Klasse Pendulum) und pendulum_v4.py sind hier vereint.
import math
from c4f import Screen

windowWidth     = 870
windowHeight    = 400
fixPoint1       = 290, 40
fixPoint2       = 360, 40
fixPoint3       = 430, 40
fixPoint4       = 500, 40
fixPoint5       = 570, 40
backgroundColor = (0, 0, 0)
color1          = (250, 0,   0)
color2          = (  0, 0, 250)


# this is just a mathematical pendulum = simple pendulum
# a physical pendulum needs more exact calculations
class Pendulum(object):
    def __init__(self, alphaMax, wireLength, wireThickness, ballSize, gravity,
                 dampingConstant, color, fixPoint):
        self.__alphaMax        = alphaMax
        self.__wireLength      = wireLength
        self.__wireThickness   = wireThickness
        self.__ballSize        = ballSize
        self.__gravity         = gravity
        self.__dampingConstant = dampingConstant
        self.__color           = color
        self.__fixPoint        = fixPoint

    def calculateAlpha(self, time):
        # e^(-dampingConstant*time) = 1.0/e^(dampingConstant*time)
        damping = 1.0 / math.exp(self.__dampingConstant * time)
        alpha   = self.__alphaMax * math.cos(math.sqrt(self.__gravity / self.__wireLength) * time) * damping

        return alpha

    def __getCirclePoint(self, position, scale, radius):
        degrees = self.__getWirePositionDegrees(position, scale)
        xPos = int(round(math.cos(degrees / 180.0 * math.pi) * radius + self.__fixPoint[0]))
        yPos = int(round(math.sin(degrees / 180.0 * math.pi) * radius + self.__fixPoint[1]))
        return (xPos, yPos)

    def __getWirePositionDegrees(self, position, scale):
        offset = -270
        degrees = 360 / scale * position + offset
        return degrees

    def draw(self, screen, position, scale):
        end = self.__getCirclePoint(position, scale, self.__wireLength)
        screen.line(self.__fixPoint, end, self.__color, self.__wireThickness)
        screen.circle(end, self.__ballSize, self.__color)
        screen.circle(self.__fixPoint, self.__wireThickness * 2, self.__color)


async def main():
    screen = Screen(windowWidth, windowHeight, background=backgroundColor,
                    title="Pendel Simulation")

    time            = 0.0
    wireThickness   = 4
    wireLength      = 280.0
    ballSize        = 35
    gravity         = 9.81
    dampingConstant = 1.0 / 200.0
    alpha1          = 45.0
    alpha2          = 0.0
    alpha3          = 0.0
    pendulum1 = Pendulum(alpha1, wireLength, wireThickness, ballSize, gravity, dampingConstant, color1, fixPoint1)
    pendulum2 = Pendulum(alpha2, wireLength, wireThickness, ballSize, gravity, dampingConstant, color2, fixPoint2)
    pendulum3 = Pendulum(alpha2, wireLength, wireThickness, ballSize, gravity, dampingConstant, color1, fixPoint3)
    pendulum4 = Pendulum(alpha2, wireLength, wireThickness, ballSize, gravity, dampingConstant, color2, fixPoint4)
    pendulum5 = Pendulum(alpha2, wireLength, wireThickness, ballSize, gravity, dampingConstant, color1, fixPoint5)

    while True:
        screen.fill(backgroundColor)

        pendulum1.draw(screen, alpha1, 360)
        pendulum2.draw(screen, alpha1, 360)
        pendulum3.draw(screen, alpha2, 360)
        pendulum4.draw(screen, alpha3, 360)
        pendulum5.draw(screen, alpha3, 360)
        alpha1 = pendulum1.calculateAlpha(time)
        alpha3 = pendulum2.calculateAlpha(time)
        time  += 0.2

        # Klack! Schwingen die linken Kugeln nach rechts durch,
        # übernehmen die rechten Kugeln die Bewegung
        if alpha1 < 0:
            alpha1, alpha3 = alpha3, alpha1

        await screen.frame(50)  # statt pygame.display.flip() und delay(20)


await main()
Pendelkette: fünf rote und blaue Kugeln an Fäden, die linken zwei ausgelenkt
Konsole

    

Was du hier lernst

  • Gedämpfte Schwingung
  • cos und e-Funktion
  • Klassen mit privaten Attributen
  • Winkel in Koordinaten umrechnen
  • Tupel tauschen
  • Parameter ausprobieren

So schwingt die Pendelkette

Eine echte Pendelkette ist ein kleines Physiklabor: Kugeln stoßen zusammen, Impuls und Energie wandern durch die Reihe. Das Programm simuliert diese Stöße nicht Kugel für Kugel. Es beschreibt die Schwingung mit einer Formel und verteilt sie mit einem Trick auf die richtigen Kugeln. Das Ergebnis sieht erstaunlich echt aus.

1. Eine Formel für die Schwingung

Ein Pendel schwingt hin und her, und mit der Zeit wird der Ausschlag kleiner. Beides steckt in calculateAlpha:

damping = 1.0 / math.exp(self.__dampingConstant * time)
alpha   = self.__alphaMax * math.cos(math.sqrt(self.__gravity / self.__wireLength) * time) * damping

Die Formel hat drei Teile:

  • alphaMax ist der größte Ausschlag, hier 45 Grad.
  • cos(√(g/l) · t) schwingt gleichmäßig zwischen −1 und 1. Wie schnell, bestimmt √(g/l): Mehr Schwerkraft g macht das Pendel schneller, ein längerer Faden l langsamer.
  • damping ist die Dämpfung. 1 / e^(d·t) beginnt bei 1 und schrumpft mit der Zeit gegen 0 – so verliert das Pendel langsam Schwung, wie durch Luftreibung.

Mathematisches Pendel

Die Formel beschreibt ein »mathematisches Pendel«: eine punktförmige Masse an einem masselosen Faden. Genau genommen stimmt sie nur für kleine Ausschläge. Für die Pendelkette ist die Näherung gut genug. Ein echtes, physikalisches Pendel bräuchte eine aufwendigere Rechnung – daran erinnert auch der Kommentar über der Klasse.

2. Eine Klasse für ein Pendel

Jedes Pendel hat seinen Aufhängepunkt, seine Farbe, Fadenlänge, Kugelgröße und die physikalischen Werte. Die Klasse Pendulum bündelt all das. Die Attribute beginnen mit zwei Unterstrichen, etwa self.__alphaMax. Python versteckt solche Namen vor dem Zugriff von außen – ein Hinweis: Diese Werte gehören dem Pendel allein.

Nach außen bietet die Klasse nur zwei Methoden an: calculateAlpha(time) für den Winkel zu einem Zeitpunkt und draw(screen, position, scale) fürs Zeichnen.

3. Vom Winkel zur Kugel

Das Zeichnen kennst du vielleicht von der analogen Uhr: Aus einem Winkel wird mit Kosinus und Sinus ein Punkt auf einem Kreis um den Aufhängepunkt.

def __getWirePositionDegrees(self, position, scale):
    offset = -270
    degrees = 360 / scale * position + offset
    return degrees

Der Versatz von −270 Grad sorgt dafür, dass ein Winkel von 0 senkrecht nach unten zeigt. Positive Winkel lenken das Pendel nach links aus, negative nach rechts. Dann zeichnet draw den Faden als Linie, die Kugel als Kreis und einen kleinen Punkt an der Aufhängung.

4. Fünf Kugeln, drei Winkel

Die Kette hat fünf Pendel, aber das Programm rechnet nur mit drei Winkeln:

pendulum1.draw(screen, alpha1, 360)
pendulum2.draw(screen, alpha1, 360)
pendulum3.draw(screen, alpha2, 360)
pendulum4.draw(screen, alpha3, 360)
pendulum5.draw(screen, alpha3, 360)

Die beiden linken Kugeln teilen sich alpha1, die beiden rechten alpha3. Die mittlere bekommt alpha2, der immer 0 bleibt – sie hängt still und gibt den Stoß nur weiter.

5. Klack: der Trick mit dem Tauschen

Jetzt kommt der Kniff. Schwingen die linken Kugeln zurück und würden über die Senkrechte hinaus nach rechts ausschlagen, wird alpha1 negativ. Genau in diesem Moment tauscht das Programm die Winkel:

if alpha1 < 0:
    alpha1, alpha3 = alpha3, alpha1

Die linken Kugeln bleiben in der Mitte stehen, die rechten übernehmen den Schwung. Beim Zurückschwingen passiert dasselbe in die andere Richtung. Python kann zwei Variablen in einer Zeile tauschen, ohne Hilfsvariable – das Tupel rechts wird erst komplett gebildet und dann verteilt.

6. Die Wirkung der Parameter

Alle Größen stehen am Anfang von main(): Schwerkraft, Fadenlänge, Dämpfung und Startwinkel. Unter »Probier mal« kannst du sie verändern. Interessant ist dabei: Der Startwinkel verändert die Höhe, aber nicht das Tempo der Schwingung. Die Schwingungsdauer hängt in der Formel nur von g und l ab.

Zeit ist keine echte Zeit

time wächst pro Bild um 0,2, bei 50 Bildern pro Sekunde also um 10 pro Sekunde. Die Simulation läuft damit schneller als die Wirklichkeit – bei einem 280 Pixel langen »Faden« und 9,81 m/s² sähe das Pendel sonst sehr träge aus. Mit dem Faktor kannst du das Tempo einstellen, ohne die Physik anzufassen.

Probier mal

Jede Variante ändert den Code oben. Ein Klick auf »Ausprobieren« übernimmt die Änderung in den Editor und startet das Programm. »Zurücksetzen« holt das Original zurück.

  1. 01Nur eine Kugel anheben

    Das klassische Bild: Links schwingt eine einzige Kugel los, rechts fliegt eine davon. Dafür hängen jetzt die zweite und die vierte Kugel fest in der Mitte, sie bekommen den Winkel alpha2, der immer 0 bleibt.

    - pendulum2.draw(screen, alpha1, 360)
    + pendulum2.draw(screen, alpha2, 360)
    - pendulum4.draw(screen, alpha3, 360)
    + pendulum4.draw(screen, alpha2, 360)
  2. 02Auf dem Mond

    Auf dem Mond beträgt die Schwerebeschleunigung nur 1,62 m/s² statt 9,81 m/s². In der Formel steckt gravity unter der Wurzel: Die Pendel schwingen gut zweieinhalbmal langsamer.

    - gravity         = 9.81
    + gravity         = 1.62
  3. 03Ohne Reibung

    Mit einer Dämpfungskonstante von 0 wird der Dämpfungsfaktor 1 / e^0 = 1 – die Kette klackt für immer. In der Wirklichkeit gibt es so ein Perpetuum mobile nicht.

    - dampingConstant = 1.0 / 200.0
    + dampingConstant = 0.0
  4. 04Honig statt Luft

    Eine zehnmal stärkere Dämpfung: Die Schwingung stirbt nach wenigen Stößen. Beobachte, wie die e-Funktion den Ausschlag immer schneller schrumpfen lässt.

    - dampingConstant = 1.0 / 200.0
    + dampingConstant = 1.0 / 20.0
  5. 05Weiter ausholen

    Starte mit 75 statt 45 Grad. Die Kugeln fliegen höher – schwingen aber gleich schnell, denn die Schwingungsdauer hängt in dieser Formel nicht vom Ausschlag ab.

    - alpha1          = 45.0
    + alpha1          = 75.0

Vom Buch in den Browser

Im Buch liegt die Klasse Pendulum in pendulum.py, das Programm für die Pendelkette in pendulum_v4.py. Hier stehen beide in einer Datei. Die Klasse ist unverändert, nur die drei Zeichenbefehle nutzen jetzt c4f statt pygame:

Im Buch (pygame) Hier (c4f)
pygame.draw.line(screen, farbe, start, ende, dicke) screen.line(start, ende, farbe, dicke)
pygame.draw.circle(screen, farbe, mitte, radius) screen.circle(mitte, radius, farbe)
screen.fill(farbe) screen.fill(farbe) – gleich geblieben
pygame.display.flip() + pygame.time.delay(20) await screen.frame(50)

Die Hauptschleife steht in einer async-Funktion main(), die am Ende mit await main() startet – nötig, damit der Browser zwischen den Bildern zeichnen kann.

Die Ereignisbehandlung des Originals entfällt: Sie beendete das Programm bei einem Tastendruck, dafür gibt es hier den Knopf »Stopp«. Bei einem Mausklick rief sie event.position_window() auf – eine Methode, die es in pygame nicht gibt. Ein Klick ins Fenster hätte das Programm deshalb mit einem Fehler beendet.

Python 3 ändert an diesem Programm fast nichts: Alle Rechnungen verwenden Kommazahlen, die Division verhält sich also in beiden Versionen gleich.