/** * Copyright (C) 2001-3, Anthony Harrison anh23@pitt.edu 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.declarative.four.learning; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jactr.core.chunk.IChunk; import org.jactr.core.chunk.ISubsymbolicChunk; import org.jactr.core.chunk.basic.AbstractSubsymbolicChunk; import org.jactr.core.logging.Logger; import org.jactr.core.logging.Logger.Stream; import org.jactr.core.model.IModel; import org.jactr.core.module.declarative.four.IBaseLevelActivationEquation; import org.jactr.core.module.declarative.four.IDeclarativeModule4; import org.jactr.core.module.procedural.IProceduralModule; import org.jactr.core.utils.references.IReferences; /** * default impl of the base level activation equation as defined in atomic * components of thought * * @author harrison * @created April 18, 2003 */ public class DefaultBaseLevelActivationEquation implements IBaseLevelActivationEquation { private static transient Log LOGGER = LogFactory .getLog(DefaultBaseLevelActivationEquation.class .getName()); IDeclarativeLearningModule4 _declarativeLearningModule; IProceduralModule _proceduralModule; IDeclarativeModule4 _declarativeModule; private IModel _model; ThreadLocal<double[]> _doubleArray = new ThreadLocal<double[]>(); ThreadLocal<StringBuilder> _stringBuilder = new ThreadLocal<StringBuilder>(); public DefaultBaseLevelActivationEquation(IModel model) { _model = model; } private IProceduralModule getProceduralModule() { if (_proceduralModule == null) { _proceduralModule = _model.getProceduralModule(); if (_proceduralModule == null) throw new RuntimeException(this.getClass().getSimpleName() + " depends upon IProceduralModule"); } return _proceduralModule; } private IDeclarativeModule4 getDeclarativeModule() { if (_declarativeModule == null) { _declarativeModule = (IDeclarativeModule4) _model.getDeclarativeModule(); if (_declarativeModule == null) throw new RuntimeException(this.getClass().getSimpleName() + " depends upon IDeclarativeModule4"); } return _declarativeModule; } private IDeclarativeLearningModule4 getDeclarativeLearningModule() { if (_declarativeLearningModule == null) { _declarativeLearningModule = (IDeclarativeLearningModule4) _model .getModule(IDeclarativeLearningModule4.class); if (_declarativeLearningModule == null) throw new RuntimeException(this.getClass().getSimpleName() + " depends upon IDeclarativeLearningModule4"); } return _declarativeLearningModule; } /** * return the thread local cached string builder so we don't keep * instantiating * * @param initialize * @return */ private StringBuilder getStringBuilder(String initialize) { StringBuilder sb = _stringBuilder.get(); if (sb == null) { sb = new StringBuilder(initialize); _stringBuilder.set(sb); } else { sb.delete(0, sb.length()); sb.append(initialize); } return sb; } /** * Description of the Method * * @param model * Description of the Parameter * @param c * Description of the Parameter * @return Description of the Return Value */ public double computeBaseLevelActivation(IModel model, IChunk c) { double now = model.getAge(); IDeclarativeModule4 declarativeModule = getDeclarativeModule(); IDeclarativeLearningModule4 declarativeLearningModule = getDeclarativeLearningModule(); ISubsymbolicChunk ssc = c.getSubsymbolicChunk(); double baseLevelActivation = 0; if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Computing activation for %s @ %.2f", c, now)); if (declarativeLearningModule.isBaseLevelLearningEnabled()) { if (LOGGER.isDebugEnabled()) LOGGER.debug("base level learning is enabled, calculating"); /* * it is safer to use model.getAge() since it is less volatile if you are * using a realtime clock. getAge() only changes once per-cycle where the * actual time can change every call. */ double minusD = -1.0 * declarativeLearningModule.getBaseLevelLearning(); double defAct = getProceduralModule().getDefaultProductionFiringTime(); double base = 0.0; IReferences referenceList = ssc.getReferences(); long numberOfTotalTimeSamples = referenceList.getNumberOfReferences(); int numberOfActualTimeSamples = referenceList .getNumberOfRecentReferences(); StringBuilder logMsg = null; if (Logger.hasLoggers(model)) logMsg = getStringBuilder(String.format(" refCount %d relativeRefs ", numberOfTotalTimeSamples)); if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("References %s", referenceList)); /* * we recycle the double[] for efficiency purposes */ double[] times = referenceList.getRelativeTimes(now, _doubleArray.get()); _doubleArray.set(times); // save it for later /* * exact portion. We cannot iterate blindly since the recycled container * may be larger than the actual number of samples. This is WRONG. */ for (int i = 0; i < numberOfActualTimeSamples; i++) { double element = times[i]; if (logMsg != null) logMsg.append(String.format("%.2f ", element)); /* * a merge (which increments reference count), followed by the logging * of the merge (outputing activation) can result in a 0 relative time. * This produces a crazy artificial spike in activation. We skip it * entirely. */ // if (Math.abs(element) <= 0.0001) continue; double tMinusD = Math.pow(Math.max(defAct, element), minusD); base += tMinusD; if (LOGGER.isDebugEnabled()) LOGGER.debug("tMinusD for " + element + " = " + tMinusD + " base = " + base); } // paranoid worst case scenario if (numberOfActualTimeSamples == 0) base = Math.pow(defAct, minusD); baseLevelActivation = base; if (LOGGER.isDebugEnabled()) LOGGER.debug("Exact-BaseLevelActivation " + baseLevelActivation + " with " + numberOfActualTimeSamples + " accesses"); /* * now for the approximate portion */ /** * <code> * (1-d) (1-d) * (n-k) * (tn^ - tk ) * -------------------------- * (1-d) * (tn-tk) * </code> */ if (numberOfTotalTimeSamples > numberOfActualTimeSamples) { // there are more references than we have times for. approx portion double oneMinusD = 1 + minusD; // # of approx times double numerator = numberOfTotalTimeSamples - numberOfActualTimeSamples; double tn = now - ssc.getCreationTime(); double tk = now - referenceList.getLastReferenceTime(); numerator *= Math.pow(tn, oneMinusD) - Math.pow(tk, oneMinusD); double denom = oneMinusD * (tn - tk); base = numerator / denom; if (LOGGER.isDebugEnabled()) LOGGER .debug(String .format( "Aprox-BaseLevelAct %.4f, numerator=%.4f denom=%.4f, tn=%.2f tk=%.2f", base, numerator, denom, tn, tk)); // add the exact and the approximate portions if (!Double.isNaN(base) && !Double.isInfinite(base)) baseLevelActivation += base; } baseLevelActivation = Math.log(baseLevelActivation); baseLevelActivation += declarativeModule.getBaseLevelConstant(); if (LOGGER.isDebugEnabled()) LOGGER.debug("Adding constant " + _declarativeModule.getBaseLevelConstant() + " BaseLevelActivation " + baseLevelActivation); if (logMsg != null) { logMsg.insert(0, String.format("%s.base = %.2f", c, baseLevelActivation)); Logger.log(model, Stream.ACTIVATION, logMsg.toString()); } } else { baseLevelActivation = ssc.getBaseLevelActivation(); if (Double.isNaN(baseLevelActivation)) baseLevelActivation = declarativeModule.getBaseLevelConstant(); if (LOGGER.isDebugEnabled()) LOGGER.debug("forcing baselevel activation to " + baseLevelActivation); if (Logger.hasLoggers(model)) Logger.log(model, Stream.ACTIVATION, String.format("%s.base = %.2f", c, baseLevelActivation)); } return baseLevelActivation; } @Override public String getName() { return "base4"; } @Override public double computeAndSetActivation(IChunk chunk, IModel model) { double base = computeBaseLevelActivation(model, chunk); ((AbstractSubsymbolicChunk) chunk.getSubsymbolicChunk()) .setBaseLevelActivation(base); return base; } }