The
assignment for this unit is to write a complete experiment and model
essentially from scratch. The
demonstration experiments for this unit are not really useful examples for
doing so. They are useful for showing
how production parameter learning occurs, but because the experiments presented
in this unit differ greatly from those of the previous units (and the one you
will be writing) the discussion in this document is probably best avoided as an
example of experiment generation for the assignment at hand.
In
the Building Sticks Task (BST) there is not a simple response collected from
the user, but instead an ongoing interaction that only ends when the correct
results are achieved. This requires some
new functions from the experiment generation tool kit and is written in a
mixture of the “trial at a time” and event-based style. The individual trials are each a small
event-based experiment and the model is iterated over them one at a time.
Because
both the bst-learn and bst-no-learn models use the same
experiment (only the number of trials presented and the type of data collected
differ) the discussion will focus on the bst-learn
model.
Here
is the code from the Misc window:
(defvar
*stick-a*)
(defvar
*stick-b*)
(defvar
*stick-c*)
(defvar
*target*)
(defvar
*current-stick*)
(defvar
*current-line*)
(defvar
*done*)
(defvar
*choice*)
(defvar
*experiment-window* nil)
(defconstant
*bst-exp-data* '(20.0 67.0 20.0 47.0 87.0 20.0 80.0 93.0
83.0 13.0 29.0
27.0 80.0 73.0 53.0))
(defparameter
*bst-stimuli* '((15 250 55
125)(10 155 22
101)
(14 200
37 112)(22 200
32 114)
(10 243
37 159)(22 175
40 73)
(15 250
49 137)(10 179
32 105)
(20 213
42 104)(14 237
51 116)
(12 149
30 72)
(14 237
51 121)(22 200
32 114)
(14 200
37 112)(15 250
55 125)))
(defun
build-display (a b c target)
(setf *experiment-window* (open-exp-window
"Building Sticks Task"
:visible nil
:width
600
:height 400))
(setf *stick-a* a)
(setf *stick-b* b)
(setf *stick-c* c)
(setf *target* target)
(setf *current-stick* 0)
(setf *done* nil)
(setf *choice* nil)
(setf *current-line* nil)
(add-button-to-exp-window
:x 10 :y 25 :height 20 :width 20 :text
"A" :action
#'button-a-pressed)
(add-button-to-exp-window
:x 10 :y 50 :height 20 :width 20
:text "B" :action
#'button-b-pressed)
(add-button-to-exp-window
:x 10 :y 75 :height 20 :width 20
:text "C" :action
#'button-c-pressed)
(add-button-to-exp-window
:x 10 :y 125 :height 20 :width 38 :text "Reset" :action
#'reset-display)
(add-line-to-exp-window
(list 50 35) (list (+ a 50) 35) 'black)
(add-line-to-exp-window
(list 50 60) (list (+ b 50) 60) 'black)
(add-line-to-exp-window
(list 50 85) (list (+ c 50) 85) 'black)
(add-line-to-exp-window
(list 50 110) (list (+ target 50) 110) 'green)
(allow-event-manager *experiment-window*))
(defun
button-a-pressed (button)
(declare (ignore button))
(unless *choice* (setf *choice* 'under))
(unless *done*
(if (> *current-stick* *target*)
(setf *current-stick* (-
*current-stick* *stick-a*))
(setf *current-stick* (+ *current-stick*
*stick-a*)))
(update-current-line)))
(defun
button-b-pressed (button)
(declare (ignore button))
(unless *choice* (setf *choice* 'over))
(unless *done*
(if (> *current-stick* *target*)
(setf *current-stick* (-
*current-stick* *stick-b*))
(setf *current-stick* (+ *current-stick*
*stick-b*)))
(update-current-line)))
(defun
button-c-pressed (button)
(declare (ignore button))
(unless *choice* (setf *choice* 'under))
(unless *done*
(if (> *current-stick* *target*)
(setf *current-stick* (-
*current-stick* *stick-c*))
(setf *current-stick* (+ *current-stick*
*stick-c*)))
(update-current-line)))
(defun
reset-display (button)
(declare (ignore button))
(unless *done*
(setf *current-stick* 0)
(update-current-line)))
(defun
update-current-line ()
(when *current-line*
(remove-items-from-exp-window
*current-line*))
(if (= *current-stick* *target*)
(progn
(setf *done* t)
(setf *current-line* (add-line-to-exp-window (list 50 135) (list (+ *target* 50) 135) 'blue))
(add-text-to-exp-window :x 180 :y 200
:width 50 :text "Done"))
(if (zerop *current-stick*)
(setf *current-line* nil)
(setf *current-line* (add-line-to-exp-window (list 50 135) (list (+
*current-stick* 50) 135) 'blue))))
(allow-event-manager *experiment-window*)
(when *actr-enabled-p* (pm-proc-display)))
(defun
do-experiment (sticks)
(apply #'build-display sticks)
(if *actr-enabled-p*
(do-experiment-model)
(do-experiment-person)))
(defun
do-experiment-model ()
(pm-install-device *experiment-window*)
(pm-proc-display :clear t)
(pm-run 60.0))
(defun
do-experiment-person ()
(while (not *done*)
(sleep .25))
(sleep 1))
(defun
do-set ()
(let ((result nil))
(when *actr-enabled-p*
(reset))
(dolist (stim *bst-stimuli*)
(do-experiment stim)
(push *choice* result))
(reverse result)))
(defun
collect-data (n)
(let ((result (make-list (length
*bst-stimuli*) :initial-element 0))
(p-values (list '(decide-over 0)
'(decide-under 0) '(force-over 0) '(force-under 0))))
(dotimes (i n result)
(setf result (mapcar #'+ result (mapcar
#'(lambda (x) (if (equal x 'over) 1 0)) (do-set))))
(setf p-values (mapcar #'(lambda (x)
(list (car x) (+ (second x) (production-p-value (car x)))))
p-values)))
(setf result (mapcar #'(lambda (x) (* 100.0
(/ x n))) result))
(when (= (length result) (length *bst-exp-data*))
(correlation result *bst-exp-data*)
(mean-deviation result *bst-exp-data*))
(format t "~%Trial ")
(dotimes (i (length result))
(format t "~8s" (1+ i)))
(format t "~% ~{~8,2f~}~%~%" result)
(dolist (x p-values)
(format t "~12s: ~6,4f~%" (car
x) (/ (second x) n)))))
(defun
production-p-value (prod)
(caar (no-output (spp-fct (list prod :p)))))
and
here is the code from the Commands
window:
(pm-set-params
:show-focus t)
(sgp :v nil :esc t :pl t :egs 3 :ut -100)
(setf
*actr-enabled-p* t)
(pm-start-hand-at-mouse)
(goal-focus
goal)
(spp
:efforts 500 :successes 100)
(spp
decide-over :failures 7 :successes 13 :efforts 100)
(spp
decide-under :failures 7 :successes 13 :efforts 100)
(spp
force-over :failures 10 :successes 10 :efforts 100)
(spp
force-under :failures 10 :successes 10 :efforts 100)
(spp
read-done :success t)
(spp
pick-another-strategy :failure t)
We
will start the discussion with the code from the Commands window. The first
call enables the fixation ring display for the model’s visual attention and has
been used before.
(pm-set-params
:show-focus t)
The
next line shows a couple of new parameters being passed to sgp.
(sgp :v nil :esc t :pl t :egs 3 :ut -100)
The
:pl, parameter learning, parameter specifies whether the production parameter
learning mechanism is enabled. The :egs,
expected gain s, parameter specifies the s parameter for the noise distribution
of the utility (or expected gain) noise, and the :ut, utility threshold,
parameter specifies the minimum utility a production must have to allow it to
fire (the setting to -100 essentially negates the threshold so that any
production will be able to fire regardless of a low utility or a large amount
of noise).
Then
the model is set as the participant.
(setf
*actr-enabled-p* t)
Because
the model will be using the mouse to press buttons the following call ensures
that the models hand is on the virtual mouse at the start of a run.
(pm-start-hand-at-mouse)
The
next line sets the initial contents of the goal buffer.
(goal-focus
goal)
The
rest of the commands are setting the parameters for the productions. The order is important because the general
setting of efforts and successes used to generate high utilities for all the
productions (the general productions are set to high utility values because
their learning is unimportant to the task) needs to be overwritten for the
critical productions.
(spp
:efforts 500 :successes 100)
(spp
decide-over :failures 7 :successes 13 :efforts 100)
(spp
decide-under :failures 7 :successes 13 :efforts 100)
(spp
force-over :failures 10 :successes 10 :efforts 100)
(spp
force-under :failures 10 :successes 10 :efforts 100)
(spp
read-done :success t)
(spp
pick-another-strategy :failure t)
Now
we will look at the experiment code from the Misc window.
It
starts of defining a bunch of global variables.
The first five are used to hold the length of the named item in pixels.
(defvar
*stick-a*)
(defvar
*stick-b*)
(defvar
*stick-c*)
(defvar
*target*)
(defvar
*current-stick*)
The
next one holds the screen object of the current line.
(defvar
*current-line*)
Done
is set when the user’s line is the same length as the target line.
(defvar
*done*)
Choice
indicates whether the initial selection was overshoot or undershoot.
(defvar
*choice*)
This
one holds the window that is being used for the task.
(defvar
*experiment-window* nil)
This
constant holds the experimental data from the original experiment (percent of
overshoot as initial strategy on each trial).
(defconstant
*bst-exp-data* '(20.0 67.0 20.0 47.0 87.0 20.0 80.0 93.0
83.0 13.0 29.0
27.0 80.0 73.0 53.0))
These
are the stick lengths to use for each trail in the proper trial order. Each list represents one trial and the
numbers are the pixel lengths of the sticks a, b, c, and the target
respectively.
(defparameter
*bst-stimuli* '((15 250 55
125)(10 155 22
101)
(14 200
37 112)(22 200
32 114)
(10 243
37 159)(22 175
40 73)
(15 250
49 137)(10 179
32 105)
(20 213
42 104)(14 237
51 116)
(12 149
30 72)
(14
237 51 121)(22
200 32 114)
(14 200
37 112)(15 250
55 125)))
The
build-display function takes four parameters which are the pixel lengths of the
sticks. It opens a window, draws the
initial display and adds the buttons which the user will use to perform the
task.
(defun
build-display (a b c target)
Open
a window and save it in the global variable.
(setf *experiment-window* (open-exp-window
"Building Sticks Task"
:visible nil
:width 600
:height 400))
Initialize
all of the global variables.
(setf *stick-a* a)
(setf *stick-b* b)
(setf *stick-c* c)
(setf *target* target)
(setf *current-stick* 0)
(setf *done* nil)
(setf *choice* nil)
(setf *current-line* nil)
Add
the buttons that the user will use to the task.
This is a new function and all of the parameters will be explained in
the detailed description below. The most
important one is the action parameter which specifies a function to be executed
when the button is pressed.
(add-button-to-exp-window
:x 10 :y 25 :height 20 :width 20 :text
"A" :action #'button-a-pressed)
(add-button-to-exp-window
:x 10 :y 50 :height 20 :width 20
:text "B" :action
#'button-b-pressed)
(add-button-to-exp-window
:x 10 :y 75 :height 20 :width 20
:text "C" :action
#'button-c-pressed)
(add-button-to-exp-window
:x 10 :y 125 :height 20 :width 38 :text "Reset" :action
#'reset-display)
Draw
the initial set of lines on the display.
This is also a new function and will be described in detail below.
(add-line-to-exp-window
(list 50 35) (list (+ a 50) 35) 'black)
(add-line-to-exp-window
(list 50 60) (list (+ b 50) 60) 'black)
(add-line-to-exp-window
(list 50 85) (list (+ c 50) 85) 'black)
(add-line-to-exp-window
(list 50 110) (list (+ target 50) 110) 'green)
Call
the system dependent event manager to give the window a chance to display.
(allow-event-manager *experiment-window*))
The
next three functions, button-a-pressed, button-b-pressed, and button-c-pressed
are called automatically when the corresponding buttons are pressed because
they were specified in the add-button-to-exp-window calls. They all get passed the button itself as a
parameter, but do not need it. All three
of them do basically the same thing. If
this is the user’s first choice it saves whether it is the overshoot or
undershoot strategy. Then it computes
the new length of the current stick after the application (either addition or
subtraction) of the chosen stick, and then calls update-current-line to display
the new configuration.
(defun
button-a-pressed (button)
(declare (ignore button))
(unless *choice* (setf *choice* 'under))
(unless *done*
(if (> *current-stick* *target*)
(setf *current-stick* (-
*current-stick* *stick-a*))
(setf *current-stick* (+ *current-stick*
*stick-a*)))
(update-current-line)))
(defun
button-b-pressed (button)
(declare (ignore button))
(unless *choice* (setf *choice* 'over))
(unless *done*
(if (> *current-stick* *target*)
(setf *current-stick* (-
*current-stick* *stick-b*))
(setf *current-stick* (+ *current-stick*
*stick-b*)))
(update-current-line)))
(defun
button-c-pressed (button)
(declare (ignore button))
(unless *choice* (setf *choice* 'under))
(unless *done*
(if (> *current-stick* *target*)
(setf *current-stick* (-
*current-stick* *stick-c*))
(setf *current-stick* (+ *current-stick*
*stick-c*)))
(update-current-line)))
The
reset-display function is called when the reset button is pressed. It takes one parameter which will be the
button object, but it does not need it.
It sets the length of the current stick to 0 and calls
update-current-line to redisplay the state.
(defun
reset-display (button)
(declare (ignore button))
(unless *done*
(setf *current-stick* 0)
(update-current-line)))
The
update-current-line function takes no parameters. It redraws the current line on the screen and
sets the done flag if the current stick is the same length as the target.
(defun
update-current-line ()
First
remove the old line if there is one.
(when *current-line*
(remove-items-from-exp-window
*current-line*))
(if (= *current-stick* *target*)
If
the target has been reached then set the done flag, draw the stick and display
the done message.
(progn
(setf *done* t)
(setf *current-line* (add-line-to-exp-window (list 50 135) (list (+ *target* 50) 135) 'blue))
(add-text-to-exp-window :x 180 :y 200
:width 50 :text "Done"))
If
the target has not yet been reached then check to see if it has been
reset. If it has been reset then just
set the current line to nil, otherwise display a new line of the correct
length.
(if (zerop *current-stick*)
(setf *current-line* nil)
(setf *current-line* (add-line-to-exp-window (list 50 135) (list (+
*current-stick* 50) 135) 'blue))))
Call
the system event manager so that the display gets a chance to update.
(allow-event-manager *experiment-window*)
If
the model is performing the task have it reevaluate the display.
(when *actr-enabled-p* (pm-proc-display)))
The
do-experiment function takes one parameter which should be a list of stick
lengths. It calls build-display to open
a window and display those sticks and then calls the appropriate function to
run one trial depending on whether a person or the model is doing the task.
(defun
do-experiment (sticks)
(apply #'build-display sticks)
(if *actr-enabled-p*
(do-experiment-model)
(do-experiment-person)))
The
do-experiment-model function takes no parameters. It sets which window to interact with and has
the model process that display as a new screen, and then runs the model for up
to 60 seconds to perform the trial.
(defun
do-experiment-model ()
(pm-install-device *experiment-window*)
(pm-proc-display :clear t)
(pm-run 60.0))
The
do-experiment-person function takes no parameters. It just loops waiting for the done flag to be
set. Then it waits one second before
returning.
(defun
do-experiment-person ()
(while (not *done*)
(sleep .25))
(sleep 1))
The
do-set function takes no parameters. It iterates
over the list of stimuli in the global list *bst-stimuli* and runs one trial of
each recording which strategy was chosen first on each one. If the model is doing the task it is reset
before the first trial. It returns the
list of strategy choices.
(defun
do-set ()
(let ((result nil))
(when *actr-enabled-p*
(reset))
(dolist (stim *bst-stimuli*)
(do-experiment stim)
(push *choice* result))
(reverse result)))
The
collect-data function takes one parameter which is the number of times to run
the full experiment. It runs the
experiment the requested number of trials and computes the percentage of times
overshoot was chosen first on each trial.
It compares that to the experimental data and displays a table of the
results. It also records the learned
value of the p parameter for the productions that are responsible for the
model’s choices and reports their average at the end as well.
(defun
collect-data (n)
(let ((result (make-list (length
*bst-stimuli*) :initial-element 0))
(p-values (list '(decide-over 0)
'(decide-under 0) '(force-over 0) '(force-under 0))))
(dotimes (i n result)
(setf result (mapcar #'+ result (mapcar
#'(lambda (x) (if (equal x 'over) 1 0)) (do-set))))
(setf p-values (mapcar #'(lambda (x)
(list (car x) (+ (second x) (production-p-value (car x)))))
p-values)))
(setf result (mapcar #'(lambda (x) (* 100.0
(/ x n))) result))
(when (= (length result) (length
*bst-exp-data*))
(correlation result *bst-exp-data*)
(mean-deviation result *bst-exp-data*))
(format t "~%Trial ")
(dotimes (i (length result))
(format t "~8s" (1+ i)))
(format t "~% ~{~8,2f~}~%~%" result)
(dolist (x p-values)
(format t "~12s: ~6,4f~%" (car
x) (/ (second x) n)))))
The
production-p-value function takes one function which should be a production
name. It returns the p value of that
production without printing the information to the listener.
(defun
production-p-value (prod)
(caar (no-output (spp-fct (list prod :p)))))
The
new functions used in this model are:
add-button-to-exp-window
– this function is similar to the
add-text-to-exp-window function that you have seen many times before. It places a button in the window that was opened using open-exp-window. It takes a few keyword parameters. :text
specifies the text to display on the button.
:x and :y specify the pixel coordinate of the upper-left corner of the button,
:height and :width specify the size of the button in pixels, and the :action
parameter specifies a function to be called when this button is pressed. That function will be called with the button
object itself as the only parameter.
add-line-to-exp-window – this is similar to the other add-*-to-exp-window
functions. It draws a line in the window
that was opened with open-exp-window. It takes two required parameters and one
optional parameter. The required
parameters specify the pixel coordinates of the end points of the line and each
should be a two element list of the x and y coordinate. There optional parameter can be used to
specify the color that the line is to be drawn in. The default is black if it is not specified.
It must be a symbol (be careful especially when using ACL because many of these
names are global variables that evaluate to an rgb object) and can be any of
black, blue, red, green, white, pink, yellow, gray, light-blue, dark-green,
purple, brown, light-gray, or dark-gray.
remove-items-from-exp-window – this function takes any number of parameters.
Each one must be an object that was added to the window opened with open-exp-window. Those objects are then removed from that
window.