User:Tamfang/programs

From Wikimedia Commons, the free media repository
Jump to: navigation, search

Python programs to paint hyperbolic tilings. They require Python Imaging Library and NumPy (numeric library).

Use a command of the form "python filename.py 2 3 7" — if you give fewer than three numbers, ∞ (infinity) will be used.

Checkerboard designs[edit]

To show the fundamental regions.

import sys
from PIL import Image
from numpy import array,dot
from numpy.linalg import solve
from math import cos,sin,sqrt,pi
from fractions import Fraction

from numpy import seterr
seterr(all='raise')

# utility functions

width = 2520
xlist = [ float(2*u+1-width)/width for u in xrange(width) ]

def refl(vector,mir): return vector - 2*dot(vector,mir)*mir

pqr = [ eval(s) for s in sys.argv[1:] ]
#pqr.sort()
if len(pqr)>3:
        print "too many numbers; dropping last"
        pqr = pqr[:3]
if sum([ Fraction(1,x) for x in pqr ]) >= 1:
        print "the reciprocals of the numbers must add to less than 1"
        while sum([ Fraction(1,x) for x in pqr ]) >= 1:
                pqr.pop()

filestem = ""
for n in pqr: filestem += repr(n)
while len(filestem) < 3:
        filestem += "i"


# make a list of mirror planes

if not pqr:     # all pairs are asymptotic
        ir3 = 1/sqrt(3)
        mirror = [ array([1j*ir3, 2*ir3, 0]),
                 array([1j*ir3, -ir3, 1]),
                 array([1j*ir3, -ir3, -1])]
else:
        p = pqr.pop(0)
        pangle = pi/p
        cosqr = [ -cos(pi/u) for u in pqr ]
        while len(cosqr) < 2: cosqr.append(-1)

        v0 = [0,1,0]
        v11 = -cos(pangle)
        v12 = sin(pangle)
        v1 = [ 0, v11, v12 ]
        v21 = cosqr[0]
        v22 = (cosqr[1] - v11*v21) / v12
        v2 = [ 1j*sqrt(abs(1-v21**2-v22**2)), v21, v22 ]
        mirror = [ array(v0), array(v1), array(v2) ]

        # Move everything so that the origin is equidistant from the mirrors.

        omnipoint = solve(array(mirror), array([-1,-1,-1]))
        omnipoint /= sqrt(abs(dot(omnipoint,omnipoint)))
        if omnipoint[0].imag < 0: omnipoint = -omnipoint

        tempmirror = omnipoint - array([1j,0,0])
        tempmirror /= sqrt(abs(dot(tempmirror,tempmirror)))

        for j,u in enumerate(mirror):
                v = refl(u,tempmirror)
                if v[0].imag <0: v = -v
                mirror[j] = v

def thecolor( x0,x1 ):
        r2 = x0**2 + x1**2
        if r2 >= 1: return (255,0)
        bottom = 1-r2
        p = array([ 1j*(1+r2)/bottom, 2*x0/bottom, 2*x1/bottom ])

        flip = 0
        clean = 0
        while True:
                for j,u in enumerate(mirror):
                        if dot(p,u) > 0:
                                p = refl(p,u)
                                flip += 1
                                clean = 0
                        else:
                                clean += 1
                                if clean >= 3:
                                        return (255*(flip&1), 255)

im = Image.new("LA", (width,width) )
im.putdata( [ thecolor(x,y)
                for y in xlist
                for x in xlist
                ] )
im.save("H2checkers_%s.png" % filestem)

Uniform tilings[edit]

Creates seven images for seven uniform tilings from a given fundamental triangle. (I haven't yet written the code for the snub tiling.)

If one of your defining numbers is 2, you'll probably want to replace four of these images, using code from the next section.

import sys
from PIL import Image
from numpy import array,dot,cross
from numpy.linalg import solve
from math import cos,sin,sqrt,pi
from fractions import Fraction

from numpy import seterr
seterr(all='raise')

# utility functions

width = 2520
xlist = [ float(2*u+1-width)/width for u in xrange(width) ]

def refl(vector,mir): return vector - 2*dot(vector,mir)*mir

def unit(vector):
        magnitude = sqrt(abs(dot(vector,vector)))
        return vector/magnitude

pqr = [ eval(s) for s in sys.argv[1:] ]
if len(pqr)>3:
        print "too many numbers; dropping last"
        pqr = pqr[:3]
if sum([ Fraction(1,x) for x in pqr ]) >= 1:
        print "the reciprocals of the numbers must add to less than 1"
        while sum([ Fraction(1,x) for x in pqr ]) >= 1:
                pqr.pop()

filestem = ""
for n in pqr: filestem += repr(n)
while len(filestem) < 3:
        filestem += "i"

# make a list of mirror planes

if not pqr:     # all pairs are asymptotic
        ir3 = 1/sqrt(3)
        mirror = [ array([1j*ir3, 2*ir3, 0]),
                 array([1j*ir3, -ir3, -1]),
                 array([1j*ir3, -ir3, 1])]
else:
        p = pqr.pop(0)
        pangle = pi/p
        cosqr = [ -cos(pi/u) for u in pqr ]
        while len(cosqr) < 2: cosqr.append(-1)

        v0 = [0,1,0]
        v11 = -cos(pangle)
        v12 = sin(pangle)
        v1 = [ 0, v11, v12 ]
        v21 = cosqr[0]
        v22 = (cosqr[1] - v11*v21) / v12
        v2 = [ 1j*sqrt(abs(1-v21**2-v22**2)), v21, v22 ]
        mirror = [ array(v0), array(v1), array(v2) ]

        # Move everything so that the origin is equidistant from the mirrors.
        omnipoint = unit(solve(array(mirror), array([-1,-1,-1])))
        if omnipoint[0].imag < 0: omnipoint = -omnipoint
        tempmirror = unit(omnipoint - array([1j,0,0]))
        w = refl(array([1j,0,0]),tempmirror)
        for j,u in enumerate(mirror):
                v = refl(u,tempmirror)
                if v[0].imag <0: v = -v
                mirror[j] = v

for u in mirror: print "mirror", u

# The meat!

def decorate(abc):
        a,b,c = abc
        if a>0 and b<0:
                return (0,0,255,255)
        if c>0:
                return (255,255,0,255)
        return (255,0,0,255)

def thecolor( x0,x1 ):
        r2 = x0**2 + x1**2
        if r2 >= 1: return (0,0,0,0)
        bottom = 1-r2
        p = array([ 1j*(1+r2)/bottom, 2*x0/bottom, 2*x1/bottom ])
        clean = 0
        while True:
                for j,u in enumerate(mirror):
                        if dot(p,u) > 0:
                                p = refl(p,u)
                                clean = 0
                        else:
                                clean += 1
                                if clean >= 3:
                                        return decorate([ dot(p,u) for u in critplane])

for threebits in range(1,8):
        vertex = solve(array(mirror), array([ -((threebits>>2)&1), -(threebits&1), -((threebits>>1)&1) ]))
        critplane = [ unit(1j*cross(vertex,u)) for u in mirror ]

        im = Image.new("RGBA", (width,width) )
        im.putdata( [ thecolor(x,y)
                        for y in xlist
                        for x in xlist
                        ] )
        im.save("H2_tiling_%s-%o.png" % (filestem, threebits))

Uniform tilings with edges[edit]

Used where two faces of the same color share an edge.

Note that this is NOT a working program: you'll need to make four copies of this file and, in each of them, un-comment the lines appropriate to case 1 (regular), case 3 (truncated), case 4 (regular dual) or case 6 (truncated dual).

BUG: For this code to work right, the first input number must be 2.

import sys
import os
from PIL import Image
from numpy import array,dot,cross
from numpy.linalg import solve
from math import cos,sin,sqrt,pi
from fractions import Fraction

from numpy import seterr
seterr(all='raise')

# utility functions

width = 512
xlist = [ float(2*u+1-width)/width for u in xrange(width) ]

def refl(vector,mir): return vector - 2*dot(vector,mir)*mir

def unit(vector):
        magnitude = sqrt(abs(dot(vector,vector)))
        return vector/magnitude

pqr = [ eval(s) for s in sys.argv[1:] ]
if len(pqr)>3:
        print "too many numbers; dropping last"
        pqr = pqr[:3]
if sum([ Fraction(1,x) for x in pqr ]) >= 1:
        print "the reciprocals of the numbers must add to less than 1"
        while sum([ Fraction(1,x) for x in pqr ]) >= 1:
                pqr.pop()

filestem = ""
for n in pqr: filestem += repr(n)
while len(filestem) < 3:
        filestem += "i"

# make a list of mirror planes

if not pqr:     # all pairs are asymptotic
        ir3 = 1/sqrt(3)
        mirror = [ array([1j*ir3, 2*ir3, 0]),
                 array([1j*ir3, -ir3, -1]),
                 array([1j*ir3, -ir3, 1])]
else:
        p = pqr.pop(0)
        pangle = pi/p
        cosqr = [ -cos(pi/u) for u in pqr ]
        while len(cosqr) < 2: cosqr.append(-1)

        v0 = [0,1,0]
        v11 = -cos(pangle)
        v12 = sin(pangle)
        v1 = [ 0, v11, v12 ]
        v21 = cosqr[0]
        v22 = (cosqr[1] - v11*v21) / v12
        v2 = [ 1j*sqrt(abs(1-v21**2-v22**2)), v21, v22 ]
        mirror = [ array(v0), array(v1), array(v2) ]

        # Move everything so that the origin is equidistant from the mirror.

        omnipoint = unit(solve(array(mirror), array([-1,-1,-1])))
        if omnipoint[0].imag < 0: omnipoint = -omnipoint
        tempmirror = unit(omnipoint - array([1j,0,0]))
        for j,u in enumerate(mirror):
                v = refl(u,tempmirror)
                if v[0].imag <0: v = -v
                mirror[j] = v

for u in mirror: print "mirror", u
v0,v1,v2 = mirror

# The meat!

def thecolor( x0,x1 ):
        r2 = x0**2 + x1**2
        if r2 >= 1: return (255,255,255,0)

        bottom = 1-r2
        p = array([ 1j*(1+r2)/bottom, 2*x0/bottom, 2*x1/bottom ])

        clean = 0
        while True:
                for j,u in enumerate(mirror):
                        if dot(p,u) > 0:
                                p = refl(p,u)
                                clean = 0
                        else:
                                clean += 1
                                if clean >= 3:
                                        #1:
                                        #if abs(dot(p,v0)) < 0.02: return (0,0,255,255)
                                        #return (255,0,0,255)

                                        #3:
                                        #if dot(p,critplane) < 0: return (255,0,0,255)
                                        #if abs(dot(p,v0)) < 0.01: return (0,0,255,255)
                                        #return (255,255,0,255)

                                        #4:
                                        #if abs(dot(p,v1)) < 0.02: return (0,0,255,255)
                                        #return (255,255,0,255)

                                        #6:
                                        #if dot(p,critplane) < 0: return (255,255,0,255)
                                        #if abs(dot(p,v1)) < 0.01: return (0,0,255,255)
                                        #return (255,0,0,255)

#3:
#vertex = solve(array(mirror), array([0,1,1]))

#6:
#vertex = solve(array(mirror), array([1,0,1]))

critplane = 1j*cross(vertex,v2)

im = Image.new("RGBA", (width,width) )
im.putdata( [ thecolor(x,y)
                for y in xlist
                for x in xlist
                ] )
im.save("%s-%s.png" % (filestem, os.path.splitext(sys.argv[0])[0]))