[Edu-sig] Tomorrow's Python Class

kirby urner kirby.urner at gmail.com
Sat May 5 05:11:36 CEST 2007

My course is taking on a "how it works" aspect, looking at two topics
of interest to most teenagers:  computer games and animated movies.

I'm explaining how the games need real time computations to keep
the frame rate high, while the movies render frame by frame, taking
hours if need be, only to give a real time, non-interactive experience
at runtime.

My technologies for illustrating these two different approaches to
computer graphics:  VPython for real time with a frame rate;
POV-Ray for rendering animations.  Python sits in the driver's
seat vis-a-vis both of these engines.

More mathematically speaking, I'm finding a lot to recommend a
Vector class as a kind of anchoring concept, a foot in the door
if you will.

Having explored Dog and Monkey, maybe Human, as subclasses
of Mammal, with attention to dot notation and special names
such as __init__ and __repr__, I think they're ready for something
more abstract that nevertheless has a visual representation.

My default background color for the VPython window is cyan,
which looks a little like sea water in a coral lagoon.  So I talk about
"the fish tank" or "the aquarium" quite a bit (which might invoke
images of 'Shark Tale' in some of our savvy viewers).  That gives
the feeling of volume or space.  It's not a flat window, but a tank.

My Vector class includes the following simplification, which you
can actually find in some linear algebra texts:  vectors always
tail-originate at the origin.  If you want an edge that connects
any two points, neither of them (0,0,0), well, that's what we call
an Edge, and two vector tips define it.

So how do we define polyhedra i.e. shapes in general then?  I go
with a wireframe view to start, and require two data structures:  a
dictionary of vectors (tails at (0,0,0)), and a list of face tuples, each
element going around a face, either clockwise or counter-clockwise.

So, for example, a Tetrahedron (subclass of Polyhedron) requires
a dictionary of four vertices {'a':Vector((x0,y0,z0)), 'b':Vector((x1,y1,z1)),
'c':Vector((x2,y2,z2)), 'd':Vector((x3,y3,z3))} and a list of four face
tuples: [('a','b','c'), ('a','c','d'), ('a','d','b'),('b','c','d')]

Those of you already into computation geometry (CG) will recognize
that I'm not doing anything very original here.  The widely used
OFF format, plus VRML, are quite similar, in giving a list of points
(what I'm calling vectors) and a list of faces consisting of points,
going around each face.  Exactly my approach as well.

Back to the Vector class, my approach with VPython has been
to wrap the native visual.vector in a class of my own design.
I implement __add__ and __mul__ for vector addition and
scalar multiplication respectively.  __neg__ returns a vector
180 degrees from the current one (e.g. newv = - oldv).  Then
I have dot product and cross product, which I wouldn't insist
on using initially.  __sub__ may be defined as "adding the
additive inverse of" i.e. __add__ the __neg__ of some other
vector to this one...

I'm going in to such detail because I don't think these will just
be the idiosyncratic choices of a lone wolf independently
operating gnu math teacher.  Other gnu math teachers will
likely find this is just the right mix of newfangled and traditional.

We've been teaching Gibbs-style vectors for several decades
to high schoolers (it's a part of the IB curriculum as well as
most slightly accelerated USA/UK maths).  Now that computers
are in the picture, and uncluttered agiles like Python, I think
we're ready to rethink this whole segment, and do something
that's fun for a change (not to mention more job relevant than
ever).

Sample IDLE session:

IDLE 1.2
>>> from stickworks import Vector, Edge
>>> thevector = Vector((1,1,1))
>>> othervector = Vector((2,-1,1))
>>> thevector + othervector
Vector @ (3.0,0.0,2.0)
>>> 3 * thevector
Vector @ (3.0,3.0,3.0)
>>> - thevector

Traceback (most recent call last):
File "<pyshell#5>", line 1, in <module>
- thevector
TypeError: bad operand type for unary -: 'Vector'
>>> thevector - othervector
Vector @ (-1.0,2.0,0.0)

Well well well, I see I haven't implemented vector
negation.  Time to pop the code and add it then...

class Vector (object):

"""
A wrapper for visual.vector that expresses a cylinder via draw(),
always pegged to the origin
"""

def __init__(self, xyz, color=(0,0,1)):
self.v = vector(*xyz)
self.xyz = xyz
self.color = color
self.cyl = None

def draw(self):
"""define and render the cylinder"""
self.cyl = cylinder(pos = (0,0,0), axis = self.v, radius =
self.radius, color = self.color)

def erase(self):
"""toss the cylinder"""
if self.cyl:
self.cyl.visible = 0
self.cyl = None

def __repr__(self):
return 'Vector @ (%s,%s,%s)' % self.xyz

# some vector ops, including scalar multiplication

def diff_angle(self, other):
return self.v.diff_angle(other.v)

def cross(self, other):
temp = cross(self.v, other.v)
return Vector((temp.x, temp.y, temp.z))

def dot(self, other):
return dot(self.v, other.v)

def __sub__(self, other):
temp = self.v - other.v
return Vector((temp.x, temp.y, temp.z))

temp = self.v + other.v
return Vector((temp.x, temp.y, temp.z))

def __mul__(self, scalar):
temp = self.v * scalar
return Vector((temp.x, temp.y, temp.z))

__rmul__ = __mul__

def __neg__(self):
return Vector((-self.v.x, -self.v.y, -self.v.v))

>>> import stickworks
<module 'stickworks' from 'C:\Python25\lib\site-packages\stickworks.py'>
>>> from stickworks import Vector, Edge
>>> thevector = Vector((1,1,1))
>>> othervector = Vector((2,-1,1))
>>> -thevector
Vector @ (-1.0,-1.0,-1.0)
>>> -othervector
Vector @ (-2.0,1.0,-1.0)

Now maybe a teacher'd rather not *start* by wrapping a visual.vector
inside this a class (like a candy wrapper).  I could see doing a
more generic Vector first, and just using it lexically, not graphically.

VPython doesn't link the cylinder object to its vector in any tight way,
and yet the cylinder makes the most sense as a visual representation.
I add in a draw method such that thevector.draw() sticks a cylinder
into the fish tank, with its tail at the origin.  Until you explicitly *ask*
for this graphical mode.

If you look at my polyhedra.py, you'll see I wire up the Icosahedron
based on three golden rectangles with sides phi and 1.  That's
explained more visually in my Feb 2000 'Getting Inventive with
Vectors' piece, part of a 4 part numeracy + computer literacy
series ( http://www.4dsolutions.net/ocn/numeracy1.html ).

I actually keep these 3 mutually orthogonal golden rectangles
available for VPython viewing, as a part of the Icosahedron class:

class Icosahedron (Polyhedron):

def __init__(self, verts = dict(
# 12 vertices at the corners of 3 mutually
# orthogonal golden rectangles
xya=Vector(( phi/2, 0.5, 0.0)), # phi rectangle in xy
xyb=Vector(( phi/2,-0.5, 0.0)),
xyc=Vector((-phi/2,-0.5, 0.0)),
xyd=Vector((-phi/2, 0.5, 0.0)),
#-----------------------------
xza=Vector((-0.5, 0.0, phi/2)), # Phi rectangle in xz
xzb=Vector(( 0.5, 0.0, phi/2)),
xzc=Vector(( 0.5, 0.0,-phi/2)),
xzd=Vector((-0.5, 0.0,-phi/2)),
#-----------------------------
yza=Vector(( 0.0, phi/2, 0.5)), # Phi rectangle in yz
yzb=Vector(( 0.0, phi/2,-0.5)),
yzc=Vector(( 0.0,-phi/2,-0.5)),
yzd=Vector(( 0.0,-phi/2, 0.5)),
)):

# 12 vertices
self.vertices = verts

# 20 equiangular triangles
self.faces = (
('xza','xzb','yza'),
('xza','xzb','yzd'),
('yza','yzb','xyd'),
('yza','yzb','xya'),
('xzc','xzd','yzb'),
('xzc','xzd','yzc'),
('xya','xyb','xzb'),
('xya','xyb','xzc'),
('xyd','xyc','xza'),
('xyd','xyc','xzd'),
('yzd','yzc','xyb'),
('yzd','yzc','xyc'))

self.edges = self._distill()

self.rectangles = (
('xya','xyb','xyc','xyd'),
('xza','xzb','xzc','xzd'),
('yza','yzb','yzc','yzd'))

def goldrects(self):
Edge.color = green
for r in self.rectangles:
c0,c1,c2,c3 = [self.vertices[i] for i in r]
Edge(c0,c1).draw()
Edge(c1,c2).draw()
Edge(c2,c3).draw()
Edge(c3,c0).draw()

[ http://www.4dsolutions.net/ocn/python/polyhedra.py ]

Kirby