org.obolibrary.robot.ExplainOperation Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of robot-core Show documentation
Show all versions of robot-core Show documentation
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