it.unife.ml.probowlapi.explanation.GlassBoxExplanation Maven / Gradle / Ivy
Show all versions of prob-owlapi Show documentation
/**
* 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 java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.mindswap.pellet.PelletOptions;
import org.mindswap.pellet.utils.Pair;
import org.mindswap.pellet.utils.SetUtils;
import org.mindswap.pellet.utils.TaxonomyUtils;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLClassExpression;
import org.semanticweb.owlapi.model.OWLObjectComplementOf;
import org.semanticweb.owlapi.model.OWLObjectIntersectionOf;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyChangeException;
import org.semanticweb.owlapi.model.OWLRuntimeException;
import aterm.ATermAppl;
import com.clarkparsia.owlapi.explanation.SingleExplanationGeneratorImpl;
import com.clarkparsia.owlapi.explanation.util.DefinitionTracker;
import com.clarkparsia.owlapiv3.OWL;
import com.clarkparsia.owlapiv3.OntologyUtils;
import com.clarkparsia.pellet.owlapiv3.AxiomConverter;
import com.clarkparsia.pellet.owlapiv3.PelletReasoner;
import com.clarkparsia.pellet.owlapiv3.PelletReasonerFactory;
/**
*
* Title: GlassBoxExplanation
*
*
* Description: Implementation of SingleExplanationGenerator interface using the
* axiom tracing facilities of Pellet.
*
*
* Copyright: Copyright (c) 2007
*
*
* Company: Clark & Parsia, LLC.
*
*
* @author Evren Sirin
*/
public class GlassBoxExplanation extends SingleExplanationGeneratorImpl {
static {
setup();
}
/**
* Very important initialization step that needs to be called once before a
* reasoner is created. this function will be called automatically when
* GlassBoxExplanation is loaded by the class loader.
*/
public static void setup() {
// initialize PelletOptions to required values for explanations
// to work before any Pellet reasoner instance is created
PelletOptions.USE_TRACING = true;
}
public static final Logger log = Logger
.getLogger(GlassBoxExplanation.class.getName());
/**
* Alternative reasoner. We use a second reasoner because we do not want to
* lose the state in the original reasoner.
*/
private PelletReasoner altReasoner = null;
private boolean altReasonerEnabled = false;
private AxiomConverter axiomConverter;
public GlassBoxExplanation(OWLOntology ontology, PelletReasonerFactory factory) {
this(factory, factory.createReasoner(ontology));
}
public GlassBoxExplanation(PelletReasoner reasoner) {
this(new PelletReasonerFactory(), reasoner);
}
public GlassBoxExplanation(PelletReasonerFactory factory, PelletReasoner reasoner) {
super(reasoner.getRootOntology(), factory, reasoner);
axiomConverter = new AxiomConverter(reasoner);
}
private void setAltReasonerEnabled(boolean enabled) {
if (enabled) {
if (altReasoner == null) {
log.fine("Create alt reasoner");
altReasoner = getReasonerFactory().createNonBufferingReasoner(getOntology());
}
}
altReasonerEnabled = enabled;
}
private OWLClass getNegation(OWLClassExpression desc) {
if (!(desc instanceof OWLObjectComplementOf)) {
return null;
}
OWLClassExpression not = ((OWLObjectComplementOf) desc).getOperand();
if (not.isAnonymous()) {
return null;
}
return (OWLClass) not;
}
private Pair getSubClassAxiom(OWLClassExpression desc) {
if (!(desc instanceof OWLObjectIntersectionOf)) {
return null;
}
OWLObjectIntersectionOf conj = (OWLObjectIntersectionOf) desc;
if (conj.getOperands().size() != 2) {
return null;
}
Iterator conjuncts = conj.getOperands().iterator();
OWLClassExpression c1 = conjuncts.next();
OWLClassExpression c2 = conjuncts.next();
OWLClass sub = null;
OWLClass sup = null;
if (!c1.isAnonymous()) {
sub = (OWLClass) c1;
sup = getNegation(c2);
} else if (!c2.isAnonymous()) {
sub = (OWLClass) c2;
sup = getNegation(c2);
}
if (sup == null) {
return null;
}
return new Pair(sub, sup);
}
private Set getCachedExplanation(OWLClassExpression unsatClass) {
PelletReasoner pellet = getReasoner();
if (!pellet.getKB().isClassified()) {
return null;
}
Pair pair = getSubClassAxiom(unsatClass);
if (pair != null) {
Set> exps = TaxonomyUtils.getSuperExplanations(
pellet.getKB().getTaxonomy(),
pellet.term(pair.first),
pellet.term(pair.second));
if (exps != null) {
Set result = convertExplanation(exps.iterator().next());
if (log.isLoggable(Level.FINE)) {
log.fine("Cached explanation: " + result);
}
return result;
}
}
return null;
}
public Set getExplanation(OWLClassExpression unsatClass) {
Set result = null;
boolean firstExplanation = isFirstExplanation();
if (log.isLoggable(Level.FINE)) {
log.fine("Explain: " + unsatClass + " " + "First: " + firstExplanation);
}
if (firstExplanation) {
altReasoner = null;
result = getCachedExplanation(unsatClass);
if (result == null) {
result = getPelletExplanation(unsatClass);
}
} else {
setAltReasonerEnabled(true);
try {
result = getPelletExplanation(unsatClass);
} catch (RuntimeException e) {
log.log(Level.SEVERE, "Unexpected error while trying to get explanation set from Pellet", e);
throw new OWLRuntimeException(e);
} finally {
setAltReasonerEnabled(false);
}
}
return result;
}
private Set getPelletExplanation(OWLClassExpression unsatClass) {
PelletReasoner pellet = getReasoner();
pellet.getKB().prepare();
// satisfiable if there is an undefined entity
boolean sat = !getDefinitionTracker().isDefined(unsatClass);
if (!sat) {
sat = isSatisfiable(pellet, unsatClass, true);
} else if (log.isLoggable(Level.FINE)) {
log.fine("Undefined entity in " + unsatClass);
}
if (sat) {
return Collections.emptySet();
} else {
Set explanation = convertExplanation(pellet.getKB().getExplanationSet());
if (log.isLoggable(Level.FINE)) {
log.fine("Explanation " + explanation);
}
Set prunedExplanation = pruneExplanation(unsatClass, explanation, true);
int prunedAxiomCount = explanation.size() - prunedExplanation.size();
if (log.isLoggable(Level.FINE) && prunedAxiomCount > 0) {
log.fine("Pruned " + prunedAxiomCount + " axioms from the explanation: "
+ SetUtils.difference(explanation, prunedExplanation));
log.fine("New explanation " + prunedExplanation);
}
return prunedExplanation;
}
}
private boolean isSatisfiable(PelletReasoner pellet, OWLClassExpression unsatClass, boolean doExplanation) {
pellet.getKB().setDoExplanation(doExplanation);
boolean sat = unsatClass.isOWLThing()
? pellet.isConsistent()
: pellet.isSatisfiable(unsatClass);
pellet.getKB().setDoExplanation(false);
return sat;
}
private Set convertExplanation(Set explanation) {
if (explanation == null || explanation.isEmpty()) {
throw new OWLRuntimeException("No explanation computed");
}
Set result = new HashSet();
for (ATermAppl term : explanation) {
OWLAxiom axiom = axiomConverter.convert(term);
if (axiom == null) {
throw new OWLRuntimeException("Cannot convert: " + term);
}
result.add(axiom);
}
return result;
}
/**
*
* Prunes the given explanation using slow pruning technique of BlackBox
* explanation. The explanation returned from Pellet axiom tracing is not
* guaranteed to be minimal so pruning is necessary to ensure minimality.
* The idea is to create an ontology with only the axioms in the
* explanation, remove an axiom, test satisfiability, and restore the axiom
* if the class turns to be satisfiable after the removal.
*
*
* There are two different pruning techniques. Incremental pruning attaches
* the reasoner as a listener and updates the reasoner with axiom
* removals/restores. Non-incremental pruning clears the reasoner at each
* iteration and reloads the axioms from scratch each time. Incremental
* pruning is faster but may return incorrect answers since axiom updates
* are less robust.
*/
private Set pruneExplanation(OWLClassExpression unsatClass, Set explanation, boolean incremental) {
try {
// initialize pruned explanation to be same as the given explanation
Set prunedExplanation = new HashSet(explanation);
// we can only prune if there is more than one axiom in the
// explanation
if (prunedExplanation.size() <= 1) {
return prunedExplanation;
}
// create an ontology from the explanation axioms
OWLOntology debuggingOntology = OWL.Ontology(explanation);
DefinitionTracker defTracker = new DefinitionTracker(debuggingOntology);
// since explanation size is generally small we can create and use a
// completely new reasoner rather than destroying the state on already
// existing reasoner
PelletReasoner reasoner = getReasonerFactory().createNonBufferingReasoner(debuggingOntology);
if (!defTracker.isDefined(unsatClass)) {
log.warning("Some of the entities in " + unsatClass
+ " are not defined in the explanation " + explanation);
}
if (isSatisfiable(reasoner, unsatClass, true)) {
log.warning("Explanation incomplete: Concept " + unsatClass
+ " is satisfiable in the explanation " + explanation);
}
// simply remove axioms one at a time. If the unsatClass turns
// satisfiable then we know that axiom cannot be a part of minimal
// explanation
for (OWLAxiom axiom : explanation) {
if (log.isLoggable(Level.FINER)) {
log.finer("Try pruning " + axiom);
}
if (!incremental) {
reasoner.dispose();
}
OntologyUtils.removeAxioms(debuggingOntology, axiom);
if (!incremental) {
reasoner = getReasonerFactory().createNonBufferingReasoner(debuggingOntology);
}
reasoner.getKB().prepare();
if (defTracker.isDefined(unsatClass)
&& !isSatisfiable(reasoner, unsatClass, false)) {
// does not affect satisfiability so remove from the results
prunedExplanation.remove(axiom);
if (log.isLoggable(Level.FINER)) {
log.finer("Pruned " + axiom);
}
} else {
// affects satisfiability so add back to the ontology
OntologyUtils.addAxioms(debuggingOntology, axiom);
}
}
if (incremental) {
// remove the listener and the ontology to avoid memory leaks
reasoner.dispose();
}
OWL.manager.removeOntology(debuggingOntology);
OWL.manager.removeOntologyChangeListener(defTracker);
return prunedExplanation;
} catch (OWLOntologyChangeException e) {
throw new OWLRuntimeException(e);
}
}
@Override
public PelletReasoner getReasoner() {
return altReasonerEnabled ? altReasoner : (PelletReasoner) super.getReasoner();
}
@Override
public PelletReasonerFactory getReasonerFactory() {
return (PelletReasonerFactory) super.getReasonerFactory();
}
public void dispose() {
getOntologyManager().removeOntologyChangeListener(getDefinitionTracker());
if (altReasoner != null) {
altReasoner.dispose();
}
}
public String toString() {
return "GlassBox";
}
}