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.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; import java.util.List; import uk.ac.manchester.cs.jfact.helpers.Templates; import uk.ac.manchester.cs.jfact.split.TSignature; import conformance.PortedFrom; /** taxonomy creator for DL */ @PortedFrom(file = "TaxonomyCreator.h", name = "TaxonomyCreator") public class TaxonomyCreator implements Serializable { private static final long serialVersionUID = 11000L; @PortedFrom(file = "TaxonomyCreator.h", name = "pTax") protected final Taxonomy pTax; @PortedFrom(file = "TaxonomyCreator.h", name = "Syns") protected final List<ClassifiableEntry> Syns = new ArrayList<ClassifiableEntry>(); /** labeller for marking nodes with a label wrt classification */ @PortedFrom(file = "TaxonomyCreator.h", name = "valueLabel") protected long valueLabel = 1; /** pointer to currently classified entry */ @PortedFrom(file = "TaxonomyCreator.h", name = "curEntry") protected ClassifiableEntry curEntry = null; /** number of tested entryes */ @PortedFrom(file = "TaxonomyCreator.h", name = "nEntries") protected int nEntries = 0; /** number of completely-defined entries */ @PortedFrom(file = "TaxonomyCreator.h", name = "nCDEntries") protected long nCDEntries = 0; /** * optimisation flag: if entry is completely defined by it's told subsumers, * no other classification required */ @PortedFrom(file = "TaxonomyCreator.h", name = "useCompletelyDefined") protected boolean useCompletelyDefined = false; /** session flag: shows the direction of the search */ @PortedFrom(file = "TaxonomyCreator.h", name = "upDirection") protected boolean upDirection; /** stack for Taxonomy creation */ @PortedFrom(file = "TaxonomyCreator.h", name = "waitStack") private final LinkedList<ClassifiableEntry> waitStack = new LinkedList<ClassifiableEntry>(); /** told subsumers corresponding to a given entry */ @PortedFrom(file = "TaxonomyCreator.h", name = "ksStack") protected final LinkedList<KnownSubsumers> ksStack = new LinkedList<KnownSubsumers>(); /** signature of a \bot-module corresponding to a given entry */ @PortedFrom(file = "TaxonomyCreator.h", name = "sigStack") protected final LinkedList<TSignature> sigStack = new LinkedList<TSignature>(); /** * @param pTax2 * pTax2 */ public TaxonomyCreator(Taxonomy pTax2) { pTax = pTax2; } /** * initialise aux entry with given concept p * * @param p * p */ @PortedFrom(file = "TaxonomyCreator.h", name = "setCurrentEntry") protected void setCurrentEntry(ClassifiableEntry p) { pTax.getCurrent().clear(); pTax.getCurrent().setSample(p, true); curEntry = p; } @PortedFrom(file = "TaxonomyCreator.h", name = "classifySynonym") protected boolean classifySynonym() { return pTax.processSynonym(); } @PortedFrom(file = "TaxonomyCreator.cpp", name = "setToldSubsumers") private void setToldSubsumers() { Collection<ClassifiableEntry> top = ksStack.peek().s_begin(); if (pTax.getOptions().isNeedLogging() && !top.isEmpty()) { pTax.getOptions().getLog().print("\nTAX: told subsumers"); } for (ClassifiableEntry p : top) { if (p.isClassified()) { if (pTax.getOptions().isNeedLogging()) { pTax.getOptions() .getLog() .printTemplate(Templates.TOLD_SUBSUMERS, p.getName()); } propagateTrueUp(p.getTaxVertex()); } } // XXX this is misleading: in the C++ code the only implementation // available will always say that top is empty here even if it never // is. // if (!top.isEmpty() && needLogging()) { // LL.print(" and possibly "); // for (ClassifiableEntry q : top) { // LL.print(Templates.TOLD_SUBSUMERS, q.getName()); // } // } } @PortedFrom(file = "TaxonomyCreator.cpp", name = "setNonRedundantCandidates") private void setNonRedundantCandidates() { if (!curEntry.hasToldSubsumers() && pTax.getOptions().isNeedLogging()) { pTax.getOptions().getLog().print("\nTAX: TOP"); pTax.getOptions().getLog().print(" completely defines concept "); pTax.getOptions().getLog().print(curEntry.getName()); } // test if some "told subsumer" is not an immediate TS (ie, not a // border // element) for (ClassifiableEntry p : ksStack.peek().s_begin()) { addPossibleParent(p.getTaxVertex()); } } /** * check if no classification needed (synonym, orphan, unsatisfiable) * * @return true if no classification */ @PortedFrom(file = "TaxonomyCreator.h", name = "immediatelyClassified") protected boolean immediatelyClassified() { return classifySynonym(); } @PortedFrom(file = "TaxonomyCreator.cpp", name = "setupTopDown") private void setupTopDown() { setToldSubsumers(); if (!needTopDown()) { ++nCDEntries; setNonRedundantCandidates(); } } /** * check if it is possible to skip TD phase * * @return true if top down can be skipped */ @PortedFrom(file = "TaxonomyCreator.h", name = "needTopDown") protected boolean needTopDown() { return false; } /** explicitely run TD phase */ @PortedFrom(file = "TaxonomyCreator.h", name = "runTopDown") protected void runTopDown() {} /** * check if it is possible to skip BU phase * * @return true if bottom up necessary */ @PortedFrom(file = "TaxonomyCreator.h", name = "needBottomUp") protected boolean needBottomUp() { return false; } /** explicitely run BU phase */ @PortedFrom(file = "TaxonomyCreator.h", name = "runBottomUp") protected void runBottomUp() {} /** actions that to be done BEFORE entry will be classified */ @PortedFrom(file = "TaxonomyCreator.cpp", name = "preClassificationActions") protected void preClassificationActions() {} @PortedFrom(file = "TaxonomyCreator.cpp", name = "performClassification") private void performClassification() { // do something before classification (tunable) preClassificationActions(); ++nEntries; if (pTax.getOptions().isNeedLogging()) { // this output is currently disabled in FaCT++ pTax.getOptions().getLog().print("\nTAX: start classifying entry "); pTax.getOptions().getLog().print(curEntry.getName()); } // if no classification needed -- nothing to do if (immediatelyClassified()) { return; } // perform main classification generalTwoPhaseClassification(); // create new vertex pTax.finishCurrentNode(); // clear all labels clearLabels(); } @PortedFrom(file = "TaxonomyCreator.cpp", name = "generalTwoPhaseClassification") private void generalTwoPhaseClassification() { setupTopDown(); if (needTopDown()) { setValue(pTax.getTopVertex(), true); setValue(pTax.getBottomVertex(), false); upDirection = false; runTopDown(); } clearLabels(); if (needBottomUp()) { setValue(pTax.getBottomVertex(), true); upDirection = true; runBottomUp(); } clearLabels(); } /** * @param v * v * @return true if V is a direct parent of current wrt labels */ @PortedFrom(file = "TaxonomyCreator.cpp", name = "isDirectParent") public boolean isDirectParent(TaxonomyVertex v) { for (TaxonomyVertex q : v.neigh(false)) { if (isValued(q) && getValue(q)) { return false; } } return true; } // -- DFS-based classification /** * add top entry together with its known subsumers * * @param p * p */ @PortedFrom(file = "TaxonomyCreator.h", name = "addTop") private void addTop(ClassifiableEntry p) { waitStack.push(p); ksStack.push(new ToldSubsumers(p.getToldSubsumers())); sigStack.push(buildSignature(p)); } /** remove top entry */ @PortedFrom(file = "TaxonomyCreator.h", name = "removeTop") protected void removeTop() { waitStack.pop(); ksStack.pop(); sigStack.pop(); } @PortedFrom(file = "TaxonomyCreator.cpp", name = "classifyTop") private void classifyTop() { assert !waitStack.isEmpty(); // load last concept setCurrentEntry(waitStack.peek()); if (pTax.getOptions().isTMP_PRINT_TAXONOMY_INFO()) { pTax.getOptions() .getLog() .print("\nTrying classify", curEntry.isCompletelyDefined() ? " CD " : " ", curEntry.getName(), "... "); } performClassification(); if (pTax.getOptions().isTMP_PRINT_TAXONOMY_INFO()) { pTax.getOptions().getLog().print("done"); } removeTop(); } /** * propagate the TRUE value of the KS subsumption up the hierarchy * * @param node * node */ @PortedFrom(file = "TaxonomyCreator.cpp", name = "propagateTrueUp") protected void propagateTrueUp(TaxonomyVertex node) { // if taxonomy class already checked -- do nothing if (isValued(node)) { assert getValue(node); return; } // overwise -- value it... setValue(node, true); // ... and value all parents for (TaxonomyVertex t : node.neigh(true)) { propagateTrueUp(t); } } /** * propagate the FALSE value of the KS subsumption down the hierarchy * * @param node * node */ @PortedFrom(file = "TaxonomyCreator.cpp", name = "propagateFalseDown") protected void propagateFalseDown(TaxonomyVertex node) { // if taxonomy class already checked -- do nothing if (isValued(node)) { assert !getValue(node); return; } // overwise -- value it... setValue(node, false); // ... and value all children for (TaxonomyVertex p : node.neigh(false)) { propagateFalseDown(p); } } /** * propagate constant VALUE into an appropriate direction * * @param node * node * @param value * value * @return value */ @PortedFrom(file = "TaxonomyCreator.cpp", name = "setAndPropagate") protected boolean setAndPropagate(TaxonomyVertex node, boolean value) { if (value) { propagateTrueUp(node); } else { propagateFalseDown(node); } return value; } /** * add PARENT as a parent if it exists and is direct parent * * @param parent * parent */ @PortedFrom(file = "TaxonomyCreator.h", name = "addPossibleParent") public void addPossibleParent(TaxonomyVertex parent) { if (parent != null && isDirectParent(parent)) { pTax.getCurrent().addNeighbour(true, parent); } } @PortedFrom(file = "TaxonomyCreator.h", name = "clearLabels") protected void clearLabels() { pTax.clearLabels(); valueLabel++; } /** * set Completely Defined flag * * @param use * use */ @PortedFrom(file = "TaxonomyCreator.h", name = "setCompletelyDefined") public void setCompletelyDefined(boolean use) { useCompletelyDefined = use; } /** * @param p * p */ @PortedFrom(file = "TaxonomyCreator.h", name = "classifyEntry") public void classifyEntry(ClassifiableEntry p) { assert waitStack.isEmpty(); // don't classify artificial concepts if (p.isNonClassifiable()) { return; } prepareTS(p); } @Override public String toString() { StringBuilder o = new StringBuilder(); o.append("Taxonomy consists of "); o.append(nEntries); o.append(" entries\n of which "); o.append(nCDEntries); o.append(" are completely defined\n\n"); return o.toString(); } /** * ensure that all TS of the top entry are classified. * * @param cur * cur * @return the reason of cycle or NULL. */ @PortedFrom(file = "TaxonomyCreator.cpp", name = "prepareTS") private ClassifiableEntry prepareTS(ClassifiableEntry cur) { // we just found that TS forms a cycle -- return stop-marker if (waitStack.contains(cur)) { return cur; } // starting from the topmost entry addTop(cur); // true iff CUR is a reason of the cycle boolean cycleFound = false; // for all the told subsumers... for (ClassifiableEntry p : ksStack.peek().s_begin()) { if (!p.isClassified()) { // need to classify it first if (p.isNonClassifiable()) { continue; } // prepare TS for *p ClassifiableEntry v = prepareTS(p); // if NULL is returned -- just continue if (v == null) { continue; } if (v.equals(cur)) { // current cycle is finished, all saved in Syns // after classification of CUR we need to mark all the // Syns // as synonyms cycleFound = true; // continue to prepare its classification continue; } else { // arbitrary vertex in a cycle: save in synonyms of a // root // cause Syns.add(cur); // don't need to classify it removeTop(); // return the cycle cause return v; } } } // all TS are ready here -- let's classify! classifyTop(); // now if CUR is the reason of cycle mark all SYNs as synonyms if (cycleFound) { TaxonomyVertex syn = cur.getTaxVertex(); for (ClassifiableEntry q : Syns) { syn.addSynonym(q); } Syns.clear(); } // here the cycle is gone return null; } // ----------------------------------------------------------------- // -- DFS-based classification // ----------------------------------------------------------------- /** * @param node * node * @return true if a NODE has been valued during current classification pass */ @PortedFrom(file = "TaxonomyCreator.h", name = "isValued") public boolean isValued(TaxonomyVertex node) { return node.isValued(valueLabel); } /** * @param node * node * @return the subsumption value of a NODE wrt currently classified one */ @PortedFrom(file = "TaxonomyCreator.h", name = "getValue") public boolean getValue(TaxonomyVertex node) { return node.getValue(); } /** * set the classification value of a NODE to VALUE * * @param node * node * @param value * value * @return val */ @PortedFrom(file = "TaxonomyCreator.h", name = "setValue") public boolean setValue(TaxonomyVertex node, boolean value) { return node.setValued(value, valueLabel); } /** * prepare signature for given entry * * @param p * p * @return signature */ @PortedFrom(file = "TaxonomyCreator.h", name = "buildSignature") protected TSignature buildSignature( @SuppressWarnings("unused") ClassifiableEntry p) { return null; } }