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 +