/* * Created on Oct 25, 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.chunk.basic; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.locks.Lock; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jactr.core.buffer.IActivationBuffer; import org.jactr.core.chunk.IChunk; import org.jactr.core.chunk.ISubsymbolicChunk; import org.jactr.core.chunk.IllegalChunkStateException; import org.jactr.core.chunk.event.ChunkEvent; import org.jactr.core.event.ParameterEvent; import org.jactr.core.model.IModel; import org.jactr.core.runtime.ACTRRuntime; import org.jactr.core.utils.DefaultAdaptable; import org.jactr.core.utils.parameter.CollectionParameterHandler; import org.jactr.core.utils.parameter.ParameterHandler; import org.jactr.core.utils.parameter.ParameterHelper; import org.jactr.core.utils.references.IReferences; public abstract class AbstractSubsymbolicChunk extends DefaultAdaptable implements ISubsymbolicChunk { /** * logger definition */ static private final Log LOGGER = LogFactory .getLog(AbstractSubsymbolicChunk.class); protected IChunk _parentChunk; protected IReferences _referenceList; protected double _creationTime; // encoding // time protected double _baseLevelActivation = Double.NaN; protected double _spreadingActivation; protected double _randomActivation; protected Map<IActivationBuffer, Double> _sourceActivation; protected double _totalActivation; protected double _timesInContext; protected double _timesNeeded; protected double _lastActivationComputationTime = -1; private Collection<IActivationParticipant> _activationParticipants = new ArrayList<IActivationParticipant>(); protected Map<String, String> _unknownParameters; protected ParameterHelper _parameterHelper = new ParameterHelper(); static private boolean _setSourceWarned = false; public AbstractSubsymbolicChunk() { // factory? really. this should be changed _referenceList = IReferences.Factory.get().newInstance(); _unknownParameters = new TreeMap<String, String>(); initializeParameters(); } public void bind(IChunk wrapper) { _parentChunk = wrapper; } protected Lock readLock() { return _parentChunk.getReadLock(); } protected Lock writeLock() { return _parentChunk.getWriteLock(); } public void accessed(double time) { Lock l = writeLock(); try { l.lock(); _referenceList.addReferenceTime(time); } finally { l.unlock(); } if (_parentChunk.hasListeners()) _parentChunk.dispatch(new ChunkEvent(_parentChunk, ChunkEvent.Type.ACCESSED)); } public void dispose() { _referenceList.clear(); } /** * non-locking since this will set creation time * * @see org.jactr.core.chunk.ISubsymbolicChunk#encode(double) */ public void encode(double when) { if (_parentChunk.isEncoded()) throw new IllegalChunkStateException("Chunk has already been encoded"); setCreationTime(when); } public void addActivationParticipant(IActivationParticipant participant) { _activationParticipants.add(participant); } public void removeActivationParticipant(IActivationParticipant participant) { _activationParticipants.remove(participant); } public Collection<IActivationParticipant> getActivationParticipants( Collection<IActivationParticipant> container) { if (container == null) container = new ArrayList<IActivationParticipant>(); container.addAll(_activationParticipants); return container; } public double getActivation() { Lock l = readLock(); refreshActivationValues(); try { l.lock(); return _totalActivation; } finally { l.unlock(); } } public double getBaseLevelActivation() { Lock l = readLock(); refreshActivationValues(); try { l.lock(); return _baseLevelActivation; } finally { l.unlock(); } } public double getRandomActivation() { Lock l = readLock(); refreshActivationValues(); try { l.lock(); return _randomActivation; } finally { l.unlock(); } } public double getCreationTime() { Lock l = readLock(); try { l.lock(); return _creationTime; } finally { l.unlock(); } } public IReferences getReferences() { return _referenceList; } public double getSourceActivation() { Lock l = readLock(); try { l.lock(); if (_sourceActivation == null) return 0; double source = 0; for (Double act : _sourceActivation.values()) source += act; return source; } finally { l.unlock(); } } public double getSourceActivation(IActivationBuffer buffer) { Lock l = readLock(); try { l.lock(); if (_sourceActivation == null) return 0; if (_sourceActivation.containsKey(buffer)) return _sourceActivation.get(buffer); else return 0; } finally { l.unlock(); } } public double getSpreadingActivation() { Lock l = readLock(); refreshActivationValues(); try { l.lock(); return _spreadingActivation; } finally { l.unlock(); } } public double getTimesInContext() { Lock l = readLock(); try { l.lock(); return _timesInContext; } finally { l.unlock(); } } public double getTimesNeeded() { Lock l = readLock(); try { l.lock(); return _timesNeeded; } finally { l.unlock(); } } public void incrementTimesInContext(double value) { setTimesInContext(getTimesInContext() + value); } public void incrementTimesNeeded(double value) { setTimesNeeded(getTimesNeeded() + value); } public void setActivation(double act) { double old = 0; Lock l = writeLock(); try { l.lock(); old = _totalActivation; _totalActivation = act; } finally { l.unlock(); } if (_parentChunk.hasParameterListeners()) _parentChunk.dispatch(new ParameterEvent(this, ACTRRuntime.getRuntime() .getClock(_parentChunk.getModel()).getTime(), ACTIVATION, old, act)); } public void setBaseLevelActivation(double base) { double old = 0; Lock l = writeLock(); try { l.lock(); old = _baseLevelActivation; _baseLevelActivation = base; } finally { l.unlock(); } if (_parentChunk.hasParameterListeners()) _parentChunk.dispatch(new ParameterEvent(this, ACTRRuntime.getRuntime() .getClock(_parentChunk.getModel()).getTime(), BASE_LEVEL_ACTIVATION, old, base)); } public void setRandomActivation(double random) { Lock l = writeLock(); try { l.lock(); _randomActivation = random; } finally { l.unlock(); } } public void setCreationTime(double time) { // if (_parentChunk.isEncoded()) // throw new IllegalChunkStateException( // "Creation time cannot be changed after encoding"); Lock l = writeLock(); double old = 0; try { l.lock(); old = _creationTime; _creationTime = time; } finally { l.unlock(); } if (_parentChunk.hasParameterListeners()) _parentChunk.dispatch(new ParameterEvent(this, ACTRRuntime.getRuntime() .getClock(_parentChunk.getModel()).getTime(), CREATION_TIME, old, time)); } public void setSourceActivation(IActivationBuffer sourceBuffer, double source) { double old = getSourceActivation(); if (sourceBuffer == null) return; // noop Lock l = writeLock(); try { l.lock(); // this is a removal, we should see if we can dispose of the map. if (source == 0 && _sourceActivation != null) { _sourceActivation.remove(sourceBuffer); if (_sourceActivation.size() == 0) _sourceActivation = null; } else if (source != 0) { if (_sourceActivation == null) _sourceActivation = new HashMap<IActivationBuffer, Double>(); _sourceActivation.put(sourceBuffer, source); } } finally { l.unlock(); } if (_parentChunk.hasParameterListeners()) _parentChunk.dispatch(new ParameterEvent(this, ACTRRuntime.getRuntime() .getClock(_parentChunk.getModel()).getTime(), SOURCE_ACTIVATION, old, source)); } public void setSpreadingActivation(double spread) { double old = 0; Lock l = writeLock(); try { l.lock(); old = _spreadingActivation; _spreadingActivation = spread; } finally { l.unlock(); } if (_parentChunk.hasParameterListeners()) _parentChunk.dispatch(new ParameterEvent(this, ACTRRuntime.getRuntime() .getClock(_parentChunk.getModel()).getTime(), SPREADING_ACTIVATION, old, spread)); } public void setTimesInContext(double context) { double old = 0; Lock l = writeLock(); try { l.lock(); old = _timesInContext; _timesInContext = context; } finally { l.unlock(); } if (_parentChunk.hasParameterListeners()) _parentChunk.dispatch(new ParameterEvent(this, ACTRRuntime.getRuntime() .getClock(_parentChunk.getModel()).getTime(), TIMES_IN_CONTEXT, old, context)); } public void setTimesNeeded(double needed) { Lock l = writeLock(); double old = 0; try { l.lock(); old = _timesNeeded; _timesNeeded = needed; } finally { l.unlock(); } if (_parentChunk.hasParameterListeners()) _parentChunk.dispatch(new ParameterEvent(this, ACTRRuntime.getRuntime() .getClock(_parentChunk.getModel()).getTime(), TIMES_NEEDED, old, needed)); } protected void initializeParameters() { // parameter help hooks. // _parameterHelper.addProcessor(null); } public String getParameter(String key) { String rtn = null; //short-term fix until everything is migrated to ParameterProcessor/Helper if (CREATION_TIME.equalsIgnoreCase(key)) rtn = ParameterHandler.numberInstance().toString(getCreationTime()); else if (TIMES_NEEDED.equalsIgnoreCase(key)) rtn = ParameterHandler.numberInstance().toString(getTimesNeeded()); else if (TIMES_IN_CONTEXT.equalsIgnoreCase(key)) rtn = ParameterHandler.numberInstance().toString(getTimesInContext()); else if (REFERENCE_COUNT.equalsIgnoreCase(key)) rtn = ParameterHandler.numberInstance().toString( getReferences().getNumberOfReferences()); else if (REFERENCE_TIMES.equalsIgnoreCase(key)) { double[] times = getReferences().getTimes(null); // make sure they are sorted Arrays.sort(times); Collection<Number> nTimes = new ArrayList<Number>(times.length); for (double time : times) nTimes.add(time); // make sure they are sorted CollectionParameterHandler<Number> aph = new CollectionParameterHandler<Number>( ParameterHandler.numberInstance()); return aph.toString(nTimes); } else if (BASE_LEVEL_ACTIVATION.equalsIgnoreCase(key)) rtn = ParameterHandler.numberInstance() .toString(getBaseLevelActivation()); else if (SPREADING_ACTIVATION.equalsIgnoreCase(key)) rtn = ParameterHandler.numberInstance() .toString(getSpreadingActivation()); else if (SOURCE_ACTIVATION.equalsIgnoreCase(key)) rtn = ParameterHandler.numberInstance().toString(getSourceActivation()); else if (ACTIVATION.equalsIgnoreCase(key)) rtn = ParameterHandler.numberInstance().toString(getActivation()); else rtn = _unknownParameters.get(key); return rtn; } public Collection<String> getPossibleParameters() { Collection<String> setable = getSetableParameters(); setable.add(SOURCE_ACTIVATION); // setable.add(SPREADING_ACTIVATION); return setable; } public Collection<String> getSetableParameters() { ArrayList<String> params = new ArrayList<String>(); params.addAll(_unknownParameters.keySet()); params.add(CREATION_TIME); params.add(TIMES_NEEDED); params.add(TIMES_IN_CONTEXT); params.add(REFERENCE_COUNT); params.add(REFERENCE_TIMES); params.add(BASE_LEVEL_ACTIVATION); params.add(SPREADING_ACTIVATION); // params.add(SOURCE_ACTIVATION); params.add(ACTIVATION); return params; } public void setParameter(String key, String value) { if (LOGGER.isDebugEnabled()) LOGGER.debug("Attempting to set " + key + " to " + value); if (CREATION_TIME.equalsIgnoreCase(key)) setCreationTime(ParameterHandler.numberInstance().coerce(value) .doubleValue()); else if (TIMES_NEEDED.equalsIgnoreCase(key)) setTimesNeeded(ParameterHandler.numberInstance().coerce(value) .doubleValue()); else if (TIMES_IN_CONTEXT.equalsIgnoreCase(key)) setTimesInContext(ParameterHandler.numberInstance().coerce(value) .doubleValue()); else if (REFERENCE_COUNT.equalsIgnoreCase(key)) { long referenceCount = ParameterHandler.numberInstance().coerce(value) .longValue(); IReferences references = getReferences(); double[] oldTimes = references.getTimes(null); long oldCount = references.getNumberOfReferences(); references.clear(); /* * create referenceCount references from creation time to now */ double min = getCreationTime(); double step = (getParentChunk().getModel().getAge() - min) / referenceCount; for (int i = 0; i < referenceCount; i++) references.addReferenceTime(getCreationTime() + i * step); _lastActivationComputationTime = -1; if (_parentChunk.hasParameterListeners()) { _parentChunk.dispatch(new ParameterEvent(this, ACTRRuntime.getRuntime() .getClock(_parentChunk.getModel()).getTime(), REFERENCE_COUNT, oldCount, referenceCount)); _parentChunk.dispatch(new ParameterEvent(this, ACTRRuntime.getRuntime() .getClock(_parentChunk.getModel()).getTime(), REFERENCE_TIMES, oldTimes, references.getTimes(null))); } } else if (REFERENCE_TIMES.equalsIgnoreCase(key)) { if (LOGGER.isDebugEnabled()) LOGGER.debug("Attempting to set reference times with " + value); CollectionParameterHandler<Number> cph = new CollectionParameterHandler<Number>( ParameterHandler.numberInstance()); Collection<Number> times = cph.coerce(value); // let's make sure they are sorted.. TreeSet<Double> refTimes = new TreeSet<Double>(); for (Number time : times) refTimes.add(time.doubleValue()); IReferences references = getReferences(); double[] oldTimes = references.getTimes(null); /* * if count was previously set, we need to maintain it.. */ references.setNumberOfReferences(Math.max(0, references.getNumberOfReferences() - refTimes.size())); /* * now we'll add these times */ for (Double time : refTimes) references.addReferenceTime(time); _lastActivationComputationTime = -1; if (_parentChunk.hasParameterListeners()) _parentChunk.dispatch(new ParameterEvent(this, ACTRRuntime.getRuntime() .getClock(_parentChunk.getModel()).getTime(), REFERENCE_TIMES, oldTimes, references.getTimes(null))); } else if (BASE_LEVEL_ACTIVATION.equalsIgnoreCase(key)) setBaseLevelActivation(ParameterHandler.numberInstance().coerce(value) .doubleValue()); else if (SPREADING_ACTIVATION.equalsIgnoreCase(key)) setSpreadingActivation(ParameterHandler.numberInstance().coerce(value) .doubleValue()); else if (SOURCE_ACTIVATION.equalsIgnoreCase(key)) { if (!_setSourceWarned) { _setSourceWarned = true; if (LOGGER.isWarnEnabled()) LOGGER .warn(String .format("Setting source activation directly via setParameter no longer supported. ")); } setSourceActivation(null, ParameterHandler.numberInstance().coerce(value) .doubleValue()); } else if (ACTIVATION.equalsIgnoreCase(key)) setActivation(ParameterHandler.numberInstance().coerce(value) .doubleValue()); else { /* * an unknown parameter. We store and fire an event. This allows others to * add parameters with no hassle. */ String oldValue = _unknownParameters.put(key, value); if (_parentChunk.hasParameterListeners()) _parentChunk .dispatch(new ParameterEvent(this, ACTRRuntime.getRuntime() .getClock(_parentChunk.getModel()).getTime(), key, oldValue, value)); } } protected void refreshActivationValues() { double now = getParentChunk().getModel().getAge(); if (now > _lastActivationComputationTime) { _lastActivationComputationTime = now; calculateValues(); } } protected void calculateValues() { IModel model = getParentChunk().getModel(); IChunk self = getParentChunk(); double total = 0; for (IActivationParticipant actCalc : _activationParticipants) { double act = actCalc.computeAndSetActivation(self, model); total += Double.isNaN(act) || Double.isInfinite(act) ? 0 : act; if (LOGGER.isDebugEnabled()) LOGGER .debug(String.format("%s %s = %.2f", self, actCalc.getName(), act)); } setActivation(total); } // abstract protected double computeBaseLevelActivation(); /** * return the spreading activation value * * @return */ // abstract protected double computeSpreadingActivation(); // abstract protected double computeRandomActivation(); public IChunk getParentChunk() { return _parentChunk; } }