package uk.ac.manchester.cs.jfact.kernel; /* This file is part of the JFact DL reasoner Copyright 2011-2013 by Ignazio Palmisano, Dmitry Tsarkov, University of Manchester 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA*/ import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import uk.ac.manchester.cs.jfact.helpers.Templates; import uk.ac.manchester.cs.jfact.kernel.Concept.CTTag; import uk.ac.manchester.cs.jfact.kernel.dl.interfaces.NamedEntity; import uk.ac.manchester.cs.jfact.kernel.modelcaches.ModelCacheInterface; import uk.ac.manchester.cs.jfact.kernel.modelcaches.ModelCacheState; import uk.ac.manchester.cs.jfact.split.SplitVarEntry; import uk.ac.manchester.cs.jfact.split.TSignature; import uk.ac.manchester.cs.jfact.split.TSplitVar; import conformance.PortedFrom; /** Concept taxonomy */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "DLConceptTaxonomy") public class DLConceptTaxonomy extends TaxonomyCreator { private static final long serialVersionUID = 11000L; /** flag shows that subsumption check could be simplified */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "inSplitCheck") private boolean inSplitCheck = false; /** host tBox */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "tBox") private final TBox tBox; /** common descendants of all parents of currently classified concept */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "Common") private final List<TaxonomyVertex> common = new ArrayList<TaxonomyVertex>(); // statistic counters @PortedFrom(file = "DLConceptTaxonomy.h", name = "nConcepts") private long nConcepts = 0; @PortedFrom(file = "DLConceptTaxonomy.h", name = "nTries") private long nTries = 0; @PortedFrom(file = "DLConceptTaxonomy.h", name = "nPositives") private long nPositives = 0; @PortedFrom(file = "DLConceptTaxonomy.h", name = "nNegatives") private long nNegatives = 0; @PortedFrom(file = "DLConceptTaxonomy.h", name = "nSearchCalls") private long nSearchCalls = 0; @PortedFrom(file = "DLConceptTaxonomy.h", name = "nSubCalls") private long nSubCalls = 0; @PortedFrom(file = "DLConceptTaxonomy.h", name = "nNonTrivialSubCalls") private long nNonTrivialSubCalls = 0; /** number of positive cached subsumptions */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "nCachedPositive") private long nCachedPositive = 0; /** number of negative cached subsumptions */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "nCachedNegative") private long nCachedNegative = 0; /** number of non-subsumptions detected by a sorted reasoning */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "nSortedNegative") private long nSortedNegative = 0; /** number of non-subsumptions because of module reasons */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "nModuleNegative") private long nModuleNegative = 0; // flags /** flag to use Bottom-Up search */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "flagNeedBottomUp") private boolean flagNeedBottomUp; /** number of processed common parents */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "nCommon") protected int nCommon = 1; /** set of possible parents */ protected final Set<TaxonomyVertex> candidates = new HashSet<TaxonomyVertex>(); /** whether look into it */ protected boolean useCandidates = false; protected Set<NamedEntity> MPlus; protected Set<NamedEntity> MMinus; // -- General support for DL concept classification /** * get access to curEntry as a TConcept * * @return current entry */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "curConcept") private Concept curConcept() { return (Concept) curEntry; } @PortedFrom(file = "DLConceptTaxonomy.h", name = "enhancedSubs") private boolean enhancedSubs(TaxonomyVertex cur) { ++nSubCalls; if (isValued(cur)) { return getValue(cur); } else { return setValue(cur, enhancedSubs2(cur)); } } /** explicitely run TD phase */ @Override @PortedFrom(file = "DLConceptTaxonomy.h", name = "runTopDown") public void runTopDown() { searchBaader(pTax.getTopVertex()); } /** explicitely run BU phase */ @Override @PortedFrom(file = "DLConceptTaxonomy.h", name = "runBottomUp") public void runBottomUp() { try { if (propagateUp()) { return; } if (isEqualToTop()) { return; } if (pTax.queryMode()) { // after classification -- bottom set up already searchBaader(pTax.getBottomVertex()); return; } // during classification -- have to find leaf nodes for (int i = 0; i < common.size(); i++) { TaxonomyVertex p = common.get(i); if (p.noNeighbours(false)) { searchBaader(p); } } } finally { clearCommon(); } } /** actions that to be done BEFORE entry will be classified */ @Override @PortedFrom(file = "DLConceptTaxonomy.h", name = "preClassificationActions") public void preClassificationActions() { ++nConcepts; tBox.getOptions().getProgressMonitor() .reasonerTaskProgressChanged((int) nConcepts, tBox.getNItems()); } /** * the only c'tor * * @param pTax * pTax * @param tbox * tbox */ public DLConceptTaxonomy(Taxonomy pTax, TBox tbox) { super(pTax); tBox = tbox; } /** process all splits */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "processSplits") public void processSplits() { for (TSplitVar v : tBox.getSplits().getEntries()) { mergeSplitVars(v); } } /** * set bottom-up flag * * @param GCIs * GCIs */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "setBottomUp") public void setBottomUp(KBFlags GCIs) { flagNeedBottomUp = GCIs.isGCI() || GCIs.isReflexive() && GCIs.isRnD(); } @PortedFrom(file = "DLConceptTaxonomy.h", name = "isUnsatisfiable") private boolean isUnsatisfiable() { Concept p = curConcept(); if (tBox.isSatisfiable(p)) { return false; } pTax.addCurrentToSynonym(pTax.getBottomVertex()); return true; } @Override @PortedFrom(file = "DLConceptTaxonomy.h", name = "buildSignature") public TSignature buildSignature(ClassifiableEntry p) { return tBox.getSignature(p); } @Override @PortedFrom(file = "DLConceptTaxonomy.h", name = "immediatelyClassified") protected boolean immediatelyClassified() { if (classifySynonym()) { return true; } if (curConcept().getClassTagPlain() == CTTag.cttTrueCompletelyDefined) { return false; // true CD concepts can not be unsat } // after SAT testing plan would be implemented tBox.initCache(curConcept(), false); return isUnsatisfiable(); } @Override @PortedFrom(file = "DLConceptTaxonomy.h", name = "needTopDown") protected boolean needTopDown() { return !(useCompletelyDefined && curEntry.isCompletelyDefined()); } @Override @PortedFrom(file = "DLConceptTaxonomy.h", name = "needBottomUp") protected boolean needBottomUp() { // we DON'T need bottom-up phase for primitive concepts during CD-like // reasoning // if no GCIs are in the TBox (C [= T, T [= X or Y, X [= D, Y [= D) // or no reflexive roles w/RnD precent (Refl(R), Range(R)=D) return flagNeedBottomUp || !useCompletelyDefined || !curConcept().isPrimitive(); } @PortedFrom(file = "DLConceptTaxonomy.h", name = "testSub") private boolean testSub(Concept p, Concept q) { assert p != null; assert q != null; if (q.isSingleton() && q.isPrimitive() && !q.isNominal()) { // singleton on the RHS is useless iff it is primitive return false; } if (inSplitCheck) { if (q.isPrimitive()) { return false; } return testSubTBox(p, q); } tBox.getOptions().getLog() .printTemplate(Templates.TAX_TRYING, p.getName(), q.getName()); if (tBox.testSortedNonSubsumption(p, q)) { tBox.getOptions().getLog().print("NOT holds (sorted result)"); ++nSortedNegative; return false; } if (isNotInModule(q.getEntity())) { tBox.getOptions().getLog().print("NOT holds (module result)"); ++nModuleNegative; return false; } switch (tBox.testCachedNonSubsumption(p, q)) { case csValid: tBox.getOptions().getLog().print("NOT holds (cached result)"); ++nCachedNegative; return false; case csInvalid: tBox.getOptions().getLog().print("holds (cached result)"); ++nCachedPositive; return true; default: tBox.getOptions().getLog().print("wasted cache test"); break; } return testSubTBox(p, q); } /** * @param entity * entity * @return true if non-subsumption is due to ENTITY is not in the * \bot-module */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "isNotInModule") private boolean isNotInModule(NamedEntity entity) { if (upDirection) { return false; } TSignature sig = sigStack.peek(); if (sig != null && entity != null && !sig.containsNamedEntity(entity)) { return true; } return false; } /** * test subsumption via TBox explicitely * * @param p * p * @param q * q * @return true if subsumption holds */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "testSubTBox") private boolean testSubTBox(Concept p, Concept q) { boolean res = tBox.isSubHolds(p, q); // update statistic ++nTries; if (res) { ++nPositives; } else { ++nNegatives; } return res; } @Override public String toString() { StringBuilder o = new StringBuilder(); o.append(String.format( Templates.DLCONCEPTTAXONOMY.getTemplate(), nTries, nPositives, nPositives * 100 / Math.max(1, nTries), nCachedPositive, nCachedNegative, nSortedNegative > 0 ? String.format( "Sorted reasoning deals with %s non-subsumptions\n", nSortedNegative) : "", nModuleNegative > 0 ? "Modular reasoning deals with " + nModuleNegative + " non-subsumptions\n" : "", nSearchCalls, nSubCalls, nNonTrivialSubCalls, nEntries * (nEntries - 1) / Math.max(1, nTries))); o.append(super.toString()); return o.toString(); } @PortedFrom(file = "DLConceptTaxonomy.h", name = "searchBaader") private void searchBaader(TaxonomyVertex cur) { pTax.setVisited(cur); ++nSearchCalls; boolean noPosSucc = true; for (TaxonomyVertex p : cur.neigh(upDirection)) { if (enhancedSubs(p)) { if (!pTax.isVisited(p)) { searchBaader(p); } noPosSucc = false; } } // in case current node is unchecked (no BOTTOM node) -- check it // explicitly if (!isValued(cur)) { setValue(cur, testSubsumption(cur)); } if (noPosSucc && cur.getValue()) { pTax.getCurrent().addNeighbour(!upDirection, cur); } } @PortedFrom(file = "DLConceptTaxonomy.h", name = "enhancedSubs1") private boolean enhancedSubs1(TaxonomyVertex cur) { ++nNonTrivialSubCalls; // need to be valued -- check all parents // propagate false // do this only if the concept is not it M- for (TaxonomyVertex n : cur.neigh(!upDirection)) { if (!enhancedSubs(n)) { return false; } } return testSubsumption(cur); } /** * short-cut from ENHANCED_SUBS * * @param cur * cur * @return true if subsumption holds */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "enhancedSubs2") private boolean enhancedSubs2(TaxonomyVertex cur) { // if bottom-up search and CUR is not a successor of checking entity -- // return false if (upDirection && !cur.isCommon()) { return false; } // for top-down search it's enough to look at defined concepts and // non-det ones // if (tBox.getOptions().isSplits()) { // if (!inSplitCheck && !upDirection && !possibleSub(cur)) { // return false; // } // } if (useCandidates && candidates.contains(cur)) { return false; } return enhancedSubs1(cur); } /** * test whether a node could be a super-node of CUR * * @param v * v * @return true if candidate */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "possibleSub") private boolean possibleSub(TaxonomyVertex v) { Concept C = (Concept) v.getPrimer(); // non-prim concepts are candidates if (!C.isPrimitive()) { return true; } // all others should be in the possible sups list return ksStack.peek().isPossibleSub(C); } @PortedFrom(file = "DLConceptTaxonomy.h", name = "testSubsumption") private boolean testSubsumption(TaxonomyVertex cur) { Concept testC = (Concept) cur.getPrimer(); if (upDirection) { return testSub(testC, curConcept()); } else { return testSub(curConcept(), testC); } } @PortedFrom(file = "DLConceptTaxonomy.h", name = "propagateOneCommon") private void propagateOneCommon(TaxonomyVertex node) { // checked if node already was visited this session if (pTax.isVisited(node)) { return; } // mark node visited pTax.setVisited(node); node.setCommon(); if (node.correctCommon(nCommon)) { common.add(node); } // mark all children for (TaxonomyVertex n : node.neigh(false)) { propagateOneCommon(n); } } @PortedFrom(file = "DLConceptTaxonomy.h", name = "propagateUp") private boolean propagateUp() { nCommon = 1; Iterator<TaxonomyVertex> list = pTax.getCurrent().neigh(upDirection) .iterator(); assert list.hasNext(); // there is at least one parent (TOP) TaxonomyVertex p = list.next(); // define possible successors of the node propagateOneCommon(p); pTax.clearVisited(); while (list.hasNext()) { p = list.next(); if (p.noNeighbours(!upDirection)) { return true; } if (common.isEmpty()) { return true; } ++nCommon; List<TaxonomyVertex> aux = new ArrayList<TaxonomyVertex>(common); common.clear(); propagateOneCommon(p); pTax.clearVisited(); int auxSize = aux.size(); for (int j = 0; j < auxSize; j++) { aux.get(j).correctCommon(nCommon); } } return false; } @PortedFrom(file = "DLConceptTaxonomy.h", name = "clearCommon") private void clearCommon() { int size = common.size(); for (int i = 0; i < size; i++) { common.get(i).clearCommon(); } common.clear(); } /** * check if no BU classification is required as C=TOP * * @return true if no classification required */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "isEqualToTop") private boolean isEqualToTop() { // check this up-front to avoid Sorted check's flaw wrt equals-to-top ModelCacheInterface cache = tBox.initCache(curConcept(), true); if (cache.getState() != ModelCacheState.csInvalid) { return false; } // here concept = TOP pTax.current.addNeighbour(false, pTax.getTopVertex()); return true; } /** @return true iff curEntry is classified as a synonym */ @Override @PortedFrom(file = "DLConceptTaxonomy.h", name = "classifySynonym") protected boolean classifySynonym() { if (super.classifySynonym()) { return true; } if (curConcept().isSingleton()) { Individual curI = (Individual) curConcept(); if (tBox.isBlockedInd(curI)) { // check whether current entry is the same as another individual Individual syn = tBox.getBlockingInd(curI); assert syn.getTaxVertex() != null; if (tBox.isBlockingDet(curI)) { // deterministic merge => curI = syn pTax.addCurrentToSynonym(syn.getTaxVertex()); return true; } else { // non-det merge: check whether it is the same tBox.getOptions() .getLog() .print("\nTAX: trying '", curI.getName(), "' = '", syn.getName(), "'... "); if (testSubTBox(curI, syn)) { // they are actually the same pTax.addCurrentToSynonym(syn.getTaxVertex()); return true; } } } } return false; } /** * after merging, check whether there are extra neighbours that should be * taken into account */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "checkExtraParents") private void checkExtraParents() { inSplitCheck = true; for (TaxonomyVertex p : pTax.current.neigh(true)) { propagateTrueUp(p); } pTax.current.clearLinks(true); runTopDown(); List<TaxonomyVertex> vec = new ArrayList<TaxonomyVertex>(); for (TaxonomyVertex p : pTax.current.neigh(true)) { if (!isDirectParent(p)) { vec.add(p); } } for (TaxonomyVertex p : vec) { p.removeLink(false, pTax.current); pTax.current.removeLink(true, p); } clearLabels(); inSplitCheck = false; } /** * merge vars came from a given SPLIT together * * @param split * split */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "mergeSplitVars") private void mergeSplitVars(TSplitVar split) { Set<TaxonomyVertex> splitVertices = new HashSet<TaxonomyVertex>(); TaxonomyVertex v = split.getC().getTaxVertex(); boolean cIn = v != null; if (v != null) { splitVertices.add(v); } for (SplitVarEntry q : split.getEntries()) { splitVertices.add(q.concept.getTaxVertex()); } // set V to be a node-to-add // FIXME!! check later the case whether both TOP and BOT are there if (splitVertices.contains(pTax.getBottomVertex())) { v = pTax.getBottomVertex(); } else if (splitVertices.contains(pTax.getTopVertex())) { v = pTax.getTopVertex(); } else { setCurrentEntry(split.getC()); v = pTax.current; } if (!v.equals(pTax.current) && !cIn) { v.addSynonym(split.getC()); } for (TaxonomyVertex p : splitVertices) { mergeVertex(v, p, splitVertices); } if (v == pTax.current) { checkExtraParents(); pTax.finishCurrentNode(); } } /** * merge a single vertex V to a node represented by CUR * * @param cur * cur * @param v * v * @param excludes * excludes */ @PortedFrom(file = "DLConceptTaxonomy.h", name = "mergeVertex") private void mergeVertex(TaxonomyVertex cur, TaxonomyVertex v, Set<TaxonomyVertex> excludes) { if (!cur.equals(v)) { cur.mergeIndepNode(v, excludes, curEntry); pTax.removeNode(v); } } /** * fill candidates * * @param cur * vertex to add to candidates (and its updirection neighbors */ public void fillCandidates(TaxonomyVertex cur) { if (isValued(cur)) { if (getValue(cur)) { // positive value -- nothing to do return; } } else { candidates.add(cur); } for (TaxonomyVertex p : cur.neigh(true)) { fillCandidates(p); } } /** * @param plus * plus * @param minus * minus */ public void reclassify(Set<NamedEntity> plus, Set<NamedEntity> minus) { MPlus = plus; MMinus = minus; pTax.deFinalise(); // fill in an order to LinkedList<TaxonomyVertex> queue = new LinkedList<TaxonomyVertex>(); List<ClassifiableEntry> toProcess = new ArrayList<ClassifiableEntry>(); queue.add(pTax.getTopVertex()); while (!queue.isEmpty()) { TaxonomyVertex cur = queue.remove(0); if (pTax.isVisited(cur)) { continue; } pTax.setVisited(cur); ClassifiableEntry entry = cur.getPrimer(); if (MPlus.contains(entry.getEntity()) || MMinus.contains(entry.getEntity())) { toProcess.add(entry); } for (TaxonomyVertex t : cur.neigh(false)) { queue.add(t); } } pTax.clearVisited(); // System.out.println("Determine concepts that need reclassification (" // + toProcess.size() + "): done in " + t); // System.out.println("Add/Del names Taxonomy:" + tax); for (int i = 0; i < toProcess.size(); i++) { ClassifiableEntry p = toProcess.get(i); reclassify(p.getTaxVertex(), tBox.getSignature(p)); } pTax.finalise(); } /** * @param node * node * @param s * s */ public void reclassify(TaxonomyVertex node, TSignature s) { upDirection = false; sigStack.add(s); curEntry = node.getPrimer(); TaxonomyVertex oldCur = pTax.getCurrent(); pTax.setCurrent(node); // FIXME!! check the unsatisfiability later boolean added = MPlus.contains(curEntry.getEntity()); boolean removed = MMinus.contains(curEntry.getEntity()); assert added || removed; clearLabels(); setValue(pTax.getTopVertex(), true); if (node.noNeighbours(true)) { node.addNeighbour(true, pTax.getTopVertex()); } // we use candidates set if nothing was added (so no need to look // further from current subs) useCandidates = !added; candidates.clear(); if (removed) { // re-check all parents // List<TaxonomyVertex> pos = new ArrayList<TaxonomyVertex>(); List<TaxonomyVertex> neg = new ArrayList<TaxonomyVertex>(); for (TaxonomyVertex p : node.neigh(true)) { if (isValued(p) && getValue(p)) { continue; } boolean sub = testSubsumption(p); if (sub) { // pos.add(p); propagateTrueUp(p); } else { setValue(p, sub); neg.add(p); } } node.removeLinks(true); // for (TaxonomyVertex q : pos) { // node.addNeighbour(true, q); // } if (useCandidates) { for (TaxonomyVertex q : neg) { fillCandidates(q); } } } else { // all parents are there for (TaxonomyVertex p : node.neigh(true)) { propagateTrueUp(p); } node.removeLinks(true); } // FIXME!! for now. later check the equivalence etc setValue(node, true); // the landscape is prepared searchBaader(pTax.getTopVertex()); node.incorporate(pTax.getOptions()); clearLabels(); sigStack.pop(); pTax.setCurrent(oldCur); } }