The Sierpiński triangle
How can a shape lose all its area while its boundary grows without end? The Sierpiński triangle starts from a triangle, cuts out the middle triangle between the midpoints of its sides and does the same to each of the three triangles left, again and again. Each step keeps three quarters of the area and adds half to the boundary. After \(n\) steps, \(3^n\) triangles of side \(s / 2^n\) are left, with
so the limit has no area and an endless boundary. The Sierpiński carpet cuts the middle ninth out of squares instead and stays in one piece, full of holes.
The example cuts both with planex2d’s Boolean subtraction and checks the number of faces and holes, the areas and the boundary lengths against the formulas.
This page is part of a collection of geometry and design examples using planex2d.
Open this example in the playground, the interactive code editor in the browser, to change the code and run it.

[1]:
import math
import planex as px
Cutting out the middle
The example starts from an equilateral triangle with the side \(s = 100\). A step cuts the middle triangle out of every triangle at once, with one Boolean subtraction px.Subtract, and returns the corners of the three triangles left of each:
[3]:
s = 100
first = [((0, 0), (s, 0), (s / 2, s * math.sqrt(3) / 2))]
triangle = px.FromPolyline(first[0], closed=True).Face()
def middle(p, q):
return ((p[0] + q[0]) / 2, (p[1] + q[1]) / 2)
def step(shape, corners):
"""cuts the middle triangle out of each triangle of corners; the corners of the triangles left"""
middles, smaller = [], []
for a, b, c in corners:
ab, bc, ca = middle(a, b), middle(b, c), middle(c, a)
middles.append(px.FromPolyline([ab, bc, ca], closed=True).Face())
smaller += [(a, ab, ca), (ab, b, bc), (ca, bc, c)]
return px.Subtract([shape] + middles), smaller
Area and boundary
After each step, the faces are counted and the area is compared with the triangle’s. The triangles left touch at their corners only, so they share no edges, and the boundary is the sum of their perimeters (Figure 1):
[4]:
shape, corners = triangle, first
steps = {}
for n in range(1, 7):
shape, corners = step(shape, corners)
steps[n] = shape
boundary = sum(edge.mass for edge in shape.edges)
print("n", n, "| faces", len(shape.faces), "3^n", 3**n, "| area", shape.mass / triangle.mass, "(3/4)^n", 0.75**n,
"| boundary", boundary, "3 (3/2)^n s", 3 * 1.5**n * s)
# px.Plot(shape)
n 1 | faces 3 3^n 3 | area 0.75 (3/4)^n 0.75 | boundary 450.0 3 (3/2)^n s 450.0
n 2 | faces 9 3^n 9 | area 0.5624999999999999 (3/4)^n 0.5625 | boundary 675.0 3 (3/2)^n s 675.0
n 3 | faces 27 3^n 27 | area 0.42187499999999994 (3/4)^n 0.421875 | boundary 1012.5 3 (3/2)^n s 1012.5
n 4 | faces 81 3^n 81 | area 0.31640624999999983 (3/4)^n 0.31640625 | boundary 1518.75 3 (3/2)^n s 1518.75
n 5 | faces 243 3^n 243 | area 0.23730468749999964 (3/4)^n 0.2373046875 | boundary 2278.125 3 (3/2)^n s 2278.125
n 6 | faces 729 3^n 729 | area 0.1779785156249998 (3/4)^n 0.177978515625 | boundary 3417.1875 3 (3/2)^n s 3417.1875
(a) After 1 step: 3 triangles
(b) After 2 steps: 9 triangles
(c) After 4 steps: 81 triangles

(d) After 6 steps: 729 triangles
After six steps, 729 triangles hold less than a fifth of the area, and the boundary is more than eleven times the triangle’s perimeter.
The carpet
The Sierpiński carpet does the same with squares. Each square is cut into nine and the middle one is taken out. The squares left share their sides, so the carpet stays one face, and every square taken out is a hole in it. After \(n\) steps it has \((8^n - 1)/7\) holes and \((8/9)^n\) of the area (Figure 2):
[6]:
def carpet_step(face, cells):
"""cuts the middle ninth out of each square (x, y, side) of cells; the squares left"""
holes, smaller = [], []
for x, y, w in cells:
w3 = w / 3
holes.append(px.QuickSketch().Move((x + w3, y + w3)).Rectangle(w3, w3).Face())
smaller += [(x + i * w3, y + j * w3, w3) for i in range(3) for j in range(3) if (i, j) != (1, 1)]
return px.Subtract([face] + holes), smaller
square = px.QuickSketch().Rectangle(s, s).Face()
carpet, cells = square, [(0, 0, s)]
for n in range(1, 5):
carpet, cells = carpet_step(carpet, cells)
print("n", n, "| faces", len(carpet.faces), "| holes", len(carpet.faces[0].wires) - 1, "(8^n - 1)/7",
(8**n - 1) // 7, "| area", carpet.mass / square.mass, "(8/9)^n", (8 / 9)**n)
# px.Plot(carpet)
n 1 | faces 1 | holes 1 (8^n - 1)/7 1 | area 0.8888888888888888 (8/9)^n 0.8888888888888888
n 2 | faces 1 | holes 9 (8^n - 1)/7 9 | area 0.7901234567901234 (8/9)^n 0.7901234567901234
n 3 | faces 1 | holes 73 (8^n - 1)/7 73 | area 0.7023319615912215 (8/9)^n 0.7023319615912207
n 4 | faces 1 | holes 585 (8^n - 1)/7 585 | area 0.6242950769699701 (8/9)^n 0.624295076969974

See Many faces for px.Subtract.