diff --git a/robot-command/src/main/java/org/obolibrary/robot/ReduceCommand.java b/robot-command/src/main/java/org/obolibrary/robot/ReduceCommand.java
new file mode 100644
index 000000000..cd6b9cfd5
--- /dev/null
+++ b/robot-command/src/main/java/org/obolibrary/robot/ReduceCommand.java
@@ -0,0 +1,151 @@
+package org.obolibrary.robot;
+
+import java.util.Map;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.semanticweb.owlapi.model.OWLOntology;
+import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handles inputs and outputs for the {@link ReduceOperation}.
+ *
+ * @author Chris Mungall
+ */
+public class ReduceCommand implements Command {
+ /**
+ * Logger.
+ */
+ private static final Logger logger =
+ LoggerFactory.getLogger(ReduceCommand.class);
+
+ /**
+ * Store the command-line options for the command.
+ */
+ private Options options;
+
+ /**
+ * Initialize the command.
+ */
+ public ReduceCommand() {
+ Options o = CommandLineHelper.getCommonOptions();
+ o.addOption("r", "reasoner", true, "reasoner to use: (ELK, HermiT)");
+ o.addOption("s", "remove-redundant-subclass-axioms",
+ true, "remove redundant subclass axioms");
+ o.addOption("i", "input", true, "reduce ontology from a file");
+ o.addOption("I", "input-iri", true, "reduce ontology from an IRI");
+ o.addOption("o", "output", true, "save reduceed ontology to a file");
+ options = o;
+ }
+
+ /**
+ * Name of the command.
+ *
+ * @return name
+ */
+ public String getName() {
+ return "reduce";
+ }
+
+ /**
+ * Brief description of the command.
+ *
+ * @return description
+ */
+ public String getDescription() {
+ return "reduce ontology";
+ }
+
+ /**
+ * Command-line usage for the command.
+ *
+ * @return usage
+ */
+ public String getUsage() {
+ return "robot reduce --input "
+ + "--reasoner "
+ + "[options] "
+ + "--output ";
+ }
+
+ /**
+ * Command-line options for the command.
+ *
+ * @return options
+ */
+ public Options getOptions() {
+ return options;
+ }
+
+ /**
+ * Handle the command-line and file operations for the reduceOperation.
+ *
+ * @param args strings to use as arguments
+ */
+ public void main(String[] args) {
+ try {
+ execute(null, args);
+ } catch (Exception e) {
+ CommandLineHelper.handleException(getUsage(), getOptions(), e);
+ }
+ }
+
+ /**
+ * Given an input state and command line arguments,
+ * run a reasoner, and add axioms to the input ontology,
+ * returning a state with the updated ontology.
+ *
+ * @param state the state from the previous command, or null
+ * @param args the command-line arguments
+ * @return the state with inferred axioms added to the ontology
+ * @throws Exception on any problem
+ */
+ public CommandState execute(CommandState state, String[] args)
+ throws Exception {
+ CommandLine line = CommandLineHelper
+ .getCommandLine(getUsage(), getOptions(), args);
+ if (line == null) {
+ return null;
+ }
+
+ if (state == null) {
+ state = new CommandState();
+ }
+
+ IOHelper ioHelper = CommandLineHelper.getIOHelper(line);
+ state = CommandLineHelper.updateInputOntology(ioHelper, state, line);
+ OWLOntology ontology = state.getOntology();
+
+ // ELK is the default reasoner
+ String reasonerName = CommandLineHelper.getDefaultValue(
+ line, "reasoner", "ELK").trim().toLowerCase();
+ OWLReasonerFactory reasonerFactory;
+ if (reasonerName.equals("structural")) {
+ reasonerFactory = new org.semanticweb.owlapi.reasoner
+ .structural.StructuralReasonerFactory();
+ } else if (reasonerName.equals("hermit")) {
+ reasonerFactory = new org.semanticweb
+ .HermiT.Reasoner.ReasonerFactory();
+ } else {
+ reasonerFactory = new org.semanticweb
+ .elk.owlapi.ElkReasonerFactory();
+ }
+
+ // Override default reasoner options with command-line options
+ Map reasonerOptions =
+ ReduceOperation.getDefaultOptions();
+ for (String option: reasonerOptions.keySet()) {
+ if (line.hasOption(option)) {
+ reasonerOptions.put(option, line.getOptionValue(option));
+ }
+ }
+
+ ReduceOperation.reduce(ontology, reasonerFactory, reasonerOptions);
+
+ CommandLineHelper.maybeSaveOutput(line, ontology);
+
+ return state;
+ }
+}
diff --git a/robot-command/src/main/java/org/obolibrary/robot/RelaxCommand.java b/robot-command/src/main/java/org/obolibrary/robot/RelaxCommand.java
new file mode 100644
index 000000000..af9b7c607
--- /dev/null
+++ b/robot-command/src/main/java/org/obolibrary/robot/RelaxCommand.java
@@ -0,0 +1,134 @@
+package org.obolibrary.robot;
+
+import java.util.Map;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.semanticweb.owlapi.model.OWLOntology;
+import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handles inputs and outputs for the {@link relaxOperation}.
+ *
+ * @author Chris Mungall
+ */
+public class RelaxCommand implements Command {
+ /**
+ * Logger.
+ */
+ private static final Logger logger =
+ LoggerFactory.getLogger(RelaxCommand.class);
+
+ /**
+ * Store the command-line options for the command.
+ */
+ private Options options;
+
+ /**
+ * Initialize the command.
+ */
+ public RelaxCommand() {
+ Options o = CommandLineHelper.getCommonOptions();
+ o.addOption("i", "input", true, "relax ontology from a file");
+ o.addOption("I", "input-iri", true, "relax ontology from an IRI");
+ o.addOption("o", "output", true, "save relaxed ontology to a file");
+ options = o;
+ }
+
+ /**
+ * Name of the command.
+ *
+ * @return name
+ */
+ public String getName() {
+ return "relax";
+ }
+
+ /**
+ * Brief description of the command.
+ *
+ * @return description
+ */
+ public String getDescription() {
+ return "relax ontology";
+ }
+
+ /**
+ * Command-line usage for the command.
+ *
+ * @return usage
+ */
+ public String getUsage() {
+ return "robot relax --input "
+ + "--reasoner "
+ + "[options] "
+ + "--output ";
+ }
+
+ /**
+ * Command-line options for the command.
+ *
+ * @return options
+ */
+ public Options getOptions() {
+ return options;
+ }
+
+ /**
+ * Handle the command-line and file operations for the relaxOperation.
+ *
+ * @param args strings to use as arguments
+ */
+ public void main(String[] args) {
+ try {
+ execute(null, args);
+ } catch (Exception e) {
+ CommandLineHelper.handleException(getUsage(), getOptions(), e);
+ }
+ }
+
+ /**
+ * Given an input state and command line arguments,
+ * run a reasoner, and add axioms to the input ontology,
+ * returning a state with the updated ontology.
+ *
+ * @param state the state from the previous command, or null
+ * @param args the command-line arguments
+ * @return the state with inferred axioms added to the ontology
+ * @throws Exception on any problem
+ */
+ public CommandState execute(CommandState state, String[] args)
+ throws Exception {
+ CommandLine line = CommandLineHelper
+ .getCommandLine(getUsage(), getOptions(), args);
+ if (line == null) {
+ return null;
+ }
+
+ if (state == null) {
+ state = new CommandState();
+ }
+
+ IOHelper ioHelper = CommandLineHelper.getIOHelper(line);
+ state = CommandLineHelper.updateInputOntology(ioHelper, state, line);
+ OWLOntology ontology = state.getOntology();
+
+
+ // Override default reasoner options with command-line options
+ Map relaxOptions =
+ RelaxOperation.getDefaultOptions();
+ for (String option: relaxOptions.keySet()) {
+ if (line.hasOption(option)) {
+ relaxOptions.put(option, line.getOptionValue(option));
+ }
+ }
+
+ RelaxOperation.relax(ontology, relaxOptions);
+
+ CommandLineHelper.maybeSaveOutput(line, ontology);
+
+ return state;
+ }
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/ReduceOperation.java b/robot-core/src/main/java/org/obolibrary/robot/ReduceOperation.java
new file mode 100644
index 000000000..644da5228
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/ReduceOperation.java
@@ -0,0 +1,223 @@
+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.semanticweb.owlapi.apibinding.OWLManager;
+import org.semanticweb.owlapi.model.AxiomType;
+import org.semanticweb.owlapi.model.IRI;
+import org.semanticweb.owlapi.model.OWLAxiom;
+import org.semanticweb.owlapi.model.OWLClass;
+import org.semanticweb.owlapi.model.OWLClassExpression;
+import org.semanticweb.owlapi.model.OWLDataFactory;
+import org.semanticweb.owlapi.model.OWLOntology;
+import org.semanticweb.owlapi.model.OWLOntologyManager;
+import org.semanticweb.owlapi.model.OWLSubClassOfAxiom;
+import org.semanticweb.owlapi.reasoner.Node;
+import org.semanticweb.owlapi.reasoner.OWLReasoner;
+import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Reason over an ontology and remove redundant SubClassOf axioms
+ *
+ * Every axiom A = SubClassOf(C D)
is tested
+ * (C or D are permitted to be anonymous, e.g. SomeValuesFrom)
+ *
+ * If there already exists an axiom SubClassOf(C Z)
Implementation
+ *
+ * Because an OWL reasoner will only return named (non-anonymous) superclasses,
+ * we add a pre-processing step, where for each class C appearing in either LHS or RHS
+ * of a SubClassOf expression, if C is anonymous, we create a named class C' and add
+ * a temporary axioms EquivalentClasses(C' C)
, which is later
+ * removed as a post-processing step. When performing reasoner tests, we can then substitute
+ * C for C'
+ *
+ * GENERAL CLASS INCLUSION AXIOMS
+ *
+ * We make a special additional case of redunancy, as in the following example:
+ *
+ *
+ * 1. (hand and part-of some human) SubClassOf part-of some forelimb
+ * 2. hand SubClassOf part-of some forelimb
+ *
+ *
+ * Here we treat axiom 1 as redundant, but this is not detected by the algorithm above,
+ * because there is no explicit SubClassOf axiom between the GCI LHS and 'human'. We
+ * therefore extend the test above and first find all superclasses of anonymous LHSs,
+ * and then test for these
+ *
+ *
+ * @author Chris Mungall
+ *
+ */
+public class ReduceOperation {
+
+ private static final Logger logger =
+ LoggerFactory.getLogger(ReduceOperation.class);
+
+ /**
+ * 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 getDefaultOptions() {
+ Map options = new HashMap();
+ //options.put("remove-redundant-subclass-axioms", "true");
+ return options;
+ }
+
+ public static void reduce(OWLOntology ontology,
+ OWLReasonerFactory reasonerFactory) {
+ reduce(ontology, reasonerFactory, getDefaultOptions());
+ }
+
+ public static void reduce(OWLOntology ontology,
+ OWLReasonerFactory reasonerFactory,
+ Map options) {
+
+ OWLOntologyManager manager = OWLManager.createOWLOntologyManager();
+ OWLDataFactory dataFactory = manager.getOWLDataFactory();
+
+
+ Map> subClassMap = new HashMap<>();
+ Set subClassAxioms = ontology.getAxioms(AxiomType.SUBCLASS_OF);
+ Set exprs = new HashSet<>();
+ //Map xmap = new HashMap<>();
+ Map rxmap = new HashMap<>();
+
+ for (OWLSubClassOfAxiom ax : subClassAxioms) {
+
+ OWLClass subClass = mapClass(dataFactory, rxmap, ax.getSubClass());
+ OWLClass superClass = mapClass(dataFactory, rxmap, ax.getSuperClass());
+ if (!subClassMap.containsKey(subClass))
+ subClassMap.put(subClass, new HashSet());
+ subClassMap.get(subClass).add(superClass);
+
+ // DEP
+ if (ax.getSubClass().isAnonymous()) {
+ exprs.add(ax.getSubClass());
+ }
+ if (ax.getSuperClass().isAnonymous()) {
+ exprs.add(ax.getSuperClass());
+ }
+ }
+ Set tempAxioms = new HashSet<>();
+ for (OWLClassExpression x : rxmap.keySet()) {
+ OWLAxiom ax = dataFactory.getOWLEquivalentClassesAxiom(rxmap.get(x), x);
+ manager.addAxiom(ontology, ax);
+ tempAxioms.add(ax);
+ }
+
+
+ // TODO: DRY - move to ReasonerOperation module
+ OWLReasoner reasoner = reasonerFactory.createReasoner(ontology);
+ if (!reasoner.isConsistent()) {
+ logger.info("Ontology is not consistent!");
+ return;
+ }
+
+ Node 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());
+ }
+ }
+ }
+
+ Set rmAxioms = new HashSet<>();
+ for (OWLSubClassOfAxiom ax : subClassAxioms) {
+
+ // TODO: make configurable
+ if (ax.getAnnotations().size() > 0) {
+ logger.debug("Protecting: "+ax);
+ continue;
+ }
+
+ logger.debug("Testing: "+ax);
+ OWLClassExpression subClassExpr = ax.getSubClass();
+ OWLClassExpression superClassExpr = ax.getSuperClass();
+ OWLClass subClass = rxmap.get(subClassExpr);
+ OWLClass superClass = rxmap.get(superClassExpr);
+ boolean isRedundant = false;
+
+ for (OWLClass assertedSuper : subClassMap.get(subClass)) {
+ if (reasoner.getSuperClasses(assertedSuper, false).containsEntity(superClass)) {
+ isRedundant = true;
+ break;
+ }
+ }
+
+ // Special case for GCIs
+ if (subClassExpr.isAnonymous()) {
+ logger.debug("GCI:"+subClassExpr);
+ for (OWLClass intermediateParent : reasoner.getSuperClasses(subClass, false).getFlattened()) {
+ if (subClassMap.containsKey(intermediateParent)) {
+ logger.debug("GCI intermediate parent:"+intermediateParent);
+ if (reasoner.getSuperClasses(intermediateParent, false).containsEntity(superClass)) {
+ isRedundant = true;
+ break;
+ }
+// for (OWLClass assertedSuper : subClassMap.get(intermediateParent)) {
+// logger.info(" DOES: "+assertedSuper+" CONTAIN:"+superClass);
+// logger.info(" SUPES="+reasoner.getSuperClasses(assertedSuper, false));
+// if (reasoner.getSuperClasses(assertedSuper, false).containsEntity(superClass)) {
+// isRedundant = true;
+// break;
+// }
+// }
+ }
+ }
+ }
+
+ if (isRedundant) {
+ logger.info("REMOVING REDUNDANT: "+ax);
+ rmAxioms.add(ax);
+ }
+ }
+
+ // post-processing step: remove temporary equivalence axioms
+ // for anonymous expressions
+ for (OWLAxiom ax : tempAxioms) {
+ manager.removeAxiom(ontology, ax);
+ }
+
+ // remove redundant axiom
+ for (OWLAxiom ax : rmAxioms) {
+ manager.removeAxiom(ontology, ax);
+ }
+
+ }
+
+ private static OWLClass mapClass(OWLDataFactory dataFactory,
+ Map rxmap, OWLClassExpression x) {
+ if (!rxmap.containsKey(x)) {
+ if (x.isAnonymous()) {
+ UUID uuid = UUID.randomUUID();
+ OWLClass c = dataFactory.getOWLClass(IRI.create("urn:uuid"+uuid.toString()));
+ logger.info(c + " ==> "+x);
+ rxmap.put(x, c);
+ }
+ else {
+ rxmap.put(x, (OWLClass) x);
+ }
+
+ }
+ return rxmap.get(x);
+ }
+
+
+
+}
diff --git a/robot-core/src/main/java/org/obolibrary/robot/RelaxOperation.java b/robot-core/src/main/java/org/obolibrary/robot/RelaxOperation.java
new file mode 100644
index 000000000..8e9471b8f
--- /dev/null
+++ b/robot-core/src/main/java/org/obolibrary/robot/RelaxOperation.java
@@ -0,0 +1,138 @@
+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.semanticweb.owlapi.apibinding.OWLManager;
+import org.semanticweb.owlapi.model.AxiomType;
+import org.semanticweb.owlapi.model.IRI;
+import org.semanticweb.owlapi.model.OWLAxiom;
+import org.semanticweb.owlapi.model.OWLClass;
+import org.semanticweb.owlapi.model.OWLClassExpression;
+import org.semanticweb.owlapi.model.OWLDataFactory;
+import org.semanticweb.owlapi.model.OWLEquivalentClassesAxiom;
+import org.semanticweb.owlapi.model.OWLObjectIntersectionOf;
+import org.semanticweb.owlapi.model.OWLObjectSomeValuesFrom;
+import org.semanticweb.owlapi.model.OWLOntology;
+import org.semanticweb.owlapi.model.OWLOntologyManager;
+import org.semanticweb.owlapi.model.OWLSubClassOfAxiom;
+import org.semanticweb.owlapi.reasoner.Node;
+import org.semanticweb.owlapi.reasoner.OWLReasoner;
+import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Add additional SubClassOf axioms that are relaxed forms of equivalence axioms
+ *
+ * Motivation
+ *
+ * It is frequently convenient to view an ontology without equivalence axioms. This is often for structural reasons. Certain editions of ontologies may come with a guarantee that the existential graph formed by all SubClassOf axioms (between named classes and existential axioms) is both complete (w.r.t graph operations) and non-redundant. Including EquivalentClasses axioms can introduce redundancy at the graph view level. For example, the genus is frequently more general than the inferred superclasses.
+ *
+ * To ensure that the existential graph is graph-complete it's necessary to write new SubClassOf axioms that are entailed by (but weaker than) Equivalence axioms
+ *
+ * Basic Operation
+ *
+ * For any equivalence axiom between a name class C and either a single existential X_1 or the class expression IntersectionOf(X_1 ... X_n), generate axioms
+ *
+ * C SubClassOf X_1
+ * ...
+ * C SubClassOf X_n
+ *
+ *
+ *
+ *
+ * @see issue 7
+ *
+ * @author Chris Mungall
+ *
+ */
+public class RelaxOperation {
+
+ private static final Logger logger =
+ LoggerFactory.getLogger(RelaxOperation.class);
+
+ /**
+ * 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 getDefaultOptions() {
+ Map options = new HashMap();
+ //options.put("remove-redundant-subclass-axioms", "true");
+ return options;
+ }
+
+
+ public static void relax(OWLOntology ontology,
+ Map options) {
+
+ OWLOntologyManager manager = OWLManager.createOWLOntologyManager();
+ OWLDataFactory dataFactory = manager.getOWLDataFactory();
+
+
+ Set newAxioms = new HashSet<>();
+
+ Set eqAxioms = ontology.getAxioms(AxiomType.EQUIVALENT_CLASSES);
+
+ for (OWLEquivalentClassesAxiom ax : eqAxioms) {
+ for (OWLClassExpression x : ax.getClassExpressions()) {
+ if (!x.isAnonymous()) {
+ OWLClass c = (OWLClass)x;
+ for (OWLClassExpression y : ax.getClassExpressionsMinus(c)) {
+ for (OWLObjectSomeValuesFrom svf : getSomeValuesFromAncestor(y)) {
+ newAxioms.add(dataFactory.getOWLSubClassOfAxiom(c, svf));
+ }
+ }
+ }
+ }
+ }
+
+
+ // remove redundant axiom
+ for (OWLAxiom ax : newAxioms) {
+ logger.info("NEW: "+ax);
+ manager.addAxiom(ontology, ax);
+ }
+
+ }
+
+ private static Set getSomeValuesFromAncestor(OWLClassExpression x) {
+ Set svfs = new HashSet<>();
+ if (x instanceof OWLObjectSomeValuesFrom) {
+ OWLObjectSomeValuesFrom svf = (OWLObjectSomeValuesFrom) x;
+ svfs.add(svf);
+ }
+ else if (x instanceof OWLObjectIntersectionOf) {
+ for (OWLClassExpression op : ((OWLObjectIntersectionOf) x).getOperands()) {
+ svfs.addAll(getSomeValuesFromAncestor(op));
+ }
+ }
+ return svfs;
+ }
+
+
+ private static OWLClass mapClass(OWLDataFactory dataFactory,
+ Map rxmap, OWLClassExpression x) {
+ if (!rxmap.containsKey(x)) {
+ if (x.isAnonymous()) {
+ UUID uuid = UUID.randomUUID();
+ OWLClass c = dataFactory.getOWLClass(IRI.create("urn:uuid"+uuid.toString()));
+ logger.info(c + " ==> "+x);
+ rxmap.put(x, c);
+ }
+ else {
+ rxmap.put(x, (OWLClass) x);
+ }
+
+ }
+ return rxmap.get(x);
+ }
+
+
+
+}
diff --git a/robot-core/src/test/java/org/obolibrary/robot/ReduceOperationTest.java b/robot-core/src/test/java/org/obolibrary/robot/ReduceOperationTest.java
new file mode 100644
index 000000000..79261ef9d
--- /dev/null
+++ b/robot-core/src/test/java/org/obolibrary/robot/ReduceOperationTest.java
@@ -0,0 +1,69 @@
+package org.obolibrary.robot;
+
+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.reasoner.OWLReasonerFactory;
+
+/**
+ * Tests for ReasonOperation.
+ */
+public class ReduceOperationTest extends CoreTest {
+
+
+
+
+
+ /**
+ * Test removing redundant subclass axioms.
+ *
+ * @throws IOException on file problem
+ */
+ @Test
+ public void testRemoveRedundantSubClassAxioms() throws IOException {
+ OWLOntology reasoned = loadOntology("/redundant_subclasses.owl");
+ OWLReasonerFactory reasonerFactory = new org.semanticweb
+ .elk.owlapi.ElkReasonerFactory();
+
+ Map options = new HashMap();
+ options.put("remove-redundant-subclass-axioms", "true");
+
+ ReduceOperation.reduce(reasoned, reasonerFactory, options);
+ assertIdentical("/without_redundant_subclasses.owl", reasoned);
+ }
+
+ /**
+ * Test removing redundant subclass expression (existential restriction) axioms.
+ *
+ * @throws IOException on file problem
+ */
+ @Test
+ public void testRemoveRedundantSubClassExpressionAxioms() throws IOException {
+ OWLOntology reasoned = loadOntology("/redundant_expr.obo");
+ OWLReasonerFactory reasonerFactory = new org.semanticweb
+ .elk.owlapi.ElkReasonerFactory();
+
+ Map options = new HashMap();
+ options.put("remove-redundant-subclass-axioms", "true");
+
+ ReduceOperation.reduce(reasoned, reasonerFactory, options);
+ assertIdentical("/redundant_expr_reduced.obo", reasoned);
+ }
+
+ @Test
+ public void testReduceGci() throws IOException {
+ OWLOntology reasoned = loadOntology("/reduce_gci_test.obo");
+ OWLReasonerFactory reasonerFactory = new org.semanticweb
+ .elk.owlapi.ElkReasonerFactory();
+
+ Map options = new HashMap();
+ options.put("remove-redundant-subclass-axioms", "true");
+
+ ReduceOperation.reduce(reasoned, reasonerFactory, options);
+ assertIdentical("/reduce_gci_reduced.obo", reasoned);
+ }
+}
diff --git a/robot-core/src/test/java/org/obolibrary/robot/RelaxOperationTest.java b/robot-core/src/test/java/org/obolibrary/robot/RelaxOperationTest.java
new file mode 100644
index 000000000..41c137063
--- /dev/null
+++ b/robot-core/src/test/java/org/obolibrary/robot/RelaxOperationTest.java
@@ -0,0 +1,38 @@
+package org.obolibrary.robot;
+
+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.reasoner.OWLReasonerFactory;
+
+/**
+ * Tests for ReasonOperation.
+ */
+public class RelaxOperationTest extends CoreTest {
+
+
+
+
+
+ /**
+ * Test removing redundant subclass axioms.
+ *
+ * @throws IOException on file problem
+ */
+ @Test
+ public void testRelax() throws IOException {
+ OWLOntology ont = loadOntology("/relax_equivalence_axioms_test.obo");
+
+ Map options = new HashMap();
+ options.put("remove-redundant-subclass-axioms", "true");
+
+ RelaxOperation.relax(ont, options);
+ assertIdentical("/relax_equivalence_axioms_relaxed.obo", ont);
+ }
+
+
+}
diff --git a/robot-core/src/test/resources/reduce_gci_reduced.obo b/robot-core/src/test/resources/reduce_gci_reduced.obo
new file mode 100644
index 000000000..dfa98b8d1
--- /dev/null
+++ b/robot-core/src/test/resources/reduce_gci_reduced.obo
@@ -0,0 +1,14 @@
+ontology: redundant_expr
+
+[Term]
+id: NCBITaxon:1
+name: example taxon
+
+[Term]
+id: X:1
+relationship: part_of X:2 !!! redundant
+
+[Typedef]
+id: part_of
+xref: BFO:0000050
+is_transitive: true
diff --git a/robot-core/src/test/resources/reduce_gci_test.obo b/robot-core/src/test/resources/reduce_gci_test.obo
new file mode 100644
index 000000000..d617d08de
--- /dev/null
+++ b/robot-core/src/test/resources/reduce_gci_test.obo
@@ -0,0 +1,15 @@
+ontology: redundant_expr
+
+[Term]
+id: NCBITaxon:1
+name: example taxon
+
+[Term]
+id: X:1
+relationship: part_of X:2 !!! redundant
+relationship: part_of X:2 {gci_relation="part_of", gci_filler="NCBITaxon:1"} !!! redundant
+
+[Typedef]
+id: part_of
+xref: BFO:0000050
+is_transitive: true
diff --git a/robot-core/src/test/resources/redundant_expr.obo b/robot-core/src/test/resources/redundant_expr.obo
new file mode 100644
index 000000000..840d38316
--- /dev/null
+++ b/robot-core/src/test/resources/redundant_expr.obo
@@ -0,0 +1,36 @@
+ontology: redundant_expr
+
+[Term]
+id: NCBITaxon:1
+name: example taxon
+
+[Term]
+id: X:1
+is_a: X:2
+relationship: part_of X:6 !!! redundant
+relationship: part_of X:5 {gci_relation="part_of", gci_filler="NCBITaxon:1"} !!! redundant
+
+[Term]
+id: X:2
+relationship: part_of X:3
+
+[Term]
+id: X:3
+is_a: X:4
+
+[Term]
+id: X:4
+relationship: part_of X:5
+
+[Term]
+id: X:5
+relationship: part_of X:6
+
+[Term]
+id: X:6
+name: x6
+
+[Typedef]
+id: part_of
+xref: BFO:0000050
+is_transitive: true
diff --git a/robot-core/src/test/resources/redundant_expr_reduced.obo b/robot-core/src/test/resources/redundant_expr_reduced.obo
new file mode 100644
index 000000000..f3fd03ba6
--- /dev/null
+++ b/robot-core/src/test/resources/redundant_expr_reduced.obo
@@ -0,0 +1,34 @@
+ontology: redundant_expr
+
+[Term]
+id: NCBITaxon:1
+name: example taxon
+
+[Term]
+id: X:1
+is_a: X:2
+
+[Term]
+id: X:2
+relationship: part_of X:3
+
+[Term]
+id: X:3
+is_a: X:4
+
+[Term]
+id: X:4
+relationship: part_of X:5
+
+[Term]
+id: X:5
+relationship: part_of X:6
+
+[Term]
+id: X:6
+name: x6
+
+[Typedef]
+id: part_of
+xref: BFO:0000050
+is_transitive: true
diff --git a/robot-core/src/test/resources/relax_equivalence_axioms_relaxed.obo b/robot-core/src/test/resources/relax_equivalence_axioms_relaxed.obo
new file mode 100644
index 000000000..26fc48791
--- /dev/null
+++ b/robot-core/src/test/resources/relax_equivalence_axioms_relaxed.obo
@@ -0,0 +1,13 @@
+ontology: relax
+
+[Term]
+id: X:1
+intersection_of: X:2
+intersection_of: part_of X:3
+relationship: part_of X:3
+
+[Typedef]
+id: part_of
+xref: BFO:0000050
+is_transitive: true
+
diff --git a/robot-core/src/test/resources/relax_equivalence_axioms_test.obo b/robot-core/src/test/resources/relax_equivalence_axioms_test.obo
new file mode 100644
index 000000000..7b88654f9
--- /dev/null
+++ b/robot-core/src/test/resources/relax_equivalence_axioms_test.obo
@@ -0,0 +1,12 @@
+ontology: relax
+
+[Term]
+id: X:1
+intersection_of: X:2
+intersection_of: part_of X:3
+
+[Typedef]
+id: part_of
+xref: BFO:0000050
+is_transitive: true
+