Fun with Cellular Automata
August 29, 2009
I was working on a cellular automaton earlier this week. Not quite sure what to use it for yet; it looks potentially useful for clustering or sorting or classification. Not so easy to find a quick use for it in financial modelling though. The algorithm I implemented is a nonlinear one instead of the often-seen Game of Life. Given a choice I usually prefer introducing randomness into an agent’s decision making process.
It’s an interesting effect, done entirely through enforcing simple rules. The images below show the algorithm’s evolution through 100 generations with a lattice size of 50, selection range of 25, and a concentration of 66%. Small-seeming changes in these variables can result in dramatically different results which, of course, is the point of using emergent algorithms.
The C# code shown below borrows from Tech Treasure for image output code but is otherwise adapted from the algorithm text linked above. Though it’s implemented in C# there is nothing preventing an easy port to any other language except for the aforementioned image output code; the most complex data structure in use otherwise is an 2D array. If you find that I’ve left anything out or can tighten up somewhere then please let me know.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace CATogetherness
{
class MainClass
{
/// <summary>
/// Random number generator.
/// </summary>
static Random MyRand = new Random(System.Diagnostics.Process.GetCurrentProcess().Id);
/// <summary>
/// Cell states, static throughout the game.
/// </summary>
static string[] Q = new string[] { "+", "-" };
/// <summary>
/// Lattice size, SxS
/// </summary>
static int S = 50;
/// <summary>
/// Selection range, 1 <= N <= S
/// </summary>
static int N = 25;
/// <summary>
/// Concentration, 0 <= C <= 1. Probability that a location is occupied by a cell.
/// </summary>
static double C = 0.66;
/// <summary>
/// "Lossy move chance"; a method of breaking out of minima
/// </summary>
static int LMC = 1000;
/// <summary>
/// Number of generations for algorithm to run
/// </summary>
static int G = 100;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
// Initialization.
string[][] Lattice = new string[S][];
for (int i = 0; i < Lattice.Length; i++)
{
Lattice[i] = new string[S];
for (int j = 0; j < Lattice[i].Length; j++)
{
double pct = Math.Abs(GetRand()) % .99;
if (pct < C)
{
int MyIdx = RandomIndex(Q.Length);
Lattice[i][j] = Q[MyIdx];
}
else
{
Lattice[i][j] = String.Empty;
}
}
}
// Print initial state.
PrintLattice(Lattice, 0);
// Evolve lattice over G generations.
for (int Gen = 1; Gen <= G; Gen++)
{
// For S^2 substeps, select a cell at random which is not surrounded by 8 neighbors
// all in the same state.
for (int Z = 0; Z < (S * S); Z++)
{
int Row = RandomIndex(S);
int Col = RandomIndex(S);
string State = Lattice[Row][Col];
if (State.Equals(String.Empty))
continue;
if (NeighborsInSameState(ref Lattice, Row, Col))
continue;
int[] Cell = new int[2];
Cell[0] = Row;
Cell[1] = Col;
// Among the locations within N units of distance, select one at random.
int[] TgtCell = RandomCellWithinDistance(ref Lattice, Cell, N);
if (TgtCell == null)
continue; // Couldn't find one, so skip.
string TgtState = Lattice[TgtCell[0]][TgtCell[1]];
// Is this already in the same state? Is it already surrounded?
if (TgtState.Equals(State) || NeighborsInSameState(ref Lattice, TgtCell[0], TgtCell[1]))
continue;
// If there is no cell at this location then calculate the increase or decrease
// in the number of neighbors of the cell in that same state as that cell, were
// the current cell to be moved to that location. If gain >= 0 then swap cells.
// If gain == -1 then move was a probability of 1 in LMC.
//
// If there is already a cell at the target location then calculate the combined
// gain. If gain >= 0 then swap cells. If gain is -1 or -2 then perform the
// swap with a probability of 1 in LMC.
if (TgtState.Equals(String.Empty))
{
int NumCurr = CountOfNeighborsInSameState(ref Lattice, Row, Col, State);
int NumMoved = CountOfNeighborsInSameState(ref Lattice, TgtCell[0], TgtCell[1], State);
int Gain = (NumMoved - NumCurr);
if (Gain >= 0)
{
Lattice[Row][Col] = TgtState;
Lattice[TgtCell[0]][TgtCell[1]] = State;
}
else if (Gain == -1)
{
double pct = Math.Abs(GetRand()) % .99;
if (pct <= ((double)1 / (double)LMC))
{
Lattice[Row][Col] = TgtState;
Lattice[TgtCell[0]][TgtCell[1]] = State;
}
}
}
else
{
int NumCurrA = CountOfNeighborsInSameState(ref Lattice, Row, Col, State);
int NumCurrB = CountOfNeighborsInSameState(ref Lattice, TgtCell[0], TgtCell[1], TgtState);
int NumMovedA = CountOfNeighborsInSameState(ref Lattice, TgtCell[0], TgtCell[1], State);
int NumMovedB = CountOfNeighborsInSameState(ref Lattice, Row, Col, TgtState);
int Gain = ((NumMovedA + NumMovedB) - (NumCurrA + NumCurrB));
if (Gain >= 0)
{
Lattice[Row][Col] = TgtState;
Lattice[TgtCell[0]][TgtCell[1]] = State;
}
else if (Gain == -1 || Gain == -2)
{
double pct = Math.Abs(GetRand()) % .99;
if (pct <= ((double)1 / (double)LMC))
{
Lattice[Row][Col] = TgtState;
Lattice[TgtCell[0]][TgtCell[1]] = State;
}
}
}
}
if (Gen % 10 == 0)
PrintLattice(Lattice, Gen);
}
}
/// <summary>
/// Returns a random number. Probably overkill but allows us to format if we ever want to.
/// </summary>
/// <returns>Random double</returns>
private static double GetRand()
{
return MyRand.NextDouble();
}
/// <summary>
/// Determine a random index into an array.
/// </summary>
/// <param name="ArraySize">Size of array</param>
/// <returns>Index</returns>
private static int RandomIndex(int ArraySize)
{
double Percentile = Math.Abs(GetRand()) % .99;
int NumSteps = ArraySize - 1;
int Index = (int)Math.Round(Percentile * (double)NumSteps, 0);
return Index;
}
/// <summary>
/// Determine a new cell reference given a current cell.
/// </summary>
/// <param name="Lattice">CA lattice</param>
/// <param name="ExistingCell">Starting cell</param>
/// <param name="AddRows">Rows to add to starting cell</param>
/// <param name="AddCols">Columns to add to starting cell</param>
/// <returns>New cell reference</returns>
private static int[] GetCellReference(ref string[][] Lattice, int[] ExistingCell, int AddRows, int AddCols)
{
int LatRows = Lattice.Length;
int LatCols = Lattice[0].Length;
// Wrapping!
int NextRow = ExistingCell[0] + AddRows;
int NextCol = ExistingCell[1] + AddCols;
if (NextRow > LatRows - 1)
NextRow = (NextRow - LatRows);
else if (NextRow < 0)
NextRow = (LatRows - Math.Abs(NextRow));
if (NextCol > LatCols - 1)
NextCol = (NextCol - LatCols);
else if (NextCol < 0)
NextCol = (LatCols - Math.Abs(NextCol));
int[] Cell = new int[2];
Cell[0] = NextRow;
Cell[1] = NextCol;
return Cell;
}
/// <summary>
/// Are all neighbors in the same state as the referenced cell?
/// </summary>
/// <param name="Lattice">CA lattice</param>
/// <param name="Row">Row in lattice</param>
/// <param name="Col">Column in lattice</param>
/// <returns>True or False</returns>
private static bool NeighborsInSameState(ref string[][] Lattice, int Row, int Col)
{
int[] Cell = new int[2];
Cell[0] = Row;
Cell[1] = Col;
string State = Lattice[Row][Col];
int Count = CountOfNeighborsInSameState(ref Lattice, Row, Col, State);
if (Count == 8)
return true;
return false;
}
/// <summary>
/// Determine a count of neighbors in the same state as the referenced cell.
/// </summary>
/// <param name="Lattice">CA lattice</param>
/// <param name="Row">Row in lattice</param>
/// <param name="Col">Col in lattice</param>
/// <param name="State">State of cell</param>
/// <returns>Count of neighbors in same state</returns>
private static int CountOfNeighborsInSameState(ref string[][] Lattice, int Row, int Col, string State)
{
int[] Cell = new int[2];
Cell[0] = Row;
Cell[1] = Col;
int Count = 0;
int[] Adders = new int[] { -1, 0, 1 };
for (int i = 0; i < Adders.Length; i++)
{
for (int j = 0; j < Adders.Length; j++)
{
if (Adders[i] == 0 && Adders[j] == 0)
continue; // the cell itself
int[] Index = GetCellReference(ref Lattice, Cell, Adders[i], Adders[j]);
string Val = Lattice[Index[0]][Index[1]];
if (Val.Equals(State))
Count++;
}
}
return Count;
}
/// <summary>
/// Return a random cell within the given distance of the referenced cell.
/// </summary>
/// <param name="Lattice">CA lattice</param>
/// <param name="Cell">Cell in lattice</param>
/// <param name="Distance">Maximum distance from cell</param>
/// <returns>Random cell (but not Cell)</returns>
private static int[] RandomCellWithinDistance(ref string[][] Lattice, int[] Cell, int Distance)
{
int Sz = Lattice.Length;
int MaxIters = 1000; // Arbitrary
int Count = 0;
// Rather than collect all cells within distance, simply pick a cell at random
// and test to see if it is within distance. Do this MaxIters times before giving up.
// Perhaps not as complete as some methods but possibly quicker for small-to-medium
// sized lattices. Regardless of the truth of that statement, it's my theory.
while (Count < MaxIters)
{
Count++;
int Row = RandomIndex(Sz);
int Col = RandomIndex(Sz);
int RowDistA = Row - Cell[0];
int RowDistB = (Sz - 1 - Row) + Cell[0];
if (Row < Cell[0])
{
RowDistA = Cell[0] - Row;
RowDistB = (Sz - 1 - Cell[0]) + Row;
}
int RowDist = Math.Min(RowDistA, RowDistB);
int ColDistA = Col - Cell[1];
int ColDistB = (Sz - 1 - Col) + Cell[1];
if (Col < Cell[1])
{
ColDistA = Cell[1] - Col;
ColDistB = (Sz - 1 - Cell[1]) + Col;
}
int ColDist = Math.Min(ColDistA, ColDistB);
int MyDist = (RowDist + ColDist);
if (MyDist == 0)
continue; // don't use the given cell
if (Distance > MyDist)
{
int[] NewCell = new int[2];
NewCell[0] = Row;
NewCell[1] = Col;
return NewCell;
}
}
return null; // Nothing found, return null.
}
/// <summary>
/// Print out the lattice to stdout.
/// </summary>
/// <param name="Lattice">CA lattice</param>
/// <param name="GenerationNumber">Generation number of lattice</param>
private static void PrintLattice(string[][] Lattice, int GenerationNumber)
{
int Sz = Lattice.Length;
string FirstLine = "";
for (int Z = 0; Z < Lattice[0].Length; Z++)
{
if (Lattice[0][Z].Equals(String.Empty))
FirstLine += " ";
else
FirstLine += Lattice[0][Z];
}
Bitmap objBmpImage = new Bitmap(1, 1);
int intWidth = 0;
int LineHeight = 0;
int intHeight = 0;
// Create the Font object for the image text drawing.
Font objFont = new Font("Courier New", 10, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Pixel);
// Create a graphics object to measure the text's width and height.
Graphics objGraphics = Graphics.FromImage(objBmpImage);
// This is where the bitmap size is determined.
intWidth = (int)objGraphics.MeasureString(FirstLine, objFont).Width;
LineHeight = (int)objGraphics.MeasureString(FirstLine, objFont).Height;
intHeight = LineHeight * ( 2 + Sz );
// Create the bmpImage again with the correct size for the text and font.
objBmpImage = new Bitmap(objBmpImage, new Size(intWidth, intHeight));
// Add the colors to the new bitmap.
objGraphics = Graphics.FromImage(objBmpImage);
// Set Background color
objGraphics.Clear(Color.White);
objGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
objGraphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
// Draw lines
SolidBrush brush = new SolidBrush(Color.FromArgb(102, 102, 102));
objGraphics.DrawString(String.Format("Generation {0}:", GenerationNumber), objFont, brush, 0, 0);
objGraphics.DrawString(" ", objFont, brush, 0, LineHeight);
for (int Z = 0; Z < Sz; Z++)
{
string line = "";
for (int i = 0; i < Lattice[Z].Length; i++)
{
if (Lattice[Z][i].Equals(String.Empty))
line += " ";
else
line += Lattice[Z][i];
}
objGraphics.DrawString(line, objFont, brush, 0, (2*LineHeight) + (LineHeight * Z));
}
// Flush buffer
objGraphics.Flush();
string path = String.Format("C:/CA_Gen_{0}.gif", GenerationNumber);
objBmpImage.Save(path, System.Drawing.Imaging.ImageFormat.Gif);
}
}
}
+- — -++- —- —— ++++ - + ++-+-+++++ – -











Looks interesting, though I am not very familiar with this stuff. Did a quick wiki. Are you planning to use it in some way?
No idea yet, just reworking some of my code and putting it out there.
[...] Fun with Cellular Automata [...]