Repository: ontodev/robot
Pull request #120 (2016-12-01 14:34:03)

Materialize in local ontology only
Changes behavior of materialize in the presence of imports closure

First iterate through all imported ontologies (non-reflexive, transitive), performs materialization.

Then when materialization is performed on the main ontology, ensure that no axioms that exist (in full imports closure) are asserted.

See #119


Patch:
diff --git a/robot-core/src/main/java/org/obolibrary/robot/MaterializeOperation.java b/robot-core/src/main/java/org/obolibrary/robot/MaterializeOperation.java
index 29db8dda..3cfb3cd5 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/MaterializeOperation.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/MaterializeOperation.java
@@ -21,6 +21,7 @@ import org.semanticweb.owlapi.model.OWLObjectProperty;
 import org.semanticweb.owlapi.model.OWLObjectSomeValuesFrom;
 import org.semanticweb.owlapi.model.OWLOntology;
 import org.semanticweb.owlapi.model.OWLOntologyManager;
+import org.semanticweb.owlapi.model.parameters.Imports;
 import org.semanticweb.owlapi.reasoner.OWLReasoner;
 import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
 import org.slf4j.Logger;
@@ -45,29 +46,69 @@ public class MaterializeOperation {
             LoggerFactory.getLogger(MaterializeOperation.class);
 
     /**
-     * Return a map from option name to default option value,
-     * for all the available reasoner options.
+     * Return a map from option name to default option value, for all the
+     * available reasoner options.
      *
      * @return a map with default values for all available options
      */
     public static Map<String, String> getDefaultOptions() {
         Map<String, String> options = new HashMap<String, String>();
-        //options.put("remove-redundant-subclass-axioms", "true");
+        // options.put("remove-redundant-subclass-axioms", "true");
         return options;
     }
 
     /**
      * Replace EquivalentClass axioms with weaker SubClassOf axioms.
      *
-     * @param ontology The OWLOntology to relax
-     * @param reasonerFactory reasoner factory for the reasoner that is to be wrapped
-     * @param properties object properties whose existentials are to be materialized (null materializes all)
-     * @param options A map of options for the operation
+     * @param ontology
+     *            The OWLOntology to relax
+     * @param reasonerFactory
+     *            reasoner factory for the reasoner that is to be wrapped
+     * @param properties
+     *            object properties whose existentials are to be materialized
+     *            (null materializes all)
+     * @param options
+     *            A map of options for the operation
+     */
+    public static void materialize(OWLOntology ontology,
+            OWLReasonerFactory reasonerFactory,
+            Set<OWLObjectProperty> properties, Map<String, String> options) {
+
+        // TODO: make reasonOverImportsClosure optional rather than always true
+        materialize(ontology, reasonerFactory, properties, options, true);
+    }
+
+    /**
+     * Replace EquivalentClass axioms with weaker SubClassOf axioms.
+     *
+     * @param ontology
+     *            The OWLOntology to relax
+     * @param reasonerFactory
+     *            reasoner factory for the reasoner that is to be wrapped
+     * @param properties
+     *            object properties whose existentials are to be materialized
+     *            (null materializes all)
+     * @param options
+     *            A map of options for the operation
+     * @param reasonOverImportsClosure
+     *            if true will first perform materialization over all ontologies in the import closure
      */
     public static void materialize(OWLOntology ontology,
             OWLReasonerFactory reasonerFactory,
             Set<OWLObjectProperty> properties,
-            Map<String, String> options) {
+            Map<String, String> options,
+            boolean reasonOverImportsClosure) {
+
+        logger.info("Materializing: "+ontology);
+        System.out.println("Materializing: "+ontology);
+        if (reasonOverImportsClosure) {
+            for (OWLOntology importedOntology : ontology.getImportsClosure()) {
+                if (!importedOntology.equals(ontology)) {
+                    materialize(importedOntology, reasonerFactory, properties, options, false);
+                }
+            }
+        }
+
 
         OWLOntologyManager manager = OWLManager.createOWLOntologyManager();
         OWLDataFactory dataFactory = manager.getOWLDataFactory();
@@ -76,9 +117,9 @@ public class MaterializeOperation {
         long elapsedTime;
         long startTime = System.currentTimeMillis();
 
-        ExpressionMaterializingReasonerFactory merf = new ExpressionMaterializingReasonerFactory(reasonerFactory);
-        ExpressionMaterializingReasoner emr = 
-                merf.createReasoner(ontology);
+        ExpressionMaterializingReasonerFactory merf = new ExpressionMaterializingReasonerFactory(
+                reasonerFactory);
+        ExpressionMaterializingReasoner emr = merf.createReasoner(ontology);
         if (!emr.isConsistent()) {
             logger.error("Ontology is not consistent!");
             return;
@@ -88,58 +129,68 @@ public class MaterializeOperation {
 
         Set<OWLAxiom> newAxioms = new HashSet<>();
 
-        logger.info("Materializing..."+properties);
+        logger.info("Materializing..." + properties);
         if (properties == null || properties.size() == 0)
             emr.materializeExpressions();
         else
             emr.materializeExpressions(properties);
-        
+
         logger.info("Materialization complete; iterating over classes");
 
-        int i=0;
-        for (OWLClass c : ontology.getClassesInSignature()) {
+        int i = 0;
+        Imports importsFlag = Imports.EXCLUDED; // TODO - make this optional
+
+        for (OWLClass c : ontology.getClassesInSignature(importsFlag)) {
             i++;
             if (i % 100 == 1) {
-                logger.info(" Materializing parents of class "+i+"/"+ontology.getClassesInSignature().size());
+                logger.info(" Materializing parents of class " + i + "/"
+                        + ontology.getClassesInSignature(importsFlag).size());
             }
             if (c.equals(dataFactory.getOWLNothing())) {
                 continue;
             }
-            Set<OWLClassExpression> sces = emr.getSuperClassExpressions(c, true);
+            Set<OWLClassExpression> sces = emr
+                    .getSuperClassExpressions(c, true);
             if (!emr.isSatisfiable(c)) {
                 // TODO: see https://github.com/ontodev/robot/issues/40
-                logger.error("Ontology is not conherent! Unsatisfiable: "+c);
+                logger.error("Ontology is not coherent! Unsatisfiable: " + c);
                 return;
             }
             for (OWLClassExpression sce : sces) {
-                
+
                 // do not make assertions involving Thing;
                 // while valid, these are trivial
-                if (!sce.getSignature().contains(dataFactory.getOWLThing())) {
-                    
-                    // avoid materializing parents with child in signature;
-                    // this can happen if a property P is reflexive
-                    // -- every class C is a subclass of P some C
-                    // while valid, this is trivial, so we avoid asserting
-                    if (!sce.getSignature().contains(c)) {
-                        OWLAxiom ax = dataFactory.getOWLSubClassOfAxiom(c, sce);
-                        newAxioms.add(ax);
-                    }
+                if (sce.getSignature().contains(dataFactory.getOWLThing())) {
+                    continue;
+                }
+
+                // avoid materializing parents with child in signature;
+                // this can happen if a property P is reflexive
+                // -- every class C is a subclass of P some C
+                // while valid, this is trivial, so we avoid asserting
+                if (sce.getSignature().contains(c)) {
+                    continue;
+                }
+                
+                OWLAxiom ax = dataFactory.getOWLSubClassOfAxiom(c, sce);
+                
+                // skip axioms that already exist
+                if (ontology.getAxioms(Imports.INCLUDED).contains(ax)) {
+                    continue;
                 }
+                newAxioms.add(ax);
             }
         }
 
-        logger.info("Adding "+newAxioms.size()+" materialized parents");
+        logger.info("Adding " + newAxioms.size() + " materialized parents");
         manager.addAxioms(ontology, newAxioms);
         emr.dispose();
 
         elapsedTime = System.currentTimeMillis() - startTime;
         seconds = (int) Math.ceil(elapsedTime / 1000);
-        logger.info("Asserting materialized superclasses took {} seconds.", seconds);
- 
+        logger.info("Asserting materialized superclasses took {} seconds.",
+                seconds);
 
     }
 
-
-
 }


Test patch:
diff --git a/robot-core/src/test/java/org/obolibrary/robot/MaterializeOperationTest.java b/robot-core/src/test/java/org/obolibrary/robot/MaterializeOperationTest.java
index a562f77e..41007139 100644
--- a/robot-core/src/test/java/org/obolibrary/robot/MaterializeOperationTest.java
+++ b/robot-core/src/test/java/org/obolibrary/robot/MaterializeOperationTest.java
@@ -2,10 +2,14 @@ package org.obolibrary.robot;
 
 import static org.junit.Assert.assertEquals;
 
+import java.io.File;
 import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.commons.io.IOUtils;
 import org.geneontology.reasoner.ExpressionMaterializingReasonerFactory;
 import org.junit.Test;
 import org.semanticweb.elk.owlapi.ElkReasonerFactory;
@@ -78,5 +82,31 @@ public class MaterializeOperationTest extends CoreTest {
         assertIdentical("/gci_example_materialized.obo", reasoned);
     }
 
+    /**
+     * Test reasoning with imports
+     *
+     * For motivation, see https://github.com/ontodev/robot/issues/119
+     *
+     * @throws IOException on file problem
+     * @throws OWLOntologyCreationException on ontology problem
+     * @throws URISyntaxException 
+     */
+    @Test
+    public void testMaterializeWithImports()
+            throws IOException, OWLOntologyCreationException, URISyntaxException {
+        
+        // TODO: minor, simplify this once https://github.com/ontodev/robot/issues/121 implemeted
+        
+        File f = new File(getClass().getResource("/import-non-reasoned.owl").toURI());
+        IOHelper ioh = new IOHelper();
+        OWLOntology reasoned =  ioh.loadOntology(f, true);
+        OWLOntology original =  ioh.loadOntology(f, true);
+
+        
+        OWLReasonerFactory coreReasonerFactory = new ElkReasonerFactory();
+        Map<String, String> opts = ReasonOperation.getDefaultOptions();
+        MaterializeOperation.materialize(reasoned, coreReasonerFactory, null, opts);
+        assertIdentical(original, reasoned);
+    }
 
 }
diff --git a/robot-core/src/test/resources/catalog-v001.xml b/robot-core/src/test/resources/catalog-v001.xml
new file mode 100644
index 00000000..2484240f
--- /dev/null
+++ b/robot-core/src/test/resources/catalog-v001.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<catalog prefer="public" xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
+
+    <!-- ./ -->
+    <uri id="User Entered Import Resolution" name="https://github.com/ontodev/robot/robot-core/src/test/resources/bot.owl" uri="./bot.owl"/>
+    <uri id="User Entered Import Resolution" name="https://github.com/ontodev/robot/robot-core/src/test/resources/convert_test.owl" uri="./convert_test.owl"/>
+    <uri id="User Entered Import Resolution" name="https://github.com/ontodev/robot/robot-core/src/test/resources/filtered.owl" uri="./filtered.owl"/>
+    <uri id="User Entered Import Resolution" name="https://github.com/ontodev/robot/robot-core/src/test/resources/map_properties_up_test.owl" uri="./map_properties_up_test.owl"/>
+    <uri id="User Entered Import Resolution" name="https://github.com/ontodev/robot/robot-core/src/test/resources/mireot.owl" uri="./mireot.owl"/>
+    <uri id="User Entered Import Resolution" name="https://github.com/ontodev/robot/robot-core/src/test/resources/redundant_subclasses.owl" uri="./redundant_subclasses.owl"/>
+    <uri id="User Entered Import Resolution" name="https://github.com/ontodev/robot/robot-core/src/test/resources/relax_equivalence_axioms_test.owl" uri="./relax_equivalence_axioms_test.owl"/>
+    <uri id="User Entered Import Resolution" name="https://github.com/ontodev/robot/robot-core/src/test/resources/simple.owl" uri="./simple.owl"/>
+    <uri id="User Entered Import Resolution" name="https://github.com/ontodev/robot/robot-core/src/test/resources/simple_elk.owl" uri="./simple_elk.owl"/>
+    <uri id="User Entered Import Resolution" name="https://github.com/ontodev/robot/robot-core/src/test/resources/simple_hermit.owl" uri="./simple_hermit.owl"/>
+    <uri id="User Entered Import Resolution" name="https://github.com/ontodev/robot/robot-core/src/test/resources/simple_parts.owl" uri="./simple_parts.owl"/>
+    <uri id="User Entered Import Resolution" name="https://github.com/ontodev/robot/robot-core/src/test/resources/simple_structural.owl" uri="./simple_structural.owl"/>
+    <uri id="User Entered Import Resolution" name="https://github.com/ontodev/robot/robot-core/src/test/resources/star.owl" uri="./star.owl"/>
+    <uri id="User Entered Import Resolution" name="https://github.com/ontodev/robot/robot-core/src/test/resources/top.owl" uri="./top.owl"/>
+    <uri id="User Entered Import Resolution" name="https://github.com/ontodev/robot/robot-core/src/test/resources/without_redundant_subclasses.owl" uri="./without_redundant_subclasses.owl"/>
+    <uri id="User Entered Import Resolution" name="https://github.com/ontodev/robot/robot-core/src/test/resources/non-reasoned.owl" uri="./non-reasoned.owl"/>
+</catalog>
diff --git a/robot-core/src/test/resources/import-non-reasoned.owl b/robot-core/src/test/resources/import-non-reasoned.owl
new file mode 100644
index 00000000..0a92cf18
--- /dev/null
+++ b/robot-core/src/test/resources/import-non-reasoned.owl
@@ -0,0 +1,12 @@
+Prefix: : <http://purl.obolibrary.org/obo/test/>
+Ontology: <https://github.com/ontodev/robot/robot-core/src/test/resources/import-non-reasoned.owl>
+Import: <https://github.com/ontodev/robot/robot-core/src/test/resources/non-reasoned.owl>
+
+## Test
+
+# note that by referencing X, we bring it into the signature,
+# this will in turn lead to inferences about X leaking into this ontology
+# when we materialize
+
+Class: X
+Class: Z SubClassOf: X
diff --git a/robot-core/src/test/resources/non-reasoned.owl b/robot-core/src/test/resources/non-reasoned.owl
new file mode 100644
index 00000000..182a5cb5
--- /dev/null
+++ b/robot-core/src/test/resources/non-reasoned.owl
@@ -0,0 +1,11 @@
+Prefix: : <http://purl.obolibrary.org/obo/test/>
+Ontology: <https://github.com/ontodev/robot/robot-core/src/test/resources/non-reasoned.owl>
+
+## Test
+
+ObjectProperty: P
+Class: X SubClassOf: Y
+Class: Y
+
+Class: A EquivalentTo: P some X
+Class: B EquivalentTo: P some Y