A Robot Simulator Using VPython

[Edit 2024-08-07: The code described below has been updated for VPython 7.6.5, and is available on GitHub.]

When I was a kid, one of my favorite “big” toys was the Big Trak, a programmable robot that looked like a futuristic tank.  It had a keypad allowing you to enter a program consisting of a series of motion commands such as “forward 10,” “turn left 15,” etc.  The Big Trak only had memory for 16 commands– this was in the early ’80s, remember– and it didn’t have any sensors, so you could only dead reckon, and for a short time at that.  But it was still extremely cool, and I had a blast playing with it.

Now fast forward to today.  Apparently I haven’t changed much.  I have been experimenting with the Scribbler Robot from Parallax.  This is also a programmable robot, with two independent motor-driven wheels (and a third tricycle wheel for stability), plus quite a few sensors, including a color camera.  The robot chassis has a hole in the center for inserting a pen or marker, for tracing the path of the robot as it moves.  Better yet, Georgia Tech and Bryn Mawr College have developed a Python API called Myro for controlling the robot from a computer via a wireless Bluetooth interface.  I think this has the potential to be a great learning tool for students.  Plus it’s fun to play with.  (For the students, I mean.  Really.)

As a segue from experimenting with VPython to working with the robot, I developed a robot simulator module, vturtle.py, using VPython.  The idea is that you can experiment with the robot, without the robot.  Using this module, you can create and manipulate a robot (or robots) with simple commands like the following, and see the effects of those commands in on-screen animation.

You can download the code at the usual location here.  Following are a few screenshots showing the robot in action, tracing a Koch snowflake:

Example using vturtle to draw a Koch snowflake.
Another view of the robot and its trail.

Note that this simulator is more like the Big Trak than the actual Scribbler in that it does not have any real sensing capability.  Hence the name vturtle.py; this is essentially a better Logo-like turtle graphics module.  But I think it has a lot of advantages over other turtle graphics modules, and most of those advantages are thanks to the VPython developers, not me:

First, and most important, all rendering is in a separate thread, so you can move the turtle around “a line at a time” at the command prompt, without the messiness of closing the event/rendering loop with mainloop() calls or whatever.  Second, it is easy to add other objects to the scene, such as walls of a maze, or obstacles to avoid, etc.  Finally, it looks good.  That is, the VPython graphics incorporate lighting, shading, texturing, etc., with mouse-controlled slewing and zooming of the camera at any time, even while the robot is moving.  This goes a long way toward making students’ efforts feel more polished and less cartoonish.

To wrap up, this is some more example code similar to that used to draw the Koch snowflake above:

from visual import *
import vturtle

# Start with a robot in the center of the table.
robot = vturtle.Robot(obstacles=vturtle.table())

# Draw one side of a Koch snowflake.
def koch(size, level):
    if level == 0:
        robot.forward(size)
    else:
        koch(size / 3.0, level - 1)
        robot.left(60)
        koch(size / 3.0, level - 1)
        robot.right(120)
        koch(size / 3.0, level - 1)
        robot.left(60)
        koch(size / 3.0, level - 1)

koch(60, 2)

Flocking Boids and Emergent Behavior

In 1986 Craig Reynolds developed a simulation of flocking behavior of creatures that he called boids.  As is typical of good ideas, this one is simple to describe: even when many individuals each behave according to a small set of very simple rules, the resulting collective behavior of the group can be very interesting and complex.  For example, consider a flock of birds, or a school of fish, or an army of orcs (that’s right, orcs), or cars on a freeway.

In the case of boids, Reynolds originally used just three simple rules: separation, alignment, and cohesion.  Separation means don’t run into any other boids.  Alignment means head where everyone else is heading.  And cohesion means stick with the group.  Just these three rules can yield interesting and realistic flocking behavior among a large number of boids that all follow those same rules.

I have VPython to blame for spending enough time on this to warrant posting about the subject.  In the process of experimenting with VPython and putting together a few example programs for students, I wrote the following boids simulation.  It’s a little quick and dirty, but I think it provides pretty convincing evidence for Python’s advertisement as “pseudo-code that runs.”  Cool flocking behavior in less than a page of code.  Each rule determines a small change in velocity of a boid as a function of its neighbors.  Note that I added a fourth rule, bounding, so that the boids tend to stay within a fixed volume of space, so that they don’t fly “off camera.”  Here is the code:

#!/usr/bin/env python

from visual import *
seterr(all='ignore')

num_boids = 30
fps = 24
dt = 1.0 / fps

boids = [arrow(pos=20 * (v - 0.5), axis=norm(v - 0.5))
         for v in random.rand(num_boids,3)]

def separation(boid):
    center = mean([b.pos for b in boids if b != boid and
                   abs(b.pos - boid.pos) < 5], 0)
    if isscalar(center):
        center = boid.pos
    return norm(boid.pos - center) * 0.05

def alignment(boid):
    heading = mean([b.axis for b in boids if b != boid and
                    abs(b.pos - boid.pos) < 10], 0)
    if isscalar(heading):
        heading = boid.axis
    return (norm(heading) - boid.axis) * 0.1

def cohesion(boid):
    center = mean([b.pos for b in boids if b != boid and
                   abs(b.pos - boid.pos) < 10], 0)
    if isscalar(center):
        center = boid.pos
    return norm(center - boid.pos) * 0.01

def bounding(boid):
    if abs(boid.pos) > 30:
        return -norm(boid.pos) * 0.25
    else:
        return vector(0, 0, 0)

rules = [separation, alignment, cohesion, bounding]

while True:
    rate(fps)
    for boid in boids:
        boid.axis = norm(boid.axis + sum([dv(boid) for dv in rules], 0))
        boid.pos = boid.pos + boid.axis * 10 * dt

The filtered list comprehensions and vectorized mathematical operations provided by Numpy (included with VPython) certainly contribute to the readability.

This sort of simulated “emergent” complexity of behavior has several interesting applications.  Hollywood has put it to good use: computer-generated bats and penguins simulated using algorithms similar to boids were first used in the movie Batman Returns.  (Okay, so maybe that’s not very good use.)  And the idea has taken off from there; Massive Software has a pretty cool gallery on their site showing, among other things, the many movies and games where similar technology has been used.

Fun with Dimensional Analysis

This post is a hodge-podge of interesting applications of dimensional analysis.  It is motivated by some recent work with students simulating and analyzing projectile motion.  Consider a ball (a baseball, in our running example) falling under gravity.  Then throw the ball in a flat-earth gravity field.  Then throw it, or drop it from a tall building, considering not just gravity but also air resistance, terminal velocity, etc.  (By the way, this is all very easy to experiment with using VPython, which I mentioned a few weeks ago and am now using with some success.)

This progression of complexity leads to the problem of “getting the units right” in the relevant equations.  For example, the terminal velocity of a baseball may be estimated by equating the opposing forces of drag due to air resistance and gravity:

F_D = \frac{1}{2}\rho v^2 C_D A = m g = W

where \rho is the air density (1.229 kg/m^3 at sea level on a standard day), v is the terminal velocity (m/s), C_D is the dimensionless coefficient of drag (approximately 0.3 for a baseball at these speeds), A is the reference area, or cross sectional area of the baseball (0.00427 m^2), m is the mass of the baseball (0.145 kg), and g is the acceleration due to gravity (9.8 m/s^2).

It may not be obvious to the student, or perhaps to the interested reader, that the “units” associated with each variable multiply and cancel to yield kg m/s^2 on both sides.  Or if it is obvious, then perhaps more interesting are the situations where (1) the equation is not known ahead of time, but can in fact be derived (up to a point) simply by considering the dimensions of the quantities involved; or (2) dimensional analysis of a problem can simplify experimental design by reducing the number of knobs to turn, so to speak.

As usual, my posts are usually just jumping-off points to better writing than you will find here.  I recommend reading Ain A. Sonin’s paper, which has some cool examples of this sort of thing, as well as a very clear and well-written motivation for and explanation of the related Buckingham’s \pi-Theorem, which puts dimensional analysis on a more rigorous linear algebraic footing.

I can think of a few other neat applications of dimensional analysis.  An episode of Mythbusters from a year or so ago involved trying to duplicate the bus jump stunt in the movie Speed.  The guys made a 1:12 scale model of the bus and bridge… but how should they scale the 50 mph speed of the full-size bus?  (Hint: it’s not just 1/12th of 50 mph.)  This was a nice puzzle, and I was pleased to see that they got it right… or at least, they used the same speed that I came up with.

Finally, I recall first seeing the following problem in an essay in my (very old) edition of Halliday and Resnick.  Automobile gas mileage is usually expressed in units like miles per gallon.  But both miles and gallons are derived length units; we can convert miles to, say, meters, and gallons to meters^3, yielding a gas mileage in units of 1/meters^2, or the reciprocal of an area.  For example, 25 miles per gallon corresponds to (1 divided by) 0.094 mm^2.

This “reciprocal area” has a nice physical interpretation.  Suppose that instead of a large fuel tank that travels with the car, we instead lay a very thin cylindrical tube along the length of the road.  We fill this thin tube with fuel, and the car “scoops” fuel from the tube as it travels.  The car’s gas mileage is then the required area of the cross section of the tube.  That is, lower gas mileage corresponds to a larger area, and vice versa.

(So as not to leave the falling baseball example behind, we can solve the equation above for velocity and, plugging in values, find that the terminal velocity of a baseball is approximately 95 miles per hour.  A follow-up question for students: how, then, have pitchers managed to throw faster than this, at speeds exceeding 100 mph?)

 

Challenging Unstated Assumptions

I will start with a puzzle, as background food for thought: what is the minimum number of weights, each weighing an integral number of pounds, required to be able to measure with a balance scale any integer weight from 1 pound to 30 pounds, inclusive?

When I was a kid, one of my mathematics classes participated in the Atlantic-Pacific Mathematics League.  This was nearly 25 years ago, but it seems the league is still going strong, with little or no change to the format.  Each month students get 6 problems and 30 minutes in which to solve them.  I remember looking forward to those problems each month.  The above puzzle was one of those problems.  Or at least, it is my best recollection of the exact statement of the problem.

What is the correct answer?  I will wait to discuss details until the end of this post.  Suffice it to say here that the answer that I submitted was incorrect… or so I was told.

I was reminded of this episode while reading an article published in the most recent College Mathematics Journal, written by Alif Anggoro, Eddy Liu, and Angus Tulloch.  The article is interesting for a couple of reasons.  First, the authors are seventh and eighth graders.  Second, the subject of the article involves a similar situation, where a problem seems to have multiple solutions, depending on assumptions that are not made explicit in the problem statement.

The article describes a dialogue between the students and their teacher, relating to the following problem: provide the next row of numbers in the following triangular array:

1

1, 1

1, 2, 1

1, 3, 3, 1

?, ?, ?, ?, ?

I usually dislike “complete the sequence” problems on sight, because they are under-determined.  As the students correctly point out, “any five numbers should be acceptable.”  In this case, the intended solution is the next row in Pascal’s triangle, or (1, 4, 6, 4, 1).  However, the students came up with (1, 4, 5, 4, 1), and they make the case in the article that the rule or pattern that yields this next row is even simpler than that for Pascal’s triangle.  I will leave it to you to either read the article or work out what that rule might be.

This raises all sorts of interesting questions.  What is meant by “simpler”?  The students’ pattern indeed yields a closed form expression for the (n, k) entry that is simpler to compute than the binomial coefficient of Pascal’s triangle.  However, the associated recurrence relation seems slightly more complex than its counterpart.

Resolution of these issues is not really the point of this post.  My point is simply to applaud the teacher’s encouragement of these questions.  The students were sufficiently interested in and excited about their proposed solution, and motivated to defend it at least in part by the teacher’s questions and arguments, that they investigated it further, proving several nice properties of “their” triangle in the process.

Coming back to the balance scale puzzle, I do not remember having quite the same engaging experience.  Here, the intended correct answer to the problem was five weights, of 1, 2, 4, 8, and 16 pounds, allowing measurement of any weight up to 31 pounds.  Looking back, I suppose the problem was meant to have a very specific purpose, to “teach” students about binary numbers, perhaps, just as the other students’ problem was intended to “teach” them about Pascal’s triangle.

The incorrect answer that I submitted was four weights of 1, 3, 9, and 17 pounds.  With these weights, you can measure any weight up to 30 pounds… with the caveat that sometimes you have to put weights on both sides of the scale.  For example, to measure the weight of a 2-pound object, you must put the object and the 1-pound weight on one plate of the scale, and the 3-pound weight on the other plate.

When I described my solution, my teacher responded to the effect of, “You can’t do that.”  Never mind that the problem didn’t state that I couldn’t do that.  I was supposed to learn about binary numbers, my solution did not reflect that, and the discussion stopped there.

That was a sad day, I think, because there were so many interesting paths where the problem and its multiple solutions could have led.  The intended “binary” solution works up to 31 pounds; why did my solution, which essentially assumed more “expressive power” in the use of weights, only work up to the required limit of 30 pounds?  Do other sets of weights do even better?  Is there a provably “best” set of weights?

As it turned out, I had the right idea, but a less-than-elegant implementation.  The 30-pound limit in the problem statement was a distraction, and I didn’t yet see the nicer “ternary” pattern emerging that would have allowed measurement of weights up to 40 pounds.

This is all a roundabout message to the teachers, parents, etc., out there: when a student asks, “Why can’t I do this?”, please do not respond with, “Because the plan was for you to do this.”  There is almost always interesting mathematics buried in those questions.  Instead, walk down the path with them: “Well, let’s see what happens if you do that.”  Quite often, it’s not just the student that ends up learning something.