five-dimensional hypercube for Beatrice's birthday

# I wrote this on Beatrice's birthday, but didn't get around to posting
# it until now.

#!/usr/bin/python
# 5-dimensional hypercube with 32 vertices for Bea's 32nd birthday
# Written in Python with list-comprehensions and "sum"; works in
# Python 2.3, might work in 2.2
import random, math

def vecmag(vec):
return math.sqrt(sum([x*x for x in vec]))

def vec_norm(vec):
size = vecmag(vec)
return [x / size for x in vec]

# generate randomish vectors to project things onto
# These vectors are insufficiently random --- they're more likely to
# point toward the corners of the unit cube rather than the faces.  oh
# well.
def randvec():
return vec_norm([random.random() * 2 - 1 for ii in range(5)])

def fourdcubepoints():
rv = []
for a in (0, 1):
for b in (0, 1):
for c in (0, 1):
for d in (0, 1):
rv.append((a, b, c, d))
return rv

def cubelines():
points = fourdcubepoints()
rv = []
for dimension in range(5):
for point in points:
rv.append((
point[:dimension] + (0,) + point[dimension:],
point[:dimension] + (1,) + point[dimension:]))
return rv

def dotprod(a, b):
assert len(a) == len(b)
return sum([a[ii] * b[ii] for ii in range(len(a))])

def twodlines(veca, vecb):
lines = cubelines()
return [((dotprod(line[0], veca), dotprod(line[0], vecb)),
(dotprod(line[1], veca), dotprod(line[1], vecb)))
for line in lines]

def pslines(lines, scale):
rv = []
for line in lines:
rv.append("%d %d moveto\n" % (line[0][0] * scale, line[0][1] * scale))
rv.append("%d %d lineto\n" % (line[1][0] * scale, line[1][1] * scale))
rv.append("stroke showpage\n")
return ''.join(rv)

def bbox(lines):
minx, miny, maxx, maxy = 10000, 10000, -10000, -10000
for line in lines:
for x, y in line:
if x < minx: minx = x
if x > maxx: maxx = x
if y < miny: miny = y
if y > maxy: maxy = y
return minx, miny, maxx, maxy

def getscale(box, width, height):
minx, miny, maxx, maxy = box
yscale = height / (maxy - miny)
xscale = width / (maxx - minx)
return min(xscale, yscale)

def orthvec(basevec, othervec):
amount = sum([othervec[ii] * basevec[ii]
for ii in range(len(basevec))])
return vec_norm([othervec[ii] - basevec[ii] * amount
for ii in range(len(basevec))])

pagewidth = 8 * 72
pageheight = 10.5 * 72
offset = 72 / 4
def goodpic():
veca, vecb = randvec(), randvec()
vecb = orthvec(veca, vecb)
lines = twodlines(veca, vecb)
box = bbox(lines)
scale = getscale(box, pagewidth, pageheight)
minx, miny, _, _ = box
offsetx, offsety = offset - minx * scale, offset - miny * scale
return "%%!\n%d %d translate\n" % (offsetx, offsety) + pslines(lines, scale)