15.3 Fractal Dimension
In addition to visual self-similarity, fractals exhibit other interesting properties. For example, notice that each step of the Sierpinski gasket iteration removes one quarter of the remaining area. If this process is continued indefinitely, we would end up essentially removing all the area, meaning we started with a 2-dimensional area, and somehow end up with something less than that, but seemingly more than just a 1-dimensional line.
import numpy as np
import matplotlib.pyplot as plt
# Build a Sierpinski gasket as a cloud of points (the "chaos game"),
# then MEASURE its dimension by covering it with smaller and smaller boxes.
N_POINTS = 40000 # EDIT
SHAPE = "gasket" # EDIT: "gasket", "square", or "segment"
rng = np.random.default_rng(7)
if SHAPE == "gasket":
verts = np.array([[0.0, 0.0], [1.0, 0.0], [0.5, np.sqrt(3) / 2]])
pts = np.zeros((N_POINTS, 2))
cur = verts[0]
for i, j in enumerate(rng.integers(0, 3, N_POINTS)):
cur = (cur + verts[j]) / 2
pts[i] = cur
elif SHAPE == "square":
pts = rng.random((N_POINTS, 2))
else:
pts = np.column_stack([rng.random(N_POINTS), np.full(N_POINTS, 0.5)])
sizes, counts = [], []
print(f"{'grid':>8}{'box size':>12}{'boxes hit N':>14}")
for k in range(2, 9):
m = 2 ** k
cells = {(int(x * m), int(y * m)) for x, y in pts}
sizes.append(1 / m)
counts.append(len(cells))
print(f"{m:>6}^2{1 / m:>12.5f}{len(cells):>14,}")
logs, logn = np.log(1 / np.array(sizes)), np.log(counts)
slope, intercept = np.polyfit(logs, logn, 1)
exact = np.log(3) / np.log(2)
print(f"\nN grows like (1/size)^D, so D is the slope of log N against log(1/size)")
print(f" measured D = {slope:.4f}")
if SHAPE == "gasket":
print(f" exact D = log(3)/log(2) = {exact:.4f} (a 2-D shape would give 2.0000)")
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4.2))
ax1.plot(pts[::10, 0], pts[::10, 1], ",", color="#1f4e79")
ax1.set_aspect("equal"); ax1.axis("off"); ax1.set_title(f"{SHAPE}: {N_POINTS:,} points")
ax2.plot(logs, logn, "o-", color="#b03a2e")
ax2.plot(logs, slope * logs + intercept, "--", color="gray")
ax2.set_xlabel("log(1 / box size)"); ax2.set_ylabel("log(boxes hit)")
ax2.set_title(f"slope = measured dimension = {slope:.3f}")
plt.tight_layout(); plt.show()
# Try it: set SHAPE = "square" (slope near 2) or "segment" (slope near 1).
To explore this idea, we need to discuss dimension. Something like a line is 1-dimensional; it only has length. Any curve is 1-dimensional. Things like boxes and circles are 2-dimensional, since they have length and width, describing an area. Objects like boxes and cylinders have length, width, and height, describing a volume, and are 3-dimensional.
Certain rules apply for scaling objects, related to their dimension.
If I had a line with length 1, and wanted scale its length by 2, I would need two copies of the original line. If I had a line of length 1, and wanted to scale its length by 3, I would need three copies of the original.
If I had a rectangle with length 2 and height 1, and wanted to scale its length and width by 2, I would need four copies of the original rectangle. If I wanted to scale the length and width by 3, I would need nine copies of the original rectangle.
If I had a cubical box with sides of length 1, and wanted to scale its length, its width, and its height by 2, I would need eight copies of the original cube. If I wanted to scale the length, width, and height by 3, I would need 27 copies of the original cube.
Notice that in the 1-dimensional case, copies needed = scale.
- In the 2-dimensional case, copies needed scale.
- In the 3-dimensional case, copies needed scale.
From these examples, we might infer a pattern.
We will now turn our attention to another type of fractal, defined by a different type of recursion. To understand this type, we are first going to need to discuss complex numbers.
Adapted from Math in Society by David Lippman, hosted on LibreTexts (math.libretexts.org) and licensed under CC BY-SA 3.0. Changes were made. License: CC-BY-SA-3.0.