ACTRSimulators.jl is a package for developing discrete event simulations of the ACT-R cognitive architecture. Although the basic framework for conducting simulations has been developed, currently some features of ACT-R have not been implimented.
As a simple example, we will develop an ACT-R model of the psychomotor vigilence task (PVT). The PVT is a reaction time task used to measure vigilance decrements stemming from fatigue. On each trial, a stimulus is presented after a random delay lasting 2 to 10 seconds. Once a response is made by keystroke, the next trial begins. Key components of the code will be described below. The full source code can be found in
ACTRSimulators.jl, the first step is to load the following dependencies.
using ACTRSimulators import ACTRSimulators: start!, press_key!, repaint! include("PVT.jl") include("PVT_Model.jl") import_gui()
Next, create an event scheduler as follows. When the option
model_trace is set to true, a description and execution time will print for each processed model event. Task events can be added to the trace with
scheduler = ACTRScheduler(;model_trace=true)
A task object is created with the
PVT constructor, which includes options for the number of trials, whether the GUI is visible and whether the task executes in real time.
task = PVT(;scheduler, n_trials=2, visible=true, realtime=true)
Now we will initialize the model. The model consists of components for the following modules:
Each of the modules are passed to the
actr model object along with a reference to the scheduler.
procedural = Procedural() T = vo_to_chunk() |> typeof visual_location = VisualLocation(buffer=T) visual = Visual(buffer=T) motor = Motor() actr = ACTR(;scheduler, procedural, visual_location, visual, motor)
A production rule consists of higher order functions: one for the conditions and another for the actions. The PVT model uses three production rules:
wait for the stimulus to appear,
attend to the stimulus once it appears,
respond to the stimulus after attending to it.
The conditions for a production rule is a set of functions that return a
Bool or a utility value proportional to the degree of match. By convention, the name for the conditions for a production rule is prefixed by "can". For example,
can_wait returns a set of functions that evaluate the conditions for executing the
wait production rule. Each condition requires an
actr model object.
The model will wait if the
visual buffers are empty and the same modules are not busy.
function can_wait(actr) c1(actr) = actr.visual_location.state.empty c2(actr) = actr.visual.state.empty c3(actr) = !actr.visual.state.busy c4(actr) = !actr.motor.state.busy return c1, c2, c3, c4 end
Upon stimulus presentation, a visual object is "stuffed" into the
visual_location buffer. The
attend production rule will execute if the
visual_location buffer is not empty and the
visual module is not busy.
function can_attend(actr) c1(actr) = !actr.visual_location.state.empty c2(actr) = !actr.visual.state.busy return c1, c2 end
Once the model attends to the stimulus, it can execute a response. The
respond production rule will fire if the
visual buffer is not empty and the
motor module is not busy.
function can_respond(actr) c1(actr) = !actr.visual.state.empty c2(actr) = !actr.motor.state.busy return c1, c2 end
After a production rule is selected, a set of actions are executed that modify the architecture and possibly modify the exeternal environment. Each production rule is associated with an action function. For example, the action function for the production rule wait is
wait_action. Each action requires an
actr model object and a
task object or
args... if the
task is note used.
The purpose of the
wait production rule is to surpress the execution of other production rules when the stimulus has not appeared. There is not time cost associated with firing the
wait production rule. Accordingly, an empty function
()->() is immediately registered to the scheduler using the keyword
function wait_action(actr, args...) description = "Wait" register!(actr.scheduler, ()->(), now; description) return nothing end
attend production rule is selected, the chunk in the
visual_location buffer is copied and passed to the function
attending, which adds the chunk after a time delay that represents the time to shift visual attention. In addition, the buffer for
visual_location is immediately cleared.
function attend_action(actr, args...) buffer = actr.visual_location.buffer chunk = deepcopy(buffer) clear_buffer!(actr.visual_location) attending!(actr, chunk) return nothing end
respond_action is executed upon selection of the
respond production rule. The function
respond_action performs two actions: (1) clear the visual buffer and (2) executes the function
responding! which executes the motor response after a delay and calls the user-defined function
press_key. The model uses
press_key to interact with the task and collect data.
function respond_action(actr, task) clear_buffer!(actr.visual) key = "sb" responding!(actr, task, key) return nothing end
Construct Production Rules
Rule creates a production rule from the following keyword arguments:
conditions: a list of functions representing selection conditions
action: a function that performs the actions of the production rule
actr: a reference to the
task: a reference to the PVT task
name: an optional name for the production rule
Each production rule is pushed into a vector located in the
procedural memory object.
conditions = can_attend() rule1 = Rule(;conditions, action=attend_action, actr, task, name="Attend") push!(procedural.rules, rule1) conditions = can_wait() rule2 = Rule(;conditions, action=wait_action, actr, task, name="Wait") push!(procedural.rules, rule2) conditions = can_respond() rule3 = Rule(;conditions, action=respond_action, actr, task, name="Respond") push!(procedural.rules, rule3)
Now that the model and task have been defined, we can now run the model simulation. A GUI will appear upon running the following code: