Concurrent VI-VI Simulation

[From Bruce Abbott (941018.1030 EST)]

Bill Powers (941017.1030 MDT)

Bruce Abbott (941016.1530 EST)

I've actually WRITTEN programs that execute VI (and other) schedules
of reinforcement and collect the data, so if you like we can use one of
those as the basis for the environmental side of the simulation.

Wonderful. I'd like to see the programs. I can read the source code if
you email it to me (and Rick) or put it on the net.

Bill, I haven't fished out my old programs yet. Meanwhile, here is my version
of the simulation, complete with a VERY dumb pigeon. Both the pigeon and
schedule are modeled as constant-probability response generators. As I have
not yet provided any keyboard input (except for ESCaping out), you have to
change parameters in the InitShed procedure. I have provided a very simple
readout of performance (textmode) that updates every 100 cycles.

The program runs the simulation until 360,000 cycles (1 hour real-time
equivalent) or until it receives and ESC keypress. It then holds the display
until you press any key.

The two VI schedules are instances of the VIsched object defined at the top of
the program. I decided to use Borland Pascal's object capability to
demonstrate how easy it is to create any number of independent objects (in
this case VI schedules) once you have defined one. Once we begin to model the
pigeon in a serious way, we can define a control system object and then create
as many as we like.

This version of the simulation is admittedly "quick and dirty," (e.g., no
COD), but it does work. Try using different schedule values and response
probabilities to see their effect.

Anyone on CSG-L is welcome to copy, use, and modify this program. Enjoy! You
will need Borland Pascal 7.0 or at least Turbo Pascal 6.0 to compile and run
it in its current form.

Although the schedule does not guarantee a given rate of reinforcement,
it does specify the probability that a reinforcer will become available
after a given interval of time. Such opportunities can be lost (the
pigeon may fail to "collect" the reinforcer after it becomes available)
but this in no way negates the fact that they were there.

This suggests that if the pigeon fails to trigger the reinforcer after
some length of time, the opportunity is lost. I had assumed that once
the key was enabled, the apparatus would simply wait for the next peck,
no matter how long it took. ???

Actually, both methods are in use, and there seems to be no agreement as to
which is "better." I prefer to keep the schedule running.

I have a simulation of a simple one-choice operant conditioning
experiment that reproduces data of Staddon's -- we can get into that
later. I've only looked at FR schedules, but it would be interesting to
look at all the others, too.

Very nice, I'd like to do that, perhaps after this project has made some
headway.

Regards,

Bruce

program VIVI;
{

···

**************************************************************************
* *
* Concurrent VI-VI Schedule Simulation *
* *
* Language : Borland Pascal 7.0; Turbo Pascal 6.0 *
* Written by : Bruce Abbott *
* Date : 10/18/94 *
* Description : Provides a simple simulation of concurrent *
* VI-VI schedules. No changeover delays are *
* included. The pigeon's behavior is modeled *
* simply as pecking at two keys with constant *
* probabilities over time and probability of *
* switching to the other key constant. *
* *
**************************************************************************

}
uses CRT;
                             { one time unit = 1/100 second }
type
  VIsched = object
    vMax, { maximum allowed interval }
    vCount, { countdown from vMax }
    vMaxed: longint; { number of times vCount reaches 0 }
    vSetup: boolean; { reinforcer pending }
    vProg, { number of reinforcers programmed }
    vDeliv: longint; { number of reinforcers delivered }
    vTime, { elapsed time schedule active }
    vResp: longint; { number of keypecks }
    vP: real; { probability of reinforcement }
    procedure Init;
    procedure SetMaximumInt(Max: longint);
    procedure SetVI(VISize: integer);
    procedure RunSched(Response: boolean);
    function ProgRft: longint;
    function DelRft: longint;
    function Resp: longint;
    function ETime: longint;
  end;

var
  Ch: char;
  VI1, VI2: VIsched; { two variable interval schedules }
  Key1, Key2: boolean; { key status for two keys }
  Key: integer; { key being pecked (1 or 2 }
  pKey1, pKey2, { probability of a keypeck }
  pSwitch: real; { probability of switching keys }

{ Procedures and functions of the VIsched Object }

procedure VIsched.Init;
{ Initialize schedule to some reasonable values }
begin
    vMax := 5 * 60 * 100; { 5 minutes }
  vCount := vMax;
  vMaxed := 0;
  vSetup := false;
   vProg := 0;
  vDeliv := 0;
   vTime := 0;
   vResp := 0;
      vP := 0.00067; {VI 15-s}
end;

procedure VIsched.SetMaximumInt(Max: longint);
begin
  vMax := Max;
end;

procedure VIsched.SetVI(VISize: integer);
{ VISize is the average interval in minutes }
begin
  vP := 1 /(VISize * 100);
end;

procedure VIsched.RunSched(Response: boolean);
{ Must be called on every clock tick while schedule is running }
begin
  inc(vTime);
  if vCount > 0 then dec(vCount);
  if (Random < vP) or (vCount = 0) then
    begin
      inc(vProg);
      vSetUp := true;
      if vCount = 0 then inc(vMaxed);
      vCount := vMax;
    end;
  if Response then
    begin
      inc(vResp);
      if vSetUp then
        begin
          inc(vDeliv);
          vSetUp := false;
        end;
    end;
end;

{ These functions return values of schedules counters. An object's
  internal variables should never be read directly in order to
  preserve encapsulation. }

function VIsched.ProgRft: longint;
begin
  ProgRft := vProg;
end;

function VIsched.DelRft: longint;
begin
  DelRft := vDeliv;
end;

function VIsched.Resp: longint;
begin
  Resp := vResp;
end;

function VIsched.ETime: longint;
begin
  ETime := vTime;
end;

{ Initializaiton Procedures }

procedure InitScreen(Y: integer);
begin
  Textmode(CO80);
  TextColor(LightGray);
  TextBackground(Black);
  ClrScr;
  gotoXY(25, Y-1); write('Key 1 Key2 p(Key1)');
  gotoXY(5, Y ); write(' KeyPecks');
  gotoXY(5, Y+1); write('Programmed Rft');
  gotoXY(5, Y+2); write(' Delivered Rft');
  gotoXY(5, Y+3); write(' Elapsed Time');
end;

procedure InitSim;
begin
  VI1.Init;
  VI2.Init;

{ Set the average interval in seconds here }

  VI2.SetVI(15);
  VI2.SetVI(45);

{ pKey1 = probability of a peck while pecking on Key 1
    pKey2 = probability of a peck while pecking on Key 2
  pSwitch = probability of switching to the other key }

    pKey1 := 0.1;
    pKey2 := 0.1;
  pSwitch := 0.01;
      Key := 1; { Start pigeon on Key 1 }

  Randomize;
end;

{ Simulation Display Updates }

procedure Show(X, Y: integer; var VI: VIsched);
begin
  with VI do begin
    gotoXY(X, Y ); write(Resp:10);
    gotoXY(X, Y+1); write(ProgRft:10);
    gotoXY(X, Y+2); write(DelRft:10);
    gotoXY(X, Y+3); write(ETime:10);
  end;
end;

{ Pigeon Simulation (Non-PCT at this point: very dumb) }

procedure KeyPeck(var Key1, Key2: boolean);

procedure SwitchKey;
begin
  if Key = 1 then
    begin
      Key := 2;
      Key1 := false;
    end
  else
    begin
      Key := 1;
      Key2 := false;
    end;
end;

begin { procedure KeyPeck }
  if Random < pSwitch then SwitchKey;
  Case Key of
    1: if Random < pKey1 then Key1 := true else Key1 := false;
    2: if Random < pKey2 then Key2 := true else Key2 := false;
  end;
end; { procedure KeyPeck }

{ Compute and display relative rates }

procedure CalcRates(Y: integer);
var
  P1, R1_Prog, R1_Del: real;
begin
  P1 := VI1.Resp / (VI1.Resp + VI2.Resp);
  R1_Prog := VI1.ProgRft / (VI1.ProgRft + VI2.ProgRft);
  R1_Del := VI1.DelRft / (VI1.DelRft + VI2.DelRft);
  gotoXY(45, Y ); write(P1:6:4);
  gotoXY(45, Y+1); write(R1_Prog:6:4);
  gotoXY(45, Y+2); write(R1_Del:6:4);
end;

begin { Main }
  InitScreen(10);
  Ch := #1;
  InitSim;
  repeat
    Keypeck(Key1, Key2);
    VI1.RunSched(Key1);
    VI2.RunSched(Key2);
    if VI1.ETime MOD 100 = 0 then
      begin
        Show(20, 10, VI1);
        Show(30, 10, VI2);
      end;
    if Keypressed then Ch := readkey;
  until (Ch = #27) or (VI1.ETime >= 360000);
    Show(20, 10, VI1);
    Show(30, 10, VI2);
    CalcRates(10);
  Ch := readkey;
  ClrScr;
end. { Main }