Tomorrow's Python Class
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 """ radius = 0.03 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)) def __add__(self, other): 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 reload(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
participants (1)
-
kirby urner