diff --git a/README.md b/README.md index 56e84db..f983f13 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Cellular Automata Evolution Simulator -## [Full article](https://github.com/xcontcom/evolving-cellular-automata/blob/main/docs/article.md) +## [Full article](https://xcontcom.github.io/evolving-cellular-automata/docs/article.html) ![CA Demo](screenshots/demo.gif) @@ -41,6 +41,8 @@ cellular-automata-evolution/ └── README.md # Project documentation ``` +[Repository](https://github.com/xcontcom/evolving-cellular-automata) + ## Installation ### Prerequisites - **Node.js** (v14 or higher) diff --git a/docs/article.md b/docs/article.md index 4818d0f..e6e733a 100644 --- a/docs/article.md +++ b/docs/article.md @@ -1,5 +1,7 @@ # Evolving Cellular Automata +[Repository](https://github.com/xcontcom/evolving-cellular-automata) + ## Introduction Hello, curious minds! Let’s explore the fascinating intersection of cellular automata and genetic algorithms to uncover emergent patterns and behaviors. @@ -10,11 +12,11 @@ This article is a translation and adaptation of my 2019 piece, originally publis The simplest form of cellular automata is the one-dimensional variant. (While zero-dimensional oscillators exist, we’ll set them aside for now.) In a one-dimensional cellular automaton, we start with a single array representing the initial state, where each cell holds a binary value (0 or 1). The next state of each cell depends on its current state and those of its two immediate neighbors, determined by a predefined rule. -With three cells (the cell itself and its two neighbors), there are \(2^3 = 8\) possible configurations: +With three cells (the cell itself and its two neighbors), there are 2^3 = 8 possible configurations: ![Illustration of 8 neighbor configurations](images/1.png) -For each configuration, we define the cell’s next state (0 or 1), forming an 8-bit rule, known as the Wolfram code. This results in \(2^8 = 256\) possible one-dimensional cellular automata. +For each configuration, we define the cell’s next state (0 or 1), forming an 8-bit rule, known as the Wolfram code. This results in 2^8 = 256 possible one-dimensional cellular automata. ![Example of an 8-bit rule](images/2.png) @@ -30,7 +32,7 @@ For consistency, we order the neighbors as follows: ![Neighbor ordering](images/5.png) -With a cell and its eight neighbors, there are \(2^9 = 512\) possible configurations, and the rules are encoded as a 512-bit string, yielding \(2^{512} \approx 1.34 \times 10^{154}\) possible two-dimensional automata—a number far exceeding the estimated atoms in the observable universe (\(\sim 10^{80}\)). +With a cell and its eight neighbors, there are 29 = 512 possible configurations, and the rules are encoded as a 512-bit string. This yields 2512 ≈ 1.34 × 10154 possible two-dimensional automata — a number far exceeding the estimated ~1080 atoms in the observable universe. ![512-bit rule representation](images/6.png) @@ -481,7 +483,818 @@ Results with 5% and 25% mutations: ![25% with multi-point](images/64.png) -## Conclusion +--- + Using genetic algorithms to evolve cellular automata reveals a vast landscape of patterns and behaviors. From static grids to dynamic oscillators and complex shapes like “habr,” the interplay of selection, crossover, and mutation uncovers solutions that would be impossible to find manually. Experimenting with mutation rates and crossover methods highlights the robustness and flexibility of this approach, offering endless possibilities for discovery. -Try running these experiments yourself to find new patterns—each run is a new adventure! +Try running these experiments yourself to find new patterns — each run is a new adventure! + +[Repository](https://github.com/xcontcom/evolving-cellular-automata) + +That's all for natural selection. For artificial selection, we'll use second-order cellular automata. + +--- + +# Artificial selection + +--- + +## Second-order cellular automaton + +Let us look at a zero-dimensional first-order cellular automaton (all the automata we discussed above are first-order). +A zero-dimensional automaton consists of a single cell. +The cell can be in one of two states, 0 or 1. +Its next state at time t depends only on its state at time t−1. +There are exactly four such zero-dimensional first-order automata (one of them is an oscillator): + +![Zero-dimensional first-order cellular automata](images/65.png) + +In a second-order cellular automaton, the next state of the cell at time t depends on both the current state at time t−1 and the previous state at time t−2. +There are four possible combinations of two cell states. +Hence 2^4 = 16 zero-dimensional second-order cellular automata: + +![Zero-dimensional second-order cellular automata](images/66.png) + +These automata already produce more complicated oscillators. + +For third order there are 2^8 = 256 zero-dimensional automata: + +![Zero-dimensional third-order cellular automata](images/67.png) + +For fourth order we would have 2^16 = 65,536 zero-dimensional automata, which can’t be shown in a single picture. + +Finding, for an n-th order automaton, a rule whose oscillation period is exactly n is a non-trivial and very interesting task. It deserves a separate article. + +In a one-dimensional second-order cellular automaton the next state of a cell is determined by the current state of three cells (itself and its two neighbours, as usual) and by the previous state of the same cell: + +![Neighborhood for a one-dimensional second-order cellular automaton](images/68.png) + +There are 2^16 = 65,536 one-dimensional second-order cellular automata. + +Code (JavaScript): + +```javascript +var rule = []; +for (var i = 0; i < 16; i++) rule[i] = Math.round(Math.random()); + +var a = []; +var b = []; +var temp; +for (var x = 0; x < sizex; x++) { + a[x] = 0; + b[x] = 0; +} +b[63] = 1; + +var xm, xp, q; +for (var y = 2; y < sizey; y++) { + temp = []; + for (var x = 0; x < sizex; x++) { + xm = x - 1; + if (xm < 0) xm = sizex + xm; + xp = x + 1; + if (xp >= sizex) xp = xp - sizex; + + q = b[xm]; + q = (q << 1) + b[x]; + q = (q << 1) + b[xp]; + q = (q << 1) + a[x]; + + temp[x] = rule[q]; + if (temp[x]) context.fillRect(x * size, y * size, size, size); + } + a = b; + b = temp; +} +``` + +Second-order cellular automata produce more complex patterns than first-order ones. + +Below are several random second-order rules (on each image, the left half shows the evolution from a single active cell at t−1, and the right half shows evolution from random states at t−1 and t−2; the binary code is the contents of the array `rule`): + +0011111011001000: + +![Second-order CA, rule 0011111011001000](images/69.png) + +0101101110011110: + +![Second-order CA, rule 0101101110011110](images/70.png) + +0110000110010010: + +![Second-order CA, rule 0110000110010010](images/71.png) + +0110011010010110: + +![Second-order CA, rule 0110011010010110](images/72.png) + +1110011010010110: + +![Second-order CA, rule 1110011010010110](images/73.png) + +0110111010000101: + +![Second-order CA, rule 0110111010000101](images/74.png) + +1111101001110110: + +![Second-order CA, rule 1111101001110110](images/75.png) + +1001010001100000: + +![Second-order CA, rule 1001010001100000](images/76.png) + +The same automaton as above on a 256×256 grid: + +![Second-order CA on a 256×256 grid](images/77.png) + +And on a 512×512 grid: + +![Second-order CA on a 512×512 grid](images/78.png) + +You can see more automata here: + +[One-dimensional second-order CA](http://xcont.com/cell/2order/) +[One-dimensional third-order CA](http://xcont.com/cell/3order/) + +You can read more about one-dimensional second-order cellular automata in Stephen Wolfram’s book *A New Kind of Science*. + +## Artificial selection + +By analogy with the one-dimensional second-order automaton, in a two-dimensional second-order cellular automaton we also use one additional bit that encodes the cell’s state from time t−2. + +For convenience we place this bit at the beginning of the binary string that represents the neighbourhood: + +![Encoding of the additional bit from t−2](images/79.png) + +The convenience here is that if the first and second halves of the genotype coincide, then the automaton can be regarded as a first-order automaton: + +![Second-order rule that effectively behaves as first-order](images/80.png) + +By adding just one additional cell (one bit from the previous state), we multiplied the number of possible automata by 2^512. +The total number of second-order two-dimensional automata is therefore: + +(2^512) × (2^512) = 2^1024. + +In the previous sections, when we spoke about “natural selection”, we defined some explicit criterion and compared automata according to this criterion. + +In the case of artificial selection we choose automata manually, using a very fuzzy heuristic: “this automaton looks interesting, that one doesn’t”. + +This principle does not help much when we try to pick out the “best” automaton from a bunch of random ones: + +![Random CA 1](images/81.gif) + +![Random CA 2](images/82.gif) + +![Random CA 3](images/83.gif) + +![Random CA 4](images/84.gif) + +There are several ways to make the selection process more meaningful. +Below I will describe four approaches. + +### 1. Single active cell in the initial state + +One approach is to observe the evolution of an automaton that starts from a single active cell in the initial configuration. + +We create an initial population filled with random automata. +A few examples from the initial population (30 iterations for each automaton): + +![Random automaton, single-seed evolution 1](images/85.gif) + +![Random automaton, single-seed evolution 2](images/86.gif) + +Within the population there is a small subset of automata that behave less chaotically. +Those are the ones we will select for crossover: + +![Selected less-chaotic automaton 1](images/89.gif) + +![Selected less-chaotic automaton 2](images/90.gif) + +![Selected less-chaotic automaton 3](images/91.gif) + +![Selected less-chaotic automaton 4](images/92.gif) + +Below are 20 random automata from the initial population (their states at iteration 30): + +![Initial population, iteration 30](images/93.png) + +After three generations of evolution: + +![Population after three generations](images/94.png) + +After eight generations of evolution: + +![Population after eight generations](images/95.png) + +Eight generations were enough for the automaton with a particular visual feature (the one that draws triangles) to take over the entire population. + +### 2. Partially filled genotype + +If we change the balance between zeros and ones in the genotype, then the balance between zeros and ones in the phenotype (the actual configuration of the automaton) will also change. + +The rule (genotype) of an automaton specifies the next state of a cell for every possible combination of the cell and its neighbours. +If the genotype contains more zeros (or more ones), then in subsequent states of the automaton zeros (or ones) will gradually dominate. + +It is interesting to look at the correlation between the proportion of ones and zeros in the genotype and the proportion of ones and zeros in the phenotype. + +Let’s plot this relationship. + +We create a series of populations, 200 automata in each. +The genotype of each second-order automaton contains 1024 genes. +For the first population we set all genes to zero. +For each subsequent population we set n genes to one (the rest remain zero). +For the first population n = 0, for the 513-th population n = 512. + +On the x-axis we put the population index. +On the y-axis (white dots) we plot the ratio of ones to zeros in the gene pool of that population. +We get a hyperbola: + +![Ratio of ones to zeros in the genotype](images/96.png) + +For each automaton (on a grid of size 89×89) we run 100 iterations. +On the 100-th iteration we count the number of ones and zeros in the state (phenotype) of each automaton. +On the graph we then plot the ratio of ones to zeros in the phenotypes (the total number of ones divided by the total number of zeros across all automata in the population). +We get the following curve: + +![Ratio of ones to zeros in the phenotypes (total)](images/97.png) + +Instead of the total ratio over all phenotypes, we can also look at the ratio within each individual phenotype: + +![Ratio of ones to zeros per automaton](images/98.png) + +On the left side of the graph we can see points that deviate the most from the average value. +We can hypothesize that these are precisely the automata whose 0-th gene is equal to one. +Let’s test that hypothesis. +We force the 0-th gene to always be zero and plot a new graph: + +![Effect of forcing gene 0 = 0](images/99.png) + +Now compare with the case where the 0-th gene is always equal to one: + +![Effect of forcing gene 0 = 1](images/100.png) + +Second-order automata have another “zero” gene — number 512. +Let us see how this gene affects the phenotype. + +0-th and 512-th genes are always zero: + +![Both gene 0 and gene 512 fixed to 0](images/101.png) + +0-th gene is zero, 512-th gene is one: + +![Gene 0 = 0, gene 512 = 1](images/102.png) + +To avoid torturing photosensitive people with excessive flickering, in the genetic algorithm we will set both the 0-th and 512-th genes to zero in the initial population. + +Now let us look at the automata we effectively removed by fixing genes 0 and 512 to zero. + +The first 8 states of the automaton where only the 0-th gene is set (gene 0 = 1, all others are zero): + +![Automaton with only gene 0 = 1](images/103.png) + +The automaton where only the 512-th gene is set: + +![Automaton with only gene 512 = 1](images/104.png) + +The automaton where only genes 0 and 512 are set: + +![Automaton with only genes 0 and 512 = 1](images/105.png) + +Now highlight on the graph the region where the population starts to split into distinct clusters: + +![Region where population splits into groups](images/106.png) + +In this region the genotypes are 25% filled with ones. + +Let’s compare two populations. + +**First population.** +30 random automata at iteration 1000. +Genotypes are 50% filled (512 ones and 512 zeros): + +![Population with 50% ones in genotype](images/107.png) + +**Second population.** +30 random automata at iteration 1000. +Genotypes are 25% filled (256 ones and 768 zeros): + +![Population with 25% ones in genotype](images/108.png) + +The second population is well suited for artificial selection. +We can easily highlight certain visual traits in these automata—for example “darker ones”, “less chaotic ones” (where white cells cluster together), and so on. + +Let us select the “darker” automata. +Mutation probability is 10%, up to 4 genes mutate at a time. +After the first selection step: + +![After first selection of darker automata](images/109.png) + +After the second selection step: + +![After second selection of darker automata](images/110.png) + +An interesting automaton has appeared in the population. + +Here it is on a 256×256 grid, at iteration 1000: + +![Evolved automaton 1, 256×256, iteration 1000](images/111.png) + +This automaton gradually takes over the population. + +After the eighth selection step: + +![Population after eight selection steps](images/112.png) + +Another interesting automaton appears. + +Again on a 256×256 grid, iteration 1000: + +![Evolved automaton 2, 256×256, iteration 1000](images/113.png) + +The population after thirteen selection steps: + +![Population after thirteen selection steps](images/114.png) + +Below are several automata from this population. +Each is shown on a 256×256 grid at iteration 1000. +Under each picture there is a link where you can watch the automaton evolve in time: + +![Selected automaton 0, 256×256, iteration 1000](images/115.png) + +[Watch in dynamics](http://xcont.com/cell/2d_2order/0/) + +![Selected automaton 1, 256×256, iteration 1000](images/116.png) + +[Watch in dynamics](http://xcont.com/cell/2d_2order/1/) + +![Selected automaton 2, 256×256, iteration 1000](images/117.png) + +[Watch in dynamics](http://xcont.com/cell/2d_2order/2/) + +![Selected automaton 3, 256×256, iteration 1000](images/118.png) + +[Watch in dynamics](http://xcont.com/cell/2d_2order/3/) + +![Selected automaton 4, 256×256, iteration 1000](images/119.png) + +[Watch in dynamics](http://xcont.com/cell/2d_2order/4/) + +![Selected automaton 5, 256×256, iteration 1000](images/120.png) + +[Watch in dynamics](http://xcont.com/cell/2d_2order/5/) + +![Selected automaton 6, 256×256, iteration 1000](images/121.png) + +[Watch in dynamics](http://xcont.com/cell/2d_2order/6/) + +![Selected automaton 7, 256×256, iteration 1000](images/122.png) + +[Watch in dynamics](http://xcont.com/cell/2d_2order/7/) + +![Selected automaton 8, 256×256, iteration 1000](images/123.png) + +[Watch in dynamics](http://xcont.com/cell/2d_2order/8/) + +![Selected automaton 9, 256×256, iteration 1000](images/124.png) + +[Watch in dynamics](http://xcont.com/cell/2d_2order/9/) + +### 3. Conway’s Game of Life and similar automata + +The most famous two-dimensional first-order cellular automaton is Conway’s *Game of Life*. + +Its rules can be stated as follows: +– If a dead cell has exactly 3 live neighbours, it becomes alive (otherwise it stays dead). +– If a live cell has 2 or 3 live neighbours, it survives (otherwise it dies). +We represent a dead cell as 0 and a live cell as 1. + +A cell can have between 0 and 8 live neighbours. +There are therefore 9 possible neighbour counts around a dead cell and 9 around a live cell. +We can encode these in an array `r`: + +```javascript +r = [ + 0,0,0,1,0,0,0,0,0, + 0,0,1,1,0,0,0,0,0 +]; +``` + +The first half of the array corresponds to a dead cell, the second half to a live cell. + +We can now expand Conway’s rule for all possible (512) combinations of the cell and its 8 neighbours: + +```javascript +r = [ + 0,0,0,1,0,0,0,0,0, + 0,0,1,1,0,0,0,0,0 +]; +var rule = []; +var q1, q2; +for (var i = 0; i < 512; i++) { + var ii = i.toString(2); + for (var j = ii.length; j < 9; j++) ii = '0' + ii; + q1 = 1 * ii[4]; + q2 = 1 * ii[0] + 1 * ii[1] + 1 * ii[2] + 1 * ii[3] + 1 * ii[5] + 1 * ii[6] + 1 * ii[7] + 1 * ii[8]; + if (q1 == 0) + rule[i] = r[q2]; + else + rule[i] = r[q2 + 9]; +} +``` + +An optimized variant: + +```javascript +r = [ + 0,0,0,1,0,0,0,0,0, + 0,0,1,1,0,0,0,0,0 +]; +var rule = []; +for (var i = 0; i < 512; i++) { + var q = ((i >> 4) & 1) * 8; + for (var j = 0; j < 9; j++) { + q += (i >> j) & 1; + } + rule[i] = r[q]; +} +``` + +For the second-order version we simply copy the second half of the `rule` array from the first one: + +```javascript +for (var i = 0; i < 512; i++) { + if (rule[i] == 0) + rule[i + 512] = 0; + else + rule[i + 512] = 1; +} +``` + +If we run the automaton with this rule, we see characteristic gliders and oscillators. +Here are several iterations of this automaton: + +![Conway-type rule, evolution from random initial state](images/125.gif) + +The array `r` has 18 entries. +There are therefore 2^18 = 262,144 rules of “Conway-type” (i.e. rules that can be written in the same language: for which neighbour counts dead cells are born, and for which neighbour counts live cells die). +You can explore these rules here: + +[Conway-type automata](http://xcont.com/cell/conway/) +(Conway’s rule is loaded by default; the “Change rule” button fills array `r` randomly.) + +Here are some random automata of this type (the binary code shown under each picture is the contents of `r`): + +110010011001111111 + +![Conway-type rule 110010011001111111](images/126.png) + +100001100110111110 + +![Conway-type rule 100001100110111110](images/127.gif) + +011111000100101110 + +![Conway-type rule 011111000100101110](images/128.gif) + +010000110000110010 + +![Conway-type rule 010000110000110010](images/129.gif) + +001111010011100111 + +![Conway-type rule 001111010011100111](images/200.gif) + +000111001000000110 + +![Conway-type rule 000111001000000110](images/201.gif) + +000101100010100001 + +![Conway-type rule 000101100010100001](images/202.gif) + +000001111101011111 + +![Conway-type rule 000001111101011111](images/203.png) + +000001100110111111 + +![Conway-type rule 000001100110111111](images/204.png) + +For the genetic algorithm we can use either the array `r` as the genotype (2^18 possible combinations), or the full array `rule` (2^512 combinations for a first-order automaton and 2^1024 combinations for a second-order automaton). + +The space of 2^18 rules is relatively small. +It is small enough that one can search it manually without a genetic algorithm (which is essentially what Conway did). + +If, however, we fill the array `rule` with random Conway-type automata and use it as the genotype, then the experiment, to some extent, becomes unsuccessful (unsuccessful enough to not include those results here). +The point is that Conway-type rules have a built-in symmetry. +For example, for the following neighbourhood patterns: + +![Neighbourhood patterns with the same next state 1](images/205.png) + +… and for these ones: + +![Neighbourhood patterns with the same next state 2](images/206.png) + +the next state of the central cell is the same. +After the first crossover this symmetry is broken in the offspring rules. +The parent rules accumulate mutations that also destroy the symmetry. +Breaking the symmetry in the genotype destroys the symmetry in the phenotype as well. + +We can see this symmetry in the phenotype if we start from a single live cell in the initial configuration. + +Let’s run an experiment. +To preserve symmetry, we will use the array `r` as the genotype. +Mutation probability is 5%, one gene mutates at a time. +The initial configuration contains a single live cell. + +Below are 30 random automata from the initial population. +Each image shows the state of the automaton at iteration 30: + +![Initial population of Conway-type rules, iteration 30](images/207.png) + +We will try to select automata that expand (grow) from a single live cell as slowly as possible. + +![Slow-growing Conway-type automaton 1](images/208.png) + +![Slow-growing Conway-type automaton 2](images/209.png) + +![Slow-growing Conway-type automaton 3](images/210.png) + +![Slow-growing Conway-type automaton 4](images/211.png) + +![Slow-growing Conway-type automaton 5](images/212.png) + +After the first selection step we get rid of automata that do not develop at all: + +![After first selection, non-developing automata removed](images/213.png) + +In the new population some non-developing automata still appear — these are unsuccessful offspring or mutants. + +From this point on we will mostly select automata whose background remains white (cells that the wave front has not yet reached). + +The black automata in these pictures are blinking. + +If the 0-th gene is zero (if a cell with all black neighbours remains black), then all such automata expand at the same speed. +Such automata do not match our selection criterion (minimal growth speed). +The number of such rules decreases with each selection. +If the 0-th gene is equal to one, then on the first (odd-numbered) iteration the background flips to white. +Later the background may either stay white or blink (white on odd iterations, black on even ones). +We do selection at iteration 30, which is even. +By discarding automata with a black background at iteration 30, we also discard the blinking ones (again, out of respect for photosensitive viewers). + +Population after the second selection step: + +![Population after second selection step](images/214.png) + +After 3 selections: + +![Population after three selections](images/215.png) + +After 5 selections: + +![Population after five selections](images/216.png) + +After 8 selections: + +![Population after eight selections](images/217.png) + +After 13 selections: + +![Population after thirteen selections](images/218.png) + +The same automata at iteration 60: + +![Same Conway-type automata at iteration 60](images/219.png) + +After 21 selections. +States at iteration 30: + +![Population after twenty-one selections, iteration 30](images/220.png) + +States at iteration 60: + +![Population after twenty-one selections, iteration 60](images/221.png) + +After 34 selections. +States at iteration 30: + +![Population after thirty-four selections, iteration 30](images/222.png) + +States at iteration 60: + +![Population after thirty-four selections, iteration 60](images/223.png) + +After that, the system essentially stops evolving. + +Here are three automata from this final population (100 iterations each). +Below each image is the corresponding array `r`: + +[1,0,1,1,1,0,0,1,0,0,1,1,0,1,0,1,1,1] + +![Stable slow-growing Conway-type automaton 1](images/224.gif) + +[1,0,1,1,1,0,0,1,0,0,0,1,0,1,0,1,1,1] + +![Stable slow-growing Conway-type automaton 2](images/225.gif) + +[1,0,0,1,1,0,0,1,1,0,1,0,1,1,0,1,1,1] + +![Stable slow-growing Conway-type automaton 3](images/226.gif) + +For comparison, here is a random automaton: + +[1,0,0,1,1,1,0,1,1,1,0,0,1,1,0,0,0,1] + +![Random Conway-type automaton](images/227.gif) + +### 4. Conway-type automata (variant 2) + +In Conway-type rules we count the total number of live cells in the Moore neighbourhood. +We can modify this by grouping the cells of the neighbourhood into four pairs and counting live cells within each pair: + +![Pair grouping of the Moore neighbourhood](images/228.png) + +This increases the number of distinct rules while still preserving symmetry in the phenotype. + +Each pair can contain 0, 1 or 2 live cells. +There are four pairs. +So there are 3^4 = 81 possible combinations for the neighbourhood of a dead cell, and 81 combinations for the neighbourhood of a live cell. +In total we get 2^162 automata of this type. + +The number + +2^162 ≈ 5.846 × 10^48 + +is suitably astronomical and is large enough to serve as a search space for a genetic algorithm. + +The genotype of each individual in our GA population has length 162 bits. + +We initialize the population with random automata: + +```javascript +var rulesize = 162; +for (var n = 0; n < PopulationSize; n++) { + population[n] = []; + fitness[n] = 0; + for (var i = 0; i < rulesize; i++) { + population[n][i] = Math.round(Math.random()); + } +} +``` + +Next we expand each such rule to all possible combinations of the central cell and its eight neighbours, filling an array `rule` for each individual: + +```javascript +function fillrule() { + var r; + for (var n = 0; n < PopulationSize; n++) { + rule[n] = []; + r = population[n]; + var q1, q2, q3, q4, q5; + var q; + for (var i = 0; i < 512; i++) { + var ii = i.toString(2); + for (var j = ii.length; j < 9; j++) ii = '0' + ii; + q1 = 1 * ii[4]; + q2 = 1 * ii[0] + 1 * ii[8]; + q3 = 1 * ii[1] + 1 * ii[7]; + q4 = 1 * ii[2] + 1 * ii[6]; + q5 = 1 * ii[3] + 1 * ii[5]; + q = parseInt('' + q2 + q3 + q4 + q5, 3); + if (q1 == 0) + rule[n][i] = r[q]; + else + rule[n][i] = r[q + 81]; + } + } +} +``` + +We use the array `rule` when computing the next state of each automaton. +The function `fillrule()` is called once at page load and again after each call of `evolute()` (which performs reproduction and mutation). + +The initial population looks chaotic. +Here are 30 random automata at iteration 1000: + +![Initial population of pair-based Conway-type rules](images/229.png) + +This chaos, however, is somewhat different from rule to rule when viewed dynamically, so the automata are still suitable for selection. +But we can simplify the task and again select the “darkest” ones (those with the most live cells, if we render live cells as black). + +Population after five selections: + +![Population after five selections in pair-based rules](images/230.png) + +From here we can start looking for automata with the most complex oscillators. +There is no point in showing the entire process in detail. +Below are several of the most interesting automata found by the genetic algorithm. + +Each image shows a 256×256 grid at iteration 10,000. +Under each image there is a link where you can see the automaton evolve: + +![Evolved pair-based automaton 0, 256×256, iteration 10000](images/231.png) + +[Watch in dynamics](http://xcont.com/cell/conway2/0/) + +![Evolved pair-based automaton 1, 256×256, iteration 10000](images/232.png) + +[Watch in dynamics](http://xcont.com/cell/conway2/1/) + +![Evolved pair-based automaton 2, 256×256, iteration 10000](images/233.png) + +[Watch in dynamics](http://xcont.com/cell/conway2/2/) + +![Evolved pair-based automaton 3, 256×256, iteration 10000](images/234.png) + +[Watch in dynamics](http://xcont.com/cell/conway2/3/) + +![Evolved pair-based automaton 4, 256×256, iteration 10000](images/235.png) + +[Watch in dynamics](http://xcont.com/cell/conway2/4/) + +![Evolved pair-based automaton 5, 256×256, iteration 10000](images/236.png) + +[Watch in dynamics](http://xcont.com/cell/conway2/5/) + +![Evolved pair-based automaton 6, 256×256, iteration 10000](images/237.png) + +[Watch in dynamics](http://xcont.com/cell/conway2/6/) + +![Evolved pair-based automaton 7, 256×256, iteration 10000](images/238.png) + +[Watch in dynamics](http://xcont.com/cell/conway2/7/) + +![Evolved pair-based automaton 8, 256×256, iteration 10000](images/239.png) + +[Watch in dynamics](http://xcont.com/cell/conway2/8/) + +![Evolved pair-based automaton 9, 256×256, iteration 10000](images/240.png) + +[Watch in dynamics](http://xcont.com/cell/conway2/9/) + +![Evolved pair-based automaton 10, 256×256, iteration 10000](images/241.png) + +[Watch in dynamics](http://xcont.com/cell/conway2/10/) + +![Evolved pair-based automaton 11, 256×256, iteration 10000](images/242.png) + +[Watch in dynamics](http://xcont.com/cell/conway2/11/) + +![Evolved pair-based automaton 12, 256×256, iteration 10000](images/243.png) + +[Watch in dynamics](http://xcont.com/cell/conway2/12/) + +![Evolved pair-based automaton 13, 256×256, iteration 10000](images/244.png) + +[Watch in dynamics](http://xcont.com/cell/conway2/13/) + +![Matrix](images/245.png) + +[Matrix-style rendering in green](http://xcont.com/cell/conway2/matrix/) +[Slower version on a 512×512 grid](http://xcont.com/cell/conway2/matrix512/) + +## Repository + +[https://github.com/xcontcom/evolving-cellular-automata](https://github.com/xcontcom/evolving-cellular-automata) + +## Conclusion + +In this project we used a genetic algorithm to explore the enormous space of two-dimensional cellular automata. +Already for first-order, two-state, two-dimensional automata there are 2^512 possible rules, and for second-order automata there are 2^1024. +This is an astronomically large space: far more automata than there are atoms in the observable universe. + +The famous Conway *Game of Life* is just one particular point in this space, and in fact belongs to a tiny, very special subclass of rules. +When people hear “cellular automata”, they almost always think only of Conway’s automaton, without realizing how many other rules are possible and how rich their behaviour can be. +The experiments in this article illustrate that even simple genetic selection, based on informal visual criteria (“less chaotic”, “darker”, “slower growth”, “more interesting oscillators”), is enough to discover a wide variety of non-trivial automata. + +In this work we used the rule of the automaton itself as the genotype and tried to evolve rules that match some chosen criterion. + +In the next project we will fix the rule (for example, Conway’s Game of Life or any other chosen rule) and instead use the *initial configuration* of the automaton as the genotype: + +[https://github.com/xcontcom/initial-state-evolution](https://github.com/xcontcom/initial-state-evolution) + +On a common toroidal grid we place two automata, A and B. +The field is wrapped around horizontally for each automaton, and vertically the upper and lower boundaries of the two automata are shared—so each automaton interacts with the other along a common border. + +As a fitness function for automaton A we use the state of automaton B at iteration n, and as the fitness function for B we use the state of A at iteration n. +In other words, each automaton’s goal is to influence the configuration of its opponent (or partner). + +Conway’s Game of Life is known to be Turing-complete; therefore, in principle, any algorithm or system of arbitrary complexity can be implemented within it. +By co-evolving automata that compete (or cooperate) with each other, we hope to obtain increasingly complex patterns and behaviours. +Unlike the current project, we will not prescribe a fixed target or end state. +Instead, the “goal” of the system is the ongoing competition (or collaboration) between automata. +This makes the evolutionary process more open-ended and may lead to structures that are unexpected and qualitatively different from what we would obtain by optimizing a manually chosen criterion. + +--- + +## About the Author + +Serhii Herasymov +*Programmer & Math Enthusiast* (Ukraine → Ireland) + +Email: sergeygerasimofff@gmail.com +GitHub: [xcontcom](https://github.com/xcontcom) +X (Twitter): [@xcontcom](https://x.com/xcontcom) + +Feedback welcome. diff --git a/docs/images/100.png b/docs/images/100.png new file mode 100644 index 0000000..3de4cd1 Binary files /dev/null and b/docs/images/100.png differ diff --git a/docs/images/101.png b/docs/images/101.png new file mode 100644 index 0000000..43c3599 Binary files /dev/null and b/docs/images/101.png differ diff --git a/docs/images/102.png b/docs/images/102.png new file mode 100644 index 0000000..a8690fe Binary files /dev/null and b/docs/images/102.png differ diff --git a/docs/images/103.png b/docs/images/103.png new file mode 100644 index 0000000..2f810c4 Binary files /dev/null and b/docs/images/103.png differ diff --git a/docs/images/104.png b/docs/images/104.png new file mode 100644 index 0000000..9ca98ad Binary files /dev/null and b/docs/images/104.png differ diff --git a/docs/images/105.png b/docs/images/105.png new file mode 100644 index 0000000..c0bcab4 Binary files /dev/null and b/docs/images/105.png differ diff --git a/docs/images/106.png b/docs/images/106.png new file mode 100644 index 0000000..f7155a1 Binary files /dev/null and b/docs/images/106.png differ diff --git a/docs/images/107.png b/docs/images/107.png new file mode 100644 index 0000000..c6627f4 Binary files /dev/null and b/docs/images/107.png differ diff --git a/docs/images/108.png b/docs/images/108.png new file mode 100644 index 0000000..af79a1c Binary files /dev/null and b/docs/images/108.png differ diff --git a/docs/images/109.png b/docs/images/109.png new file mode 100644 index 0000000..bcf484a Binary files /dev/null and b/docs/images/109.png differ diff --git a/docs/images/110.png b/docs/images/110.png new file mode 100644 index 0000000..ef9a740 Binary files /dev/null and b/docs/images/110.png differ diff --git a/docs/images/111.png b/docs/images/111.png new file mode 100644 index 0000000..992167e Binary files /dev/null and b/docs/images/111.png differ diff --git a/docs/images/112.png b/docs/images/112.png new file mode 100644 index 0000000..f1ebee4 Binary files /dev/null and b/docs/images/112.png differ diff --git a/docs/images/113.png b/docs/images/113.png new file mode 100644 index 0000000..9c3c0f7 Binary files /dev/null and b/docs/images/113.png differ diff --git a/docs/images/114.png b/docs/images/114.png new file mode 100644 index 0000000..7b64d5c Binary files /dev/null and b/docs/images/114.png differ diff --git a/docs/images/115.png b/docs/images/115.png new file mode 100644 index 0000000..1c6403c Binary files /dev/null and b/docs/images/115.png differ diff --git a/docs/images/116.png b/docs/images/116.png new file mode 100644 index 0000000..61088e1 Binary files /dev/null and b/docs/images/116.png differ diff --git a/docs/images/117.png b/docs/images/117.png new file mode 100644 index 0000000..5cebe01 Binary files /dev/null and b/docs/images/117.png differ diff --git a/docs/images/118.png b/docs/images/118.png new file mode 100644 index 0000000..296816e Binary files /dev/null and b/docs/images/118.png differ diff --git a/docs/images/119.png b/docs/images/119.png new file mode 100644 index 0000000..7a0301f Binary files /dev/null and b/docs/images/119.png differ diff --git a/docs/images/120.png b/docs/images/120.png new file mode 100644 index 0000000..1305955 Binary files /dev/null and b/docs/images/120.png differ diff --git a/docs/images/121.png b/docs/images/121.png new file mode 100644 index 0000000..d6de3bd Binary files /dev/null and b/docs/images/121.png differ diff --git a/docs/images/122.png b/docs/images/122.png new file mode 100644 index 0000000..10bba0c Binary files /dev/null and b/docs/images/122.png differ diff --git a/docs/images/123.png b/docs/images/123.png new file mode 100644 index 0000000..c7f32b9 Binary files /dev/null and b/docs/images/123.png differ diff --git a/docs/images/124.png b/docs/images/124.png new file mode 100644 index 0000000..81282ab Binary files /dev/null and b/docs/images/124.png differ diff --git a/docs/images/125.gif b/docs/images/125.gif new file mode 100644 index 0000000..5641b6a Binary files /dev/null and b/docs/images/125.gif differ diff --git a/docs/images/126.png b/docs/images/126.png new file mode 100644 index 0000000..29b64ca Binary files /dev/null and b/docs/images/126.png differ diff --git a/docs/images/127.gif b/docs/images/127.gif new file mode 100644 index 0000000..252291d Binary files /dev/null and b/docs/images/127.gif differ diff --git a/docs/images/128.gif b/docs/images/128.gif new file mode 100644 index 0000000..31dfb33 Binary files /dev/null and b/docs/images/128.gif differ diff --git a/docs/images/129.gif b/docs/images/129.gif new file mode 100644 index 0000000..7532d7b Binary files /dev/null and b/docs/images/129.gif differ diff --git a/docs/images/200.gif b/docs/images/200.gif new file mode 100644 index 0000000..4db06d4 Binary files /dev/null and b/docs/images/200.gif differ diff --git a/docs/images/201.gif b/docs/images/201.gif new file mode 100644 index 0000000..5d1a4f6 Binary files /dev/null and b/docs/images/201.gif differ diff --git a/docs/images/202.gif b/docs/images/202.gif new file mode 100644 index 0000000..4373f77 Binary files /dev/null and b/docs/images/202.gif differ diff --git a/docs/images/203.png b/docs/images/203.png new file mode 100644 index 0000000..9008120 Binary files /dev/null and b/docs/images/203.png differ diff --git a/docs/images/204.png b/docs/images/204.png new file mode 100644 index 0000000..ccaf083 Binary files /dev/null and b/docs/images/204.png differ diff --git a/docs/images/205.png b/docs/images/205.png new file mode 100644 index 0000000..4ab8795 Binary files /dev/null and b/docs/images/205.png differ diff --git a/docs/images/206.png b/docs/images/206.png new file mode 100644 index 0000000..2739fb1 Binary files /dev/null and b/docs/images/206.png differ diff --git a/docs/images/207.png b/docs/images/207.png new file mode 100644 index 0000000..bb0a7b9 Binary files /dev/null and b/docs/images/207.png differ diff --git a/docs/images/208.png b/docs/images/208.png new file mode 100644 index 0000000..0681afe Binary files /dev/null and b/docs/images/208.png differ diff --git a/docs/images/209.png b/docs/images/209.png new file mode 100644 index 0000000..8141833 Binary files /dev/null and b/docs/images/209.png differ diff --git a/docs/images/210.png b/docs/images/210.png new file mode 100644 index 0000000..b3b055a Binary files /dev/null and b/docs/images/210.png differ diff --git a/docs/images/211.png b/docs/images/211.png new file mode 100644 index 0000000..26c68a6 Binary files /dev/null and b/docs/images/211.png differ diff --git a/docs/images/212.png b/docs/images/212.png new file mode 100644 index 0000000..b030ada Binary files /dev/null and b/docs/images/212.png differ diff --git a/docs/images/213.png b/docs/images/213.png new file mode 100644 index 0000000..c02e885 Binary files /dev/null and b/docs/images/213.png differ diff --git a/docs/images/214.png b/docs/images/214.png new file mode 100644 index 0000000..69b4247 Binary files /dev/null and b/docs/images/214.png differ diff --git a/docs/images/215.png b/docs/images/215.png new file mode 100644 index 0000000..1f54ded Binary files /dev/null and b/docs/images/215.png differ diff --git a/docs/images/216.png b/docs/images/216.png new file mode 100644 index 0000000..b20af1b Binary files /dev/null and b/docs/images/216.png differ diff --git a/docs/images/217.png b/docs/images/217.png new file mode 100644 index 0000000..ffa9c1b Binary files /dev/null and b/docs/images/217.png differ diff --git a/docs/images/218.png b/docs/images/218.png new file mode 100644 index 0000000..4edf31a Binary files /dev/null and b/docs/images/218.png differ diff --git a/docs/images/219.png b/docs/images/219.png new file mode 100644 index 0000000..b66deac Binary files /dev/null and b/docs/images/219.png differ diff --git a/docs/images/220.png b/docs/images/220.png new file mode 100644 index 0000000..71881d0 Binary files /dev/null and b/docs/images/220.png differ diff --git a/docs/images/221.png b/docs/images/221.png new file mode 100644 index 0000000..861347b Binary files /dev/null and b/docs/images/221.png differ diff --git a/docs/images/222.png b/docs/images/222.png new file mode 100644 index 0000000..2a65be2 Binary files /dev/null and b/docs/images/222.png differ diff --git a/docs/images/223.png b/docs/images/223.png new file mode 100644 index 0000000..639bc6e Binary files /dev/null and b/docs/images/223.png differ diff --git a/docs/images/224.gif b/docs/images/224.gif new file mode 100644 index 0000000..82daf07 Binary files /dev/null and b/docs/images/224.gif differ diff --git a/docs/images/225.gif b/docs/images/225.gif new file mode 100644 index 0000000..27405a6 Binary files /dev/null and b/docs/images/225.gif differ diff --git a/docs/images/226.gif b/docs/images/226.gif new file mode 100644 index 0000000..051e036 Binary files /dev/null and b/docs/images/226.gif differ diff --git a/docs/images/227.gif b/docs/images/227.gif new file mode 100644 index 0000000..50ddcaf Binary files /dev/null and b/docs/images/227.gif differ diff --git a/docs/images/228.png b/docs/images/228.png new file mode 100644 index 0000000..a3ca622 Binary files /dev/null and b/docs/images/228.png differ diff --git a/docs/images/229.png b/docs/images/229.png new file mode 100644 index 0000000..e30244e Binary files /dev/null and b/docs/images/229.png differ diff --git a/docs/images/230.png b/docs/images/230.png new file mode 100644 index 0000000..01fe8c4 Binary files /dev/null and b/docs/images/230.png differ diff --git a/docs/images/231.png b/docs/images/231.png new file mode 100644 index 0000000..de41d16 Binary files /dev/null and b/docs/images/231.png differ diff --git a/docs/images/232.png b/docs/images/232.png new file mode 100644 index 0000000..235c1a1 Binary files /dev/null and b/docs/images/232.png differ diff --git a/docs/images/233.png b/docs/images/233.png new file mode 100644 index 0000000..810da97 Binary files /dev/null and b/docs/images/233.png differ diff --git a/docs/images/234.png b/docs/images/234.png new file mode 100644 index 0000000..0c1538d Binary files /dev/null and b/docs/images/234.png differ diff --git a/docs/images/235.png b/docs/images/235.png new file mode 100644 index 0000000..e57b395 Binary files /dev/null and b/docs/images/235.png differ diff --git a/docs/images/236.png b/docs/images/236.png new file mode 100644 index 0000000..1212f0f Binary files /dev/null and b/docs/images/236.png differ diff --git a/docs/images/237.png b/docs/images/237.png new file mode 100644 index 0000000..07c83dc Binary files /dev/null and b/docs/images/237.png differ diff --git a/docs/images/238.png b/docs/images/238.png new file mode 100644 index 0000000..01fec07 Binary files /dev/null and b/docs/images/238.png differ diff --git a/docs/images/239.png b/docs/images/239.png new file mode 100644 index 0000000..444089a Binary files /dev/null and b/docs/images/239.png differ diff --git a/docs/images/240.png b/docs/images/240.png new file mode 100644 index 0000000..4b9f44d Binary files /dev/null and b/docs/images/240.png differ diff --git a/docs/images/241.png b/docs/images/241.png new file mode 100644 index 0000000..bce8eb9 Binary files /dev/null and b/docs/images/241.png differ diff --git a/docs/images/242.png b/docs/images/242.png new file mode 100644 index 0000000..3bd8a0e Binary files /dev/null and b/docs/images/242.png differ diff --git a/docs/images/243.png b/docs/images/243.png new file mode 100644 index 0000000..65a5bda Binary files /dev/null and b/docs/images/243.png differ diff --git a/docs/images/244.png b/docs/images/244.png new file mode 100644 index 0000000..9e7aa38 Binary files /dev/null and b/docs/images/244.png differ diff --git a/docs/images/245.png b/docs/images/245.png new file mode 100644 index 0000000..6d35053 Binary files /dev/null and b/docs/images/245.png differ diff --git a/docs/images/62.png b/docs/images/62.png index c99602f..602259b 100644 Binary files a/docs/images/62.png and b/docs/images/62.png differ diff --git a/docs/images/65.png b/docs/images/65.png new file mode 100644 index 0000000..5c009a7 Binary files /dev/null and b/docs/images/65.png differ diff --git a/docs/images/66.png b/docs/images/66.png new file mode 100644 index 0000000..9491cbf Binary files /dev/null and b/docs/images/66.png differ diff --git a/docs/images/67.png b/docs/images/67.png new file mode 100644 index 0000000..2fb9f10 Binary files /dev/null and b/docs/images/67.png differ diff --git a/docs/images/68.png b/docs/images/68.png new file mode 100644 index 0000000..66a4bb6 Binary files /dev/null and b/docs/images/68.png differ diff --git a/docs/images/69.png b/docs/images/69.png new file mode 100644 index 0000000..45aacbf Binary files /dev/null and b/docs/images/69.png differ diff --git a/docs/images/70.png b/docs/images/70.png new file mode 100644 index 0000000..9750e43 Binary files /dev/null and b/docs/images/70.png differ diff --git a/docs/images/71.png b/docs/images/71.png new file mode 100644 index 0000000..22cc6ab Binary files /dev/null and b/docs/images/71.png differ diff --git a/docs/images/72.png b/docs/images/72.png new file mode 100644 index 0000000..fc5a7dd Binary files /dev/null and b/docs/images/72.png differ diff --git a/docs/images/73.png b/docs/images/73.png new file mode 100644 index 0000000..2e76595 Binary files /dev/null and b/docs/images/73.png differ diff --git a/docs/images/74.png b/docs/images/74.png new file mode 100644 index 0000000..b208ad3 Binary files /dev/null and b/docs/images/74.png differ diff --git a/docs/images/75.png b/docs/images/75.png new file mode 100644 index 0000000..05d663d Binary files /dev/null and b/docs/images/75.png differ diff --git a/docs/images/76.png b/docs/images/76.png new file mode 100644 index 0000000..e958066 Binary files /dev/null and b/docs/images/76.png differ diff --git a/docs/images/77.png b/docs/images/77.png new file mode 100644 index 0000000..8e02e57 Binary files /dev/null and b/docs/images/77.png differ diff --git a/docs/images/78.png b/docs/images/78.png new file mode 100644 index 0000000..95fe8c9 Binary files /dev/null and b/docs/images/78.png differ diff --git a/docs/images/79.png b/docs/images/79.png new file mode 100644 index 0000000..cadea47 Binary files /dev/null and b/docs/images/79.png differ diff --git a/docs/images/80.png b/docs/images/80.png new file mode 100644 index 0000000..2914da3 Binary files /dev/null and b/docs/images/80.png differ diff --git a/docs/images/81.gif b/docs/images/81.gif new file mode 100644 index 0000000..da18254 Binary files /dev/null and b/docs/images/81.gif differ diff --git a/docs/images/82.gif b/docs/images/82.gif new file mode 100644 index 0000000..fde1b3a Binary files /dev/null and b/docs/images/82.gif differ diff --git a/docs/images/83.gif b/docs/images/83.gif new file mode 100644 index 0000000..b10aee0 Binary files /dev/null and b/docs/images/83.gif differ diff --git a/docs/images/84.gif b/docs/images/84.gif new file mode 100644 index 0000000..fde1b3a Binary files /dev/null and b/docs/images/84.gif differ diff --git a/docs/images/85.gif b/docs/images/85.gif new file mode 100644 index 0000000..fc1ea01 Binary files /dev/null and b/docs/images/85.gif differ diff --git a/docs/images/86.gif b/docs/images/86.gif new file mode 100644 index 0000000..b3a38ca Binary files /dev/null and b/docs/images/86.gif differ diff --git a/docs/images/87.gif b/docs/images/87.gif new file mode 100644 index 0000000..6adf75e Binary files /dev/null and b/docs/images/87.gif differ diff --git a/docs/images/88.gif b/docs/images/88.gif new file mode 100644 index 0000000..966a066 Binary files /dev/null and b/docs/images/88.gif differ diff --git a/docs/images/89.gif b/docs/images/89.gif new file mode 100644 index 0000000..12a4bb9 Binary files /dev/null and b/docs/images/89.gif differ diff --git a/docs/images/9.png b/docs/images/9.png index 783d89e..565c714 100644 Binary files a/docs/images/9.png and b/docs/images/9.png differ diff --git a/docs/images/90.gif b/docs/images/90.gif new file mode 100644 index 0000000..2a64ef3 Binary files /dev/null and b/docs/images/90.gif differ diff --git a/docs/images/91.gif b/docs/images/91.gif new file mode 100644 index 0000000..13fb4a9 Binary files /dev/null and b/docs/images/91.gif differ diff --git a/docs/images/92.gif b/docs/images/92.gif new file mode 100644 index 0000000..1917583 Binary files /dev/null and b/docs/images/92.gif differ diff --git a/docs/images/93.png b/docs/images/93.png new file mode 100644 index 0000000..05406b1 Binary files /dev/null and b/docs/images/93.png differ diff --git a/docs/images/94.png b/docs/images/94.png new file mode 100644 index 0000000..82d9b5f Binary files /dev/null and b/docs/images/94.png differ diff --git a/docs/images/95.png b/docs/images/95.png new file mode 100644 index 0000000..54187d1 Binary files /dev/null and b/docs/images/95.png differ diff --git a/docs/images/96.png b/docs/images/96.png new file mode 100644 index 0000000..c98ffc3 Binary files /dev/null and b/docs/images/96.png differ diff --git a/docs/images/97.png b/docs/images/97.png new file mode 100644 index 0000000..dc533ea Binary files /dev/null and b/docs/images/97.png differ diff --git a/docs/images/98.png b/docs/images/98.png new file mode 100644 index 0000000..11c5735 Binary files /dev/null and b/docs/images/98.png differ diff --git a/docs/images/99.png b/docs/images/99.png new file mode 100644 index 0000000..4fe9eba Binary files /dev/null and b/docs/images/99.png differ