| ||||||||||||||
| ||||||||||||||
|
||||||||||||||
Cellular Automata with the Generation5 JDKCellular 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.
IntroductionBelow is an example of a dictyostelium slime mold simulation implemented as a cellular automata and iterated over 9000 timesteps.
Dictyostelium Slime MoldDictyostelium 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:
CellularAutomata and CellularAutomataLayeredWithin 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 Codepublic 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. ConclusionThis 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.
|
|
|||||||||||||
All content copyright © 1998-2007, Generation5 unless otherwise noted.
- Privacy Policy - Legal - Terms of Use -