Repository: ontodev/robot
Pull request #208 (2017-12-05 22:25:53)

First pass at reference violation tests
see #1

Currently warnings will be produced if reason attempted with dangling/deprecated

TODO

 - [ ] hook into command line
 - [ ] hook into import module creation


Patch:
diff --git a/robot-core/src/main/java/org/obolibrary/robot/ReasonOperation.java b/robot-core/src/main/java/org/obolibrary/robot/ReasonOperation.java
index 7e45e172..f2519f78 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/ReasonOperation.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/ReasonOperation.java
@@ -1,6 +1,9 @@
 package org.obolibrary.robot;
 
 import org.geneontology.reasoner.ExpressionMaterializingReasoner;
+import org.obolibrary.robot.checks.InvalidReferenceChecker;
+import org.obolibrary.robot.checks.InvalidReferenceViolation;
+import org.obolibrary.robot.exceptions.InvalidReferenceException;
 import org.obolibrary.robot.exceptions.OntologyLogicException;
 import org.obolibrary.robot.reason.EquivalentClassReasoning;
 import org.obolibrary.robot.reason.EquivalentClassReasoningMode;
@@ -70,6 +73,7 @@ public class ReasonOperation {
         options.put("annotate-inferred-axioms", "false");
         options.put("exclude-duplicate-axioms", "false");
         options.put("equivalent-classes-allowed", ALL.written());
+        options.put("prevent-invalid-references", "false");
 
         return options;
     }
@@ -83,11 +87,12 @@ public class ReasonOperation {
      * @param reasonerFactory the factory to create a reasoner instance from
      * @throws OWLOntologyCreationException on ontology problem
      * @throws OntologyLogicException on inconsistency or incoherency
+     * @throws InvalidReferenceException 
      */
     public static void reason(OWLOntology ontology,
             OWLReasonerFactory reasonerFactory)
                     throws OWLOntologyCreationException,
-                    OntologyLogicException {
+                    OntologyLogicException, InvalidReferenceException {
         reason(ontology, reasonerFactory, getDefaultOptions());
     }
 
@@ -101,11 +106,12 @@ public class ReasonOperation {
      * @param options a map of option strings, or null
      * @throws OWLOntologyCreationException on ontology problem
      * @throws OntologyLogicException on inconsistency or incoherency
+     * @throws InvalidReferenceException 
      */
     public static void reason(OWLOntology ontology,
             OWLReasonerFactory reasonerFactory,
             Map<String, String> options) throws OWLOntologyCreationException,
-                    OntologyLogicException {
+                    OntologyLogicException, InvalidReferenceException {
         logger.info("Ontology has {} axioms.", ontology.getAxioms().size());
 
         logger.info("Fetching labels...");
@@ -119,6 +125,19 @@ public class ReasonOperation {
 
         OWLOntologyManager manager = OWLManager.createOWLOntologyManager();
         OWLDataFactory dataFactory = manager.getOWLDataFactory();
+        
+        Set<InvalidReferenceViolation> referenceViolations = InvalidReferenceChecker.getInvalidReferenceViolations(ontology, false);
+        if (referenceViolations.size() > 0) {
+            logger.error("Reference violations found: "+referenceViolations.size()+" - reasoning may be incomplete");
+            for (InvalidReferenceViolation v : referenceViolations) {
+                logger.error("Reference violation: "+v);
+            }
+            
+            if (OptionsHelper.optionIsTrue(options, "prevent-invalid-references")) {
+                throw new InvalidReferenceException(referenceViolations);
+            }
+            
+        }
 
         logger.info("Starting reasoning...");
         OWLReasoner reasoner = reasonerFactory.createReasoner(ontology);
diff --git a/robot-core/src/main/java/org/obolibrary/robot/checks/InvalidReferenceChecker.java b/robot-core/src/main/java/org/obolibrary/robot/checks/InvalidReferenceChecker.java
new file mode 100644
index 00000000..101be271
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/checks/InvalidReferenceChecker.java
@@ -0,0 +1,111 @@
+package org.obolibrary.robot.checks;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.obolibrary.robot.checks.InvalidReferenceViolation.Category;
+import org.semanticweb.owlapi.model.OWLAnnotationAssertionAxiom;
+import org.semanticweb.owlapi.model.OWLAxiom;
+import org.semanticweb.owlapi.model.OWLClass;
+import org.semanticweb.owlapi.model.OWLEntity;
+import org.semanticweb.owlapi.model.OWLObjectProperty;
+import org.semanticweb.owlapi.model.OWLOntology;
+import org.semanticweb.owlapi.model.parameters.Imports;
+
+/**
+ * 
+ * See: https://github.com/ontodev/robot/issues/1
+ * 
+ * @author cjm
+ *
+ */
+public class InvalidReferenceChecker {
+    
+    
+    /**
+     * @param ontology
+     * @param entity 
+     * @return true if owlClass has no logical or non-logical axioms
+     */
+    public static boolean isDangling(OWLOntology ontology, OWLEntity entity)  {
+        if (entity instanceof OWLClass) {
+            OWLClass owlClass = (OWLClass)entity;
+            if (ontology.getAxioms(owlClass, Imports.INCLUDED).size() > 0)
+                return false;
+            if (ontology.getAnnotationAssertionAxioms(owlClass.getIRI()).size() > 0)
+                return false;
+            return true;
+        }
+        if (entity instanceof OWLObjectProperty) {
+            OWLObjectProperty owlProperty = (OWLObjectProperty)entity;
+            if (ontology.getAxioms(owlProperty, Imports.INCLUDED).size() > 0)
+                return false;
+            if (ontology.getAnnotationAssertionAxioms(owlProperty.getIRI()).size() > 0)
+                return false;
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * @param ontology
+     * @param entity
+     * @return true if entity is deprecated
+     */
+    public static boolean isDeprecated(OWLOntology ontology, OWLEntity entity)  {
+        for (OWLAnnotationAssertionAxiom a : ontology.getAnnotationAssertionAxioms(entity.getIRI())) {
+            if (a.isDeprecatedIRIAssertion()) {
+                if (a.getValue().asLiteral().get().parseBoolean()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+   
+    
+    /**
+     * @param ontology
+     * @param axioms
+     * @return all violations
+     */
+    public static Set<InvalidReferenceViolation> getInvalidReferenceViolations(OWLOntology ontology, Set<OWLAxiom> axioms, boolean ignoreDangling) {
+        Set<InvalidReferenceViolation> violations = new HashSet<>();
+        for (OWLAxiom axiom : axioms) {
+            for (OWLEntity e : axiom.getSignature()) {
+                if (!ignoreDangling && isDangling(ontology, e)) {
+                    violations.add(InvalidReferenceViolation.create(axiom, e, Category.DANGLING));
+                }
+                if (isDeprecated(ontology, e)) {
+                    violations.add(InvalidReferenceViolation.create(axiom, e, Category.DEPRECATED));
+                }
+
+            }
+        
+        }
+        return violations;
+    }
+    
+    /**
+     * @param ontology
+     * @param ignoreDangling
+     * @return all violations in ontology
+     */
+    public static Set<InvalidReferenceViolation> getInvalidReferenceViolations(OWLOntology ontology, boolean ignoreDangling) {
+        return getInvalidReferenceViolations(ontology, ontology.getAxioms(Imports.INCLUDED), ignoreDangling);
+    }
+    
+    
+    /**
+     * Checks an import module. If the base ontology refers to deprecated entities in the import module
+     * this constitutes a violation
+     * 
+     * @param importModule
+     * @param baseOntology
+     * @return all deprecation-reference violations in baseOntology with respect to import module
+     */
+    public static Set<InvalidReferenceViolation> checkImportModule(OWLOntology importModule, OWLOntology baseOntology) {
+        return getInvalidReferenceViolations(importModule, baseOntology.getAxioms(Imports.INCLUDED), true);
+    }
+ 
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/checks/InvalidReferenceViolation.java b/robot-core/src/main/java/org/obolibrary/robot/checks/InvalidReferenceViolation.java
new file mode 100644
index 00000000..c4335dac
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/checks/InvalidReferenceViolation.java
@@ -0,0 +1,99 @@
+package org.obolibrary.robot.checks;
+
+import org.semanticweb.owlapi.model.OWLAxiom;
+import org.semanticweb.owlapi.model.OWLEntity;
+
+/**
+ * Represents a reference violation
+ * 
+ * TODO: we may want to make warnings if a class is slated for deprecation in the future:
+ * see https://github.com/information-artifact-ontology/ontology-metadata/issues/22
+ * 
+ * @author cjm
+ *
+ */
+public class InvalidReferenceViolation {
+    
+    final private OWLAxiom axiom;
+    final private OWLEntity referencedObject;
+    final private Category category;
+    
+    
+    
+    private InvalidReferenceViolation(OWLAxiom axiom,
+            OWLEntity referencedObject, Category category) {
+        super();
+        this.axiom = axiom;
+        this.referencedObject = referencedObject;
+        this.category = category;
+    }
+    
+    /**
+     * @param axiom
+     * @param referencedObject
+     * @param category
+     * @return new instance
+     */
+    public static InvalidReferenceViolation create(OWLAxiom axiom,
+            OWLEntity referencedObject, Category category) {
+        return new InvalidReferenceViolation(axiom, referencedObject, category);
+    }
+
+
+
+    /**
+     * @return the axiom that contains the invalid reference
+     */
+    public OWLAxiom getAxiom() {
+        return axiom;
+    }
+
+
+
+    /**
+     * @return the referencedObject that is either deprecated or dangling
+     */
+    public OWLEntity getReferencedObject() {
+        return referencedObject;
+    }
+
+
+
+    /**
+     * @return the category of violation (deprecated or dangling)
+     */
+    public Category getCategory() {
+        return category;
+    }
+
+
+
+    /**
+     * A reference may be invalid for different reasons
+     * 
+     *  - the referenced object may be deprecated
+     *  - the referenced object may be dangling
+     * 
+     * @author cjm
+     *
+     */
+    public enum Category {
+        DANGLING, DEPRECATED
+    }
+
+
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        // TODO: pretty-printer
+        return "InvalidReferenceViolation [axiom=" + axiom
+                + ", referencedObject=" + referencedObject + ", category="
+                + category + "]";
+    }
+    
+    
+
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/exceptions/InvalidReferenceException.java b/robot-core/src/main/java/org/obolibrary/robot/exceptions/InvalidReferenceException.java
new file mode 100644
index 00000000..e6fd4c27
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/exceptions/InvalidReferenceException.java
@@ -0,0 +1,28 @@
+package org.obolibrary.robot.exceptions;
+
+import java.util.Set;
+
+import org.obolibrary.robot.checks.InvalidReferenceViolation;
+import org.semanticweb.owlapi.model.OWLClass;
+
+/**
+ * Ontology contains unsatisfiable classes.
+ *
+ * @author cjm
+ *
+ */
+public class InvalidReferenceException extends OntologyStructureException {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = -6157565029337883652L;
+
+    /**
+     * @param unsatisfiableClasses list of unsat classes
+     */
+    public InvalidReferenceException(Set<InvalidReferenceViolation> violations) {
+        // TODO Auto-generated constructor stub
+    }
+
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/exceptions/OntologyStructureException.java b/robot-core/src/main/java/org/obolibrary/robot/exceptions/OntologyStructureException.java
new file mode 100644
index 00000000..9fd674be
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/exceptions/OntologyStructureException.java
@@ -0,0 +1,17 @@
+package org.obolibrary.robot.exceptions;
+
+/**
+ * Ontology contains contains structure that constitutes an exception
+ * 
+ * Example: a logical axiom referencing a deprecated entity
+ *
+ * @author cjm
+ *
+ */
+public abstract class OntologyStructureException extends Exception {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 8211835056287159708L;
+}


Test patch:
diff --git a/robot-core/src/test/java/org/obolibrary/robot/ReasonOperationTest.java b/robot-core/src/test/java/org/obolibrary/robot/ReasonOperationTest.java
index abe11142..b5fb7621 100644
--- a/robot-core/src/test/java/org/obolibrary/robot/ReasonOperationTest.java
+++ b/robot-core/src/test/java/org/obolibrary/robot/ReasonOperationTest.java
@@ -9,6 +9,7 @@ import java.util.Map;
 
 import org.geneontology.reasoner.ExpressionMaterializingReasonerFactory;
 import org.junit.Test;
+import org.obolibrary.robot.exceptions.InvalidReferenceException;
 import org.obolibrary.robot.exceptions.OntologyLogicException;
 import org.semanticweb.elk.owlapi.ElkReasonerFactory;
 import org.semanticweb.owlapi.model.OWLOntology;
@@ -27,11 +28,12 @@ public class ReasonOperationTest extends CoreTest {
      * @throws IOException on file problem
      * @throws OWLOntologyCreationException on ontology problem
      * @throws OntologyLogicException on logic error
+     * @throws InvalidReferenceException 
      */
     @Test
     public void testStructural()
             throws IOException, OWLOntologyCreationException,
-            OntologyLogicException {
+            OntologyLogicException, InvalidReferenceException {
         OWLOntology reasoned = loadOntology("/simple.owl");
         OWLReasonerFactory reasonerFactory = new org.semanticweb
                 .owlapi.reasoner.structural.StructuralReasonerFactory();
@@ -45,10 +47,11 @@ public class ReasonOperationTest extends CoreTest {
      * @throws IOException on file problem
      * @throws OWLOntologyCreationException on ontology problem
      * @throws OntologyLogicException on logic error
+     * @throws InvalidReferenceException 
      */
     @Test
     public void testELK() throws IOException, OWLOntologyCreationException,
-            OntologyLogicException {
+            OntologyLogicException, InvalidReferenceException {
         OWLOntology reasoned = loadOntology("/simple.owl");
         OWLReasonerFactory reasonerFactory = new ElkReasonerFactory();
         ReasonOperation.reason(reasoned, reasonerFactory);
@@ -61,10 +64,11 @@ public class ReasonOperationTest extends CoreTest {
      * @throws IOException on file problem
      * @throws OWLOntologyCreationException on ontology problem
      * @throws OntologyLogicException on logic error
+     * @throws InvalidReferenceException 
      */
     @Test
     public void testHermit() throws IOException, OWLOntologyCreationException,
-            OntologyLogicException {
+            OntologyLogicException, InvalidReferenceException {
         OWLOntology reasoned = loadOntology("/simple.owl");
         OWLReasonerFactory reasonerFactory = new org.semanticweb
                 .HermiT.Reasoner.ReasonerFactory();
@@ -79,10 +83,11 @@ public class ReasonOperationTest extends CoreTest {
      * @throws IOException on file problem
       * @throws OWLOntologyCreationException on ontology problem
      * @throws OntologyLogicException on logic error
+     * @throws InvalidReferenceException 
     */
     @Test
     public void testJFact() throws IOException, OWLOntologyCreationException,
-            OntologyLogicException {
+            OntologyLogicException, InvalidReferenceException {
         OWLOntology reasoned = loadOntology("/simple.owl");
         OWLReasonerFactory reasonerFactory = new JFactFactory();
         ReasonOperation.reason(reasoned, reasonerFactory);
@@ -96,11 +101,12 @@ public class ReasonOperationTest extends CoreTest {
      * @throws IOException on file problem
      * @throws OWLOntologyCreationException on ontology problem
      * @throws OntologyLogicException on logic error
+     * @throws InvalidReferenceException 
      */
     @Test
     public void testInferIntoNewOntology()
             throws IOException, OWLOntologyCreationException,
-            OntologyLogicException {
+            OntologyLogicException, InvalidReferenceException {
         OWLOntology reasoned = loadOntology("/simple.owl");
         OWLReasonerFactory reasonerFactory = new org.semanticweb
                 .HermiT.Reasoner.ReasonerFactory();
@@ -120,11 +126,12 @@ public class ReasonOperationTest extends CoreTest {
      * @throws IOException on file problem
      * @throws OWLOntologyCreationException on ontology problem
      * @throws OntologyLogicException on logic error
+     * @throws InvalidReferenceException 
      */
     @Test
     public void testInferIntoNewOntologyNonTrivial()
             throws IOException, OWLOntologyCreationException,
-            OntologyLogicException {
+            OntologyLogicException, InvalidReferenceException {
         OWLOntology reasoned =
                 loadOntology("/relax_equivalence_axioms_test.obo");
         OWLReasonerFactory reasonerFactory = new org.semanticweb
@@ -149,11 +156,12 @@ public class ReasonOperationTest extends CoreTest {
      * @throws IOException on file problem
       * @throws OWLOntologyCreationException on ontology problem
      * @throws OntologyLogicException on logic error
+     * @throws InvalidReferenceException 
     */
     @Test
     public void testInferIntoNewOntologyNoDupes()
             throws IOException, OWLOntologyCreationException,
-            OntologyLogicException {
+            OntologyLogicException, InvalidReferenceException {
         OWLOntology reasoned =
                 loadOntology("/relax_equivalence_axioms_test.obo");
         OWLReasonerFactory reasonerFactory = new org.semanticweb
@@ -173,11 +181,12 @@ public class ReasonOperationTest extends CoreTest {
      * @throws IOException on file problem
      * @throws OWLOntologyCreationException on ontology problem
      * @throws OntologyLogicException on logic error
+     * @throws InvalidReferenceException 
      */
     @Test
     public void testRemoveRedundantSubClassAxioms()
             throws IOException, OWLOntologyCreationException,
-            OntologyLogicException {
+            OntologyLogicException, InvalidReferenceException {
         OWLOntology reasoned = loadOntology("/redundant_subclasses.owl");
         OWLReasonerFactory reasonerFactory = new org.semanticweb
                 .elk.owlapi.ElkReasonerFactory();
@@ -201,11 +210,12 @@ public class ReasonOperationTest extends CoreTest {
      * @throws IOException on file problem
      * @throws OWLOntologyCreationException on ontology problem
      * @throws OntologyLogicException on logic error
+     * @throws InvalidReferenceException 
      */
     @Test
     public void testEMRBasic()
             throws IOException, OWLOntologyCreationException,
-            OntologyLogicException {
+            OntologyLogicException, InvalidReferenceException {
         OWLOntology reasoned = loadOntology("/simple.owl");
         OWLReasonerFactory coreReasonerFactory = new ElkReasonerFactory();
         OWLReasonerFactory reasonerFactory =
@@ -222,11 +232,12 @@ public class ReasonOperationTest extends CoreTest {
      * @throws IOException on file problem
      * @throws OWLOntologyCreationException on ontology problem
      * @throws OntologyLogicException on logic error
+     * @throws InvalidReferenceException 
      */
     @Test
     public void testEMRRelax()
             throws IOException, OWLOntologyCreationException,
-            OntologyLogicException {
+            OntologyLogicException, InvalidReferenceException {
         OWLOntology reasoned =
                 loadOntology("/relax_equivalence_axioms_test.obo");
         OWLReasonerFactory coreReasonerFactory = new ElkReasonerFactory();