2

I have written a Python script which creates a Ball class and then creates instances of it. The balls should bounce off the walls, and later (not implemented yet) off of each other. Note that, instead of just reversing the direction of the ball when it reaches a wall, the wall exerts a force (or acceleration) on the ball.

Unfortunately, after bouncing off of a wall, the balls then enter an oscillatory motion, with the amplitude getting bigger and bigger.

I am very new to Python, but have written various javascript/canvas simulations before.

This particular python script uses Verlet integration to compute the positions and velocities. I am basing the script on this:

http://physics.weber.edu/schroeder/software/demos/MolecularDynamics.html

Can anybody suggest how I should modify the code so that it is "stable" with the balls bouncing off the walls? Thank you

from Tkinter import *
import random
window = Tk()
canvas = Canvas(window, width = 400, height = 300)
canvas.pack()
wallStiffness=0.05
dt=0.02


class Ball:

    def __init__(self):
        self.x=0
        self.y=0
        self.xvel=random.random()*2-1
        self.yvel=random.random()*2-1
        self.ax=0
        self.ay=0
        self.rad=20



def computeAccelerations(z):

        if z.x<z.rad:
             z.ax=wallStiffness*(z.rad-z.x)
        else: 
            if z.x>400-z.rad:
                z.ax=wallStiffness*(400-z.rad-z.x) 
        #only have horizontal bounces so far, and no bounces between balls yet


list=[]

for i in range(100):    #0 to 99, make 100 balls
    myball=Ball()
    list.append(myball)

#script to place in a crystal to begin with

current_x=30
current_y=0

for j in list:

        j.x=current_x
        current_x+=30
        j.y=current_y

        if current_x+30>370:
            current_x=30
            current_y+=30            

        j.ball=canvas.create_oval(j.x,j.y,j.x+j.rad,j.y+j.rad,fill="blue")



while True:

    for i in range(10):     #10 steps per frame
        for j in list:
            j.x+=j.xvel*dt+0.5*j.ax*dt*dt
            j.y+=j.yvel*dt+0.5*j.ay*dt*dt
            j.xvel+=0.5*j.ax*dt
            j.yvel+=0.5*j.ay*dt
            computeAccelerations(j)
        for j in list:
            j.xvel+=0.5*j.ax*dt
            j.yvel+=0.5*j.ay*dt

    canvas.after(10)    #duration of frame in ms
    for z in list:
        canvas.move(z.ball,z.xvel, z.yvel)
        canvas.update()

window.mainloop()   #keeps the window open
  • I was missing a *dt in the j.x+= and j.y+= part of the while loop. The oscillatory motion has gone, but the particles are going through the walls ! – user2094585 Jun 16 '13 at 20:15

2 Answers2

1

I believe the issue you were having is that you forgot to put a time step into your action loop:

while True:
    for i in range(10):
        for j in list:
            j.x += j.xvel * dt + 0.5 * j.ax * dt * dt
            j.y += j.yvel * dt + 0.5 * j.ay * dt * dt
            j.xvel += 0.5 * j.ax * dt
            j.yvel += 0.5 * j.ay * dt
            compute_accelerations(j)
        for j in list:
            j.xvel += 0.5 * j.ax * dt
            j.yvel += 0.5 * j.ay * dt

Also, though your code works, I think your Ball acceleration method could be cleaned up:

# box_width = 400

def compute_accelerations(self):
        if self.x < self.rad:
             self.ax = wallStiffness * (self.rad - self.x)
        elif self.x > box_width - self.rad:
                self.ax = wallStiffness * (box_width - self.rad - self.x) 

where here you'll see that self is used in place of z. This is the standard way to create methods inside a class in Python. Here the self stands for the current instance of your Ball object. Does that make sense?

You would call this newly modified method like this:

j.compute_accelerations()
john_science
  • 6,325
  • 6
  • 43
  • 60
1

This doesn't directly answer your question, but you're using Tkinter wrong. As a general rule of thumb you should not call update, you should never have an infinite loop, and you should never call after with a single argument. Tkinter already has an infinite loop running -- mainloop -- so why not take advantage of it? By doing so, you can eliminate all three of the bad practices I listed.

You should remove the body of the while True loop and put it into a function. Then, you have that function arrange for itself to be called again at some interval. For example:

def draw_frame():
    for i in range(10):
        for j in list:
            ...
    for z in list:
        canvas.move(z.ball,z.xvel, z.yvel)

    canvas.after(10, draw_frame)

This will draw a single frame, wait 10 ms, then draw another frame, repeating forever. If you want the ability to stop the animation, you can create a button that sets a flag variable (eg: we_should_continue=False), in which case you would do something like:

    if we_should_continue:
        canvas.after(10, draw_frame)

The advantage this has over your scheme, is that your scheme has small 10ms windows where the GUI is completely frozen, which might lead to some very slight stuttering. The bigger advantage is simply that this is how tkinter is designed to work.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685