Physics with VPython

Contact: compwiki@physics.utoronto.ca

1. Introduction

Welcome to the tutorial on using VPython! Before you begin this tutorial you should have completed Tutorial Part 1, Tutorial Part 2 (but just up to and NOT including section 3 "Defining your own functions"), Tutorial Part 3 (only sections 2 "Logicals", 3 "If statements" and 4 "While loops"). The basics on python programming you learned in those tutorials are all usable in VPython. What VPython will allow you to do, that python alone does not, is make 3D virtual visualizations of your python programs (the "V" stands for "Visual"). We are hoping this will help you in understanding the physics you will be studying. VPython will be used in the first year physics courses: PHY151/152 and PHY131/132.

During this tutorial we will link to pages on the vpython.org website. This website gives you additional information, examples and tutorials using vpython if you are interested. Here is their main website:

http://vpython.org/
The Visual library is Copyright (c) 2000 by David Scherer. All Rights Reserved. The permission notice can by found here:

If you've completed Tutorial Part 1 then you've already been exposed to VPython without knowing it! The program "bounce.py" which was used to validate the installation of the Python package, and also used to introduce the concept of variables used VPython to display the bouncing ball. Lets examine this program in more detail...

2. A vpython program: bounce.py


Activity 1: Examining bounce.py
Start VIDLE and open the program "bounce.py" located on the installation CD (or where ever you have copied the examples folder to on your computer). Run the program. Here are some key things to note about what this program does:
  • It opens a visual window
  • It creates 2 objects, a red ball and a blue box
  • The ball moves (i.e. its position changes) and we will find out that it moves according to the rules of gravity
  • The ball "feels" or "interacts" with the rectangle (since it bounces off of it)

Now stop the program and take a look at the code itself. A copy can be found here: . There are a few things in the code that should look familiar from your previous python tutorial:
  • variables like floor, ball, dt are used to represent something
  • while 1: This line starts an infinite loop so the lines indented in the code after this line will repeat forever.
  • if ... else ...: These lines are conditionals where the lines indented after them are only applied if the condition is true.

There are also a few things that won't be familiar and some are specific to VPython. We will discuss those in detail below.

(a) CALLING THE VISUAL PACKAGE:


The first line in the code says:
from visual import *
This command tells the compiler that we will be using commands in the Visual package. Using the * makes all the built in commands in VPython accessible to the program. You may remember a similar line of code used for calling the "numpy" package in Tutorial Part 2. This gave you access to built in functions like sin, cos and pi. You should begin all your VPython codes with the above line.

(b) OBJECTS:

The variables floor and ball are defined in the 2nd and 3rd lines of the code. The floor is defined by an object called a "box" and the ball is defined as an object called a "sphere". VPython has a built in set of shapes you can use to define objects that will appear in your visual window. These include arrows, boxes, cones, curves, cylinders, pyramids, spheres... A link to the available objects can be found here (from the vpython.org webpage): http://vpython.org/contents/docs/visual/primitives.html

You will also notice a set of parameters for the objects in the parentheses following them. These define various properties of the objects. For example:
floor = box(length=4, height=0.5, width=4, color=color.blue)
 
tells the code to make a box of length 4, height 0.5 and width 4, and make it blue. It also gives it the variable name 'floor' so we can refer to it later in the program Similarly,

ball = sphere(pos=(0,4,0), color=color.red)
tells the code to make a sphere and put it at a position 'pos' of (0,4,0) (i.e. 0 on the x axis (pointing towards the right), 4 on the y axis (pointing upwards) and 0 on the z axis (pointing into the screen)). It also tells the code to make the sphere red and give it the variable name 'ball'.

You might notice that there was no 'pos' given for the floor and no information on size (e.g. radius) given for the ball. When parameters aren't defined in the parentheses, a default setting is used. For example, the default 'pos' setting is (0,0,0) so the floor is centered at the origin. Similarly the default radius of a sphere is set as '1'.

Each object has unique parameters that can be set for it. Details on the available parameters for the various objects can be found on this vpython.org webpage: http://vpython.org/contents/docs/visual/index.html by using the menu on the left hand side of the page. For example you can "Choose an object" to see details about it or "Choose an option" under "Work with 3D objects" to see the various options on material, lighting, rotations etc.

Lets write a program that makes some of our own objects:

Activity 2: Creating objects
Open a new VIDLE window where you can write your own code. In the first line we will tell the code we will be needing the visual package commands. In the second line we will create a green sphere of radius 2 at position (2,1,3). Here are the lines of code:

from visual import *
 
ballA=sphere(radius=2.0, color=color.green, pos=(2,1,3))

Run your program. Now explore the different objects and their properties by rewriting the second line of your code. Try to make:
  • a blue box of length 4, width 5, height 2 at a location x=0, y=4, z=1
  • a white helix of radius 1.0, located at x=1,y=-2, z=0 and having an axis in the x direction.

(c) MAKING THE BALL MOVE:


In the bounce.py program the ball doesn't stay at one location. It moves as a function of time according to the laws of gravity. As time increments, we want to update the POSITION of the ball. Recall that "pos" is one of the attributes of the ball. You can assign values to any of the attributes of a specific object by identifying the individual attribute. You do this by using the form: objectname.attribute. For example, you can refer to the ball's position as ball.pos or to the floor's width as floor.width etc.

In the bounce.py program we want to move the ball in time. We can do this by creating a loop in time and updating ball.pos within the loop (so its position is updated with every time step. Examining the bounce.py code we see that a while loop is used it increment the time. It is made to run forever:
while 1:

All the lines that are indented after this "while" statement will be executed through each iteration of the loop. The first command in the while loop is:

rate(100)

The rate command is used to control how fast the commands are executed in the loop. We usually need to slow down the execution so that the visualization is viewable (the computer is too fast for us!).

Activity 3: The rate command

You can study the effects of the rate command by:

  • commenting it out of the bounce.py program by placing a # in front of it (so the code ignores this line) and running the code:
# rate(100)

  • changing the number in the parentheses. For example, remove the # sign and change the value in parentheses to 10. Run your program. Now try setting the value to 1. Run your program.

This should give you an idea of how the rate statement affects the timing of the visualization. You should NOT assume that the timing in the visualization is real time. For example you could not use your own stop watch to determine the time it takes for a real ball to fall the distance of 4 meters by using this visualization.

The next line in the program updates the position of the ball in each iteration of the loop:

ball.pos=ball.pos+ball.velocity*dt
Notice that ball.pos occurs on both sides of the equal sign. Recall from Tutorial 1 that in programming, the equal sign is an assignment statement rather than an equality statement. So we are saying: Make the new value of ball.pos equal to the old value of ball.pos plus ball.velocity*dt. We are updating ball.pos by adding an amount ball.velocity*dt during each iteration. This method of updating the position is known as "numerical integration". In the following activity you will learn the underlying idea behind numerical integration.

From calculus and kinematics we know that the velocity is the derivative of position with respect to time:



where


Rearranging the equation for velocity and removing the explicit limit sign (but still keeping in mind that this is only true in the limit that the time interval "Delta t" is very very small:



This is the equation that is implemented in the bounce.py program where we update the ball's position. Notice that in order to use this equation we need to define the ball's velocity and the time interval "Delta t" which in the code is the variable named dt.

Since dt will be the same value throughout the program (i.e. we will use the same length of time increment in each iteration), we can define it outside of the loop (since it doesn't need to be updated every iteration). Notice it is defined before the while loop so that its value is known by the program in the while loop. Also notice that it is set as a very small value.

dt = 0.01

How to determine what value of dt is small enough really depends on the equation you are numerically integrating. You want to be efficient and use a value that is small enough to give you the correct behaviour but large enough that you aren't doing unnecessary iterations. Later in this tutorial you will see the effect of altering your dt value, for now you can assume dt is small enough.

The only thing left that we need to implement the numerical integration is the ball's velocity. Because the ball is moving under the influence of gravity, its velocity will change with time which means we need to update the ball's velocity in the while loop as well as the position. Using the "constant gravitational acceleration" approximation for motion near Earth's surface, we know:





This is the equation that is found in the last line of the program bounce.py:
ball.velocity.y = ball.velocity.y - 9.8*dt

There are a few things to notice about implementing this equation:

  • You need to give an initial value for ball.velocity.y before the while loop or the program won't be able to update this value in the first iteration. This is done by defining the variable ball.velocity as a vector object near the beginning of the program. Its value is set as the initial velocity of the ball:
ball.velocity = vector(0,-1,0)
 
  • The form of this variable looks like its an attribute for the object ball (just like ball.pos was used for the 'pos' attribute and floor.width was used for the 'width' attribute). Velocity is NOT a predefined attribute for ball. This variable name is used in the program so that it is clear that the velocity we speak of is the ball's velocity. However it is just a regular variable, we could just as well have called it velofball or anything else we like.


  • In the updating equation, only a specific component of the velocity is updated. This is done by appending ".y" at the end of the variable name to mean the y component of ball.velocity. We can do this because in free fall examples like this one, gravity only acts in the y direction and only the y component of velocity changes. We could also have updated the entire velocity vector at each step by using the following command:
ball.velocity=ball.velocity+(0,-9.8,0)*dt
  • but then we would be unnecessarily updating the x and z components of velocity which don't change. Notice we had to use the vector form for the acceleration in the above expression. (Note: you could now make the argument that the command that updates the ball.pos in this program could also just be written for the y component. You would be right! The code writer likely used the vector form for the position equation so that this program could easily be converted to one that does projectile motion (where the initial x or z velocity is non-zero but the acceleration is still only due to gravity)
  • The equation for updating the velocity occurs within an if ... else... conditional. This is because we want the ball to interact with the floor in this example, so the ball's velocity is only given by the above expression when the ball doesn't hit the floor. We discuss how to make the ball interact with the floor in the next subsection.

(d) MAKING THE BALL INTERACT WITH THE FLOOR:


Please note: Just because we created an object called floor doesn't mean that any other object knows its there. Without us telling the program, the ball will act as if the floor was never there. You can see this by creating a new program, call it nobounce.py and copying the following lines of the program bounce.py to it:

from visual import *
 
floor = box(length=4, height=0.5, width=4, color=color.blue)
 
ball = sphere(pos=(0,4,0), color=color.red, radius=1)
ball.velocity = vector(0,-1,0)
 
dt = 0.01
while 1:
    rate(100)
    ball.pos = ball.pos + ball.velocity*dt
    ball.velocity.y = ball.velocity.y - 9.8*dt
Notice that this is identical to bounce.py except we removed the if... else... condition and just updated the ball's velocity with the equation involving acceleration.

Activity 4: nobounce

Run the above program. You should see that the ball falls right through the floor.

So we need to tell the program that ball should interact with the floor. In order to determine what the interaction will be we need to incorporate some physics. In this example, it is assumed that the collision between the ball and the floor is perfectly elastic so that the collision causes the ball's velocity to invert its direction but maintain the same speed. Once the ball changes direction it won't interact with the floor again until gravity has the ball change direction and it reaches the floor's position again.

So we want to tell the program that "if the ball's position is at the location of the floor's position, then it needs to change its direction of motion, but if it isn't at the floor's position, then it should continue moving as under the influence of gravity. This is done in the program with the if...else... conditional. The ball hits the floor when the bottom of the ball is at the same location as the top of the floor. Since the ball has a radius of 1 and the floor has a height of 0.5 and is centered at (0,0,0) the ball will hit the floor when ball.y (the vertical position of the CENTER of the ball) is equal to 1.25 (the location of the TOP of the floor).

Activity 5: Correcting the if/else conditional

If you look at the if/else conditional in the code it says:
   if ball.y < 1:
        ball.velocity.y = -ball.velocity.y
    else:
        ball.velocity.y = ball.velocity.y - 9.8*dt
So the code reverses the direction of the ball's motion when ball.y < 1. This is not what we came up with above and is due to a slight sloppiness by the code writer (no offense intended to the code writer). It was likely done because our eye will have a hard time distinguishing the slight overlap of the ball and the floor. Or maybe, the code writer was trying to implement a ball that squishes (changes shape) a little during the contact? Anyhow, the actual point at which the bottom of the rigid ball reaches the top of the floor is ball.pos.y=1.25.

Now, you might ask why the if statement uses a < sign rather than a == sign. This is because == signs are very unforgiving when it comes to determining if a condition is true or not. For example, since our code is determining the position by using an iteration formula with a time step of 0.01, the position is not a continuous function, but rather a set of points. What if, in one iteration the value for ball.y is 1.26 and in the next iteration it is 1.24. In our minds we realize that what is happening is that the ball is falling and that it would have reached 1.25 somewhere between this iteration. But the code only does what we tell it to do, and according to the code in this example, ball.y is never 1.25 so the "if" part of the statement is never true and it only executes the "else" part. In this way the ball never interacts with the floor. Demonstrate this by changing the if conditional in the code to the following:

if ball.y == 1.25:
Run your code and confirm that the ball falls through the floor. Of course, if we had gotten lucky and one iteration actually gave the exact value 1.25 then the ball would interact with the floor, but this is highly unlikely.

Now change the if part of the conditional to what it technically should be:

if ball.y <=1.25:
By using the <= sign we ensure that even if an iteration skips the exact value of 1.25, the bounce will still occur. Save your program and run it.

3. Visualizing Motion with Arrows and Trails


Vpython offers many possibilities for visualizing the motion of the objects you create. The 2 we will focus on in this section are arrows and trails.

(a) ARROWS

We can represent a vector like velocity using the "arrow" object. All the attributes of an arrow can be found on the vpython.org page: http://vpython.org/contents/docs/visual/arrow.html

Activity 6: Making arrows to represent velocity

Some typical attributes you will want to set for an arrow representing the velocity vector are it's position and axis (which includes both the length and direction since its a vector). In the bounce.py program, create an arrow with the variable name "velocityarrow" by including the following line before the while loop but after the line that sets the initial value for ball.velocity:

velocityarrow=arrow(pos=ball.pos, axis=ball.velocity, color=color.yellow)
This arrow will have its end positioned at the center of the ball, its direction will be parallel to the ball's velocity and its length will be the magnitude of the velocity. Run your program. You should see a stationary yellow arrow (at least when the ball isn't in the way). This arrow represents the initial velocity of the ball. If we want it to represent the velocity of the ball at any step in the iteration then we need to update the position and axis of the arrow in the while loop. At the end of the while loop add the following 2 lines (make sure it is indented so its part of the while loop but not so indented that it is part of the 'else' conditional):

velocityarrow.pos=ball.pos
velocityarrow.axis=ball.velocity
These lines update the attributes "pos" and "axis" of the arrow object. Run your program and confirm that the arrow now stays attached to the ball and changes length and direction based on what the ball's velocity is.

(b) TRAILS

The trajectory of an object like the ball can be drawn out using the "curve" object. All the attributes of a curve can be found on the vpython.org page: http://vpython.org/contents/docs/visual/curve.html

Activity 7: Using a curve to trace out a trajectory

A curve is defined by a set of points that are joined to together. You provide the points and vpython joins them together. To trace a trajectory, we would should the position of the ball during each iteration as the points in the curve. In this case, we don't know all the points in the curve right away, but we want to keep adding points to the curve as the motion continues. First we will let vpython know we want to create the curve, then we'll add points to the curve during the iteration.

Just before the while loop add the line:

trajectory=curve(color=color.white)
Now, a special procedure called "append" will allow us to continue adding points to our curve whenever we update the position of the ball. In the while loop just after the position of the ball is updated (so the line starting ball.pos) add the line:

trajectory.append(pos=ball.pos)
Run your program and confirm that the trajectory of the ball is traced out. You should just see a thin white vertical line behind the ball. Lets do a slightly more exciting trajectory by considering a projectile motion case. Change the initial velocity of the ball (i.e. before the while loop) so that it reads:

ball.velocity=vector(1,-1,0)
This gives the ball an additional component to its initial velocity in the x direction. Run your program. You may notice that the ball continues to bounce even after it reaches the end of the floor. This is becauseour if condition only cares about the y position of the ball reaching a value of 1.25.

3. Graphing in Vpython


All the above tools (3D visualization, arrows and trails) are good methods for visualizing motion, but to do more quantitative analysis, graphs of kinematic variables such as graphs of position vs time and velocity vs time are usually needed. In this section we will learn about another type of visualization that vpython allows, namely graphs. Note that there are other methods you can use to graph (like matplotlib), but if you are trying to analyse motion you have generated using vpython then you will want to use the visual.graph module we discuss below.

Activity 8: Graphing position and velocity vs time

(a) Incorporating time


Since we will be making plots of position and velocity versus time, we should introduce a variable to represent time and we should initialize it to some starting value (like 0). Near the beginning of the program (before the while loop) insert the command:

time=0.0

Now we want to update the variable time with every iteration of the code by an amount 'dt'. In the while loop add the command:

time = time + dt

Now the time will increment by the amount of the time interval between iterations.

(b) Calling the visual.graph module


Just like we needed to tell our program that we would use the functions in the visual module with a statement at the beginning of the program: from visual import *, we also need to tell the program we will be using the functions built in to the visual.graph module. We will do this in the second line of the program so your first two lines should now look like:

from visual import *
from visual.graph import *
If you run your program nothing different should happen yet because we haven't used any of the built in visual.graph commands.

(d) Initializing gcurves, gdots and others:


Now we want to draw some graphs to represent the velocity and position of the ball. There are different types of graphs you can make with visual.graph. The two we will concentrate on here are: (1) gcurve: a continuous curve, and (2) gdots: a set of points. First we need to tell the program that we are creating these curves. Type in the following commands before the while loop:

poscurve=gcurve(color=color.blue)
velcurve=gdots(color=color.orange)
This tells the program we are going to create 2 curves in a graphing window: a blue curve for position and an orange curve for velocity. We will represent the position as a continuous curve whereas we will represent the velocity as a set of points (dots).

(Aside: the "g" at the beginning of "gcurve" and "gdots" tells vpython that it will be plotting these objects in a graphing display window. If you want to make more than one graphing display window (say plot poscurve and velcurve on separate graphs), you need to name new gdisplay windows. Info on how to do so can be found on the following vpython.org webpage: http://vpython.org/contents/docs/visual/graph.html . Make sure to define the objects you want to graph immediately after you define the window you want them graphed in. In this example we will graph both curves in the same gdisplay window so we don't need to initialize any.)

There are many other attributes you can give the gcurves and gdots. For a list of them, see the following vpython.org webpage: http://vpython.org/contents/docs/visual/graph.html

Now we have to add points to the curves to be graphed. Each point should be given an horizontal and vertical coordinate, so in our case a time and position (or velocity) value. We will do this every time the ball's position and velocity is updated using a special procedure known as "plot". In the while loop, after the ball's position is updated, add the line:

poscurve.plot(pos=(time,ball.pos.y))
Run your program. You should now see a curve in your graphing window representing the y component of the ball's position as a function of time. Now plot the velocity by adding the following line after the ball's velocity is updated (so after the if/else conditional, make sure you only have 1 indent so are aren't part of the if/else conditional):

velcurve.plot(pos=(time,ball.velocity.y))
Run your program. You should now see the position curve you had before and the set of dots representing velocity (they may be pretty close together so they may look like a line).

Activity 9: Creating a gcurve for an analytic function

You can also graph analytic functions using gcurve (rather than adding points in an iteration). Below is an example of graphing such a curve using a for loop to generate a set of points:

Create a new program with the following lines:

from visual import *
from visual.graph import *
 
analyticcurve=gcurve(color=color.green)
 
for t in arange(0,2,0.005):
     analyticcurve.plot(pos=(t,20+0.5*-9.8*t**2))
This program creates a gcurve that plots values of the y position of a projectile with 0 initial velocity and initial height of 20 as a function of time. Run the program and see if the result is what you expect.

4. Summary and Conclusions


In this tutorial we learned how to create 3D objects such as spheres, cones, boxes... in order to visual the motion of physical objects. We also learned the basics of numerical integration in order to update the motion of these objects. We discussed various methods for analyzing and visualizing motion including: arrows, trails and graphs.


This concludes the vpython introductory tutorial.