Mandelbrot Set Fractal Visualizer

by santiago_ramires in Craft > Digital Graphics

437 Views, 12 Favorites, 0 Comments

Mandelbrot Set Fractal Visualizer

mandelbrot_hd_22.png
mandelbrot_hd_23.png

Hello!

This project began four years ago when I was in high school. I was exploring interesting math topics and found myself captured by the fascinating world of complex numbers. Eventually, I discovered fractals.

Fractals are complex patterns that exhibit self-similarity across different and infinite scales. Some of the most famous examples include the Newton fractal, the Sierpinski triangle, the Koch curve, the Julia Set, and of course, the Mandelbrot Set.

What's even more amazing is that you can find similar structures all around us in nature—snowflakes, tree branches, lightning, seashells, and mineral formations all exhibit fractal-like patterns.

The purpose of this Instructable is to guide you through coding a visualizer that lets you explore the beauty and complexity hidden within the Mandelbrot set. I hope you enjoy it as much as I did!


Santiago Ramírez, second-year mechatronic engineering undergraduate.

Supplies

Basic considerations

Before we start, I would like to recommend previous programming knowledge in a basic-intermediate level, we will be using Python 3.12 (64-bit) or later versions, NumPy and Matplotlib libraries and Visual Studio Community 2022 as our IDE.

However if you follow the process carefully you will be able to understand and apply this instructable without any coding experience :)

Math knowledge is not necessary, but I also recommend to study some basic linear algebra and complex numbers before starting.


PC requirements

My PC has 32 GB of RAM, but 16 or even 8 could work also fine.



Feel free to use all the material available in this instructable.

Setting Up Python

Step 1.1: Install Python

  1. Download Python:
  2. Go to the official Python website: https://www.python.org/downloads/.
  3. Click on the download button for the latest version (Python 3.x).
  4. Run the Installer:
  5. Once the installer is downloaded, run it.
  6. Important: On the installation screen, check the box that says "Add Python to PATH" before clicking Install. This ensures you can use Python from the command line.
  7. Choose Installation Options:
  8. Select "Install Now" or choose "Customize Installation" if you want to select specific features or installation location. For most users, the default options are fine.
  9. Verify Python Installation:
  10. Open the Command Prompt (press Win + R, type cmd, and hit Enter).
  11. Type python --version and press Enter. You should see the Python version you installed (e.g., Python 3.11.x).

Step 1.2: Install Pip (Python's Package Manager)

  1. Pip Installation (usually comes with Python):
  2. In the Command Prompt, type pip --version to check if pip is installed. If you see the version number, pip is already installed.
  3. If pip is not installed, follow the instructions here: https://pip.pypa.io/en/stable/installation/.

Step 1.3: Install Matplotlib and NumPy

  1. Open Command Prompt:
  2. Press Win + R, type cmd, and press Enter to open the Command Prompt.
  3. Install Using Pip:
  4. In the Command Prompt, type the following command and press Enter: pip install matplotlib numpy
  5. This will automatically download and install the Matplotlib and NumPy libraries.
  6. Verify Installation:
  7. After installation, you can verify that Matplotlib and NumPy are installed by opening a Python shell
  8. Then, inside the Python shell, type: import matplotlib
  9. A line below type: import numpy
  10. If no error appears, libraries are successfully installed.

Step 1.4: Install Visual Studio Community Edition

  1. Download Visual Studio:
  2. Go to the official Visual Studio download page: https://visualstudio.microsoft.com/free-developer-offers/.
  3. Under Community Edition, click Free Download.
  4. Run the Visual Studio Installer:
  5. Open the installer once it's downloaded.
  6. Select Workloads:
  7. During installation, you'll be asked to select the workloads. To develop with Python, select the following workloads:
  8. Python Development (under Desktop & Mobile).
  9. Optionally, if you're planning on working with web or cloud development, you can choose additional workloads as needed.
  10. Install Visual Studio:
  11. Click Install and wait for the installation to complete.

Step 1.5: Set Up a Python Project in Visual Studio

  1. Open Visual Studio:
  2. Launch Visual Studio Community from the Start menu or desktop.
  3. Create a New Project:
  4. Go to File > New > Project.
  5. In the project templates, search for Python.
  6. Select Python Application and click Next.
  7. Configure the Project:
  8. Give your project a name and specify the location where you want to save it.
  9. Click Create.
  10. Write and Run Python Code:
  11. In the main editor, you can now start writing your Python code.
  12. You can run your code by clicking the green Start button or pressing F5.


**Double check that the Python environment in VS community includes the libraries.


Understanting the Algorithm

untitled (1).png

1. Complex Numbers

  1. The Mandelbrot set exists in the complex plane, where numbers are of the form z = a + bi, with a and b being real numbers, and i is the imaginary unit (square root of −1).
  2. Each point in the complex plane corresponds to a complex number, with the real part a on the x-axis and the imaginary part b on the y-axis.

2. The Mandelbrot Function

The recursive equation behind the Mandelbrot set is an iterative process:

z₀ = c

zₙ₊₁ = z ₙ²z + c

where:

  1. zₙ​ is a complex number that evolves with each iteration.
  2. c is a complex constant that represents the point being tested in the complex plane.
  3. z₀​, the starting point for the iteration, is always 0.

3. Escape Velocity Criteria

The Mandelbrot set consists of all points ccc for which the sequence z₀, z₁, z₂… remains bounded as the iterations continue. Specifically, the sequence must never "escape" to infinity, no matter how many iterations you perform. If |zₙ| (the magnitude or absolute value of zₙ​) exceeds 2 at any point during the iterations, the point c is considered to have "escaped" and is not part of the Mandelbrot set.

4. Visualization

  1. Inside the Set: Points ccc where the sequence remains bounded are inside the Mandelbrot set, usually colored black in visualizations.
  2. Outside the Set: Points that escape are colored based on how quickly they escape. The faster they escape (the fewer iterations it takes), the brighter the color used in visualization.

5. Fractal Properties

  1. The Mandelbrot set has a boundary that exhibits fractal geometry: it is self-similar and infinitely complex. Zooming into any region of the boundary reveals intricate, repeating patterns that never fully resolve, no matter how far you zoom in.
  2. This boundary is also where the chaos happens, as it contains points whose behavior under iteration is extremely sensitive to small changes in c.

6. Iterations

  1. In practice, we can’t run infinite iterations to decide whether a point is in the Mandelbrot set or not, because we may cause an overflow in our program, so we use a fixed number of iterations (e.g., 100 or 1000).
  2. The number of iterations it takes for a point to escape is often used to assign colors in visualizations of the Mandelbrot set. This gives the iconic multicolored fractal images, where points inside the set are typically black and points outside the set are colored according to their escape time.

7. Connectedness

  1. The Mandelbrot set is a connected set, meaning that all the points in the set form a single, unbroken region. Despite its complex boundary, there is no point where the set is completely disconnected or broken apart.

Building the Code

1. Importing Libraries:

import numpy as np

import matplotlib as mpl

import matplotlib.pyplot as plt

import matplotlib.colors as clr

  1. numpy (np): Used for numerical operations and array manipulations. Essential for creating grids of complex numbers and performing mathematical operations on them.
  2. matplotlib (mpl): A comprehensive library for creating static, animated, and interactive visualizations in Python.


2. Defining Parameters:

max_iter = 100

resolution = 1000

xmin, xmax, ymin, ymax = (-2, 2, -2, -2)

  1. max_iter: The maximum number of iterations used for checking if a point in the complex plane escapes the Mandelbrot set.
  2. resolution: The number of points along each axis in the grid (1000x1000 grid).
  3. xmin, xmax, ymin, ymax: These values define the bounds of the region in the complex plane that will be visualized. This small window is a zoomed-in portion of the Mandelbrot set.


3. Defining the Mandelbrot Function:

def f(z,c):

return z*z + c

  1. This is the iterative function for generating the Mandelbrot set: zₙ₊₁ = z ₙ²z + c, where:
  2. z is the current complex number being iterated.
  3. c is the initial complex number corresponding to a point in the complex plane.


4. Creating the Grid of Complex Numbers:

x = np.linspace(xmin, xmax, num=resolution)

y = np.linspace(ymin, ymax, num=resolution)

xx, yy = np.meshgrid(x, y)

c = xx + 1j * yy


np.linspace(xmin, xmax, num=resolution): 1D array of resolution points linearly spaced between xmin and xmax for the real axis. Similarly for y.

np.meshgrid(x, y): 2D grid (or matrix) of points by pairing the x values with the y values to cover the entire rectangular region. xx contains the real parts and yy contains the imaginary parts.

c = xx + 1j * yy: 2D grid of complex numbers


5. Initializing Arrays:

iterations = np.zeros(c.shape)

colors = np.zeros(c.shape)

z = np.zeros(c.shape, dtype=complex)

mask = np.ones(c.shape, dtype=bool)

  1. iterations: Stores how many iterations each point took to escape (or whether it didn’t escape). Initially set to zero.
  2. colors: Used to assign colors to the plot based on the number of iterations. Initially all zeros.
  3. z: The array that holds the iterative values of z for each point in the complex plane. Initialized to zero (starting point of the Mandelbrot iteration).
  4. mask: A boolean array used to track which points are still being iterated (i.e., haven't escaped yet). Initialized to True (all points are in the set initially).


6. Iterating Through Points:

for i in range(max_iter):

z[mask] = f(z[mask], c[mask])

mask = np.abs(z) < 2


escaped = mask

iterations[escaped] = i - np.log2(np.abs(z[escaped]))

colors[escaped] = iterations[escaped]/max_iter

  1. for i in range(max_iter): Loops through each iteration (up to 100 iterations in this case).
  2. z[mask] = f(z[mask], c[mask]): Updates the values of z for points that haven’t escaped (determined by the mask).
  3. mask = np.abs(z) < 2: Updates the mask to True for points where the magnitude of z is still less than 2. These points are still potentially part of the Mandelbrot set.
  4. escaped = mask: Assigns the mask to a variable called escaped for further use.
  5. iterations[escaped] = i - np.log2(np.abs(z[escaped])): For points that have just escaped (where |z| first exceeds 2), records how many iterations it took for them to escape. The logarithmic correction is added to smooth the escape timing for better visual gradients.
  6. colors[escaped] = iterations[escaped] / max_iter: Normalizes the iteration count to a value between 0 and 1, which will later be used for coloring.


7. Creating a Custom Colormap:

custom_cmap = clr.LinearSegmentedColormap.from_list('custom',[(0,'antiquewhite'),(0.5,'black'),(0.7,'slategrey'),(1,'antiquewhite')],N=524)

This is extremely important to enhace the beauty of final result of the plot, a colormap determines the rgb values of each point in the set based in their escape velocity. There are several ways to create a custom color map, you can find more information at https://matplotlib.org/stable/users/explain/colors/colormap-manipulation.html


8. Plotting the Mandelbrot Set:

fig, ax = plt.subplots(figsize=(10, 10))

im = ax.imshow(colors, cmap=custom_cmap, extent=[xmin, xmax, ymin, ymax])

ax.axis('off')

fig, ax = plt.subplots(figsize=(10, 10)): Creates the final figure (fig) and axes (ax) for plotting. The figure size is set to 10x10 inches.

ax.imshow(colors, cmap=custom_cmap, extent=[xmin, xmax, ymin, ymax]): Plots the colors array as an image using the custom colormap. The extent parameter maps the grid to the specified complex plane window (from xmin to xmax, and ymin to ymax).

ax.axis('off'): Turns off the axis labels and ticks for a cleaner look. (Optional)


10. Displaying the Image:

plt.show()

Don't forget to plot the result!

Not a Step, But the Gallery

mandelbrot_hd_7.png
mandelbrot_hd_6.png
mandelbrot_hd_19.png
mandelbrot_hd_20.png
mandelbrot_hd_final1.png
mandelbrot_hd_8.png

Thanks for reading!

FULL HD Version

This final code is beyond the scope of the instructable because of its complexity, but its free to copypaste.

It takes a long time to render (more than 5 minutes), but it worth it.

**Avoid calculating a resolution bigger than 4500 and a number of iterations higher than 3000.

**I used this web visualizer to get cool coordinates since my code does not support zooming: https://mandel.gart.nz/


import numpy as np

import matplotlib as mpl

import matplotlib.pyplot as plt

import matplotlib.colors as clr


def compute_bounds(center_re, center_im, zoom_level, aspect_ratio=1.0):

base_width = 3.5

base_height = base_width / aspect_ratio


width = base_width / zoom_level

height = base_height / zoom_level


xmin = center_re - width / 2

xmax = center_re + width / 2

ymin = center_im - height / 2

ymax = center_im + height / 2


return xmin, xmax, ymax, ymin


def f(z, c):

return z*z + c


max_iter = 1500

resolution = 3000

subpixel_factor = 3

zoom_level = 397243


center_re = -1.768802322

center_im = -0.001724199


xmin, xmax, ymin, ymax = compute_bounds(center_re, center_im, zoom_level)


x = np.linspace(xmin,xmax,num=resolution)

y = np.linspace(ymin,ymax,num=resolution)

xx, yy = np.meshgrid(x,y)

c = xx+1j*yy


iterations = np.zeros(c.shape)

colors = np.zeros(c.shape)


z = np.zeros(c.shape, dtype=complex)

mask = np.ones(c.shape, dtype=bool)


for i in range(max_iter):

z[mask] = f(z[mask], c[mask])

mask = np.abs(z) < 2


escaped = mask

iterations[escaped] = i - np.log2(np.abs(z[escaped]))

colors[escaped] = iterations[escaped]/max_iter



top = mpl.colormaps['twilight'].resampled(128)

bottom = mpl.colormaps['twilight_r'].resampled(128)


newcolors = np.vstack((top(np.linspace(0, 1, 128)),

bottom(np.linspace(0, 1, 128))))

custom_cmap = clr.ListedColormap(newcolors, name='custom_cmap')


#custom_cmap = clr.LinearSegmentedColormap.from_list('custom',[(0,'antiquewhite'),(0.3,'slategrey'),(0.8,'black'),(1,'antiquewhite')],N=524)


fig, ax = plt.subplots(figsize=(10, 10))

im = ax.imshow(colors, cmap=custom_cmap, extent=[xmin, xmax, ymax, ymin])



ax.axis('off')


#cbar = plt.colorbar(im)

#cbar.set_ticks([])


plt.savefig('mandelbrot_hd_**.png', dpi=600, bbox_inches='tight')


plt.show()