Repository: ontodev/robot (java)

Issue:
reasoner behavior with Thing and Nothing
Every satisfiable class is entailed to be a subset of Thing, and Nothing is a proper subclass of every satisfiable class and equivalent to any non-satisfiable class.

When should entailments be materialized? Always? Never? Configurable?

The Thing superclass can be mildly annoying

The Nothing leaf class can occasionally cause massive problems e.g.

 * https://github.com/geneontology/amigo/issues/428
 * https://github.com/SciGraph/SciGraph/issues/236

logically correct but highly unintuitive for non-OWL people.

Additionally, the behavior with Nothing seems indeterminate. Sometimes it's "there" for the OWLAPI to query and add sometimes not

```
Prefix: owl: <http://www.w3.org/2002/07/owl#>
Prefix: rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
Prefix: rdfs: <http://www.w3.org/2000/01/rdf-schema#>
Prefix: xml: <http://www.w3.org/XML/1998/namespace>
Prefix: xsd: <http://www.w3.org/2001/XMLSchema#>
Prefix: : <http://example.org/>

## Test
Ontology: <http://example.org/test>

Class: :A

Class: :B SubClassOf: :A

Class: :C SubClassOf: :A

Class: owl:Nothing SubClassOf: :C
```

Running robot reason over this makes

```
    <owl:Class rdf:about="http://www.w3.org/2002/07/owl#Nothing">
        <rdfs:subClassOf rdf:resource="http://example.org/C"/>
        <rdfs:subClassOf rdf:resource="http://www.w3.org/2002/07/owl#Nothing"/>
    </owl:Class>
```

which is weird. Why a subclass of itself? Usually the reasoner API returns proper subclasses.

Why assert this one and not the equally correct Nothing SubClassOf B?

It may be potentially useful to see equivalent to Nothing materialized (e.g. make reasoner results introspectable by SPARQL). But otherwise the assertion of Thing/Nothing axioms does not add real information and should be off by default?

Pull request description:
Add `exclude-tautologies` option for reason command.
This fixes #173. Two things to maybe discuss before merging:

- The default for this arg is `false`. This is just to maintain current behavior. However I think most people consider the current behavior to be a bug. I would prefer to make `true` the default, but this is less conservative.
- I implemented a manual check for three kinds of axioms (`X SubClassOf owl:Thing`, `owl:Nothing SubClassOf X`, `X SubClassOf X`). @matentzn had proposed a more principled approach where any axiom that is not a subclass axiom is checked by seeing if it is entailed by a reasoner on an empty ontology. I left this out for now, but it could be easily added. It occurred to me that we can always use HermiT for this, since it is reasoning on the empty ontology, not the input ontology. But I don't know how much of a performance hit it would be to run axioms through that query.

Patch:
diff --git a/docs/examples/xref-need-of-repair.obo b/docs/examples/xref-need-of-repair.obo
deleted file mode 100644
index afdbaf4f..00000000
--- a/docs/examples/xref-need-of-repair.obo
+++ /dev/null
@@ -1,24 +0,0 @@
-ontology: xref-need-of-repair
-
-[Term]
-id: GO:1
-name: leaf node
-is_a: GO:2
-
-[Term]
-id: GO:2
-name: original parent
-is_obsolete: true
-replaced_by: GO:3
-xref: FOO:1
-
-[Term]
-id: GO:3
-name: replacement parent
-
-[Term]
-id: GO:4
-name: other term
-is_obsolete: true
-replaced_by: GO:3
-xref: FOO:2
diff --git a/docs/examples/xref-repaired.obo b/docs/examples/xref-repaired.obo
deleted file mode 100644
index 96f4e3f8..00000000
--- a/docs/examples/xref-repaired.obo
+++ /dev/null
@@ -1,24 +0,0 @@
-ontology: xref-need-of-repair
-
-[Term]
-id: GO:1
-name: leaf node
-is_a: GO:3
-
-[Term]
-id: GO:2
-name: original parent
-is_obsolete: true
-replaced_by: GO:3
-
-[Term]
-id: GO:3
-name: replacement parent
-xref: FOO:1
-xref: FOO:2
-
-[Term]
-id: GO:4
-name: other term
-is_obsolete: true
-replaced_by: GO:3
diff --git a/docs/reason.md b/docs/reason.md
index d55b6c28..f77c7ac5 100644
--- a/docs/reason.md
+++ b/docs/reason.md
@@ -73,6 +73,18 @@ The `<arg>` options are:
  * `all`: always allow (default behavior)
  * `none`: never allow
  * `asserted-only`: allow asserted equivalence, but throw an error if equivalence is inferred
+
+## Excluding tautologies
+
+A **tautology** is an axiom that would be true in any ontology, i.e., it's just a given due to the semantics of OWL. For example, every class is a subclass 
+of `owl:Thing`; it is not generally useful for an ontology to explicitly state this. Some reasoners may however include these kinds of statements as part 
+of their output. The ROBOT `reason` command provides an option for filtering out tautologies generated by the reasoning process: `--exclude-tautologies <arg>`.
+
+The options for `<arg>` are:
+
+ * `false` (default): allow any generated tautologies in the output.
+ * `all` (recommended): use the HermiT reasoner to exclude any inferred axioms that would be entailed by an empty ontology.
+ * `structural` (fast): exclude axioms matching a hard-coded set of tautological patterns (e.g., `X SubClassOf owl:Thing`, `owl:Nothing SubClassOf X`, `X SubClassOf X`). Much faster than `all`.
  
 ## Generated Axioms
 
diff --git a/docs/repair.md b/docs/repair.md
index 7be6367b..1e2c5c61 100644
--- a/docs/repair.md
+++ b/docs/repair.md
@@ -6,10 +6,3 @@ ROBOT can repair certain problems encountered in ontologies. So far, this is lim
       --input need-of-repair.owl \
       --output results/repaired.owl
       
-
-By default, annotation axioms are not migrated to replacement classes. However, this can be enabled for a list of annotation properties passed either as arguments to `--annotation-property` or in a term file `--annotation-properties-file`:
-
-    robot repair \
-      --input xref-need-of-repair.obo \
-      --annotation-property oboInOwl:hasDbXref \
-      --output results/xref-repaired.obo
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 fdcb812d..06284f2f 100644
--- a/robot-command/src/main/java/org/obolibrary/robot/ReasonCommand.java
+++ b/robot-command/src/main/java/org/obolibrary/robot/ReasonCommand.java
@@ -70,6 +70,11 @@ public class ReasonCommand implements Command {
         "exclude-external-entities",
         true,
         "if true, do not add an axiom if it is about classes in external ontologies");
+    o.addOption(
+        "t",
+        "exclude-tautologies",
+        true,
+        "specify approach for excluding tautologies: 'structural' (fast), 'all' (use HermiT, slower), 'false' (allow tautologies)");
     o.addOption("T", "exclude-owl-thing", true, "if true, exclude inferences to owl:Thing");
     o.addOption(
         "e",
diff --git a/robot-command/src/main/java/org/obolibrary/robot/RepairCommand.java b/robot-command/src/main/java/org/obolibrary/robot/RepairCommand.java
index 8c0dd4b0..77c636af 100644
--- a/robot-command/src/main/java/org/obolibrary/robot/RepairCommand.java
+++ b/robot-command/src/main/java/org/obolibrary/robot/RepairCommand.java
@@ -1,12 +1,8 @@
 package org.obolibrary.robot;
 
-import java.util.Set;
-import java.util.stream.Collectors;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.Options;
 import org.semanticweb.owlapi.model.IRI;
-import org.semanticweb.owlapi.model.OWLAnnotationProperty;
-import org.semanticweb.owlapi.model.OWLDataFactory;
 import org.semanticweb.owlapi.model.OWLOntology;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -36,12 +32,6 @@ public class RepairCommand implements Command {
         true,
         "if true, merge axiom annotations on duplicate axioms");
     options = o;
-    o.addOption("a", "annotation-property", true, "an annotation property to migrate");
-    o.addOption(
-        "A",
-        "annotation-properties-file",
-        true,
-        "load annotation properties to migrate from a file");
   }
 
   /**
@@ -122,15 +112,7 @@ public class RepairCommand implements Command {
     boolean mergeAxiomAnnotations =
         CommandLineHelper.getBooleanValue(line, "merge-axiom-annotations", false);
 
-    OWLDataFactory factory = inputOntology.getOWLOntologyManager().getOWLDataFactory();
-    Set<OWLAnnotationProperty> properties =
-        CommandLineHelper.getTerms(
-                ioHelper, line, "annotation-property", "annotation-properties-file")
-            .stream()
-            .map(factory::getOWLAnnotationProperty)
-            .collect(Collectors.toSet());
-
-    RepairOperation.repair(inputOntology, ioHelper, mergeAxiomAnnotations, properties);
+    RepairOperation.repair(inputOntology, ioHelper, mergeAxiomAnnotations);
     outputOntology = inputOntology;
     if (outputIRI != null) {
       outputOntology.getOWLOntologyManager().setOntologyDocumentIRI(outputOntology, outputIRI);
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 c9b794a6..a6e572e1 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/ReasonOperation.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/ReasonOperation.java
@@ -11,6 +11,7 @@ import org.obolibrary.robot.exceptions.*;
 import org.obolibrary.robot.reason.EquivalentClassReasoning;
 import org.obolibrary.robot.reason.EquivalentClassReasoningMode;
 import org.obolibrary.robot.reason.InferredSubClassAxiomGeneratorIncludingIndirect;
+import org.semanticweb.HermiT.ReasonerFactory;
 import org.semanticweb.owlapi.apibinding.OWLManager;
 import org.semanticweb.owlapi.model.*;
 import org.semanticweb.owlapi.model.parameters.Imports;
@@ -56,6 +57,7 @@ public class ReasonOperation {
     options.put("dump-unsatisfiable", null);
     options.put("axiom-generators", "subclass");
     options.put("include-indirect", "false");
+    options.put("exclude-tautologies", "false");
 
     return options;
   }
@@ -350,7 +352,8 @@ public class ReasonOperation {
    * @param options Map of reason options
    */
   private static void addInferredAxioms(
-      OWLOntology ontology, OWLOntology newAxiomOntology, Map<String, String> options) {
+      OWLOntology ontology, OWLOntology newAxiomOntology, Map<String, String> options)
+      throws OWLOntologyCreationException {
     OWLOntologyManager manager = OWLManager.createOWLOntologyManager();
     OWLDataFactory dataFactory = manager.getOWLDataFactory();
 
@@ -378,6 +381,16 @@ public class ReasonOperation {
       value = dataFactory.getOWLLiteral("true");
     }
 
+    // If we will need a tautology checker, create it only once
+    String tautologiesOption = OptionsHelper.getOption(options, "exclude-tautologies", "false");
+    OWLReasoner tautologyChecker;
+    if (tautologiesOption.equalsIgnoreCase("all")) {
+      OWLOntology empty = OWLManager.createOWLOntologyManager().createOntology();
+      tautologyChecker = new ReasonerFactory().createReasoner(empty);
+    } else {
+      tautologyChecker = null;
+    }
+
     // Look at each inferred axiom
     // Check the options, and maybe add the inferred axiom to the ontology
     for (OWLAxiom a : newAxiomOntology.getAxioms()) {
@@ -424,6 +437,43 @@ public class ReasonOperation {
         }
       }
 
+      if (tautologiesOption.equalsIgnoreCase("structural")) {
+        if (a instanceof OWLSubClassOfAxiom) {
+          OWLSubClassOfAxiom subClassOfAxiom = (OWLSubClassOfAxiom) a;
+          if (subClassOfAxiom.getSuperClass().isOWLThing()) {
+            continue;
+          } else if (subClassOfAxiom.getSubClass().isOWLNothing()) {
+            continue;
+          } else if (subClassOfAxiom.getSubClass().equals(subClassOfAxiom.getSuperClass())) {
+            continue;
+          }
+        } else if (a instanceof OWLEquivalentClassesAxiom) {
+          OWLEquivalentClassesAxiom equivAxiom = (OWLEquivalentClassesAxiom) a;
+          if (equivAxiom.getClassExpressions().size() < 2) {
+            continue;
+          }
+        } else if (a instanceof OWLClassAssertionAxiom) {
+          OWLClassAssertionAxiom classAssertion = (OWLClassAssertionAxiom) a;
+          if (classAssertion.getClassExpression().isOWLThing()) {
+            continue;
+          }
+        } else if (a instanceof OWLObjectPropertyAssertionAxiom) {
+          OWLObjectPropertyAssertionAxiom assertion = (OWLObjectPropertyAssertionAxiom) a;
+          if (assertion.getProperty().isOWLTopObjectProperty()) {
+            continue;
+          }
+        } else if (a instanceof OWLDataPropertyAssertionAxiom) {
+          OWLDataPropertyAssertionAxiom assertion = (OWLDataPropertyAssertionAxiom) a;
+          if (assertion.getProperty().isOWLTopDataProperty()) {
+            continue;
+          }
+        }
+      } else if (tautologiesOption.equalsIgnoreCase("all") && (tautologyChecker != null)) {
+        if (tautologyChecker.isEntailed(a)) {
+          continue;
+        }
+      }
+
       // If the axiom has not been skipped, add it to the ontology
       manager.addAxiom(ontology, a);
       // If propertyIRI isn't null, we are annotating the inferred axioms
diff --git a/robot-core/src/main/java/org/obolibrary/robot/RepairOperation.java b/robot-core/src/main/java/org/obolibrary/robot/RepairOperation.java
index 148a43c7..8ef1ff87 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/RepairOperation.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/RepairOperation.java
@@ -1,7 +1,11 @@
 package org.obolibrary.robot;
 
-import java.util.*;
-import java.util.stream.Collectors;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import org.obolibrary.robot.checks.InvalidReferenceChecker;
 import org.obolibrary.robot.checks.InvalidReferenceViolation;
 import org.obolibrary.robot.checks.InvalidReferenceViolation.Category;
@@ -48,22 +52,6 @@ public class RepairOperation {
     repair(ontology, ioHelper, getDefaultOptions(), mergeAxiomAnnotations);
   }
 
-  /**
-   * Repairs ontology
-   *
-   * @param ontology the OWLOntology to repair
-   * @param ioHelper IOHelper to work with the ontology
-   * @param mergeAxiomAnnotations if true, merge annotations on duplicate axioms
-   * @param migrateAnnotations set of annotation properties for which to migrate values
-   */
-  public static void repair(
-      OWLOntology ontology,
-      IOHelper ioHelper,
-      boolean mergeAxiomAnnotations,
-      Set<OWLAnnotationProperty> migrateAnnotations) {
-    repair(ontology, ioHelper, getDefaultOptions(), mergeAxiomAnnotations, migrateAnnotations);
-  }
-
   /**
    * Repairs ontology
    *
@@ -78,7 +66,7 @@ public class RepairOperation {
   /**
    * Repairs ontology
    *
-   * @param ontology the OWLOntology to repair
+   * @param ontology the OWLOntology to reapir
    * @param ioHelper IOHelper to work with the ontology
    * @param options map of repair options
    * @param mergeAxiomAnnotations if true, merge annotations on duplicate axioms
@@ -88,30 +76,10 @@ public class RepairOperation {
       IOHelper ioHelper,
       Map<String, String> options,
       boolean mergeAxiomAnnotations) {
-    repair(ontology, ioHelper, options, mergeAxiomAnnotations, Collections.emptySet());
-  }
-
-  /**
-   * Repairs ontology
-   *
-   * @param ontology the OWLOntology to repair
-   * @param ioHelper IOHelper to work with the ontology
-   * @param options map of repair options
-   * @param mergeAxiomAnnotations if true, merge annotations on duplicate axioms
-   * @param migrateAnnotations set of annotation properties for which to migrate values
-   */
-  public static void repair(
-      OWLOntology ontology,
-      IOHelper ioHelper,
-      Map<String, String> options,
-      boolean mergeAxiomAnnotations,
-      Set<OWLAnnotationProperty> migrateAnnotations) {
     Set<InvalidReferenceViolation> violations =
         InvalidReferenceChecker.getInvalidReferenceViolations(ontology, true);
-    repairInvalidReferences(ioHelper, ontology, violations, migrateAnnotations);
-    if (mergeAxiomAnnotations) {
-      mergeAxiomAnnotations(ontology);
-    }
+    repairInvalidReferences(ioHelper, ontology, violations);
+    mergeAxiomAnnotations(ontology);
   }
 
   /**
@@ -173,26 +141,6 @@ public class RepairOperation {
    */
   public static void repairInvalidReferences(
       IOHelper iohelper, OWLOntology ontology, Set<InvalidReferenceViolation> violations) {
-    repairInvalidReferences(iohelper, ontology, violations, Collections.emptySet());
-  }
-
-  /**
-   * Repairs invalid references
-   *
-   * <p>Currently only able to repair references to deprecated classes.
-   *
-   * <p>Assumes OBO vocabulary
-   *
-   * @param iohelper IOHelper to work with the ontology
-   * @param ontology the OWLOntology to repair
-   * @param violations set of references violations
-   * @param migrateAnnotations set of annotation properties for which to migrate values
-   */
-  public static void repairInvalidReferences(
-      IOHelper iohelper,
-      OWLOntology ontology,
-      Set<InvalidReferenceViolation> violations,
-      Set<OWLAnnotationProperty> migrateAnnotations) {
     logger.info("Invalid references: " + violations.size());
     OWLOntologyManager manager = ontology.getOWLOntologyManager();
     OWLEntityRenamer renamer = new OWLEntityRenamer(manager, ontology.getImportsClosure());
@@ -240,12 +188,7 @@ public class RepairOperation {
         axiomsToPreserve.addAll(ontology.getAxioms((OWLObjectProperty) obsObj, Imports.EXCLUDED));
       }
       axiomsToPreserve.addAll(ontology.getDeclarationAxioms(obsObj));
-      axiomsToPreserve.addAll(
-          ontology
-              .getAnnotationAssertionAxioms(obsObj.getIRI())
-              .stream()
-              .filter(ax -> !migrateAnnotations.contains(ax.getProperty()))
-              .collect(Collectors.toSet()));
+      axiomsToPreserve.addAll(ontology.getAnnotationAssertionAxioms(obsObj.getIRI()));
       logger.info("Replacing: " + obsObj + " -> " + replacedBy);
     }
 
diff --git a/robot-core/src/main/java/org/obolibrary/robot/Template.java b/robot-core/src/main/java/org/obolibrary/robot/Template.java
index 5c352140..7bc3df0b 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/Template.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/Template.java
@@ -881,15 +881,16 @@ public class Template {
       throws Exception {
     Set<OWLAnnotation> axiomAnnotations = new HashSet<>();
     Set<OWLClassExpression> expressions = new HashSet<>();
-    expressions.add(cls);
     for (int column : expressionColumns.keySet()) {
       // Maybe get an annotation on the expression (all annotations will be on the one intersection)
       axiomAnnotations.addAll(maybeGetAxiomAnnotations(row, column));
       // Add all expressions to the set of expressions
       expressions.addAll(expressionColumns.get(column));
     }
-    // Create the axiom as an intersection of the provided expressions
-    axioms.add(dataFactory.getOWLEquivalentClassesAxiom(expressions, axiomAnnotations));
+    // Each expression will be its own equivalent class statement
+    for (OWLClassExpression expr : expressions) {
+      axioms.add(dataFactory.getOWLEquivalentClassesAxiom(cls, expr, axiomAnnotations));
+    }
   }
 
   /**
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
index c400d985..3cb5f34c 100644
--- a/robot-core/src/main/java/org/obolibrary/robot/checks/InvalidReferenceChecker.java
+++ b/robot-core/src/main/java/org/obolibrary/robot/checks/InvalidReferenceChecker.java
@@ -88,7 +88,7 @@ public class InvalidReferenceChecker {
       OWLOntology ontology, Set<OWLAxiom> axioms, boolean ignoreDangling) {
     Set<InvalidReferenceViolation> violations = new HashSet<>();
     for (OWLAxiom axiom : axioms) {
-      for (OWLEntity e : expandedSignature(axiom, ontology)) {
+      for (OWLEntity e : axiom.getSignature()) {
         if (!ignoreDangling && isDangling(ontology, e)) {
           violations.add(InvalidReferenceViolation.create(axiom, e, Category.DANGLING));
         }
@@ -126,27 +126,4 @@ public class InvalidReferenceChecker {
     return getInvalidReferenceViolations(
         importModule, baseOntology.getAxioms(Imports.INCLUDED), true);
   }
-
-  /**
-   * Get the entity signature for an axiom. This expands the standard axiom signature to include
-   * entities found in the ontology which have IRIs used as the subject for an annotation assertion
-   * axiom.
-   *
-   * @param axiom the axiom for which to get the entity signature
-   * @param ontology ontology to search for entities with IRIs used in annotation assertions
-   * @return set of entities representing the signature of the axiom
-   */
-  private static Set<OWLEntity> expandedSignature(OWLAxiom axiom, OWLOntology ontology) {
-    if (axiom instanceof OWLAnnotationAssertionAxiom) {
-      Set<OWLEntity> signature = axiom.getSignature();
-      OWLAnnotationAssertionAxiom annotationAxiom = (OWLAnnotationAssertionAxiom) axiom;
-      if (annotationAxiom.getSubject().isIRI()) {
-        IRI subjectIRI = (IRI) annotationAxiom.getSubject();
-        signature.addAll(ontology.getEntitiesInSignature(subjectIRI));
-      }
-      return signature;
-    } else {
-      return axiom.getSignature();
-    }
-  }
 }


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 7cc87194..7e1cebce 100644
--- a/robot-core/src/test/java/org/obolibrary/robot/ReasonOperationTest.java
+++ b/robot-core/src/test/java/org/obolibrary/robot/ReasonOperationTest.java
@@ -11,6 +11,7 @@ import org.semanticweb.HermiT.ReasonerFactory;
 import org.semanticweb.elk.owlapi.ElkReasonerFactory;
 import org.semanticweb.owlapi.model.*;
 import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
+import org.semanticweb.owlapi.search.EntitySearcher;
 import uk.ac.manchester.cs.jfact.JFactFactory;
 
 /** Tests for ReasonOperation. */
@@ -150,6 +151,19 @@ public class ReasonOperationTest extends CoreTest {
     assertIdentical("/without_redundant_subclasses.owl", reasoned);
   }
 
+  @Test
+  public void testExcludeTautologies() throws Exception {
+    OWLOntology input = loadOntology("/reason_exclude_tautologies.ofn");
+    OWLReasonerFactory reasonerFactory = new org.semanticweb.elk.owlapi.ElkReasonerFactory();
+    Map<String, String> options = new HashMap<>();
+    options.put("exclude-tautologies", "structural");
+    ReasonOperation.reason(input, reasonerFactory, options);
+    assertFalse(
+        EntitySearcher.getSuperClasses(
+                dataFactory.getOWLClass(IRI.create("http://example.org/B")), input)
+            .contains(dataFactory.getOWLThing()));
+  }
+
   /**
    * Test reasoning with Expression Materializing Reasoner.
    *
diff --git a/robot-core/src/test/java/org/obolibrary/robot/RepairOperationTest.java b/robot-core/src/test/java/org/obolibrary/robot/RepairOperationTest.java
index b94f3f5c..7ae91244 100644
--- a/robot-core/src/test/java/org/obolibrary/robot/RepairOperationTest.java
+++ b/robot-core/src/test/java/org/obolibrary/robot/RepairOperationTest.java
@@ -1,11 +1,7 @@
 package org.obolibrary.robot;
 
 import java.io.IOException;
-import java.util.Collections;
 import org.junit.Test;
-import org.semanticweb.owlapi.apibinding.OWLManager;
-import org.semanticweb.owlapi.model.IRI;
-import org.semanticweb.owlapi.model.OWLAnnotationProperty;
 import org.semanticweb.owlapi.model.OWLOntology;
 import org.semanticweb.owlapi.model.OWLOntologyCreationException;
 
@@ -26,16 +22,4 @@ public class RepairOperationTest extends CoreTest {
     iohelper.saveOntology(ontology, "target/foo.owl");
     assertIdentical("/repaired.owl", ontology);
   }
-
-  @Test
-  public void testXrefRepair() throws IOException {
-    OWLAnnotationProperty hasDbXref =
-        OWLManager.getOWLDataFactory()
-            .getOWLAnnotationProperty(
-                IRI.create("http://www.geneontology.org/formats/oboInOwl#hasDbXref"));
-    OWLOntology ontology = loadOntology("/xref-need-of-repair.obo");
-    IOHelper iohelper = new IOHelper();
-    RepairOperation.repair(ontology, iohelper, true, Collections.singleton(hasDbXref));
-    assertIdentical("/xref-repaired.obo", ontology);
-  }
 }
diff --git a/robot-core/src/test/resources/reason_exclude_tautologies.ofn b/robot-core/src/test/resources/reason_exclude_tautologies.ofn
new file mode 100644
index 00000000..fd3f40d5
--- /dev/null
+++ b/robot-core/src/test/resources/reason_exclude_tautologies.ofn
@@ -0,0 +1,29 @@
+Prefix(:=<http://example.org/>)
+Prefix(owl:=<http://www.w3.org/2002/07/owl#>)
+Prefix(rdf:=<http://www.w3.org/1999/02/22-rdf-syntax-ns#>)
+Prefix(xml:=<http://www.w3.org/XML/1998/namespace>)
+Prefix(xsd:=<http://www.w3.org/2001/XMLSchema#>)
+Prefix(rdfs:=<http://www.w3.org/2000/01/rdf-schema#>)
+
+
+Ontology(<http://example.org/>
+
+Declaration(Class(:A))
+Declaration(Class(:B))
+Declaration(Class(:C))
+Declaration(ObjectProperty(:r))
+
+############################
+#   Classes
+############################
+
+# Class: :A (:A)
+
+SubClassOf(:A ObjectSomeValuesFrom(:r :B))
+
+# Class: :C (:C)
+
+EquivalentClasses(:C ObjectSomeValuesFrom(:r :B))
+
+
+)
\ No newline at end of file
diff --git a/robot-core/src/test/resources/xref-need-of-repair.obo b/robot-core/src/test/resources/xref-need-of-repair.obo
deleted file mode 100644
index afdbaf4f..00000000
--- a/robot-core/src/test/resources/xref-need-of-repair.obo
+++ /dev/null
@@ -1,24 +0,0 @@
-ontology: xref-need-of-repair
-
-[Term]
-id: GO:1
-name: leaf node
-is_a: GO:2
-
-[Term]
-id: GO:2
-name: original parent
-is_obsolete: true
-replaced_by: GO:3
-xref: FOO:1
-
-[Term]
-id: GO:3
-name: replacement parent
-
-[Term]
-id: GO:4
-name: other term
-is_obsolete: true
-replaced_by: GO:3
-xref: FOO:2
diff --git a/robot-core/src/test/resources/xref-repaired.obo b/robot-core/src/test/resources/xref-repaired.obo
deleted file mode 100644
index 96f4e3f8..00000000
--- a/robot-core/src/test/resources/xref-repaired.obo
+++ /dev/null
@@ -1,24 +0,0 @@
-ontology: xref-need-of-repair
-
-[Term]
-id: GO:1
-name: leaf node
-is_a: GO:3
-
-[Term]
-id: GO:2
-name: original parent
-is_obsolete: true
-replaced_by: GO:3
-
-[Term]
-id: GO:3
-name: replacement parent
-xref: FOO:1
-xref: FOO:2
-
-[Term]
-id: GO:4
-name: other term
-is_obsolete: true
-replaced_by: GO:3