it.unife.ml.probowlapi.explanation.BundleGlassBoxExplanation 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;
/**
*
* @author riccardo
*/
//@Deprecated
public class BundleGlassBoxExplanation 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(BundleGlassBoxExplanation.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 BundleGlassBoxExplanation(OWLOntology ontology, PelletReasonerFactory factory) {
this(factory, factory.createReasoner(ontology));
}
public BundleGlassBoxExplanation(PelletReasoner reasoner) {
this(new PelletReasonerFactory(), reasoner);
}
public BundleGlassBoxExplanation(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());
// altReasoner = getReasonerFactory().createReasoner(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;
}
@Override
public Set getExplanation(OWLClassExpression unsatClass) {
Set result = null;
boolean firstExplanation = isFirstExplanation();
if (log.isLoggable(Level.FINE)) {
log.fine("Explain: " + unsatClass + " " + "First: " + firstExplanation);
}
if (firstExplanation) {
if (altReasoner != null) {
altReasoner.dispose();
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();
try {
pellet.getKB().prepare();
} catch (NullPointerException ex) {
log.warning("Prepare KB problem: " + ex.getLocalizedMessage());
pellet.dispose();
if (altReasonerEnabled) {
altReasoner = getReasonerFactory().createNonBufferingReasoner(getOntology());
pellet = altReasoner;
pellet.getKB().prepare();
} else {
throw ex;
}
}
// 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);
}
// it seems there are some problems with the incremental version
Set prunedExplanation = pruneExplanation(unsatClass, explanation, false);
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);
if (!defTracker.isDefined(unsatClass)) {
log.warning("Some of the entities in " + unsatClass
+ " are not defined in the explanation " + explanation);
// reasoner.dispose();
return Collections.emptySet();
}
// 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);
// PelletReasoner reasoner = getReasonerFactory().createReasoner(debuggingOntology);
if (isSatisfiable(reasoner, unsatClass, false)) {
log.warning("Explanation incomplete: Concept " + unsatClass
+ " is satisfiable in the explanation " + explanation);
reasoner.dispose();
return Collections.emptySet();
}
// 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);
// reasoner.flush();
}
}
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();
}
@Override
public void dispose() {
getOntologyManager().removeOntologyChangeListener(getDefinitionTracker());
if (altReasoner != null) {
altReasoner.dispose();
}
}
public String toString() {
return "BundleGlassBox";
}
}