ACT-UP
Documentation

David Reitter

Overview

ACT-UP is a cognitive modeling library that allows modelers to specify their model's functionality in Common Lisp. Whenever a cognitive explanation in a particular part of the model is sought, the modeler uses the library to provide characteristics of

following the ACT-R 6 theory.

As in the ACT-R 6 implementation, modelers are free to adhere more or less to the theoretical limitations. However, ACT-UP's design encourages modelers to underspecify portions of the model's functionality that do not contribute to the model's explanations and predictions of human performance.

How do I...

... load the library?

Just load the file load-act-up.lisp: The easiest way is to store the ACT-UP directory somewhere on your hard drive and then hard-code the path:

(load "/Users/me/modeling/ACT-UP/load-act-up.lisp")

Windows users, beware: backslashes need to be doubled in Lisp strings; forward slashes should work fine.

A more sophisticated solution uses a path relative to the model file. Assuming our model file is ACT-UP/tutorials/model.lisp, do this:

(load (concatenate 'string (directory-namestring *load-truename*) "../load-act-up.lisp"))

Here, we adjust its path so that it is relative to the current file (rather than the directory that happens to be current when Lisp is started or when the model file is loaded).

... define a chunk type?

Unlike ACT-R, ACT-UP is not normally strongly typed. All slot names are declared initially, but ACT-UP does not distinguish chunk types within a type hierarchy. Chunk types are lisp structure types that inherit from the type actup-chunk. This type exists once the `define-slots' macro is called:

(define-slots name dampen success)

If you do want to define a type hierarchy, ACT-UP provides the necessary macros. For example, the following structure defines a chunk type of name strategy with four slots. One of these slots is assigned a default value (strategy).

(define-chunk-type strategy
  (type 'a-strategy)
  name
  dampen
  success)

Note that the type member is not required by ACT-UP.

To define an inherited type, use this construction:

(define-chunk-type (lazy-strategy :include strategy)

...define a new model?

The model is defined automatically when act-up.lisp is loaded. To reset the model, use the reset-model function. To create a new model (multiple models may be used in parallel), use make-model. Use the function set-current-actUP-model to define the current model.

The ACT-UP meta-process keeps track of model time that is common to all models. You may define several meta-processes and use/reuse them as you like with the function make-meta-process. You can bind *current-actUP-meta-process* to a meta-process to switch. Use reset-mp to discard and reset the current meta-process.

... define a procedural rule ("production")?

ACT-UP does not use IF-THEN production rules as known from ACT-R. Instead, it allows you define Lisp functions that we call procedures; they represent multiple, theoretical ACT-R productions. An important property of ACT-UP models is that the procedures are not always tested in parallel; flow control is achieved through standard Lisp programming. Define procedures using defproc, similar to the way you would define a Lisp function with the `defun' macro:

(defproc subtract-digit (minuend subtrahend)
   "Perform subtraction of a single digit"
   (- minuend subtrahend))

...define a chunk?

Chunks are Lisp structures that are of type `chunk', or of a type defined with `define-chunk-type'. They can be created with the `make-chunk' function, or with the creator functions of the more specific type.

When a chunk is created, a unique name should be assigned. Otherwise, this name is assigned automatically when the chunk is added to the DM.

(make-chunk :name 'andrew :age 42 :spouse 'louise)
(make-chunk :name 'louise :age 35 :spouse 'andrew)


When assigning values to the attributes defining a chunk, symbols are interpreted as names of other chunks in DM. This is often more comfortable than assigning the values directly.

Note, however, that certain actions - such as defining Sji weights between chunks - will cause ACT-UP to implicitly define an empty chunk of a given name in the DM, if that chunk is not found in DM.

...commit a chunk to memory or reinforce it?

To specify the "presentation" of a specific chunk, use the function learn. The chunk reference may be supplied in a normal variable (equivalent to ACT-R's buffer), or the chunk may be produced right there and then using the make-chunk function, as in the following example:

(learn-chunk  (make-chunk :name 'guess :success 0.2))

This will create a new strategy chunk, setting two of its parameters, and commit it to memory. To reinforce the existing chunk, use the chunk's name:

(learn-chunk 'guess)

Note that making a new chunk and calling `learn-chunk' will always create a separate chunk. It will not merge the new chunk with any existing chunk (this would not scale very well, computationally). You must use the unique chunk name, or retrieve the chunk before comitting it, or use the `make-chunk*' syntax to extract a chunk from declarative memory for learning. For example:

(learn-chunk (make-chunk :success 0.2))   [1]
(learn-chunk (make-chunk* :success 0.2))  [2]

Case 1 would make a new chunk with the given success value, give it a unique name, and add it to declarative memory. Case 2, on the other hand, would find the chunk that is already in declarative memory, and boost its presentation count via base-level learning.

... retrieve an item from declarative memory?

Simply use the high-level functions retrieve-chunk, or blend-retrieve-chunk (for blending). The following example retrieves the most active chunk that has the name guess . The chunk contained in the variable valve-open-chunk spreads activation. No partial matching is used:

(retrieve-chunk '(:name guess))
            :cues (list valve-open-chunk))

Several low-level functions are provided as well. filter-chunks produces a list of all chunks that match a given set of criteria. In the example below, we are looking for a chunk with the name attribute guess.

The best-chunk function does the actual (time-consuming and noisy) retrieval: it selects the best chunk out of the (filtered) list of chunks, given additional retrieval cues that spread activation and, if so desired, a set of filter specifications for partial matching. In this example, we use an existing chunk stored in the valve-open-chunk variable as a single retrieval cue, and no partial matching:

(best-chunk (filter-chunks
                 (model-chunks (current-actUP-model))
                 '(:name guess))
            (list valve-open-chunk)
            nil)

... debug an ACT-UP model? ("production")?

We're providing a separate tutorial on debugging ACT-UP models.

... retrieve a blended chunk?

Use the high-level function blend-retrieve-chunk .

When combining low-level functions, use the function blend instead of retrieve-chunk. In addition to the cues and partial-matching specification known from retrieve-chunk, it also expects a chunk type (such as strategy), which determines the kind of chunk created as a result of blending.

... define chunk similarities?

Use the add-sji-fct and reset-sji-fct' functions.

... select a procedure (in lieu of a production rule) using subsymbolic utility learning?

Quick Answer

Define competing procedures as above and give each a :group attribute in order to group them into a competition set:

(defproc  force-over ()
  :group choose-strategy
  ...)
(defproc force-under ()
  :group choose-strategy
  ...)

Then, invoke one of the procedures (as chosen by utility) as such:

(choose-strategy)

Arguments may be used as well (but ensure that all procedures accept the same arguments).

Utilities are learned using the function assign-reward:

(assign-reward 1.5)

This example distributes a reward of across the recently invoked procedures. Procedures do not have to have a :group attribute and they do not have to have been invoked via the group name in order to receive a reward; however, they have to have been defined using the defproc macro (rather than just being Lisp functions).

Configure utility learning via the parameters *au-rpps*, *au-rfr*, *alpha*, and *iu*.

Worked Example
Note that ACT-UP supports utility learning and even procedure compilation. Utility learning means that multiple procedures may compete for execution, and that the actually executed procedures are assigned rewards if they lead to some form of success. To define competing procedures, they must be grouped together in a Group. A group is a set of procedures, such as the following:

(defproc subtract-digit-by-addition (minuend subtrahend)
   :group subtract
   "Perform subtraction of a single digit via addition."
   (let ((chunk (retrieve-chunk `(:chunk-type addition-fact
                                  :result ,minuend
                                  :add1 ,subtrahend))))
       (when chunk
          (learn-chunk chunk)
          (addition-fact-add2 chunk))))
(defproc subtract-digit-by-subtraction (minuend subtrahend)
   :group subtract
   "Perform subtraction of a single digit via subtraction knowledge."
   (let ((chunk (retrieve-chunk `(:chunk-type addition-fact
                                  :min ,minuend
                                  :sub subtrahend))))
       (print "addition by subtraction.")
       (when chunk
          (learn-chunk chunk)
          (subtraction-fact-result chunk))))
(defproc subtract-digit-by-addition-faulty (minuend subtrahend)
   :group subtract
   "Perform subtraction of a single digit via addition.  Faulty strategy."
   (let ((chunk (retrieve-chunk `(:chunk-type addition-fact
                                  :add2 ,minuend
                                  :result ,subtrahend))))
       (when chunk
          (learn-chunk chunk)
          (addition-fact-add2 chunk))))
(defproc subtract-digit-by-decrement (minuend subtrahend)
   :group subtract
   "Perform subtraction of a single digit via subtraction knowledge."
   ...)

Note that each procedure in the group takes the same, two arguments (minuend, subtrahend). In order to execute a subtraction, we simply call a function that is named after the group:
(subtract 7 3)
ACT-UP will automatically choose one of procedures in the subtract group. In order to gauge the utility of each group, we must propagate rewards to the procedures. This can be done with the `assign-reward' function:

(defproc subtraction-model (a b)
  (let ((result (subtract a b)))
     ;; obtain feedback from experimental environment:
     (if (get-feedback a b result)
         (assign-reward 2.0))))

;; environment:
(defun get-feedback (a b result)
  "Environment function (experimental setup) - not part of the model.
Return T if problem solved correctly."

  (if result  ;; note result may be nil
      (= result (- a b))))

After a short period of time, this model should learn to choose an effective, reliable strategy to carry out a subtraction.

Rewards are assigned to ACT-UP procedures just like they would be assigned to production procedures in ACT-R. The most recently invoked procedure received the largest portion of the reward; Difference-Learning governs how much of a procedure benefits from its reward portion.

...model effects via production compilation?

ACT-UP may not have productions, but it does have procedures. These procedures can be compiled. To do so, we need to keep in mind that procedure compilation will side-step any intemediate action that a model might undertake in order to execute a procedure. This includes retrievals from declarative memory, but also any other side-effects such as sensory-motor interaction, or even Lisp code.

ACT-UP's procedure compilation can be enabled by setting the *procedure-compilation* parameter to t.

Every time a procedure ("source procedure") is invoked, procedure compilation will create a compiled procedure (specific to the arguments given to the procedure at invokation); its initial utility will we *iu*. If the source procedure is compiled a second time, the utility of the compiled procedure will be boosted by the utility of the source procedure according to reward assignment mechanism (difference learning equation, as above). The compiled procedure will, eventually, have a higher utility than the source procedure, and it will be executed instead. In the subtraction example from above, the following gives a sample of the acquired compiled procedures:

(subtract-digit-by-subtraction 8 3) --> 5
(subtract-digit-by-addition 6 2) --> 4
(subtract-digit-by-addition-faulty 7 3) --> nil
(subtraction-model 3 1) --> 2

Note that in order to execute the best procedure among one or more source procedures, and all their compiled equivalents, the modeler must define a group for the procedures and invoke them via the group name. Reward assignment and procedure compilation will take place no matter how the procedure was invoked. So, in the above example, the `subtraction-model' will never be run in its compiled form, and rewards will always be propagated, because it does not belong to a group and cannot be called that way.

Again, note that in its compiled form, the procedure merely returns its result. No side-effects are observed. For instance, the `subtract-digit-by-subtraction' procedure will print the debug message "Addition by subtraction!" every time it is run - as long as it isn't compiled. Once compiled, it will always just return the result.

... run model code in parallel?

ACT-UP is designed assuming that most modeled processes can be formulated as a sequence of cognitive actions. However, in some situations, parallelism may be necessary.

To asynchronously request the execution of some code (that is, without waiting for the results), use the `request-' syntax, e.g., request-retrieve-chunk. The request- functions are defined for each module-specific ACT-UP function that can take some time, e.g., best-chunk, filter-chunks, retrieve-chunk (for the declarative memory module), and all functions defined with `defproc' (for the procedural module). The functions all return an execution handle.

This function kicks off task execution in parallel; it returns without delay (in ACT-UP time). Once the result of the operation is needed, it may be retrieved using the `receive' function and the previously obtained handle.

(let ((handle (request-retrieve-chunk '(:chunk-type ...))))
;; do something else
...
(receive handle))
Different threads of execution may share resources. We follow Anderson et al. (2004) in that each module can only handle one request at a time. We follow some of Salvucci&Taatgen's (2008) threaded cognition approach: threads acquire resources in a "greedy" and "polite" manner. When a `request-' function is called, it will wait until the module is available, but then reserve the module regardless of other goals that may exist. The module functions (such as `retrieve-chunk') will also wait for the module to be free. Similarly, `receive' will wait. To check if the result is available, use the `response-available-p' function.

Example
The following example shows how a retrieval request is initiated and finished. Upon initiating the request, ACT-UP does not "wait" for the retrieval to finish.
(print (actup-time))
(let ((retrieval-process (request-retrieve-chunk '(:chunk-type person))))
  (print (actup-time)) ;; no time has elapsed
  (print (response-available-p retrieval-process))  ;; module is busy
  (pass-time 0.05) ;; let's spend some time
  (print (response-available-p retrieval-process)) ;; module is still busy
  ;; (wait-for-response retrieval-process)   ;; wait for result - not needed
  ;; (print (response-available-p retrieval-process))
  (print (actup-time)) ;; this takes some time!
  (print (receive retrieval-process))) ;; waits and receives
More related functions

ACT-UP provides a `reset-module' function to explicitly cancel a module's operation. To wait for a module to finish processing when the handle is not known, use `wait-for-module'.

Threads
ACT-UP's does not provide an API to `threads' as in threaded cognition. However, Lisp Threads might be useful. Appropriate interference will be the result.

Note that parallelism has seen limited testing in ACT-UP so far. Your feedback is appreciated!