Hey all, Jonathan here.
Today, I'm going to show an example
of how you might go about making your very own elementary cellular automata
hereafter referred to as ECA
in NetLogo.
This exercise in particular asks us to
homebrew an asynchronous ECA
which you'll see happens quite naturally in this program,
since NetLogo, by default, likes to call agents in random order.
Alrighty, so,
as you saw from this unit,
ECAs being one-dimensional
are simulated row by row,
one row at a time.
First time step is simulated on the top row.
Second time step plays out in the second row.
Third iteration on third row,
etc., etc.
So it might behoove us to set up the coordinate system
in such a way that facilitates this logic.
Here, notice I set the origin at the top,
so that the first row counts as py coordinate of zero
and counts down minus one, minus two, minus three, and so on
from there.
Due to starting on the top left corner,
px coordinate integers also can be (inaudible) count
from zero, one, two three, etc.
which could also be nice if you later decide
to expand the width of the rows.
Now for the big questions.
You want to ask:
What are the essential features of the ECA model?
Fayal (???) or automatas are made of cells, of course,
which in NetLogo codes, are the patches.
The main properties of patches in the ECAs
are the current states, of which they have zero or one,
and neighborhood configurations,
used to determine the cell's subsequent state.
Another important feature of the model are the rules,
which, as you know,
is simply a list of states telling the cells
which neighborhoods produce which states.
As we have seen from the various Wolfram classes,
different rules can produce radically different behaviors
on the large scale,
while individual cells can have their own states,
and see different neighborhoods,
the rule set itself isn't specific to the individual cells,
but rather the model as a whole.
So, I list is as a global variable.
We also don't want to forget the obligatory Setup and Go procedures
to make the model run and stuff,
so I'm putting that in right now.
For the Setup, it's generally good practice to write
"clear-all",
since we want to be able to reset these models
and replicate results independently,
without interference from previous runs.
Particular to the way I set up this model,
I'm also resetting such initializing ticks,
since ticks can be useful for keeping track
of which time step we're at.
Finally, we want the patches to sort themselves,
to an initial random configuration
so I'm going to ask the patches
albeit with a specific qualifier,
patches with pycor equal zero, to start with,
since, as you know,
we want cells on the top row in the beginning.
Now these patch cells just seem to have
those states randomly set.
Recall that each cell can be in one of two states: zero or one.
So for our current purposes,
we want a random decider variable,
with an equal chance of returning
a state of zero or one to the cells.
I'm going to call this one "draw"
just because I like the analogy of drawing cards
although naming isn't really consequential here.
So, let the temporary variable, "draw", be random float one,
which is just any random quantity between zero and one
that the computer can conceive of.
So for this random "draw",
we can say if a number picked between zero and one
is below 0.5, which as you know, is a fifty percent chance,
then set "state" zero,
and you want an "else" clause in there too,
so if the "draw" number happens to be bigger than .5,
which occurs the other fifty percent of the time,
then set "state" to 1 instead.
Keep in mind that "state" is a variable that's stored inside the patch,
that currently is not visible to the human user.
So we want to mark up
through a new functional procedure called changeColor,
which is pretty straight-forward.
So, to change color,
it might be convenient to use another "ifelse" statement.
If the cell state is zero,
we get one color.
Otherwise, if it is a different state,
ergo one,
we get a different color
to distinguish these cells from each other.
Beyond that, it's obviously up to personal taste
which colors we want to use.
I'm going to say that white cells are zeros,
that is, set pcolor to white, if patch's state is zero.
Conversely, if the state is one, let's make it look green instead.
I personally like using these lighter colors for this,
mostly because it helps us separate the rows of cells that are done
from the darker ones that aren't done yet.
With that, we end the procedure, and check that nothing is broken yet.
I'm going to put up some buttons, just to test the model so far,
and see if we're on the right track.
We have a Setup button to run our setup procedures,
and a Go button.
The Go button shouldn't do anything just yet,
but it will eventually need to be there.
Setting up the model now.
See, so now we have about half white cells and half green cells in the starter row,
reflecting our fifty percent chance of the cells turning white,
and fifty percent chance the cell is seeded green
in random sequence.
Initial conditions are re-set each time using this procedure,
which you see,
are completely random each time,
but roughly speaking, you'll have fifty-fifty greens and whites
on average.
Go, surprise, does nothing yet, because I didn't put anything
out in the Go procedure, yet.
Next thing,
we want the cells to actually interact with and respond to cells around them,
and change according to specific rules,
regarding their neighborhood configuration.
Right?
So far, I haven't defined what the rule sets are.
So just to quickly see that in action,
I'm going to initialize the model for now,
with one of my favorite rule sets.
This is one of those rules with the interesting properties of the
somewhat orderly yet chaotic behavior characteristic of, you know,
complex structures.
With this rule set,
as a temporary place-holder,
let's specify the Go procedure,
where the CA rules come into play.
Remember, the rules dictate which state
the cells change to for each neighborhood configuration,
which require each patch cell to detect the
state of their two immediate neighbors,
as well as their own state.
Sticking to the top patches for now,
we're going to have each patch update
and record its neighborhood configuration.
That is, the three state combination that includes the cell's left neighbor,
itself, and its right neighbor.
Since neighborhood is defined by a
combination of cell states,
you want to create a list,
which is just another vector.
In this vector, you want to include as the first entry,
the binary state of the left cell.
So a little bit of NetLogo vocabulary for you guys.
"patch-at" refers to the cell patch
in a position relative to the current cell calling it.
The first number is the x distance
to the left, or right.
The second number, y distance,
up or down.
So patch-at -1 0
refers to the cell to the immediate left of the current cell.
Now, add in the state of the current patch
calling this procedure
which is just "state" by default
and finally the state on the right cell,
patch-at 1 0
because now we're looking to the right.
With that, we have a cogent command block,
which I'll just close up for now.
Whoops, my bad,
So, to recap,
each cell is looking to its left, and right,
and within itself,
for the current set of states
updating at each time step.
The cells are hence merely storing information each time,
not actually doing anything with it,
and changing their states in accordance.
Hence, the model's apparent behavior hasn't changed yet.
As you can see, clicking these buttons,
Obviously there are several factors here that are required to make
the model run properly.
For example, we can't see the manifestation
of the cell's new state
without a changeColor procedure
But more fundamentally,
the model is currently missing an application of the rules,
when they supposedly direct cell behavior.
In fact,
the initialist rule vector I picked out
isn't really doing anything currently.
So we need a way for the cells to refer to those rules in the vector.
Now, there's many, many ways to program this part,
and as I'm sure the computer scientists among you will quickly realize,
the one I'm showing is by no means the most efficient.
But some of you will at least hopefully appreciate
the syntactic simplicity of my approach here.
So far, we have the patches looking at the neighborhood states
and keeping track of them at each time step.
Now we want them to update their state
as a function of these current configurations.
So we're going to have to have our patches check these states,
and look up the appropriate corresponding response state from the rule vector.
For instance, ask the patch if its neighborhood configuration
was zero zero zero.
This corresponds to a neighborhood of all white cells
on the left, center, and right cells here.
If the cell in question sees this state,
we want this cell to set the state corresponding to item zero
from the rule set list.
This causes the focal patch to change its state to the first item in the rule vector
While we know off-hand that this would
currently to set the state to zero
we ultimately want the patch to respond to any arbitrary binary rule vector
the user specifies.
That's the one above I made up.
Checking for the next possible configuration,
zero zero one,
ergo, a white, white, green,
we want the cell to change states
to the second output state on the list,
item 1.
Similarly the third configuration corresponds to item 2.
Fourth configuration, three,
and, you get the idea.
In an effort to save some time,
I'm just going to copy and paste here,
to set up the remaining six possible permutations.
Again, this is not the most expedient algorithm.
For you code-savvy folk,
there's definitely ways, if we use "foreach" loops,
that count up and generate any full set of neighborhood configurations
under Wolfram's ordering scheme,
to align those configurations with the rule vector.
We're keeping it simple right now,
so at least you have a template to play around with.
In the meantime,
I'm just checking and filling in the remaining possible configurations
in the cell's next state,
corresponding with the respective items from the rule set.
And looks good.
Quick Setup and Go.
Now we see some interaction between cells in the first row.
Notice how every time I'm doing this,
it's updating the patches
based on their prior neighborhoods,
and even without us doing the drop down CA runs,
we're eventually going to get in the next couple of code revisions,
one might formulate hypotheses,
about the pattern upon casual observation.
Is there a specific set of repetitive cycles present?
Or the emergence of more complex tortuous meta cycles,
characteristic of chaos?
Or is it mostly random noise,
playing tricks on our brain?
However, to see that bigger picture time evolution in one visualization,
we actually want to enable the target cells to drop down,
so we can see this row get filled,
this row,
this one,
and so forth.
Happily for us,
NetLogo already has its own native means of keeping track of time steps
called "tick".
As you should already be familiar with,
"tick" is a built-in function that tallies the total number of times
its passed through the code
keeping count of the current iteration we're at.
So this is what our coordinate scheme from the beginning comes in handy.
Since both the ticks counter and this p-y coordinate system start at zero,
we can literally use the number of ticks to determine which row we want to display
the pertinent cell states.
Keep in mind,
the p-y cor values count down negative one, negative two,
negative three.
Since downward represents the negative direction
on the y axis,
so we want p-y cor to be the negative
of the current tick count.
For future reference,
let's refer to cells in these given rows
as the current patches.
With these current patches,
we first want to copy the last row onto the new one,
so the cells at the current time step
can interact along the new row
without losing their previous history.
To do this,
set state for each of the current patches
the same as their former cells
directly above, with
patchAt 0 1
Now in this new row,
we can ask the cells to scan their neighborhoods,
and update their states accordingly,
just like they were doing before,
only now in a separate row.
All right!
This is awesome.
We finally get to see a little bit of temporal evolution in action.
In our first official run of the model,
you can see the cells moving down a row
each time step.
And slowing it down a little,
you can see they are doing this asynchronously,
in random order,
as we intended them to
while we get fragments of familiar clusters,
popping up at various frequencies,
the overall distribution pattern of green
is too sparse to manifest the same complex behaviors
as the synchronous deterministic version of this rule.
In fact,
a more thorough analysis of these so-called clusters
might reveal that they are not actually occurring at much higher frequency
than one would expect by mere chance,
telling us randomness, not structure,
might be the dominating factor for this rule set
under the stochastic ECA.
But to have a true general ECA,
synchronous or not,
we obviously need to have the capacity to change the rules,
and compare them, within the same interface.
Furthermore,
we want to compact these long binary based rule vectors
into the shorthand base 10 rule numbering system for the user,
with designations like
Rule 30, Rule 212, or for what we just did
Rule 129.
So we' like the user to see these colloquial rule numberings,
that are readily translated into rule sets
this program can directly use.
To have a rule set that always updates,
based on what the user changes the number to,
a reporter can be really useful.
So first off we want an Arabic numeral input from the user.
So it's probably convenient to put a slider here
to adjust the rule number.
We know this model has a total of 256 rules.
So it's numbered from 0 to 255.
To try out another potentially interesting rule that exhibited rich complexity
in the original ECA,
I'm going to put in Rule 90 here.
So let's get rid of the variable in this line,
which is redundant with our new rule set slider parameter.
And likewise, we don't need the initial rule set and setup any more,
thus leading us back to the task of converting base 10 numbers to binary.
So,
using digit-by-digit counting approach,
let remainder start off as the current rule number base ten.
So in our case, the 90,
and since remainder is actually a reserved name for a
mathematical operation in NetLogo,
I'm just going to call it remainer
Kinda makes sense I hope.
So again, let remainer be the user specified rule number.
And then,
here's a good place to use the foreach function.
This is where we methodically go through the binary digits in the rule vector,
where each carry equivalent base ten value
is from greatest to least.
So the first significant digit is
two to the seventh power,
ergo 128.
Next one is 64, 32, 16 etc
Again there's probably countless ways to derive and generate this vector
without manually putting in everything,
but I'm just being pedagogical-ish.
Oh yeah,
one more thing here.
Let there be a temporary variable, RuleList.
This is just a stand-in for a rule set variable,
which starts out as an empty list,
but collects zeros and ones,
until it has eight binary digits
and reports a complete list as the rule set.
For each of these binary digit values,
we're going to run a comparison test.
So I'm going to say,
for remainer, ifelse remainer less than the current binary digit,
set this digit to zero.
For example, for a rule number of 90,
the leading digit is zero
since anything with one in the front
is going to be 128 or greater
which we add to the rule list
with LPUT.
LPUT appending the value to the latest item on the current list.
We get to use Set for these funtions
which has to do with the way NetLogo updates lists,
since they can't edit existing lists directly.
So if rule number is less than current place value,
set binary digit to zero,
but otherwise, if it is equal to or greater than this place,
as in the case of the second significant digit,
where 90 is greater than 64,
set this digit to one.
However, we have to account for the fact
that a value of 64 is now included in the current rule vector.
So we deduct that from 90,
leaving us with a remainder, or in this code, remainer,
of 26.
This algorithm continues running through each of the following significant digits
in the same manner,
running a similar comparison test,
and recording the appropriate binary digits and subtractions.
And finally with the 8 digit list completed,
we report the final output,
allowing NetLogo to store it in our rule set variable.
And this variable is automatically being referenced and updated
by the program at every step.
So you don't need to set it
anywhere else.
I'll even create a monitor of the actual rule set so you can see
what the rule vector NetLogo is actually using,
and how instantaneous this conversion process is.
Running Rule 90 in the asynchronous ECA
we some semblance of emergent macro structure.
I'll leave it to you to explore the rest of what the asynchronous ECA has to offer:
what differences it has,
what properties does it preserve
from the ordered synchronous ECA.
Of course, there a bunch of other features
you can add to this model at the drop of a hat.
For example, we started with a default number of patches along each row,
which is 33 here,
but we can decrease or increase this number by teaming up the max px cor
with another slider for instance, to change perspective,
maybe get a broader birds eye view of the patterns.
Along those same lines,
you could also change the maximum py cor,
and/or even make the pixels smaller.
It may be convenient to have a Go button
that runs automatically
and views evolution at different speeds.
We might not necessarily even want to have
50-50 starting conditions for the patches,
so you can adjust the probability in the code,
or slider,
or ???est level of max, or highest ECA model,
have an edit mode that lets you choose the patches individually.
All right folks
Hope this demo was illuminating and helpful
i hope you have fun with everything.
See you later.