This document (and the similar documents for future units) will describe the Lisp code that controls the experiments and how ACT-R is interfaced to them.  It is not necessary that you write the experiments for models in Lisp, but since ACT-R runs in Lisp it is far and away the easiest way to do it and there are tools provided with ACT-R/PM to make the task more manageable. It is not required that you use these tools (ACT-R/PM can process and manipulate windows that contain typical interface elements (text, edit boxes, and buttons) no matter how they are generated in MCL and ACL), but one advantage of the tools provided is that they work the same on different systems. Thus, your model will be able to run on any machine that is running ACT-R 5 even if it does not have a graphic display (ACT-R/PM has a virtual window abstraction built into it).  The ACT-R/PM documentation contains additional information on creating displays for those requiring more advanced experiments.

 

Here is the experiment code from the demo2 model (the contents of the misc and command windows):

 

Misc window:

 

(defvar *response* nil)

 

(defun do-experiment ()

  (if *actr-enabled-p*

      (do-experiment-model)

    (do-experiment-person)))

 

 

(defun do-experiment-person ()

 

  (let* ((lis (permute-list '("B" "C" "D" "F" "G" "H"

                              "J" "K" "L" "M" "N" "P"

                              "Q" "R" "S" "T" "V" "W"

                              "X" "Y" "Z")))

         (text1 (first lis))

         (window (open-exp-window "Letter recognition")))

   

    (add-text-to-exp-window :text text1 :x 125 :y 150)

   

    (setf *response* nil)

   

    (while (null *response*)

      (allow-event-manager window))

   

    *response*))

 

(defun do-experiment-model ()

 

  (let* ((lis (permute-list '("B" "C" "D" "F" "G" "H"

                              "J" "K" "L" "M" "N" "P"

                              "Q" "R" "S" "T" "V" "W"

                              "X" "Y" "Z")))

         (text1 (first lis))

         (window (open-exp-window "Letter recognition")))

   

    (add-text-to-exp-window :text text1 :x 125 :y 150)

   

    (reset)  

    (pm-install-device window)

    (pm-proc-display)

   

    (setf *response* nil)

 

    (pm-run 10)

   

    *response*))

 

(defmethod rpm-window-key-event-handler ((win rpm-window) key)

  (setf *response* (string key))

  (clear-exp-window)

  (when *actr-enabled-p* (pm-proc-display)))

 

 

Commands Window:

 

 

(sgp :v t)

 

(pm-set-params :real-time t :show-focus t)

 

(goal-focus goal)

 

(setf *actr-enabled-p* t)

 

 

Before getting into the code, there are some details about the structure of the code that should be addressed.  First, for all the experiments in the tutorial there will be two functions that run the experiment, one for a human participant and one for the model.  The names of those functions will end in –person and –model respectively.  This is not necessary since most of the code is identical it could easily be written as one function with a few conditionals in it, but by separating it this way we keep it simpler for discussion by avoiding those conditionals.  The main function that is called to run the experiment will call the appropriate function between the two.  We also use the Lisp convention of placing *’s around global variables’ names and always define them.  Because ACT-R is also running in the same Lisp if we were to use a variable in the experiment that was already used in the ACT-R files there could be some serious problems with the model.  By defining the variables you use in the model you will be given a warning if you redefine a variable that is already in use and thus can change the name before it results in problems.

 

We will start with the command window code.  Sgp, pm-set-params, and goal-focus should be familiar from the unit 1 and unit 2 texts.

 

We turn on the verbose flag so that the trace is displayed.  

 

(sgp :v t)

 

We set the system to run in real time and enable the display of the attention ring.

 

(pm-set-params :real-time t :show-focus t)

 

We place the chunk named goal into the goal buffer.

 

(goal-focus goal)

 

 

Then there is the line:

 

(setf *actr-enabled-p* t)

 

*actr-enabled-p* is a global variable defined in ACT-R that operates as a flag to the system to let it know whether a model or person is interacting with the interface. It can also be used by the experiment code (as we will show below).  When it is set to t the system operates as if a model is doing the task and if it is set to nil it operates as if a person is doing it.  It should always be set appropriately in the experiment when using the experiment tools provided.

 

Now we will consider the code in the misc window.  First, we see that there is a global variable defined called *response*:

 

(defvar *response* nil)

 

This variable is going to be used to record the key pressed during the trial.

 

Then there is the function that is called to run the experiment – do-experiment:

 

(defun do-experiment ()

  (if *actr-enabled-p*

      (do-experiment-model)

    (do-experiment-person)))

 

 

All this function does is test the *actr-enabled-p* variable and call the appropriate function for performing the task.

 

Next is the function that runs a person through the task.  This function utilizes several of the experiment building functions included in ACT-R (those in red below) and they will be discussed in detail.

 

(defun do-experiment-person ()

 

  (let* ((lis (permute-list '("B" "C" "D" "F" "G" "H"

                              "J" "K" "L" "M" "N" "P"

                              "Q" "R" "S" "T" "V" "W"

                              "X" "Y" "Z")))

         (text1 (first lis))

         (window (open-exp-window "Letter recognition")))

   

    (add-text-to-exp-window :text text1 :x 125 :y 150)

   

    (setf *response* nil)

   

    (while (null *response*)

      (allow-event-manager window))

   

    *response*))

 

 

What this function does is select a random letter from a list,

 

  (let* ((lis (permute-list '("B" "C" "D" "F" "G" "H"

                              "J" "K" "L" "M" "N" "P"

                              "Q" "R" "S" "T" "V" "W"

                              "X" "Y" "Z")))

         (text1 (first lis))

 

open a window in which to do the task

 

         (window (open-exp-window "Letter recognition")))

   

display the chosen letter in the window

 

    (add-text-to-exp-window :text text1 :x 125 :y 150)

   

clear the response variable

 

    (setf *response* nil)

   

wait for a person to press a key

 

    (while (null *response*)

      (allow-event-manager window))

   

and then return the key that was pressed.

 

    *response*))

 

The experiment construction functions used are:

 

Permute-list – this function takes one parameter which must be a list and returns a randomly ordered copy of that list. 

 

Open-exp-window – this function takes one required parameter which is the title for the window.  It can also take several keyword parameters that control how the window is displayed.  Those are described fully in the manual and will be introduced in later units as necessary.  This function opens a window for performing an experiment and returns that window.  If there is already an experiment window open with that title it clears its contents and brings it to the foreground.  If there is not already an experiment window with that title it closes the previous experiment window if one exists and opens a new window with the requested title and brings it to the foreground.

 

Add-text-to-exp-window – this function draws a static text string on the window that was opened using open-exp-window.  It takes a few keyword parameters. :text specifies the text string to display.  :x and :y specify the pixel coordinate of the upper-left corner of the box in which the text is to be displayed, and there are 3 others that are not shown :height, :width and :color.  Height and width specify the size of the box in which to draw the text in pixels.  The default value for :height is 20 and for :width is 75.  Color specifies in which color the text will be drawn and defaults to black (there is a limited set of colors which are supported see the experiment writing text for more details).

 

While – this is a looping construct.  It takes an arbitrary number of parameters.  The first parameter specifies the test condition, and the rest specify the body of the loop.  The test is evaluated and if it returns anything other than nil all of the forms in the body are executed in order.  This is repeated until the test returns nil.  Thus, while the test is true (non-nil) the body is executed.

 

Allow-event-manager – this function takes one parameter, which must be a window of the experiment.  It calls the appropriate function of the system to handle user interaction.  Giving the system a chance to handle the user interactions is important because otherwise the rpm-window-key-event-handler method (described below) will never be called.

 

 

Next, comes the function to run the model through the task.  Much of the code is exactly the same as for a person (the green text).  There are a couple of extra steps necessary to interface the model to the experiment shown in red, and instead of waiting for a key press as was done for a person the model is run to produce a key press.

 

(defun do-experiment-model ()

 

  (let* ((lis (permute-list '("B" "C" "D" "F" "G" "H"

                              "J" "K" "L" "M" "N" "P"

                              "Q" "R" "S" "T" "V" "W"

                              "X" "Y" "Z")))

         (text1 (first lis))

         (window (open-exp-window "Letter recognition")))

   

    (add-text-to-exp-window :text text1 :x 125 :y 150)

   

    (reset)  

    (pm-install-device window)

    (pm-proc-display)

   

    (setf *response* nil)

   

    (pm-run 10)

   

    *response*))

 

The functions used to interface the model to the task are important, and will be seen in every experiment.  What they do are:

 

Reset – this function call does the same thing as pressing the reset button in the environment.  It returns the model to time 0 and sets the state of the parameters, working memory, and the productions to those specified in the model file the last time it was loaded.

 

Pm-install-device – this function takes one parameter which must be a window or device (a device is an abstract representation used by ACT-R/PM which can be used for more complicated interactions – the full details of their use are covered in the ACT-R/PM manual).  This tells the model which window (or device) it is interacting with.  All of the models actions (key presses, mouse movement and mouse clicks) will be sent to this window and the contents of this window will be what the model can “see”.

  

Pm-proc-display – this function can take one keyword parameter which is not used here. It tells the model to process the display for visual information.  This function makes the model “look” at the window.  Whenever the window is changed you must call pm-proc-display again to make sure the model becomes aware of those changes.  The re-encoding described in the unit can only happen after this function is called, and the bottom-up visual attention mechanism discussed in unit 4 (buffer stuffing) will also only occur when this function is called.  The keyword parameter :clear if specified as t will cause the model to treat the window as all new items – everything there will be considered unattended.

 

Pm-run – this function takes one required parameter which is the time to run a model in seconds and a keyword parameter not used here.  It runs the model just like pressing the run button in the environment.  The model will run until either the requested amount of time passes, or there is nothing left for the model to do (no productions will fire and there are no pending actions that can change the state).  If the keyword parameter called :full-time is specified as t, then the model will be advanced the entire amount of time requested even if there are no productions that can fire.  

 

The final function defined in the misc window is this one:

 

(defmethod rpm-window-key-event-handler ((win rpm-window) key)

  (setf *response* (string key))

  (clear-exp-window)

  (when *actr-enabled-p* (pm-proc-display)))

 

This method is automatically called by the system when a key press occurs in an experiment window by either a person or a model.  It is passed two parameters, the window in which the key press occurred and the character representing the key that was pressed.

 

In this experiment it does the following:

 

Set the global variable *response* to a string containing the key pressed.  It is put in a string because it is easier to compare strings ignoring case (as will be done in the unit assignment’s experiment)

 

  (setf *response* (string key))

 

erase the contents of the window

 

  (clear-exp-window)

 

and then if the model is doing the task it makes the model look at the window

 

  (when *actr-enabled-p* (pm-proc-display)))

 

The reason the window is cleared is to demonstrate the re-encoding of the vision module.  The clearing is done with the experiment construction function clear-exp-window.

 

Clear-exp-window  - this function takes no parameters. It removes all of the items that have been added to the experiment window that was opened with open-exp-window.

 

The code to present the assignment’s experiment is very similar to the code for the demo2 model.  There only real difference is that more items are displayed and the response is checked for correctness.  Here is the do-experiment-model function to show those differences.

 

(defun do-experiment-model ()

 

  (let* ((letters (permute-list '("B" "C" "D" "F" "G" "H" "J" "K" 

                                  "L" "M" "N" "P" "Q" "R" "S" "T" 

                                  "V" "W" "X" "Y" "Z")))

         (target (first letters))

         (foil (second letters))

         (window (open-exp-window "Letter difference"))

         (text1 foil)

         (text2 foil)

         (text3 foil))      

   

    (reset)

   

    (pm-install-device window)

   

    (case (random 3)

      (0 (setf text1 target))

      (1 (setf text2 target))

      (2 (setf text3 target)))

   

    (add-text-to-exp-window :text text1 :x 125 :y 75)

    (add-text-to-exp-window :text text2 :x 75 :y 175)

    (add-text-to-exp-window :text text3 :x 175 :y 175)

   

    (setf *response* nil)

   

    (pm-proc-display)

    (pm-run 10)

   

    (if (string-equal *response* target)

        'correct

      nil)))

 

 

Here is the description of what it does.

 

Randomize the list of possible letters

 

 

  (let* ((letters (permute-list '("B" "C" "D" "F" "G" "H" "J" "K" 

                                  "L" "M" "N" "P" "Q" "R" "S" "T" 

                                  "V" "W" "X" "Y" "Z")))

 

pick one to be the target letter and one to be the foils

 

         (target (first letters))

         (foil (second letters))

 

open a window for the task

 

         (window (open-exp-window "Letter difference"))

 

create variables to hold the text displayed and set them all to the foil

 

         (text1 foil)

         (text2 foil)

         (text3 foil))      

   

 

reset the model and tell it which window to interact with

 

    (reset)

   

    (pm-install-device window)

   

 

randomly set one of the text items to the target letter

 

    (case (random 3)

      (0 (setf text1 target))

      (1 (setf text2 target))

      (2 (setf text3 target)))

 

display the three letters

   

    (add-text-to-exp-window :text text1 :x 125 :y 75)

    (add-text-to-exp-window :text text2 :x 75 :y 175)

    (add-text-to-exp-window :text text3 :x 175 :y 175)

   

 

clear the response variable

 

    (setf *response* nil)

   

 

make the model look at the window

 

    (pm-proc-display)

 

run the model

 

    (pm-run 10)

   

compare the target letter to the model’s response and return correct if they match and nil if they do not.  The Lisp function string-equal is used because it is not case sensitive.

 

    (if (string-equal *response* target)

        'correct

      nil)))