org.jactr.core.module.procedural.six.DefaultProceduralModule6.java Source code

Java tutorial

Introduction

Here is the source code for org.jactr.core.module.procedural.six.DefaultProceduralModule6.java

Source

/*
 * Created on Aug 14, 2006 Copyright (C) 2001-6, Anthony Harrison anh23@pitt.edu
 * (jactr.org) This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the License,
 * or (at your option) any later version. This library is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
 * the GNU Lesser General Public License for more details. You should have
 * received a copy of the GNU Lesser General Public License along with this
 * library; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA
 */
package org.jactr.core.module.procedural.six;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jactr.core.buffer.IActivationBuffer;
import org.jactr.core.buffer.event.ActivationBufferEvent;
import org.jactr.core.buffer.event.IActivationBufferListener;
import org.jactr.core.chunk.IChunk;
import org.jactr.core.chunktype.IChunkType;
import org.jactr.core.event.ACTREventDispatcher;
import org.jactr.core.event.IParameterEvent;
import org.jactr.core.logging.Logger;
import org.jactr.core.model.IModel;
import org.jactr.core.model.ModelTerminatedException;
import org.jactr.core.module.AbstractModule;
import org.jactr.core.module.procedural.IProceduralModule;
import org.jactr.core.module.procedural.IProductionInstantiator;
import org.jactr.core.module.procedural.IProductionSelector;
import org.jactr.core.module.procedural.event.IProceduralModuleListener;
import org.jactr.core.module.procedural.event.ProceduralModuleEvent;
import org.jactr.core.module.random.IRandomModule;
import org.jactr.core.module.random.six.DefaultRandomModule;
import org.jactr.core.production.CannotInstantiateException;
import org.jactr.core.production.IInstantiation;
import org.jactr.core.production.IProduction;
import org.jactr.core.production.ISymbolicProduction;
import org.jactr.core.production.action.IAction;
import org.jactr.core.production.action.IBufferAction;
import org.jactr.core.production.action.RemoveAction;
import org.jactr.core.production.condition.AbstractBufferCondition;
import org.jactr.core.production.condition.ChunkCondition;
import org.jactr.core.production.condition.ChunkTypeCondition;
import org.jactr.core.production.condition.IBufferCondition;
import org.jactr.core.production.condition.ICondition;
import org.jactr.core.production.condition.VariableCondition;
import org.jactr.core.production.six.DefaultProduction6;
import org.jactr.core.production.six.ISubsymbolicProduction6;
import org.jactr.core.utils.parameter.IParameterized;
import org.jactr.core.utils.parameter.ParameterHandler;

/**
 * <b>strict harvesting</b> is implemented by
 * {@link #addProductionInternal(IProduction)}. The {@link IProduction}'s
 * {@link ICondition}s are checked. If they are {@link IBufferCondition}, it
 * checks to see if {@link IActivationBuffer#isStrictHarvestingEnabled()} and if
 * so, ensures that there is an {@link IBufferAction} for that buffer as well,
 * if not, an {@link RemoveAction} is added (and therefore the production
 * explicitly removes the chunk).
 * 
 * @author harrison
 */
public class DefaultProceduralModule6 extends AbstractModule implements IProceduralModule6, IParameterized {

    /**
     * logger definition
     */
    static public final Log LOGGER = LogFactory.getLog(DefaultProceduralModule6.class);

    private ACTREventDispatcher<IProceduralModule, IProceduralModuleListener> _eventDispatcher;

    protected Map<String, IProduction> _allProductionsByName;

    protected Map<IChunkType, Collection<IProduction>> _allProductionsByChunkType;

    protected Map<String, Collection<IProduction>> _ambiguousProductions;

    protected ReentrantReadWriteLock _readWriteLock;

    protected double _productionFiringTime = 0.05; // 50ms

    protected long _productionsFired = 0;

    protected boolean _breakExpectedUtilityTiesRandomly = true;

    protected double _expectedUtilityNoise;

    protected ProductionUtilityComparator _comparator;

    protected IRandomModule _randomModule;

    private IProductionSelector _selector;

    private IProductionInstantiator _instaniator;

    private Collection<Map<String, Object>> _provisionalBindings;

    public DefaultProceduralModule6() {
        super("procedural");
        _allProductionsByName = new TreeMap<String, IProduction>();
        _allProductionsByChunkType = new HashMap<IChunkType, Collection<IProduction>>();
        _ambiguousProductions = new HashMap<String, Collection<IProduction>>();
        _readWriteLock = new ReentrantReadWriteLock();
        _eventDispatcher = new ACTREventDispatcher<IProceduralModule, IProceduralModuleListener>();

        _selector = new IProductionSelector() {

            public IInstantiation select(Collection<IInstantiation> instantiations) {
                if (instantiations.size() > 0)
                    return instantiations.iterator().next();
                return null;
            }

        };

        _instaniator = new IProductionInstantiator() {

            public Collection<IInstantiation> instantiate(IProduction production,
                    Collection<Map<String, Object>> provisionalBindings) throws CannotInstantiateException {
                try {
                    return production.instantiateAll(provisionalBindings);
                } catch (Exception e) {
                    throw new CannotInstantiateException(
                            "Could not instantiate " + production + " : " + e.getMessage(), e);
                }
            }

        };

        _comparator = new ProductionUtilityComparator() {
            @Override
            public int compare(IProduction one, IProduction two) {
                int rtn = super.compare(one, two);
                if (rtn != 0)
                    return rtn;

                if (_breakExpectedUtilityTiesRandomly)
                    return _randomModule.randomBoolean() ? 1 : -1;

                return one.getSymbolicProduction().getName().compareTo(two.getSymbolicProduction().getName());
            }
        };
    }

    public void setProductionSelector(IProductionSelector selector) {
        _selector = selector;
    }

    public void setProductionInstantiator(IProductionInstantiator instantiator) {
        _instaniator = instantiator;
    }

    public void addListener(IProceduralModuleListener listener, Executor executor) {
        _eventDispatcher.addListener(listener, executor);
    }

    public void removeListener(IProceduralModuleListener listener) {
        _eventDispatcher.removeListener(listener);
    }

    @Override
    public void dispose() {
        super.dispose();
        try {
            _readWriteLock.writeLock().lock();
            // actually dispose of the productions
            for (IProduction production : _allProductionsByName.values())
                production.dispose();

            _ambiguousProductions.clear();
            _ambiguousProductions = null;

            _allProductionsByChunkType.clear();
            _allProductionsByChunkType = null;

            _allProductionsByName.clear();
            _allProductionsByName = null;

            _eventDispatcher.clear();
            _eventDispatcher = null;

        } finally {
            _readWriteLock.writeLock().unlock();
        }
    }

    protected void fireProductionCreated(IProduction production) {
        _eventDispatcher
                .fire(new ProceduralModuleEvent(this, ProceduralModuleEvent.Type.PRODUCTION_CREATED, production));
    }

    protected void fireProductionAdded(IProduction production) {
        _eventDispatcher
                .fire(new ProceduralModuleEvent(this, ProceduralModuleEvent.Type.PRODUCTION_ADDED, production));
        if (Logger.hasLoggers(getModel()))
            Logger.log(getModel(), Logger.Stream.PROCEDURAL, "Encoded " + production);
    }

    protected void fireProductionWillFire(IInstantiation instantiation) {
        _eventDispatcher.fire(
                new ProceduralModuleEvent(this, ProceduralModuleEvent.Type.PRODUCTION_WILL_FIRE, instantiation));
        if (Logger.hasLoggers(getModel()))
            Logger.log(getModel(), Logger.Stream.PROCEDURAL, "Can fire " + instantiation);
    }

    protected void fireProductionFired(IInstantiation instantiation) {
        _eventDispatcher
                .fire(new ProceduralModuleEvent(this, ProceduralModuleEvent.Type.PRODUCTION_FIRED, instantiation));
        if (Logger.hasLoggers(getModel()))
            Logger.log(getModel(), Logger.Stream.PROCEDURAL, "Fired " + instantiation);
    }

    protected void fireProductionsMerged(IProduction original, IProduction duplicate) {
        ArrayList<IProduction> prods = new ArrayList<IProduction>();
        prods.add(original);
        prods.add(duplicate);
        _eventDispatcher
                .fire(new ProceduralModuleEvent(this, ProceduralModuleEvent.Type.PRODUCTIONS_MERGED, prods));
    }

    protected void fireConflictSetAssembled(Collection<IInstantiation> instances) {
        _eventDispatcher.fire(
                new ProceduralModuleEvent(this, ProceduralModuleEvent.Type.CONFLICT_SET_ASSEMBLED, instances));
        if (Logger.hasLoggers(getModel()))
            Logger.log(getModel(), Logger.Stream.PROCEDURAL, "Conflict Set " + instances);
    }

    protected IProduction addProductionInternal(IProduction production) {
        ISymbolicProduction symProd = production.getSymbolicProduction();

        /*
         * we need all the chunktypes that this production matches against this info
         * is used to accelerate conflict set assembly
         */
        Set<IChunkType> candidateChunkTypes = new HashSet<IChunkType>();
        /*
         * in some cases where no chunktype can be infered, we just snag the buffer
         * name
         */
        Set<String> bufferNames = new HashSet<String>();
        Set<String> ambiguousBufferNames = new HashSet<String>();
        for (ICondition condition : symProd.getConditions())
            if (condition instanceof ChunkTypeCondition) {
                ChunkTypeCondition ctc = (ChunkTypeCondition) condition;
                IChunkType chunkType = ctc.getChunkType();

                bufferNames.add(ctc.getBufferName());

                if (chunkType != null)
                    candidateChunkTypes.add(chunkType);
            } else if (condition instanceof ChunkCondition) {
                ChunkCondition cc = (ChunkCondition) condition;
                IChunkType chunkType = cc.getChunk().getSymbolicChunk().getChunkType();

                bufferNames.add(cc.getBufferName());

                if (chunkType != null)
                    candidateChunkTypes.add(chunkType);
            } else if (condition instanceof AbstractBufferCondition) {
                String bufferName = ((AbstractBufferCondition) condition).getBufferName();

                if (condition instanceof VariableCondition)
                    bufferNames.add(bufferName);

                /*
                 * this will catch all queries and variable conditions. These are
                 * production conditions from which we can't immediately determine the
                 * chunktype of the buffer contents
                 */
                ambiguousBufferNames.add(bufferName);
            }

        /*
         * ok, to support strict harvesting, which as far as I can tell is just a
         * stylistic implementation. I cannot see an architectural reason for it. It
         * just looks like a convenience for modelers so that they don't have to
         * manually invoke remove actions.. so, I zip through the buffers, for those
         * that have strict harvesting enabled, I check the actions of the
         * production. If it contains an action on that buffer, we leave it alone.
         * If not, we add a remove, thereby enforcing strict harvesting
         */
        Collection<IAction> actions = new ArrayList<IAction>(symProd.getActions());
        IModel model = getModel();
        for (String bufferName : bufferNames) {
            IActivationBuffer buffer = model.getActivationBuffer(bufferName);
            boolean actionFound = false;
            if (buffer.isStrictHarvestingEnabled()) {
                if (LOGGER.isDebugEnabled())
                    LOGGER.debug("Strict harvesting enabled for " + bufferName + ", checking actions of "
                            + symProd.getName());

                for (IAction action : actions)
                    if (action instanceof IBufferAction
                            && ((IBufferAction) action).getBufferName().equals(bufferName)) {
                        actionFound = true;
                        break;
                    }

                if (!actionFound) {
                    if (LOGGER.isDebugEnabled())
                        LOGGER.debug(bufferName + " requires strict harvest but " + symProd.getName()
                                + " doesn't operate on the buffer after the match. Adding a remove");

                    symProd.addAction(new RemoveAction(bufferName));
                }
            }
        }

        /*
         * figure out all the children of the chunktypes.. since any production that
         * could be fired for chunkTypeA could fire for chunkTypeB if chunkTypeB is
         * a child (derived from) of chunkTypeA
         */
        Set<IChunkType> chunkTypesToProcess = new HashSet<IChunkType>(candidateChunkTypes);
        for (IChunkType chunkType : candidateChunkTypes)
            chunkTypesToProcess.addAll(chunkType.getSymbolicChunkType().getChildren());

        _readWriteLock.writeLock().lock();

        /*
         * make sure the name is unique
         */
        String productionName = getSafeName(symProd.getName(), _allProductionsByName);
        symProd.setName(productionName);

        production.encode();

        /*
         * add it to the name map
         */
        _allProductionsByName.put(productionName.toLowerCase(), production);

        /*
         * add it to the chunktype maps
         */
        for (IChunkType chunkType : chunkTypesToProcess) {
            Collection<IProduction> productions = _allProductionsByChunkType.get(chunkType);
            if (productions == null) {
                productions = new ArrayList<IProduction>();
                _allProductionsByChunkType.put(chunkType, productions);
            }
            productions.add(production);
        }

        /*
         * now for the ambiguous conditions
         */
        for (String bufferName : ambiguousBufferNames) {
            Collection<IProduction> productions = _ambiguousProductions.get(bufferName);
            if (productions == null) {
                productions = new ArrayList<IProduction>();
                _ambiguousProductions.put(bufferName, productions);
            }
            productions.add(production);
        }

        _readWriteLock.writeLock().unlock();

        fireProductionAdded(production);

        return production;
    }

    public Future<IProduction> addProduction(final IProduction production) {
        Callable<IProduction> callable = new Callable<IProduction>() {

            public IProduction call() throws Exception {
                return addProductionInternal(production);
            }
        };
        return delayedFuture(callable, getExecutor());
    }

    protected IProduction removeProductionInternal(IProduction production) {
        ISymbolicProduction symProd = production.getSymbolicProduction();

        /*
         * we need all the chunktypes that this production matches against this info
         * is used to accelerate conflict set assembly
         */
        Set<IChunkType> candidateChunkTypes = new HashSet<IChunkType>();
        /*
         * in some cases where no chunktype can be infered, we just snag the buffer
         * name
         */
        Set<String> bufferNames = new HashSet<String>();
        Set<String> ambiguousBufferNames = new HashSet<String>();
        for (ICondition condition : symProd.getConditions())
            if (condition instanceof ChunkTypeCondition) {
                ChunkTypeCondition ctc = (ChunkTypeCondition) condition;
                IChunkType chunkType = ctc.getChunkType();

                bufferNames.add(ctc.getBufferName());

                if (chunkType != null)
                    candidateChunkTypes.add(chunkType);
            } else if (condition instanceof ChunkCondition) {
                ChunkCondition cc = (ChunkCondition) condition;
                IChunkType chunkType = cc.getChunk().getSymbolicChunk().getChunkType();

                bufferNames.add(cc.getBufferName());

                if (chunkType != null)
                    candidateChunkTypes.add(chunkType);
            } else if (condition instanceof AbstractBufferCondition) {
                String bufferName = ((AbstractBufferCondition) condition).getBufferName();

                if (condition instanceof VariableCondition)
                    bufferNames.add(bufferName);

                /*
                 * this will catch all queries and variable conditions. These are
                 * production conditions from which we can't immediately determine the
                 * chunktype of the buffer contents
                 */
                ambiguousBufferNames.add(bufferName);
            }

        /*
         * figure out all the children of the chunktypes.. since any production that
         * could be fired for chunkTypeA could fire for chunkTypeB if chunkTypeB is
         * a child (derived from) of chunkTypeA
         */
        Set<IChunkType> chunkTypesToProcess = new HashSet<IChunkType>(candidateChunkTypes);
        for (IChunkType chunkType : candidateChunkTypes)
            chunkTypesToProcess.addAll(chunkType.getSymbolicChunkType().getChildren());

        _readWriteLock.writeLock().lock();

        /*
         * make sure the name is unique
         */
        String productionName = symProd.getName();

        /*
         * add it to the name map
         */
        _allProductionsByName.remove(productionName.toLowerCase());

        /*
         * add it to the chunktype maps
         */
        for (IChunkType chunkType : chunkTypesToProcess) {
            Collection<IProduction> productions = _allProductionsByChunkType.get(chunkType);
            if (productions != null)
                productions.remove(production);
        }

        /*
         * now for the ambiguous conditions
         */
        for (String bufferName : ambiguousBufferNames) {
            Collection<IProduction> productions = _ambiguousProductions.get(bufferName);
            if (productions != null)
                productions.remove(production);
        }

        _readWriteLock.writeLock().unlock();

        return production;
    }

    public Future<IProduction> removeProduction(final IProduction production) {
        Callable<IProduction> callable = new Callable<IProduction>() {

            public IProduction call() throws Exception {
                return removeProductionInternal(production);
            }
        };
        return delayedFuture(callable, getExecutor());
    }

    protected IProduction createProductionInternal(String name) {
        IModel model = getModel();

        IProduction production = new DefaultProduction6(model);
        production.getSymbolicProduction().setName(getSafeName(name, _allProductionsByName));

        fireProductionCreated(production);
        return production;
    }

    public Future<IProduction> createProduction(final String name) {
        Callable<IProduction> callable = new Callable<IProduction>() {

            public IProduction call() throws Exception {
                return createProductionInternal(name);
            }
        };
        return delayedFuture(callable, getExecutor());
    }

    protected Collection<IInstantiation> getConflictSetInternal(Collection<IActivationBuffer> buffers) {
        IModel model = getModel();
        Set<IProduction> productions = new TreeSet<IProduction>(new ProductionNameComparator());

        /*
         * we iterate over the buffers, examining the source chunks and using their
         * types to assemble a set of candidate productions..
         */
        _readWriteLock.readLock().lock();
        for (IActivationBuffer buffer : buffers) {
            for (IChunk chunk : buffer.getSourceChunks()) {
                IChunkType chunkType = chunk.getSymbolicChunk().getChunkType();
                Collection<IProduction> possible = _allProductionsByChunkType.get(chunkType);
                if (possible != null) {
                    if (LOGGER.isDebugEnabled())
                        LOGGER.debug("chunktype " + chunkType + " in buffer " + buffer.getName() + " produced "
                                + possible);
                    productions.addAll(possible);
                }
            }

            // snag all the ambiguous productions
            Collection<IProduction> possible = _ambiguousProductions.get(buffer.getName().toLowerCase());
            if (possible != null) {
                if (LOGGER.isDebugEnabled())
                    LOGGER.debug("buffer " + buffer.getName() + " produced ambiguous productions " + possible);
                productions.addAll(possible);
            }
        }
        _readWriteLock.readLock().unlock();

        /*
         * we now have every production that could conceivably fire, and some extra
         * ones too now we must zip through them trying to instantiate
         */
        if (LOGGER.isDebugEnabled() || Logger.hasLoggers(model)) {
            StringBuilder sb = new StringBuilder("Considering ");
            sb.append(productions.size()).append(" productions for conflict set");
            String message = sb.toString();
            if (LOGGER.isDebugEnabled())
                LOGGER.debug(message);
            Logger.log(model, Logger.Stream.PROCEDURAL, message);
        }

        SortedSet<IInstantiation> keepers = createAndSortInstantiations(productions);

        if (LOGGER.isDebugEnabled())
            LOGGER.debug("Final conflict set " + keepers);

        fireConflictSetAssembled(keepers);
        return keepers;
    }

    /**
     * iterates over productions, attempting to instantiate each. Those that can
     * be instantiated will be sorted by utility and returned.
     * 
     * @param productions
     * @return
     */
    protected SortedSet<IInstantiation> createAndSortInstantiations(Collection<IProduction> productions) {
        IModel model = getModel();
        SortedSet<IInstantiation> keepers = new TreeSet<IInstantiation>(_comparator);

        /**
         * provisional bindings maps the buffer name variables to all the possible
         * chunks given the buffer names provided. one problem with using these
         * provisional bindings is that they will have references to other buffers
         * even if the production doesnt touch them..
         */
        Collection<Map<String, Object>> provisionalBindings = computeProvisionalBindings();

        StringBuilder message = new StringBuilder();

        for (IProduction production : productions)
            /*
             * only consider those with sufficient utility
             */
            // double tmpGain =
            // production.getSubsymbolicProduction().getExpectedGain();
            // if (tmpGain >= _expectedGainThreshold)
            try {
                if (LOGGER.isDebugEnabled())
                    LOGGER.debug("Instantiating " + production);
                Collection<IInstantiation> instantiations = _instaniator.instantiate(production,
                        provisionalBindings);

                for (IInstantiation instantiation : instantiations) {
                    double noise = _randomModule.logisticNoise(getExpectedUtilityNoise());
                    ISubsymbolicProduction6 p = (ISubsymbolicProduction6) instantiation.getSubsymbolicProduction();
                    double utility = p.getExpectedUtility();

                    if (Double.isNaN(utility))
                        utility = p.getUtility();

                    if (LOGGER.isDebugEnabled())
                        LOGGER.debug(production + " utility: " + utility + " noise:" + noise + " expected utility: "
                                + (utility + noise));

                    p.setExpectedUtility(utility + noise);

                    if (LOGGER.isDebugEnabled() || Logger.hasLoggers(model)) {
                        message.delete(0, message.length());
                        message.append("Instantiated ").append(production).append(" expected utility ");
                        message.append(utility + noise).append(" (").append(noise).append(" noise)");

                        String msg = message.toString();
                        if (LOGGER.isDebugEnabled())
                            LOGGER.debug(msg);
                        if (Logger.hasLoggers(model))
                            Logger.log(model, Logger.Stream.PROCEDURAL, msg);
                    }

                    keepers.add(instantiation);
                }
            } catch (CannotInstantiateException cie) {

                if (LOGGER.isDebugEnabled() || Logger.hasLoggers(model)) {
                    StringBuilder sb = new StringBuilder("Could not instantiate ");
                    sb.append(production).append(" : ").append(cie.getMessage());
                    String msg = sb.toString();
                    LOGGER.debug(msg);
                    Logger.log(model, Logger.Stream.PROCEDURAL, msg);
                }
            }
        // else if (LOGGER.isDebugEnabled() || Logger.hasLoggers(model))
        // {
        //
        // StringBuilder sb = new StringBuilder("Ignoring ");
        // sb.append(production).append(" since its expected gain ").append(
        // tmpGain);
        // sb.append(" is less than threshold ").append(_expectedGainThreshold);
        // String message = sb.toString();
        // if (LOGGER.isDebugEnabled()) LOGGER.debug(message);
        // Logger.log(model, Logger.Stream.PROCEDURAL, message);
        // }
        return keepers;
    }

    public Future<Collection<IInstantiation>> getConflictSet(final Collection<IActivationBuffer> buffers) {
        Callable<Collection<IInstantiation>> callable = new Callable<Collection<IInstantiation>>() {

            public Collection<IInstantiation> call() throws Exception {
                return getConflictSetInternal(buffers);
            }

        };

        return delayedFuture(callable, getExecutor());
    }

    protected IProduction getProductionInternal(String name) {
        _readWriteLock.readLock().lock();
        IProduction rtn = _allProductionsByName.get(name.toLowerCase());
        _readWriteLock.readLock().unlock();
        return rtn;
    }

    public Future<IProduction> getProduction(final String name) {
        Callable<IProduction> callable = new Callable<IProduction>() {

            public IProduction call() {
                return getProductionInternal(name);
            }
        };
        return delayedFuture(callable, getExecutor());
    }

    protected IInstantiation selectInstantiationInternal(Collection<IInstantiation> instantiations) {
        return _selector.select(instantiations);
    }

    public Future<IInstantiation> selectInstantiation(final Collection<IInstantiation> instantiations) {
        Callable<IInstantiation> callable = new Callable<IInstantiation>() {

            public IInstantiation call() throws Exception {
                return selectInstantiationInternal(instantiations);
            }

        };

        return delayedFuture(callable, getExecutor());
    }

    protected Double fireProductionInternal(IInstantiation instantiation, double firingTime) {
        try {
            IModel model = getModel();

            fireProductionWillFire(instantiation);

            if (LOGGER.isDebugEnabled() || Logger.hasLoggers(model)) {
                if (LOGGER.isDebugEnabled())
                    LOGGER.debug("Firing " + instantiation);
                Logger.log(model, Logger.Stream.PROCEDURAL, "Firing " + instantiation);
            }

            instantiation.fire(firingTime);

            fireProductionFired(instantiation);

            setNumberOfProductionsFired(_productionsFired + 1);
        } catch (ModelTerminatedException mte) {
            if (LOGGER.isDebugEnabled())
                LOGGER.debug("Model has terminated naturally");
            return Double.NaN;
        }
        return instantiation.getActionLatency();
    }

    public Future<Double> fireProduction(final IInstantiation instantiation, final double firingTime) {
        Callable<Double> callable = new Callable<Double>() {
            public Double call() {
                return fireProductionInternal(instantiation, firingTime);
            }
        };

        return delayedFuture(callable, getExecutor());
    }

    public double getDefaultProductionFiringTime() {
        return _productionFiringTime;
    }

    public void setDefaultProductionFiringTime(double firingTime) {
        double old = _productionFiringTime;
        _productionFiringTime = firingTime;
        if (_eventDispatcher.hasListeners())
            _eventDispatcher.fire(new ProceduralModuleEvent(this, DEFAULT_PRODUCTION_FIRING_TIME, old, firingTime));
    }

    @Override
    public void initialize() {
        // noop
        /*
         * snag the random module
         */
        _randomModule = (IRandomModule) getModel().getModule(IRandomModule.class);
        if (_randomModule == null)
            _randomModule = DefaultRandomModule.getInstance();

        /**
         * attach a little something to the buffers so we know when to clear
         * provisional bindings
         */
        for (IActivationBuffer buffer : getModel().getActivationBuffers())
            buffer.addListener(new IActivationBufferListener() {

                public void chunkMatched(ActivationBufferEvent abe) {
                    // noop

                }

                public void requestAccepted(ActivationBufferEvent abe) {
                    // noop

                }

                public void sourceChunkAdded(ActivationBufferEvent abe) {
                    _provisionalBindings = null;
                }

                public void sourceChunkRemoved(ActivationBufferEvent abe) {
                    _provisionalBindings = null;

                }

                public void sourceChunksCleared(ActivationBufferEvent abe) {
                    _provisionalBindings = null;

                }

                public void statusSlotChanged(ActivationBufferEvent abe) {
                    // TODO Auto-generated method stub

                }

                public void parameterChanged(IParameterEvent pe) {
                    // TODO Auto-generated method stub

                }

            }, null); // inline
    }

    public double getExpectedUtilityNoise() {
        return _expectedUtilityNoise;
    }

    public long getNumberOfProductionsFired() {
        return _productionsFired;
    }

    public void setExpectedUtilityNoise(double noise) {
        double old = _expectedUtilityNoise;
        _expectedUtilityNoise = noise;
        if (_eventDispatcher.hasListeners())
            _eventDispatcher.fire(new ProceduralModuleEvent(this, EXPECTED_UTILITY_NOISE, old, noise));
    }

    public void setNumberOfProductionsFired(long fired) {
        long old = _productionsFired;
        _productionsFired = fired;
        if (_eventDispatcher.hasListeners())
            _eventDispatcher.fire(new ProceduralModuleEvent(this, NUMBER_OF_PRODUCTIONS_FIRED, old, fired));
    }

    protected Collection<IProduction> getProductionsInternal() {
        try {
            _readWriteLock.readLock().lock();
            return new ArrayList<IProduction>(_allProductionsByName.values());
        } finally {
            _readWriteLock.readLock().unlock();
        }
    }

    public Future<Collection<IProduction>> getProductions() {
        return delayedFuture(new Callable<Collection<IProduction>>() {

            public Collection<IProduction> call() throws Exception {
                return getProductionsInternal();
            }

        }, getExecutor());
    }

    /**
     * @see org.jactr.core.utils.parameter.IParameterized#getParameter(java.lang.String)
     */
    public String getParameter(String key) {
        if (NUMBER_OF_PRODUCTIONS_FIRED.equalsIgnoreCase(key))
            return "" + getNumberOfProductionsFired();
        if (EXPECTED_UTILITY_NOISE.equalsIgnoreCase(key))
            return "" + getExpectedUtilityNoise();
        if (DEFAULT_PRODUCTION_FIRING_TIME.equalsIgnoreCase(key))
            return "" + getDefaultProductionFiringTime();
        return null;
    }

    /**
     * @see org.jactr.core.utils.parameter.IParameterized#getPossibleParameters()
     */
    public Collection<String> getPossibleParameters() {
        return getSetableParameters();
    }

    /**
     * @see org.jactr.core.utils.parameter.IParameterized#getSetableParameters()
     */
    public Collection<String> getSetableParameters() {
        ArrayList<String> rtn = new ArrayList<String>();
        rtn.add(EXPECTED_UTILITY_NOISE);
        rtn.add(DEFAULT_PRODUCTION_FIRING_TIME);
        rtn.add(NUMBER_OF_PRODUCTIONS_FIRED);
        return rtn;
    }

    /**
     * @see org.jactr.core.utils.parameter.IParameterized#setParameter(java.lang.String,
     *      java.lang.String)
     */
    public void setParameter(String key, String value) {
        if (NUMBER_OF_PRODUCTIONS_FIRED.equalsIgnoreCase(key))
            setNumberOfProductionsFired(ParameterHandler.numberInstance().coerce(value).longValue());
        else if (EXPECTED_UTILITY_NOISE.equalsIgnoreCase(key))
            setExpectedUtilityNoise(ParameterHandler.numberInstance().coerce(value).doubleValue());
        else if (DEFAULT_PRODUCTION_FIRING_TIME.equalsIgnoreCase(key))
            setDefaultProductionFiringTime(ParameterHandler.numberInstance().coerce(value).doubleValue());
        else if (LOGGER.isWarnEnabled())
            LOGGER.warn("No clue how to set " + key + " to " + value);

    }

    /**
     * in order to handle the iterative nature of the instantiation process in
     * addition to the possibility for multiple sources chunks, provisional
     * bindings must be created for all the chunk permutations. Ugh.
     */
    private Collection<Map<String, Object>> computeProvisionalBindings() {
        // if (_provisionalBindings != null && _provisionalBindings.size() != 0)
        // return _provisionalBindings;
        //
        // if (_provisionalBindings == null)
        _provisionalBindings = new ArrayList<Map<String, Object>>();

        IModel model = getModel();
        Collection<Map<String, Object>> provisionalBindings = new ArrayList<Map<String, Object>>();

        Map<String, Object> initialBinding = new TreeMap<String, Object>();
        initialBinding.put("=model", model);
        provisionalBindings.add(initialBinding);

        /*
         * with all the buffers this production should match against, we snag their
         * sources
         */
        for (IActivationBuffer buffer : model.getActivationBuffers()) {
            Collection<IChunk> sourceChunks = buffer.getSourceChunks();

            if (sourceChunks.size() == 0)
                continue;

            Map<IChunk, Collection<Map<String, Object>>> keyedProvisionalBindings = new HashMap<IChunk, Collection<Map<String, Object>>>();

            /*
             * if there are more than one source chunk, we need to duplicate all the
             * provisional bindings, add the binding for the source chunk and then
             * merge the duplicates back into the provisional set
             */
            for (IChunk source : sourceChunks) {
                Collection<Map<String, Object>> bindings = provisionalBindings;
                // more than one, duplicate.
                if (keyedProvisionalBindings.size() != 0)
                    bindings = copyBindings(provisionalBindings);

                // add binding to all bindings
                for (Map<String, Object> binding : bindings)
                    binding.put("=" + buffer.getName(), source);

                // store
                keyedProvisionalBindings.put(source, bindings);
            }

            /*
             * merge these provisionals back into the full set. if there was only one
             * source chunk, it was already added to the provisional binding, so we
             * ignore it. If multi, we add all
             */
            for (Collection<Map<String, Object>> bindings : keyedProvisionalBindings.values())
                if (bindings != provisionalBindings)
                    provisionalBindings.addAll(bindings);
        }

        _provisionalBindings.addAll(provisionalBindings);

        return _provisionalBindings;
    }

    private Collection<Map<String, Object>> copyBindings(Collection<Map<String, Object>> src) {
        Collection<Map<String, Object>> rtn = new ArrayList<Map<String, Object>>(src.size());
        for (Map<String, Object> map : src)
            rtn.add(new TreeMap<String, Object>(map));
        return rtn;
    }

    public void reset() {
        // noop
    }
}