/* * 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.module.declarative.four.associative; import javolution.util.FastList; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jactr.core.chunk.IChunk; import org.jactr.core.chunk.event.ChunkEvent; import org.jactr.core.chunk.event.ChunkListenerAdaptor; import org.jactr.core.chunk.four.ISubsymbolicChunk4; import org.jactr.core.chunk.four.Link4; import org.jactr.core.chunk.link.IAssociativeLink; import org.jactr.core.module.declarative.associative.IAssociativeLinkContainer; import org.jactr.core.module.declarative.basic.chunk.IChunkFactory; import org.jactr.core.module.declarative.basic.chunk.ISubsymbolicChunkFactory; /** * Chunk listener that handles associative links. This does three things. 1) it * creates (or removes) associative links between this chunk (i) and any chunks * it has as slot values (j). 2) it handles the associative links when a chunk * is merged. 3) updates the statistics used for Sji calculation on merging * * @author developer */ public class ChunkListener extends ChunkListenerAdaptor { /** * logger definition */ static private final Log LOGGER = LogFactory .getLog(ChunkListener.class); private final DefaultAssociativeLinkageSystem4 _linkageSystem; public ChunkListener(DefaultAssociativeLinkageSystem4 linkageSystem) { _linkageSystem = linkageSystem; } @Override public void chunkEncoded(ChunkEvent ce) { // remove listener ce.getSource().removeListener(this); } /** * handles the updating of associative links, The updating is done here since * the listener is removed after encoding, so the master chunk will not have * this listener attached * * @param event * @see org.jactr.core.chunk.event.ChunkListenerAdaptor#mergingInto(org.jactr.core.chunk.event.ChunkEvent) */ @Override public void mergingInto(ChunkEvent event) { IChunk self = event.getSource(); IChunk master = event.getChunk(); if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("** Start Merge ** Merging %s into %s", self, master)); IChunk selfCopiedFrom = (IChunk) self .getMetaData(IChunkFactory.COPIED_FROM_KEY); Object sscCopied = self .getMetaData(ISubsymbolicChunkFactory.SUBSYMBOLICS_COPIED_KEY); boolean subsymbolicsCopied = sscCopied != null ? (Boolean) sscCopied : false; if (LOGGER.isDebugEnabled()) { LOGGER.debug(String.format("Master %s(N:%.2f, C:%.2f)", master, master .getSubsymbolicChunk().getTimesNeeded(), master.getSubsymbolicChunk() .getTimesInContext())); LOGGER.debug(String.format("Mergee %s(N:%.2f, C:%.2f)", self, self .getSubsymbolicChunk().getTimesNeeded(), self.getSubsymbolicChunk() .getTimesInContext())); } if (selfCopiedFrom != null && selfCopiedFrom.equals(master) && subsymbolicsCopied) // in this case, self has the most recent links including those from // master, so we can just // replace master's links with self's links (after juggling IDs) copyMergingIntoMaster(master, self); else /* * if self was copied from a different chunk or is a new blank chunk, we * have to merge the two.. */ newMergingIntoMaster(master, self); if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("** End Merge **")); } /** * merge copy's self link with master's. We also check to see if master and * copy are linked to each other. If they are, merging would result in * overcompensation of FNiCj by the FNiCj of the master-copy link. This * returns the corrections necessary [copyI-masterJ, masterI-copyJ]. We need * both correction values since the rules of association are not specified at * this level. If links are cooccuring, then both values should be the same, * but if links are created or strengthened unidirectionally, we need both * values. * * @param master * @param copy * @param absorb * if true, copy's self-link values will override masters * @return */ protected double[] processSelfLinks(IChunk master, IChunk copy, boolean absorb) { double[] correction = new double[2]; master.getAdapter(IAssociativeLinkContainer.class); copy.getAdapter(IAssociativeLinkContainer.class); FastList.newInstance(); // link between copy-master Link4 link = (Link4) getAssociativeLink(copy, master, false); if (link != null) { correction[0] = link.getFNICJ(); // remove so that they don't become redundant self-links _linkageSystem.removeLink(link); } // link between master-copy link = (Link4) getAssociativeLink(copy, master, true); if (link != null) { correction[1] = link.getFNICJ(); // remove so that they don't become redundant self-links _linkageSystem.removeLink(link); } /* * self won't actually have a self link since it isn't created until * encoding. but lets just make sure */ link = (Link4) getAssociativeLink(copy, copy, true); if (link != null) { // master selflink Link4 mLink = (Link4) getAssociativeLink(master, master, false); if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format( "%s has a self link %s. %s into master self link %s", copy, link, absorb ? "Absorbing" : "Merging", mLink)); if (absorb) // if copy was a literally subsymbolic copy, it will // have the original values of mLink, so we just swap them entirely mLink.setFNICJ(link.getFNICJ()); else mLink.setFNICJ(mLink.getFNICJ() + link.getFNICJ()); if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Master self link now : %s", mLink)); // and remove so we don't double process _linkageSystem.removeLink(link); } if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("FNiCj corrections i-j [%.2f] j-i[%.2f]", correction[0], correction[1])); return correction; } /** * @param master * @param copy * @param absorbLinks * true if copies links should override master's values. false if * they are to be merged instead * @param processInboundLinks * true to process the links where copy and master are the i, false * if they are the j */ protected void processLinks(IChunk master, IChunk copy, boolean absorbLinks, boolean processInboundLinks, double fnicjCorrection) { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("%s merging into %s, processing %sLinks", copy, master, processInboundLinks ? "j" : "i")); ISubsymbolicChunk4 masterSSC = master.getSubsymbolicChunk().getAdapter( ISubsymbolicChunk4.class); IAssociativeLinkContainer cCont = copy .getAdapter(IAssociativeLinkContainer.class); master.getAdapter(IAssociativeLinkContainer.class); FastList<IAssociativeLink> links = FastList.newInstance(); if (processInboundLinks) cCont.getInboundLinks(links); else cCont.getOutboundLinks(links); if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Testing %d links", links.size())); for (IAssociativeLink link : links) { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Testing link %s", link)); Link4 oldLink = (Link4) link; /** * otherChunk is the other side of the link. If we are processing jLinks * (i.e., copy is the iChunk), the otherChunk is link.getJChunk(); */ IChunk otherChunk = processInboundLinks ? oldLink.getJChunk() : oldLink .getIChunk(); /* * skip the self-links */ if (copy.equals(otherChunk) || master.equals(otherChunk)) continue; /* * regardless of what we do with this, we need to remove it from the * otherChunk so that otherChunk doesn't link back to the merged chunk. */ ISubsymbolicChunk4 otherSSC = otherChunk .getAdapter(ISubsymbolicChunk4.class); double fNiCj = oldLink.getFNICJ(); int count = oldLink.getCount(); /* * further traces support that this correction code is actually creating a * problem that was inadvertantly corrected downstream. This is a test * change - commenting out the correct, until confirmation (AMH 6/30/16) */ // if (fnicjCorrection > 0) // { // /** // * // */ // fNiCj = Math.max(0, fNiCj - fnicjCorrection); // if (LOGGER.isDebugEnabled()) // LOGGER.debug(String.format("Original FNiCj(%.2f) corrected (%.2f)", // oldLink.getFNICJ(), fNiCj)); // } /* * now we process the link, either merging it or absorbing its values */ Link4 masterLink = (Link4) getAssociativeLink(master, otherChunk, !processInboundLinks); // we need to flip this so that I/Js match if (masterLink != null) { if (LOGGER.isDebugEnabled()) LOGGER .debug(String .format( "Master(N:%.2f, C:%.2f) is already linked to %s(N:%.2f, C:%.2f) via %s", masterSSC.getTimesNeeded(), masterSSC.getTimesInContext(), otherChunk, otherSSC.getTimesNeeded(), otherSSC.getTimesInContext(), masterLink)); if (absorbLinks) { // copy's values supercede master's if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format( "Absorbing copy's link values %s to master %s", oldLink, masterLink)); masterLink.setCount(count); masterLink.setFNICJ(fNiCj); } else { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format( "Merging copy's link values %s into master %s", oldLink, masterLink)); // merge the values of the links masterLink.setCount(Math.max(count, masterLink.getCount())); masterLink.setFNICJ(masterLink.getFNICJ() + fNiCj); } if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Updating link %s", masterLink)); } else { Link4 newLink = null; if (processInboundLinks) newLink = (Link4) _linkageSystem.createLink(master, otherChunk); else newLink = (Link4) _linkageSystem.createLink(otherChunk, master); newLink.setCount(count); newLink.setFNICJ(fNiCj); // no longer make these calls directly // otherSSC.addLink(newLink); // masterSSC.addLink(newLink); _linkageSystem.addLink(newLink); if (LOGGER.isDebugEnabled()) LOGGER .debug(String .format( "Master was not linked to %s, but copy was %s. Created new link %s", otherChunk, oldLink, newLink)); } /* * after all is said and done, remove the old link. we mess with the count * to be sure we remove the link entirely. */ oldLink.setCount(1); _linkageSystem.removeLink(oldLink); // copySSC.removeLink(oldLink); // otherSSC.removeLink(oldLink); } FastList.recycle(links); } /** * Return a single associative link that existing between containingChunk and * referenceChunk. If getOutbound is true, containingChunk is j & * referenceChunk is I. If false, containingChunk is i & reference chunk is j * * @param containingChunk * @param referenceChunk * @param getOutbound * @return */ static public IAssociativeLink getAssociativeLink(IChunk containingChunk, IChunk referenceChunk, boolean getOutbound) { FastList<IAssociativeLink> links = FastList.newInstance(); try { IAssociativeLinkContainer alc = containingChunk .getAdapter(IAssociativeLinkContainer.class); if (getOutbound) alc.getOutboundLinks(referenceChunk, links); else alc.getInboundLinks(referenceChunk, links); if (links.size() == 0) return null; return links.iterator().next(); } finally { FastList.recycle(links); } } /** * if a copy of master and master are to be merged, and copy has master's * subsymbolics, then master can simply assume the same values and links. * * @param master * @param copy */ protected void copyMergingIntoMaster(IChunk master, IChunk copy) { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("%s, a copy of %s is being merged back in.", copy, master)); ISubsymbolicChunk4 selfSSC = copy.getAdapter(ISubsymbolicChunk4.class); ISubsymbolicChunk4 masterSSC = master.getAdapter(ISubsymbolicChunk4.class); /** * Sji stats */ masterSSC.setTimesInContext(selfSSC.getTimesInContext()); masterSSC.setTimesNeeded(selfSSC.getTimesNeeded()); if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Master %s(N:%.2f, C:%.2f)", master, master .getSubsymbolicChunk().getTimesNeeded(), master.getSubsymbolicChunk() .getTimesInContext())); double[] correction = processSelfLinks(master, copy, true); // process master-J processLinks(master, copy, true, true, correction[0]); // process I-master processLinks(master, copy, true, false, correction[1]); } /** * a new chunk merging into an existing one requires that the new links be * updated and absorbed into master, while addressing links between master and * identical (new). * * @param master * @param identical */ protected void newMergingIntoMaster(IChunk master, IChunk identical) { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("%s is now identical to %s. Merging.", identical, master)); ISubsymbolicChunk4 selfSSC = identical.getAdapter(ISubsymbolicChunk4.class); ISubsymbolicChunk4 masterSSC = master.getAdapter(ISubsymbolicChunk4.class); /** * Sji stats */ masterSSC.setTimesInContext(masterSSC.getTimesInContext() + selfSSC.getTimesInContext()); masterSSC.setTimesNeeded(masterSSC.getTimesNeeded() + selfSSC.getTimesNeeded()); if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Master %s(N:%.2f, C:%.2f)", master, master .getSubsymbolicChunk().getTimesNeeded(), master.getSubsymbolicChunk() .getTimesInContext())); double[] correction = processSelfLinks(master, identical, false); // process master-J processLinks(master, identical, false, true, correction[0]); // process I-master processLinks(master, identical, false, false, correction[1]); } @Override public void slotChanged(ChunkEvent ce) { IChunk iChunk = ce.getSource(); Object oldValue = ce.getOldSlotValue(); Object newValue = ce.getNewSlotValue(); if (LOGGER.isDebugEnabled()) LOGGER.debug(iChunk + "." + ce.getSlotName() + "=" + newValue + " (was " + oldValue + ")"); if (oldValue instanceof IChunk) _linkageSystem.linkSlotValue(iChunk, (IChunk) oldValue, true); if (newValue instanceof IChunk) _linkageSystem.linkSlotValue(iChunk, (IChunk) newValue, false); } }