Visualizing Cellular Automatons

Visualizations can be really great. They can quickly demonstrate a concept which we can understand much faster than trying to grasp it from words. The majority of people probably thinks visually at lot of the time. This is not the case for all problems or people, of course, as this quote from Lagrange shows:

The reader will find no figures in this work. The methods which I set forth do not require either constructions or geometrical or mechanical reasonings: but only algebraic operations, subject to a regular and uniform rule of procedure.

Preface to Mécanique Analytique. (Source)

In physics, there are a lot of things that one can visualize in experimental physics. In theoretical physics, one visualizes different things (e.g. Feynman diagrams). Numerical simulations very often lend themselves to visualizations. The class of systems I want to introduce here are systems that are defined on a square grid. Representing the whole state of the system in a single picture allows to create movies from that. Let me demonstrate with some examples and then source code.

Example Systems

Forest Fire (Percolation)

The forest fire simulation is a simple cellular automaton. There are three states:

  1. No tree,
  2. Tree,
  3. Burning tree.

The rules are quite simple:

  • If a tree is burning, it will be no tree in the next time step on that field.
  • If there is a tree and one of its four neighbors burns, the tree will start burning in the next time step.

Different starting configurations of the forest can create vastly different spreading of the fire. Here I use a random population where the chance for each grid point to be populated by a tree is a certain percentage.

In the following video you can see the dramatic difference between low, high, and critical population density:

The critical density is here shown again with actual forest fire colors:

Ising Model

The Ising model is a spin interaction system where next neighboring spins interact via the Hamiltonian

$$H = - J \sum_{\langle i, j \rangle} s_i s_j \,.$$

The summation is meant over the next neighbors only. Depending on the sign of the interaction strength $J$, the system is a ferromagnet or an antiferromagnet. I have chosen a positive coupling such that it is a ferromagnet.

At a small temperature, the system will cool down from the random (hot) state. One can see how ferromagnetic regions form slowly:

At even lower temperature, the thermalization takes longer and the emerging structures will be slightly larger:

For medium temperature, the structures will be finer and there are also more fluctuations:

And finally, at a high temperature above the spontaneous symmetry breaking point, there is magnetization and all the spins just point into random directions:

How to Implement It

I have used C++ to generate these videos above but there are lots of different options. Using a 2D matrix library that supports writing matrices to image files I have created a bunch of images. Then, using ffmpeg, I have created a video from those images.


A library that I already had experience with is OpenCV (Open Computer Vision). There are many other image libraries out there, this is just included in the Ubuntu and Fedora repositories and is not that hard to use.

First include the appropriate header:

#include <opencv2/opencv.hpp>

Then create a matrix of the size you want. The height and width are variables that I have declared. Using CV_8UC1 means that you want an 8U, an 8-Bit unsigned integer type, with C1, one channel. That gives you a grayscale image.

cv::Mat lattice(height, width, CV_8UC1);

Somewhere you need to do your simulation steps. Just use the lattice matrix/array to store your state. Then after each step you can store that to disk.

for (uint_fast32_t iteration = 1; iteration < 1000; ++iteration) {
    for (uint_fast32_t x = 0; x < width; ++x) {
        for (uint_fast32_t y = 0; y < height; ++y) {
            // TODO Compute the new value for the (x, y) position here.
            // This of course depends on your simulation.

  <Field>(x, y) = new_value;

    // Generate a filename which has enough leading zeros.
    std::ostringstream filename;
    filename << "lattice/" << std::setfill('0') << std::setw(6)
             << iteration << ".jpg";

    // Store the image.
    cv::imwrite(filename.str(), lattice);

Compilation with OpenCV needs a lot of flags. Luckily CMake can take care of that with just a few lines. If you are not using CMake but are writing a Makefile by hand or just write g++ every time you want to compile, you might want to check out CMake out. To compile the code, you will need a CMakeLists.txt file like this:

cmake_minimum_required(VERSION 3.0)
project(percurlation C CXX)
set(CMAKE_CXX_COMPILER "/usr/bin/g++")
add_executable(ising ising.cpp)
target_link_libraries(ising ${OpenCV_LIBS})

Then compile and run your code:

$ cmake . -DCMAKE_BUILD_TYPE=Release
$ make
$ mkdir lattice
$ ./ising

That cmake step only needs to be done once, just run make whenever you want to compile the changed code. Creating the directory is also needed just once. If you do not do that, no images get stored to disk!


If you want to use Python, there is the imsave function that lets you store a NumPy array as an image file on disk. Store one image per frame you want in the video, usually that is one step in your simulation. Then create the video with the command below.

Other Languages

You always have the option to directly create an image file on your own, the bitmap (BMP) format should be somewhat easy to program because it is just a header and then all the pixels.

Otherwise look for a library that lets you work with image files. Even if you do not use the image objects they have for your actual data, you can still copy your data and store it to disk.

If you find anything that is useful, please tell me and I'll start a list here.

Create the Video

The directory fill of images can then be converted into a video file using ffmpeg like this:

ffmpeg -start_number 0 -i lattice/%06d.jpg -vcodec libx264 ising-TEMPERATURE.mp4

There are a lot of encoding options (framerate, bitrate, codec, ...) you can tweak. Apparently the defaults are sensible enough.