All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.obolibrary.robot.ReduceOperation Maven / Gradle / Ivy

Go to download

Core library for ROBOT: Library for working with OWL ontologies, especially Open Biological and Biomedical Ontologes (OBO).

The newest version!
package org.obolibrary.robot;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import java.util.*;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.model.*;
import org.semanticweb.owlapi.model.parameters.Imports;
import org.semanticweb.owlapi.reasoner.Node;
import org.semanticweb.owlapi.reasoner.NodeSet;
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), where Z is entailed to be a * proper SubClassOf of D (direct or indirect), then A is redundant, and removed. * *

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 { /** Logger. */ 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("preserve-annotated-axioms", "false"); options.put("named-classes-only", "false"); return options; } /** * Remove redundant SubClassOf axioms. * * @param ontology The ontology to reduce. * @param reasonerFactory The reasoner factory to use. * @throws OWLOntologyCreationException on ontology problem */ public static void reduce(OWLOntology ontology, OWLReasonerFactory reasonerFactory) throws OWLOntologyCreationException { reduce(ontology, reasonerFactory, getDefaultOptions()); } /** * Remove redundant SubClassOf axioms. * * @param ontology The ontology to reduce. * @param reasonerFactory The reasoner factory to use. * @param options A map of options for the operation. * @throws OWLOntologyCreationException on ontology problem */ public static void reduce( OWLOntology ontology, OWLReasonerFactory reasonerFactory, Map options) throws OWLOntologyCreationException { boolean preserveAnnotatedAxioms = OptionsHelper.optionIsTrue(options, "preserve-annotated-axioms"); boolean namedClassesOnly = OptionsHelper.optionIsTrue(options, "named-classes-only"); if (namedClassesOnly) { reduceNamedOnly(ontology, reasonerFactory, preserveAnnotatedAxioms); } else { reduceAllClassExpressions(ontology, reasonerFactory, preserveAnnotatedAxioms); } } /** * Remove redundant SubClassOf axioms. * * @param ontology The ontology to reduce. * @param reasonerFactory The reasoner factory to use. * @param preserveAnnotatedAxioms Whether to not remove redundant, but annotated, axioms. * @throws OWLOntologyCreationException on ontology problem */ private static void reduceAllClassExpressions( OWLOntology ontology, OWLReasonerFactory reasonerFactory, boolean preserveAnnotatedAxioms) throws OWLOntologyCreationException { OWLOntologyManager manager = OWLManager.createOWLOntologyManager(); OWLDataFactory dataFactory = manager.getOWLDataFactory(); // we treat an axiom as redundant if its is redundant within the // subClassOf graph, including OP characteristic axioms (e.g. transitivity) OWLOntology subOntology = manager.createOntology(); for (OWLAxiom a : ontology.getAxioms(Imports.INCLUDED)) { if (a instanceof OWLSubClassOfAxiom || a instanceof OWLObjectPropertyCharacteristicAxiom) { manager.addAxiom(subOntology, a); } } Map> assertedSubClassMap = new HashMap<>(); Set assertedSubClassAxioms = ontology.getAxioms(AxiomType.SUBCLASS_OF); // TODO - exprs is updated but never used Set exprs = new HashSet<>(); Map exprToNamedClassMap = new HashMap<>(); for (OWLSubClassOfAxiom ax : assertedSubClassAxioms) { OWLClass subClass = mapClass(dataFactory, exprToNamedClassMap, ax.getSubClass()); OWLClass superClass = mapClass(dataFactory, exprToNamedClassMap, ax.getSuperClass()); if (!assertedSubClassMap.containsKey(subClass)) { assertedSubClassMap.put(subClass, new HashSet<>()); } assertedSubClassMap.get(subClass).add(superClass); // DEP if (ax.getSubClass().isAnonymous()) { exprs.add(ax.getSubClass()); } if (ax.getSuperClass().isAnonymous()) { exprs.add(ax.getSuperClass()); } } for (OWLClassExpression x : exprToNamedClassMap.keySet()) { OWLAxiom ax = dataFactory.getOWLEquivalentClassesAxiom(exprToNamedClassMap.get(x), x); manager.addAxiom(subOntology, ax); } // TO DO: DRY - move to ReasonerOperation module OWLReasoner reasoner = reasonerFactory.createReasoner(subOntology); 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()); } } } // Constructing an inverse map of exprToNamedClassMap, which will be generated and used for the // purpose of debugging only. Multimap revExprToNamedClassMap = HashMultimap.create(); if (logger.isDebugEnabled()) { for (Map.Entry entry : exprToNamedClassMap.entrySet()) { revExprToNamedClassMap.put(entry.getValue(), entry.getKey()); } } Set rmAxioms = new HashSet<>(); for (OWLSubClassOfAxiom ax : assertedSubClassAxioms) { if (preserveAnnotatedAxioms) { if (ax.getAnnotations().size() > 0) { logger.debug("Protecting axiom with annotations: " + ax); continue; } } logger.debug("Testing: " + ax); OWLClassExpression subClassExpr = ax.getSubClass(); OWLClassExpression superClassExpr = ax.getSuperClass(); OWLClass subClass = exprToNamedClassMap.get(subClassExpr); OWLClass superClass = exprToNamedClassMap.get(superClassExpr); boolean isRedundant = false; for (OWLClass assertedSuper : assertedSubClassMap.get(subClass)) { if (reasoner.getSuperClasses(assertedSuper, false).containsEntity(superClass)) { isRedundant = true; // (Previous) DUMB CODE FOR DEBUGGING /* OWLClassExpression assertedSuperX = assertedSuper; for (OWLClassExpression x : exprToNamedClassMap.keySet()) { if (exprToNamedClassMap.get(x).equals(assertedSuper)) { assertedSuperX = x; } } */ // Optimized codes that will run only if the debugging mode is enabled if (logger.isDebugEnabled()) { Collection classExprs = revExprToNamedClassMap.get(assertedSuper); for (OWLClassExpression assertedSuperX : classExprs) { logger.debug( "Redundant: " + ax + ", because " + assertedSuper + "(" + assertedSuperX + ") " + " subClassOf " + superClass + " (" + superClassExpr + ")"); } } break; } } // Special case for GCIs if (subClassExpr.isAnonymous()) { logger.debug("GCI:" + subClassExpr); for (OWLClass intermediateParent : reasoner.getSuperClasses(subClass, false).getFlattened()) { if (assertedSubClassMap.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); } } // remove redundant axiom for (OWLAxiom ax : rmAxioms) { manager.removeAxiom(ontology, ax); } reasoner.dispose(); } /** * Map a class expression to an equivalent named class; creates temp class plus axiom if not * already present. * * @param dataFactory A datafactory for creating the mapped class expression * @param rxmap A map from class expressions to classes * @param x The OWLClassExpression to map * @return the mapped OWLClass */ 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.debug(c + " ==> " + x); rxmap.put(x, c); } else { rxmap.put(x, (OWLClass) x); } } return rxmap.get(x); } /** * Remove redundant SubClassOf axioms, only considering named classes. When only considering named * classes, a somewhat more efficient algorithm can be used. * * @param ontology The ontology to reduce. * @param reasonerFactory The reasoner factory to use. * @param preserveAnnotatedAxioms Whether to not remove redundant, but annotated, axioms. */ private static void reduceNamedOnly( OWLOntology ontology, OWLReasonerFactory reasonerFactory, boolean preserveAnnotatedAxioms) { // Map> Map>> assertions = new HashMap<>(); Set assertedSubClassAxioms = ontology.getAxioms(AxiomType.SUBCLASS_OF); for (OWLSubClassOfAxiom ax : assertedSubClassAxioms) { if (!ax.getSubClass().isAnonymous() && !ax.getSuperClass().isAnonymous()) { OWLClass subclass = ax.getSubClass().asOWLClass(); OWLClass superclass = ax.getSuperClass().asOWLClass(); if (!assertions.containsKey(superclass)) { assertions.put(superclass, new HashMap<>()); } Map> subMap = assertions.get(superclass); if (!subMap.containsKey(subclass)) { subMap.put(subclass, new HashSet<>()); } Set axioms = subMap.get(subclass); axioms.add(ax); } } 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 nonredundant = new HashSet<>(); Set> alreadySeen = new HashSet<>(); findNonRedundant(reasoner.getTopClassNode(), reasoner, assertions, nonredundant, alreadySeen); OWLOntologyManager manager = ontology.getOWLOntologyManager(); for (OWLSubClassOfAxiom ax : assertedSubClassAxioms) { if (!ax.getSubClass().isAnonymous() && !ax.getSuperClass().isAnonymous()) { if (preserveAnnotatedAxioms) { if (ax.getAnnotations().size() > 0) { logger.debug("Protecting axiom with annotations: " + ax); continue; } } if (!nonredundant.contains(ax)) { manager.removeAxiom(ontology, ax); } } } reasoner.dispose(); } private static void findNonRedundant( Node node, OWLReasoner reasoner, Map>> assertions, Set nonredundant, Set> alreadySeen) { if (!alreadySeen.contains(node)) { NodeSet subclasses = reasoner.getSubClasses(node.getRepresentativeElement(), true); for (OWLClass superclass : node.getEntities()) { for (OWLClass subclass : subclasses.getFlattened()) { if (assertions.containsKey(superclass)) { Map> subclassAxiomsBySubclass = assertions.get(superclass); if (subclassAxiomsBySubclass.containsKey(subclass)) { nonredundant.addAll(subclassAxiomsBySubclass.get(subclass)); } } } } alreadySeen.add(node); for (Node subclassNode : subclasses.getNodes()) { findNonRedundant(subclassNode, reasoner, assertions, nonredundant, alreadySeen); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy