it.unife.ml.probowlapi.explanation.BundleHSTExplanationGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of prob-owlapi Show documentation
Show all versions of prob-owlapi Show documentation
This package encapsulates OWL API adding tools for managing DISPONTE probabilistic semantics. Used by the reasoner BUNDLE.
The newest version!
/**
* This file is part of BUNDLE.
*
* BUNDLE is a probabilistic reasoner for OWL 2 ontologies.
*
* BUNDLE can be used both as module and as standalone.
*
* LEAP was implemented as a plugin of DL-Learner http://dl-learner.org,
* but some components can be used as stand-alone.
*
* BUNDLE and all its parts are 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
package it.unife.ml.probowlapi.explanation;
import com.clarkparsia.owlapi.explanation.MultipleExplanationGenerator;
import com.clarkparsia.owlapi.explanation.TransactionAwareSingleExpGen;
import com.clarkparsia.owlapi.explanation.util.ExplanationProgressMonitor;
import com.clarkparsia.owlapi.explanation.util.OntologyUtils;
import com.clarkparsia.owlapi.explanation.util.SilentExplanationProgressMonitor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
//import java.util.logging.Logger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLClassExpression;
import org.semanticweb.owlapi.model.OWLDeclarationAxiom;
import org.semanticweb.owlapi.model.OWLEntity;
import org.semanticweb.owlapi.model.OWLException;
import org.semanticweb.owlapi.model.OWLLogicalAxiom;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.semanticweb.owlapi.model.OWLRuntimeException;
import org.semanticweb.owlapi.model.RemoveAxiom;
import static org.semanticweb.owlapi.model.parameters.Imports.INCLUDED;
import org.semanticweb.owlapi.reasoner.OWLReasoner;
import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
import org.semanticweb.owlapi.util.CollectionFactory;
import org.semanticweb.owlapi.util.OWLAPIPreconditions;
import org.semanticweb.owlapi.util.OWLEntityCollector;
import static org.semanticweb.owlapi.util.OWLAPIPreconditions.checkNotNull;
/**
*
* @author Riccardo Zese , Giuseppe Cota
*
*/
public class BundleHSTExplanationGenerator implements MultipleExplanationGenerator {
/**
* The Constant log.
*/
private static final Logger LOGGER = LoggerFactory
.getLogger(BundleHSTExplanationGenerator.class.getName());
/**
* The single explanation generator.
*/
private final TransactionAwareSingleExpGen singleExplanationGenerator;
/**
* The progress monitor.
*/
private ExplanationProgressMonitor progressMonitor = new SilentExplanationProgressMonitor();
Map> annType;
public BundleHSTExplanationGenerator(TransactionAwareSingleExpGen singleExplanationGenerator, Map> probType) {
this.singleExplanationGenerator = singleExplanationGenerator;
annType = probType;
}
public BundleHSTExplanationGenerator(TransactionAwareSingleExpGen singleExplanationGenerator) {
this.singleExplanationGenerator = singleExplanationGenerator;
annType = new HashMap>();
}
@Override
public void setProgressMonitor(ExplanationProgressMonitor progressMonitor) {
this.progressMonitor = checkNotNull(progressMonitor,
"progressMonitor cannot be null");
}
@Override
public OWLOntologyManager getOntologyManager() {
return singleExplanationGenerator.getOntologyManager();
}
@Override
public OWLOntology getOntology() {
return singleExplanationGenerator.getOntology();
}
@Override
public OWLReasoner getReasoner() {
return singleExplanationGenerator.getReasoner();
}
@Override
public OWLReasonerFactory getReasonerFactory() {
return singleExplanationGenerator.getReasonerFactory();
}
/**
* Gets the single explanation generator.
*
* @return the explanation generator
*/
@Nonnull
public TransactionAwareSingleExpGen getSingleExplanationGenerator() {
return singleExplanationGenerator;
}
@Override
public Set getExplanation(OWLClassExpression unsatClass) {
return singleExplanationGenerator.getExplanation(unsatClass);
}
@Override
public Set> getExplanations(OWLClassExpression unsatClass) {
return getExplanations(unsatClass, 0);
}
@Override
public void dispose() {
singleExplanationGenerator.dispose();
}
@Override
public Set> getExplanations(OWLClassExpression unsatClass,
@Nonnegative int maxExplanations) {
OWLAPIPreconditions.checkNotNegative(maxExplanations,
"max explanations cannot be negative");
Object max = maxExplanations == 0 ? "all" : maxExplanations;
// log.fine("Get " + max + " explanation(s) for: " + unsatClass);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Get {} explanation(s) for: {}", max, unsatClass);
}
try {
Set firstMups = getExplanation(unsatClass);
if (firstMups.isEmpty()) {
return CollectionFactory.emptySet();
}
Set> allMups = new LinkedHashSet<>();
progressMonitor.foundExplanation(firstMups);
allMups.add(firstMups);
Set> satPaths = new HashSet<>();
Set currentPathContents = new HashSet<>();
singleExplanationGenerator.beginTransaction();
try {
constructHittingSetTree(unsatClass, firstMups, allMups,
satPaths, currentPathContents, maxExplanations);
} finally {
singleExplanationGenerator.endTransaction();
}
progressMonitor.foundAllExplanations();
return allMups;
} catch (OWLException e) {
throw new OWLRuntimeException(e);
}
}
// Hitting Set Stuff
/**
* Orders the axioms in a single MUPS by the frequency of which they appear in all MUPS.
*
* @param mups The MUPS containing the axioms to be ordered
* @param allMups The set of all MUPS which is used to calculate the ordering
* @return the ordered mups
*/
@Nonnull
private static List getOrderedMUPS(@Nonnull List mups,
@Nonnull final Set> allMups) {
Comparator mupsComparator = new Comparator() {
@Override
public int compare(OWLAxiom o1, OWLAxiom o2) {
// The axiom that appears in most MUPS has the lowest index
// in the list
assert o1 != null;
assert o2 != null;
int occ1 = getOccurrences(o1, allMups);
int occ2 = getOccurrences(o2, allMups);
return -occ1 + occ2;
}
};
Collections.sort(mups, mupsComparator);
return mups;
}
/**
* Given an axiom and a set of axioms this method determines how many sets contain the axiom.
*
* @param ax The axiom that will be counted.
* @param axiomSets The sets to count from
* @return the occurrences
*/
protected static int getOccurrences(@Nonnull OWLAxiom ax,
@Nonnull Set> axiomSets) {
int count = 0;
for (Set axioms : axiomSets) {
if (axioms.contains(ax)) {
count++;
}
}
return count;
}
/**
* Returns the entities referenced in an axiom.
*
* @param axiom axiom whose signature is being computed
* @return the entities referenced in the axiom
*/
@Nonnull
private static Set getSignature(@Nonnull OWLAxiom axiom) {
Set toReturn = new HashSet<>();
OWLEntityCollector collector = new OWLEntityCollector(toReturn);
axiom.accept(collector);
return toReturn;
}
/**
* This is a recursive method that builds a hitting set tree to obtain all justifications for an
* unsatisfiable class.
*
* @param unsatClass the unsat class
* @param mups The current justification for the current class. This corresponds to a node in
* the hitting set tree.
* @param allMups All of the MUPS that have been found - this set gets populated over the course
* of the tree building process. Initially this should just contain the first justification
* @param satPaths Paths that have been completed.
* @param currentPathContents The contents of the current path. Initially this should be an
* empty set.
* @param maxExplanations the max explanations
* @throws OWLException the oWL exception
*/
private void constructHittingSetTree(
@Nonnull OWLClassExpression unsatClass,
@Nonnull Set mups, @Nonnull Set> allMups,
@Nonnull Set> satPaths,
@Nonnull Set currentPathContents, int maxExplanations)
throws OWLException {
// LOGGER.fine("MUPS " + allMups.size() + ": " + mups);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("MUPS {}: {}", allMups.size(), mups);
}
if (progressMonitor.isCancelled()) {
return;
}
// We go through the current mups, axiom by axiom, and extend the tree
// with edges for each axiom
List orderedMups = getOrderedMUPS(new ArrayList<>(mups),
allMups);
while (!orderedMups.isEmpty()) {
if (progressMonitor.isCancelled()) {
return;
}
OWLAxiom axiom = orderedMups.get(0);
assert axiom != null;
orderedMups.remove(0);
// get annotated axiom (the annotation contains the probability)
OWLAxiom axiomToRestore = axiom;
// boolean found = false;
// for (OWLOntology ont : getReasoner().getRootOntology().getImportsClosure()) {
// Set ontologyAxioms = ont.getAxiomsIgnoreAnnotations(axiom);
Set ontologyAxioms = getReasoner().getRootOntology()
.getAxiomsIgnoreAnnotations(axiom, INCLUDED);
for (OWLAxiom annotatedAxiom : ontologyAxioms) {
if (annotatedAxiom.equalsIgnoreAnnotations(axiom)) {
axiomToRestore = annotatedAxiom;
// found = true;
break;
}
}
// if (found) {
// break;
// }
// }
if (allMups.size() == maxExplanations) {
LOGGER.trace("Computed {} explanations", maxExplanations);
return;
}
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Removing axiom: {} {} more removed: {}", axiomToRestore,
currentPathContents.size(), currentPathContents);
}
// Removal may have dereferenced some entities, if so declarations
// are added
List temporaryDeclarations = new ArrayList<>();
Set ontologies = removeAxiomAndAddDeclarations(axiomToRestore,
temporaryDeclarations);
// getReasoner().flush();
currentPathContents.add(axiom);
boolean earlyTermination = checkEarlyTermination(satPaths,
currentPathContents);
if (!earlyTermination) {
orderedMups = recurse(unsatClass, allMups, satPaths,
currentPathContents, maxExplanations, orderedMups,
axiom);
}
backtrack(currentPathContents, axiom, axiomToRestore, temporaryDeclarations,
ontologies);
}
}
/**
* Check early termination.
*
* @param satPaths the sat paths
* @param currentPathContents the current path contents
* @return true, if successful
*/
private static boolean checkEarlyTermination(
@Nonnull Set> satPaths,
@Nonnull Set currentPathContents) {
boolean earlyTermination = false;
// Early path termination. If our path contents are the superset of
// the contents of a path then we can terminate here.
for (Set satPath : satPaths) {
if (currentPathContents.containsAll(satPath)) {
earlyTermination = true;
// LOGGER.fine("Stop - satisfiable (early termination)");
LOGGER.trace("Stop - satisfiable (early termination)");
break;
}
}
return earlyTermination;
}
/**
* Recurse.
*
* @param unsatClass the unsat class
* @param allMups the all mups
* @param satPaths the sat paths
* @param currentPathContents the current path contents
* @param maxExplanations the max explanations
* @param orderedMups the ordered mups
* @param axiom the axiom
* @return the list
* @throws OWLException the oWL exception
*/
@Nonnull
private List recurse(@Nonnull OWLClassExpression unsatClass,
@Nonnull Set> allMups,
@Nonnull Set> satPaths,
@Nonnull Set currentPathContents, int maxExplanations,
@Nonnull List orderedMups, @Nonnull OWLAxiom axiom)
throws OWLException {
Set newMUPS = getNewMUPS(unsatClass, allMups,
currentPathContents);
// Generate a new node - i.e. a new justification set
if (newMUPS.contains(axiom)) {
// How can this be the case???
throw new OWLRuntimeException(
"Explanation contains removed axiom: " + axiom);
}
if (!newMUPS.isEmpty()) {
// Note that getting a previous justification does not mean
// we can stop. stopping here causes some justifications to
// be missed
allMups.add(newMUPS);
progressMonitor.foundExplanation(newMUPS);
// Recompute priority here?
constructHittingSetTree(unsatClass, newMUPS, allMups, satPaths,
currentPathContents, maxExplanations);
// We have found a new MUPS, so recalculate the ordering
// axioms in the MUPS at the current level
return getOrderedMUPS(orderedMups, allMups);
} else {
// LOGGER.fine("Stop - satisfiable");
LOGGER.trace("Stop - satisfiable");
// End of current path - add it to the list of paths
satPaths.add(new HashSet<>(currentPathContents));
}
return orderedMups;
}
private void backtrack(@Nonnull Set currentPathContents,
@Nonnull OWLAxiom axiomWithoutAnnotation,
@Nonnull OWLAxiom annotatedAxiom,
@Nonnull List temporaryDeclarations,
@Nonnull Set ontologies) {
// Back track - go one level up the tree and run for the next axiom
currentPathContents.remove(axiomWithoutAnnotation);
// LOGGER.fine("Restoring axiom: " + axiom);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Restoring axiom: {}", axiomWithoutAnnotation);
}
// Remove any temporary declarations
for (OWLDeclarationAxiom decl : temporaryDeclarations) {
assert decl != null;
OntologyUtils.removeAxiom(decl, getReasoner().getRootOntology()
.getImportsClosure(), getOntologyManager());
}
// Done with the axiom that was removed. Add it back in
OntologyUtils.addAxiom(annotatedAxiom, ontologies, getOntologyManager());
// getReasoner().flush();
}
/**
* Gets the new mups.
*
* @param unsatClass the unsat class
* @param allMups the all mups
* @param currentPathContents the current path contents
* @return the new mups
*/
@Nonnull
private Set getNewMUPS(@Nonnull OWLClassExpression unsatClass,
@Nonnull Set> allMups,
@Nonnull Set currentPathContents) {
Set newMUPS = null;
for (Set foundMUPS : allMups) {
Set foundMUPSCopy = new HashSet<>(foundMUPS);
foundMUPSCopy.retainAll(currentPathContents);
if (foundMUPSCopy.isEmpty()) {
newMUPS = foundMUPS;
break;
}
}
if (newMUPS == null) {
newMUPS = getExplanation(unsatClass);
}
return newMUPS;
}
/**
* Removes the axiom and add declarations.
*
* @param axiom the axiom
* @param temporaryDeclarations the temporary declarations
* @return the sets the
*/
@Nonnull
private Set removeAxiomAndAddDeclarations(
@Nonnull OWLAxiom axiom,
@Nonnull List temporaryDeclarations) {
// Remove the current axiom from all the ontologies it is included
// in
// Set ontologies = removeAxiom(axiom,
// getReasoner().getRootOntology().getImportsClosure(),
// getOntologyManager());
Set ontologies = OntologyUtils.removeAxiom(axiom,
getReasoner().getRootOntology().getImportsClosure(),
getOntologyManager());
collectTemporaryDeclarations(axiom, temporaryDeclarations);
for (OWLDeclarationAxiom decl : temporaryDeclarations) {
assert decl != null;
OntologyUtils.addAxiom(decl, getReasoner().getRootOntology()
.getImportsClosure(), getOntologyManager());
}
return ontologies;
}
private Set removeAxiom(OWLAxiom axiom,
Set ontologies, OWLOntologyManager manager) {
Set modifiedOnts = new HashSet<>();
for (OWLOntology ont : ontologies) {
Set axioms = ont.getAxioms();
for (OWLAxiom axiomit : axioms) {
boolean cond = axiomit.equalsIgnoreAnnotations(axiom);
// if (axiom.isOfType(AxiomType.EQUIVALENT_CLASSES)
// && axiomit.isOfType(AxiomType.EQUIVALENT_CLASSES)) {
// Set classExpAxiom = ((OWLEquivalentClassesAxiom) axiom).getClassExpressions();
// Set classExpAxiomIt = ((OWLEquivalentClassesAxiom) axiomit).getClassExpressions();
// cond = cond || classExpAxiom.equals(classExpAxiomIt);
// }
if (cond) {
modifiedOnts.add(ont);
manager.applyChange(new RemoveAxiom(ont, axiomit));
break;
}
}
}
return modifiedOnts;
}
private void collectTemporaryDeclarations(@Nonnull OWLAxiom axiom,
@Nonnull List temporaryDeclarations) {
for (OWLEntity e : getSignature(axiom)) {
assert e != null;
boolean referenced = getReasoner().getRootOntology().isDeclared(e,
INCLUDED);
if (!referenced) {
temporaryDeclarations.add(getDeclaration(e));
}
}
}
/**
* Gets the declaration.
*
* @param e the e
* @return the declaration
*/
@Nonnull
private OWLDeclarationAxiom getDeclaration(@Nonnull OWLEntity e) {
return getOntologyManager().getOWLDataFactory().getOWLDeclarationAxiom(
e);
}
}