--- jupyter: description: 'The Sierpiński triangle with planex2d: middle triangles cut out with Boolean subtractions, 3^n triangles with (3/4)^n of the area and a boundary of 3(3/2)^n times the side, and the Sierpiński carpet, one face with (8^n − 1)/7 holes and (8/9)^n of the area.' --- # 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 $$A_n = \left(\tfrac{3}{4}\right)^n A_0, \qquad L_n = 3 \left(\tfrac{3}{2}\right)^n s,$$ 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](geometry-and-design.md) using planex2d. .. rst-class:: px-example-links `Open this example in the playground `__, the interactive code editor in the browser, to change the code and run it. ![The Sierpiński triangle after six steps and the Sierpiński carpet after four](../_static/gallery/sierpinski-triangle.png){.result} ```python import math import planex as px ``` ```python nbsphinx="hidden" from docs_figures import Row ``` ## 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: ```python 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): ```python 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) ``` ```python nbsphinx="hide-input" Row(*[(steps[n], f"After {n} {'step' if n == 1 else 'steps'}: {3**n} triangles") for n in (1, 2, 4, 6)], caption="The Sierpiński triangle step by step") ``` 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): ```python 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) ``` ```python nbsphinx="hide-input" Row((carpet, "After four steps: one face with 585 holes", {"lw": 0.6}), caption="The Sierpiński carpet") ``` See [Many faces](../core/booleans/many-faces.md) for `px.Subtract`. ```python nbsphinx="hidden" import sys import matplotlib.pyplot as plt sys.path.insert(0, "..") from docs_figures import FACE, INK, PLANEX from gallery import save_figure fig, ax = plt.subplots(figsize=(6, 4)) steps[6].Plot(ax=ax, face_col=PLANEX, line_col=PLANEX, legend=False) carpet.Translated((s + 15, 0)).Plot(ax=ax, face_col=FACE, line_col=INK, legend=False) for artist in ax.lines + ax.collections + ax.patches: artist.set_linewidth(0.35) ax.set_xlim(-12, 2 * s + 27) ax.set_ylim(-26, s + 26) ax.set_aspect("equal") ax.axis("off") save_figure(fig, "sierpinski-triangle") plt.close(fig) ```