Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ok-landscape
GitHub Repository: Ok-landscape/computational-pipeline
Path: blob/main/latex-templates/templates/mathematics/fractals.tex
51 views
unlisted
1
\documentclass[a4paper, 11pt]{report}
2
\usepackage[utf8]{inputenc}
3
\usepackage[T1]{fontenc}
4
\usepackage{amsmath, amssymb}
5
\usepackage{graphicx}
6
\usepackage{siunitx}
7
\usepackage{booktabs}
8
\usepackage{xcolor}
9
\usepackage[makestderr]{pythontex}
10
11
\definecolor{mandel}{RGB}{0, 0, 139}
12
\definecolor{julia}{RGB}{139, 0, 139}
13
\definecolor{sierp}{RGB}{34, 139, 34}
14
\definecolor{koch}{RGB}{255, 140, 0}
15
16
\title{Fractal Geometry and Self-Similarity:\\
17
Computational Analysis of Fractal Structures}
18
\author{Department of Applied Mathematics\\Technical Report AM-2024-003}
19
\date{\today}
20
21
\begin{document}
22
\maketitle
23
24
\begin{abstract}
25
This report presents a computational exploration of fractal geometry, examining the Mandelbrot set, Julia sets, Sierpinski triangle, and Koch snowflake. We compute fractal dimensions using box-counting methods, analyze escape-time algorithms, and investigate the self-similar structures that characterize these mathematical objects. All visualizations are generated using PythonTeX for complete reproducibility.
26
\end{abstract}
27
28
\tableofcontents
29
30
\chapter{Introduction}
31
32
Fractals are geometric objects that exhibit self-similarity at all scales. Unlike classical Euclidean geometry, fractals often have non-integer (fractal) dimensions, reflecting their intricate structure.
33
34
\section{Defining Fractals}
35
A set $F$ is a fractal if:
36
\begin{itemize}
37
\item It has a fine structure at arbitrarily small scales
38
\item It is too irregular to be described by traditional geometry
39
\item It exhibits self-similarity (exact or statistical)
40
\item Its fractal dimension exceeds its topological dimension
41
\end{itemize}
42
43
\section{Fractal Dimension}
44
The box-counting dimension is defined as:
45
\begin{equation}
46
D = \lim_{\epsilon \to 0} \frac{\log N(\epsilon)}{\log(1/\epsilon)}
47
\end{equation}
48
where $N(\epsilon)$ is the number of boxes of size $\epsilon$ needed to cover the set.
49
50
\chapter{The Mandelbrot Set}
51
52
\section{Definition}
53
The Mandelbrot set $M$ is defined as the set of complex numbers $c$ for which the iteration:
54
\begin{equation}
55
z_{n+1} = z_n^2 + c, \quad z_0 = 0
56
\end{equation}
57
remains bounded as $n \to \infty$.
58
59
\begin{pycode}
60
import numpy as np
61
import matplotlib.pyplot as plt
62
from matplotlib.colors import LinearSegmentedColormap
63
plt.rc('text', usetex=True)
64
plt.rc('font', family='serif')
65
66
np.random.seed(42)
67
68
def mandelbrot(c, max_iter):
69
z = 0
70
for n in range(max_iter):
71
if abs(z) > 2:
72
return n
73
z = z*z + c
74
return max_iter
75
76
def mandelbrot_set(xmin, xmax, ymin, ymax, width, height, max_iter):
77
x = np.linspace(xmin, xmax, width)
78
y = np.linspace(ymin, ymax, height)
79
mset = np.zeros((height, width))
80
81
for i in range(height):
82
for j in range(width):
83
c = complex(x[j], y[i])
84
mset[i, j] = mandelbrot(c, max_iter)
85
86
return mset
87
88
# Generate Mandelbrot set
89
width, height = 800, 600
90
max_iter = 100
91
92
# Full view
93
mset_full = mandelbrot_set(-2.5, 1.0, -1.25, 1.25, width, height, max_iter)
94
95
# Zoomed view (Seahorse valley)
96
mset_zoom = mandelbrot_set(-0.75, -0.73, 0.1, 0.12, width, height, 200)
97
98
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
99
100
ax = axes[0]
101
im = ax.imshow(mset_full, extent=[-2.5, 1.0, -1.25, 1.25], cmap='hot', aspect='equal')
102
ax.set_xlabel('Re$(c)$')
103
ax.set_ylabel('Im$(c)$')
104
ax.set_title('Mandelbrot Set')
105
plt.colorbar(im, ax=ax, label='Iterations')
106
107
ax = axes[1]
108
im = ax.imshow(mset_zoom, extent=[-0.75, -0.73, 0.1, 0.12], cmap='twilight_shifted', aspect='equal')
109
ax.set_xlabel('Re$(c)$')
110
ax.set_ylabel('Im$(c)$')
111
ax.set_title('Seahorse Valley (Zoom)')
112
plt.colorbar(im, ax=ax, label='Iterations')
113
114
plt.tight_layout()
115
plt.savefig('mandelbrot_set.pdf', dpi=150, bbox_inches='tight')
116
plt.close()
117
\end{pycode}
118
119
\begin{figure}[htbp]
120
\centering
121
\includegraphics[width=0.95\textwidth]{mandelbrot_set.pdf}
122
\caption{The Mandelbrot set: full view (left) and zoomed into the Seahorse Valley (right), demonstrating self-similarity.}
123
\end{figure}
124
125
\section{Escape Time Algorithm}
126
Points outside $M$ are colored by escape time---the number of iterations before $|z_n| > 2$:
127
\begin{equation}
128
\text{escape time}(c) = \min\{n : |z_n| > 2\}
129
\end{equation}
130
131
\chapter{Julia Sets}
132
133
\section{Definition}
134
For a fixed $c$, the filled Julia set $K_c$ consists of initial points $z_0$ for which $z_{n+1} = z_n^2 + c$ remains bounded.
135
136
\begin{pycode}
137
def julia_set(c, xmin, xmax, ymin, ymax, width, height, max_iter):
138
x = np.linspace(xmin, xmax, width)
139
y = np.linspace(ymin, ymax, height)
140
jset = np.zeros((height, width))
141
142
for i in range(height):
143
for j in range(width):
144
z = complex(x[j], y[i])
145
for n in range(max_iter):
146
if abs(z) > 2:
147
jset[i, j] = n
148
break
149
z = z*z + c
150
else:
151
jset[i, j] = max_iter
152
153
return jset
154
155
# Different Julia sets
156
c_values = [
157
complex(-0.7, 0.27015),
158
complex(0.285, 0.01),
159
complex(-0.8, 0.156),
160
complex(-0.4, 0.6),
161
]
162
163
fig, axes = plt.subplots(2, 2, figsize=(12, 12))
164
165
for ax, c in zip(axes.flatten(), c_values):
166
jset = julia_set(c, -1.5, 1.5, -1.5, 1.5, 600, 600, 100)
167
ax.imshow(jset, extent=[-1.5, 1.5, -1.5, 1.5], cmap='magma', aspect='equal')
168
ax.set_xlabel('Re$(z)$')
169
ax.set_ylabel('Im$(z)$')
170
ax.set_title(f'Julia Set: $c = {c.real:.3f} + {c.imag:.3f}i$')
171
172
plt.tight_layout()
173
plt.savefig('julia_sets.pdf', dpi=150, bbox_inches='tight')
174
plt.close()
175
\end{pycode}
176
177
\begin{figure}[htbp]
178
\centering
179
\includegraphics[width=0.95\textwidth]{julia_sets.pdf}
180
\caption{Julia sets for different values of $c$, showing connected ($c \in M$) and disconnected ($c \notin M$) structures.}
181
\end{figure}
182
183
\section{Connection to Mandelbrot Set}
184
The Julia set $K_c$ is connected if and only if $c \in M$. Otherwise, $K_c$ is a Cantor set (totally disconnected).
185
186
\chapter{Sierpinski Triangle}
187
188
\section{Iterated Function System}
189
The Sierpinski triangle can be generated using three affine transformations:
190
\begin{align}
191
T_1(\mathbf{x}) &= \frac{1}{2}\mathbf{x} \\
192
T_2(\mathbf{x}) &= \frac{1}{2}\mathbf{x} + \begin{pmatrix} 1/2 \\ 0 \end{pmatrix} \\
193
T_3(\mathbf{x}) &= \frac{1}{2}\mathbf{x} + \begin{pmatrix} 1/4 \\ \sqrt{3}/4 \end{pmatrix}
194
\end{align}
195
196
\begin{pycode}
197
def sierpinski_chaos_game(n_points):
198
# Vertices of equilateral triangle
199
vertices = np.array([[0, 0], [1, 0], [0.5, np.sqrt(3)/2]])
200
201
# Start from random point
202
point = np.random.rand(2)
203
points = [point.copy()]
204
205
for _ in range(n_points):
206
vertex = vertices[np.random.randint(3)]
207
point = (point + vertex) / 2
208
points.append(point.copy())
209
210
return np.array(points)
211
212
def sierpinski_recursive(vertices, depth):
213
if depth == 0:
214
return [vertices]
215
216
v0, v1, v2 = vertices
217
m01 = (v0 + v1) / 2
218
m12 = (v1 + v2) / 2
219
m02 = (v0 + v2) / 2
220
221
triangles = []
222
triangles.extend(sierpinski_recursive([v0, m01, m02], depth - 1))
223
triangles.extend(sierpinski_recursive([m01, v1, m12], depth - 1))
224
triangles.extend(sierpinski_recursive([m02, m12, v2], depth - 1))
225
226
return triangles
227
228
fig, axes = plt.subplots(1, 3, figsize=(14, 4))
229
230
# Chaos game
231
ax = axes[0]
232
points = sierpinski_chaos_game(50000)
233
ax.scatter(points[:, 0], points[:, 1], s=0.1, c='darkgreen', alpha=0.5)
234
ax.set_xlabel('$x$')
235
ax.set_ylabel('$y$')
236
ax.set_title('Chaos Game (50,000 points)')
237
ax.set_aspect('equal')
238
239
# Recursive construction
240
ax = axes[1]
241
vertices = np.array([[0, 0], [1, 0], [0.5, np.sqrt(3)/2]])
242
triangles = sierpinski_recursive(vertices, 6)
243
for tri in triangles:
244
triangle = plt.Polygon(tri, fill=True, facecolor='darkgreen', edgecolor='none')
245
ax.add_patch(triangle)
246
ax.set_xlim(-0.1, 1.1)
247
ax.set_ylim(-0.1, 1.0)
248
ax.set_xlabel('$x$')
249
ax.set_ylabel('$y$')
250
ax.set_title('Recursive Construction (Depth 6)')
251
ax.set_aspect('equal')
252
253
# Different depths
254
ax = axes[2]
255
colors = plt.cm.Greens(np.linspace(0.3, 0.9, 5))
256
for i, depth in enumerate([1, 2, 3, 4, 5]):
257
n_triangles = 3**depth
258
ax.scatter(depth, n_triangles, c=[colors[i]], s=100)
259
ax.set_xlabel('Recursion Depth')
260
ax.set_ylabel('Number of Triangles')
261
ax.set_title('Scaling: $N = 3^d$')
262
ax.set_yscale('log')
263
ax.grid(True, alpha=0.3)
264
265
plt.tight_layout()
266
plt.savefig('sierpinski.pdf', dpi=150, bbox_inches='tight')
267
plt.close()
268
269
# Fractal dimension
270
sierpinski_dim = np.log(3) / np.log(2)
271
\end{pycode}
272
273
\begin{figure}[htbp]
274
\centering
275
\includegraphics[width=0.95\textwidth]{sierpinski.pdf}
276
\caption{Sierpinski triangle: chaos game method (left), recursive IFS (center), scaling behavior (right).}
277
\end{figure}
278
279
The fractal dimension is $D = \frac{\log 3}{\log 2} \approx \py{f'{sierpinski_dim:.4f}'}$.
280
281
\chapter{Koch Snowflake}
282
283
\section{Construction}
284
Each line segment is replaced by four segments of length $1/3$:
285
286
\begin{pycode}
287
def koch_curve(p1, p2, depth):
288
if depth == 0:
289
return [p1, p2]
290
291
# Divide segment into thirds
292
d = (p2 - p1) / 3
293
a = p1
294
b = p1 + d
295
c = p1 + 2*d
296
e = p2
297
298
# Compute apex of equilateral triangle
299
angle = np.pi / 3
300
rotation = np.array([[np.cos(angle), -np.sin(angle)],
301
[np.sin(angle), np.cos(angle)]])
302
apex = b + rotation @ d
303
304
# Recurse
305
points = []
306
for seg in [(a, b), (b, apex), (apex, c), (c, e)]:
307
pts = koch_curve(seg[0], seg[1], depth - 1)
308
points.extend(pts[:-1])
309
points.append(e)
310
311
return points
312
313
def koch_snowflake(depth):
314
# Start with equilateral triangle
315
p1 = np.array([0, 0])
316
p2 = np.array([1, 0])
317
p3 = np.array([0.5, np.sqrt(3)/2])
318
319
# Generate each side
320
side1 = koch_curve(p1, p2, depth)
321
side2 = koch_curve(p2, p3, depth)
322
side3 = koch_curve(p3, p1, depth)
323
324
return side1[:-1] + side2[:-1] + side3
325
326
fig, axes = plt.subplots(2, 3, figsize=(14, 8))
327
328
# Different iteration depths
329
for i, depth in enumerate([0, 1, 2, 3, 4, 5]):
330
ax = axes[i // 3, i % 3]
331
points = koch_snowflake(depth)
332
points = np.array(points)
333
ax.plot(points[:, 0], points[:, 1], 'b-', linewidth=0.5)
334
ax.fill(points[:, 0], points[:, 1], alpha=0.3, color='blue')
335
ax.set_xlabel('$x$')
336
ax.set_ylabel('$y$')
337
ax.set_title(f'Iteration {depth}')
338
ax.set_aspect('equal')
339
ax.set_xlim(-0.1, 1.1)
340
ax.set_ylim(-0.2, 1.0)
341
342
plt.tight_layout()
343
plt.savefig('koch_snowflake.pdf', dpi=150, bbox_inches='tight')
344
plt.close()
345
346
# Compute perimeter and area
347
koch_dim = np.log(4) / np.log(3)
348
\end{pycode}
349
350
\begin{figure}[htbp]
351
\centering
352
\includegraphics[width=0.95\textwidth]{koch_snowflake.pdf}
353
\caption{Koch snowflake construction: iterations 0-5. The perimeter grows infinitely while the area converges.}
354
\end{figure}
355
356
\section{Properties}
357
\begin{itemize}
358
\item Perimeter: $L_n = L_0 \cdot (4/3)^n \to \infty$
359
\item Area: $A_n \to \frac{2\sqrt{3}}{5} s^2$ (finite)
360
\item Fractal dimension: $D = \frac{\log 4}{\log 3} \approx \py{f'{koch_dim:.4f}'}$
361
\end{itemize}
362
363
\chapter{Box-Counting Dimension}
364
365
\begin{pycode}
366
def box_counting(points, box_sizes):
367
counts = []
368
for size in box_sizes:
369
# Count occupied boxes
370
boxes = set()
371
for p in points:
372
box = (int(p[0] / size), int(p[1] / size))
373
boxes.add(box)
374
counts.append(len(boxes))
375
return np.array(counts)
376
377
# Generate Sierpinski points
378
sierp_points = sierpinski_chaos_game(100000)
379
380
# Box sizes
381
box_sizes = np.logspace(-3, 0, 20)
382
counts = box_counting(sierp_points, box_sizes)
383
384
# Fit for dimension
385
log_sizes = np.log(1 / box_sizes)
386
log_counts = np.log(counts)
387
coeffs = np.polyfit(log_sizes, log_counts, 1)
388
computed_dim = coeffs[0]
389
390
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
391
392
ax = axes[0]
393
ax.loglog(1/box_sizes, counts, 'bo-')
394
ax.set_xlabel('$1/\\epsilon$')
395
ax.set_ylabel('$N(\\epsilon)$')
396
ax.set_title('Box-Counting for Sierpinski Triangle')
397
ax.grid(True, alpha=0.3)
398
399
ax = axes[1]
400
ax.plot(log_sizes, log_counts, 'bo', label='Data')
401
ax.plot(log_sizes, np.polyval(coeffs, log_sizes), 'r-', label=f'Fit: $D = {computed_dim:.3f}$')
402
ax.set_xlabel('$\\log(1/\\epsilon)$')
403
ax.set_ylabel('$\\log N(\\epsilon)$')
404
ax.set_title('Linear Fit for Fractal Dimension')
405
ax.legend()
406
ax.grid(True, alpha=0.3)
407
408
plt.tight_layout()
409
plt.savefig('box_counting.pdf', dpi=150, bbox_inches='tight')
410
plt.close()
411
\end{pycode}
412
413
\begin{figure}[htbp]
414
\centering
415
\includegraphics[width=0.95\textwidth]{box_counting.pdf}
416
\caption{Box-counting dimension estimation: log-log plot (left) and linear fit (right).}
417
\end{figure}
418
419
Computed dimension: $D = \py{f'{computed_dim:.4f}'}$ (theoretical: $\py{f'{sierpinski_dim:.4f}'}$)
420
421
\chapter{Summary of Results}
422
423
\begin{pycode}
424
fractal_data = [
425
('Mandelbrot boundary', 2.0, 'Coastline'),
426
('Julia set ($c \\in M$)', '$\\approx 2$', 'Connected'),
427
('Sierpinski triangle', f'{sierpinski_dim:.4f}', 'Self-similar'),
428
('Koch snowflake', f'{koch_dim:.4f}', 'Infinite perimeter'),
429
('Cantor set', f'{np.log(2)/np.log(3):.4f}', 'Disconnected'),
430
]
431
\end{pycode}
432
433
\begin{table}[htbp]
434
\centering
435
\caption{Fractal dimensions of common fractals}
436
\begin{tabular}{@{}lcc@{}}
437
\toprule
438
Fractal & Dimension & Property \\
439
\midrule
440
\py{fractal_data[0][0]} & \py{fractal_data[0][1]} & \py{fractal_data[0][2]} \\
441
\py{fractal_data[1][0]} & \py{fractal_data[1][1]} & \py{fractal_data[1][2]} \\
442
\py{fractal_data[2][0]} & \py{fractal_data[2][1]} & \py{fractal_data[2][2]} \\
443
\py{fractal_data[3][0]} & \py{fractal_data[3][1]} & \py{fractal_data[3][2]} \\
444
\py{fractal_data[4][0]} & \py{fractal_data[4][1]} & \py{fractal_data[4][2]} \\
445
\bottomrule
446
\end{tabular}
447
\end{table}
448
449
\chapter{Conclusions}
450
451
\begin{enumerate}
452
\item The Mandelbrot set contains all $c$ for which Julia sets are connected
453
\item Julia sets exhibit diverse topologies depending on parameter $c$
454
\item IFS methods generate exact self-similar fractals
455
\item Box-counting provides numerical estimates of fractal dimension
456
\item Fractals demonstrate that infinite complexity can arise from simple rules
457
\end{enumerate}
458
459
\end{document}
460
461