Variations of the Mandelbrot set

This short article by Dan Polansky explores variations of the Mandelbrot set. It asks: what happens if we change the exponent of the iterative function (away from 2) or if we let the orbits start from a fixed value different from 0?

The results are interesting, some of them bizarre. That a simple formula iterated could result in the curves of the Mandelbrot set is curious enough; the kind of deformations that result from changing these parameters perhaps even more so.

Let us recall: the function being iterated in the standard Mandelbrot set is f(x) = x ** 2 + c, where the iteration process starts at zero. We can instead take e.g. f(x) = x ** 2.1 + c, starting at 0.5 + 0.5i instead of 0.

Exercise: install PyPy and other requirements, copy the Python code below to a local file, and try your own hand in discovering interesting plots. Try different parameter values or even different iteration formulas.

On a subjective note, some people to whom I have shown the images asked whether it was something like Rorschach test. Indeed, one could perhaps produce a series of neo-Rorschach test using this technique.

Gallery edit

A gallery follows.

Normal: exponent 2, start from 0:
 

Exponent 0.5:
 

Exponent 1.0:
 

Exponent 1.5:
 

Exponent 1.9:
 

Exponent 2.1:
 

Exponent 3:
 

Exponent 4:
 

Exponent 2, starting from 0.5:
 

Exponent 2, starting from 0.5i:
 

Exponent 2, starting from 0.5 + 0.5i:
 

Exponent 2.1, starting from 0.5 + 0.5i:
 

Exponent 2, starting from 0.3 + 0.3i:
 

Exponent 2, starting from 0.7 + 0.7i:
 

Exponent 2, starting from 0.9 + 0.9i:
 

Part of the above images are covered in the animation File:Mandelbrot Set Animation 1280x720.gif.

Plotting code edit

The images above were plotted using the following Python code, run using PyPy JIT compiler rather than the standard CPython. For this code, the speed difference from using PyPy is huge; the speed with PyPy in plotting a 2000 x 2000 pixel image is pleasant, unlike with CPython. We make use of pure-Python PNG-creation library purepng. To be fair to CPython, the plotting could turn rather tolerable if it would use vectorization using numpy. The code is very straightforward, like code one might find written in Basic in a popular computing magazine decades ago.

import png # purepng
import math, cmath
import sys

# Adapted from https://github.com/cbertram/mandelbrot/blob/master/mandelbrot.py, which is copyright Christian Betram under MIT license.
# Uses purepng pure-Python library ==> runs in PyPy, astronomically faster than in CPython.

preset = 1
if preset == 1:
  name = "MandelbrotSetVariant.png"
  width, height = 2000, 2000
  centerX, xSize = -0.5, 3
  centerY, ySize = 0, 3
  maxIterCount = 256 * 2
  z0 = 0
  
baseX = centerX - xSize / 2.0
baseY = centerY + ySize / 2.0

def setUpColors1(maxIterCount):
    # Use gray and a single hue
    red, green, blue = [0] * maxIterCount, [0] * maxIterCount, [0] * maxIterCount
    for iterCount in xrange(maxIterCount):
        a = (maxIterCount - iterCount) % 32
        b = (maxIterCount - iterCount) % 64
        if a == b:#b < a
            a = 32 - a
        baseColor = a * 7 + 31
        redFactor = a / 32.0
        greenFactor = (0.5 + redFactor * 0.5)
        red[iterCount] = int(baseColor * redFactor)
        green[iterCount] = int(baseColor * greenFactor)
        blue[iterCount] = baseColor
    return red, green, blue

def setUpColors2(maxIterCount):
    # Cycle various hues to better see iteration count escape boundaries
    red, green, blue = [0] * maxIterCount, [0] * maxIterCount, [0] * maxIterCount
    for iterCount in xrange(maxIterCount):
        m = iterCount
        baseColor = (maxIterCount - m) % 256
        if m % 5 == 0:
            red[iterCount] = int(baseColor * 0.75)
            green[iterCount] = baseColor
            blue[iterCount] = baseColor
        elif m % 5 == 1:
            red[iterCount] = int(baseColor * 0.75)
            green[iterCount] = int(baseColor * 0.75)
            blue[iterCount] = baseColor
        elif m % 5 == 2:
            red[iterCount] = baseColor
            green[iterCount] = baseColor
            blue[iterCount] = int(baseColor * 0.75)
        elif m % 5 == 3:
            red[iterCount] = baseColor
            green[iterCount] = int(baseColor * 0.75)
            blue[iterCount] = int(baseColor * 0.75)
        elif m % 5 == 4:
            red[iterCount] = int(baseColor * 0.75)
            green[iterCount] = baseColor
            blue[iterCount] = int(baseColor * 0.75)
    return red, green, blue

def setUpColors3(maxIterCount):
    # Use a single hue and very slowly decreasing color brightness
    red, green, blue = [0] * maxIterCount, [0] * maxIterCount, [0] * maxIterCount
    for iterCount in xrange(maxIterCount):
        a = (maxIterCount - iterCount) % 256
        b = (maxIterCount - iterCount) % 512
        if a == b:
            a = 255 - a
        baseColor = a
        redFactor = a / 256.0
        greenFactor = (0.5 + redFactor * 0.5)
        red[iterCount] = int(baseColor * redFactor)
        green[iterCount] = int(baseColor * greenFactor)
        blue[iterCount] = baseColor
    return red, green, blue

def iterate(z0, c, maxIterCount):
    z = z0
    iterCount = 0
    while iterCount < maxIterCount:
        z = z ** 2 + c
        #z = z ** 1 + c
        #z = z ** 2.1 + c
        if z.real**2 + z.imag**2 > 4:
            return iterCount
        iterCount += 1
    return None

def main():
    red, green, blue = setUpColors1(maxIterCount)
    image = []
    for h in xrange(height):
        if h % 10 == 0:
            sys.stdout.write(str(100 * h / height) + "% complete \r")
            sys.stdout.flush()
        row = [0] * width * 3
        for w in xrange(width):
            x = baseX + w * float(xSize) / width
            y = baseY - h * float(ySize) / height
            m = iterate(z0, complex(x, y), maxIterCount)
            if m is None:
                row[3 * w] = 0
                row[3 * w + 1] = 0
                row[3 * w + 2] = 0
            else:
                row[3 * w] = red[m]
                row[3 * w + 1] = green[m]
                row[3 * w + 2] = blue[m]
        image.append(row)
    print ""
    with open(name, "wb") as file1:
        w = png.Writer(width, height, greyscale=False)
        w.write(file1, image)

if __name__ == "__main__":
    main()

Plotting prerequisites edit

One option for installing prerequisites is this:

1) Install PyPy

2) Install pip for PyPy: pypy -m ensurepip

3) Use pip to install purepng: pypy -m pip install purepng

Links:

Further reading edit