org.obolibrary.robot.RelaxOperation Maven / Gradle / Ivy
Show all versions of robot-core Show documentation
package org.obolibrary.robot;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.model.AxiomType;
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.OWLObjectCardinalityRestriction;
import org.semanticweb.owlapi.model.OWLObjectIntersectionOf;
import org.semanticweb.owlapi.model.OWLObjectPropertyExpression;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Add additional SubClassOf axioms that are relaxed forms of equivalence axioms.
*
* This is a form of incomplete reasoning that is useful when either axioms are invisible to EL
* reasoners, or to produce an existential subclass graph that has SomeValuesFrom superclasses
* materialized.
*
*
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
*
Additionally, if X_i is a qualified cardinality constraint, weaken to an existential
*
* Usage
*
* For ELK reasoners, it is recommended to use this prior to the reasoning step, as relaxation can
* reveal weaker forms of axioms that are outside EL.
*
* It is recommended that the reduce operation is executed after relaxation, to remove any
* redundancies within the subclass graph
*
*
Note that the materialize operation may seem to make the relax step pointless; materialization
* produces the most specific existential parent. However, in some cases relaxation should still be
* performed: - if Elk is used (see above) - if materialization is only performed on a subset of
* properties
*
*
The standard sequence is: relax-materialize-reduce, or relax-reason-reduce
*
* @see issue 7
* @see issue 135
* @author Chris Mungall
*/
public class RelaxOperation {
/** Logger. */
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() {
// options.put("remove-redundant-subclass-axioms", "true");
return new HashMap<>();
}
/**
* Replace EquivalentClass axioms with weaker SubClassOf axioms.
*
* @param ontology The OWLOntology to relax
* @param options A map of options for the operation
*/
public static void relax(OWLOntology ontology, Map options) {
relax(ontology);
}
/**
* Replace EquivalentClass axioms with weaker SubClassOf axioms.
*
* @param ontology The OWLOntology to relax
*/
public static void relax(OWLOntology ontology) {
relax(ontology, false, false, false);
}
/**
* Replace EquivalentClass axioms with weaker SubClassOf axioms.
*
* @param ontology The OWLOntology to relax
* @param enforceOboFormat if true, only axioms allowed in OBO format are asserted as a
* consequence of relax
* @param excludeNamedClasses if true, equivalent class axioms between named classes are ignored
* during processing
* @param includeSubclassOf if true, equivalent class axioms between named classes are ignored
* during processing
*/
public static void relax(
OWLOntology ontology,
boolean enforceOboFormat,
boolean excludeNamedClasses,
boolean includeSubclassOf) {
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()) {
// we only relax in cases where the equivalence is between one
// named and one anon expression
if (!x.isAnonymous()) {
OWLClass c = (OWLClass) x;
// ax = EquivalentClasses(x y1 y2 ...)
for (OWLClassExpression y : ax.getClassExpressionsMinus(c)) {
// if we are excluding equivalents between named classes, skip
if (!y.isAnonymous() && excludeNamedClasses) {
continue;
}
relaxExpression(c, y, newAxioms, enforceOboFormat, dataFactory);
}
}
}
}
if (includeSubclassOf) {
Set subClassAxioms = ontology.getAxioms(AxiomType.SUBCLASS_OF);
for (OWLSubClassOfAxiom ax : subClassAxioms) {
OWLClassExpression subClass = ax.getSubClass();
OWLClassExpression superClass = ax.getSuperClass();
// we only relax in cases where the subclass is a named class
// and the superclass a complex expression
if (!subClass.isAnonymous() && superClass.isAnonymous()) {
OWLClass namedSubClass = (OWLClass) subClass;
relaxExpression(namedSubClass, superClass, newAxioms, enforceOboFormat, dataFactory);
}
}
}
// remove redundant axiom
for (OWLAxiom ax : newAxioms) {
logger.info("NEW: " + ax);
manager.addAxiom(ontology, ax);
}
}
private static void relaxExpression(
OWLClass namedSubClass,
OWLClassExpression anonymousSuperClass,
Set newAxioms,
boolean enforceOboFormat,
OWLDataFactory dataFactory) {
// limited structural reasoning:
// return (P some Z), if:
// - y is of the form (P some Z)
// - y is of the form ((P some Z) and ...),
// or any level of nesting
for (OWLObjectSomeValuesFrom svf :
getSomeValuesFromAncestor(anonymousSuperClass, enforceOboFormat, dataFactory)) {
newAxioms.add(dataFactory.getOWLSubClassOfAxiom(namedSubClass, svf));
}
for (OWLClass z : getNamedAncestors(anonymousSuperClass)) {
newAxioms.add(dataFactory.getOWLSubClassOfAxiom(namedSubClass, z));
}
}
/**
* Given an OWLClassExpression y, return a set of OWLObjectSomeValuesFrom objects (p some v),
* where (p some v) is a superclass of y.
*
* Not guaranteed to be complete
*
* @param x The OWLClassExpression to check.
* @param dataFactory OWLDataFactory
* @return the set of OWLObjectSomeValuesFrom objects
*/
private static Set getSomeValuesFromAncestor(
OWLClassExpression x, boolean enforceOboFormat, OWLDataFactory dataFactory) {
Set svfs = new HashSet<>();
if (x instanceof OWLObjectSomeValuesFrom) {
OWLObjectSomeValuesFrom svf = (OWLObjectSomeValuesFrom) x;
if (!enforceOboFormat || isOboFormatConformant(svf)) {
svfs.add(svf);
}
} else if (x instanceof OWLObjectCardinalityRestriction) {
OWLObjectCardinalityRestriction ocr = (OWLObjectCardinalityRestriction) x;
OWLClassExpression filler = ocr.getFiller();
OWLObjectPropertyExpression p = ocr.getProperty();
if (ocr.getCardinality() > 0) {
OWLObjectSomeValuesFrom svf = dataFactory.getOWLObjectSomeValuesFrom(p, filler);
if (!enforceOboFormat || isOboFormatConformant(svf)) {
svfs.add(svf);
}
}
} else if (x instanceof OWLObjectIntersectionOf) {
for (OWLClassExpression op : ((OWLObjectIntersectionOf) x).getOperands()) {
svfs.addAll(getSomeValuesFromAncestor(op, enforceOboFormat, dataFactory));
}
}
return svfs;
}
private static boolean isOboFormatConformant(OWLObjectSomeValuesFrom svf) {
return !svf.getProperty().isAnonymous() && !svf.getFiller().isAnonymous();
}
/**
* Given an OWLClassExpression y, return a set of named classes c, such that c is a superclass of
* y,
*
* obtained by relaxing/unwinding expression, in a way that is guaranteed valid but not
* guaranteed complete.
*
*
This is effectively poor-mans reasoning over IntersectionOf; e.g
*
*
* C SubClassOf (C and ...)
*
*
* @param x The OWLClassExpression to unwind.
* @return the set of OWLClass superclasses
*/
private static Set getNamedAncestors(OWLClassExpression x) {
Set cs = new HashSet<>();
if (!x.isAnonymous()) {
cs.add(x.asOWLClass());
} else if (x instanceof OWLObjectIntersectionOf) {
for (OWLClassExpression op : ((OWLObjectIntersectionOf) x).getOperands()) {
cs.addAll(getNamedAncestors(op));
}
}
return cs;
}
}