CyCell Logo
HOME >> LEARN>>TUTORIAL Part I

Tutorial Part I

PART I PART II PART III PART IV
Your First Cell
^

FileName: tutorial_01.cyr

STATE myFirstState
ENTERING_STATE :: GOAL_AREA=200

Load or enter the code above and hit Compile Rules.

Tab over to the Monitor console. Within the Manual Placement widget, you should see myFirstState listed as one of the STATES. (The other three, Default_Blue, Default_Yellow, and OBSTRUCTION are always present... we'll ignore them.)

Select myFirstState and click the checkbox next to Manual Placement.

Move your mouse over to the Petri Dish and draw your cell... it doesn't really matter what shape. A single small blob will suffice.

Check the Toggle radio button and hit Iterate. Your cell should come alive.

This basic process is how you will begin all the tutorials.

Let's turn now to the program itself.

The Internal Variable GOAL_AREA is set to 200. This command is triggered only on the first pass through the Rules due to the ENTERING_STATE condition. CYCELL will try to keep the cell at 200 nodes.

If you put your mouse over the cell and press SPACE, some data about the cell will be displayed in the Monitor textbox. You'll notice the data may show your cell has fewer (or more) than 200 nodes, especially if you place some more cells and move them so that they adhere to one another. We'll address this in the next tutorial.

Controlling Size
^

FileName: tutorial_02.cyr

STATE myFirstState
ENTERING_STATE :: TYPICAL_AREA=200
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1

Compile the Rules, place a myFirstState cell, and Iterate.

In your monitor, you should see that the ACTUAL_AREA hovers around 200.

We accomplished this by letting GOAL_AREA float instead of setting it directly.

The workhorse here is TYPICAL_AREA. This Internal Variable is not used directly by the program in the way that GOAL_AREA is. Instead, it's just a handy built-in variable as this little size-controlling feedback loop is used so frequently. (In fact, these first three lines -- or at least a version of them -- will be used in almost all the STATEs below. We at CYCELL got so tired defining a U_VAR to handle this for every single set of Rules, that we made TYPICAL_AREA a built-in variable.)

The Rules start by setting TYPICAL_AREA to 200. The second and third lines compare the ACTUAL_AREA of the cell to the TYPICAL_AREA. If the former is smaller than the latter, we add 1 to GOAL_AREA, and vice versa.

As an analogy, this little feedback loop is like a cruise-control in your car. TYPICAL_AREA is the speed setting on your cruise-control, ACTUAL_AREA is your speed, and GOAL_AREA is your accelerator/brake (controlled automatically by the cruise-control.)

When cells are squished together, or migrating, or stretched out, the Area component of their energy function may get overwhelmed by the other energy factors. This little technique will allow your cell's GOAL_AREA to adjust to ensure that the cell's size remains more or less where your want it. You can experiment changing the 1 to 0.1 on the second and third lines. This simply controls how quickly the cell is going to get to your desired TYPICAL_AREA... if you make the values too high, of course, the cell may explode.

Color
^

FileName: tutorial_03.cyr

STATE myFirstState
ENTERING_STATE :: TYPICAL_AREA=200; RGB(200,214,93)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1

Compile the Rules, place a myFirstState cell, and Iterate.

The default colors suck. Change them. Here, we've just added an RGB command that will set the cell's color upon its first pass through this state's rules.

STATE myFirstState
ENTERING_STATE :: TYPICAL_AREA=50
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1
RGB(200,214,93)

We could have also put the RGB command on the last time. The only difference is that in the former placement, the command will be processed only if the ENTERING_STATE condition evaluates true (which only happens once.) In the current placement, however, the command will be processed every cycle. Now, the RGB command takes microseconds to process... so, it doesn't really matter. But, some commands (and conditions) can hog CPU cycles, so get into the habit of writing your Rules with efficiency in mind.

Different States
^

FileName: tutorial_04.cyr

STATE Skin_Cell
ENTERING_STATE :: TYPICAL_AREA=50; RGB(200,34,93)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1


STATE Stomach_Cell
ENTERING_STATE :: TYPICAL_AREA=100; RGB(20,14,233)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1


STATE Eye_Cell
ENTERING_STATE :: TYPICAL_AREA=500; RGB(220,99,23)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=2
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=2

Our Rules now have three different STATEs. Each has been assigned a different color and size. They should all be present in the Manual Placement widget. Create a bunch of each. Drag them around. You'll notice that cells of the same STATE adhere to one another a bit more than cells of different STATEs. As we haven't defined any specific ADHESIONS values, the defaults are being used. We'll get to ADHESIONS in a bit.

U_VAR variables
^

FileName: tutorial_05.cyr

U_VAR apple
U_VAR bear


STATE Skin_Cell
ENTERING_STATE :: TYPICAL_AREA=50; RGB(200,34,93); apple=0; bear=255;
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1
apple+=1; bear-=2
apple(254,INF] :: apple=0
bear[0] :: bear=255
RGB(apple, bear, 100)


STATE Eye_Cell
ENTERING_STATE :: TYPICAL_AREA=20; RGB(10,34,243); apple=20; bear=0
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=2
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=2
bear[0] :: apple+=5
!bear[0] :: apple-=5
apple(500,INF] :: bear=1
apple[20] :: bear=0
TYPICAL_AREA=apple

Here, we have declared two U_VAR variables: apple and bear. Not very creative names. If you want to sound cool, you can name your variables things like "methionine" or "triose_phosphate_isomerase." It's up to you.

Upon entering the Skin_Cell state, both variables are set to certain values.

On the fifth line in the state, we add 1 to apple and subtract 2 from bear. This will occur every cycle.

The next two lines show how variables can be used in conditions: if apple is greater than 254, it's reset to 0. Likewise, bear gets set back to 255 if it hits 0.

You can do many things with variables. Here, we use them to control the color.

In the Eye_Cell state, we use the apple variable to control the size. And, we use the bear variable as a Boolean toggle switch. That is, bear starts off at 0, and so long as it remains 0, apple will increase by 5. But, when apple hits 500, bear is set to 1 and henceforth apple's value will be reduced by 5. When apple reaches 0, bear is set back to 0 and the process repeats. All the while, of course, the size is tracking apple causing the cell to grow and shrink. Notice the use of the 'not' signifier (the !) on the fifth sentence in the Eye_Cell state. This condition could just as well have been written as bear(0,INF].

Keep in mind that a cell will only process the portion of the Rules pertaining to its particular STATE. So, a cell in the Eye_Cell state will have it's apple and bear variables changed only by commands in the Eye_Cell portion of the Rules.

CHANGE_STATE
^

FileName: tutorial_06.cyr

U_VAR apple

STATE Skin_Cell
ENTERING_STATE :: TYPICAL_AREA=100; RGB(255,222,230); apple=0
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1
apple+=1
apple[100] :: CHANGE_STATE{Eye_Cell}

STATE Eye_Cell
ENTERING_STATE :: TYPICAL_AREA=40; RGB(140,240,233); apple=0
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=2
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=2
apple+=1
apple[75] :: CHANGE_STATE{Skin_Cell}

Here, we use apple as a timer. It starts at 0 in the Skin_Cell state. When it reaches 100, the STATE will change to Eye_Cell. Once in Eye_Cell, the counter is reset to 0 and once it reaches 75, it's back to Skin_Cell.

Not very exciting. But, place a dozen or so of each type of cell in the Petri Dish... you'll witness how the ADHESIONS can really affect their behavior.

This would also be good time to Monitor a cell and check Debug Monitored Cell. Watch the debug console carefully. You will notice that the cell "Returns" from the Rules immediately after CHANGE_STATE is processed. CHANGE_STATE (along with MITOSIS, DIE, and of course END) will terminate the Rules upon being processed.

Finally, if you place a bunch of cells, you'll notice that they all switch back and forth in a regular manner... very unlife-like. We'll fix that in the following section with the POISSON condition.

POISSON
^

FileName: tutorial_07.cyr

U_VAR apple

STATE Skin_Cell
ENTERING_STATE :: TYPICAL_AREA=100; RGB(255,222,230); apple=0
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1
POISSON[10] :: apple+=10
apple[100,INF] :: CHANGE_STATE{Eye_Cell}

STATE Eye_Cell
ENTERING_STATE :: TYPICAL_AREA=40; RGB(140,240,233); apple=0
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=2
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=2
POISSON[4] :: apple+=4
apple[75,INF] :: CHANGE_STATE{Skin_Cell}

There are just a few subtle changes in Skin_Cell. Instead of incrementing apple each time, we're only going to increment it if POISSON[10] evaluates true. The Rules will only pass this condition, on average, every 10 times the condition is evaluated. The key is "on average." You never know when it's going to evaluate true, but you can rest assured that in the long-run, it will tend toward every 10 cycles... on average.

(Since apple is only being incremented every 10 times on average, we've made it increment by 10 just so that we'll get, more or less, the same pace as the last example.)

Similar changes have been made in Eye_Cell.

Now, when you place a few dozen cells of each type, they will switch back and forth in Poisson-ishy random manner.

You will note that the conditionals in the last line of each STATE have changed a bit. For instance, instead of apple[75], we now have apple[75,INF]. Why? As we're incrementing apple by 4 in Eye_Cell, it will never equal 75. In short, don't use 'equal to' if a 'greater than' or 'less than' will do; with larger sets of Rules, it would have been easy to have introduced a bug by changing the apple increment from 1 to 4 without remembering that 75 isn't a multiple of 4.

G_VAR Variables
^

FileName: tutorial_08.cyr

G_VAR ooze
G_VAR mucous

U_VAR apple
U_VAR cat

STATE Skin_Cell
ENTERING_STATE :: TYPICAL_AREA=100; RGB(255,222,230); apple=0; cat=0;
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=2
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=2
POISSON[2] :: apple+=1;
cat=0
apple[0,10] :: cat=1;
cat[1] :: TYPICAL_AREA+=30; EMIT{ooze}
cat[0] :: TYPICAL_AREA=100
apple[100,INF] :: apple=0;


STATE Eye_Cell
ENTERING_STATE :: TYPICAL_AREA=50; RGB(4,222,30); apple=0;cat=0;
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=2
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=2
POISSON[2] :: apple+=1;
cat=0
apple[0,10] :: cat=1;
cat[1] :: TYPICAL_AREA+=30; EMIT{mucous}
cat[0] :: TYPICAL_AREA=50
apple[100,INF] :: apple=0;


STATE Probe_Cell
ENTERING_STATE :: TYPICAL_AREA=50; RGB(70,70,250)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=2
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=2
RGB(70,70,250)
mucous[0.01,INF]; mucous(ooze,INF] :: RGB (70,250,70)
ooze[0.01,INF]; ooze(mucous,INF] :: RGB (250,70,70)

We've defined two G_VAR variables, each with very scientific names: ooze and mucous.

Incorporating some of the lessons learned so far, you'll notice that apple and cat are being used to make the Skin_Cell and Eye_Cell swell every so often. Why do we make it swell? No reason... just looks cool. But, you'll notice that when the cell does swell, it also processes the command EMIT.

The Skin_Cell will emit ooze, and the Eye_cell will emit mucous.

When the EMIT command is processed, every Node in the cell will set the referenced G_VAR variable to 1.0. From that point on, the value will diffuse from Node to Node, decreasing in value the farther it diffuses. You can check Draw Gradient to watch.

Each G_VAR variable has a built-in default color. The first G_VAR declared will be red, the second green, and so forth. (Although you may declare as many G_VARs as you wish, there are only 7 built-in colors; the default color for higher-numbered G_VAR is white.)

Note that the values diminish very quickly. If you are going to have a cell change its behavior based on a G_VAR value that is diffusing through the Petri Dish, you'll probably be dealing with 1/100ths or 1/1000ths. To get a feeling for the drop-off in the values as a function of distance, place a Probe_Cell and drag it around some Skin_Cells and Eye_Cells. It will turn red when near ooze and green when near mucous. You can adjust the levels in the conditional (or, better yet, Monitor the cell and watch the values) to get a feel for the way G_VAR values diffuse.

(We've been asked why the mucous(ooze,INF) and ooze(mucous,INF] conditions are present in the last two lines. They are to cover cases when both ooze and mucous are at levels strong enough to trigger the color change; we wish to turn the cell red only if there is more ooze than mucous and vice versa.)

G_VARs have a very important purpose... they allow cells to communicate across distances. Without question, this occurs in real biological systems. As you'll see, though, its a very imprecise way to send signals.

V_VAR vectors and MIGRATE
^

FileName: tutorial_09.cyr

G_VAR ooze

V_VAR myFirstVector

STATE Sun_Cell
ENTERING_STATE :: TYPICAL_AREA=600; RGB(245,245,66)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=2
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=2
EMIT{ooze}

STATE Uranus_Cell
ENTERING_STATE :: TYPICAL_AREA=150; RGB(131,232,245)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=2
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=2
SET_VEC{myFirstVector}[ooze]; MIGRATE[myFirstVector,-60,90]

STATE Venus_Cell
ENTERING_STATE :: TYPICAL_AREA=150; RGB(31,232,145)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=2
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=2
SET_VEC{myFirstVector}[ooze]; MIGRATE[myFirstVector,-50,-90]

STATE Asteroid_Cell ENTERING_STATE :: TYPICAL_AREA=80; RGB(186,156,147)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=2
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=2
SET_VEC{myFirstVector}[ooze]; MIGRATE[myFirstVector,-60]

STATE Comet_Cell ENTERING_STATE :: TYPICAL_AREA=80; RGB(255,255,223)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=2
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=2
SET_VEC{myFirstVector}[.5,.5]; MIGRATE[myFirstVector,-40]

Cells move. Their movement is a vital component of morphogenesis.

In order to tell the cell which way to move, we need to declare a V_VAR. We'll call ours myFirstVector.

We set the direction of a V_VAR with the SET_VEC command. We can have our vector point towards the highest concentration of a particular U_VAR variable in adjoining cells. We can set an actual value ourselves, using the Cartesian coordinate system. Or, we can have it point towards a concentration of a G_VAR variable in the Petri Dish.

Here, we'll explore the second and third possibilities.

Let's start with the last cell first.
The Comet_Cell sets the V_VAR to (0.5, 0.5). This means our vector will point down and to the right. (CYCELL uses graphics-style coordinates... the Y-axis increases in value from top to bottom.) Once we have set the V_VAR, we use it as the curly-bracket parameter in the MIGRATE command.

Now, Uranus_Cell, Venus_Cell, and Asteroid_Cell have their V_VARs set to point to ooze. Our Sun_Cell conveniently emits ooze.

The MIGRATE command for Asteroid_Cell is straightforward; the offset angle is set to 0, so it will just move in the direction of myFirstVector with a 'force' of -60.

The MIGRATE command for Uranus_Cell and Venus_Cell utilize the 'offset-angle' parameter, with one set to 90 and the other to -90. The end result is they will travel at right angles to the direction of myFirstVector.

PUTTING IT ALL TOGETHER: Place a Sun_Cell in the middle of the Petri Dish. Sprinkle a few Uranus_Cells and Venus_Cells around it. When the ooze diffuses out to your 'planets,' they will start orbiting. Yes, CYCELL can double as an orbital mechanics simulator. (Not really.)

Check Draw Vectors to see the directions your cells are trying to travel.

MITOSIS
^

FileName: tutorial_10.cyr

ADHESIONS
<stalk,bud_1>(-6)
<stalk,stalk>(-6)
<stalk,bud_2>(-6)

V_VAR cleavage

U_VAR growth_factor

STATE unchecked_growth
ENTERING_STATE :: TYPICAL_AREA=80; RGB(255,222,230)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1
ACTUAL_AREA[TYPICAL_AREA,INF] :: MITOSIS

STATE controlled_growth
ENTERING_STATE :: TYPICAL_AREA=80; RGB(225,212,200); growth_factor=1000;
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1
ACTUAL_AREA[TYPICAL_AREA,INF]; growth_factor[10,INF] :: MITOSIS

STATE bud_1
ENTERING_STATE :: TYPICAL_AREA=80; RGB(225,112,100)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1
ACTUAL_AREA[TYPICAL_AREA,INF] :: MITOSIS{stalk}

STATE bud_2
ENTERING_STATE :: TYPICAL_AREA=80; RGB(225,112,100)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1
ACTUAL_AREA[TYPICAL_AREA,INF] :: SET_VEC{cleavage}[stalk]; MITOSIS{stalk}[cleavage,90,CCW]

STATE stalk
ENTERING_STATE :: TYPICAL_AREA=80; RGB(125,222,120)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1

Brief Overview

The MITOSIS command causes a cell to split. A few general principles must be remembered.

Another thing to notice is the use of ADHESIONS. As we will have a few different types of cells working together, their ADHESIONS values have been set to be consistent. The syntax of ADHESIONS is straightforward. (To try it out, in the previous tutorial, make the Uranus_Cells and Venus_Cells stick together and watch them try to navigate in opposite directions around the 'sun'... sort of cruel, but interesting.)

STATE unchecked_growth

With that out of the way, let's start with unchecked_growth. This cell type will undergo mitosis continuously, so long as ACTUAL_AREA is large enough. (If this condition were to be removed, the cell would immediately try to split again and again, getting smaller and smaller each time.)

Now, unchecked_growth will completely fill up the Petri Dish.

How does one stop the cells from splitting?

This is actually a very good question. What makes your nose stop growing? Nobody really knows. In fact, there is a fairly good chance you will die someday precisely because a few cells in your body mutate and lose their ability to know when to stop growing. (i.e. cancer.)

There are many techniques you can come up with in CYCELL to limit cell-growth. We've implemented one, as an example, in the next state.

STATE controlled_growth

You'll notice that controlled_growth sets the U_VAR growth_factor to 1000 when it first enters the state. After every division, the value of this variable will be halved. You can do the math and calculate how many divisions are possible before the growth_factor[10,INF] condition is no longer triggered. (You also may notice that this is NOT a very good way of making a scrum with large numbers of cells as the initial value of growth_factor would need to be enormous. But, there are other techniques... this one is just quick and easy.)

STATE bud_1

With bud_1 we begin taking advantage of MITOSIS parameters. MITOSIS{stalk} will perform mitosis, but it will result in a 'daughter' cell belonging to STATE stalk.

You'll notice that the cleavage plane of the cell is random.

STATE bud_2

With bud_2 we take control of how our cell divides. Directional mitosis is a thing in real biological systems. As with MIGRATE, we use a V_VAR vector to get things going.

In the example, SET_VEC{cleavage}[stalk] will set cleavage to a vector pointing towards the average location of adjoining stalk cells. (If there are no adjoining stalk cells, the vector will point in a random direction.)

We use the V_VAR in the MITOSIS command to direct the cell to split along a line parallel to the vector. Importantly, though, we've also set an offset angle of 90; thus, the cleavage line will be perpendicular to the vector. We've finally set the third parameter to CCW (counter-clockwise) which directs which of the two resulting cells will be assigned to state stalk. Phew. In short, we can grow tendrils and whatnot in a specific direction. Change CCW to CW. Change 90 to 45. You should get the gist of it.

Demonstration...

We first place a cell from the uncontrolled_growth state. Then, we place a couple of controlled_growth cells. Then, we place a bud_1 and a couple bud_2s.

A final word about cell growth...

The cell types bud_1 and bud_2 will grow forever. The quick technique we used for controlled_growth isn't all that useful here. If we wanted our bud_2 to make a long snaking tendril with 100 or so cells and then stop, our growth_factor variable would need to start out at something like 2^100. That's silly.

You will be tempted to think that there must be some kind of counter that we could implement that will tell our bud_2 cell to quit dividing after, say, 100 splits. But, it doesn't work that way, and that's precisely what we were getting out with the fourth point in the "Brief Overview" section above. Specifically, although it might look like its the same bud_2 cell moving along and spitting out stalk cells, in reality after mitosis we're left with two brand-new cells, so to speak, who have never undergone mitosis before! Not to get all philosophical about it, but in truth, a cell can't undergo mitosis more than once, for after mitosis it's no longer the same cell.

What we really want is a way to count the generations of a cell. That's a tricky thing to keep track of. For, after mitosis, every variable into which we could encode such a counter is chopped in two, making it very unwieldy to maintain some permanent record of a cell's biography. (Not impossible... just unwieldy.)

GAP_JUNCTIONS
^

FileName: tutorial_11.cyr

ADHESIONS
<Stem,Stem>(-6)
<Stem,Skin>(-6)
<Skin,Skin>(-6)
<Mutant,Skin>(-6)
<Mutant,Mutant>(-6)
<Skin,Macrophage>(-9)
<SELF,Macrophage>(-18)
<Macrophage,Macrophage>(8)
<Stem,Macrophage>(-6)

GAP_JUNCTIONS
<Skin,Skin,intruder_alert>(0.01)
<Skin,Mutant,intruder_alert>(0.01)
<Skin,Macrophage,intruder_alert>(0.01)

U_VAR generic_a
U_VAR generic_b

U_VAR color_aux

U_VAR intruder_alert

V_VAR attack_vector

STATE Stem
ENTERING_STATE :: TYPICAL_AREA=40; RGB(245,245,160); generic_a=2800
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=.1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=.1
ACTUAL_AREA[TYPICAL_AREA,INF] :: SET_RAND{generic_b}[0,100];
ACTUAL_AREA[TYPICAL_AREA,INF]; generic_b[0,95] :: MITOSIS
ACTUAL_AREA[TYPICAL_AREA,INF]; generic_b(95,100] :: MITOSIS{Macrophage}
generic_a[0,10] :: CHANGE_STATE{Skin}

STATE Macrophage
ENTERING_STATE :: TYPICAL_AREA=40; RGB(150,150,255)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=.1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=.1
!intruder_alert[0] :: SET_VEC{attack_vector}[intruder_alert]; MIGRATE[attack_vector,-80]
intruder_alert=0;

STATE Skin
ENTERING_STATE :: TYPICAL_AREA=40; RGB(255,255,255)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=.1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=.1
color_aux=intruder_alert;
color_aux*=2;
GREEN=255; BLUE=255
GREEN-=color_aux;
BLUE-=color_aux;
intruder_alert*=.95
ADJ_MEMBRANES{MEDIUM}[0]; intruder_alert[0]; POISSON[2000] :: CHANGE_STATE{Mutant}
intruder_alert[0,.1] :: intruder_alert=0;

STATE Mutant
ENTERING_STATE :: TYPICAL_AREA=40; RGB(20,250,20); generic_a=0;
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=.1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=.1
intruder_alert=300;
ADJ_CELLS{Macrophage}[1] :: generic_a+=2
ADJ_CELLS{Macrophage}[2] :: generic_a+=4
ADJ_CELLS{Macrophage}[3] :: generic_a+=6
ADJ_CELLS{Macrophage}[4,INF] :: generic_a+=10
generic_a[100,INF] :: CHANGE_STATE{Skin}

There's a lot of stuff going on with this one. We'll walk you through it.

Where we're heading...

Let's start with the end-result and work backwards. We're going to grow a scrum of Skin cells. Some of them will be Macrophages. These blueish Macrophages comprise our scrum's immune system. Occasionally, one of the Skin cells will turn into a greenish-Mutant cell! Luckily, the Mutant cell exudes a protein, which is spread from cell to cell (with a reddish-tint.) When our Macrophages sense it, they MIGRATE towards the source to attack! After the Macrophages have been in contact with it long enough, the Mutant cell will CHANGE_STATE back to a Skin cell.

Now, on to the code.

Adhesions

First, the ADHESIONS. As before, we are working with several types of cells. With the default adhesions, different types of cells don't adhere to each other. We've set things so that everything sticks together to the same degree, more or less. We've made our Macrophages avoid each other by assigning them a positive adhesions value. And, we've lowered the default Macrophage-SELF value just to make these types of cells a little more slithery. In short, the values are primarily for aesthetics in this set of Rules; there's nothing magical about the values, so don't get overwhelmed.

The GAP_JUNCTIONS

The real point of this lesson pertains to GAP_JUNCTIONS. As you already know, you can declare various U_VAR variables. But, remember that these 'variables' represent actual stuff in the cell... chemicals, proteins, or whatever. And, like real cells, this stuff can be exchanged between cells.

Unlike G_VAR stuff that just passively oozes out of a cell into the environment, U_VAR values use GAP_JUNCTIONS to engage in inter-cellular active transport. For instance, in the first line, we are telling the program that the Skin cell's level of intruder_alert will be actively transported into an adjoining Skin cell at a rate of 1% per shared membrane. This is not like osmosis where the level must be higher in one cell for it to diffuse into the other cell. Instead, the 1% per membrane will be forced into the other cell regardless of the neighbor's own level of intruder_alert. It's best to keep the rate very low... for instance, if there were 100 shared membranes between two cell's, all of the intruder_alert would be transferred on a single simulation cycle. (And, then that neighbor would transfer it all right back on the next cycle... this would be very unrealistic.) The purpose of GAP_JUNCTIONS is not to simulate a mathematically rigorous PDE solver; rather, it's just a rough way to have substances travel through cell networks in a somewhat realistic manner.

When you run these Rules, you will see the Mutant cells transport their intruder_alert into the adjoining cells. And, those cells do the same... We've linked the Skin cell's color to the level of intruder_alert so you will be able to see the spreading intruder_alert as a red tint.

The initial growth

The Stem cells create a modest-size scrum just to get things started. You'll notice that about every 5% of their mitoses will create a Macrophage cell. We've accomplished this using the SET_RAND command.

Once we have the appropriate number of cells in the Petri Dish (controlled, once again, by the multiple halving of a generic U_VAR) we turn all the stem cells to Skin cells.

Rise of the mutant cells

On the 10th line under the Skin cell definition, we force the cell to turn into a Mutant. There are three conditions.

The first pertains merely to aesthetics; we don't want mutants to arise on the periphery, so we require that none of the Skin cells can have any membranes touching the MEDIUM.

The second condition requires that there not be any intruder_alert in our cell, and the third is just a POISSON to make the mutations arise at a random frequency.

The Mutant cells, when they arise, maintain a constant level of intruder_alert at 300. At each cycle, due to the GAP_JUNCTIONS definitions, a percentage of this value will be transported into their neighbors. (You'll notice in Skin cell that we decrease the value of intruder_alert by 5% each cycle, and set it to 0 when it gets below 0.1. This is to prevent the entire scrum filling up with intruder_alert.)

The 4th through 8th lines under the Skin cell definition are worth looking at. They deal with how the cell is colored. We want a reddish-tint to correspond to the level of intruder_alert. With computer graphics, to make something more red, we decrease the levels of green and blue. We've used a little throw-away U_VAR called color_aux to handle this. In short, these five-lines are more for the user's benefit (and to help with debugging) than they are with controlling the cell's behavior. So, don't get confused them... there are any number of ways to mess around with the color and whatnot, and we threw this in here just to demonstrate one way of making a 'tint'.

Attack of the macrophages

Meanwhile, the Macrophage cells are just hanging out. But, when their level of intruder_alert exceeds 0, they spring into action. Using SET_VEC they set a vector towards the greatest concentration of intruder_alert, and MIGRATE in that direction.

When a Macrophage makes contact with a Mutant, a counter begins. You'll notice we make the count faster depending on how many Macrophage cells are touching the Mutant. When the count reaches 100, the Mutant turns back into a normal Skin cell.

A Lizard?

FileName: tutorial_12.cyr

ADHESIONS
<Stalk,Bud>(-6.5)
<SecondStage,Stalk>(-6.5)
<SecondStage,Bud>(-6.5)

<Posterior,Thorax>(-6.5)
<Anterior,Thorax>(-6.5)
<Posterior,Posterior>(-6.5)
<Thorax,Thorax>(-6.5)
<Anterior,Anterior>(-6.5)

<Posterior,SecondStage>(-6.5)
<Anterior,SecondStage>(-6.5)
<Thorax,SecondStage>(-6.5)

<Posterior,Leg_Bud>(-9.5)
<Anterior,Leg_Bud>(-9.5)
<Thorax,Leg_Bud>(-9.5)

<Leg,Leg>(-4.5)

<Leg,Leg_Bud>(-6.5)
<Leg,Leg_Tip>(-6.5)
<Leg_Tip,Leg_Bud>(-6.5)

GAP_JUNCTIONS
<Bud,Stalk,growth_factor>(0.01)
<Stalk,Stalk,growth_factor>(0.01)

V_VAR cleavage
V_VAR stretch_vector
V_VAR migrate_vector

U_VAR growth_factor
U_VAR thorax_growth

G_VAR straight


STATE Bud
ENTERING_STATE :: TYPICAL_AREA=40; RGB(225,112,100)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1
SET_VEC{migrate_vector}[straight]; MIGRATE{migrate_vector}[0,10]
ACTUAL_AREA[TYPICAL_AREA,INF]; POISSON[5] :: SET_VEC{cleavage}[Stalk]; MITOSIS{Stalk}[cleavage,90,CCW]
growth_factor=300
ADJ_CELLS{SecondStage}[1,INF] :: CHANGE_STATE{SecondStage}

STATE Stalk
ENTERING_STATE :: TYPICAL_AREA=80; RGB(125,222,120)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1
growth_factor*=.98
RED=125
RED+=growth_factor
growth_factor[0,0.2] :: CHANGE_STATE{SecondStage}
ADJ_CELLS{SecondStage}[1,INF] :: CHANGE_STATE{SecondStage}
growth_factor[0,100] :: EMIT{straight}

STATE SecondStage
ENTERING_STATE :: TYPICAL_AREA=80; RGB(225,222,120)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1
POISSON[50]; growth_factor[0,1] :: CHANGE_STATE{Posterior}
POISSON[50]; growth_factor(1,10] :: CHANGE_STATE{Thorax}
POISSON[50]; growth_factor(10,INF] :: CHANGE_STATE{Anterior}

STATE Posterior
ENTERING_STATE :: TYPICAL_AREA=80; RGB(197,215,219)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1

STATE Thorax
ENTERING_STATE :: TYPICAL_AREA=80; RGB(217,218,166); thorax_growth=1
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1
ACTUAL_AREA[TYPICAL_AREA,INF]; thorax_growth[1,INF]; ADJ_MEMBRANES_PERCENT{MEDIUM}[20,INF] :: MITOSIS
thorax_growth[0,1); ADJ_CELLS{Leg_Bud}[0]; ADJ_MEMBRANES_PERCENT{MEDIUM}[20,INF]; ADJ_CELLS{Posterior}[1,INF] :: CHANGE_STATE{Leg_Bud}
thorax_growth[0,1); ADJ_CELLS{Leg_Bud}[0]; ADJ_MEMBRANES_PERCENT{MEDIUM}[20,INF]; ADJ_CELLS{Anterior}[1,INF] :: CHANGE_STATE{Leg_Bud}
thorax_growth[0,1) :: TYPICAL_AREA=40

STATE Anterior
ENTERING_STATE :: TYPICAL_AREA=80; RGB(237,134,134)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=.1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=.1
growth_factor[30,100] :: TYPICAL_AREA=50;
growth_factor(100,INF] :: TYPICAL_AREA=30;

STATE Leg_Bud
ENTERING_STATE :: TYPICAL_AREA=80; RGB(37,34,234); growth_factor=100;
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=1
growth_factor[100] :: SET_VEC{cleavage}[MEDIUM]; MITOSIS{Leg_Tip}[cleavage,90,CCW]

STATE Leg
ENTERING_STATE :: TYPICAL_AREA=40; RGB(90,134,145)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=.1
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=.1
//SET_VEC{migrate_vector}[straight]; MIGRATE{migrate_vector}[0,10]

STATE Leg_Tip
ENTERING_STATE :: TYPICAL_AREA=40; RGB(237,134,234)
ACTUAL_AREA(TYPICAL_AREA,INF] :: GOAL_AREA-=.5
ACTUAL_AREA[0,TYPICAL_AREA) :: GOAL_AREA+=.5
ACTUAL_AREA[TYPICAL_AREA,INF]; ADJ_CELLS{Leg_Bud}[0]:: SET_VEC{cleavage}[Leg]; MITOSIS{Leg}[cleavage,90,CCW]
ACTUAL_AREA[TYPICAL_AREA,INF]; !ADJ_CELLS{Leg_Bud}[0]:: SET_VEC{cleavage}[Leg_Bud]; MITOSIS{Leg}[cleavage,90,CCW]
SET_VEC{migrate_vector}[straight]; MIGRATE{migrate_vector}[0,15]
straight[0,.1] :: CHANGE_STATE{Leg}

Hehehe... This one's a monster. It incorporates nearly everything discussed so far.

It's included to demonstrate how difficult it is to use mere rules to create a complex form. This lizard-ish looking form might self-assemble most of the time. But, there are a gazillion ways it can go wrong.

While we won't go through this one step-by-step, a few standard techniques are worth mentioning.

First, as the Bud grows a tendril, the cells it produces emit a G_VAR gradient that pushes the Bud away. This usually makes the tendril grow straight.

Second, while the thing is grown, a U_VAR is flowing through the body using a GAP_JUNCTION. This creates a gradient that we can use to differentiate the body into three sections.

Once we have the sections defined, we can place leg-buds at the intersections of the three components. These then grow until they are outside the range of the left-over G_VAR gradient referenced above.

Play around with it... there are a million different ways to make it go ka-ployie.

It's an ad-hoc, unreliable, and brittle system. There must be something more behind self-assembly. The next set of tutorials reveal what that something might be.

ON TO PART II...