Odd slowing effect on a double-pendulum animation with python tkinter

314 Views Asked by At

I am just getting started with python and tried to create a double-pendulum animation using tkinter. I am aware that because of my inexperience, I likely took a convoluted approach.

I have an error that I did not expect. It seems that the pendulum slows down oddly in places. In time, the energy of the system seems to go down, which should not happen, as I have not accounted for any friction.

I do not think I made an error in my formulae for theta1_dotdot and theta2_dotdot, because the slowing happens even when I use simpler (non-physical) formulae.

Here is my program:

from tkinter import*
from random import*
from math import*

gui = Tk()
gui.title("Double Pendulum")
canvas = Canvas(gui, width=300, height=300)
canvas.pack()

r1,r2,m1,m2 = 75,75,10,10
g = 9.81
t=0
delt=0.001
theta1 = random()*2*pi
theta2 = random()*2*pi
theta1_dot,theta2_dot = 0,0
dt = 0.1
t = 0

while t < 1000000:
    num1 = (-g*(2*m1+m2)*sin(theta1))
    num2 = -m2*g*sin(theta1-2*theta2)
    num3 = (-2*sin(theta1-theta2)*m2*            (theta2_dot**2*r2+theta1_dot**2*r1*cos(theta1-theta2)))
    denom1 = r1*(2*m1+m2-m2*cos(2*theta1-2*theta2))
    theta1_dotdot = (num1 + num2 + num3)/denom1

    num4 = 2*sin(theta1-theta2)
    num5 = (theta1_dot**2*r1*(m1+m2))
    num6 = g*(m1+m2)*cos(theta1)
    num7 = theta2_dot**2*r2*m2*cos(theta1-theta2)
    denom2 = r2*(2*m1+m2-m2*cos(2*theta1-2*theta2))
    theta2_dotdot = (num4*(num5+num6+num7))/denom2

    theta1_dot += theta1_dotdot * dt
    theta2_dot += theta2_dotdot * dt
    theta1 += theta1_dot * dt
    theta2 += theta2_dot * dt

    x1 = r1*sin(theta1)
    y1 = r1*cos(theta1)

    x2 = x1 + r2*sin(theta2)
    y2 = y1 + r2*cos(theta2)

    trace = canvas.create_oval(150 + x2, 60 + y2, 150 + x2, 60 + y2, fill='black', outline='black')
    lin1 = canvas.create_line(150,60,150+x1, 60+y1,width=3,fill='pink')
    lin2 = canvas.create_line(150+x1, 60+y1,150+x2, 60+y2,width=3,fill='pink')
    ov1 = canvas.create_oval(140+x1,50+y1,160+x1,70+y1,     fill='pink',outline='pink')
    ov2 = canvas.create_oval(140+x2,50+y2,160+x2,70+y2, fill='pink',outline='pink')
    t += .1
    canvas.after(1)
    canvas.update()
    canvas.delete(ov1)
    canvas.delete(ov2)
    canvas.delete(lin1)
    canvas.delete(lin2)

gui.mainloop()
1

There are 1 best solutions below

0
On BEST ANSWER

I've rewritten your code to remove blanket imports (import *), use a class structure rather than globals and functions, and to actually use the tkinter mainloop and after function properly:

import tkinter as tk
import random
from math import pi, sin, cos

r1,r2,m1,m2 = 75,75,10,10
g = 9.81

class App(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.title("Double Pendulum")
        self.canvas = tk.Canvas(self, width=300, height=300)
        self.canvas.pack()
        self.delt=0.001
        self.theta1 = random.random()*2*pi
        self.theta2 = random.random()*2*pi
        self.theta1_dot,self.theta2_dot = 0,0
        self.dt = 0.1
        self.t = 0

        self.after(1, self.do_after)

    def do_after(self):
        self.canvas.delete('pendulum')

        num1 = (-g*(2*m1+m2)*sin(self.theta1))
        num2 = -m2*g*sin(self.theta1-2*self.theta2)
        num3 = (-2*sin(self.theta1-self.theta2)*m2*(self.theta2_dot**2*r2+self.theta1_dot**2*r1*cos(self.theta1-self.theta2)))
        denom1 = r1*(2*m1+m2-m2*cos(2*self.theta1-2*self.theta2))
        theta1_dotdot = (num1 + num2 + num3)/denom1

        num4 = 2*sin(self.theta1-self.theta2)
        num5 = (self.theta1_dot**2*r1*(m1+m2))
        num6 = g*(m1+m2)*cos(self.theta1)
        num7 = self.theta2_dot**2*r2*m2*cos(self.theta1-self.theta2)
        denom2 = r2*(2*m1+m2-m2*cos(2*self.theta1-2*self.theta2))
        theta2_dotdot = (num4*(num5+num6+num7))/denom2

        self.theta1_dot += theta1_dotdot * self.dt
        self.theta2_dot += theta2_dotdot * self.dt
        self.theta1 += self.theta1_dot * self.dt
        self.theta2 += self.theta2_dot * self.dt

        x1 = r1*sin(self.theta1)
        y1 = r1*cos(self.theta1)

        x2 = x1 + r2*sin(self.theta2)
        y2 = y1 + r2*cos(self.theta2)

        self.canvas.create_oval(150 + x2, 60 + y2, 150 + x2, 60 + y2, fill='black', outline='black', tag='trace')
        self.canvas.create_line(150,60,150+x1, 60+y1,width=3,fill='pink', tags='pendulum')
        self.canvas.create_line(150+x1, 60+y1,150+x2, 60+y2,width=3,fill='pink', tags='pendulum')
        self.canvas.create_oval(140+x1,50+y1,160+x1,70+y1,     fill='pink',outline='pink', tags='pendulum')
        self.canvas.create_oval(140+x2,50+y2,160+x2,70+y2, fill='pink',outline='pink', tags='pendulum')
        self.t += .1

        self.after(1, self.do_after)

if __name__ == '__main__':
    app = App()
    app.mainloop()

in your code you're forcibly calling update on every iteration of the loop instead of letting tkinter handle when it needs to update, you're also calling after without a callback which AFAICT doesn't actually do anything.
I also added tags to the pendulum parts so you can delete them all with a single call rather than having to store their ID's each time.

upon further testing the real problem is that you're creating thousands of objects on the canvas which tkinter struggles to render. to keep a trace you can keep a list of the coordinates and plot it as a line instead:

import tkinter as tk
import random
from math import pi, sin, cos

r1,r2,m1,m2 = 75,75,10,10
g = 9.81

class App(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.title("Double Pendulum")
        self.canvas = tk.Canvas(self, width=300, height=300)
        self.canvas.pack()
        self.delt=0.001
        self.theta1 = random.random()*2*pi
        self.theta2 = random.random()*2*pi
        self.theta1_dot,self.theta2_dot = 0,0
        self.dt = 0.1
        self.t = 0

        self.trace_coords = []

        self.after(1, self.do_after)

    def do_after(self):
        self.canvas.delete('trace')
        self.canvas.delete('pendulum')

        num1 = (-g*(2*m1+m2)*sin(self.theta1))
        num2 = -m2*g*sin(self.theta1-2*self.theta2)
        num3 = (-2*sin(self.theta1-self.theta2)*m2*(self.theta2_dot**2*r2+self.theta1_dot**2*r1*cos(self.theta1-self.theta2)))
        denom1 = r1*(2*m1+m2-m2*cos(2*self.theta1-2*self.theta2))
        theta1_dotdot = (num1 + num2 + num3)/denom1

        num4 = 2*sin(self.theta1-self.theta2)
        num5 = (self.theta1_dot**2*r1*(m1+m2))
        num6 = g*(m1+m2)*cos(self.theta1)
        num7 = self.theta2_dot**2*r2*m2*cos(self.theta1-self.theta2)
        denom2 = r2*(2*m1+m2-m2*cos(2*self.theta1-2*self.theta2))
        theta2_dotdot = (num4*(num5+num6+num7))/denom2

        self.theta1_dot += theta1_dotdot * self.dt
        self.theta2_dot += theta2_dotdot * self.dt
        self.theta1 += self.theta1_dot * self.dt
        self.theta2 += self.theta2_dot * self.dt

        x1 = r1*sin(self.theta1)
        y1 = r1*cos(self.theta1)

        x2 = x1 + r2*sin(self.theta2)
        y2 = y1 + r2*cos(self.theta2)

        self.trace_coords.append((150 + x2, 60 + y2, 150 + x2, 60 + y2))
        self.canvas.create_line(self.trace_coords, fill='black', tag='trace')
        self.canvas.create_line(150,60,150+x1, 60+y1,width=3,fill='pink', tags='pendulum')
        self.canvas.create_line(150+x1, 60+y1,150+x2, 60+y2,width=3,fill='pink', tags='pendulum')
        self.canvas.create_oval(140+x1,50+y1,160+x1,70+y1,     fill='pink',outline='pink', tags='pendulum')
        self.canvas.create_oval(140+x2,50+y2,160+x2,70+y2, fill='pink',outline='pink', tags='pendulum')
        self.t += .1

        self.after(1, self.do_after)

if __name__ == '__main__':
    app = App()
    app.mainloop()