Charles Darwin created the concept ,“Survival of the Fittest”, where those who are able to adapt and perform the best are deemed fit and are given the opportunity to survive and reproduce. However, in the wild, organisms sometimes face changes, and the organisms that previously seemed unfit, prove to be fit in other scenarios. This project investigates how periodically changing fitness functions can create artificial evolution in the Wolfram cellular automaton framework. Traditional evolutionary models rely on set fitness goals, but biological systems are shaped by fluctuating environments. Several rules are modeled and put through different mutations to enhance their fitness. These mutated rules are then compared to fitness targets to see how well the adaption improved the rules fitness. By constructing time dependent fitness targets, such as gradual growth and sudden jumps, this project aims to simulate fitness selection pressures.
Introduction
Introduction
Cellular automata are simple rule-based systems that can generate remarkably complex behavior from simple components. These automata operate by repeatedly applying a local update rule to an array of cells. Each rule impacts how a cell’s state changes based on the states of its neighboring cells. What makes these systems particularly interesting is that, despite their simplicity, they can model processes found in natural and biological systems, such as growth, movement, and evolution.
This project explores how mutation and selection can be simulated in the Wolfram Evolution Model by periodically adjusting the criteria used to evaluate fitness. Rather than fixing a static evolutionary goal, a set of step-dependent fitness functions that shift over the course of the simulation are created. The goal is to eventually evolve cellular automata that not only perform well under a fixed condition but can adapt to dynamically changing selective pressures.
This project explores how mutation and selection can be simulated in the Wolfram Evolution Model by periodically adjusting the criteria used to evaluate fitness. Rather than fixing a static evolutionary goal, a set of step-dependent fitness functions that shift over the course of the simulation are created. The goal is to eventually evolve cellular automata that not only perform well under a fixed condition but can adapt to dynamically changing selective pressures.
Implementing Random Rule Mutation Function
Implementing Random Rule Mutation Function
Mutation is a key mechanism of evolution, which introduces variation that allows selection to operate. In this system, we simulate mutation by randomly altering the rule of a cellular automaton. This involves modifying the update rules that define how each cell behaves based on its neighborhood. The function constructed allows us to mutate either arbitrary or symmetric rule sets, with control over the number of mutations introduced.
The following three blocks of code come from Stephen Wolfram’s article titled, “Why Does Biological Evolution Work? A Minimal Model for Biological Evolution and Other Adaptive Processes”:
Defining the symmetric indexing of neighborhood configurations:
In[]:=
SymIndexMap[k_, r_] := SymIndexMap[k, r] = With[{tups = Tuples[Reverse[Range[0, k - 1]], 2*r + 1]}, Sort[Catenate[MapIndexed[{Position[tups, #1][[1,1]], #2[[1]]} & , GatherBy[tups, Sort[{#1, Reverse[#1]}] & ], {2}]]]]
Extracting upward and downward symmetric halves of the rule table:
In[]:=
SymUp
SymUp
SymDown
SymDown
Taking a rule number and randomly alters part of its update table, potentially modifying its behavior significantly:
In[]:=
RandomRuleMutation[{rn_, k_, r_}, many_Integer:1, OptionsPattern[{"Symmetric" -> False}]] := Module[{up, down, cases},{up, down} = If[TrueQ[OptionValue["Symmetric"]], {SymUp[k, r], SymDown[k, r]}, {All, All}]; cases = IntegerDigits[rn, k, k^(2*r + 1)][[down]]; ({FromDigits[#1[[up]], k], k, r} & )[MapAt[Mod[#1 + RandomInteger[{1, k - 1}], k] & , cases, List /@ RandomSample[1 ;; Length[cases] - 1, many]]]]
Visualization of rule 45:
In[]:=
ArrayPlot[CellularAutomaton[{45,2,2},{{1},0},100]]
Out[]=
A mutated rule of rule 45 is produced:
In[]:=
RandomRuleMutation[{45,2,2}]
Out[]=
{16777261,2,2}
Visualization of a mutated rule which stemmed from rule 45:
In[]:=
ArrayPlot[CellularAutomaton[RandomRuleMutation[{45,2,2}],{{1},0},100]]
Out[]=
Fitness function to determine lifespan of rule
Fitness function to determine lifespan of rule
In biological evolution, fitness is often linked to survival, with the organisms that persist longer in their environments, end up more likely to reproduce. To simulate this idea computationally, we define a lifespan-based fitness function. This function calculates how long a given cellular automaton remains “alive” before it dies out or stabilizes. If the system continues indefinitely without dying, it is considered infinite and assigned a penalized fitness value of -1.
Returns lifespan for any cellular automata rule:
In[]:=
fitness[rule_,lifetime_] :=Module[{arr = CellularAutomaton[rule, {{1},0}, 500], positions},positions = Position[arr,_?(#!=0&)];If[positions === {}, Return[0]];If[Last[positions][[1]] - First[positions][[1]] + 1 > lifetime, Return[-1]];Last[positions][[1]] - First[positions][[1]] + 1]
This function works by checking every cell in the CA’s space-time evolution and recording when any non-zero (active) state appears. It then calculates the difference in timestep between the first and last active states, returning that span as the lifespan. If the automaton continues producing active states for the full given time, it is considered potentially infinite and assigned a score of -1.
2-timestep lifespan:
In[]:=
fitness[{853845652,2,2},500]
Out[]=
2
60-timestep lifespan:
In[]:=
fitness[{953845652,2,2},500]
Out[]=
60
308-timestep lifespan:
In[]:=
fitness[{977937844,2,2},500]
Out[]=
308
(Considered) Infinite timestep lifespan (Returns -1 if considered infinite)
In[]:=
fitness[{623143632,2,2},500]
Out[]=
-1
Creating Mutated Rules to Adapt to a Target Lifetime
Creating Mutated Rules to Adapt to a Target Lifetime
One of the key goals in this project is to evolve cellular automata that meet a specific fitness target. Rather than manually identifying rules that survive for exactly 500 steps, we construct an automated mutation and selection loop. This loop takes a given rule and iteratively mutates it, attempting to maximize its lifespan without letting it grow infinitely.
However, in practice, finding a rule that meets the target precisely within a restricted time construct is extremely difficult. Many mutations tend to push rules into either dying quickly or becoming infinite. As such, this section attempts the search process but also acknowledges the challenges and limitations.
However, in practice, finding a rule that meets the target precisely within a restricted time construct is extremely difficult. Many mutations tend to push rules into either dying quickly or becoming infinite. As such, this section attempts the search process but also acknowledges the challenges and limitations.
Mutates rule until finding a fit lifespan:
findFitRule[{rule_,k_,r_}, n_]:= Module[{bestRule, bestScore},bestRule = {rule, k, r};bestScore = fitness[{rule, k, r}, 500];compRules[curr_]:= Module[{currScore = fitness[curr, 500]},currScore = If[currScore >= 500, -1,currScore];RandomRuleMutation[If[currScore > bestScore, bestScore = currScore; bestRule = curr, bestRule]]];Nest[compRules, {rule, k, r}, n];{bestRule, bestScore}];
findFitRule begins with an initial rule and finds its lifespan. It then applies a series of mutations using RandomRuleMutation, attempting to improve the rule’s fitness. After each mutation, the function checks whether the new rule has a longer lifespan than the best one found so far. If so, it updates the best rule accordingly. The process continues for a fixed number of iterations, or until a rule is found that reaches the target lifespan of 500 steps.
Runs findFitRule to try to evolve a rule with lifespan 60 toward the target of 500:
In[]:=
findFitRule[{953845652,2,2},100]
Out[]=
{{953845652,2,2},60}
This output reflects the mutation loop trying to find better rules (with higher lifespans) but in this input of 100 attempts, all the mutated rules either failed to improve or resulted in infinite growth. In practice, it could take hundreds or thousands of iterations to find a rule that precisely hits 500 without being unbounded.
Creating Functions to Adapt Mutated Cellular Automata
Creating Functions to Adapt Mutated Cellular Automata
In this section, a new method that allows the cellular automaton to adapt over time by editing its own active states, rather than changing the entire rule itself was created. The main goal was to extend how long the automaton stays alive by making simple mutations to its final row. By making point mutations to one state of a cellular automata, it impacted the rest of the body including both lifespan and width.
Finds lifespan of rule from any initial state:
In[]:=
fitness2[rule_, initialState_] := Module[{arr = CellularAutomaton[rule, {initialState, 0}, 500], positions},positions = Position[arr, _?(#!=0&)];If[positions === {}, Return[0]];Last[positions][[1]] - First[positions][[1]] + 1];
fitness2 calculates how many steps the automaton lasts when starting from a given initial state. This is similar to the earlier fitness function, however instead of using an initial condition of a single 1 in the center, it allows any starting row so that it could track how long a custom evolving pattern survives, rather than just a randomly mutated one.
Makes single point mutation to specified row:
In[]:=
mutateRow[row_] := Module[{positions = Flatten[Position[row, 1]], index, r = row},index = RandomSample[Range[positions[[1]] + 1, positions[[-1]]-1], 1][[1]];r[[index]] = If[r[[index]] == 1, 0, 1];r];
mutateRow takes the final row of a matrix and flips a single cell from 1 to 0 or 0 to 1. This single point mutation acts like a random mutation to the organism’s current state, with the potential to improve its future survival.
Combines mutated and before mutation matrices:
spliceEvolutons is used to combine two evolution matrices, one from before a mutation and one from after. Since the mutated row might shift the pattern horizontally or have a different widths, this function pads both matrices so they line up properly. It calculates the difference between where the active parts start and adjusts accordingly. It also makes sure the width of both matrices match by padding the shorter one with zeros. Once they’re aligned, it joins them together into one continuous evolution, forming the new, adapted automata.
Combines all functions to mutate cellular automata to reach target lifespan:
extendLifespan is where the actual adaptation happens. It starts by running the rule normally and then save the result as an initial matrix. Then, it creates a small mutation in the final row and runs the automaton again using that new row as a starting point. If this mutated evolution lives longer, the two parts are merged using the spliceEvolutions function. This cycle repeats by mutating, evolving, and then combining until the automaton survives for the target number of steps.
Generates cellular automata with lifespan of 500, starting from initially mutated 60 lifetime rule:
Constructing a Dynamic Fitness Function
Constructing a Dynamic Fitness Function
In natural evolution, selective pressures often change over time. Organisms that thrive in one generation may struggle in the next if the environment changes. To simulate this idea, a dynamic fitness target was created, that varies periodically as the simulation progresses. This allows us to model changing evolutionary goals, testing whether cellular automata can adapt over time rather than optimizing for a single static condition.
In this section, cellular automata rules were tested to see if they could follow a gradually increasing width target over time . The idea is to see if the adapted automaton expands in width step-by-step, at a consistent rate, instead of growing too fast or dying out.
In this section, cellular automata rules were tested to see if they could follow a gradually increasing width target over time . The idea is to see if the adapted automaton expands in width step-by-step, at a consistent rate, instead of growing too fast or dying out.
Returns width of mutated automata and target width for automata:
gradualWidthFitness runs the given rule for a set number of steps and tracks how wide the active part of the cellular automaton is at each step. It then compares that actual width to a target width that grows steadily over time, based on the specified growth rate:
Plots line plot of the original mutated rule without adaption:
This graph shows how without any adaptation, the mutated rule can not meet or get close to the target width after reaching its maximum lifespan. The rule dies out at its regular lifespan (60) and the target width continues to grow.
Plots a line plot of mutated rule with adaption:
Unlike the unadapted rule, this rule initially expands in width, but then slowly begins falling below the red line as it tries to keep up, showing how although the adaption made could not fully keep up with the growing target, it did significantly better than the unadapted rule.
Gives a close-up of how the pattern develops over the first few steps:
Through this visual, it is clear that the rule is growing and expanding in width in small triangle like shapes before it meets it maximum lifetime and width. It then dies out at around the set target lifespan of 500.
This output shows the points plotted by the cellular automata and the target line on the line plot, showing the drastic differences between some of the ideal and actual points :
Adding a Sudden Change in Target Width (Shock)
Adding a Sudden Change in Target Width (Shock)
This part of the project, was meant to simulate a sudden shift in environmental conditions. Instead of a fitness target that increases smoothly over time, this is a version where the target width increases slowly at first, but then jumps up at a specific moment. This stimulates sudden changes in the environment, where organisms have to adapt quickly to keep up with the fluctuating system.
Returns width of mutated automata and target width with jump in steps:
suddenWidthFitness works by running a rule for a number of steps and measuring the width of activity over time, just like in the gradual fitness section. However, at the given jumpStep, the target width increases sharply by a fixed jumpAmount. This simulates a system where the rules must suddenly adapt to meet new demands.
Plots line plot of mutated rule without adaptation:
In this graph, the cellular automata does not make an adaption, and therefore is not able to grow with the growing width and dies out before the sudden growth in width is implemented.
Plots line plot of mutated rule with adaptation:
However, unlike the unadapted rule, the adapted rule is able to sustain its growth until the shock is added to the system. The automata then fails to meet the new target but continues its lifespan.
This output shows the points plotted by the cellular automata and the target line on the line plot, showing the drastic differences between some of the ideal and actual points, especially after the target width jumps 30 in width:
Visualization of automata that initially expanded in width, but failed once the sudden jump was added:
Comparing Different Cellular Automata
Comparing Different Cellular Automata
In an environment, there are organisms that are considered “more fit” than others, but what happens when these “fit” organisms are compared to those previously deemed unfit? In this section of the project, I will be comparing different rules with different lifespans to see which mutations and adaptations proved to be more fit through different fitness functions. Rules with lifespans of 60-308 will be compared to see which rules react and adapt best to sudden and gradual width changes.
Plots (adapted) 60 lifespan automata’s width vs target width:
Plots (adapted) 65 lifespan automata’s width vs target width:
Plots (adapted) 90 lifespan automata’s width vs target width:
Plots (adapted) 308 lifespan automata’s width vs target width:
These plots show different rules reacting to the sudden change in target width. While the adapted 60 lifespan rule could not increase in width during the jump, the 65 lifespan rule was able to adapt and increase in width. Not only did it perform better in the width aspect, but it also adapted to a longer lifetime of 700 while the other rule only adapted to a new lifetime of 540. Similarly, the adapted 90 lifespan rule initially grew with the target width, however after the jump, it began to perform much lower than the target. Finally, when we adapt the initially fittest rule, with an original lifetime of 308, it does worse than the 65 lifespan rule and performs more similarly to the 60 lifespan rule. A rule that was once the fittest, underperformed against a rule considered “not fit”. These findings can be seen reflected in any environment, where survival of the fittest returns different results based off of the changes made in a setting.
Conclusion
Conclusion
This project explores how cellular automata can evolve under different fitness conditions. I started by defining a rule mutation function and measuring how long each rule survives. Then, I created a function to mutate rules until they reached a certain lifespan considered “fit”, such as 500 steps. Later, I tested other types of fitness, such as gradual width growth and sudden target changes. These different tests helped me see how flexible or limited each rule is when the environment changes. Rules that had initially been less fit than other rules in a lifespan test, proved to adapt better and perform better in width fitness tests. By mutating a series of rules, each rule was given an opportunity to increase its fitness before being tested in different scenarios where the adaption would prove either beneficial or ineffective. The visual plots helped see exactly how each rule behaved and why it succeeded or failed. Overall, this project gives a better understanding of how selection pressure and mutations affect behavior over time in a computation system, which can be seen mirrored in a physical environment.
Future Directions
Future Directions
In the future, I want to add true adaptation to changing fitness. Right now, the code applies the fitness functions, but the cellular automaton does not evolve during the run, therefore this code can seem like a model of the aftermath of a environmental fluctuation. In the future, I want to mutate the rule as the environment changes and see if it can keep up. I would also be interested in testing out other fitness functions such as symmetry and possibly changing the fitness function every step to stimulate environmental factors to a larger extent. Lastly, in the future, I would like to expand my project to work with larger state spaces greater than two, to see how different automata patterns and initial rules interact and behave with changing fitness functions.
Acknowledgements
Acknowledgements
Thank you to my mentor, Tate Allen for guiding me through this project and giving me ideas for implementing certain functions. This project was greatly influenced by his support, especially when I was stuck, or unaware of what steps to take next. His constant checking in and reminders helped me stay on track and get my project done. I also wanted to say thank you to Kevin Toren who helped me debug and write many of my functions.
References
References
1
.Wolfram (2024), “Why Does Biological Evolution Work? A Minimal Model for Biological Evolution and Other Adaptive Processes.” https://writings.stephenwolfram.com/2024/05/why-does-biological-evolution-work-a-minimal-model-for-biological-evolution-and-other-adaptive-processes/#the-model
2
.Wolfram (2024), “Foundations of Biological Evolution: More Results & More Surprises.” https://writings.stephenwolfram.com/2024/12/foundations-of-biological-evolution-more-results-more-surprises/#the-macroscopic-flow-of-evolution
CITE THIS NOTEBOOK
CITE THIS NOTEBOOK
Periodically changing fitness functions in the Wolfram Evolution Model
by Hansika Sudunagunta
Wolfram Community, STAFF PICKS, July 10, 2025
https://community.wolfram.com/groups/-/m/t/3500376
by Hansika Sudunagunta
Wolfram Community, STAFF PICKS, July 10, 2025
https://community.wolfram.com/groups/-/m/t/3500376