jACT-R Tutorials

Tutorial code is available as project downloads in each tutorial and at GitHub.

Novice Tutorials

These are tutorials meant for individuals new to modeling with ACT-R, regardless of the implementation.

Unit 1 Tutorial

[updated 08/09/15]

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. Now download the prroject archive for the tutorial: org.jactr.tutorial.unit1.zip (for experts: the contents of the archive are also available on Github.) Import instructions to import the jACT-R project contained in the zip file into Eclipse.

Count Model

The ACT-R tutorial unit 1 starts you off with the basics 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.

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:

Modeler's Tutorials

These tutorials are intended for those familiar with the canonical Lisp ACT-R. We'll dive first into the tools and the interfacing, glossing over modeling details.

Unit 1 - Advanced.

Check out the project from GitHub. The project, itself, contains the tutorial documentation. It will be added here shortly.

This tutorial shows some more advanced features such as custom conditions, actions, and scripting.

Developer Tutorials

Tutorials intended for developers only. You must be familiar with Java, and have at least done either the Novice or Modeler's tutorials. Warning: not all examples have been fully updated for v2. Some meta information may need updating.

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.

More information on this javascript interpreter can be found at the Mozilla Rhino development site.

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
     * effect 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

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

Development Issues

Cannot resolve bundle or missing classes

Sometimes after upgrading a workspace or an installation (including creating a new install), Eclipse gets confused and hoses things it shouldn't. A common problem is to see a slew of bundle resolution errors in the Manifest.MF, where before there were none. 

Merely go to Preferences - Plugin Development - Target Platform. Most likely it will be empty. Just click to restore defaults and save. 

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.

 

Bug Reports

Bugs happen. Particularly in research software, no matter how many best practices you observe (or ignore). This is how you should submit a problem.

 

  1. Make sure you can reliably reproduce the error. If you can't, no one else will either.
  2. Spend sometime trying to localize where the problem is. The entire source for jACT-R is included. If you look in the Plugin Dependencies folder of your project, you'll see org.jactr_xxxxxxxxxxx.jar, open it up. Packages are organized along theoretical lines. Problem with production instantiation? check org.jactr.core.production or org.jactr.core.module.procedural. Problem with the visual module? check org.jactr.modules.pm.visual. You get the idea. 
  3. When you have an idea as to where the problem might be, turn on the architectural logging. In the root of your project is a jactr-log.xml file. It allows you to enable/disable logging at the package or individual class level. Turn it on for where you think the problem is and examine the log output. If you see where the problem is, mark it in the file. You may need to turn on logging in the run configuration as well.
  4. If it doesn't contain sensitive information, or that information can be scrubbed out, export your project along with the run configuration and relevant log files to an archive file. 
  5. Submit a bug report with as much detailed information as possible. 
    1. What is the expected behavior of the model
    2. What is the observed behavior of the model
    3. Steps to reproduce
  6. For extra credit, you can reimport jactr into your workspace, try to fix it yourself and attach your recommended patch.

 

You can submit your bug reports to the GitHub site for each subcomponent:

  1. jACT-R Core proper
  2. jACT-R Eclipse interface