jACT-R Manual

jACT-R

Dependencies

At the most basic level, jACT-R really only relies upon a Java virtual machine that supports version 1.5 or later. The system utilizes many open source projects such as Apache Commons, OSGi, Mozilla's Rhino, etc. In order to manage all of these dependencies, jACT-R also utilizes Eclipse, which is both an application platform and an IDE. Not only is the jACT-R IDE built on top of Eclipse, but Eclipse is also used to manage plugins, modules, other dependencies and launch behavior.

Do you need to use Eclipse to run jACT-R? No. You can run it from the command line, but currently only Eclipse based binary downloads are available and the build system is within Eclipse. If you want command line only, it's usually easier just to install from Eclipse and manually extract the jar files.

So what do you need to download and install before actually installing jACT-R?

  1. Java 1.5+
  2. Eclipse IDE (for RCP/Plug-in development)

Once they have been installed you can go on to the jACT-R installation.

Installation

Note: Instructions below are outdated. Will update soonish. For now, go to the Eclipse Market Place (under Help menu in eclipse). Search for "jactr" install the core and the IDE support. It will take some time, but the tool seems to (finally) find all the dependencies.

Assuming you have installed the prerequisite software, you are now ready to install jACT-R. The first thing that you need to do is to tell Eclipse which update sites to search for the software. Those who are less inclined to read can view either of these screencasts (install1, install2).

  1. Launch Eclipse
  2. Open the Update Manager (Help -> Software Updates -> Find and Install..)

The update manager allows you to specify which websites to query for new or updated software. You're going to tell it about a series of update sites for the core, ide, and example software. (you can also import all the relevant update sites by importing this file)

  1. Select Search for new features to install and click Next
  2. Click New Remote Site...
  3. Enter the name and url for the following update sites:
Make sure the new update sites are selected in the checkbox list, as well as Eclipse Project Updates (as it is necessary for some of the support packages). Clicking on Finish will query all the sites for available updates.
  1. Select the jACT-R packages to install. If an error is reported (unsatisfied dependencies), click on Select Required (this might take some time).
  2. Click on Next and finish up the installation.

After the reboot, you should be installed. Coming next, how to use the IDE.

Quick Start

Do you want to read all the instructions? Well, I don't want to write them just yet. How about a quick start instead?

Importing Existing Projects

The following screencast covers how to import both zipped and installed jACT-R projects. It also introduces viewers to run configurations and the creation of custom modules.

Importing/Exporting v1
Importing/Exporting v2

Run Configurations

Eclipse uses run configurations to manage and store the necessary options and parameters needed in order to execute programs, this includes running of jACT-R models. This screencast covers the basics of getting a particular model run configured as well as importing of models.

Iterative Runs

The following screencast illustrates how one can run batch model executions. This is useful for data collection as well as parameter space searches. As with the run configurations, listeners can be attached in order to influence and record the model behavior.

Remote execution

The remote execution tools permit the execution of iterative runs over the network. It can be run both locally and remotely, with auto-start options. The screencast below illustrates how to use this new feature. But first, it has to be installed:

  • Update site : http://jactr.org/update/remote/
  • Screencast

Tutorial

These tutorials are meant to augment the standard tutorial content from the canonical Lisp implementation.

Unit 1 Tutorial

Abstract

This document is an augmentation to the unit one tutorial found in the standard ACT-R distribution. To use this tutorial extension, read through the original tutorial and refer back to this document for jACT-R specific comments and features.

Getting Started

Presumably, you have already followed the instructions and configured your Eclipse environment to run jACT-R. You will also want to import the latest version of the unit one tutorial, included with this document.

  • Project Archive : org.jactr.tutorial.unit1.zip
  • Note: The exporter trims out empty folders. You will need to add the empty java folder at the root of the project. If not, you will receive a error for the project, but it's not critical

Count Model

The ACT-R tutorial starts you off with the basic of chunk, chunk-type, and production creation using the count model. The count.jactr model (found in org.jactr.tutorial.unit1/models/) contains the full model description using the jACT-R (XML-based) syntax, with the Lisp in comments. You may also use the Lisp code in jACT-R, however the Lisp parser is a tad buggy. (jACT-R aims to be syntactically agnostic. All models are compiled into an intermediate representation before building. One merely needs to build the appropriate parser to support alternative language syntaxes.)

Running Models

jACT-R conforms to a compiled-language development model. You write your models, they are compiled, and are then executed in a separate runtime. As you edit the model you may see warnings (yellow underline markers) or errors (red underline markers). Mouse hovering or the Problems View will provide greater detail as to the error. All errors, but not warnings, must be resolved before attempting to the run the model.

errors.jpg

Once the model is free of compilation errors, you can create a run configuration for that model. Run configurations allow you to specify how the model should be executed, any startup/shutdown logic, and what devices and instruments should be connected to the model. If you open the Run Configurations dialog, you should see a jACT-R Run configuration for count already. Just select it and click on run.

run-config.jpg

Inspecting Model Output

The IDE includes a table based model log viewer where each row represents a block of time and each column corresponds to a module, buffer, or other logging source. Log messages are kept in a scrolling buffer that can be increased in size through the preferences. The amount and type of information that is logged is configured in the run configuration. The basics include routing the pure textual log messages as well as maintaining a visual representation of buffer states and contents.

logging.jpg

jACT-R Note: You may notice that in the ACT-R tutorial trace productions are selected at one time and then fired in the next, yet in jACT-R they appear to be selected and fired in the same time step. This is just a difference in logging. In both systems productions when fired post events that will be executed at some time in the future (typically 50ms later). jACT-R notifies that the production has fired immediately after the events are posted, ACT-R notifies once the events start to fire. Same behavior, same timing, just different logging.

IDE Note: The ACT-R trace in the tutorial runs 50ms longer than it does in the jACT-R run. This is a silly bug where the runtime can exit before it has finished sending the last block of logging information to the IDE. Nothing to worry about.

IDE Note: If you look under the org.jactr.tutorial.unit1/runs/ directory you will see a date stamped, then time stamped folder. Within that folder will (at least) be environment.xml. This folder is where the model is actually running. All tools, instruments, devices, whatever, will output to this location. This makes keeping track of the various outcomes really simple. The environment.xml file is used to configure the jACT-R runtime and is quite useful in debugging.

Pattern Matching Exercise

There is no mechanism (yet) to directly manipulate the instantiation of productions in jACT-R. Since the runtime is intentionally hidden from the modeler, something like this is not worth the effort (unless people start clamoring for it). However, if you want to see what is going on, take a look at the trace in for Procedural. jACT-R errs on the verbose side when it comes to logging and you can see quite clearly why any production does not match at any time. Combined with the buffer hovers (to show buffer state and content), you can usually figure out all you need to know.

Addition Model

As with the count model, the addition model is provided and commented with the matching lisp code. Running it is just as simple, merely run the addition run configuration.

Semantic Model

For the semantic model, the lisp tutorial asks readers to change which goal is the focus with the (goal-focus) command. Similar effect can be achieved by modifying the chunk attribute of the buffer tag at the end of the semantic.jactr model file.

Unlike the lisp version of the model, all chunks are defined in the jACT-R model. Missing chunks are not warnings in jACT-R but outright errors. Delete the chunk color and see what happens. Having trouble finding the chunk in all that code? You can use standard Find command, or poke around in the Outline viewer (look under chunk).

outline.jpg

Building a Model

The first step in building a new model is to invoke the new model wizard. Right click on the models folder and select New - jACT-R Model.

new-model-1.jpg

This will open the wizard, allowing you to select from a set of preexisting modules. You will only need Declarative, Procedural, Retrieval, and Goal. Clicking Finish will generate the initial code necessary to get started.

new-model.jpg

Chunk-types & chunks

To create the two chunk-types needed, scroll down to the declarative-memory section, type chu and hit Control-Space. This will pop up a sorted list of code template proposals. Typing XML is a pain, so I've automated most of it for you (templates exist for the Lisp editor too). Enter the name of chunk-type (addition-fact) and the first slot name (addend1). The slot value can be left as null (null and nil are treated the same). Hit return to close the template.

chunktype.jpg

But we still have two more slots to define! Type sl and hit Control-Space again. Look, slot templates.

chunktype-2.jpg

You can use this technique for almost every element in jACT-R. Go ahead, finish the chunk-types and chunks. Then we'll hit the productions.

Productions

Productions can use the exact same template system, but these templates are often a little more complex. First, make sure you are in the procedural section of the model, then type p and Control-Space. The default production template will prompt you for the production name, the initial buffer to test, it's type, a slotName to test, an initial and final slot value. This pattern is really useful because it helps ensure that you write productions that don't loop forever (because they don't change the state).

production.jpg

Now, let's assume we are really lazy typers, or forgot the name of the chunk-type we are testing. Type the first couple of letters, add and hit Control-Space again. You will see a list of possible chunk-types, select and it will fill in the rest. Since the XML format is strongly typed, context completion is generally really good at guessing whether you want buffer, chunk-types, chunks, slots or variables. The Lisp editor isn't quite that good (yet).

guess.jpg

Finally, you are also able to use code templates for queries, proxies and scripts (jACT-R equivalents of !eval!), as well as add, remove, and output actions.

IDE Note : Templates can be modified and customized through the preferences.

Initial Goal

To set the initial goal, merely modify the goal buffer section towards the end of the model.

goal.jpg

Running Your Model

The easiest way to create a new run configuration is to just copy an old one. From the Run Configuration dialog, select the count configuration and click the duplicate buttoncopy_edit.gif . Then uncheck the count model, check your new model and click Add Alias. You'll probably want to change the name of the run configuration, apply and run.

run-copy.jpg

jACT-R Note : The alias mechanism allows you to run multiple instances of the same model in the runtime without any further hassle.

If that isn't enough to keep you occupied until I write the unit 2 tutorial project, you can take a look at these other topics:

Structure

Within jACT-R there is are clear functional distinctions between various elements. At the highest level is the model itself. Contained within the model can be both modules and extensions. Modules contribute functionality to the model in a theoretically justified manner such as the visual, motor, or declarative modules. Extensions are atheoretic contributions that are often used for integration or computational purposes. Both modules and extensions are embedded within the model file and can contribute their own model elements (e.g. chunks, productions, buffers).

There are also instruments which are elements that are attached to models but do not affect the execution of it. Instruments include loggers, tracers, recorders and probes. Instruments are contributed and configured via the run configuration.

Finally there are the sensors which allow you to interface simulation or realtime systems. Sensors provide an abstraction (via CommonReality) to percept and effectors. Using this abstraction opens the door to other architectures being able to interface the systems (as opposed to coding the interface directly to jACT-R).

Chunks

Chunks are the declarative glue that holds a model together. They are the explicitly inspectable knowledge representation (as opposed to productions). At the architectural level, the declarative module is responsible for their creation, indexing, and management. At the model level, they are created when a new representation is added to a specific buffer and encoded on removal (usually).

Copying

Copying a chunk is frequently needed by buffers that copy on insertion. This is delegated to the IDeclarativeModule. Note: You cannot copy chunks from one model into another. That's a theoretical brain transplant.

 IModel model = ...
 IDeclarativeModule decM = model.getDeclarativeModule();
 IChunk toCopy = ...
 IChunk copied = decM.copyChunk(toCopy).get();

Creating

  IModel model = ...
  IDeclarativeModule decM = model.getDeclarativeModule();
  IChunkType chunkType = ...
  String chunkName = ...
  Future result = decM.createChunk(chunkType, chunkName);
  IChunk newChunk = result.get();

The IDeclarativeModule explicitly supports asynchronous action. That is why all the methods return futures and not the relevant type directly. If you don't care about asynchrony, just call the future's get() method. Otherwise, separate the request from the harvesting to give other threads a chance..

Encoding

 IModel model = ...
 IChunk chunkToEncode = ...
 IDeclarativeModule decM = model.getDeclarativeModule();
 Future result = decM.addChunk(chunkToEncode);
 
 IChunk encoded = result.get();
 
/*
 Merging might mean that the returned chunk is not the exact same reference,
 however, both IChunk objects will point to the same content
*/

 boolean alwaysTrue = encoded.equalsSymbolic(chunkToEncode);

 boolean trueIfNotMerged = encoded == chunkToEncode;

Modifying

Chunks once encoded cannot be modified, only copied (IDeclarativeModule.copyChunk()). However, if it hasn't been encoded, there are two considerations. If you have just created the chunk and no other buffers or modules might have access to it, you can merely manipulate it directly:

 IChunk chunk = ...
 ISymbolicChunk sc = chunk.getSymbolicChunk();
 
 for(ISlot slot : sc.getSlots())
  ((IMutableSlot) slot).setValue(...);

//the same applies for parameters

However, once the chunk is accessible in the system (added to a buffer, referenced by another chunk, etc), you need to be concerned about other threads accessing it. If the chunk is in a buffer, the easiest way to do this is with ChunkUtilities and its associated IChunkModifier interface. The utility class handles the locking and execution time for you.

  IModel model = ...;
  IActivationBuffer goalBuffer = model.getActivationBuffer(IActivationBuffer.GOAL);
  ChunkUtilities.manipulateChunkLater(goalBuffer, new IChunkModifier(){
    public void modify(IChunk chunk, IActivationBuffer buffer)
    {
       ISymbolicChunk sc = chunk.getSymbolicChunk();
       for(ISlot slot : sc.getSlots())
        ((IMutableSlot) slot).setValue(...);
    }
});

Otherwise, you should use the chunk's write lock to ensure no contention.

 IChunk chunk = ...
 Lock lock = chunk.getWriteLock();
 try
 {
   lock.lock();
    ISymbolicChunk sc = chunk.getSymbolicChunk();
       for(ISlot slot : sc.getSlots())
        ((IMutableSlot) slot).setValue(...);
 }
 finally
 {
  lock.unlock();
 }

Modules

Theoretical functionality in jACT-R is defined and extended through the use of modules. As modules are developed and refined, their specification and details will be added here.

Declarative

The declarative module interface defines the methods necessary to asynchronously create and encode chunktypes and chunks. It also includes methods for disposing of temporary chunks (or cleaning up chunks during model disposal), and both partial and exact search methods.

An abstract implementation is provided that handles all of the basics (including event firing) requiring implementations of the actual create, add, copy, dispose and search methods.

There are specific extension interfaces for theory revisions four and five.

The default implementation conforms to theory revision six.

AbstractDeclarativeModule

The AbstractDeclarativeModule provides most of the nitty-gritty implementation details that are required for the IDeclarativeModule. However, it leaves the theoretically relevant components (creating, encoding, searching of chunks/types) to the client.

DefaultDeclarativeModule

The DefaultDeclarativeModule is the general starting point for any theory specific implementation. It is thread safe, provides all the required functionality, and also provides access to the three most commonly required extension points. Specifically, you can provide it with:

  • IDeclarativeNamer to specify the names of chunks/types on encoding
  • IDeclarativeInstantiator to instantiate the specific class of chunk/types required by the module (defaults to DefaultChunk5 and DefaultChunkType5)
  • IDeclarativeConfigurator to configure the instantiate chunk/types (useful for adding non-default parameters, etc).

Version 6

Default declarative module for ACT-R 6. Carries with it some reminents from ACT-R 5. (namely support for similarities). This module uses the IDeclarativeConfigurator to set the various activation equations used by the chunks (i.e., IBaseLevelActivationEquation, ISpreadingActivationEquation, IRandomActivationEquation)

Parameters

  • EnablePartialMatching : default false.
  • ActivationNoise : default 0. Note: a random module must be installed to use.
  • PermanentActivationNoise : default 0. See note above.
  • BaseLevelConstant : default 0.
  • MismatchPenalty : default 1.
  • MaximumSimilarity: default 0
  • MaximumDifference : default -1.

Associative Linkage

Associations in jACT-R are handled by IAssociativeLinkageSystem, which is handled by the Declarative module (often pulling the linkage system from a declarative learning module).

The linkage system provides methods for creating links, accessing their respective equations (for updating values) and a parameter handler to transform strings into lists of associative links. In conjunction with their declarative learning module, they will often install event listeners in order to install and update the links correctly.

Declarative Learning

Declarative learning is generally accomplished by an IDeclarativeLearningModule. Since the learning theories evolve faster than the rest of the architecture, there are significant differences between the various implementations and their expectations.

Version 4

DefaultDeclarativeLearningModule4 provides the ACT-R 4 style of base-level learning and associative learning. It expects that the chunks created by the installed declarative module support the ISubsymbolicChunk4 interface, as well the associative linkage system to be the DefaultAssociativeLinkageSystem (which creates Link4 associative links).

Note: Theoretically outdated. Limited support available.

Parameters

  • BaseLevelLearningRate : Decay rate of base level activation (values: numeric>0, NaN (off). default: 0.5)
  • AssociativeLearningRate : Carry over from prior estimates to the current (values: numeric >=0. default 1).
  • OptimizedLearning : Number of reference times to retain in the calculations (values: 0-100. default: 0) Note: 0 implies no optimization, that is, all references are retained.

Version 6

DefaultDeclarativeLearningModule6 extends the version for 4 by replacing the associative linkage system and providing an additional parameter for the MaximumAssociativeStrength.

Parameters

  • BaseLevelLearningRate : Decay rate of base level activation (values: numeric>0, NaN (off). default: 0.5)
  • MaximumAssociativeStrength : Smax (values: numeric >=0. default 1).
  • OptimizedLearning : Number of reference times to retain in the calculations (values: 0-100. default: 0) Note: 0 implies no optimization, that is, all references are retained.

Imaginal

The imaginal system is used to store intermediate problem state representations. Typically used in conjunction with the goal module which retains the goal state.

The imaginal system is time dependent (unlike the goal module), so all imaginal buffer operations take some (configurable) amount of time.

Version 6

DefaultImaginalModule6 is the default implementation of the imaginal system.
It provides the imaginal buffer, and has parameters to manipulate the amount of time add and modify requests take.

Parameters

  • AddDelayTime : How much time does it take to add a new representation to the imaginal buffer (values: numeric>=0. default:0.2)
  • ModifyDelayTime : How much time does a modification take (values: numeric>=0. default:0.2)
  • RandomizeDelaysEnabled : are the delays stochastic? (values:true/false. default: false) (Note: requires random module to be installed)

Intentional (goal) module

The DefaultGoalModule6 provides a simple immediate buffer and little else.

Perceptual Modules

Aural Module

(exists, documentation pending)

Motor module

DefaultMotorModule6 provides the functionality of the manual module in the lisp, but generalizes it over all possible muscles.

(exists, documentation pending)

Visual Module

DefaultVisualModule6 provides most of the visual functionality found within the lisp equivalent. It provides a fully extensible system for adding new feature maps (for visual searches), visual encoders (for converting percepts into chunks), and filters (for search time prioritizing). The time it takes to find or encode visual objects is also configurable.

Those wishing to add new functionality to the system will typically implement the following delegate interfaces or their abstract implementations:

  • IPerceptualEncoder or AbstractVisualEncode : Provides methods to determine whether or not a particular percept (IAfferentObject) is relevant. If so, it is responsible for creating the chunk, updating it, and marking it as invalid if it changes too much.
  • IFeatureMap, AbstractVisualFeatureMap, or AbstractSortedVisualFeatureMap : Provides a new feature map to be searched by the visual system. The class is responsible from pulling features out of a percept, indexing them, and providing search results.
  • IIndexFilter, or AbstractVisualLocationIndexFilter : Responsible for normalizing requests (i.e., resolving module dependent values like nearest=current), defining a weight (relative to other filters) for prioritizing, and filtering out other visual-locations.

To add any of these, merely provide the full class name of the implementor as a parameter name, and set its value to true. Setting the value to false will remove the named class.

Significant Divergences

(from the lisp)

  • visual-location.screen-x/-y are in visual degrees, not pixels.
  • origin of the visual field is in the center, increasing up and to the right (as opposed to the screen based upper-left corner, increasing down and right)
  • object tracking is automatic, so long as the percept does not exceed movement tolerances.
  • visual errors will set the visual buffer's error slot to provide more detailed information as to why the failure occurred. (error-nothing-matches, error-nothing-available, error-changed-too-much, error-no-longer-available, error-invalid-index)

Parameters

  • VisualEncodingTimeEquationClass: class name of implementor of IVisualEncodingTimeEquation. (default: DefaultEncodingTimeEquation).
  • VisualSearchTimeEquationClass: class name of implementor of IVisualSearchTimeEquation. (default: DefaultSearchTimeEquation)
  • EnableVisualBufferStuff: should new visual locations be stuffed into the buffer w/o a search. (default: false).
  • VisualFieldWidth: width in degrees of the visual field (default: 140)
  • VisualFieldHorizontalResolution: How many visual degrees separate unique visual-locations (default : 1)
  • VisualFieldHeight: height in degrees of the visual field (default: 90?)
  • VisualFieldVerticalResolution: How many visual degrees separate unique visual-locations (default : 1)
  • NumberOfFINSTS: how many finsts are available (default: 3)
  • FINSTDurationTime: how long does a finst last (default: 4)
  • NewFINSTOnsetDurationTime: how long does a new finst remain marked as new (default: ?)
  • MovementTolerance: How many visual angles must a percept move in a single interval before it has moved too much. (default: ?)
  • EnableStrictSynchronization: force the architecture to process all incoming perceptual messages before attempting to do a search or encoding. (default: false)

Vocal Module

(exists, documentation pending)

Procedural

The procedural module is the second primary module that all models must have (the other being declarative). It is responsible for managing all productions in the model.

Version 6

DefaultProceduralModule6 provides the primary procedural functionality. The most common points of extension are in conflict set assembly, production instantiation and selection. Each has its own delegate interface (IConflictSetAssembler, IProductionSelector, IProductionInstantiator).

This implementation is also responsible for enforcing strict harvesting. For any buffer that has strict harvesting enabled (IActivationBuffer.isStrictHarvestingEnabled()), the module checks to see if the production matching on that buffer also includes a remove. If not, one is added automatically.

Parameters

  • NumberOfProductionsFired : How many productions have fired up to this point.
  • ExpectedUtilityNoise : Noise parameter for utility (values: number>=0. default : 0).
  • DefaultProductionFiringTime : How much time a production takes to fire (values: numeric>0. default: 0.05)

Procedural Learning

Procedural learning has undergone much evolution since the early days of ACT-R.

Version 6

DefaultProceduralLearningModule6 provides access to production compilation and utility learning. Utility learning utilizes the IExpectedUtilityEquation. Production compilation delegates to the IProductionCompiler. The module is responsible for propagating rewards backward in time.

Utility learning is fully functional, and can even support the learning of utility noises (UtilityNoiseLearningExtension).

Production compilation, while implemented, is based directly on the lisp version and is not as extensible as desired. The long term goal is to get it using the ICompilableContext which describes buffers in terms of their critical properties for production compilation.

This implementation also has the ability reward productions selectively based upon what buffers they act upon. Obviously, we want to reward productions that act on the goal and imaginal buffers, but there exist a class of productions where rewards are less relevant. Goal-free or reflexive productions are often used for basic model behavior that exists below the intentional level. By limiting the productions to be rewarded to those that access the "IncludeBuffers" parameter, you can exclude some productions from rewards, without affecting the rest of the reward chain.

Parameters

  • ExpectedUtilityEquation : class name for the implementor of IExpectedUtilityEquation (default : DefaultExpectedUtilityEquation)
  • ProductionCompiler : class name for the implementor of IProductionCompiler (default: DefaultProductionCompiler6)
  • EnableProductionCompilation : should comilation be used (values:true/false. default:false)
  • OptimizedLearning : number of time references (for the production) to be retained. 0 means retain them all. (default: 0)
  • ParameterLearningRate : Discount applied to utility learning (values: numeric >0, NaN (off) default: NaN)
  • IncludeBuffers : a list of buffer names (coma separated) that productions must match/manipulate in order to be considered for rewarding. (default: "goal, imaginal, retrieval")

Random

IRandomModule provides a centralized location for the generation of random numbers. Any module that relies upon randomness should check for the existence of a random module (e.g. (IRandomModule) model.getModule(IRandomModule.class)) and use the returned instance. If no random module is installed, random behavior should not be enabled. This allows one to easily enable/disable random behavior. By centralizing the random generation, you can more easily guarantee repeatable behavior for a given random seed.

There is a single default implementation.

Default

DefaultRandomModule

Parameters

  • RandomSeed : default automatically generated.
  • TimeRandomizer : default 3. Sets the seed for the time randomization used for the equal distribution (time * ((randomizer-1)/randomizer))

Retrieval

The basic retrieval module template. All retrieval module implementations must support the parameter RetrievalThreshold, which defines the minimum chunk activation value that a chunk must have to be accessible.
This module class also provides access to the model-wide retrieval time equation. And an asynchronous retrieval method. Note: the retrieval method need not actually be asynchronous, but support is provided at this level since retrofitting is such a pain.
There is currently a single default implementation of this module.

Version 6

The default implementation provides access to the basic retrieval functionality, as well as exposes the parameters defined in 4.0 (LatencyFactor,LatencyExponent) and permits partial matching (if the declarative module's PartialMatchingEnabled is true).
Since it can be so useful for those that aren't completely concerned with retrieval behavior, the module also permits the use of indexed retrievals. If you already have a reference to a specific chunk (say as a slot value), you can insert it directly into the retrieval buffer to access it on the next cycle. To enable this, set the EnableIndexedRetrievals to true. Otherwise, +retrieval> =chunkRef will merely create a pattern based on that chunk and search for it, as is the canonical behavior.
Unlike most other buffers, the retrieval buffer provided by this module does not copy the chunk on insertion. It won't permit you to make modifications (=retrieval> slot newValue) will always fail. This was done to prevent unnecessary copying. Reference counts/access times are still consistent.
Actual retrievals are merely delegated to the declarative module. If the declarative module is asynchronous, so too shall the retrievals.

Parameters

  • RetrievalThreshold : default 0. -Infinity to disable entirely. If -Infinity is used, 0 will be used for the retrieval time equation so that failed retrievals don't take infinite time to complete.
  • LatencyFactor : default 1.
  • LatencyExponent: default 1. (unpublished parameter).
  • EnableIndexedRetrievals : default false. If true, a direct reference to the chunk is immediately retrieved.
  • FINSTDurationTime: default 3 seconds.
  • NumberOfFINSTs: default 4 chunks.

Meta-slots

  • :recently-retrieved : Test for whether this chunk has been recently retrieved (values: true/false). Can be used at request (RHS) or matching (LHS).

Extensions

Extensions provide non-theoretic functionality to models. They may contribute chunks, types, and productions - but do not normally contribute buffers. Below is a listing of some of the existing extensions, what they do and where they can be found.

Performance extensions

These extensions are used to tweak the performance of model runs.

Optimized conflict resolution

DefaultProceduralModule6 has support for pluggable components that influence conflict set generation, production instantiation, and instantiation selection. This enables one to optimize some of the most expensive parts of cycle execution without regard to the underlying storage mechanism. By default these methods are fairly stupid. The selector just takes the first one (assuming they are sorted by utility). The instantiator just attempts a blind instantiation of all possibilities given the current buffer contents. The conflict set generator simply grabs all the potentially relevant productions (based on the chunktypes in buffers).

Given that performance profiling has shown that instantiation is actually the most expensive phase (given that so many productions will fail), it was an obvious target for some optimizations. The org.jactr.extensions.cached.procedural.CachedProductionSystem tracks and caches instantiation failures. When the condition that resulted in the failure changes, the cache is invalidated, and the next time the production is in the conflict set, it will attempt to instantiate it. If at instantiation, there is a cached failure for that production, it is returned instead. All of this was done by just providing a new IProductionInstantiator.

How much of a performance improvement can you get? Well, that depends on nature of your model, but a ballpark figure is anywhere between 35-90% of an improvement in the real cycle time. How can you tell? Turn on profiling.

Poor-man's Profiling

jACT-R has some basic performance profiling built in. To enable it, add -Djactr.profiling=true to the VM arguments in your run configuration. Be sure to remove all instruments and IDE tracers when running. When the model finishes running, you'll see a print out like this:

Total actual processing cycles 13057

Simulated processing cycles 13057

Total actual time 13.460383s

Simulate time 499.0947763613296s

Average sleep time (wait for clock) 0.26255740215976103ms

Average time processing events 0.07167572949375814ms

Average production cycle time 0.7668560925174236ms

Average production time + waits 1.0308940032166656ms

Realtime factor 37.07879459011899 X

If you enable the CachedProductionSystem (more on that in a bit), you can expect something like this:

Total actual processing cycles 13163

Simulated processing cycles 13163

Total actual time 10.886717s

Simulate time 503.54204421495893s

Average sleep time (wait for clock) 0.312651371267948ms

Average time processing events 0.06956552457646432ms

Average production cycle time 0.5131492061080302ms

Average production time + waits 0.8270695889994683ms

Realtime factor 46.25288268400463 X

Of particular interest is the change in average production cycle time. This includes conflict set assemble, instantiation, selection, and the posting of new events. The improvement here was only 35%, but that is because this particular model has many productions that compete (on average each cycle has 3 productions) - the fewer competing productions you have, the bigger your performance improvement.

Using Extension

The CachedProductionSystem is included in the default org.jactr.tools bundle. To enable it, include the following in your extensions block (which is after the modules block):

      
       <extension class="org.jactr.extensions.cached.procedural.CachedProductionSystem">

<parameters>
<parameter name="EnableCaching" value="true" />
<parameter name="ValidateInstantiations" value="false" />
</parameters>
</extension> Caveat:

When you first use it, set ValidateInstantiations to true. This will perform the normal caching operations and still attempt to instantiate the production. If there is a discrepancy, an error will be logged. This is just to verify that it does work for all cases, until a more formal test can be devised (at which time this code will be rolled into the main distribution).

Instruments

Instruments are classes that are intended to inspect running models in some way. They are to be passive, in that they should have no influence on the model's behavior. Typically they are used for logging, tracking, or sending data to file.

Default Model Logger

This instrument intercepts the log messages and sends them to a named file. After the file reaches a maximum size, it is backed up. The number of backups can be configured. By default, this instrument attaches to all the models in the runtime. When configured programmatically, you can specify which log streams go to which file. In the IDE, all the streams go to a single file.

Parameters

  • MaxFileSize: in megabytes before the file is backed up and a new file created.
  • NumberOfBackups: the number of backups to retain.
  • all: what filename to send all the streams to.

Model Recorder

Instrument that records the runtime's models at the start and stop of execution. The format of the saved models can be configured to use any of the installed syntaxes (e.g., jactr, lisp). The location of the files can also be specified.

Parameters

  • SaveAsExtension : extension of the code generator to use. (default: jactr)
  • StartDirectory : local working directory path to save the start state models to. Will be created if missing. (default: start/)
  • StopDirectory : local working directory path to save the stop models to. Will be created if missing. (default: stop/)
  • TrimModuleContributions : Don't include the injected content from modules and extensions in the generated code. (default: true).

XML Model Logger

An alternative logger that routes messages to an xml file. This logger attaches to all the models in the runtime.

Parameters

  • FileName : the xml file to save to.

Hacking jACT-R

Here's a quick peak at how to find and fix bugs that may appear in jACT-R. This screencast illustrates how you can actually view all the jACT-R source from within Eclipse in case something goes wrong. It also provides instructions for how to import the source packages, permitting you to edit the core and make any changes you desire.

Developing for jACT-R: Scripted Conditions and Actions

Developing for jACT-R: Scripted Conditions and Actions

All model require some measure of atheoretic coding to interface with some device or experiment or to implement behavior that is outside of theoretical consideration. While custom conditions and actions are best, sometimes we just need to do a little hacking. That's where scripting comes in. Currently jACT-R only supports using javascript, but adding new scripting languages is relatively easy (if it's supported by javax.script).

Conditions

Scriptable conditions use the scriptable-condition tag which must enclose a CDATA element (so that arbitrary text can be included). Then it's just a matter of implementing matches() and returning true or false (whether the condition is satisfied). Here's a simple snippet that expects =base to be resolved to a number, increments it and binds the value to =inc.

<scriptable-condition lang="javascript"><![CDATA[
        function matches()
        { // expects =base and binds =inc
          //test to make sure =base is resolved
         jactr.requires("=base");
//get the value of =base as a double and increment var inc = jactr.get("=base").doubleValue(); inc += 1;
//bind =new to the value of inc jactr.set("=inc", inc); return true; }]]> </scriptable-condition>

The use of the jactr.requires() method allows the condition to verify that the required variables have been resolved before attempt to execute the script.

Actions

Actions use the scriptable-action tag with the enclosed CDATA and implement the fire() method, with return value in seconds. The return value represents a theoretical amount of time that this action can take, just leave it as 0. This example posts a mock event to fire one second in the future. This is a trick that many Lisp modelers use to keep their models running when conflict resolution might otherwise result in the model being terminated.

<scriptable-action lang="javascript"><![CDATA[
        function fire()
        { 
         jactr.postMockEvent(jactr.getTime()+1);
         return 0; //amount of time this action takes to execute in theory
        }]]>
</scriptable-action>


Script support

The jactr script object provides access to many common routines and is backed by the ScriptSupport class. With it you can copy or encode chunks, get and set variables (local and global) and slot values, get the current time, send output to the log, stop a model, post events (to delay model termination), and even get access to the runtime.

Developing for jACT-R: Custom Conditions and Actions

Developing for jACT-R: Custom Conditions and Actions

All models require some amount of atheoretic code to address interfacing or behavior that resides outside of the modeled behavior. While jACT-R supports scripting (equivalent to !eval! in the canonical Lisp implementation), the faster option is to use compiled java code. Developers are free to create any custom conditions or actions which can be accessed via the proxy-condition and proxy-action directives (in the jACT-R syntax). (apologies that my blogging tool screws up code formatting)

      <production name="custom-short-cut">
<conditions>
<match buffer="goal" type="add">
<slot name="arg1" equals="=num1"/>
<slot name="arg2" equals="=num2"/>
</match>
<query buffer="retrieval">
<slot name="state" equals="error"/>
</query>
<proxy-condition class="org.jactr.examples.custom.AdditionCondition">
<slot name="arg1" equals="=num1" />
<slot name="arg2" equals="=num2" />
<slot name="sum" equals="=sum" />
</proxy-condition>
</conditions>
<actions>
<proxy-action class="org.jactr.examples.custom.SetResultAction">
<slot name="add-chunk" equals="=goal" />
<slot name="sum" equals="=sum"/>
</proxy-action>
<proxy-action class="org.jactr.examples.custom.OutputSumAction">
<slot name="output" equals="=sum"/>
</proxy-action>
<!-- reset the retrieval buffer -->
<add buffer="retrieval" type="clear"/>
</actions>
</production>

While one can implement the full interfaces for IAction or ICondition, it is usually easier to extend one of the abstract or default implementations. AbstractSlotCondition is the best starting point for conditions, and DefaultSlotAction for the actions. However, the class must provide a zero-arg constructor to be properly instantiated by the builder.

Unit 1 Addition

The Unit 1 addition model uses declarative retrievals to add two numbers. The model's success depends upon it having sufficient count-order chunks to cover both numeric arguments. If it doesn't, the model will have a retrieval failure and halt. Let's make a simple modification so that upon retrieval failure, a production kicks in to save the day. The full production is below. It uses one custom condition and two custom actions.

AdditionCondition

In this model the AdditionCondition does just that. It takes the two arguments, adds them together and will resolve the sum slot. The condition can also be used to test the sum if the sum slot is not a variable. To implement it we start by extending AbstractSlotCondition which provides us the majority of the logic required.We still have to provide two key pieces clone() and bind().

During conflict resolution, the procedural module first attempts to instantiate each of the productions. The first step is to clone the production so that it can be modified. Each of its conditions are cloned during which the condition can perform any tests to see if it is even possible to instantiate it.


/**
* check that the required slots are present and numbers/variables then copy
*
* @see ICondition#clone(IModel, Map) for details
*/
public ICondition clone(IModel model, Map<String, Object> variableBindings)
throws CannotMatchException
{
/*
* let's make sure arg1, arg2, and sum exist and that they are variables or
* numbers
*/
for (String slotName : new String[] { "arg1", "arg2", "sum" })
{
ISlot slot = getSlot(slotName);
if (slot == null)
throw new CannotMatchException(String.format(
"slot %s must be defined (number or variable)", slotName));


if (!slot.isVariableValue() && !(slot.getValue() instanceof Number))
throw new CannotMatchException(String.format(
"slot %s must be a number or variable", slotName));
}


AdditionCondition clone = new AdditionCondition();
/*
* the slot based request manages all the slots for us, but we do need to
* copy them.
*/
clone.setRequest(new SlotBasedRequest(getSlots()));
return clone;
}

Once all of the conditions have been cloned, the procedural module will iteratively resolve the variable bindings (it has to be done iteratively to support variable slot names and capacity buffers). During binding, each condition's bind method is called. It returns the number of unresolved variables or throws a CannotMatchException. Once fully bound, the instantiation copies and resolves the production's actions and the instantiation can then be selected to fire.


/**
* @see ICondition#bind(IModel, Map, boolean) for more details
*/
public int bind(IModel model, Map<String, Object> variableBindings,
boolean isIterative) throws CannotMatchException
{
//let the slot based request handle most of the binding
int unresolved = getRequest().bind(model, variableBindings, isIterative);
/*
* there are three possible unresolved slots here: arg1, arg2, and sum. we
* can test and calculate once unresolved is <=1 or isIterative is false, as
* that means sum is unresolved (unresolved=1), needs to be compared in a
* test (i.e. arg1 1 arg2 2 sum 3), or this is the last iteration
*/
if (unresolved <= 1 || !isIterative)
{
ISlot arg1 = getSlot("arg1");
ISlot arg2 = getSlot("arg2");


/*
* none of those can be null, otherwise we'd never have gotten through
* clone, but they may have been variables that have been resolved to
* something other than a number
*/
double one = getValue(arg1);
double two = getValue(arg2);
double trueSum = one + two;


/*
* now one of two things could be true about the sum slot. It could still
* be unresolved, in which case we resolve it ourselves and decrement
* unresolved. Or we need to compare a resolved sum value to the true
* value. To do this, we need to snag all the slots that are named sum
*/
for (IConditionalSlot cSlot : getConditionalSlots())
if (cSlot.getName().equals("sum"))
{
if (cSlot.isVariableValue())
{
// resolve
if (cSlot.getCondition() == IConditionalSlot.EQUALS)
{
//add the binding
variableBindings.put((String) cSlot.getValue(), trueSum);
//and resolve the value
cSlot.setValue(trueSum);
unresolved--;
}
else if (!isIterative)
throw new CannotMatchException(String.format("%s is unresolved",
cSlot.getValue()));
}
else
{
/*
* the slot already has a value, which means we need to make a
* comparison against trueSum
*/
if (!cSlot.matchesCondition(trueSum))
throw new CannotMatchException(String.format(
"%s=%.2f does not match condition %s.", cSlot.getName(),
trueSum, cSlot));
}
}
}


return unresolved;
}

OutputSumAction

Actions come in two general flavors: immediate and deferred. Immediate actions are fired when the production first starts to fire. Typically you will use these if the action does not depend upon or manipulate specific chunk or buffer states. This simple action merely outputs a value to standard out.

Once an instantiation's conditions have been fully resolved, the production's actions are copied and bound to the instantiation. This is accomplished by calling the action's bind method which returns a fully bound copy of the action.

The OutputSumAction merely extends DefaultSlotAction and provides a bind method that checks to see if the output slot exists and returns a copy of itself.


/**
* just check to see if the output slot exists.
*/
public IAction bind(Map<String, Object> variableBindings)
throws CannotInstantiateException
{
ISlot slot = getSlot("output");
if(slot==null)
throw new CannotInstantiateException("slot output must be defined");

/*
* this constructor will resolve the variables in the slots based on
* the variable bindings
*/
return new OutputSumAction(variableBindings, getSlots());
}

If the instantiation is then select to fire, all of the actions' fire methods are called (in order). The method returns a value which can be used to offset the execution time of the production beyond the default action time. In this case, we merely print the value of output to standard out.


/**
* merely print the value of the output slot.
* @see IAction#fire(IInstantiation, double);
*/
@Override
public double fire(IInstantiation instantiation, double firingTime)
{
Object value = getSlot("output").getValue();

System.out.println(String.format("Output value %s @ %.2f", value, firingTime));

return 0;
}

SetResultAction

Delayed actions are used when you need to manipulate chunks or buffers. These occur at the end of the production firing (typically 50ms after the initial firing time). The default add, modify, and remove actions all do this and is recommended for any action that operates on chunks or buffers. If you do not, the actions may not be properly sequenced and strange things can happen.

In this contrived example, SetResultAction takes two parameters: a chunk of type add and a sum. It will then set the sum slot of the chunk to sum and set the count slot to arg2 (the terminal conditions for the model). But first let's look at the bind method. Bind checks to make sure each of the slots are defined and then passes things off to a private constructor. The private constructor then performs some resolution and verifies the types of each of the slot values.


/**
* just check to see if the count-chunk and sum slots exists.
*/
public IAction bind(Map<String, Object> variableBindings)
throws CannotInstantiateException
{
ISlot slot = getSlot("add-chunk");
if(slot==null)
throw new CannotInstantiateException("slot add-chunk must be defined");

slot = getSlot("sum");
if(slot==null)
throw new CannotInstantiateException("slot sum must be defined");

/*
* this constructor will resolve the variables in the slots based on
* the variable bindings
*/
return new SetResultAction(variableBindings, getSlots());
}

/**
* calls the {@link DefaultSlotAction} full constructor which will perform the variable resolution
* @param variableBindings
* @param slots
* @throws CannotInstantiateException
*/
private SetResultAction(Map<String, Object> variableBindings,
Collection<? extends ISlot> slots) throws CannotInstantiateException
{
super(variableBindings, slots);

Object value = getSlot("sum").getValue();
if(!(value instanceof Number))
throw new CannotInstantiateException("sum must be a number");

/*
* let's make sure count-chunk is correct
*/
value = getSlot("add-chunk").getValue();
if(!(value instanceof IChunk))
throw new CannotInstantiateException("add-chunk needs to be a chunk");

/*
* it's a chunk, but is it the correct type?
*/
IChunk chunk = (IChunk) value;
IChunkType chunkType = chunk.getSymbolicChunk().getChunkType();
if(!chunkType.getSymbolicChunkType().getName().equals("add"))
throw new CannotInstantiateException(String.format("%s is not add", chunkType));
}

Finally, when fire is called, instead of performing the chunk manipulations right there, it queues an ITimedEvent. This goes onto the timed event queue which is processed after the clock but before the conflict resolution phase.


/**
* here we will change the sum slot of the count-chunk to the actual sum value.
* Instead of doing it immediately, we delay until the production finish time (typically
* 50ms after the firingTime).
* @see IAction#fire(IInstantiation, double)
*/
@Override
public double fire(IInstantiation instantiation, double firingTime)
{
/*
* first we create the timed event to do the work. This will be fired
* on the model thread after the clock has advanced but before the conflict
* resolution phase
*/
IModel model = instantiation.getModel();
IProceduralModule procMod = model.getProceduralModule();


double finishTime = firingTime + procMod.getDefaultProductionFiringTime();

final IChunk chunk = (IChunk) getSlot("add-chunk").getValue();
final double sum = ((Number)getSlot("sum").getValue()).doubleValue();

ITimedEvent futureEvent = new AbstractTimedEvent(firingTime, finishTime){
public void fire(double currentTime)
{
super.fire(currentTime);

/*
* we only manipulate unencoded, non disposed chunks. It could
* have been encoded if it was removed from the buffer before
* this was called. Disposal is rare, but is good to check for
* just in case.
*/
if(!chunk.isEncoded() && !chunk.hasBeenDisposed())
{
/*
* contains the symbolic portion of the chunk: name, type, slots
*/
ISymbolicChunk sChunk = chunk.getSymbolicChunk();
/*
* now we need to get the correct slot
*/
ISlot sumSlot = sChunk.getSlot("sum");
/*
* since the chunk isn't encoded, it's safe to assume that this
* slot is actually mutable
*/
((IMutableSlot)sumSlot).setValue(sum);

/*
* and the model doesnt terminate until count==arg2
*/
((IMutableSlot)sChunk.getSlot("count")).setValue(sChunk.getSlot("arg2").getValue());
}
}
};

/*
* now we queue up the timed event
*/
model.getTimedEventQueue().enqueue(futureEvent);

/*
* we still return 0 because the production still only takes 50ms to fire
*/
return 0;
}

Running the Model

Now when you run the model, if it has sufficient count-order chunks, it will run as before. However, if it does not, the custom-short-cut production will fire, which will do the addition, update the chunk and output the answer to standard out. If you were doing this coding yourself instead of importing the provided code or using the new project wizard, it wouldn't actually run..

Java-specific Nuisances

jACT-R is built on top of Equinox (an implementation of OSGi). This provides it with many benefits, but it does expose the modeler to a built of Java specific irritation: classpaths. If you were coding this yourself and tried to run the model, you'd see some ClassNotFoundExceptions in the console. That is because the parser and builders do not automatically have access to the custom code. To make the code visible, the relevant packages need to be exported to the runtime (making them visible to other classes). This can be accomplished by opening the META-INF/MANIFEST.MF file, selecting the Runtime tab, and adding the packages to the export set.

The manifest file handles all of the meta information for jACT-R projects (bundles or plugins). As these developer examples progress, we will keep returning to this file as the final step in putting everything together.

References

Developing for jACT-R: Instrumentation

Developing for jACT-R: Instrumentation

One of the key benefits of modeling is that it allows us to instrument and inspect internal states that are not otherwise accessible in the real world. While most psychological measures are behavioral, sometimes we want to record data from deeper within the model. The IInstrument interface allows us to do just that.

Instruments

Instruments are the bits of code that inspect the state of the model. They are intended to be light-weight and not influence the execution of the model in anyway. A prime example of an instrument is the Model Recorder which can be used to save a copy of the model pre- and post-execution. These are the tools that are accessible in the run configuration's Instruments tab:

200910221415.jpg

This example will walk you through the implementation of a simple instrument that tracks the average interval between two specified production firings.

Events and Listeners

Before diving into the details of the example instrument, it is worth discussing how most instruments (and most jACT-R code in general) are implemented. Where the canonical Lisp ACT-R implementation relies upon hooks to extend behavior, jACT-R uses events and registered listeners. Virtually every theoretically and computationally relevant element in jACT-R fires off events to interested listeners. A partial sampling includes:

Most of the event sources support both synchronous and asynchronous event notification by requiring you to specify an Executor for each listener. This basically allows you to control what thread the event will be processed on. If you need to receive the event immediately and on the same thread that issued the event, you can use ExecutorServices.INLINE_EXECUTOR. However, instruments should not do this unless absolutely necessary. Synchronous event handling runs the risk of negatively impacting model execution speed (and in some cases, logical flow). Instead, instruments should use a separate executor. By default the runtime provides a background executor which is a low-priority, shared thread (ExecutorServices.getExecutor(ExecutorServices.BACKGROUND)).

ProductionIntervalTimer

The example starts by implementing two interfaces: IInstrument and IParameterized. The first marks it as an instrument and has a contract for three methods: install, uninstall, and initialize. The second allows the instrument to be customized at runtime using named parameter values. The most important piece here is the install method in which we attach a listener to the procedural module. One thing to note: if a runtime includes multiple models, only one instrument will be instantiated and its install & uninstall methods will be called for each model.


public ProductionIntervalTimer()
{
/*
* when the procedural module fires events, this will be called. We're only
* interested in the productionFired event, all the others are Noops.
*/
_proceduralListener = new ProceduralModuleListenerAdaptor() {
public void productionFired(ProceduralModuleEvent pme)
{
/*
* pass on the production (actually the IInstantiation) and
* the simulated time that the event occured at.
*/
checkProduction(pme.getProduction(), pme.getSimulationTime());
}
};
}
/**
* called when the instrument is installed, this is the time to attach any
* event listeners
*/
public void install(IModel model)
{
/*
* When the procedural module does something important, it fires off an
* event. We can register a listener for that event quite simply. But we
* need to consider how that even will be delivered. It can come
* synchronously (on the model thread as the event occurs) or asynchronously
* (to be delievered later). Since this is an instrument, which should not
* affect model execution, we will attach asynchronously
*/
IProceduralModule procMod = model.getProceduralModule();


/*
* the background executor is a low priority shared thread that is often
* used for listeners. If we wanted to listen synchronously we could use
* ExecutorServices.INLINE_EXECUTOR
*/
procMod.addListener(_proceduralListener, ExecutorServices
.getExecutor(ExecutorServices.BACKGROUND));
}

When a production is fired, the listener is asynchronously notified on the background thread and checks to see if the production is the start or stop production and handles the timer logic accordingly.


/**
* test the production that fired to see if it start or stop and adjust the
* clock accordingly
*
* @param firedProduction
* @param firingTime
*/
private void checkProduction(IProduction firedProduction, double firingTime)
{
// timer hasn't been started, check for start
if (Double.isNaN(_startTime))
{
if (_startProductionName.matcher(
firedProduction.getSymbolicProduction().getName()).matches())
_startTime = firingTime;
}
else
{
//timer has started
if (_stopProductionName.matcher(
firedProduction.getSymbolicProduction().getName()).matches())
{
_sampleTimes.addValue(firingTime - _startTime);
_startTime = Double.NaN;
}
}
}

When the model execution completes, uninstall is called, so we use that opportunity to dump the statistics.


/**
* after the model has run and before it is cleaned up, instruments are
* uninstalled. we'll output the result here
*/
public void uninstall(IModel model)
{
/*
* detach the listener
*/
model.getProceduralModule().removeListener(_proceduralListener);


System.out.println(String.format(
"Average time between productions : %.2f (%.2f)", _sampleTimes
.getMean(), _sampleTimes.getStandardDeviation()));
}

The start and stop production name regular expression patterns are defined using the setParameter method defined by the IParameterized interface.


/**
* we set the start/stop regex patterns here
*/
public void setParameter(String key, String value)
{
if(START_PATTERN.equalsIgnoreCase(key))
_startProductionName = Pattern.compile(value);
else if(STOP_PATTERN.equalsIgnoreCase(key))
_stopProductionName = Pattern.compile(value);
}

Running the Model

Included in this example is the unit 1 semantic model. You'll want to create a run configuration for it and enable the Production Interval Timer instrument (as seen in the image at the top of the article). Run the model and in the console you should see the output of the instrument.

Java-specific Nuisances

As we saw in the prior article, there needs to be some configuration of the META-INF/MANIFEST.MF file, which manages all the meta information for the project. As with the last example, we need to make sure that the instrument's package is exported to the runtime. That will allow the runtime to see, create and install the instrument. However, that won't tell the IDE about the existence of your instrument.

In order to make your instrument visible to the IDE, you need to provide an Eclipse extension (not to be confused with a jACT-R extension). From the manifest file, you can select the Extensions tab, click on add and select org.jactr.instruments. From there you will specify the name and class of the instrument, a description, and provide default parameter names and values. Once this is done, the IDE will be able to see your instrument and it will be accessible in the run configuration's Instruments tab.

200910221515.jpg

References

Developing for jACT-R: Extensions

Developing for jACT-R : Extensions

Sometimes a model needs to interact with a device or system, but a full perceptual interface would be overkill. This is particularly true when the theoretical aspects of the model have nothing to do with embodied interaction. Sometimes you just need to model to have information dumped straight into its head. That's where extensions come in. They provide atheoretic additional functionality.

Overview

Here's the hypothetical situation: The model needs to interface with a networked service and track some variables that will change over time. The extension will provide a buffer tracker containing a chunk of type track-data that contains three slots: time (the variable being tracked), changed (boolean flagging whether the data is new) and requested-at (holds the simulated time that the request was made at).

Module

Because buffers are theoretical model components, an extension cannot (easily) contribute a new buffer. Modules are the default contributor of buffers. First we will create a TrackerModule extending AbstractModule, which will return a BasicBuffer6 from the createBuffers protected method. We'll also ensure that there is a track-data chunk in the buffer via the initialize method.

  

protected Collection<IActivationBuffer> createBuffers()
{
return Collections.singleton((IActivationBuffer) new BasicBuffer6(
"tracker", this));
}

@Override
public void initialize()
{
/*
* let's make sure that we've got a tracker-data chunk in the buffer
*/
IActivationBuffer buffer = getModel().getActivationBuffer("tracker");
try
{
IDeclarativeModule decM = getModel().getDeclarativeModule();
IChunkType chunkType = decM.getChunkType("track-data").get();
IChunk chunk = decM.createChunk(chunkType, null).get();
buffer.addSourceChunk(chunk);
}
catch (Exception e)
{
LOGGER.error("Could not find or create track-data chunk : ", e);
}
}

Contributing Chunk-types

So that any model using the module and extension are aware of the buffer and chunk-type, we provide a model snippet (tracker.jactr) that is automatically injected into other models. This is accomplished by providing an IASTParticipant (usually just an extension of BasicASTParticipant), that is registered using Eclipse's extension mechanism (which will be discussed later).

public class TrackerModuleParticipant extends BasicASTParticipant{
  public TrackerModuleParticipant()
{
super("org/jactr/examples/tracker/io/tracker.jactr");
setInstallableClass(TrackerModule.class);
}
}

MockInterface

The mock network interface provides long running methods to open and close the connection, as well as methods to request new data and to process the results. Because all of these operations are long running (merely calling Thread.sleep()), calling them from the model thread would result in a significant performance penalty. Finally, the interface provides a method hasNewData() which is used to signal whether the data processing method can be called.

TrackerExtension

Finally, we have the extension. This is the code that utilizes the simulated network interface, performs the data requests and processing, and massages the results into the buffer provided by the module. Ideally, we'd connect to the network just before the model starts running, disconnect after termination, and update the buffer contents at the top of each production cycle. This can all be accomplished by using the model listener with an inline executor (discussed previously).

Threading and Performance

As was mentioned in the previous article, when using event listeners you should consider the performance characteristics of your code. If your code is fast and light, or you absolutely need to be notified of an event immediately, you can use the inline executor. This ensures that the thread that fired the event also fires your listener. However, expensive code should usually be processed on a separate executor (possibly the shared background or periodic threads) so that it doesn't get in the way of normal model execution. There is one other consideration when using a separate executor, and that is the frequency of the event. If you've got slow code that fires very frequently (e.g. blocking IO called at the top of each production cycle), it will never be able to keep up with the model. Events will be rapidly queued onto the executor faster than they can be cleared off.

In this particular example, we have very expensive connection management and processing. Performing these operation inline with the model would bring it to a screeching halt (e.g. 100ms blocking IO at the top of each production cycle). However, using a separate executor to process the events would also cause a problem. The executor simply wouldn't be able to handle the flood of events in a timely manner. Instead, we'll use a combination of strategies. The model event listener (listed below) will handle the events inline with the model thread. However, all the actual lengthy processing will be handled by a dedicated executor. Because we only update the buffer if there is new data, the majority of the cycleStarted() methods do nothing.

  public TrackerExtension()
{
_modelListener = new ModelListenerAdaptor() {
/**
* at each cycle, check to see if the network interface has new data for
* us to process. If so, execute the processing on the dedicated executor.
*/
public void cycleStarted(ModelEvent me)
{
if (_networkInterface.hasNewData() && !_dataProcessingQueued)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format("new data is available"));
_dataProcessingQueued = true;
_networkExecutor.execute(new Runnable() {
public void run()
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format("processing data"));
// lengthy operation
processData();
//reset the flag
_dataProcessingQueued = false;
}
});
}
}
/**
* called on startup, this is our chance to actually connect to the
* network interface. Again, we do it in a dedicated connection
*/
public void modelConnected(final ModelEvent me)
{
_networkExecutor.execute(new Runnable() {
public void run()
{
try
{
if (!_networkInterface.isConnected())
{
_networkInterface.connect(_connectionString);
// make initial request
_networkInterface.requestUpdate(me.getSimulationTime());
}
}
catch (Exception e)
{
// do something
}
}
});
}
/**
* and disconnect on the network thread
*/
public void modelDisconnected(ModelEvent me)
{
_networkExecutor.execute(new Runnable() {
public void run()
{
try
{
if (_networkInterface.isConnected())
_networkInterface.disconnect();
}
catch (Exception e)
{
// do something smart
}
finally
{
_networkInterface = null;
}
}
});
}
};
}

public void install(IModel model)
{
_model = model;
/*
* first test to see if the tracker module is installed
*/
TrackerModule tm = (TrackerModule) _model.getModule(TrackerModule.class);
if (tm == null)
throw new IllegalExtensionStateException(
"TrackerModule must be installed");


_trackerBuffer = _model.getActivationBuffer("tracker");
/*
* since network based code often is a massive bottleneck, we want to put it
* on its own thread. We'll use this executor
*/
_networkExecutor = Executors.newSingleThreadExecutor();
/*
* but we'll queue up processing on the executor based on the model events,
* which we will handle inline with the model
*/
_model.addListener(_modelListener, ExecutorServices.INLINE_EXECUTOR);
}

ChunkUtilities

One thing to notice is that the manipulation of the trace-data chunk in the tracker buffer is being delegated through ChunkUtilities. There are a few caveats when manipulating chunks. If you've just created the chunk and no one has access to it, you can just manipulate the slots returned from IChunk.getSymbolicChunk().getSlots() after casting each slot to an IMutableSlot. If the chunk has made it into declarative memory, it will be marked as encoded and immutable, any attempts to change the values will throw an exception. However, if the chunk is in a buffer it won't be encoded (unless it's the retrieval buffer) and can be manipulated. But to do so, you need to make sure that it is locked to prevent other access. To further ensure safety, it is recommended that you perform the manipulation on the model thread itself. ChunkUtilities will invoke your ChunkUtilities.IChunkModifier on the model thread within the write lock as soon as possible. This is a convenience method for those that don't want to deal with the nitty-gritty details.

    /**
   * hypothetical processing of data.. We take the data and then (safely) modify
* the chunk that is in the buffer to match
*/
private void processData()
{
final Map<String, Object> data = _networkInterface.getNewData();
/*
* to safely update the buffer contents, we must do it on the model thread.
* we could cache the data locally and use an additional, inline model
* listener and do the work in cycleStarted(), or use a timed event.
* ChunkUtilities.modifyLater does the later
*/
ChunkUtilities.manipulateChunkLater(_trackerBuffer,
new ChunkUtilities.IChunkModifier() {
public void modify(IChunk chunk, IActivationBuffer buffer)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format("Updating buffer"));
/*
* we are assuming that the fields in data match 1-1 to the slots of
* the track-data chunk
*/
ISymbolicChunk sc = chunk.getSymbolicChunk();
for (Map.Entry<String, Object> entry : data.entrySet())
{
IMutableSlot slot = (IMutableSlot) sc.getSlot(entry.getKey());
if (slot == null) continue;
slot.setValue(entry.getValue());
}
}
});
/*
* after we've processed the data, let's request an update
*/
try
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format("requesting update"));


_networkInterface.requestUpdate(getModel().getAge());
}
catch (IOException e)
{
// handle this gracefully
}
}

Running the Model

The model for this example is incredibly simple. First notice that it installs both the TrackerModule and the TrackerExtension. The TrackerModule, via the TrackerModuleParticipant, injects the chunk-type and buffer into the model. Once the model is built, the TrackerExtension is initialized and the model can start. Two productions will fire: echo-old when there is old data available, and echo-new when there is new data in the buffer. The model will run for ever, so terminate it after you get a feel for what is going on. In the screenshot below you can see what is happening. At 61.95 (simulated) the model makes a request for new data. It gets the response at 74.95 (simulated), but in realtime, only 519ms have actually elapsed. The simulation is able to plow forward in time with no problem, without saturating the network with spurious requests.

200912291044.jpg

Java-specific Nuisances

As we've seen in the past two articles, the META-INF/MANIFEST.MF file needs to be tweaked. First, we export the org.jactr.examples.tracker packages so that they are visible to the core runtime. If you do not do this, you will be plagued by class not found exceptions. Next we provide three Eclipse extensions that allow the runtime to detect the module, extension, and ASTParticipant. For each extension you'll basically be providing a name and class, plus a view extra bits of information. Without this, your model will have compilation errors as the module and extension won't be visible (to your model or anyone else's). Without the ASTParticipant extension defined, your model won't correctly import the injected content.

200912291050.jpg

References

Classpath Woes

One of the biggest hassle in Java is managing the classpath. OSGi takes care of some of the problems, but sometimes you still get bitten by FileNotFound or ClassNotFound, and yet the file/class is right there! Here's a checklist of things to look at when it comes to this problem:

  1. Is the resource you are trying to access stored under {project}/configuration? This folder is provided as a safe location to stash your resources without having to deal with custom classpaths.
  2. Remove the runtime cache ({home}/.jactr/configuration). This is where classpath information is cached so that future runs execute quicker.
  3. Clean build?

If none of those fix it, you are facing a problem with projects having to access each other's classpath. Each project has its own classpath that only it can see. If project A depends on project B, A can see into B's classpath, but not the other way around. Most likely the problem you are running into is that B needs to access a resource in A's classpath. The prime example of this is a module (provided by B) needs to read and parse a configuration file (provided by A). B needs a way to peak back inside of A.

If you look in the META-INF/MANIFEST.MF file, you will see how this is accomplished. You're looking for the Eclipse-RegisterBuddy, this tag tells OSGi that the following projects should be allowed to access the project's classpath.

200811191813.jpg

In the above example, everything in CommonReality, core, tools and io can access the project's classpath. This information is in addition to the dependencies. All register buddies must also be in the dependency list. New jACT-R projects automatically have the above register buddies set up.

After you add the project that needs to peek inside the current one, you can update the classpath via the PDE context menu.

CommonReality

What is CommonReality, Sensors, and Agents?

Cognitive modeling is increasingly focusing on embodiment, not only to improve model fidelity, but because additional affordances and cognitive short-cuts. In ACT-R this is accomplished via the perceptual/motor modules (i.e. visual, aural, etc). But that perception on the cognitive side. We still need a means of accessing the sensation. Where as the Lisp implementation uses devices, jACT-R takes a step further back.

CommonReality

CommonReality is a simulation management system. It was built to provide a general mechanism to manage agents (i.e. models, actual humans, AI systems) and sensors (i.e. simulation systems), their communication, time management, and control. It was designed with the intent that all these various pieces could be running anywhere, on any hardware. Network latency and the added layer of abstraction do limit simulation speed, but I believe the added flexibility is worth it.

At the heart of CommonReality is a system that acts as a central broker. It controls the clock (which can be managed in a configurable manner), control flow and state changes, and communication among participants (but point-to-point is possible).

Agents

Agents are the cognitive entities in a simulation. They are the consumers of afferent objects, aka sensations, generated by a sensor for it. Agents can also make use of a combination of efferent objects and their commands which permit the agent to execution actions in the simulation (or reality). The agent handles the communication with CommonReality and the translation of afferent and efferent objects for the cognitive model, in this case jACT-R.

The structure of afferent objects is basically just a unique identifier and a series of properties. The set of properties is modality specific. There are recommended properties for visual and aural modalities. Sensors are free to implement the recommended properties, but may supply additional perceptual information. As the system grows and evolves, a discovery mechanism will be developed, but for now it requires consideration one the sensor and agent sides.

Sensors

Sensors provide a perspective on some element(s) of a simulation to the agents it is aware of. Most sensors will provide information to all agents, but this is not strictly required. For any sensation, the sensor will request new afferent objects to represent the sensation to each of the agents. It will then update those objects and communicate those changes. When the sensation is no longer available, it will be removed. Some sensors may provide efferent objects, and templates to create proper commands to control them. There are a handful of sensors already available, and hopefully with collaborative effort there will be many more.

The XMLSensor merely translates a configuration file into raw afferent objects. It does so based on timed or programmatic events. Because it can provide any afferent object, it is ideal for simplified testing. However, more complex situations require other options.

DefaultAuralSensor provides a rough skeleton for programmatically providing auditory information. This is useful as a starting point for other sensors. DefaultSpeechSensor serves a similar purpose.

The DefaultKeyboardSensor provides hands and fingers to models that can then control the actual keyboard (by using java.awt.Robot). Mouse support is on the to-do list.

Finally, there is an interface for playerstage that is continually evolving.

A framework exists for aiding in the development of more complex motor control sensors. Best practices for developing sensors is still evolving, but the provided sensors will get you started.

Developing for CommonReality: Extending BaseSensor

Developing for CommonReality: Extending BaseSensor

When a model's interaction with the physical characteristics of some device become the focus, merely duct-taping the model to a system (such as with an IExtension) will not do. In these situations, it is time to develop an interface within CommonReality, jACT-R's abstraction layer. Briefly, CommonReality divides a simulation (or real system) into participants, specifically agents (i.e. models, AIs, or real people) and the inappropriately named sensors (i.e. sensor systems, effectors, simulators). jACT-R includes an agent interface to CommonReality, and hopefully the future will see interfaces for other cognitive architectures. There are existing sensors for keyboard control, generic speech generation, seeing Java GUIs, simple static simulations, seeing and moving within PlayerStage simulations, and even for controlling NRL's MDS robots.

The CommonReality system manages the participants' life-cycles, configuration, time synchronization and messaging, allowing participants to be executed in various configurations. The underlying protocols and transports are all configurable, decoupling the system from running within a single VM, machine or language. Unfortunately, building sensors (or agents) is a fairly involved process involving many different object managers (i.e. afferent, efferent, simulation object), listeners, and different threads of execution. Over the course of developing the existing sensors, a few patterns have become apparent, particularly for perceptual sensors (as opposed to effectors). The BaseSensor codifies many of these and will be continually developed to make it easier to quickly build sensors while focusing strictly on the interfacing relevant to your task.

BaseSensor

The BaseSensor bundle includes two abstract implementations and two interfaces that need to be considered. BaseSensor itself handles the basic life-cycle of the sensor and thread management. On each cycle it sends any pending messages out to CommonReality. Next, it calls methods to trigger the processing of perceptual and motor information for each of the connected agents (BaseSensor handles multiple agents with no difficulty). It then synchronizes with the clock before looping back. For the most part, extenders will just want to customize the configuration, initialization, start, and shutdown methods (being sure to call the super class implementations when done). The processing of percepts is typically delegated to the PerceptManager.

PerceptManager

The PerceptManager provides a consistent mechanism for the linking of program objects (or events) to percepts to be passed to connected agents. The PerceptManager doesn't create the percepts itself, rather it delegates that responsibility to the installed IObjectCreators and IObjectProcessors. By delegating, it is easier to extend or modify the perceptual processing on the fly without having to dig into any other code. The general concept is that the code that is interacting with the interfaced system will want to generate percepts for some set of objects. Those objects will serve as keys to the PerceptManager. When an object changes (or is created), markAsDirty(Object) is called. When the object is removed, flagForRemoval(Object) is called. When processDirtyObjects() is called (typically from BaseSensor.processPercepts()), PerceptManager routes new objects to the IObjectCreators, updated objects to the IObjectProcessors, and removals to the owning IObjectCreators (allowing them to veto the removal). It packages the resulting state updates and passes them back to the BaseSensor for packaging for CommonReality. Generally, you can use the default implementation provided by the BaseSensor.

IObjectCreator

ObjectCreators are intended to link the programmatic objects with individual percepts (on a per-agent basis). Typically an ObjectCreator will be associated with a single type of programmatic object (i.e. string) and a single type of percept (i.e. visual). If the object creator can handle the programmatic object, it will be asked to create an IObjectKey (merely a structure linking the programmatic object, percept, and creator). Next the creator will actually create the basic percept and any default values for it. Actual deep perceptual processing is typically passed off to the object processors to handle. The object creator is also responsible for handling the clean up of the object upon removal (i.e. the creator may attach listeners to the programmatic object to track its state and trigger PerceptManager.markAsDirty, which you'd want to detach after the object has been removed).

IObjectProcessor

The object processors are the actual work-horses of the BaseSensor. If the processor can handle the associated object key, it is then asked to perform its actual processing. This is where the properties of the percept are actually extracted and assigned to the percept.

Threading

The design of this system is such that it can handle multiple different threading models. You could use listeners attached to the programmatic objects to merely mark them as dirty, allowing the PerceptManager to process them in BaseSensor's main thread. Or, you could do the processing in the listeners, and merely harvest the results when PerceptManager calls the IObjectProcessors. You could even call PerceptManager.processDirtyObjects() on a separate thread as well. Hell, you could even overload the method and split that processing up across multiple threads. It all depends on the demands of your interface.

StringSensor

Let's put this system to a very trivial example. The StringSensor takes a single argument, "TextSource" which is a string. The sensor will break that string at the white spaces and then feed back to CommonReality visual percepts of each word one at a time per second. Let's start with the lowest level and work up.

StringObjectCreator

The object creator extends the abstract implementation which merely creates a DefaultObjectKey and an IAfferentObject from that key. It handles only strings, and using the abstract implementations initialize() method, sets the percept as a visual one.


public StringObjectCreator()
{
}


public boolean handles(Object object)
{
return object instanceof String;
}
/**
* initialize some default values
*/
@Override
protected void initialize(DefaultObjectKey objectKey,
IMutableObject afferentPercept)
{
// make it a visual percept
afferentPercept.setProperty(IVisualPropertyHandler.IS_VISUAL, Boolean.TRUE);
}

StringObjectProcessor

The object processor also extends the abstract implementation and only handles strings. It's process method sets all the required (and optional) properties of visual percepts. If your system had more properties, there is nothing stopping you from assigning them as well. These just represent the barebones, some of which jACT-R requires, some not.


public StringObjectProcessor()
{
}


public boolean handles(DefaultObjectKey object)
{
return object.getObject() instanceof String;
}
public void process(DefaultObjectKey object, IMutableObject simulationObject)
{
/*
* let's spoof some visual properties. Ideally you wouldn't set all the
* properties every time, rather just those that change on each call to
* process.
*/
String string = (String) object.getObject();


// currently visible, required
simulationObject.setProperty(IVisualPropertyHandler.VISIBLE, Boolean.TRUE);


// 1 meter in front of the eye, required
simulationObject.setProperty(IVisualPropertyHandler.RETINAL_DISTANCE, 1);


// 1 degree visual angle up and to the right (0,0 is center), required
simulationObject.setProperty(IVisualPropertyHandler.RETINAL_LOCATION,
new double[] { 1, 1 });


// 2 degrees visual angle in size, required
simulationObject.setProperty(IVisualPropertyHandler.RETINAL_SIZE,
new double[] { 2, 2 });


// type of visual percept, required
simulationObject.setProperty(IVisualPropertyHandler.TYPE,
new String[] { "text" });


// actual unique identifier, required
simulationObject.setProperty(IVisualPropertyHandler.TOKEN, string);


// text value, if available
simulationObject.setProperty(IVisualPropertyHandler.TEXT, string);


// horizontal, optional
simulationObject.setProperty(IVisualPropertyHandler.SLOPE, 0);


// RGBA, optional
simulationObject.setProperty(IVisualPropertyHandler.COLOR, new double[] {
1, 0, 0, 1 });
}

StringSensor

Normally, you'd have your system marking objects as dirty or flagging them for removal through some other execution flow. In this trivial example, I'm just going to do it at the top of each cycle (i.e. before the sensor sends the pending messages). It simply checks the time to see if it should remove (or send) a word. If so, it does its processing and notifies the PerceptManager. That's it. The super class takes care of all the rest.


/**
* ideally you'd perform some configuration here. these options are provided
* (typically) from the environment configuration file at the start of the
* run.
*/
@Override
public void configure(Map<String, String> options) throws Exception
{
super.configure(options);


_wordsToSend = new ArrayList<String>();
/*
* TextSource property is defined in the plugin.xml extension and can be
* customized through the run configuration
*/
for (String word : options.get("TextSource").split(" "))
_wordsToSend.add(word);

/**
* install the default creator and processor
*/
getPerceptManager().install(new StringObjectCreator());
getPerceptManager().install(new StringObjectProcessor());
/*
* just for giggles, let's make it realtime (ish)
*/
setRealtimeClockEnabled(true); }




/**
* at the top of the cycle, we peel of a new word and create a percept for it,
* removing the old word in the process. This is a silly single threaded
* example. Normally, you'd have some other thread churning along in response
* to other events and interacting with the percept manager
*/
@Override
protected void startOfCycle()
{
if (_sendNextWordAt <= getClock().getTime())
{
if (_lastWord != null)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format("Removing %s", _lastWord));


getPerceptManager().flagForRemoval(_lastWord);
}
_lastWord = null;


if (_wordsToSend.size() > 0)
{
/*
* peel off a word, notify the percept manager to remove the previous
* one and add the new one
*/
String newWord = _wordsToSend.remove(0);


if (LOGGER.isDebugEnabled())
LOGGER.debug(String.format("Perceiving new word %s", newWord));


// percept manager will handle all the heavy lifting for us
getPerceptManager().markAsDirty(newWord);
_lastWord = newWord;
_sendNextWordAt = getClock().getTime() + 1; // 1 second later
}
else
// setting this to + inf will prevent any further processing
_sendNextWordAt = Double.POSITIVE_INFINITY;
}
}

Running the Model

If you look in the second example project (sensors.example.test) you will find an incredibly simple model. It merely looks for anything on the screen and runs indefinitely. Take a look at the included run configuration (string.visual-test). Under the CommonReality tab, you will see the StringSensor selected and it's single parameter. We're telling the system to launch CommonReality with this model (as an agent) and the StringSensor. We could just as easily replace the StringSensor with any other sensor that provides visual information and the model would be non-the-wiser. Go ahead, give it a run. You should see some stuff in the visicon viewer and the model should output each of the words individually.

200912291454.jpg

Java-specific Nuisances

As we've seen in the past three articles, the META-INF/MANIFEST.MF file needs to be tweaked. First, let's look at the one in org.commonreality.sensors.example. First, we add the dependency to org.commonreality.sensors.base, otherwise we wouldn't have access to the base classes. Next, we export the org.commonreality.sensors.example packages so that they are visible to the core runtime. If you do not do this, you will be plagued by class not found exceptions. Finally, we provide one Eclipse extensions that allow the runtime to detect the sensor (making it visible to the IDE as well). We can also provide the parameter that the sensor takes allowing us to configure it in the IDE.

200912291459.jpg
Turning our attention to the sensors.example.test project, we also need to add a dependency, this time to org.commonreality.sensors.example. That's it. Everything is wired together.

References

Divergences

Here I list out the general differences between the canonical and the Java implementations of ACT-R. Since jACT-R has been coded largely in isolation from the canonical code, there are many design decision differences.

I am fully committed to theoretical compatibility, but not implementation. In other words, equations and key behaviors are the primary goal (i.e. activation equations and chunk immutability), not consistency in trivial areas (i.e. exact chunk structures, slot names, or visual search priorities). But, I will endeavor to ensure minimal hassle when migrating models from one version to another (i.e. automatic translation/transformation).

Productions

Overwrite action

Canonical ACT-R supports an overwrite buffer operation that performs an end-run around the managing module and also prevents the encoding of previous contents. =goal> =chunk will replace the contents of the goal buffer with =chunk without encoding the current contents.

jACT-R does not directly support this operation. You can set, add, remove, modify or request, and that's it. You cannot circumvent the encoding. The best you can do is use the set operation which will move a chunk from one buffer to another (in 0 time). But the prior contents will still be encoded. The Lisp parser will tranform overwrites into sets automatically and notify you that this has been done.

Explicit state reset

If a retrieval fails, the only way to reset the state is to complete a successful retrieval. There is no mechanism to explicitly reset the state. While some modules do have this (+visual> isa clear), it is not standard. jACT-R solves this by using an explicit remove operation (-retrieval>). If there is nothing in the buffer, it will have its states reset.

Retrieval module

Indexed Retrievals

Retrieval module does permit indexed (i.e. immediate) retrievals of chunks you already have references to. However, this must be explicitly enabled with the EnableIndexedRetrievals parameter.

Asynchrony

Retrievals can also be performed asynchronously if the declarative module is asynchronous.

Declarative FINSTS

Declarative FINSTs are controlled by two parameters: FINSTDurationTime (3 seconds) and NumberOfFINSTs (4 chunks). :recently-retrieved (null, false, true) can be used in retrieval requests.


Visual System

Coordinate System

Quick, draw a two-dimensional graph. Did you put the x-axis on the bottom, increasing to the right with the y-axis on the left increasing upwards? You sure didn't draw it with y-axis decreasing downwards. Now draw a graph with a nature center point. Yeah, you get the picture.

Canonical Lisp uses a computer screen based coordinate system, +x to the right, +y down. While the slot names are the same (for now), the coordinates in jACT-R are retinotopic visual angles (degrees). 0,0 is at center, +x to the right, +y up.

Recycled Locations

From an informal survey of modelers, I found that almost none of us use the visual-location chunks for anything other than to encode some object. Because of this limited use and the fact that during the lifetime of a model there can be thousands of these chunks created, jACT-R actually recycles visual-locations.

There is at most one visual-location for each possible retinotopic position (as defined by the visual field size and resolution). Its x and y locations are fixed, but the remaining slot values are mutable by the system. The mutable slot values have a relatively limited lifespan. They are valid only after a visual search until the next visual search is started. In this way the explosive growth of visual locations is eliminated, which is particularly important when operating for long periods in a truly embodied environment.

FINSTS

Canonical ACT-R marks FINSTS at the visual-location level. This makes sense assuming that the visual scene is limited. However, if you've got multiple objects at the same location (due to visual resolution issue or overlap), you can easily end up missing objects and can possibly end up in a situation where you can never actually encode some objects.

To prevent this, jACT-R assigns FINSTS at the object level. In simple scenes, the behavior is exactly the same. In more complex scenes (with overlapping objects), this allows the visual system to differentiate between attended and unattended objects at the same location. The semantics are exactly the same for making a visual-location request.

The only incompatibility this introduces is with the visual-location buffer query. Canonical ACT-R supports ?visual-location> attended {t|new|nil}. jACT-R does not support this query at all. I'm looking at implementing it in the future, but it will be based on the attended status of all the objects at that location.

If you require this query, contact me and I'll see about bumping up its priority.

Movement Tolerances

Canonical ACT-R uses movement tolerance to allow the model to encode a visual object after it has moved from the location returned from the visual search. jACT-R uses this tolerance to also deal with object tracking. If the object is moving too fast, it will exceed the tolerance. When this occurs, the track is lost, the visual buffer state is set to error, but the chunk is maintained in the buffer. (I'm still trying to figure out whether it should be removed from the buffer or not)

Object tracking

So if I've attended to an object why do I have to explicitly tell the visual system to follow it? When something you're looking at moves, it's hard not to follow it. In jACT-R all attended objects are automatically tracked. The only benefit of using the object tracking mechanism is that it stuffs the updated visual location of the object into the visual-location buffer.

If the parameter EnableStickyAttention is true, attended objects will remain attended as long as the object is in the visual field. No amount of movement will shake it.

Asynchrony

All perceptual/motor modules are already asynchronous.

Motor System

There is no manual system, just a global motor system. The motor system is a drop-in replacement with the same basic functionality, plus a few bells and whistles.

Muscle-level parallelism

If EnableMuscleLevelParallelism is true, motor programs can be prepared and executed in parallel so long as they don't overlap in the muscle groups used.

Compound commands

The motor buffer can also contain chunks derived from compound-motor-command. Compound motor commands allow you to execute primitive motor commands in a coordinated manner, even though they are not actual motor commands.

Preparation

Canonical ACT-R provides a separate chunk-type to specify a motor command to be prepared in advance and then executed later. The problem with this mechanism is that it locks you into a limited set of predefined movements (Specifically keyboard and mouse movements), preventing modelers from contributing their own movement types. jACT-R instead uses a meta-slot (:prepare-only) which can be added to any movement request. Using this, any movement command (past or future) can be prepared in advance, and then executed with the execute chunk-type request.

Common Issues

How do I keep a model running when there is no goal?

Setting the model's EnablePersistentExecution parameter to true will enforce conflict resolution when there is no goal and no pending events.

How do I make the model skip cycles where nothing can happen?

Models that are entirely internal (i.e., they are not reactive to the external world), can use the internal event queue to determine when it can possibly fire again. To enable this, set the model's EnableUnusedCycleSkipping parameter to true.

Be warned, though - if the model is connected to another system for its perceptual world, the model will skip cycles, making the detection time wonky.

Model and IDE are maxing out my processor and sucking up memory!

Ah, now this is a problem that I run into regularly. Here's the deal: it's not a bug and it's not a memory leak. Almost all communication in jACT-R is asynchronous. This allows the model to run instrumented with (relatively) little performance impact. The problem arises when the model is actually running too fast (and producing too much data) for the IDE to keep up with. When this happens, network buffers rapidly start filling up and memory is consumed at an alarming rate. The two process then start butting against the resource limits and performance crawls to a halt - potentially with out of memory errors.

This problem most often occurs when persistent execution is on and cycle skipping is off. Basically, your model is plowing through a lot of worthless cycles and generating a bunch of data that is worthless. The IDE chokes on the volume and kaboom.

Your best bet fix is to enable the SynchronizationManager instrument in the run configuration. This will force message synchronization at a periodic (and configurable) rate.

Using IDE

The IDE has been built upon Eclipse, a powerful, multi-language IDE. It brings with it many features:

Quick Tips

Project management

  • Revert edits
  • Use source control (i.e. CVS, SVN)
  • Import/Export projects

User Interface

  • Change code or annotation (e.g. error, warning, reference, bookmark) colors?
  • Use collaborative editing?

Tooling

The IDE includes many useful tools both for tracing/inspecting model execution and for recording information.

Broadly speaking, there are tool classes of tools: instruments and tracers.

Instruments

Instruments are simple tools that can be attached at runtime to any model or model element and are intended to modify or record behavior. They do not interface with the IDE.
Instruments can be easily built by extending the IInstrument interface. Instruments can be made visible to the IDE by using the instruments extension point.

Tracers

Tracers are special instruments that have a GUI component installed in the IDE. These are a tad more complicated, but certainly within the realm of an experienced java programmer.
Tracers typically record and send massive amounts of data to the IDE, so care must be taken to handle the information in a responsive manner that doesn't make the IDE unusable.

General Probe

The general probe is a combination two tools that provides a general capacity to track arbitrary parameters over time. On the runtime side, org.jactr.tools.grapher.core.GeneralProbe selects model elements (productions, chunks, types, modules, buffers, etc), based on regular expression name matches (quick and easy to implement, but a little wonky if you don't know reg-ex), and attaches probes to them. The general probe then sends that data to the IDE where it is rendered in the jACT-R Probes view.  

Probe Configuration

GeneralProbe reads a user specified probe descriptor file that defines what model elements should be tracked and what parameters recorded. These probe files are usually stored within the configuration/ directory so that it is accessible on the classpath. The following is a useful template for building your own custom probes:

<!-- how frequently should we poll the probes (seconds) -->

<instrument window="1">

<!-- probes can be grouped together so that they share a common axis -->

<group id="harvestors">

<!-- this will just select a model named 'sub', .* will select all -->

<model pattern="sub">

<!-- harvest productions -->

<!-- just probe productions with '-harvest' at the end -->

<production pattern=".*-harvest">

<!-- just record the expected utility -->

<probe pattern="ExpectedUtility" poll="true" />

</production>

</model>

</group>

</instrument>

You can also probe chunks:

<chunk-type pattern="configural">

<chunk pattern="configural-.">

<probe pattern="BaseLevelActivation" poll="true"/>

</chunk>

</chunk-type>

The above will only probe configural chunks that match the name 'configural-?'. The chunk-type tag is optional.

Modules can also be probed:

<module pattern=".*Retrieval.*">

<probe pattern=".*Threshold" poll="true"/>

</module>

Using probes

Once the probe file is defined, you merely have to enable it in the run configuration. Merely enable General Probe in the Logging/Trace tab of the run configuration and provide it the probe file.

200812160953.jpg

As the model runs, the probes view will graph the values over time. The graphs aren't good enough for publication, but as a first pass monitor, it's value cannot be overstated. You'll understand immediately why a production is loosing out in conflict resolution or a chunk is failing to be retrieved.

200812161014.jpg

The viewer also has two export options: PNG image file or CSV data output.

Visicon Viewer

To help debug models that rely upon the visual system, jACT-R includes a graphical visicon viewer. It's a little rough around the edges right now, but it is certainly functional.

Using Visicon Viewer

To use, simply enable the visicon tracer in the run configuration for your model. Once the model starts, the visicon viewer (lower-left corner of the IDE) should be updated.

200812161002.jpg

200812161002.jpg

Currently the color coding is used to signal search and encoding results. A red border means the object there has been found, a solid red means it is the current focus of attention. If you mouse over the object you will see a listing of its primitive features.

Future updates will include better formatting of the information.