--- jupyter: description: 'The Koch snowflake with planex2d: six steps from a triangle to 12288 corners, the area against A0 (1 + 3/5 (1 − (4/9)^n)) on its way to 8/5 of the triangle, the perimeter growing by 4/3 per step, and the whole snowflake inside the circle through the triangle''s corners.' --- # The Koch snowflake How can a shape that fits inside a circle have an endless boundary? The Koch snowflake starts from an equilateral triangle. In every step, the middle third of each edge is replaced by two sides of a small equilateral triangle pointing outward. Every edge becomes four edges a third as long, so the perimeter grows by $4/3$ per step, without end. The area grows too, but by less each time, and tends to $8/5$ of the triangle's area $A_0$. After $n$ steps, $$A_n = A_0 \left(1 + \tfrac{3}{5} \left(1 - \left(\tfrac{4}{9}\right)^n\right)\right), \qquad P_n = 3 s \left(\tfrac{4}{3}\right)^n.$$ The example builds six steps as polylines and lets planex2d compute the area and the perimeter of each, which are checked against the formulas. It also checks that every step stays inside the circle through the triangle's corners. 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 Koch snowflake after five steps, the first steps drawn inside it](../_static/gallery/koch-snowflake.png){.result} ```python import math import numpy as np import planex as px ``` ```python nbsphinx="hidden" from docs_figures import Row ``` ## The steps The triangle has the side $s = 100$ and runs counterclockwise, so outward is to the right of each edge. A step turns the middle third of every edge by 60° to the right and adds the corner of the small triangle. NumPy does this for all edges at once: ```python s = 100 triangle = np.array([(0, 0), (s, 0), (s / 2, s * math.sqrt(3) / 2)]) def koch(points): """every edge's middle third replaced by two sides of an equilateral triangle, pointing outward""" third = (np.roll(points, -1, axis=0) - points) / 3 c, si = math.cos(math.radians(-60)), math.sin(math.radians(-60)) turned = np.column_stack([c * third[:, 0] - si * third[:, 1], si * third[:, 0] + c * third[:, 1]]) return np.stack([points, points + third, points + third + turned, points + 2 * third], axis=1).reshape(-1, 2) ``` ## Area and perimeter Each step is a closed polyline, and planex2d's face inside it gives the area and the length of its outline (Figure 1): ```python A0 = math.sqrt(3) / 4 * s**2 points = triangle flakes = {} for n in range(7): flakes[n] = px.FromPolyline(points, closed=True).Face() print("n", n, "| corners", len(points), "| area / A0", flakes[n].mass / A0, "formula", 1 + 3 / 5 * (1 - (4 / 9)**n), "| perimeter", flakes[n].wires[0].mass, "3 s (4/3)^n", 3 * s * (4 / 3)**n) points = koch(points) # px.Plot(flakes[6]) ``` ```python nbsphinx="hide-input" Row(*[(flakes[n], f"After {n} {'step' if n == 1 else 'steps'}: {3 * 4**n} edges", {"lw": 1.0 if n > 3 else 1.8}) for n in (1, 2, 3, 6)], caption="The Koch snowflake step by step") ``` ## Inside a circle The corners of the small triangles never reach past the circle through the triangle's corners, so the whole snowflake stays inside it, and the Boolean difference of each step and the disc is empty. Its area stays below $8/5$ of the triangle's while its perimeter grows without end, so the isoperimetric quotient $4 \pi A / P^2$, which is 1 for the circle, falls toward zero: ```python center = (s / 2, s * math.sqrt(3) / 6) disc = px.Circle(center, s / math.sqrt(3)).Face() for n, flake in flakes.items(): print("n", n, "| outside the disc", (flake - disc).mass, "| 4πA/P²", 4 * math.pi * flake.mass / flake.wires[0].mass**2) ``` See [The isoperimetric inequality](isoperimetric-inequality.md) for the quotient and [The coastline paradox](coastline-paradox.md) for a real boundary whose length depends on the ruler. ```python nbsphinx="hidden" import sys import matplotlib.pyplot as plt sys.path.insert(0, "..") from docs_figures import FACE, PLANEX from gallery import save_figure fig, ax = plt.subplots(figsize=(6, 4)) flakes[6].Plot(ax=ax, face_col=FACE, line_col=PLANEX, legend=False) for artist in ax.lines + ax.collections + ax.patches: artist.set_linewidth(0.8) ax.set_xlim(center[0] - 90, center[0] + 90) ax.set_ylim(center[1] - 60, center[1] + 60) ax.set_aspect("equal") ax.axis("off") save_figure(fig, "koch-snowflake") plt.close(fig) ```