Skip to content

Commit

Permalink
Throws exceptions if ontology logically incoherent, fixes #40
Browse files Browse the repository at this point in the history
Includes incoherent OPs in logical tests, fixes #104

Note these two tickets are coupled, so were tackled together.

To handle validation checks a new ReasonerHelper class was created.

This implements

 - inconsistency
 - incoherent TBox (ie unsat classes)
 - incoherent RBox (using probe classes)

Exceptions are thrown on encountering any of these.

Currently there is no way to allow the user to ignore exceptions;
this could in theory be added but would likely be a very bady idea
  • Loading branch information
cmungall committed Jan 6, 2017
1 parent f020cd7 commit 1c18e29
Show file tree
Hide file tree
Showing 13 changed files with 359 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

import org.geneontology.reasoner.ExpressionMaterializingReasoner;
import org.geneontology.reasoner.ExpressionMaterializingReasonerFactory;
import org.obolibrary.robot.exceptions.IncoherentRBoxException;
import org.obolibrary.robot.exceptions.IncoherentTBoxException;
import org.obolibrary.robot.exceptions.InconsistentOntologyException;
import org.obolibrary.robot.exceptions.OntologyLogicException;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.model.AxiomType;
import org.semanticweb.owlapi.model.IRI;
Expand Down Expand Up @@ -71,10 +75,11 @@ public static Map<String, String> getDefaultOptions() {
* @param options
* A map of options for the operation
* @throws OWLOntologyCreationException
* @throws OntologyLogicException
*/
public static void materialize(OWLOntology ontology,
OWLReasonerFactory reasonerFactory,
Set<OWLObjectProperty> properties, Map<String, String> options) throws OWLOntologyCreationException {
Set<OWLObjectProperty> properties, Map<String, String> options) throws OWLOntologyCreationException, OntologyLogicException {

// TODO: make reasonOverImportsClosure optional rather than always true
materialize(ontology, reasonerFactory, properties, options, true);
Expand All @@ -95,12 +100,13 @@ public static void materialize(OWLOntology ontology,
* @param reasonOverImportsClosure
* if true will first perform materialization over all ontologies in the import closure
* @throws OWLOntologyCreationException
* @throws OntologyLogicException
*/
public static void materialize(OWLOntology ontology,
OWLReasonerFactory reasonerFactory,
Set<OWLObjectProperty> properties,
Map<String, String> options,
boolean reasonOverImportsClosure) throws OWLOntologyCreationException {
boolean reasonOverImportsClosure) throws OntologyLogicException, OWLOntologyCreationException {

if (reasonOverImportsClosure) {
logger.info("Materializing imported ontologies...");
Expand All @@ -123,11 +129,8 @@ public static void materialize(OWLOntology ontology,
ExpressionMaterializingReasonerFactory merf = new ExpressionMaterializingReasonerFactory(
reasonerFactory);
ExpressionMaterializingReasoner emr = merf.createReasoner(ontology);
if (!emr.isConsistent()) {
logger.error("Ontology is not consistent!");
return;
}

ReasonerHelper.validate(emr);

startTime = System.currentTimeMillis();

Set<OWLAxiom> newAxioms = new HashSet<>();
Expand Down
31 changes: 10 additions & 21 deletions robot-core/src/main/java/org/obolibrary/robot/ReasonOperation.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
import java.util.stream.Collectors;

import org.geneontology.reasoner.ExpressionMaterializingReasoner;
import org.obolibrary.robot.exceptions.IncoherentRBoxException;
import org.obolibrary.robot.exceptions.IncoherentTBoxException;
import org.obolibrary.robot.exceptions.InconsistentOntologyException;
import org.obolibrary.robot.exceptions.OntologyLogicException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.semanticweb.owlapi.apibinding.OWLManager;
Expand Down Expand Up @@ -74,10 +78,11 @@ public static Map<String, String> getDefaultOptions() {
* @param ontology the ontology to reason over
* @param reasonerFactory the factory to create a reasoner instance from
* @throws OWLOntologyCreationException on ontology problem
* @throws OntologyLogicException on inconsistency or incoherency
*/
public static void reason(OWLOntology ontology,
OWLReasonerFactory reasonerFactory)
throws OWLOntologyCreationException {
throws OWLOntologyCreationException, OntologyLogicException {
reason(ontology, reasonerFactory, getDefaultOptions());
}

Expand All @@ -90,10 +95,11 @@ public static void reason(OWLOntology ontology,
* @param reasonerFactory the factory to create a reasoner instance from
* @param options a map of option strings, or null
* @throws OWLOntologyCreationException on ontology problem
* @throws OntologyLogicException
*/
public static void reason(OWLOntology ontology,
OWLReasonerFactory reasonerFactory,
Map<String, String> options) throws OWLOntologyCreationException {
Map<String, String> options) throws OWLOntologyCreationException, OntologyLogicException {
logger.info("Ontology has {} axioms.", ontology.getAxioms().size());

logger.info("Fetching labels...");
Expand All @@ -110,28 +116,11 @@ public static void reason(OWLOntology ontology,

logger.info("Starting reasoning...");
OWLReasoner reasoner = reasonerFactory.createReasoner(ontology);
logger.info("Testing for consistency...");
if (!reasoner.isConsistent()) {
logger.info("Ontology is not consistent!");
return;
}

ReasonerHelper.validate(reasoner);

logger.info("Precomputing class hierarchy...");
reasoner.precomputeInferences(InferenceType.CLASS_HIERARCHY);

logger.info("Checking for unsatisfiable classes...");
Node<OWLClass> unsatisfiableClasses =
reasoner.getUnsatisfiableClasses();
if (unsatisfiableClasses.getSize() > 1) {
logger.info("There are {} unsatisfiable classes in the ontology.",
unsatisfiableClasses.getSize());
for (OWLClass cls : unsatisfiableClasses) {
if (!cls.isOWLNothing()) {
logger.info(" unsatisfiable: " + cls.getIRI());
}
}
}

boolean isEquivalentsAllowed = OptionsHelper.optionIsTrue(options, "equivalent-classes-allowed");
int nEquivs = 0;
logger.info("Finding equivalencies...");
Expand Down
106 changes: 106 additions & 0 deletions robot-core/src/main/java/org/obolibrary/robot/ReasonerHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package org.obolibrary.robot;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import org.obolibrary.robot.exceptions.IncoherentTBoxException;
import org.obolibrary.robot.exceptions.IncoherentRBoxException;
import org.obolibrary.robot.exceptions.InconsistentOntologyException;
import org.semanticweb.owlapi.model.IRI;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLDataFactory;
import org.semanticweb.owlapi.model.OWLObjectProperty;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyManager;
import org.semanticweb.owlapi.model.parameters.Imports;
import org.semanticweb.owlapi.reasoner.InferenceType;
import org.semanticweb.owlapi.reasoner.Node;
import org.semanticweb.owlapi.reasoner.OWLReasoner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
*
* Provides convenience methods for working with OWL reasoning.
*
* @author cjm
*
*/
public class ReasonerHelper {

/**
* Logger.
*/
private static final Logger logger =
LoggerFactory.getLogger(ReasonerHelper.class);


public static void validate(OWLReasoner reasoner) throws IncoherentTBoxException, InconsistentOntologyException, IncoherentRBoxException {

OWLOntology ont = reasoner.getRootOntology();
OWLOntologyManager manager = ont.getOWLOntologyManager();
OWLDataFactory dataFactory = manager.getOWLDataFactory();
OWLClass nothing = dataFactory.getOWLNothing();
OWLClass thing = dataFactory.getOWLThing();
logger.info("Checking for inconsistencies");
if (!reasoner.isConsistent()) {
throw new InconsistentOntologyException();
}

logger.info("Checking for unsatisfiable classes...");
Set<OWLClass> unsatisfiableClasses =
reasoner.getUnsatisfiableClasses().getEntitiesMinus(nothing);
if (unsatisfiableClasses.size() > 0) {
logger.error("There are {} unsatisfiable classes in the ontology.",
unsatisfiableClasses.size());
for (OWLClass cls : unsatisfiableClasses) {
logger.error(" unsatisfiable: " + cls.getIRI());
}
throw new IncoherentTBoxException(unsatisfiableClasses);
}

logger.info("Checking for unsatisfiable object properties...");

Set<OWLAxiom>tempAxioms = new HashSet<>();
Map<OWLClass, OWLObjectProperty> probeFor = new HashMap<>();
for (OWLObjectProperty p : ont.getObjectPropertiesInSignature(Imports.INCLUDED)) {
UUID uuid = UUID.randomUUID();
IRI probeIRI = IRI.create(p.getIRI().toString() + "-" + uuid.toString());
OWLClass probe = dataFactory.getOWLClass(probeIRI);
probeFor.put(probe, p);
tempAxioms.add(dataFactory.getOWLDeclarationAxiom(probe));
tempAxioms.add(dataFactory.getOWLSubClassOfAxiom(probe,
dataFactory.getOWLObjectSomeValuesFrom(p, thing)));
}
manager.addAxioms(ont, tempAxioms);
reasoner.flush();

Set<OWLClass> unsatisfiableProbeClasses =
reasoner.getUnsatisfiableClasses().getEntitiesMinus(nothing);

// leave no trace
manager.removeAxioms(ont, tempAxioms);
reasoner.flush();

if (unsatisfiableProbeClasses.size() > 0) {
logger.error("There are {} unsatisfiable properties in the ontology.",
unsatisfiableProbeClasses.size());
Set<OWLObjectProperty> unsatPs = new HashSet<>();
for (OWLClass cls : unsatisfiableProbeClasses) {
OWLObjectProperty unsatP = probeFor.get(cls);
unsatPs.add(unsatP);
logger.error(" unsatisfiable property: " + unsatP.getIRI());
}
throw new IncoherentRBoxException(unsatPs);
}


}



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.obolibrary.robot.exceptions;

import java.util.Set;

import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLEntity;
import org.semanticweb.owlapi.model.OWLObject;
import org.semanticweb.owlapi.model.OWLObjectProperty;

/**
* Ontology contains unsatisfiable properties
*
* @author cjm
*
*/
public class IncoherentRBoxException extends OntologyLogicException {

public IncoherentRBoxException(Set<OWLObjectProperty> unsatisfiableProperties) {
// TODO Auto-generated constructor stub
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.obolibrary.robot.exceptions;

import java.util.Set;

import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLEntity;
import org.semanticweb.owlapi.model.OWLObject;

/**
* Ontology contains unsatisfiable classes
*
* @author cjm
*
*/
public class IncoherentTBoxException extends OntologyLogicException {

public IncoherentTBoxException(Set<OWLClass> unsatisfiableClasses) {
// TODO Auto-generated constructor stub
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.obolibrary.robot.exceptions;

/**
* Ontology contains logical inconsistencies
*
* Note inconsistency is not the same as incoherency
*
* @author cjm
*
*/
public class InconsistentOntologyException extends OntologyLogicException {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.obolibrary.robot.exceptions;

/**
* Ontology contains unsatisfiable classes, properties or inconsistencies
*
* @author cjm
*
*/
public class OntologyLogicException extends Exception {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
*
*/
/**
* @author cjm
*
*/
package org.obolibrary.robot.exceptions;
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.apache.commons.io.IOUtils;
import org.geneontology.reasoner.ExpressionMaterializingReasonerFactory;
import org.junit.Test;
import org.obolibrary.robot.exceptions.OntologyLogicException;
import org.semanticweb.elk.owlapi.ElkReasonerFactory;
import org.semanticweb.owlapi.model.OWLOntology;
import org.semanticweb.owlapi.model.OWLOntologyCreationException;
Expand All @@ -33,7 +34,7 @@ public class MaterializeOperationTest extends CoreTest {
*/
@Test
public void testMaterialize()
throws IOException, OWLOntologyCreationException {
throws IOException, OWLOntologyCreationException, OntologyLogicException {
OWLOntology reasoned = loadOntology("/relax_equivalence_axioms_test.obo");
OWLReasonerFactory coreReasonerFactory = new ElkReasonerFactory();
Map<String, String> opts = ReasonOperation.getDefaultOptions();
Expand All @@ -53,7 +54,7 @@ public void testMaterialize()
*/
@Test
public void testMaterializeWithReflexivity()
throws IOException, OWLOntologyCreationException {
throws IOException, OWLOntologyCreationException, OntologyLogicException {
OWLOntology reasoned = loadOntology("/mat_reflexivity_test.obo");
OWLReasonerFactory coreReasonerFactory = new ElkReasonerFactory();
Map<String, String> opts = ReasonOperation.getDefaultOptions();
Expand All @@ -73,7 +74,7 @@ public void testMaterializeWithReflexivity()
*/
@Test
public void testMaterializeGCIs()
throws IOException, OWLOntologyCreationException {
throws IOException, OWLOntologyCreationException, OntologyLogicException {
OWLOntology reasoned = loadOntology("/gci_example.obo");
OWLReasonerFactory coreReasonerFactory = new ElkReasonerFactory();
Map<String, String> opts = ReasonOperation.getDefaultOptions();
Expand All @@ -93,7 +94,7 @@ public void testMaterializeGCIs()
*/
@Test
public void testMaterializeWithImports()
throws IOException, OWLOntologyCreationException, URISyntaxException {
throws IOException, OWLOntologyCreationException, OntologyLogicException, URISyntaxException {

// TODO: minor, simplify this once https://github.com/ontodev/robot/issues/121 implemeted

Expand Down
Loading

0 comments on commit 1c18e29

Please sign in to comment.