Generating Fractals in Elixir

by Jeremy D. Frens on May 30, 2015
part of the Fractals in Elixir series


My last year teaching, I started on a Haskell program to generate fractals1 so that I had a non-trivial example of Haskell code for my students. And to try out parallelism. Later I copied the code over to a new repo for a ployglot collection of fractal-generating programs, with the intent of trying other (functional) programming languages. I eventually got some interesting results in Haskell, and one day I might blog about that solution. It looks like I got a ways into an Erlang solution but gave up on it four years ago.

I started on an Elixir solution last month.

The fractals

My goal has been to generate different types of “escape-time fractals” like the Mandelbrot set where you iterate over the complex number plane. So far I’ve implemented:

Options for the fractals and the images are fed into the Elixir program as JSON files. (I couldn’t find a good library for parsing YAML.)

Output

Output is to PPM files. PPM is a simple text format for 24-bit RGB images; it’s really easy to write and debug. I conver the PPM to GIF for easy viewing.

Image pipeline

I use an Elixir pipeline to generate the image from three components:

  • The fractal’s formula (next_function).
  • A color function (color_function) which maps the escape velocity of a complex number to a color.
  • A grid of complex numbers (generate_grid and build_complex), one for each pixel in the image.
def generate_image(options) do
  color_func = color_function(options)
  options
  |> generate_grid
  |> map(&build_complex/1)
  |> map(fn   grid_point ->
          { grid_point, next_function(grid_point, options) }
        end)
  |> map(fn { grid_point, next_func } ->
          generate_pixel(grid_point, next_func, color_func)
        end)
end

Functionally, this is a fine solution; it’s not really a good Elixir solution. Both “color function” and “next function” should actually be servers, running in their own processes. Generating the grid could also be its own server. This would open up some concurrency options.

Iterating on a pixel

def fractal_iterate(next, grid_point) do
  { grid_point, 1 }
  |> iterate(fn { z, i } -> { next.(z), i+1 } end)
  |> take_while(fn { z, _ } ->
                  magnitude_squared(z) < @magnitude_cutoff_squared
                end)
  |> take(256)
  |> take(-1)
  |> Enum.to_list
  |> List.last
end

All of the functions in the pipeline are from Stream, not so much for their potential concurrency, but for their control.

  • iterate iterates with the given function, feeding the output of one iteration as input for the next iteration.
  • The take_while finds points that escape.
  • take(256) stops the iteration if the point does not escape.
  • The rest of the pipeline grabs the last element from the stream.

The escape velocity is the second number in the tuple that’s passed along.

I’ve spent some time in this function and the complex calculations, trying to get it more efficient; but this appears to be a losing battle in Elixir. The Erlang virtual machine, the complex number structure, and the stream overhead make it very difficult to keep the floating-point processor as busy as possible.

Concurrency will get me bigger gains with Elixir, although I’m somewhat skeptical that this pipeline would benefit.

Next steps

Concurrency. Maybe OTP.

Footnotes

  1. I still have problems with the Newton fractal in my Elixir solution.

elixir fractals