/* Copyright 2008, 2009, 2010 by the Oxford University Computing Laboratory
This file is part of HermiT.
HermiT 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 3 of the License, or
(at your option) any later version.
HermiT 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 HermiT. If not, see <http://www.gnu.org/licenses/>.
*/
package org.semanticweb.HermiT.hierarchy;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.Stack;
import org.semanticweb.HermiT.graph.Graph;
import org.semanticweb.HermiT.hierarchy.DeterministicClassification.GraphNode;
import org.semanticweb.HermiT.hierarchy.HierarchySearch.Relation;
import org.semanticweb.HermiT.model.Atom;
import org.semanticweb.HermiT.model.AtomicConcept;
import org.semanticweb.HermiT.model.DLClause;
import org.semanticweb.HermiT.model.DLPredicate;
import org.semanticweb.HermiT.model.Individual;
import org.semanticweb.HermiT.tableau.ExtensionTable;
import org.semanticweb.HermiT.tableau.Node;
import org.semanticweb.HermiT.tableau.ReasoningTaskDescription;
import org.semanticweb.HermiT.tableau.Tableau;
public class QuasiOrderClassification {
protected final Tableau m_tableau;
protected final ClassificationProgressMonitor m_progressMonitor;
protected final AtomicConcept m_topElement;
protected final AtomicConcept m_bottomElement;
protected final Set<AtomicConcept> m_elements;
protected final Graph<AtomicConcept> m_knownSubsumptions;
protected final Graph<AtomicConcept> m_possibleSubsumptions;
public QuasiOrderClassification(Tableau tableau,ClassificationProgressMonitor progressMonitor,AtomicConcept topElement,AtomicConcept bottomElement,Set<AtomicConcept> elements) {
m_tableau=tableau;
m_progressMonitor=progressMonitor;
m_topElement=topElement;
m_bottomElement=bottomElement;
m_elements=elements;
m_knownSubsumptions=new Graph<AtomicConcept>();
m_possibleSubsumptions=new Graph<AtomicConcept>();
}
public Hierarchy<AtomicConcept> classify() {
Relation<AtomicConcept> relation=new Relation<AtomicConcept>() {
public boolean doesSubsume(AtomicConcept parent,AtomicConcept child) {
Set<AtomicConcept> allKnownSubsumers=getAllKnownSubsumers(child);
if (allKnownSubsumers.contains(parent))
return true;
else if (!m_possibleSubsumptions.getSuccessors(child).contains(parent))
return false;
Individual freshIndividual=Individual.createAnonymous("fresh-individual");
Map<Individual,Node> checkedNode=new HashMap<Individual,Node>();
checkedNode.put(freshIndividual,null);
boolean isSubsumedBy=!m_tableau.isSatisfiable(true,Collections.singleton(Atom.create(child,freshIndividual)),null,null,Collections.singleton(Atom.create(parent,freshIndividual)),checkedNode,getSubsumptionTestDescription(child,parent));
if (!isSubsumedBy)
prunePossibleSubsumers();
readKnownSubsumersFromRootNode(child, checkedNode.get(freshIndividual));
m_possibleSubsumptions.getSuccessors(child).removeAll(getAllKnownSubsumers(child));
return isSubsumedBy;
}
};
return buildHierarchy(relation);
}
protected Hierarchy<AtomicConcept> buildHierarchy(Relation<AtomicConcept> hierarchyRelation) {
double totalNumberOfTasks=m_elements.size();
makeConceptUnsatisfiable(m_bottomElement);
initialiseKnownSubsumptionsUsingToldSubsumers();
double tasksPerformed=updateSubsumptionsUsingLeafNodeStrategy(totalNumberOfTasks);
// Unlike Rob's paper our set of possible subsumptions P would only keep unknown possible subsumptions and not known subsumptions as well.
Set<AtomicConcept> unclassifiedElements=new HashSet<AtomicConcept>();
for (AtomicConcept element : m_elements) {
if (!isUnsatisfiable(element)) {
m_possibleSubsumptions.getSuccessors(element).removeAll(getAllKnownSubsumers(element));
if (!m_possibleSubsumptions.getSuccessors(element).isEmpty()) {
unclassifiedElements.add(element);
continue;
}
}
}
Set<AtomicConcept> classifiedElements=new HashSet<AtomicConcept>();
while (!unclassifiedElements.isEmpty()) {
AtomicConcept unclassifiedElement=null;
for (AtomicConcept element : unclassifiedElements) {
m_possibleSubsumptions.getSuccessors(element).removeAll(getAllKnownSubsumers(element));
if (!m_possibleSubsumptions.getSuccessors(element).isEmpty()) {
unclassifiedElement=element;
break;
}
classifiedElements.add(element);
while (unclassifiedElements.size()<(totalNumberOfTasks-tasksPerformed)) {
m_progressMonitor.elementClassified(element);
tasksPerformed++;
}
}
unclassifiedElements.removeAll(classifiedElements);
if (unclassifiedElements.isEmpty())
break;
Set<AtomicConcept> unknownPossibleSubsumers=m_possibleSubsumptions.getSuccessors(unclassifiedElement);
if (!isEveryPossibleSubsumerNonSubsumer(unknownPossibleSubsumers,unclassifiedElement,2,7) && !unknownPossibleSubsumers.isEmpty()) {
Hierarchy<AtomicConcept> smallHierarchy=buildHierarchyOfUnknownPossible(unknownPossibleSubsumers);
checkUnknownSubsumersUsingEnhancedTraversal(hierarchyRelation,smallHierarchy.getTopNode(),unclassifiedElement);
}
unknownPossibleSubsumers.clear();
}
return buildTransitivelyReducedHierarchy(m_knownSubsumptions,m_elements);
}
protected Hierarchy<AtomicConcept> buildHierarchyOfUnknownPossible(Set<AtomicConcept> unknownSubsumers) {
Graph<AtomicConcept> smallKnownSubsumptions=new Graph<AtomicConcept>();
for (AtomicConcept unknownSubsumer0 : unknownSubsumers) {
smallKnownSubsumptions.addEdge(m_bottomElement,unknownSubsumer0);
smallKnownSubsumptions.addEdge(unknownSubsumer0,m_topElement);
Set<AtomicConcept> knownSubsumersOfElement=getAllKnownSubsumers(unknownSubsumer0);
for (AtomicConcept unknownSubsumer1 : unknownSubsumers)
if (knownSubsumersOfElement.contains(unknownSubsumer1))
smallKnownSubsumptions.addEdge(unknownSubsumer0,unknownSubsumer1);
}
Set<AtomicConcept> unknownSubsumersWithTopBottom = new HashSet<AtomicConcept>(unknownSubsumers);
unknownSubsumersWithTopBottom.add(m_bottomElement);
unknownSubsumersWithTopBottom.add(m_topElement);
return buildTransitivelyReducedHierarchy(smallKnownSubsumptions,unknownSubsumersWithTopBottom);
}
protected double updateSubsumptionsUsingLeafNodeStrategy(double totalNumberOfTasks) {
double conceptsProcessed = 0;
Hierarchy<AtomicConcept> hierarchy=buildTransitivelyReducedHierarchy(m_knownSubsumptions,m_elements);
Stack<HierarchyNode<AtomicConcept>> toProcess=new Stack<HierarchyNode<AtomicConcept>>();
toProcess.addAll(hierarchy.getBottomNode().getParentNodes());
Set<HierarchyNode<AtomicConcept>> unsatHierarchyNodes=new HashSet<HierarchyNode<AtomicConcept>>();
while (!toProcess.empty()) {
HierarchyNode<AtomicConcept> currentHierarchyElement=toProcess.pop();
AtomicConcept currentHierarchyConcept=currentHierarchyElement.getRepresentative();
if (conceptsProcessed < Math.ceil(totalNumberOfTasks*0.85)) {
m_progressMonitor.elementClassified(currentHierarchyConcept);
conceptsProcessed++;
}
if (!conceptHasBeenProcessedAlready(currentHierarchyConcept)) {
Node rootNodeOfModel=buildModelForConcept(currentHierarchyConcept);
// If the leaf was unsatisfable we go up to explore its parents, until a satisfiable parent is discovered. Each time a node is unsat this information is propagated downwards.
if (rootNodeOfModel==null) {
makeConceptUnsatisfiable(currentHierarchyConcept);
unsatHierarchyNodes.add(currentHierarchyElement);
toProcess.addAll(currentHierarchyElement.getParentNodes());
Set<HierarchyNode<AtomicConcept>> visited=new HashSet<HierarchyNode<AtomicConcept>>();
Queue<HierarchyNode<AtomicConcept>> toVisit=new LinkedList<HierarchyNode<AtomicConcept>>(currentHierarchyElement.getChildNodes());
while (!toVisit.isEmpty()) {
HierarchyNode<AtomicConcept> current=toVisit.poll();
if (visited.add(current) && !unsatHierarchyNodes.contains(current)) {
toVisit.addAll(current.getChildNodes());
unsatHierarchyNodes.add(current);
makeConceptUnsatisfiable(current.getRepresentative());
toProcess.remove(current);
for (HierarchyNode<AtomicConcept> parentOfRemovedConcept : current.getParentNodes())
if (!conceptHasBeenProcessedAlready(parentOfRemovedConcept.getRepresentative()))
toProcess.add(parentOfRemovedConcept);
}
}
}
else {
// We cannot do rootNodeOfModel.getCanonicalNode() here. This is done
// in readKnownSubsumersFromRootNode(), but only if rootNodeOfModel
// has not been merged into another node, or if the merge was deterministic.
readKnownSubsumersFromRootNode(currentHierarchyConcept,rootNodeOfModel);
updatePossibleSubsumers();
}
}
}
return conceptsProcessed;
}
private boolean conceptHasBeenProcessedAlready(AtomicConcept atConcept) {
return !m_possibleSubsumptions.getSuccessors(atConcept).isEmpty() || isUnsatisfiable(atConcept);
}
protected Node buildModelForConcept(AtomicConcept concept) {
Individual freshIndividual=Individual.createAnonymous("fresh-individual");
Map<Individual,Node> checkedNode=new HashMap<Individual,Node>();
checkedNode.put(freshIndividual,null);
if (m_tableau.isSatisfiable(false,Collections.singleton(Atom.create(concept,freshIndividual)),null,null,null,checkedNode,getSatTestDescription(concept)))
return checkedNode.get(freshIndividual);
else
return null;
}
protected void makeConceptUnsatisfiable(AtomicConcept concept) {
addKnownSubsumption(concept,m_bottomElement);
m_possibleSubsumptions.getSuccessors(concept).clear();
}
protected boolean isUnsatisfiable(AtomicConcept concept) {
return m_knownSubsumptions.getSuccessors(concept).contains(m_bottomElement);
}
protected void readKnownSubsumersFromRootNode(AtomicConcept subconcept,Node checkedNode) {
if (checkedNode.getCanonicalNodeDependencySet().isEmpty()) {
checkedNode=checkedNode.getCanonicalNode();
ExtensionTable.Retrieval retrieval=m_tableau.getExtensionManager().getBinaryExtensionTable().createRetrieval(new boolean[] { false,true },ExtensionTable.View.TOTAL);
retrieval.getBindingsBuffer()[1]=checkedNode;
retrieval.open();
while (!retrieval.afterLast()) {
Object conceptObject=retrieval.getTupleBuffer()[0];
if (conceptObject instanceof AtomicConcept && retrieval.getDependencySet().isEmpty() && m_elements.contains(conceptObject))
addKnownSubsumption(subconcept,(AtomicConcept)conceptObject);
retrieval.next();
}
}
}
protected void updatePossibleSubsumers() {
ExtensionTable.Retrieval retrieval=m_tableau.getExtensionManager().getBinaryExtensionTable().createRetrieval(new boolean[] { false,false },ExtensionTable.View.TOTAL);
retrieval.open();
Object[] tupleBuffer=retrieval.getTupleBuffer();
while (!retrieval.afterLast()) {
Object conceptObject=tupleBuffer[0];
if (conceptObject instanceof AtomicConcept && m_elements.contains(conceptObject)) {
AtomicConcept atomicConcept=(AtomicConcept)conceptObject;
Node node=(Node)tupleBuffer[1];
if (node.isActive() && !node.isBlocked()) {
if (m_possibleSubsumptions.getSuccessors(atomicConcept).isEmpty())
readPossibleSubsumersFromNodeLabel(atomicConcept,node);
else
prunePossibleSubsumersOfConcept(atomicConcept,node);
}
}
retrieval.next();
}
}
protected void prunePossibleSubsumers() {
ExtensionTable.Retrieval retrieval=m_tableau.getExtensionManager().getBinaryExtensionTable().createRetrieval(new boolean[] { false,false },ExtensionTable.View.TOTAL);
retrieval.open();
Object[] tupleBuffer=retrieval.getTupleBuffer();
while (!retrieval.afterLast()) {
Object conceptObject=tupleBuffer[0];
if (conceptObject instanceof AtomicConcept && m_elements.contains(conceptObject)) {
Node node=(Node)tupleBuffer[1];
if (node.isActive() && !node.isBlocked())
prunePossibleSubsumersOfConcept((AtomicConcept)conceptObject,node);
}
retrieval.next();
}
}
protected void prunePossibleSubsumersOfConcept(AtomicConcept atomicConcept,Node node) {
Set<AtomicConcept> possibleSubsumersOfConcept=new HashSet<AtomicConcept>(m_possibleSubsumptions.getSuccessors(atomicConcept));
for (AtomicConcept atomicCon : possibleSubsumersOfConcept)
if (!m_tableau.getExtensionManager().containsConceptAssertion(atomicCon,node))
m_possibleSubsumptions.getSuccessors(atomicConcept).remove(atomicCon);
}
protected void readPossibleSubsumersFromNodeLabel(AtomicConcept atomicConcept,Node node) {
ExtensionTable.Retrieval retrieval=m_tableau.getExtensionManager().getBinaryExtensionTable().createRetrieval(new boolean[] { false,true },ExtensionTable.View.TOTAL);
retrieval.getBindingsBuffer()[1]=node;
retrieval.open();
while (!retrieval.afterLast()) {
Object concept=retrieval.getTupleBuffer()[0];
if (concept instanceof AtomicConcept && m_elements.contains(concept))
addPossibleSubsumption(atomicConcept,(AtomicConcept)concept);
retrieval.next();
}
}
protected Hierarchy<AtomicConcept> buildTransitivelyReducedHierarchy(Graph<AtomicConcept> knownSubsumptions,Set<AtomicConcept> elements) {
final Map<AtomicConcept,GraphNode<AtomicConcept>> allSubsumers=new HashMap<AtomicConcept,GraphNode<AtomicConcept>>();
for (AtomicConcept element : elements) {
Set<AtomicConcept> extendedSubs = new HashSet<AtomicConcept>(knownSubsumptions.getSuccessors(element));
extendedSubs.add(m_topElement);
extendedSubs.add(element);
allSubsumers.put(element,new GraphNode<AtomicConcept>(element,extendedSubs));
}
allSubsumers.put(m_bottomElement,new GraphNode<AtomicConcept>(m_bottomElement,elements));
return DeterministicClassification.buildHierarchy(m_topElement,m_bottomElement,allSubsumers);
}
protected void initialiseKnownSubsumptionsUsingToldSubsumers() {
initialiseKnownSubsumptionsUsingToldSubsumers(m_tableau.getPermanentDLOntology().getDLClauses());
}
protected void initialiseKnownSubsumptionsUsingToldSubsumers(Set<DLClause> dlClauses) {
for (DLClause dlClause : dlClauses) {
if (dlClause.getHeadLength()==1 && dlClause.getBodyLength()==1) {
DLPredicate headPredicate=dlClause.getHeadAtom(0).getDLPredicate();
DLPredicate bodyPredicate=dlClause.getBodyAtom(0).getDLPredicate();
if (headPredicate instanceof AtomicConcept && bodyPredicate instanceof AtomicConcept) {
AtomicConcept headConcept=(AtomicConcept)headPredicate;
AtomicConcept bodyConcept=(AtomicConcept)bodyPredicate;
if (m_elements.contains(headConcept) && m_elements.contains(bodyConcept))
addKnownSubsumption(bodyConcept,headConcept);
}
}
}
}
protected void checkUnknownSubsumersUsingEnhancedTraversal(Relation<AtomicConcept> hierarchyRelation,HierarchyNode<AtomicConcept> startNode,AtomicConcept pickedElement) {
Set<HierarchyNode<AtomicConcept>> startSearch=Collections.singleton(startNode);
Set<HierarchyNode<AtomicConcept>> visited=new HashSet<HierarchyNode<AtomicConcept>>(startSearch);
Queue<HierarchyNode<AtomicConcept>> toProcess=new LinkedList<HierarchyNode<AtomicConcept>>(startSearch);
while (!toProcess.isEmpty()) {
HierarchyNode<AtomicConcept> current=toProcess.remove();
Set<HierarchyNode<AtomicConcept>> subordinateElements=current.getChildNodes();
for (HierarchyNode<AtomicConcept> subordinateElement : subordinateElements) {
AtomicConcept element=subordinateElement.getRepresentative();
if (visited.contains(subordinateElement))
continue;
if (hierarchyRelation.doesSubsume(element,pickedElement)) {
addKnownSubsumption(pickedElement,element);
addKnownSubsumptions(pickedElement,subordinateElement.getEquivalentElements());
if (visited.add(subordinateElement))
toProcess.add(subordinateElement);
}
visited.add(subordinateElement);
}
}
}
protected boolean isEveryPossibleSubsumerNonSubsumer(Set<AtomicConcept> unknownPossibleSubsumers,AtomicConcept pickedElement,int lowerBound,int upperBound) {
if (unknownPossibleSubsumers.size()>lowerBound && unknownPossibleSubsumers.size()<upperBound) {
Individual freshIndividual=Individual.createAnonymous("fresh-individual");
Atom subconceptAssertion=Atom.create(pickedElement,freshIndividual);
Set<Atom> superconceptAssertions=new HashSet<Atom>();
Object[] superconcepts=new Object[unknownPossibleSubsumers.size()];
int index=0;
for (AtomicConcept unknownSupNode : unknownPossibleSubsumers) {
Atom atom = Atom.create(unknownSupNode,freshIndividual);
superconceptAssertions.add(atom);
superconcepts[index++]=atom.getDLPredicate();
}
Map<Individual,Node> checkedNode=new HashMap<Individual,Node>();
checkedNode.put(freshIndividual,null);
boolean isSubsumedBy=!m_tableau.isSatisfiable(false,Collections.singleton(subconceptAssertion),null,null,superconceptAssertions,checkedNode,getSubsumedByListTestDescription(pickedElement,superconcepts));
if (!isSubsumedBy)
prunePossibleSubsumers();
else {
readKnownSubsumersFromRootNode(pickedElement, checkedNode.get(freshIndividual));
m_possibleSubsumptions.getSuccessors(pickedElement).removeAll(getAllKnownSubsumers(pickedElement));
}
return !isSubsumedBy;
}
return false;
}
protected Set<AtomicConcept> getAllKnownSubsumers(AtomicConcept child) {
return m_knownSubsumptions.getReachableSuccessors(child);
}
protected void addKnownSubsumption(AtomicConcept subConcept,AtomicConcept superConcept) {
m_knownSubsumptions.addEdge(subConcept,superConcept);
}
protected void addKnownSubsumptions(AtomicConcept subConcept,Set<AtomicConcept> superConcepts) {
m_knownSubsumptions.addEdges(subConcept,superConcepts);
}
protected void addPossibleSubsumption(AtomicConcept subConcept,AtomicConcept superConcept) {
m_possibleSubsumptions.addEdge(subConcept,superConcept);
}
protected ReasoningTaskDescription getSatTestDescription(AtomicConcept atomicConcept) {
return ReasoningTaskDescription.isConceptSatisfiable(atomicConcept);
}
protected ReasoningTaskDescription getSubsumptionTestDescription(AtomicConcept subConcept,AtomicConcept superConcept) {
return ReasoningTaskDescription.isConceptSubsumedBy(subConcept,superConcept);
}
protected ReasoningTaskDescription getSubsumedByListTestDescription(AtomicConcept subConcept,Object[] superconcepts) {
return ReasoningTaskDescription.isConceptSubsumedByList(subConcept,superconcepts);
}
}