Repository: ontodev/robot
Pull request #81 (2016-06-09 16:02:43)

Additional options for reason command to annotate inferences and place new inferences in a separate ontology
aded create-new-ontology option in ReasonerOperation for #66
TODO: Tests, command, #67


Patch:
diff --git a/examples/README.md b/examples/README.md
index 84cac6ad..36d3cc18 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -83,6 +83,10 @@ One of the main benefits of working with OWL is the availability of powerful aut
 
     robot reason --reasoner ELK --input ribosome.owl --output results/reasoned.owl
 
+It's also possible to place the new inferences in their own ontology:
+
+    robot reason --reasoner ELK --create-new-ontology true --input ribosome.owl --output results/new_axioms.owl
+
 ## Relaxing equivalence axioms
 
 Robot can be used to relax Equivalence Axioms to weaker SubClassOf axioms. The resulting axioms will be redundant with the stronger equivalence axioms, but may be useful for applications that only consume SubClassOf axioms
diff --git a/examples/new_axioms.owl b/examples/new_axioms.owl
new file mode 100644
index 00000000..43df7e88
--- /dev/null
+++ b/examples/new_axioms.owl
@@ -0,0 +1,168 @@
+<?xml version="1.0"?>
+<rdf:RDF xmlns="http://purl.obolibrary.org/obo/go.owl#"
+     xml:base="http://purl.obolibrary.org/obo/go.owl"
+     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:owl="http://www.w3.org/2002/07/owl#"
+     xmlns:xml="http://www.w3.org/XML/1998/namespace"
+     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
+     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
+    <owl:Ontology rdf:about="http://purl.obolibrary.org/obo/go.owl"/>
+    
+
+
+    <!-- 
+    ///////////////////////////////////////////////////////////////////////////////////////
+    //
+    // Classes
+    //
+    ///////////////////////////////////////////////////////////////////////////////////////
+     -->
+
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0000313 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0000313">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0005840"/>
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0044422"/>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0005575 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0005575">
+        <rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Thing"/>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0005622 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0005622">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0044464"/>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0005623 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0005623">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0005575"/>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0005737 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0005737">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0044424"/>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0005840 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0005840">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0030529"/>
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0043232"/>
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0044444"/>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0030529 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0030529">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0044424"/>
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_1990904"/>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0032991 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0032991">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0005575"/>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0043226 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0043226">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0005575"/>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0043228 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0043228">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0043226"/>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0043229 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0043229">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0043226"/>
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0044424"/>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0043232 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0043232">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0043228"/>
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0043229"/>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0044422 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0044422">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0005575"/>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0044424 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0044424">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0044464"/>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0044444 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0044444">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0044424"/>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_0044464 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_0044464">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0005575"/>
+    </owl:Class>
+    
+
+
+    <!-- http://purl.obolibrary.org/obo/GO_1990904 -->
+
+    <owl:Class rdf:about="http://purl.obolibrary.org/obo/GO_1990904">
+        <rdfs:subClassOf rdf:resource="http://purl.obolibrary.org/obo/GO_0032991"/>
+    </owl:Class>
+</rdf:RDF>
+
+
+
+<!-- Generated by the OWL API (version 0.0.1-SNAPSHOT) https://github.com/owlcs/owlapi -->
+
diff --git a/robot-command/src/main/java/org/obolibrary/robot/AnnotateCommand.java b/robot-command/src/main/java/org/obolibrary/robot/AnnotateCommand.java
index 53b9f318..4455b9e5 100644
--- a/robot-command/src/main/java/org/obolibrary/robot/AnnotateCommand.java
+++ b/robot-command/src/main/java/org/obolibrary/robot/AnnotateCommand.java
@@ -69,6 +69,13 @@ public class AnnotateCommand implements Command {
         a.setLongOpt("typed-annotation");
         a.setArgs(3);
         options.addOption(a);
+
+        // Annotate with a property and a typed literal with a language tag
+        a = new Option("x", "annotate all axioms in the ontology "
+                          + "with PROP VALUE");
+        a.setLongOpt("axiom-annotation");
+        a.setArgs(3);
+        options.addOption(a);
     }
 
     /**
@@ -198,6 +205,18 @@ public class AnnotateCommand implements Command {
                     ioHelper.createTypedLiteral(value, type));
         }
 
+        // Add annotations with PROP VALUE
+        items = CommandLineHelper.getOptionValues(line, "axiom-annotation");
+        while (items.size() > 0) {
+            String property = items.remove(0);
+            String value    = items.remove(0);
+            OntologyHelper.addAxiomAnnotations(
+                    ontology,
+                    ioHelper.createIRI(property),
+                    ioHelper.createLiteral(value));
+        }
+
+
         // Load any annotation files as ontologies and merge them in
         List<OWLOntology> ontologies = new ArrayList<OWLOntology>();
         List<String> paths =
diff --git a/robot-command/src/main/java/org/obolibrary/robot/ReasonCommand.java b/robot-command/src/main/java/org/obolibrary/robot/ReasonCommand.java
index f8448b13..646ae46c 100644
--- a/robot-command/src/main/java/org/obolibrary/robot/ReasonCommand.java
+++ b/robot-command/src/main/java/org/obolibrary/robot/ReasonCommand.java
@@ -35,6 +35,12 @@ public class ReasonCommand implements Command {
         o.addOption("r", "reasoner",  true, "reasoner to use: (ELK, HermiT)");
         o.addOption("s", "remove-redundant-subclass-axioms",
                 true, "remove redundant subclass axioms");
+        o.addOption("n", "create-new-ontology", true,
+                "switch to a new ontology containing only the inferences");
+        o.addOption("a", "annotate-inferred-axioms",     true,
+                "annotate all inferred axioms (only when -n is passed)");
+        o.addOption("x", "exclude-duplicate-axioms",     true,
+                "do not add an axiom if it exists in import chain");
         o.addOption("i", "input",     true, "reason ontology from a file");
         o.addOption("I", "input-iri", true, "reason ontology from an IRI");
         o.addOption("o", "output",    true, "save reasoned ontology to a file");
diff --git a/robot-core/src/main/java/org/obolibrary/robot/OntologyHelper.java b/robot-core/src/main/java/org/obolibrary/robot/OntologyHelper.java
index 069f5c0b..f44d51f3 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/OntologyHelper.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/OntologyHelper.java
@@ -26,6 +26,7 @@ import org.semanticweb.owlapi.model.OWLLiteral;
 import org.semanticweb.owlapi.model.OWLOntology;
 import org.semanticweb.owlapi.model.OWLOntologyManager;
 import org.semanticweb.owlapi.model.OWLOntologyID;
+import org.semanticweb.owlapi.model.OWLSubClassOfAxiom;
 import org.semanticweb.owlapi.model.RemoveOntologyAnnotation;
 import org.semanticweb.owlapi.model.SetOntologyID;
 import org.semanticweb.owlapi.util.ReferencedEntitySetProvider;
@@ -574,4 +575,72 @@ public class OntologyHelper {
         manager.applyChange(addition);
     }
 
+    /**
+     * Given an ontology, an axiom, a property IRI, and a value string,
+     * add an annotation to this ontology with that property and value.
+     *
+     * Note that as axioms are immutable,
+     * the axiom is removed and replaced with a new one.
+     *
+     * @param ontology the ontology to modify
+     * @param axiom the axiom to annotate
+     * @param propertyIRI the IRI of the property to add
+     * @param value the IRI or literal value to add
+     */
+    public static void addAxiomAnnotation(OWLOntology ontology, OWLAxiom axiom,
+            IRI propertyIRI, OWLAnnotationValue value) {
+        OWLOntologyManager manager = ontology.getOWLOntologyManager();
+        OWLDataFactory df = manager.getOWLDataFactory();
+
+        OWLAnnotationProperty property =
+                df.getOWLAnnotationProperty(propertyIRI);
+        OWLAnnotation annotation = df.getOWLAnnotation(property, value);
+        addAxiomAnnotation(ontology,  axiom, Collections.singleton(annotation));
+    }
+
+    /**
+     * Given an ontology, an axiom, and a set of annotations,
+     * annotate the axiom with the annotations in the ontology.
+     *
+     * Note that as axioms are immutable,
+     * the axiom is removed and replaced with a new one.
+     *
+     * @param ontology the ontology to modify
+     * @param axiom the axiom to annotate
+     * @param annotations the set of annotation to add to the axiom
+     */
+    public static void addAxiomAnnotation(OWLOntology ontology, OWLAxiom axiom,
+            Set<OWLAnnotation> annotations) {
+        OWLOntologyManager manager = ontology.getOWLOntologyManager();
+        OWLDataFactory factory = manager.getOWLDataFactory();
+        OWLAxiom newAxiom;
+        if (axiom instanceof OWLSubClassOfAxiom) {
+            OWLSubClassOfAxiom x = ((OWLSubClassOfAxiom) axiom);
+            newAxiom = factory.getOWLSubClassOfAxiom(x.getSubClass(),
+                    x.getSuperClass(), annotations);
+            logger.debug("ANNOTATED: " + newAxiom);
+        } else {
+            // TODO - See https://github.com/ontodev/robot/issues/67
+            throw new UnsupportedOperationException(
+                    "Cannot annotate axioms of type: " + axiom.getClass());
+        }
+        manager.removeAxiom(ontology, axiom);
+        manager.addAxiom(ontology, newAxiom);
+    }
+
+    /**
+     * Given an ontology, an annotation property IRI, and an annotation value,
+     * annotate all axioms in the ontology with that property and value.
+     *
+     * @param ontology the ontology to modify
+     * @param propertyIRI the IRI of the property to add
+     * @param value the IRI or literal value to add
+     */
+    public static void addAxiomAnnotations(OWLOntology ontology,
+            IRI propertyIRI, OWLAnnotationValue value) {
+        for (OWLAxiom a : ontology.getAxioms()) {
+            addAxiomAnnotation(ontology, a, propertyIRI, value);
+        }
+    }
+
 }
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 e5274949..01acc7e3 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/ReasonOperation.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/ReasonOperation.java
@@ -9,14 +9,19 @@ import java.util.Set;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-
 import org.semanticweb.owlapi.apibinding.OWLManager;
+import org.semanticweb.owlapi.model.IRI;
+import org.semanticweb.owlapi.model.OWLAnnotationValue;
 import org.semanticweb.owlapi.model.OWLAxiom;
 import org.semanticweb.owlapi.model.OWLClass;
 import org.semanticweb.owlapi.model.OWLDataFactory;
+import org.semanticweb.owlapi.model.OWLImportsDeclaration;
 import org.semanticweb.owlapi.model.OWLOntology;
+import org.semanticweb.owlapi.model.OWLOntologyCreationException;
 import org.semanticweb.owlapi.model.OWLOntologyManager;
 import org.semanticweb.owlapi.model.OWLSubClassOfAxiom;
+import org.semanticweb.owlapi.model.RemoveImport;
+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;
@@ -35,7 +40,7 @@ public class ReasonOperation {
      * Logger.
      */
     private static final Logger logger =
-        LoggerFactory.getLogger(ReasonOperation.class);
+            LoggerFactory.getLogger(ReasonOperation.class);
 
     /**
      * Given a map of options and a key name,
@@ -104,6 +109,10 @@ public class ReasonOperation {
     public static Map<String, String> getDefaultOptions() {
         Map<String, String> options = new HashMap<String, String>();
         options.put("remove-redundant-subclass-axioms", "true");
+        options.put("create-new-ontology", "false");
+        options.put("annotate-inferred-axioms", "false");
+        options.put("exclude-duplicate-axioms", "false");
+
         return options;
     }
 
@@ -114,9 +123,11 @@ public class ReasonOperation {
      *
      * @param ontology the ontology to reason over
      * @param reasonerFactory the factory to create a reasoner instance from
+     * @throws OWLOntologyCreationException on ontology problem
      */
     public static void reason(OWLOntology ontology,
-            OWLReasonerFactory reasonerFactory) {
+            OWLReasonerFactory reasonerFactory)
+        throws OWLOntologyCreationException {
         reason(ontology, reasonerFactory, getDefaultOptions());
     }
 
@@ -128,10 +139,11 @@ public class ReasonOperation {
      * @param ontology the ontology to reason over
      * @param reasonerFactory the factory to create a reasoner instance from
      * @param options a map of option strings, or null
+     * @throws OWLOntologyCreationException on ontology problem
      */
     public static void reason(OWLOntology ontology,
             OWLReasonerFactory reasonerFactory,
-            Map<String, String> options) {
+            Map<String, String> options) throws OWLOntologyCreationException {
         logger.info("Ontology has {} axioms.", ontology.getAxioms().size());
         logger.info("Starting reasoning...");
 
@@ -150,10 +162,10 @@ public class ReasonOperation {
 
         reasoner.precomputeInferences(InferenceType.CLASS_HIERARCHY);
         Node<OWLClass> unsatisfiableClasses =
-            reasoner.getUnsatisfiableClasses();
+                reasoner.getUnsatisfiableClasses();
         if (unsatisfiableClasses.getSize() > 1) {
             logger.info("There are {} unsatisfiable classes in the ontology.",
-                        unsatisfiableClasses.getSize());
+                    unsatisfiableClasses.getSize());
             for (OWLClass cls : unsatisfiableClasses) {
                 if (!cls.isOWLNothing()) {
                     logger.info("    unsatisfiable: " + cls.getIRI());
@@ -163,10 +175,10 @@ public class ReasonOperation {
 
         // Make sure to add the axiom generators in this way!!!
         List<InferredAxiomGenerator<? extends OWLAxiom>> gens =
-            new ArrayList<InferredAxiomGenerator<? extends OWLAxiom>>();
+                new ArrayList<InferredAxiomGenerator<? extends OWLAxiom>>();
         gens.add(new InferredSubClassAxiomGenerator());
         InferredOntologyGenerator generator =
-            new InferredOntologyGenerator(reasoner, gens);
+                new InferredOntologyGenerator(reasoner, gens);
         logger.info("Using these axiom generators:");
         for (InferredAxiomGenerator inf: generator.getAxiomGenerators()) {
             logger.info("    " + inf);
@@ -176,15 +188,71 @@ public class ReasonOperation {
         seconds = (int) Math.ceil(elapsedTime / 1000);
         logger.info("Reasoning took {} seconds.", seconds);
 
+        // we first place all inferred axioms into a new ontology;
+        // these will be later transferred into the main ontology,
+        // unless the create new ontology option is passed
+        OWLOntology newAxiomOntology;
+        newAxiomOntology = manager.createOntology();
+
         startTime = System.currentTimeMillis();
-        generator.fillOntology(dataFactory, ontology);
+        generator.fillOntology(dataFactory, newAxiomOntology);
+
+        logger.info("Reasoning created {} new axioms.",
+                newAxiomOntology.getAxioms().size());
+
+
+        if (optionIsTrue(options, "create-new-ontology")) {
+            // because the ontology is passed by reference,
+            // we manipulate it in place
+            logger.info("Placing inferred axioms into a new ontology");
+            // todo: set ontology id
+            manager.removeAxioms(ontology, ontology.getAxioms());
+
+            Set<OWLImportsDeclaration> oids =
+                ontology.getImportsDeclarations();
+            for (OWLImportsDeclaration oid : oids) {
+                RemoveImport ri = new RemoveImport(ontology, oid);
+                manager.applyChange(ri);
+            }
+        }
 
-        logger.info("Ontology has {} axioms after reasoning.",
-                    ontology.getAxioms().size());
+
+        IRI propertyIRI = null;
+        OWLAnnotationValue value = null;
+        if (optionIsTrue(options, "annotate-inferred-axioms")) {
+            // the default is the convention used by OWLTools and GO, which is
+            // the property is_inferred with a literal (note: not xsd) "true"
+            propertyIRI = IRI.create(
+                "http://www.geneontology.org/formats/oboInOwl#is_inferred");
+            value = dataFactory.getOWLLiteral("true");
+        }
+        Set<OWLAxiom> existingAxioms = ontology.getAxioms(Imports.INCLUDED);
+        for (OWLAxiom a : newAxiomOntology.getAxioms()) {
+            if (optionIsTrue(options, "exclude-duplicate-axioms")) {
+                // TODO to a check that ignores annotations
+                if (existingAxioms.contains(a)) {
+                    logger.debug("Already have: " + a);
+                    continue;
+                }
+                if (a.containsEntityInSignature(dataFactory.getOWLThing())) {
+                    logger.debug("Ignoring trivial axioms with "
+                               + "OWLThing in signature: "
+                               + a);
+                    continue;
+                }
+            }
+            manager.addAxiom(ontology, a);
+            if (propertyIRI != null) {
+                OntologyHelper.addAxiomAnnotation(
+                    ontology, a, propertyIRI, value);
+            }
+        }
 
         if (optionIsTrue(options, "remove-redundant-subclass-axioms")) {
             removeRedundantSubClassAxioms(reasoner);
         }
+        logger.info("Ontology has {} axioms after all reasoning steps.",
+                ontology.getAxioms().size());
 
         elapsedTime = System.currentTimeMillis() - startTime;
         seconds = (int) Math.ceil(elapsedTime / 1000);
@@ -239,13 +307,13 @@ public class ReasonOperation {
                     continue;
                 }
                 OWLClass assertedSuperClass =
-                    subClassAxiom.getSuperClass().asOWLClass();
+                        subClassAxiom.getSuperClass().asOWLClass();
                 if (inferredSuperClasses.contains(assertedSuperClass)) {
                     continue;
                 }
                 manager.removeAxiom(ontology,
                         dataFactory.getOWLSubClassOfAxiom(
-                            thisClass, assertedSuperClass));
+                                thisClass, assertedSuperClass));
             }
         }
         logger.info("Ontology now has {} axioms.", ontology.getAxioms().size());


Test patch:
diff --git a/robot-core/src/test/java/org/obolibrary/robot/ExtractOperationTest.java b/robot-core/src/test/java/org/obolibrary/robot/ExtractOperationTest.java
index 97d8c887..2df78eee 100644
--- a/robot-core/src/test/java/org/obolibrary/robot/ExtractOperationTest.java
+++ b/robot-core/src/test/java/org/obolibrary/robot/ExtractOperationTest.java
@@ -6,7 +6,6 @@ import java.util.Set;
 
 import org.junit.Test;
 import org.semanticweb.owlapi.model.IRI;
-import org.semanticweb.owlapi.model.OWLAxiom;
 import org.semanticweb.owlapi.model.OWLOntology;
 import org.semanticweb.owlapi.model.OWLOntologyCreationException;
 
diff --git a/robot-core/src/test/java/org/obolibrary/robot/MireotOperationTest.java b/robot-core/src/test/java/org/obolibrary/robot/MireotOperationTest.java
index 2ac59c57..da33df6f 100644
--- a/robot-core/src/test/java/org/obolibrary/robot/MireotOperationTest.java
+++ b/robot-core/src/test/java/org/obolibrary/robot/MireotOperationTest.java
@@ -8,7 +8,6 @@ import java.util.Set;
 
 import org.junit.Test;
 import org.semanticweb.owlapi.model.IRI;
-import org.semanticweb.owlapi.model.OWLAxiom;
 import org.semanticweb.owlapi.model.OWLOntology;
 import org.semanticweb.owlapi.model.OWLOntologyCreationException;
 
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 a7d12b4b..0cd249ff 100644
--- a/robot-core/src/test/java/org/obolibrary/robot/ReasonOperationTest.java
+++ b/robot-core/src/test/java/org/obolibrary/robot/ReasonOperationTest.java
@@ -1,12 +1,14 @@
 package org.obolibrary.robot;
 
+import static org.junit.Assert.assertEquals;
+
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 
 import org.junit.Test;
-
 import org.semanticweb.owlapi.model.OWLOntology;
+import org.semanticweb.owlapi.model.OWLOntologyCreationException;
 import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
 
 /**
@@ -17,9 +19,11 @@ public class ReasonOperationTest extends CoreTest {
      * Test reasoning with StructuralReasoner.
      *
      * @throws IOException on file problem
+     * @throws OWLOntologyCreationException on ontology problem
      */
     @Test
-    public void testStructural() throws IOException {
+    public void testStructural()
+            throws IOException, OWLOntologyCreationException {
         OWLOntology reasoned = loadOntology("/simple.owl");
         OWLReasonerFactory reasonerFactory = new org.semanticweb
             .owlapi.reasoner.structural.StructuralReasonerFactory();
@@ -31,9 +35,10 @@ public class ReasonOperationTest extends CoreTest {
      * Test reasoning with ELK.
      *
      * @throws IOException on file problem
+     * @throws OWLOntologyCreationException on ontology problem
      */
     @Test
-    public void testELK() throws IOException {
+    public void testELK() throws IOException, OWLOntologyCreationException {
         OWLOntology reasoned = loadOntology("/simple.owl");
         OWLReasonerFactory reasonerFactory = new org.semanticweb
             .elk.owlapi.ElkReasonerFactory();
@@ -45,23 +50,95 @@ public class ReasonOperationTest extends CoreTest {
      * Test reasoning with HermiT.
      *
      * @throws IOException on file problem
+     * @throws OWLOntologyCreationException on ontology problem
      */
     @Test
-    public void testHermit() throws IOException {
+    public void testHermit() throws IOException, OWLOntologyCreationException {
         OWLOntology reasoned = loadOntology("/simple.owl");
         OWLReasonerFactory reasonerFactory = new org.semanticweb
             .HermiT.Reasoner.ReasonerFactory();
         ReasonOperation.reason(reasoned, reasonerFactory);
+        assertEquals(6, reasoned.getAxiomCount());
         assertIdentical("/simple_hermit.owl", reasoned);
     }
 
+    /**
+     * Test inferring into new ontology.
+     *
+     * @throws IOException on file problem
+     * @throws OWLOntologyCreationException on ontology problem
+     */
+    @Test
+    public void testInferIntoNewOntology()
+            throws IOException, OWLOntologyCreationException {
+        OWLOntology reasoned = loadOntology("/simple.owl");
+        OWLReasonerFactory reasonerFactory = new org.semanticweb
+            .HermiT.Reasoner.ReasonerFactory();
+        Map<String, String> opts = new HashMap<>();
+        // see https://github.com/ontodev/robot/issues/80
+        opts.put("create-new-ontology", "true");
+        // see https://github.com/ontodev/robot/issues/80
+        opts.put("annotate-inferred-axioms", "true");
+        ReasonOperation.reason(reasoned, reasonerFactory, opts);
+        assertEquals(2, reasoned.getAxiomCount());
+        //assertIdentical("/simple_hermit.owl", reasoned);
+    }
+
+    /**
+     * Test inferring into new ontology.
+     *
+     * @throws IOException on file problem
+     * @throws OWLOntologyCreationException on ontology problem
+     */
+    @Test
+    public void testInferIntoNewOntologyNonTrivial()
+            throws IOException, OWLOntologyCreationException {
+        OWLOntology reasoned =
+            loadOntology("/relax_equivalence_axioms_test.obo");
+        OWLReasonerFactory reasonerFactory = new org.semanticweb
+            .HermiT.Reasoner.ReasonerFactory();
+        Map<String, String> opts = new HashMap<>();
+        // see https://github.com/ontodev/robot/issues/80
+        opts.put("create-new-ontology", "true");
+        // see https://github.com/ontodev/robot/issues/80
+        opts.put("annotate-inferred-axioms", "true");
+
+        ReasonOperation.reason(reasoned, reasonerFactory, opts);
+        assertEquals(3, reasoned.getAxiomCount());
+        //assertIdentical("/simple_hermit.owl", reasoned);
+    }
+
+    /**
+     * Test inferring into new ontology.
+     *
+     * @throws IOException on file problem
+     * @throws OWLOntologyCreationException on ontology problem
+     */
+    @Test
+    public void testInferIntoNewOntologyNoDupes()
+            throws IOException, OWLOntologyCreationException {
+        OWLOntology reasoned =
+            loadOntology("/relax_equivalence_axioms_test.obo");
+        OWLReasonerFactory reasonerFactory = new org.semanticweb
+            .HermiT.Reasoner.ReasonerFactory();
+        Map<String, String> opts = new HashMap<>();
+        opts.put("create-new-ontology", "true");
+        opts.put("annotate-inferred-axioms", "true");
+        opts.put("exclude-duplicate-axioms", "true");
+        ReasonOperation.reason(reasoned, reasonerFactory, opts);
+        assertEquals(1, reasoned.getAxiomCount());
+        //assertIdentical("/simple_hermit.owl", reasoned);
+    }
+
     /**
      * Test removing redundant subclass axioms.
      *
      * @throws IOException on file problem
+     * @throws OWLOntologyCreationException on ontology problem
      */
     @Test
-    public void testRemoveRedundantSubClassAxioms() throws IOException {
+    public void testRemoveRedundantSubClassAxioms()
+            throws IOException, OWLOntologyCreationException {
         OWLOntology reasoned = loadOntology("/redundant_subclasses.owl");
         OWLReasonerFactory reasonerFactory = new org.semanticweb
             .elk.owlapi.ElkReasonerFactory();