More Selection BY Consequences...

[From Bruce Abbott (941220.1400 EST)]

The program appearing below provides an example of "selection BY consequences"
that even Rick Marken will like! It is based on my two-level e. coli whose
top-level control system regulates the (perceived) levels of stored nutrients.
Four e. coli are created with certain of their control-system parameters
chosen at random; they then "do their stuff" in an environment containing a
single source of nutrient. The "fit" survive, the rest die and are replaced
by new variants of e. coli.

To create several e. coli at once, I revamped my old code to create an e. coli
Turbo Pascal "object." If you haven't had the chance to try object-oriented
programming, here's an example you can use as a guide. Please note, however,
that I violated one of the principles of object-oriented programming by
directly accessing the internal variables of the ecoli object. I did this
primarily to increase execution speed, but a proper implementation of the
object would include procedures and functions through which the internal
variables are set and read. When these are included the object is said to be
"encapsulated." Encapsulation allows you to change the way internal variables
are stored/computed without having to alter any code that references the
object. An example function for getting the current level of stored fuel
appears in the program.

Because an object is just a "type," you have to declare a variable of that
type before you can use an object. In this program I declared an array
variable called Bug:

var Bug: array[1..4] of ecoli;

This provides the four e. coli instances visible on the screen, accessed as
Bug[1], Bug[2], etc.

An object might be described as a record with attached procedures and
functions. You access the variables, procedures, and functions in an object
just as you would the fields of a record. Thus, to initialize Bug[1], you
write the statement

Bug[1].Init;

which calls the Init procedure of the ecoli object. Alternatively, you can
access the procedures and functions from within a WITH clause:

with Bug[1] do
begin
  Init;
  Step;
end;

This again is exactly parallel to the way records are used.

Enjoy!

Regards,

Bruce

{***********************************************************************
* ECOLI7A: SELECTION BY CONSEQUENCES *
* *
* Programmer: Dr. Bruce B. Abbott *
* Psychological Sciences *
* Indiana U. - Purdue U. *
* Fort Wayne, IN 46805-1499 *
* (219) 481-6399 *
* *
* Created: 11/05/94 *
* *
* This program simulates the "swim and tumble" behavior of e. coli *
* using a two-level perceptual control system. The bottom level *
* regulates change in nutrient levels by altering the rate of *
* tumbling inversely with the rate of change of sensed nutrient. *
* The direction of e. coli's travel after tumbling is random. *
* *
* The second level regulates the gain of the first-level system *
* according to the e. coli's level of stored nutrients relative to *
* a reference level of 100 units. E. coli is assumed to absorb the *
* nutrients around it at a rate proportional to nutrient *
* concentration, and to use up nutrients at a rate proportional to *
* its speed. *
* *
* The environment of e. coli contains a food sources indicated by *
* a small rectangle. Nutrient level is 100 at the source, but *
* decreases with the square of the radial distance from the source. *
* Thus, as e. coli wanders away from the sources, the level of *
* nutrient in its vicinity drops off rapidly. *
* *
* In this simulation, four e. coli are "born" with randomly selected *
* control parameters. If an e. coli either runs out of stored *
* nutrient ("fuel") or reaches its maximum stored nutrient level *
* (200, thus "bursting" the cell), it dies and is replaced by a new *
* e. coli with another randomly selected set of parameters. Those *
* born with the "right" parameters for this environment survive, *
* the rest do not survive. This rather brutal "consequence" acts to *
* "select" well-adapted e. coli over time. *
* *
* The parameters selected for e. coli are as follows: *
* 1. Speed: The rate of movement. Faster e. coli go farther *
* in a unit of time but use up more fuel. *
* 2. Min T: The minimum increment in time since last tumble. *
* Higher values mean higher tumble rates. *
* 3. Gain2: Multiplier that converts fuel-level error to gain *
* of the low-level nutrient rate control system. *
* When this is positive and fuel is below reference, *
* nutrients attract. *
***********************************************************************}

program Ecoli7;

uses crt, graph, grUtils;

const
  TWOPI = PI * 2;
  ENDSESSION = 50000;

type
  ecoli = object
    eX, eY, eColor, eT, eT0, eDeltaT, eTmax,
    eTimeSinceTumble, eMinTime: integer;
    eNewNut, eNutCon, edNutRef, edNutError, edNut, eGain, eMaxGain,
    eAbsorbRate, eLossRate, eGain2: real;
    eFuel, eFuelRef: real;
    eEcoliX, eEcoliY,
    eSpeed, eAngle: real;
    eTimeAlive: longint;
    function FuelLevel: real;
    procedure Tumble;
    procedure GetNutLevel(NutLevel: real);
    procedure Init;
    procedure Move;
    procedure Step;
  end;

function ecoli.FuelLevel: real;
begin
  FuelLevel := eFuel;
end;

procedure ecoli.Tumble;
begin
  eAngle := TwoPi * Random;
end;

procedure ecoli.Init;
begin
  eEcoliX := Random * 250.0;
  eEcoliY := Random * 250.0;
  eX := Round(eEcoliX);
  eY := Round(eEcoliY);
  eColor := white;
  eSpeed := 1.0;
  edNutRef := 0.00; { Reference rate of change in concentration }
  edNut := 0;
  eTmax := 200;
  eT0 := 5;
  eT := 0;
  eMinTime := 1;
  eTimeSinceTumble := 0;
  eGain :=50.0;
  eMaxGain := 50000.0;
  eFuelRef := 100;
  eFuel := 90;
  eGain2 := 50;
  eAbsorbRate := 0.005;
  eLossRate := 0.2;
  eAngle := PI/2;
end;

procedure ecoli.Move;
begin
  PutPixel(eX, eY, Black);
  PutPixel(eX-1, eY, Black);
  PutPixel(eX, eY-1, Black);

  eEcoliX := eEcoliX + eSpeed * cos(eAngle);
  eEcoliY := eEcoliY + eSpeed * sin(eAngle);
  eX := Round(eEcoliX);
  eY := Round(eEcoliY);

  PutPixel(eX, eY, eColor);
  PutPixel(eX-1, eY, eColor);
  PutPixel(eX, eY-1, eColor);
end;

procedure ecoli.GetNutLevel(NutLevel: real);
begin
  eNewNut := NutLevel;
end;

procedure ecoli.Step;
var
  NewNut: real;
begin
  edNut := edNut * 0.25 + 0.5 * (eNewNut - eNutCon);
  eDeltaT := Round(eGain * (edNutRef - edNut));
  eT := eT + eT0 + eDeltaT;
  inc(eTimeSinceTumble);
  if eT < 0 then eT := 0;
  if (eT > eTmax) and (eTimeSinceTumble >= eMinTime) then
    begin
      Tumble;
      eT := 0;
      eTimeSinceTumble := 0;
    end;
  eNutCon := eNewNut;
  eFuel := eFuel + eAbsorbRate*eNutCon - eLossRate*eSpeed;
  if eFuel < 0 then eFuel := 0 else
    if eFuel > 200 then eFuel := 200;
  eGain := eGain2 * (eFuelRef - eFuel); {50}
  if (eGain > eMaxGain) then eGain := eMaxGain
  else if (eGain < -eMaxGain) then eGain := -eMaxGain;
end;

var
  Bug: array[1..4] of ecoli;
  MaxX, MaxY, Line, NutrX, NutrY, i: integer;
  NutMag, NutLevel, DeadBugs: real;
  Ch: char;
  Clock: longint;

procedure ShowReal(x,y: integer; v: real);
var s: string;
begin
str(v:6:2,s);
setfillstyle(0,0);
bar (x,y,x+textwidth(s),y+textheight(s));
outtextxy(x,y,s);
end;

procedure InitScreen;
var
  I, Y: integer;
begin
  ClrScr;
  InitGraphics;
  SetFillStyle(0,0);
  MaxX := GetMaxX; MaxY := GetMaxY;
  SetColor(Yellow);
  OutTextXY(MaxX div 2 - 200, 5,
    'E. COLI SIMULATION: SELECTION BY CONSEQUENCES');
  OutTextXY(20, MaxY-10, 'Press ESC to Quit...');
  SetColor(White);
  Rectangle(0, 15, MaxX-150, MaxY-15);
  Rectangle(MaxX-150, 15, MaxX, MaxY-15);
  SetColor(lightblue);
  OutTextXY(MaxX-115, 20, 'E. Coli #1');
  SetColor(yellow);
  OutTextXY(MaxX-115, 120, 'E. Coli #2');
  SetColor(lightgreen);
  OutTextXY(MaxX-115, 220, 'E. Coli #3');
  SetColor(lightred);
  OutTextXY(MaxX-115, 320, 'E. Coli #4');
  SetColor(lightgray);
  Y := 35;
  for i := 1 to 4 do
    begin
      OutTextXY(MaxX-135, Y, ' Fuel:');
      OutTextXY(MaxX-135, Y+15, ' Speed:');
      OutTextXY(MaxX-135, Y+30, ' Gain2:');
      OutTextXY(MaxX-135, Y+45, 'T Rate:');
      OutTextXY(MaxX-135, Y+60, 'Status:');
      Y := Y+100;
    end;
  OutTextXY(MaxX-135, MaxY-50, ' Dead:');
  SetColor(white);
  SetViewPort(21, 16, MaxX-151, MaxY-16, True);
end;

procedure ShowFuel;
var
  i, Y: integer;
begin
  SetViewPort(0, 0, MaxX, MaxY, True);
  Y := 35;
  for i := 1 to 4 do
    begin
      ShowReal(MaxX-70, Y, Bug[i].eFuel);
      Y := Y + 100;
    end;
  SetViewPort(21, 16, MaxX-151, MaxY-16, True);
end;

procedure ShowParams;
var
  i, Y: integer;
begin
  SetViewPort(0, 0, MaxX, MaxY, True);
  Y := 35;
  for i := 1 to 4 do
    begin
      ShowReal(MaxX-70, Y, Bug[i].eFuel);
      ShowReal(MaxX-70, Y+15, Bug[i].eSpeed);
      ShowReal(MaxX-70, Y+30, Bug[i].eGain2);
      ShowReal(MaxX-70, Y+45, Bug[i].eT0);
      SetColor(Green);
      OutTextXY(MaxX-38, Y+60, 'OK');
      SetColor(White);
      Y := Y + 100;
    end;
  ShowReal(MaxX-70, MaxY - 50, DeadBugs);
  SetViewPort(21, 16, MaxX-151, MaxY-16, True);
end;

procedure DrawNut(X, Y: integer);
begin
  Rectangle(X-2, Y-2, X+2, Y+2);
  Rectangle(X-1, Y-1, X+1, Y+1);
end;

procedure SetParams(BugNo: integer);
begin
  with Bug[BugNo] do
    begin
      if (eX < 0) or (eY < 0) or (eX > (MaxX-150)) or (eY > MaxY-20)
        then
          begin
            eEcoliX := Random * 250.0;
            eEcoliY := Random * 250.0;
          end;
      eFuel := 100;
      eGain2 := Random * 200.0 - 100.0;
      eSpeed := Random * 2.0 + 0.5;
      eT0 := Random(20);
    end;
end;

procedure InitSim;
begin
  Randomize;
  DeadBugs := 0;
  NutMag := 100.0; { max concentration }
  NutrX := (MaxX-150) div 2;
  NutrY := MaxY div 2;
  DrawNut(NutrX, NutrY);
  Bug[1].Init;
  Bug[2].Init;
  Bug[3].Init;
  Bug[4].Init;
  Bug[1].eColor := lightblue;
  Bug[2].eColor := yellow;
  Bug[3].eColor := lightgreen;
  Bug[4].eColor := lightred;
  for i := 1 to 4 do SetParams(i);
end;

procedure ShowDeath(BugNo: integer);
var
  i, Y: integer;
  S: String;
begin
  SetViewPort(0, 0, MaxX, MaxY, True);
  SetFillStyle(0,0);
  SetColor(LightRed);
  Y := BugNo * 100 - 5;
  S := 'DEAD';
  Bar (MaxX-55 ,Y, MaxX-55+textwidth(s), Y+textheight(s));
  for i := 1 to 20 do
    begin
      OutTextXY(MaxX-55, Y, S);
      delay(100);
      Bar (MaxX-55 ,Y, MaxX-55+textwidth(s), Y+textheight(s));
      delay(50);
    end;
  SetColor(White);
  SetViewPort(21, 16, MaxX-151, MaxY-16, True);
end;

function NutConcen(X, Y: real): real;
{ Nutient concentration at point X, Y: environment function }
var
  Dist: real;
begin
  Dist := Sqrt(Sqr(X - NutrX) + Sqr(Y - NutrY));
  NutConcen := NutMag / (1 + 0.001*(Sqr(Dist)));
end;

begin
  InitScreen;
  InitSim;
  ShowParams;
  repeat
    inc(Clock);
    for i := 1 to 4 do
      with Bug[i] do
        begin
          Move;
          GetNutLevel(NutConcen(Bug[i].eX, Bug[i].eY));
          Step;
          if (eFuel = 0.0) or (eFuel = 200.0) then
            begin
              ShowParams;
              DeadBugs := DeadBugs + 1.0;
              Sound(4000);
              Delay(10);
              NoSound;
              ShowDeath(i);
              SetParams(i);
              ShowParams;
            end;
        end;
    DrawNut(NutrX, NutrY);
    if (Clock MOD 10 = 0) then ShowFuel;
    if Keypressed then Ch := ReadKey;
  until (Ch = #27) or (Clock >= ENDSESSION);
  if (Ch <> #27) then Ch := ReadKey;
  RestoreCRTMode;
  CloseGraph;
end.