Penrose tiling by deflation
Roger Penrose’s rhombus tiling covers the plane without ever repeating. It grows by deflation: each half-rhombus, a golden triangle, splits into smaller ones by the golden ratio φ. The example starts from a wheel of ten golden triangles of radius 100, deflates it seven times and builds the tiles of the sixth generation. The tiles are exact planex2d faces. Their union must keep the area of the wheel it started from, and the ratio of thick to thin rhombs tends to φ.

[1]:
import cmath
import math
import matplotlib.pyplot as plt
import planex as px
Deflation
A triangle is its kind, 0 for a half of a thin rhomb and 1 for a half of a thick one, and its corners A, B and C as complex numbers. deflate splits every triangle into two or three smaller ones. Check: the ratio of thick to thin halves tends to φ:
[3]:
phi = (1 + math.sqrt(5)) / 2
radius = 100.0
def deflate(triangles):
result = []
for kind, A, B, C in triangles:
if kind == 0:
P = A + (B - A) / phi
result += [(0, C, P, B), (1, P, C, A)]
else:
Q = B + (A - B) / phi
R = B + (C - B) / phi
result += [(1, R, C, A), (1, Q, R, B), (0, R, Q, A)]
return result
wheel = []
for i in range(10):
B = cmath.rect(radius, (2 * i - 1) * math.pi / 10)
C = cmath.rect(radius, (2 * i + 1) * math.pi / 10)
wheel.append((0, 0j, C, B) if i % 2 == 0 else (0, 0j, B, C))
generations = [wheel]
for g in range(7):
generations.append(deflate(generations[-1]))
for g, triangles in enumerate(generations):
thin = sum(1 for t in triangles if t[0] == 0)
thick = len(triangles) - thin
print("generation", g, thick, "thick and", thin, "thin halves, ratio", round(thick / thin if thin else 0, 6))
print("golden ratio", round(phi, 6))
generation 0 0 thick and 10 thin halves, ratio 0.0
generation 1 10 thick and 10 thin halves, ratio 1.0
generation 2 30 thick and 20 thin halves, ratio 1.5
generation 3 80 thick and 50 thin halves, ratio 1.6
generation 4 210 thick and 130 thin halves, ratio 1.615385
generation 5 550 thick and 340 thin halves, ratio 1.617647
generation 6 1440 thick and 890 thin halves, ratio 1.617978
generation 7 3770 thick and 2330 thin halves, ratio 1.618026
golden ratio 1.618034
Exact tiles
Each half-rhombus of the sixth generation is a planex2d face. Check: their union is the wheel of ten triangles they came from, one face with the area 10 r² sin(36°) / 2:
[4]:
def face(A, B, C):
return px.FromPolyline([(A.real, A.imag), (B.real, B.imag), (C.real, C.imag)], closed=True).Face()
triangles = generations[6]
halves = [(kind, face(A, B, C)) for kind, A, B, C in triangles]
union = px.Unify([f for kind, f in halves])
print(len(halves), "triangles, union area", union.mass, "wheel", 10 * radius**2 * math.sin(math.radians(36)) / 2)
print(len(union.faces), "face")
2330 triangles, union area 29389.262614623654 wheel 29389.262614623658
1 face
Rhombs
Two halves that share their base make one rhomb, either a thick one of 72° or a thin one of 36° (Figure 1).
[5]:
pairs = {}
for (kind, A, B, C), (_, f) in zip(triangles, halves):
key = (round((B + C).real / 2, 6), round((B + C).imag / 2, 6))
pairs.setdefault(key, []).append((kind, f))
rhombs = {0: [], 1: []}
for parts in pairs.values():
if len(parts) == 2:
rhombs[parts[0][0]].append(px.Unify([parts[0][1], parts[1][1]]))
print(len(rhombs[1]), "thick and", len(rhombs[0]), "thin whole rhombs, ratio", round(len(rhombs[1]) / len(rhombs[0]), 4))
700 thick and 440 thin whole rhombs, ratio 1.5909
