com.clarkparsia.owlapi.explanation.GlassBoxExplanation Maven / Gradle / Ivy
// Copyright (c) 2006 - 2008, Clark & Parsia, LLC.
// This source code is available under the terms of the Affero General Public License v3.
//
// Please see LICENSE.txt for full license terms, including the availability of proprietary exceptions.
// Questions, comments, or requests for clarification: [email protected]
package com.clarkparsia.owlapi.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.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";
}
}