ProgrammingPython

Programming fractal art using tensorflow

Fractals are undeniably beautiful. The self-similar, highly complex patterns reflect elements of the natural world, yet appear geometric and almost alien in their organisation. I decided generate some using python and tensorflow as a short mini-project. There are a tonne of resources online for generating fractals in any programming language – I made particular use of this GitHub repo by hzy46this blog over on IBM and this reddit post for colouring – so this a relatively quick exercise, but rewarding! You can find all the code I wrote for this exercise over on GitHub – check out the jupyter notebooks there for examples and explanations.

The two most famous fractals are the Mandelbrot set and the Julia set. My algorithm for producing either set first requires defining a complex matrix (the “grid”) on which to operate. Then we can use tensorflow to perform the Mandelbrot/Julia algorithm. Tensorflow’s matrix algebra provides an easy method for fast computation of the fractals with built-in hardware acceleration, a useful feature for our fractals, which repeat the same operation many times on every cell in the matrix. With our grid, plus “threshold” and “iterations” parameters, the Mandelbrot algorithm is:

# Define the tensorflow variables
c = tf.constant(grid.astype(np.complex64))
z = tf.Variable(c)
n = tf.Variable(tf.zeros_like(c, tf.float32))
# Start the tensorflow session
with tf.Session():
    tf.global_variables_initializer().run()
    # Define the main mandelbrot algorithm - either take the square plus x, or keep z
    z_out = tf.where(tf.abs(z) < threshold, z ** 2 + c, z)
    not_diverged = tf.abs(z_out) < threshold
    # Create a group of tensorflow operations
    step = tf.group(
        z.assign(z_out),
        n.assign_add(tf.cast(not_diverged, tf.float32)))
    # Run the operations for a set number of steps
    for i in range(iterations):
        step.run()

We now have filled our grid with complex values from the algorithm. For colouring the cells according to their complex value, we define a few options:

  • If the absolute value of the cell is below some minimum threshold, we display the background colour (black by default).
  • Otherwise, a function maps complex values to (logarithmic) real values so that we can colour them.
  • Depending on the final value, we then have two sets of ratios of RBG colour – one for the overall background, and one for the highlights on the edges of the complex fractal structure.

With this all together, we use the following code for each cell, where the threshold and colour ratios (r1, r2, r3, b1, b2 and b3) are globally defined.

def colour(z, i):
    """
    Gets the colour of a z and step value.

    :param z: the z value from the mandelbrot set
    :param i: the step value

    :rtype: list
    :return: list containing the RGB colours
    """
    if abs(z) < threshold:
        return 0, 0, 0
    v = np.log2(i + threshold - np.log2(np.log2(abs(z)))) / threshold
    if v < 1.0:
        return v ** b1, v ** b2, v ** b3 # coloured tones
    else:
        v = max(0, 2 - v)
        return v ** r1, v ** r2, v ** r3  # sepia tones

Now we can experiment with different colouring for some better-looking fractals.

The classic Mandelbrot set.

Or explore different areas of the fractal.

And now theJulia set.

The Julia set has a periodicity. For some \(\theta\),  we have

\(c=-(a-r cos(\theta)) – (b + r sin(\theta))i\)

where \(a\) and \(b\) are constants defining the initial shape and \(r\) defines the amount of variation over a single period. Using this periodic formula on \(\theta\), and therefore on \(c\), we can produce an animation of the Julia set.

Cool, right? I’d recommend writing your own fractal generator and trying to generate some fractal art for yourself.

Leave a Reply

Your email address will not be published. Required fields are marked *