Notion - Update docs

This commit is contained in:
shiffman 2023-08-23 16:10:06 +00:00 committed by GitHub
parent d81d095e04
commit 7bc3cc9814
27 changed files with 89 additions and 91 deletions

View file

@ -39,89 +39,86 @@
<img src="images/07_ca/07_ca_4.png" alt="Figure 7.4: A neighborhood in one dimension is three cells. ">
<figcaption>Figure 7.4: A neighborhood in one dimension is three cells. </figcaption>
</figure>
<p>I have a line of cells, each with an initial state, and each with two neighbors. The exciting thing is, even with this simplest CA imaginable, the properties of complex systems can emerge. But I havent yet discussed perhaps the most important detail of how cellular automata work: change over time.</p>
<p>Im not really talking about real-world time here, but rather about the CA developing across a series of discrete time steps, which could also be called a <strong>generations</strong>. In the case of a CA in p5.js, time will likely be tied to the frame count of the animation. The question, as depicted in Figure 7.5, is this: given the states of the cells at time equals 0 (or generation 0), how do I compute the states for all cells at generation 1? And then how do I get from generation 1 to generation 2? And so on and so forth.</p>
<figure>
<img src="images/07_ca/07_ca_5.png" alt="Figure 7.5: The edge cell only has a neighborhood of two. ">
<figcaption>Figure 7.5: The edge cell only has a neighborhood of two. </figcaption>
</figure>
<p>I have a line of cells, each with an initial state, and each with two neighbors. The exciting thing is, even with this simplest CA imaginable, the properties of complex systems can emerge. But I havent yet discussed perhaps the most important detail of how cellular automata work: time.</p>
<p>Im not really talking about real-world time here, but rather about the CA developing across a series of discrete time steps, which could also be called a <strong>generations</strong>. In the case of a CA in p5.js, time will likely be tied to the frame count of the animation. The question, as depicted in Figure 7.6, is this: given the states of the cells at time equals 0 (or generation 0), how do I compute the states for all cells at generation 1? And then how do I get from generation 1 to generation 2? And so on and so forth.</p>
<figure>
<img src="images/07_ca/07_ca_6.png" alt="Figure 7.6: The states for generation 1 are calculated using the states of the cells from generation 0.">
<figcaption>Figure 7.6: The states for generation 1 are calculated using the states of the cells from generation 0.</figcaption>
<img src="images/07_ca/07_ca_5.png" alt="Figure 7.5: The states for generation 1 are calculated using the states of the cells from generation 0.">
<figcaption>Figure 7.5: The states for generation 1 are calculated using the states of the cells from generation 0.</figcaption>
</figure>
<p>Lets say theres an individual cell in the CA called <span data-type="equation">\text{cell}</span>. The formula for calculating the cells state at any given time <span data-type="equation">t</span> (<span data-type="equation">\text{cell}_t</span>) is as follows:</p>
<div data-type="equation">\text{cell}_t = f(\text{cell neighborhood}_{t-1})</div>
<p>In other words, a cells new state is a function of all the states in the cells neighborhood at the previous generation (time <span data-type="equation">t-1</span>). A new state value is calculated by looking at the previous generations neighbor states (Figure 7.7).</p>
<p>In other words, a cells new state is a function of all the states in the cells neighborhood at the previous generation (time <span data-type="equation">t-1</span>). A new state value is calculated by looking at the previous generations neighbor states (Figure 7.6).</p>
<figure>
<img src="images/07_ca/07_ca_7.png" alt="Figure 7.7 The state of a cell at generation 1 is a function of the previous generations neighborhood.">
<figcaption>Figure 7.7 The state of a cell at generation 1 is a function of the previous generations neighborhood.</figcaption>
<img src="images/07_ca/07_ca_6.png" alt="Figure 7.6 The state of a cell at generation 1 is a function of the previous generations neighborhood.">
<figcaption>Figure 7.6 The state of a cell at generation 1 is a function of the previous generations neighborhood.</figcaption>
</figure>
<p>There are many ways to compute a cells state from its neighbors states. Consider blurring an image. (Guess what? Image processing works with CA-like rules!) A pixels new state (its color) is the average of its neighbors colors. Similarly, a cells new state could be the sum of all of its neighbors states. However, in Wolframs elementary CA, the process takes a different approach: instead of mathematical operations, new states are determined by predefined rules that account for every possible configuration of a cell and its neighbors. These rules are known collectively as a <strong>ruleset</strong>.</p>
<p>This approach might seem ridiculous at first—wouldnt there be way too many possibilities for it to be practical? Well, lets give it a try. A neighborhood consists of three cells, each with a state of 0 or 1. How many possible ways can the states in a neighborhood be configured? A quick way to figure this out is to think of each neighborhood configuration as a binary number. Binary numbers use “base 2,” meaning theyre represented with only two possible digits (0 and 1). In this case, each neighborhood configuration corresponds to a 3-bit number, and how many values can you represent with 3 bits? Eight, from 0 (000) up to 7 (111). Figure 7.8 shows how.</p>
<p>This approach might seem ridiculous at first—wouldnt there be way too many possibilities for it to be practical? Well, lets give it a try. A neighborhood consists of three cells, each with a state of 0 or 1. How many possible ways can the states in a neighborhood be configured? A quick way to figure this out is to think of each neighborhood configuration as a binary number. Binary numbers use “base 2,” meaning theyre represented with only two possible digits (0 and 1). In this case, each neighborhood configuration corresponds to a 3-bit number, and how many values can you represent with 3 bits? Eight, from 0 (000) up to 7 (111). Figure 7.7 shows how.</p>
<figure>
<img src="images/07_ca/07_ca_8.png" alt="Figure 7.8: Counting with 3 bits in binary, or the eight possible configurations of a three-cell neighborhood">
<figcaption>Figure 7.8: Counting with 3 bits in binary, or the eight possible configurations of a three-cell neighborhood</figcaption>
<img src="images/07_ca/07_ca_7.png" alt="Figure 7.7: Counting with 3 bits in binary, or the eight possible configurations of a three-cell neighborhood">
<figcaption>Figure 7.7: Counting with 3 bits in binary, or the eight possible configurations of a three-cell neighborhood</figcaption>
</figure>
<p>Once all the possible neighborhood configurations are defined, an outcome (new state value: 0 or 1) is specified for each configuration, as in Figure 7.9.</p>
<p>Once all the possible neighborhood configurations are defined, an outcome (new state value: 0 or 1) is specified for each configuration. In Wolfram's original notation and other common references, these configurations are written in descending order, Figure 7.8 follows this convention, starting with 111 and counting down to 000.</p>
<figure>
<img src="images/07_ca/07_ca_9.png" alt="Figure 7.9: A ruleset shows the outcome for each possible configuration of three cells.">
<figcaption>Figure 7.9: A ruleset shows the outcome for each possible configuration of three cells.</figcaption>
<img src="images/07_ca/07_ca_8.png" alt="Figure 7.8: A ruleset shows the outcome for each possible configuration of three cells.">
<figcaption>Figure 7.8: A ruleset shows the outcome for each possible configuration of three cells.</figcaption>
</figure>
<p>Keep in mind that unlike the sum or averaging methods, the rulesets in elementary CA dont follow any arithmetic logic—theyre just arbitrary mappings of inputs to outputs. The input is the current configuration of the neighborhood (one of eight possibilities), and the output is the next state of the middle cell in the neighborhood (0 or 1—its up to you to define the rule).</p>
<p>Once you have a ruleset, you can set the cellular automaton in motion. The standard Wolfram model is to start generation 0 with all cells having a state of 0 except for the middle cell, which should have a state of 1. You can do this with any size (length) grid, but for clarity, Ill use a one-dimensional CA of nine cells so that the middle is easy to pick out.</p>
<figure>
<img src="images/07_ca/07_ca_10.png" alt="Figure 7.10: Generation 0 in a Wolfram CA, with the center cell set to 1">
<figcaption>Figure 7.10: Generation 0 in a Wolfram CA, with the center cell set to 1</figcaption>
<img src="images/07_ca/07_ca_9.png" alt="Figure 7.9: Generation 0 in a Wolfram CA, with the center cell set to 1">
<figcaption>Figure 7.9: Generation 0 in a Wolfram CA, with the center cell set to 1</figcaption>
</figure>
<p>Based on the ruleset in Figure 7.9, how do the cells change from generation 0 to generation 1? In Figure 7.11, Ive shown how the center cell, with a neighborhood of 010, switches from a 1 to a 0. Try applying the ruleset to the remaining cells to fill in the rest of the generation 1 states.</p>
<p>Based on the ruleset in Figure 7.8, how do the cells change from generation 0 to generation 1? Figure 7.10 shows how the center cell, with a neighborhood of 010, switches from a 1 to a 0. Try applying the ruleset to the remaining cells to fill in the rest of the generation 1 states.</p>
<figure>
<img src="images/07_ca/07_ca_11.png" alt="Figure 7.11: Determining a state for generation 1 using the CA rule set">
<figcaption>Figure 7.11: Determining a state for generation 1 using the CA rule set</figcaption>
<img src="images/07_ca/07_ca_10.png" alt="Figure 7.10: Determining a state for generation 1 using the CA rule set">
<figcaption>Figure 7.10: Determining a state for generation 1 using the CA rule set</figcaption>
</figure>
<p>Now for a slight change: instead of representing the cells states with 0s and 1s, Ill indicate them with visual cues—white for 0 and black for 1 (see FIgure 7.12). Although this might seem counterintuitive, as 0 usually signifies black in computer graphics, Im using this convention because the examples in this book have a white background, so “turning on” a cell corresponds to switching its color from white to black.</p>
<p>Now for a slight change: instead of representing the cells states with 0s and 1s, Ill indicate them with visual cues—white for 0 and black for 1 (see Figure 7.11). Although this might seem counterintuitive, as 0 usually signifies black in computer graphics, Im using this convention because the examples in this book have a white background, so “turning on” a cell corresponds to switching its color from white to black.</p>
<figure>
<img src="images/07_ca/07_ca_12.png" alt="Figure 7.12: A white cell indicates 0, and a black cell indicates 1.">
<figcaption>Figure 7.12: A white cell indicates 0, and a black cell indicates 1.</figcaption>
<img src="images/07_ca/07_ca_11.png" alt="Figure 7.11: A white cell indicates 0, and a black cell indicates 1.">
<figcaption>Figure 7.11: A white cell indicates 0, and a black cell indicates 1.</figcaption>
</figure>
<p>With this switch from numerical representations into visual forms, the fascinating dynamics and patterns of cellular automata will come into view! To see them even more clearly, instead of drawing one generation at a time, Ill also start stacking the generations, with each new generation appearing below the previous one, as shown in Figure 7.13.</p>
<p>With this switch from numerical representations into visual forms, the fascinating dynamics and patterns of cellular automata will come into view! To see them even more clearly, instead of drawing one generation at a time, Ill also start stacking the generations, with each new generation appearing below the previous one, as shown in Figure 7.12.</p>
<figure>
<img src="images/07_ca/07_ca_13.png" alt="Figure 7.13 Translating a grid of 0s and 1s to white and black squares.">
<figcaption>Figure 7.13 Translating a grid of 0s and 1s to white and black squares.</figcaption>
<img src="images/07_ca/07_ca_12.png" alt="Figure 7.12 Translating a grid of 0s and 1s to white and black squares.">
<figcaption>Figure 7.12 Translating a grid of 0s and 1s to white and black squares.</figcaption>
</figure>
<p>The low-resolution shape that emerges in Figure 7.13 is the <strong>Sierpiński triangle</strong>. Named after the Polish mathematician Wacław Sierpiński, its a famous example of a <strong>fractal</strong>. Ill examine fractals more closely in the next chapter, but briefly, theyre patterns where the same shapes repeat themselves at different scales. To give you a better sense of this, Figure 7.14 shows the CA over several more generations, and with a wider grid size.</p>
<p>The low-resolution shape that emerges in Figure 7.12 is the <strong>Sierpiński triangle</strong>. Named after the Polish mathematician Wacław Sierpiński, its a famous example of a <strong>fractal</strong>. Ill examine fractals more closely in the next chapter, but briefly, theyre patterns where the same shapes repeat themselves at different scales. To give you a better sense of this, Figure 7.13 shows the CA over several more generations, and with a wider grid size.</p>
<figure>
<img src="images/07_ca/07_ca_14.png" alt="Figure 7.14: Wolfram elementary CA, rule 90 ">
<figcaption>Figure 7.14: Wolfram elementary CA, rule 90 </figcaption>
<img src="images/07_ca/07_ca_13.png" alt="Figure 7.13: Wolfram elementary CA, rule 90 ">
<figcaption>Figure 7.13: Wolfram elementary CA, rule 90 </figcaption>
</figure>
<p>And Figure 7.15 shows the CA again, this time with cells that are just a single pixel wide so the resolution is much higher.</p>
<p>And Figure 7.14 shows the CA again, this time with cells that are just a single pixel wide so the resolution is much higher.</p>
<figure>
<img src="images/07_ca/07_ca_15.png" alt="Figure 7.15: Wolfram elementary CA, rule 90, at higher resolution">
<figcaption>Figure 7.15: Wolfram elementary CA, rule 90, at higher resolution</figcaption>
<img src="images/07_ca/07_ca_14.png" alt="Figure 7.14: Wolfram elementary CA, rule 90, at higher resolution">
<figcaption>Figure 7.14: Wolfram elementary CA, rule 90, at higher resolution</figcaption>
</figure>
<p>Take a moment to let the enormity of what youve just seen sink in. Using an incredibly simple system of 0s and 1s, with little neighborhoods of three cells, I was able to generate a shape as sophisticated and detailed as the Sierpiński triangle. This is the beauty of complex systems.</p>
<p>Of course, this particular result didnt happen by accident. I picked the set of rules in Figure 7.9 because I knew the pattern it would generate. The mere act of defining a ruleset doesnt guarantee visually exciting results. In fact, for a one-dimensional CA where each cell can have two possible states, there are exactly 256 possible rulesets to choose from, and only a handful of them are on par with the Sierpiński triangle. How do I know there are 256 possible rulesets? It comes down to a little more binary math.</p>
<p>Of course, this particular result didnt happen by accident. I picked the set of rules in Figure 7.8 because I knew the pattern it would generate. The mere act of defining a ruleset doesnt guarantee visually exciting results. In fact, for a one-dimensional CA where each cell can have two possible states, there are exactly 256 possible rulesets to choose from, and only a handful of them are on par with the Sierpiński triangle. How do I know there are 256 possible rulesets? It comes down to a little more binary math.</p>
<h3 id="defining-rulesets">Defining Rulesets</h3>
<p>Take a look back at Figure 7.8 and notice again how there are eight possible neighborhood configurations, from 000 to 111. These are a rulesets inputs, and they remain constant from ruleset to ruleset. Its only the outputs that vary from one ruleset to another—the individual 0 or 1 paired with each neighborhood configuration. Figure 7.9 represented a ruleset entirely with 0s and 1s. Now Figure 7.16 shows the same ruleset visualized with white and black squares.</p>
<p>Take a look back at Figure 7.7 and notice again how there are eight possible neighborhood configurations, from 000 to 111. These are a rulesets inputs, and they remain constant from ruleset to ruleset. Its only the outputs that vary from one ruleset to another—the individual 0 or 1 paired with each neighborhood configuration. Figure 7.9 represented a ruleset entirely with 0s and 1s. Now Figure 7.16 shows the same ruleset visualized with white and black squares.</p>
<figure>
<img src="images/07_ca/07_ca_16.png" alt="Figure 7.16 Representing the same ruleset (from Figure 7.9) with white and black squares">
<figcaption>Figure 7.16 Representing the same ruleset (from Figure 7.9) with white and black squares</figcaption>
<img src="images/07_ca/07_ca_15.png" alt="Figure 7.15 Representing the same ruleset (from Figure 7.8) with white and black squares">
<figcaption>Figure 7.15 Representing the same ruleset (from Figure 7.8) with white and black squares</figcaption>
</figure>
<p>Since the eight possible inputs are the same no matter what, a potential shorthand for indicating a ruleset is to specify just the outputs, writing them as a sequence of eight 0s or 1s—in other words, an 8-bit binary number. For example, the ruleset in Figure 7.16 could be written as 01011010. The 0 on the right corresponds to input configuration 000, the 1 next to it corresponds to input 001, and so on. On Wolframs website, CA rules are illustrated using a combination of this binary shorthand an Figure 7.16s black-and-white square representation, yielding depictions like Figure 7.17.</p>
<p>Since the eight possible inputs are the same no matter what, a potential shorthand for indicating a ruleset is to specify just the outputs, writing them as a sequence of eight 0s or 1s—in other words, an 8-bit binary number. For example, the ruleset in Figure 7.15 could be written as 01011010. The 0 on the right corresponds to input configuration 000, the 1 next to it corresponds to input 001, and so on. On Wolframs website, CA rules are illustrated using a combination of this binary shorthand and the black-and-white square representation, yielding depictions like Figure 7.16.</p>
<figure>
<img src="images/07_ca/07_ca_17.png" alt="Figure 7.17: How the Wolfram website represents a ruleset">
<figcaption>Figure 7.17: How the Wolfram website represents a ruleset</figcaption>
<img src="images/07_ca/07_ca_16.png" alt="Figure 7.16: How the Wolfram website represents a ruleset">
<figcaption>Figure 7.16: How the Wolfram website represents a ruleset</figcaption>
</figure>
<p>A ruleset can be reduced to an 8-bit number. And how many combinations of eight 0s and 1s are there? Exactly <span data-type="equation">2^8</span>, or 256. You might remember this from when you first learned about RGB color in p5.js. When you write <code>background(r, g, b)</code>, each color component (red, green, and blue) is represented by an 8-bit number ranging from 0 to 255 in decimal, or 00000000 to 11111111 in binary. And speaking of decimal, an even shorter shorthand for a ruleset is to convert its 8-bit binary number to decimal (or “base 10”). “Rule 01011010” is therefore more commonly known as “rule 90.”</p>
<p>Figure 7.16 is identified by Wolfram as “Rule 90.” Where does 90 come from? Each ruleset is essentially an 8-bit number. How many combinations of eight 0s and 1s are there? Exactly <span data-type="equation">2^8</span>, or 256. You might remember this from when you first learned about RGB color in p5.js. When you write <code>background(r, g, b)</code>, each color component (red, green, and blue) is represented by an 8-bit number ranging from 0 to 255 in decimal, or 00000000 to 11111111 in binary.</p>
<p>To make ruleset naming even more concise, Wolfram uses decimal (or “base 10”) representations. To name a rule, you convert its 8-bit binary number to its decimal counterpart. The binary number “01011010” translates to the decimal number 90, and therefore its named “Rule 90.”</p>
<p>Since there are 256 possible combinations of eight 0s and 1s, there are also 256 unique rulesets. Lets check out another one. How about rule 11011110, or more commonly, rule 222. Figure 7.18 shows how it looks.</p>
<figure>
<img src="images/07_ca/07_ca_18.png" alt="Figure 7.18: Wolfram elementary CA, rule 222 ">
<figcaption>Figure 7.18: Wolfram elementary CA, rule 222 </figcaption>
<img src="images/07_ca/07_ca_17.png" alt="Figure 7.17: Wolfram elementary CA, rule 222 ">
<figcaption>Figure 7.17: Wolfram elementary CA, rule 222 </figcaption>
</figure>
<figure class="half-width-right">
<img src="images/07_ca/07_ca_19.jpg" alt="Figure 7.19: A textile cone snail (Conus textile), Cod Hole, Great Barrier Reef, Australia, 7 August 2005. Photographer: Richard Ling richard@research.canon.com.au ">
<figcaption>Figure 7.19: A textile cone snail (<em>Conus textile</em>), Cod Hole, Great Barrier Reef, Australia, 7 August 2005. Photographer: Richard Ling richard@research.canon.com.au </figcaption>
<img src="images/07_ca/07_ca_18.jpg" alt="Figure 7.18: A textile cone snail (Conus textile), Cod Hole, Great Barrier Reef, Australia, 7 August 2005. Photographer: Richard Ling richard@research.canon.com.au ">
<figcaption>Figure 7.18: A textile cone snail (<em>Conus textile</em>), Cod Hole, Great Barrier Reef, Australia, 7 August 2005. Photographer: Richard Ling richard@research.canon.com.au </figcaption>
</figure>
<p>The result is a recognizable shape, though it certainly isnt as exciting as the Sierpiński triangle. As I saidd earlier, most of the 256 elementary rulesets dodnt produce compelling outcomes. However, its still quite incredible that even just a few of these rulesets—simple systems of cells with only two possible states—can produce fascinating patterns seen every day in nature (see Figure 7.19, a snail shell resembling Wolframs rule 30). This demonstrates how valuable CAs can be in simulation and pattern generation.</p>
<p>The result is a recognizable shape, though it certainly isnt as exciting as the Sierpiński triangle. As I saidd earlier, most of the 256 elementary rulesets dodnt produce compelling outcomes. However, its still quite incredible that even just a few of these rulesets—simple systems of cells with only two possible states—can produce fascinating patterns seen every day in nature (see Figure 7.18, a snail shell resembling Wolframs rule 30). This demonstrates how valuable CAs can be in simulation and pattern generation.</p>
<p>Before I go too far down the road of characterizing the results of different rulesets, though, lets look at how to build a p5.js sketch that generates and visualizes a Wolfram elementary CA.</p>
<h3 id="programming-an-elementary-ca">Programming an Elementary CA</h3>
<p>You may be thinking: “OK, Ive got this cell thing. And the cell thing has some properties, like a state, what generation its on, who its neighbors are, and where it lives pixel-wise on the screen. And maybe it has some functions, like to display itself and determine its new state.” This line of thinking is an excellent one and would likely lead you to write some code like this:</p>
@ -130,10 +127,10 @@
}</pre>
<p>However, this isnt the road I want to travel down right now. Later in this chapter, Ill discuss why an object-oriented approach could prove valuable in developing a CA simulation, but to begin, its easier to work with a more elementary data structure. After all, what is an elementary CA but a list of 0s and 1s? Why not describe a generation of a one-dimensional CA using an array?</p>
<pre class="codesplit" data-code-language="javascript">let cells = [1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0];</pre>
<p>This array corresponds to the row of cells shown in Figure 7.20.</p>
<p>This array corresponds to the row of cells shown in Figure 7.19.</p>
<figure>
<img src="images/07_ca/07_ca_20.png" alt="Figure 7.20: One generation of a 1D cellular automata">
<figcaption>Figure 7.20: One generation of a 1D cellular automata</figcaption>
<img src="images/07_ca/07_ca_19.png" alt="Figure 7.19: One generation of a 1D cellular automata">
<figcaption>Figure 7.19: One generation of a 1D cellular automata</figcaption>
</figure>
<p>To show that array, I check if each element is a 0 or a 1, choose a fill color accordingly, and draw a rectangle.</p>
<pre class="codesplit" data-code-language="javascript">//{!1} Loop through every cell.
@ -217,10 +214,10 @@ cells = newcells;</pre>
<p>Im almost done, but I still need to define <code>rules()</code>, the function that computes the new state value based on the neighborhood (left, middle, and right cells). I know the function needs to return an integer (0 or 1), as well as receive three arguments (for the three neighbors).</p>
<pre class="codesplit" data-code-language="javascript"> //{!1} Function signature: receives 3 ints and returns 1.
function rules (a, b, c) { return _______ }</pre>
<p>There are many ways to write this function, but Id like to start with a long-winded one that will hopefully provide a clear illustration of whats happening. First, I need to establish how Ill store the ruleset. Recall that a ruleset is a series of 8 bits (0 or 1) defining the outcome for every possible neighborhood configuration. If you need to refresh your memory, Figure 7.21 shows a visual representation of the Sierpiński triangle ruleset.</p>
<p>There are many ways to write this function, but Id like to start with a long-winded one that will hopefully provide a clear illustration of what's happening. How shall I store the ruleset? Remember that a ruleset is a series of 8 bits (0 or 1) that define the outcome for every possible neighborhood configuration. If you need a refresher, Figure 7.20 shows the Wolfram notation for the Sierpiński triangle ruleset, along with the corresponding 0s and 1s listed in order. This should give you a hint as to the data structure I have in mind!</p>
<figure>
<img src="images/07_ca/07_ca_21.png" alt="Figure 7.21 A visual representation of a Wolfram ruleset with numeric encoding ">
<figcaption>Figure 7.21 A visual representation of a Wolfram ruleset with numeric encoding </figcaption>
<img src="images/07_ca/07_ca_20.png" alt="Figure 7.20 A visual representation of a Wolfram ruleset with numeric encoding ">
<figcaption>Figure 7.20 A visual representation of a Wolfram ruleset with numeric encoding </figcaption>
</figure>
<p>I can store this ruleset in an array.</p>
<pre class="codesplit" data-code-language="javascript">let ruleset = [0, 1, 0, 1, 1, 0, 1, 0];</pre>
@ -292,10 +289,10 @@ function rules(a, b, c) {
}</pre>
<p>This is great, but theres still one more missing piece: what good is a cellular automaton if you cant see it?</p>
<h3 id="drawing-an-elementary-ca">Drawing an Elementary CA</h3>
<p>The standard technique for drawing an elementary CA is to stack the generations one on top of the other, and to draw each cell as a square thats black (for state 1) or white (for state 0), as in Figure 7.22. Before implementing this particular visualization, however, Id like to point out two things.</p>
<p>The standard technique for drawing an elementary CA is to stack the generations one on top of the other, and to draw each cell as a square thats black (for state 1) or white (for state 0), as in Figure 7.21. Before implementing this particular visualization, however, Id like to point out two things.</p>
<figure>
<img src="images/07_ca/07_ca_22.png" alt="Figure 7.22 Ruleset 90 visualized as a stack of generations">
<figcaption>Figure 7.22 Ruleset 90 visualized as a stack of generations</figcaption>
<img src="images/07_ca/07_ca_21.png" alt="Figure 7.21 Ruleset 90 visualized as a stack of generations">
<figcaption>Figure 7.21 Ruleset 90 visualized as a stack of generations</figcaption>
</figure>
<p>First, this visual interpretation of the data is completely literal. Its useful for demonstrating the algorithms and results of Wolframs elementary CA, but it shouldnt necessarily drive your own personal work. Its rather unlikely that youre building a project that needs precisely this algorithm with this visual style. So while learning to draw a CA in this way will help you understand and implement CA systems, this skill should exist only as a foundation.</p>
<p>Second, the fact that a one-dimensional CA is visualized with a two-dimensional image can be misleading. Its very important to remember that this is <em>not</em> a 2D CA. Im simply choosing to show a history of all the generations stacked vertically. This technique creates a two-dimensional image out of many instances of one-dimensional data, but the system itself is one-dimensional. Later, Ill show you an actual 2D CA (the Game of Life), and Ill cover how to visualize such a system.</p>
@ -304,7 +301,7 @@ function rules(a, b, c) {
<p>Assuming the canvas is 640 pixels wide, that means the CA will have 64 cells. Of course, I can calculate this value dynamically when I initialize the <code>cells</code> array in <code>setup()</code>.</p>
<pre class="codesplit" data-code-language="javascript">//{!1} How many cells fit across given a certain width
let cells = new Array(floor(width / w));</pre>
<p>Drawing the cells now involves iterating over the array and drawing a white square when the state equals 1 or a black when the state equals 0.</p>
<p>Drawing the cells now involves iterating over the array and drawing a square based on the state of each cell.</p>
<pre class="codesplit" data-code-language="javascript">for (let i = 0; i &#x3C; cells.length; i++) {
//{!2} By multiplying the cell state the result is 0 or 255
fill(cells[i] * 255);
@ -312,8 +309,8 @@ let cells = new Array(floor(width / w));</pre>
// 0, 10, 20, 30, all the way to 640.
square(i * w, 0, w);
}</pre>
<p>In truth, I could simplify the code by having a black background and only drawing when there is a white cell (saving the work of drawing many squares), but in most cases this solution is good enough (and necessary for other more sophisticated designs with varying colors.) Ill also note that if the size of each cell were 1 pixel I would not want to use p5.jss <code>square()</code> function, but rather access the pixel array directly.</p>
<p>Right now the y-position for each square is hardcoded to 0. If I want the generations to be stacked on top of each other, with each row of cells marking a new generation, Ill also need to calculate a y-position based on the generation number. I could accomplish this by adding a <code>generation</code> variable and incrementing it each time through <code>draw()</code>. With these additions, I can now look at the entire sketch.</p>
<p>There are two things off about this code. First, when multiplying the state by 255, cells with a state of 1 will be black and those with 0 will be white, which is the opposite of what I originally intended! While this is of course “ok” since the color representation is arbitrary, Ill correct this in the full example.</p>
<p>The more pressing issue is that the y-position for each square is hardcoded to 0. If I want the generations to be stacked on top of each other, with each row of cells marking a new generation, Ill also need to calculate a y-position based on the generation number. I can accomplish this by adding a <code>generation</code> variable and incrementing it each time through <code>draw()</code>. With these additions, I can now look at the entire sketch.</p>
<div data-type="example">
<h3 id="example-71-wolfram-elementary-cellular-automata">Example 7.1: Wolfram Elementary Cellular Automata</h3>
<figure>
@ -372,6 +369,7 @@ function rules(a, b, c) {
let index = parseInt(s, 2);
return ruleset[7 - index];
}</pre>
<p>You may have noticed an optimization I made in this example by simplifying the drawing. I included a white background and rendered only the black squares, which saves the work of drawing many squares. This solution isnt suitable for all cases (what if I want multi-colored cells!), but it provides a performance boost in this simple case. Ill also note that if the size of each cell were 1 pixel I would not want to use p5.jss <code>square()</code> function, but rather access the pixel array directly.</p>
<div data-type="exercise">
<h3 id="exercise-71">Exercise 7.1</h3>
<p>Expand Example 7.1 to have the following feature: when the CA reaches the bottom of the canvas, the CA starts over with a new, random ruleset.</p>
@ -390,25 +388,25 @@ function rules(a, b, c) {
</div>
<h2 id="wolfram-classification">Wolfram Classification</h2>
<p>Now that you have a sketch for visualizing an elementary CA, you can supply it whatever ruleset you want and see the results. What kind of outcomes can you expect? As I noted earlier, the vast majority of elementary CA rulesets produce visually uninspiring results, while some result in wondrously complex patterns like those found in nature. Wolfram himself has divided up the range of outcomes into four classes.</p>
<p><strong>Class 1: Uniformity.</strong> Class 1 CAs end up, after some number of generations, with every cell constant. This isnt terribly exciting to watch. Rule 222 (see Figure 7.23) is a class 1 CA; if you run it for enough generations, every cell will eventually become and remain black.</p>
<p><strong>Class 1: Uniformity.</strong> Class 1 CAs end up, after some number of generations, with every cell constant. This isnt terribly exciting to watch. Rule 222 (see Figure 7.22) is a class 1 CA; if you run it for enough generations, every cell will eventually become and remain black.</p>
<figure>
<img src="images/07_ca/07_ca_23.png" alt="Figure 7.23: Rule 222 ">
<figcaption>Figure 7.23: Rule 222 </figcaption>
<img src="images/07_ca/07_ca_22.png" alt="Figure 7.22: Rule 222 ">
<figcaption>Figure 7.22: Rule 222 </figcaption>
</figure>
<p><strong>Class 2: Repetition.</strong> Like class 1 CAs, class 2 CAs remain stable, but the cell states arent constant. Instead, they oscillate in some repeating pattern of 0s and 1s. In rule 190 (Figure 7.24), each cell follows the sequence <code>11101110111011101110</code>.</p>
<p><strong>Class 2: Repetition.</strong> Like class 1 CAs, class 2 CAs remain stable, but the cell states arent constant. Instead, they oscillate in some repeating pattern of 0s and 1s. In rule 190 (Figure 7.23), each cell follows the sequence <code>11101110111011101110</code>.</p>
<figure>
<img src="images/07_ca/07_ca_24.png" alt="Figure 7.24: Rule 190 ">
<figcaption>Figure 7.24: Rule 190 </figcaption>
<img src="images/07_ca/07_ca_23.png" alt="Figure 7.23: Rule 190 ">
<figcaption>Figure 7.23: Rule 190 </figcaption>
</figure>
<p><strong>Class 3: Random.</strong> Class 3 CAs appear random and have no easily discernible pattern. In fact, rule 30 (Figure 7.25) is used as a random number generator in Wolframs Mathematica software. Again, this is a moment where you can feel amazed that such a simple system with simple rules can descend into a chaotic and random pattern.</p>
<p><strong>Class 3: Random.</strong> Class 3 CAs appear random and have no easily discernible pattern. In fact, rule 30 (Figure 7.24) is used as a random number generator in Wolframs Mathematica software. Again, this is a moment where you can feel amazed that such a simple system with simple rules can descend into a chaotic and random pattern.</p>
<figure>
<img src="images/07_ca/07_ca_25.png" alt="Figure 7.25: Rule 30 ">
<figcaption>Figure 7.25: Rule 30 </figcaption>
<img src="images/07_ca/07_ca_24.png" alt="Figure 7.24: Rule 30 ">
<figcaption>Figure 7.24: Rule 30 </figcaption>
</figure>
<p><strong>Class 4: Complexity.</strong> Class 4 CAs can be thought of as a mix between class 2 and class 3. You can find repetitive, oscillating patterns inside the CA, but where and when these patterns appear is unpredictable and seemingly random. Class 4 CAs exhibit the properties of complex systems described earlier in this chapter and in Chapter 5. If a class 3 CA wowed you, then a class 4 like Rule 110 (Figure 7.26) should really blow your mind!</p>
<p><strong>Class 4: Complexity.</strong> Class 4 CAs can be thought of as a mix between class 2 and class 3. You can find repetitive, oscillating patterns inside the CA, but where and when these patterns appear is unpredictable and seemingly random. Class 4 CAs exhibit the properties of complex systems described earlier in this chapter and in Chapter 5. If a class 3 CA wowed you, then a class 4 like Rule 110 (Figure 7.25) should really blow your mind!</p>
<figure>
<img src="images/07_ca/07_ca_26.png" alt="Figure 7.26: Rule 110 ">
<figcaption>Figure 7.26: Rule 110 </figcaption>
<img src="images/07_ca/07_ca_25.png" alt="Figure 7.25: Rule 110 ">
<figcaption>Figure 7.25: Rule 110 </figcaption>
</figure>
<div data-type="exercise">
<h3 id="exercise-75">Exercise 7.5</h3>
@ -425,11 +423,11 @@ function rules(a, b, c) {
<p>This might sound cryptic, but it essentially describes a Wolfram class 4 CA. The CA should be patterned but unpredictable over time, eventually settling into a uniform or oscillating state. In other words, though Conway didnt use this terminology, it should have all those properties of a <em>complex system</em>.</p>
<h3 id="the-rules-of-the-game">The Rules of the Game</h3>
<p>Lets look at how the Game of Life works. It wont take up too much time or space, since I can build on everything from Wolframs elementary CA. First, instead of a line of cells, theres now a two-dimensional matrix of cells. As with the elementary CA, the possible states are 0 or 1. In this case, however, since the system is all about “life," 0 means “dead” and 1 means “alive.”</p>
<figure class="half-width-right">
<img src="images/07_ca/07_ca_27.png" alt="Figure 7.27: A two-dimensional CA showing the neighborhood of 9 cells.">
<figcaption>Figure 7.27: A two-dimensional CA showing the neighborhood of 9 cells.</figcaption>
<p>Since the Game of Life is two-dimensional, each cells neighborhood has now expanded. If a neighbor is an adjacent cell, a neighborhood is now nine cells instead of three, as shown in Figure 7.26.</p>
<figure>
<img src="images/07_ca/07_ca_26.png" alt="Figure 7.26: A two-dimensional CA showing the neighborhood of 9 cells.">
<figcaption>Figure 7.26: A two-dimensional CA showing the neighborhood of 9 cells.</figcaption>
</figure>
<p>Since the Game of Life is two-dimensional, each cells neighborhood has now expanded. If a neighbor is an adjacent cell, a neighborhood is now nine cells instead of three, as shown in Figure 7.27.</p>
<p>With three cells, a 3-bit number had eight possible configurations. With nine cells, there are 9 bits, or 512 possible neighborhoods. In most cases, it would be impractical to define an outcome for every single possibility. The Game of Life gets around this problem by defining a set of rules according to general characteristics of the neighborhood: is the neighborhood overpopulated with life, surrounded by death, or just right? Here are the rules of life.</p>
<ol>
<li><strong>Death.</strong> If a cell is alive (state = 1), it will die (state becomes 0) under the following circumstances:
@ -446,26 +444,26 @@ function rules(a, b, c) {
</ul>
</li>
</ol>
<p>Figure 7.28 shows a few examples of these rules. Focus on what happens to the center cell.</p>
<p>Figure 7.27 shows a few examples of these rules. Focus on what happens to the center cell.</p>
<figure>
<img src="images/07_ca/07_ca_28.png" alt="Figure 7.28: Example scenarios for “death” and “birth” in the Game of Life">
<figcaption>Figure 7.28: Example scenarios for “death” and “birth” in the Game of Life</figcaption>
<img src="images/07_ca/07_ca_27.png" alt="Figure 7.27: Example scenarios for “death” and “birth” in the Game of Life">
<figcaption>Figure 7.27: Example scenarios for “death” and “birth” in the Game of Life</figcaption>
</figure>
<p>With the elementary CA, I visualized many generations at once, stacked as rows in a 2D grid. With the Game of Life, however, the CA itself is in two dimensions. I could try to create an elaborate 3D visualization of the results and stack all the generations in a cube structure (and in fact, you might want to try this as an exercise), but a more typical way to visualize the Game of Life is to treat each generation as a single frame in an animation. This way, instead of viewing all the generations at once, you see them one at a time, and the result resembles rapidly growing bacteria in a Petri dish.</p>
<p>One of the exciting aspects of the Game of Life is that there are known initial patterns that yield intriguing results. For example, the patterns shown in Figure 7.29 some remain static and never change.</p>
<p>With the elementary CA, I visualized many generations at once, stacked as rows in a 2D grid. With the Game of Life, however, the CA itself is in two dimensions. I could try to create an elaborate 3D visualization of the results and stack all the generations in a cube structure (and in fact, you might want to try this as an exercise), but a more typical way to visualize the Game of Life is to treat each generation as a single frame in an animation. This way, instead of viewing all the generations at once, you see them one at a time, and the result resembles rapidly developing bacteria in a Petri dish.</p>
<p>One of the exciting aspects of the Game of Life is that there are known initial patterns that yield intriguing results. For example, the patterns shown in Figure 7.28 some remain static and never change.</p>
<figure>
<img src="images/07_ca/07_ca_29.png" alt="Figure 7.29: Initial configurations of cells that remain stable">
<figcaption>Figure 7.29: Initial configurations of cells that remain stable</figcaption>
<img src="images/07_ca/07_ca_28.png" alt="Figure 7.28: Initial configurations of cells that remain stable">
<figcaption>Figure 7.28: Initial configurations of cells that remain stable</figcaption>
</figure>
<p>The patterns in Figure 7.30 oscillate back and forth between two states.</p>
<p>The patterns in Figure 7.29 oscillate back and forth between two states.</p>
<figure>
<img src="images/07_ca/07_ca_30.png" alt="Figure 7.30: Initial configurations of cells that oscillate between two states">
<figcaption>Figure 7.30: Initial configurations of cells that oscillate between two states</figcaption>
<img src="images/07_ca/07_ca_29.png" alt="Figure 7.29: Initial configurations of cells that oscillate between two states">
<figcaption>Figure 7.29: Initial configurations of cells that oscillate between two states</figcaption>
</figure>
<p>And the patterns in Figure 7.31 appear to move about the grid from generation to generation. The cells themselves dont actually move, but you see the illusion of motion in the result of adjacent cells turning on and off.</p>
<figure>
<img src="images/07_ca/07_ca_31.png" alt="Figure 7.31: Initial configurations of cells that appear to move">
<figcaption>Figure 7.31: Initial configurations of cells that appear to move</figcaption>
<img src="images/07_ca/07_ca_30.png" alt="Figure 7.30: Initial configurations of cells that appear to move">
<figcaption>Figure 7.30: Initial configurations of cells that appear to move</figcaption>
</figure>
<p>If youre interested in these patterns, there are several good “out of the box” Game of Life demonstrations online that allow you to configure the CAs initial state and watch it run at varying speeds. Two examples are:</p>
<ul>
@ -510,10 +508,10 @@ for (let i = 0; i &#x3C; columns; i++) {
}
}</pre>
<figure class="half-width-right">
<img src="images/07_ca/07_ca_32.png" alt="Figure 7.32: The index values for the neighborhood of cells.">
<figcaption>Figure 7.32: The index values for the neighborhood of cells.</figcaption>
<img src="images/07_ca/07_ca_31.png" alt="Figure 7.31: The index values for the neighborhood of cells.">
<figcaption>Figure 7.31: The index values for the neighborhood of cells.</figcaption>
</figure>
<p>Next, I need to sort out how to actually calculate each cells new state. For that, I need to determine how to reference the cells neighbors. In the case of a 1D CA, this was simple: if a cell index was <code>i</code>, its neighbors were <code>i-1</code> and <code>i+1</code>. Here, each cell doesnt have a single index, but rather a column and row index: <code>i,j</code>. As shown in Figure 7.32, the neighbors are <code>i-1,j-1</code> , <code>i,j-1</code>, <code>i+1,j-1</code>, <code>i-1,j</code>, <code>i+1,j</code>, <code>i-1,j+1</code>, <code>i,j+1</code>, and <code>i+1,j+1</code>.</p>
<p>Next, I need to sort out how to actually calculate each cells new state. For that, I need to determine how to reference the cells neighbors. In the case of a 1D CA, this was simple: if a cell index was <code>i</code>, its neighbors were <code>i-1</code> and <code>i+1</code>. Here, each cell doesnt have a single index, but rather a column and row index: <code>i,j</code>. As shown in Figure 7.31, the neighbors are <code>i-1,j-1</code> , <code>i,j-1</code>, <code>i+1,j-1</code>, <code>i-1,j</code>, <code>i+1,j</code>, <code>i-1,j+1</code>, <code>i,j+1</code>, and <code>i+1,j+1</code>.</p>
<p>The Game of Life rules operate by knowing how many neighbors are alive. If I create a counter variable and increment it for each neighbor with a state of 1, Ill have the total of live neighbors.</p>
<pre class="codesplit" data-code-language="javascript">let sum = 0;
@ -691,7 +689,7 @@ board = next;</pre>
<h3 id="exercise-79">Exercise 7.9</h3>
<p>Create a CA using a grid of hexagons (as below), each with six neighbors.</p>
<figure>
<img src="images/07_ca/07_ca_33.png" alt="">
<img src="images/07_ca/07_ca_32.png" alt="">
<figcaption></figcaption>
</figure>
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 KiB

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 KiB

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 70 KiB