In this post about a minimal model for biological evolution, Stephen Wolfram used a genetic algorithm to evolve cellular automata to behave in certain ways, like running for a certain amount of time. In this post also by Wolfram, a somewhat similar minimal system was used to model machine learning. In that second post, there was a system like a cellular automaton, except that it was more complicated in that instead of just evolving the same rule that applies on each step like a cellular automaton, he made something more like a neural network where each step of the system, like each layer in a neural network, has a different rule, with things he called “rule arrays”. So the system was a bit more complex, but instead of trying to get the system to hit one target like how can one input to the cellular automaton run for a certain output number of steps, he tried to approximate functions where different inputs give different outputs.
I feel like something was missing in this though, a system in between these two. Instead of evolving a more complex “rule array” to match some function, or a simpler cellular automaton to go from one input to one output, what if we tried to go big and see if we can actually get the simpler cellular automaton to match the function, in a sense an even simpler model for machine learning? I have had good success with this in approximating at least simpler functions, and attempts and ideas for matching more complex ones.
Results
Results
Let’s try and get it to match this function:
In[]:=
piecewiseRange={2,13};
In[]:=
Plot[piecewise[x],{x,Splice[piecewiseRange]},Exclusions->None]
Out[]=
The inputs to the function will be represented as a line of length x 1’s as an initial condition. To simplify things, we’ll count it as a 0 if it grows for some finite lifetime, and 1 if it appears to grow forever (in reality we have to set some cutoff when deciding how long it grows is enough. In the following specific case 50 steps seems to be enough). It should match this sequence of values when seeing if it grows forever or not:
In[]:=
Table[piecewise[i],{i,##}]&@@piecewiseRange
Out[]=
{0,0,1,1,0,0,1,1,1,1,0,0}
Here we try and match the function / sequence:
In[]:=
SeedRandom[2];
In[]:=
breakthroughs=ResourceFunction["AdaptiveCellularAutomaton"][<|"Rule"->{0,3,1},"Depth"->1000,"FitnessFunction"->piecewiseFunction|>,"Breakthroughs"];
In[]:=
TableArrayPlotCellularAutomaton[Last[breakthroughs]["Rule"],{Table[1,i],0},50],ColorRules->["Colors"],{i,##}&@@piecewiseRange
Out[]=
,
,
,
,
,
,
,
,
,
,
,
The first seed I tried matched preety well, but as you can see, by only the 2nd I tried, it matched perfectly! Interestingly it seems to have done even better at this kind of thing than Wolfram’s rule arrays.
A plot showing the fitness increasing over time:
A plot showing the fitness increasing over time:
In[]:=
SeedRandom[2];
In[]:=
ListStepPlot[ResourceFunction["AdaptiveCellularAutomaton"][<|"Rule"->{0,3,1},"Depth"->1000,"FitnessFunction"->piecewiseFunction|>,All,"Fitness"]]
Out[]=
Next I tried to get it to match a Sin curve, and despite trying things like offsetting the sin curve by a certain amount from the y axis, I wasn’t able to get it to match well at all. Here’s a typical example where it does tries to match a certain sin curve offset from the y axis by 40, but it just tries to grow for about 40 steps for each input:
In[]:=
sinRange={10,40};
In[]:=
SeedRandom[1];
In[]:=
breakthroughs=With[{range=sinRange},ResourceFunction["AdaptiveCellularAutomaton"][<|"Rule"->{0,4,1},"Depth"->200,"FitnessFunction"->Function[fitness[#,10Cos[#/4+(4Pi)/4]+40&,range,100]]|>,"Breakthroughs"]];
In[]:=
TableArrayPlotCellularAutomaton[Last[breakthroughs]["Rule"],{Replace[Table[1,i],{}->{0}],0},100],ColorRules->["Colors"],{i,##}&@@sinRange
Out[]=
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
Perhaps there are other tweaks that could be tried, like running the evolution for more steps. Another idea would be to run a different rule at each step, like Wolfram’s “vertical rule arrays”, but to evolve each rule on each step. Perhaps this would give enough flexibility to match more complex functions, but that’s to be tried at another time.
Code
Code
Run this code before the code above to reproduce those results.
In[]:=
SeedRandom[Method->"ExtendedCA"];
In[]:=
piecewise=Piecewise[{{0,#<4},{1,#<6},{0,#<8},{1,#<12}}]&;
In[]:=
cALifetime[rule_,init_List,steps_Integer]:=Length@NestWhileList[CellularAutomaton[rule],init,!ContainsOnly[First[#],{0}]&,1,steps]-1
In[]:=
functionDifference[f1_,f2_,{range__}]:=Mean@Apply[Abs@*Subtract,Table[#[i],{i,range}]&/@{f1,f2}]
CITE THIS NOTEBOOK
CITE THIS NOTEBOOK
Evolving cellular automata to match functions
by Eric Parfitt
Wolfram Community, STAFF PICKS, July 27, 2025
https://community.wolfram.com/groups/-/m/t/3519468
by Eric Parfitt
Wolfram Community, STAFF PICKS, July 27, 2025
https://community.wolfram.com/groups/-/m/t/3519468