At the forefront of Artificial Intelligence
  Home Articles Reviews Interviews JDK Glossary Features Discussion Search
Home » Articles » Artificial Life » Applications/Code

Cellular Automata with the Generation5 JDK

Cellular automata can be a little tricky to program at times, especially when features such as torodial geometry, collision detection and double-buffering are required. Thankfully, the Generation5 JDK takes care of all of the underlying technicalities and provides a nice interface to very quickly and concisely create CA examples and experiments.

Introduction

Below is an example of a dictyostelium slime mold simulation implemented as a cellular automata and iterated over 9000 timesteps.

Believe it or not, this entire system is implemented in less than 100 lines of code. In this article we will look at how this was implemented step-by-step. Firstly, a look at exactly what a dictyostelium simulation actually is.

Dictyostelium Slime Mold

Dictyostelium slime mold (dicty, for short) cells follow extremely simple rules whereby under certain conditions they give off a pheromone. This pheromone attracts other surrounding dicty cells, but evaporates quite quickly. Therefore, we can say that each dicty cell follows two simple rules:
  • Move in the direction that has the highest pheromone concentration.
  • If no pheromone, move randomly.
All the while, each dicty cell is giving off a pheromone which evaporates each timestep.

CellularAutomata and CellularAutomataLayered

Within the Generation5 JDK, there are two principle types of cellular automata. The first type of cellular automata is contained totally within the world; the world in effect is the cellular automata. The perfect example of this is Conway's Life, each cell is fixed spatially and has its state evaluated every time-step. Other examples of this are Wolfram's one-dimensional CA, Langton's self-replicating loop or a Spatial IPD. CA systems like this are derived from CellularAutomata.

Sometimes though, the cells themselves need to be independent of the world they are contained within. They may modify the world itself, but will have their own internal states and rules. Termites, Langton's Ant and our dicty simulation are all examples of this sort of CA. These systems should derive from CellularAutomataLayered (which itself is derived from CellularAutomata).

So, without further ado, let us look at the code:

Dicty Code

public DictyosteliumCA() {
    this(0,0);
}
    
public DictyosteliumCA(int size_x, int size_y) {
    super(size_x, size_y, DOUBLE_BUFFERING);

    Gradient gradient = new Gradient();
    gradient.addPoint(Color.black);
    gradient.addPoint(new Color(0,128,0));
    gradient.addPoint(Color.green);
    gradient.addPoint(Color.yellow);
    gradient.addPoint(Color.white);
    gradient.createGradient();
        
    setWorldColors(gradient.getGradient());
}
This constructors for the cellular automata classes have to support default constructors with no parameters, as well as a constructor with size information. The first thing to do (due to Java language restrictions more than anything), is called the super constructor with the size information and a flag saying that we want to double buffer the world. Double buffering allows us to simulate updating the world synchronously.

Next, we want to set up the pheromone colour gradient for visualization purposes. In the dicty code, we use the world states to indicate pheromone levels at that position in the world. The pheromone levels can be between 0 and 255. Similarly, the Gradient (org.generation5.util) utility class generates a gradient with 256 by default, making initialization of the world state colours very straightforward.

Next, every CellularAutomata(Layered) class must implement two abstract functions, init and doStep. These methods are specified by the Steppable interface.

public void init() {
    clearWorld();
    removeAll();
        
    int num = (int)(caWorld_x * caWorld_y * 0.05);
    for (int i = 0; i < num; i++) {
        int px = random.nextInt(caWorld_x);
        int py = random.nextInt(caWorld_y);
        addAutomaton(px, py, 1);
    }
        
    setCollisionDetection(true);
        
    flipBuffer();
}
init should initialize the CA class. It is important to note that classes implementing the Steppable interface also have a reset method, but within CellularAutomata-derived classes this defaults to calling the init method. This is why clearWorld and removeAll are called, since the class may already have been initialized. clearWorld clears the world (pheromone levels), whereas removeAll deletes all CA agents from the class.

Next, we populate 5% of the world with CAAgents, initialized to random positions within the world. Note that random is an instance of java.util.Random. Collision detection is then turned on. Collision detection ensures that no two CAs occupy the same space within the world—even within collision detection turned on, checks are not made when agents are added to the world.

Finally, the buffer is flipped. It is very important to remember to flip the buffer at the end of both the init and doStep methods in double buffered classes.

public void doStep() {
    // Evaporate the pheromone levels
    for (int i=0; i<getSizeX(); i++) {
        for (int j=0; j<getSizeY(); j++) {
            setWorldAtRelative(i, j, -1);
            int avg = 0;
            for (int x=i-1; x<i+2; x++) {
                for (int y=j-1; y<j+2; y++) {
                    avg += getWorldAt(x,y);
                }
            }
                
            setWorldAt(i, j, avg / 9);
        }
    }
This is where all the pheromone levels are averaged (simulating evaporation). Note the difference between setWorldAt and setWorldAtRelative; the former takes an absolute value, the latter a relative number.

Now, within CellularAutomataLayered-derived class, you have to update every CAAgent. This is what we do next.

    // For each CA, find the strong pheromone level
    for (int i=0; i<getNumCAs(); i++) {
        CAAgent ca = getCA(i);
        setWorldAtRelative(ca.getX(), ca.getY(), 8);

        int dx = random.nextInt(3) - 1;
        int dy = random.nextInt(3) - 1;
        int world = 0;
        int highest = 16;
        int cx = ca.getX(), cy = ca.getY();
        for (int x=-1; x<2; x++) {
            for (int y=-1; y<2; y++) {
                if (x == 0 && y == 0) continue;
                if (isCellFree(cx+x, cy+y) == true) {
                    world = getWorldAt(cx+x, cy+y);
                    if (world > highest ||
                      ((world == highest && Math.random()<0.5))) {
                        highest = world;
                        dx = x;
                        dy = y;
                    }
                }
            }
        }
            
        moveCARelative(i, dx, dy);
    }
        
    flipBuffer();
}
The code looks fairly complicated, since there are a number of embedded loops. Nevertheless, it is fairly simple if broken down: Iterate through each CA agent, deposit pheromone, pick an initial random direction to move in, search the surrounding eight cells for the highest pheromone level and move to it beforing flipping the buffer.

Note that highest is set to '16' to ensure that isolated cells don't oscillates back and forth between their current and previous positions. Also note the use of isCellFree to check for collisions. Note that attempting to move a CAAgent into an occupied space wouldn't be allowed, but checking the space using isCellFree is a little more efficient.

So there we go, a dicty cell simulation! The next step is actually running the CA within something. CellularAutomata provides the iterateCA method that runs the world, taking snapshots at regular intervals (the image at the top was the result of this). Running the CA within a VisStepApplet-derived applet is also an option, although this is best covered in another article.

Conclusion

This has been a fairly quick introduction to CellularAutomata(Layered)-derived classes. A lot goes on underneath "the hood", especially for a simulation such as dicty: torodial geometry is enforced, collision detection is in place, the world is double-buffered and rendering is taken care of. Using the JDK allows you to concentrate on the problem at hand, while quickly visualizing the results.

Submitted: 19/08/2004

Article content copyright © James Matthews, 2004.
 Article Toolbar
Print
BibTeX entry

Search

Latest News
- Generation5 10-year Anniversary (03/09/2008)
- New Generation5 Design! (09/04/2007)
- Happy New Year 2007 (02/01/2007)
- Where has Generation5 Gone?! (04/11/2005)
- NeuroEvolving Robotic Operatives (NERO) (25/06/2005)

What's New?
- Back-propagation using the Generation5 JDK (07/04/2008)
- Hough Transforms (02/01/2008)
- Kohonen-based Image Analysis using the Generation5 JDK (11/12/2007)
- Modelling Bacterium using the JDK (19/03/2007)
- Modelling Bacterium using the JDK (19/03/2007)


All content copyright © 1998-2007, Generation5 unless otherwise noted.
- Privacy Policy - Legal - Terms of Use -