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

org.obolibrary.robot.ExplainOperation 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 java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import org.semanticweb.owl.explanation.api.*;
import org.semanticweb.owl.explanation.impl.blackbox.checker.InconsistentOntologyExplanationGeneratorFactory;
import org.semanticweb.owl.explanation.impl.rootderived.StructuralRootDerivedReasoner;
import org.semanticweb.owlapi.apibinding.OWLManager;
import org.semanticweb.owlapi.io.OWLObjectRenderer;
import org.semanticweb.owlapi.manchestersyntax.renderer.ManchesterOWLSyntaxOWLObjectRendererImpl;
import org.semanticweb.owlapi.model.*;
import org.semanticweb.owlapi.model.parameters.Imports;
import org.semanticweb.owlapi.reasoner.OWLReasoner;
import org.semanticweb.owlapi.reasoner.OWLReasonerFactory;
import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory;
import org.semanticweb.owlapi.util.AnnotationValueShortFormProvider;
import org.semanticweb.owlapi.util.ShortFormProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.ac.manchester.cs.owl.explanation.ProtegeExplanationOrderer;
import uk.ac.manchester.cs.owl.explanation.ordering.ExplanationOrderer;
import uk.ac.manchester.cs.owl.explanation.ordering.ExplanationTree;
import uk.ac.manchester.cs.owl.explanation.ordering.Tree;

/**
 * Compute an explanation for an entailed axiom.
 *
 * @author Jim Balhoff
 */
public class ExplainOperation {

  /** Logger */
  private static final Logger logger = LoggerFactory.getLogger(ExplainOperation.class);

  private static final OWLDataFactory df = OWLManager.getOWLDataFactory();

  /**
   * Compute explanations for an entailed axiom.
   *
   * @param axiom entailed axiom to explain
   * @param ontology ontology to search for explanation
   * @param reasonerFactory reasoner factory used to create reasoners to test entailments
   * @param maxExplanations maximum number of explanations to compute
   * @return explanations
   */
  public static Set> explain(
      OWLAxiom axiom,
      OWLOntology ontology,
      OWLReasonerFactory reasonerFactory,
      int maxExplanations) {
    logger.debug("Explaining: " + axiom);

    ExplanationGeneratorFactory genFac =
        ExplanationManager.createExplanationGeneratorFactory(reasonerFactory);
    ExplanationGenerator gen = genFac.createExplanationGenerator(ontology);
    return gen.getExplanations(axiom, maxExplanations);
  }

  /**
   * Compute explanations for inconsistent ontology
   *
   * @param ontology the ontology to be tested
   * @param reasonerFactory the reasoner factory to be used for determining the explanation
   * @param max maximum number of explanations to be computed
   * @return a set of explanations for inconsistent ontology
   */
  public static Set> explainInconsistent(
      OWLOntology ontology, OWLReasonerFactory reasonerFactory, int max) {
    InconsistentOntologyExplanationGeneratorFactory igf =
        new InconsistentOntologyExplanationGeneratorFactory(reasonerFactory, 10000);
    ExplanationGenerator generator = igf.createExplanationGenerator(ontology);
    OWLAxiom entailment = df.getOWLSubClassOfAxiom(df.getOWLThing(), df.getOWLNothing());
    return generator.getExplanations(entailment, max);
  }

  /**
   * Compute explanations for all unsatisfiable classes
   *
   * @param ontology the ontology to be tested
   * @param reasoner the reasoner to be used to determine the unsatisfiable classes
   * @param reasonerFactory the reasoner factory to be used to compute the explanations
   * @param max maximum number of explanations to be computed
   * @return a set of explanations
   */
  public static Set> explainUnsatisfiableClasses(
      OWLOntology ontology, OWLReasoner reasoner, OWLReasonerFactory reasonerFactory, int max) {
    return explainUnsatisfiableClasses(ontology, reasoner, reasonerFactory, max, -1);
  }

  /**
   * Compute explanations for all unsatisfiable classes
   *
   * @param ontology the ontology to be tested
   * @param reasoner the reasoner to be used to determine the unsatisfiable classes
   * @param reasonerFactory the reasoner factory to be used to compute the explanations
   * @param max maximum number of explanations to be computed
   * @param maxUnsat cutoff - limit number of tested unsatisfiable classes to maxUnsat classes
   * @return a set of explanations
   */
  public static Set> explainUnsatisfiableClasses(
      OWLOntology ontology,
      OWLReasoner reasoner,
      OWLReasonerFactory reasonerFactory,
      int max,
      int maxUnsat) {
    List unsatisfiable_classes =
        new ArrayList<>(reasoner.getUnsatisfiableClasses().getEntitiesMinusBottom());
    Collections.sort(unsatisfiable_classes);
    if (maxUnsat > 0 && unsatisfiable_classes.size() > maxUnsat) {
      unsatisfiable_classes = unsatisfiable_classes.subList(0, maxUnsat);
    }
    return getUnsatExplanationsForClasses(ontology, reasonerFactory, max, unsatisfiable_classes);
  }

  /**
   * Compute explanations for all root unsatisfiable classes See
   * https://github.com/matthewhorridge/owlexplanation/blob/439c5ca67835f5e421adde725e4e8a3bcd760ac8/src/main/java/org/semanticweb/owl/explanation/impl/rootderived/StructuralRootDerivedReasoner.java#L122
   *
   * @param ontology the ontology to be tested
   * @param reasoner the reasoner to be used to determine the unsatisfiable classes
   * @param reasonerFactory the reasoner factory to be used to compute the explanations
   * @param max maximum number of explanations to be computed
   * @return a set of explanations
   */
  public static Set> explainRootUnsatisfiableClasses(
      OWLOntology ontology, OWLReasoner reasoner, OWLReasonerFactory reasonerFactory, int max) {
    RootDerivedReasoner rootReasoner =
        new StructuralRootDerivedReasoner(
            ontology.getOWLOntologyManager(), reasoner, reasonerFactory);
    List unsatisfiable_classes =
        new ArrayList<>(rootReasoner.getRootUnsatisfiableClasses());
    return getUnsatExplanationsForClasses(ontology, reasonerFactory, max, unsatisfiable_classes);
  }

  /**
   * Compute explanations for all most general unsatisfiable classes Note that this is a very naive
   * implementation which assumes and acyclic class hierarchy.
   *
   * @param ontology the ontology to be tested
   * @param reasoner the reasoner to be used to determine the unsatisfiable classes
   * @param reasonerFactory the reasoner factory to be used to compute the explanations
   * @param max maximum number of explanations to be computed
   * @return a set of explanations
   */
  public static Set> explainMostGeneralUnsatisfiableClasses(
      OWLOntology ontology, OWLReasoner reasoner, OWLReasonerFactory reasonerFactory, int max) {
    List unsatisfiable_classes =
        new ArrayList<>(getMostGeneralUnsatisfiableClasses(reasoner, ontology));
    return getUnsatExplanationsForClasses(ontology, reasonerFactory, max, unsatisfiable_classes);
  }

  /**
   * Render an Explanation object as Markdown text, linking text labels to term IRIs and indenting
   * axioms.
   *
   * @param explanation explanation to render
   * @param manager OWLOntologyManager containing source ontologies for explanation axioms
   * @return Markdown-formatted explanation text
   */
  public static String renderExplanationAsMarkdown(
      Explanation explanation, OWLOntologyManager manager) {
    ExplanationOrderer orderer = new ProtegeExplanationOrderer(manager);
    ExplanationTree tree =
        orderer.getOrderedExplanation(explanation.getEntailment(), explanation.getAxioms());
    ShortFormProvider labelProvider =
        new AnnotationValueShortFormProvider(
            Collections.singletonList(OWLManager.getOWLDataFactory().getRDFSLabel()),
            Collections.emptyMap(),
            manager);
    ShortFormProvider linkProvider = new MarkdownLinkShortFormProvider(labelProvider);
    ManchesterOWLSyntaxOWLObjectRendererImpl axiomRenderer =
        new ManchesterOWLSyntaxOWLObjectRendererImpl();
    axiomRenderer.setShortFormProvider(linkProvider);
    return renderTree(tree, axiomRenderer);
  }

  public static String renderAxiomImpactSummary(
      Map axiomMap, OWLOntology ontology, OWLOntologyManager manager) {
    ShortFormProvider labelProvider =
        new AnnotationValueShortFormProvider(
            Collections.singletonList(OWLManager.getOWLDataFactory().getRDFSLabel()),
            Collections.emptyMap(),
            manager);
    ShortFormProvider linkProvider = new MarkdownLinkShortFormProvider(labelProvider);
    ManchesterOWLSyntaxOWLObjectRendererImpl axiomRenderer =
        new ManchesterOWLSyntaxOWLObjectRendererImpl();
    axiomRenderer.setShortFormProvider(linkProvider);
    Map> mapInversed = new HashMap<>();
    Map> associatedOntologyIds = new HashMap<>();
    Map ontologyIdAbbreviation = new HashMap<>();

    for (Map.Entry e : axiomMap.entrySet()) {
      associatedOntologyIds.put(e.getKey(), new HashSet<>());
      if (!mapInversed.containsKey(e.getValue())) {
        mapInversed.put(e.getValue(), new ArrayList<>());
      }
      mapInversed.get(e.getValue()).add(e.getKey());

      /*
      Determine source ontologies (if imports are present).
       */
      Set oids = getOntologyIds(e.getKey(), ontology);
      for (OWLOntologyID oid : oids) {
        if (!ontologyIdAbbreviation.containsKey(oid)) {
          String soid = getAbbreviationForOntologyID(oid);
          ontologyIdAbbreviation.put(oid, soid);
        }
        associatedOntologyIds.get(e.getKey()).add(ontologyIdAbbreviation.get(oid));
      }
    }
    List sorted = new ArrayList<>(mapInversed.keySet());
    sorted.sort(Collections.reverseOrder());
    StringBuilder sb = new StringBuilder();
    sb.append("\n\n" + "# Axiom Impact " + "\n");
    for (Integer i : sorted) {
      List l = new ArrayList<>(new HashSet<>(mapInversed.get(i)));
      Collections.sort(l);
      sb.append(renderAxiomWithImpact(l, i, axiomRenderer, associatedOntologyIds));
    }
    sb.append("\n\n" + "# Ontologies used: " + "\n");
    for (OWLOntologyID oid : ontologyIdAbbreviation.keySet()) {
      String soid = ontologyIdAbbreviation.get(oid);
      String oiri = oid.getOntologyIRI().or(IRI.create("unknown.iri")).toString();
      sb.append("- ").append(soid).append(" (").append(oiri).append(")\n");
    }
    return sb.toString();
  }

  private static int ontologyCounter = 1;

  private static String getAbbreviationForOntologyID(OWLOntologyID oid) {
    String soid = "O" + ontologyCounter;
    if (oid.getOntologyIRI().isPresent()) {
      IRI iri = oid.getOntologyIRI().get();
      String shortform = iri.getShortForm();
      if (!shortform.isEmpty()) {
        return shortform;
      } else {
        ontologyCounter++;
      }
    }
    return soid;
  }

  private static Set getOntologyIds(OWLAxiom ax, OWLOntology o) {
    Set importsClosure = new HashSet<>();
    for (OWLOntology closure : o.getImportsClosure()) {
      if (closure.getAxioms(Imports.EXCLUDED).contains(ax)) {
        importsClosure.add(closure.getOntologyID());
      }
    }
    return importsClosure;
  }

  /**
   * Render axiom tree in indented Markdown using the provided renderer.
   *
   * @param tree indented collection of axioms
   * @param renderer renderer for displaying axioms and entities
   * @return markdown string
   */
  private static String renderTree(Tree tree, OWLObjectRenderer renderer) {
    StringBuilder builder = new StringBuilder();
    if (tree.isRoot()) {
      builder.append("## ");
      builder.append(renderer.render(tree.getUserObject()));
      builder.append(" ##");
      builder.append("\n");
    } else {
      String padding =
          tree.getPathToRoot().stream().skip(1).map(x -> "  ").collect(Collectors.joining());
      builder.append(padding);
      builder.append("- ");
      builder.append(renderer.render(tree.getUserObject()));
    }
    if (!tree.isLeaf()) builder.append("\n");
    String children =
        tree.getChildren().stream()
            .map(child -> renderTree(child, renderer))
            .collect(Collectors.joining("\n"));
    builder.append(children);
    return builder.toString();
  }

  private static String renderAxiomWithImpact(
      List axioms,
      int impact,
      OWLObjectRenderer renderer,
      Map> associatedOntologyIds) {
    StringBuilder builder = new StringBuilder();
    builder.append("## Axioms used ").append(impact).append(" times").append("\n");
    for (OWLAxiom ax : axioms) {
      builder
          .append("- ")
          .append(renderer.render(ax))
          .append(" [")
          .append(String.join(",", associatedOntologyIds.get(ax)))
          .append("]\n");
    }
    builder.append("\n");
    return builder.toString();
  }

  private static Set> getUnsatExplanationsForClasses(
      OWLOntology ontology,
      OWLReasonerFactory reasonerFactory,
      int max,
      List unsatisfiable_classes) {
    Set> explanations = new HashSet<>();
    for (OWLClass unsat_cl : unsatisfiable_classes) {
      OWLAxiom axiom = df.getOWLSubClassOfAxiom(unsat_cl, df.getOWLNothing());
      explanations.addAll(ExplainOperation.explain(axiom, ontology, reasonerFactory, max));
    }
    return explanations;
  }

  private static Set getMostGeneralUnsatisfiableClasses(
      OWLReasoner reasoner, OWLOntology ontology) {
    Set mgu = new HashSet<>();
    OWLReasoner structural = new StructuralReasonerFactory().createReasoner(ontology);
    Set unsats =
        new HashSet<>(reasoner.getUnsatisfiableClasses().getEntitiesMinusBottom());
    for (OWLClass c : unsats) {
      Set superclasses = structural.getSuperClasses(c, false).getFlattened();
      superclasses.retainAll(unsats);
      if (superclasses.isEmpty()) {
        // If there is no superclass in the ontology according to the structural reasoner which is
        // also unsatisfiable, we consider C a "most general unsatisfiable class".
        mgu.add(c);
      }
    }
    return mgu;
  }

  /**
   * A ShortFormProvider which renders entities as Markdown links, using another provided
   * ShortFormProvider to render entity labels.
   */
  private static class MarkdownLinkShortFormProvider implements ShortFormProvider {

    final ShortFormProvider labelProvider;

    public MarkdownLinkShortFormProvider(ShortFormProvider labelProvider) {
      this.labelProvider = labelProvider;
    }

    @Nonnull
    @Override
    public String getShortForm(@Nonnull OWLEntity entity) {
      String label = labelProvider.getShortForm(entity);
      return "[" + label + "](" + entity.getIRI().toString() + ")";
    }

    @Override
    public void dispose() {}
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy