diff --git a/.gitignore b/.gitignore index ea8c4bf..628a391 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /target +semantic.cache +LOG* diff --git a/bin/emr b/bin/emr new file mode 100755 index 0000000..269c9d9 --- /dev/null +++ b/bin/emr @@ -0,0 +1,147 @@ +#!/bin/sh +DIRNAME=`dirname $0` +#PATH_TO_ME=`which $0`; +# Give a space-separated list of classpath items RELATIVE TO THE CURRENT SCRIPT +# These will be resolved into absolute pathnames +# Wildcards are allowed +CLASSPATH_RELATIVE=emr.jar + +## Check for Cygwin, use grep for a case-insensitive search +IS_CYGWIN="FALSE" +if uname | grep -iq cygwin; then + IS_CYGWIN="TRUE" +fi + +# If literal classpath values are needed, uncomment the line below +# This can be useful if the classpath contains URLs +# CLASSPATH_LITERAL="" + +# To set a specific default Java path, set the JAVAPATH variable below. +# This value can be overridden with the -Jvm= option. +# If JAVAPATH is not set, the script will use whatever version of Java is on the +# path. If there is no copy of Java on the path, the JAVA_HOME environment +# variable will be used. If that fails, we just use "java" in the hopes that the +# failure message will make a little more sense. +# JAVAPATH="java" + +if [ $IS_CYGWIN = "TRUE" ] +then + PATH_SEP=";" +else + PATH_SEP=":" +fi + +JAVAARGS=" " +CMDARGS=" " + +# Remove the name of this script from the end of the path +PATH_TO_ME=`which $0`; +PATH_TO_ME=`echo $PATH_TO_ME | sed -e "s/\(.*\)\/.*/\1/g"` + +if [ $IS_CYGWIN = "TRUE" ] +then + LAUNCHER_DIR="`cygpath --mixed $PATH_TO_ME`" +else + LAUNCHER_DIR="$PATH_TO_ME" +fi + + +# Just the name of the script. +SCRIPTNAME=`echo $PATH_TO_ME | sed -e "s/.*\/\(.*\)/\1/g"` + +## Add vmoptions to JAVAARGS if the proper file is available. +if [ -e "$PATH_TO_ME/$SCRIPTNAME.vmoptions" ] +then + VMOPTIONS=`cat $PATH_TO_ME/$SCRIPTNAME.vmoptions` + for OPTION in "$VMOPTIONS" + do + JAVAARGS="$JAVAARGS '${OPTION}'" + done +else + if [ $EMR_MEMORY ] + then + JAVAARGS="$JAVAARGS -Xmx$EMR_MEMORY" + else + JAVAARGS="$JAVAARGS -Xmx12G" + fi +fi + +## Walk through all the command line arguments and add them to +## the CMDARGS depending. + +for ARG in "$@" +do + CMDARGS="${CMDARGS} '${ARG}'" + shift 1; +done + +## Add to CLASSPATH using CLASSPATH_RELATIVE. +CLASSPATH="" +for ARG in "$CLASSPATH_RELATIVE" +do + DEREFERENCED_CLASSPATH=`ls -1 -L $PATH_TO_ME/$ARG | grep -v ontologyrelease` + for CP_ENTRY in $DEREFERENCED_CLASSPATH + do + if [ $IS_CYGWIN = "TRUE" ] + then + CP_ENTRY="`cygpath --mixed \"$CP_ENTRY\"`" + fi + if [ -z "$CLASSPATH" ] + then + CLASSPATH="$CP_ENTRY" + else + CLASSPATH="$CLASSPATH$PATH_SEP$CP_ENTRY" + fi + done +done + +## Add to CLASSPATH using CLASSPATH_LITERAL. +if [ -n "$CLASSPATH_LITERAL" ] +then + for CP_ENTRY in $CLASSPATH_LITERAL + do + if [ -z "$CLASSPATH" ] + then + CLASSPATH="$CP_ENTRY" + else + CLASSPATH="$CLASSPATH$PATH_SEP$CP_ENTRY" + fi + done +fi + +## Figure out which java to use. +if [ -z "$JAVAPATH" ] +then + JAVAPATH=`which java` + if [ -z "$JAVAPATH" ] + then + if [ -n "$JAVA_HOME" && -e "$JAVA_HOME" ] + then + JAVAPATH=$JAVA_HOME/bin/java + else + JAVAPATH="java" + fi + fi +fi + +## Assemble and run the final command. +CMD="\"$JAVAPATH\" -Xms2048M -DentityExpansionLimit=4086000 -Djava.awt.headless=true -classpath \"$CLASSPATH\" -DlauncherDir=\"$LAUNCHER_DIR\" $JAVAARGS org.geneontology.reasoner.EmrRunner $CMDARGS" + +## DEBUG +## Let's see a little of the environment if we declare DEBUG=1. +if [ $DEBUG ] +then + for MYVAR in DEBUG PATH_TO_ME SCRIPTNAME DIRNAME JAVAARGS CMDARGS CLASSPATH_RELATIVE CMD + do + LETVAR=`echo "$"$MYVAR` + TMPVAR=`eval echo $LETVAR` + echo "${MYVAR}: \"${TMPVAR}\"" + done +fi + +## Run the final command. +if [ $DEBUG ] +then + echo "$CMD" +fi +sh -c "$CMD" diff --git a/pom.xml b/pom.xml index 4b5c6a1..9dcf754 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.geneontology expression-materializing-reasoner - 0.1.3 + 0.2.0 jar ${project.groupId}:${project.artifactId} An OWL reasoner extension for quering over existential restrictions @@ -38,7 +38,7 @@ HEAD - + UTF-8 4.2.5 @@ -51,6 +51,35 @@ + + + org.apache.maven.plugins + maven-assembly-plugin + + emr + false + false + ${project.basedir}/bin + + jar-with-dependencies + + + + true + org.geneontology.reasoner.EmrRunner + + + + + + make-assembly + package + + single + + + + org.sonatype.plugins nexus-staging-maven-plugin @@ -150,7 +179,6 @@ org.semanticweb.elk elk-owlapi 0.4.3 - test net.sourceforge.owlapi @@ -159,10 +187,9 @@ test - org.slf4j - slf4j-log4j12 - 1.7.10 - test + com.beust + jcommander + 1.48 diff --git a/src/main/java/org/geneontology/reasoner/EmrRunner.java b/src/main/java/org/geneontology/reasoner/EmrRunner.java new file mode 100644 index 0000000..7b596dc --- /dev/null +++ b/src/main/java/org/geneontology/reasoner/EmrRunner.java @@ -0,0 +1,154 @@ +package org.geneontology.reasoner; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.semanticweb.elk.owlapi.ElkReasonerFactory; +import org.semanticweb.owlapi.apibinding.OWLManager; +import org.semanticweb.owlapi.formats.FunctionalSyntaxDocumentFormat; +import org.semanticweb.owlapi.model.IRI; +import org.semanticweb.owlapi.model.OWLDataFactory; +import org.semanticweb.owlapi.model.OWLObjectProperty; +import org.semanticweb.owlapi.model.OWLOntology; +import org.semanticweb.owlapi.model.OWLOntologyCreationException; +import org.semanticweb.owlapi.model.OWLOntologyManager; +import org.semanticweb.owlapi.model.OWLOntologyStorageException; +import org.semanticweb.owlapi.model.OWLSubClassOfAxiom; + +import com.beust.jcommander.JCommander; +import com.beust.jcommander.Parameter; + +/** + * Command line wrapper + * + * @author cjm + * + */ +public class EmrRunner { + + private static Logger LOG = Logger.getLogger(EmrRunner.class); + + @Parameter(names = { "-v", "--verbose" }, description = "Level of verbosity") + private Integer verbose = 1; + + @Parameter(names = { "-o", "--out"}, description = "output OWL file") + private String outpath; + + @Parameter(names = { "-p", "--properties"}, description = "OWL file containing declarations of properties to be used") + private String propOntPath; + + @Parameter(names = { "-m", "--method"}, description = "one of: closure, svf") + private String method = "closure"; + + @Parameter(names = { "-t", "--tabfile"}, description = "output tabfile") + private String outtabfile; + + @Parameter(description = "Files") + private List files = new ArrayList<>(); + + OWLOntologyManager manager; + + + public static void main(String ... args) throws OWLOntologyCreationException, IOException, OWLOntologyStorageException { + EmrRunner main = new EmrRunner(); + new JCommander(main, args); + main.run(); + } + + public void run() throws OWLOntologyCreationException, IOException, OWLOntologyStorageException { + + Logger.getRootLogger().setLevel(Level.INFO); + Logger.getLogger("org.semanticweb.elk").setLevel(Level.OFF); + + OWLOntology ontology = this.loadOWL(files.get(0)); + + OWLOntology propOnt = null; + Set props = null; + if (propOntPath != null) { + propOnt = loadOWL(propOntPath); + props = propOnt.getObjectPropertiesInSignature(); + LOG.info("PROPS: "+props); + } + + ElkReasonerFactory ef = new ElkReasonerFactory(); + OWLExtendedReasonerFactory elkEmrFactory; + elkEmrFactory = new ExpressionMaterializingReasonerFactory(ef); + LOG.info("creating extended reasoner"); + OWLExtendedReasoner xreasoner = elkEmrFactory.createReasoner(ontology); + LOG.info("Done creating reasoner"); + + LOG.info("materializing props"); + if (props != null) { + for (OWLObjectProperty p : props) { + ((ExpressionMaterializingReasoner)xreasoner).materializeExpressions(p); + } + } + else { + LOG.info("materializing ALL props"); + ((ExpressionMaterializingReasoner)xreasoner).materializeExpressions(); + } + LOG.info("DONE materializing props"); + + + OWLOntologyManager mgr = ontology.getOWLOntologyManager(); + OWLDataFactory df = mgr.getOWLDataFactory(); + OWLOntology newOntology = mgr.createOntology(); + newOntology = mgr.createOntology(); + if (method.equals("svf")) { + LOG.info("Getting all subsumptions"); + Set axioms = ExtenderReasonerUtils.getInferredSubClassOfGCIAxioms(xreasoner, props); + mgr.addAxioms(newOntology, axioms); + if (outtabfile != null) { + ExtenderReasonerUtils.writeInferredSubClassOfGCIAxioms(axioms, outtabfile); + } + } + else { + // assume method is closure + Set axioms = ExtenderReasonerUtils.getInferredSubClassOfAxiomsForNamedClasses(xreasoner, false); + mgr.addAxioms(newOntology, axioms); + } + File outfile = new File(outpath); + LOG.info("Saving to "+outfile); + System.out.println("Saving to "+outfile); + FunctionalSyntaxDocumentFormat fmt = new FunctionalSyntaxDocumentFormat(); + mgr.saveOntology(newOntology, fmt, IRI.create(outfile)); + + } + + private OWLOntologyManager getOWLOntologyManager() { + if (manager == null) + manager = OWLManager.createOWLOntologyManager(); + return manager; + } + /** + * @param iri + * @return OWL Ontology + * @throws OWLOntologyCreationException + */ + public OWLOntology loadOWL(IRI iri) throws OWLOntologyCreationException { + return getOWLOntologyManager().loadOntology(iri); + } + + public OWLOntology loadOWL(String path) throws OWLOntologyCreationException { + File file = new File(path); + return loadOWL(file); + } + + + + + /** + * @param file + * @return OWL Ontology + * @throws OWLOntologyCreationException + */ + public OWLOntology loadOWL(File file) throws OWLOntologyCreationException { + IRI iri = IRI.create(file); + return getOWLOntologyManager().loadOntologyFromOntologyDocument(iri); + } +} diff --git a/src/main/java/org/geneontology/reasoner/ExpressionMaterializingReasoner.java b/src/main/java/org/geneontology/reasoner/ExpressionMaterializingReasoner.java index f9b061f..9fb622a 100644 --- a/src/main/java/org/geneontology/reasoner/ExpressionMaterializingReasoner.java +++ b/src/main/java/org/geneontology/reasoner/ExpressionMaterializingReasoner.java @@ -8,6 +8,8 @@ import java.util.Map; import java.util.Set; +import org.apache.log4j.Logger; +import org.semanticweb.elk.owlapi.ElkReasoner; import org.semanticweb.owlapi.model.AddImport; import org.semanticweb.owlapi.model.AxiomType; import org.semanticweb.owlapi.model.IRI; @@ -17,17 +19,20 @@ import org.semanticweb.owlapi.model.OWLDataFactory; import org.semanticweb.owlapi.model.OWLDataProperty; import org.semanticweb.owlapi.model.OWLDataPropertyExpression; +import org.semanticweb.owlapi.model.OWLDisjointClassesAxiom; import org.semanticweb.owlapi.model.OWLEquivalentClassesAxiom; import org.semanticweb.owlapi.model.OWLLiteral; import org.semanticweb.owlapi.model.OWLNamedIndividual; import org.semanticweb.owlapi.model.OWLObjectProperty; import org.semanticweb.owlapi.model.OWLObjectPropertyExpression; +import org.semanticweb.owlapi.model.OWLObjectPropertyRangeAxiom; import org.semanticweb.owlapi.model.OWLObjectSomeValuesFrom; import org.semanticweb.owlapi.model.OWLOntology; import org.semanticweb.owlapi.model.OWLOntologyChange; import org.semanticweb.owlapi.model.OWLOntologyCreationException; import org.semanticweb.owlapi.model.OWLOntologyID; import org.semanticweb.owlapi.model.OWLOntologyManager; +import org.semanticweb.owlapi.model.OWLSubClassOfAxiom; import org.semanticweb.owlapi.model.SetOntologyID; import org.semanticweb.owlapi.model.UnknownOWLOntologyException; import org.semanticweb.owlapi.model.parameters.Imports; @@ -65,14 +70,17 @@ * to expressions of depth k * * In terms of performance the biggest impact are the number of {@link OWLObjectProperty} - * for which the materialization is required. + * for which the materialization is required. + * * It is usually *NOT* recommended to use all properties of an ontology signature. + * Instead call materializeExpressions * * @author cjm * */ public class ExpressionMaterializingReasoner extends OWLReasonerBase implements OWLExtendedReasoner { + private static Logger LOG = Logger.getLogger(ExpressionMaterializingReasoner.class); private OWLReasoner wrappedReasoner; private final OWLDataFactory dataFactory; @@ -80,7 +88,10 @@ public class ExpressionMaterializingReasoner extends OWLReasonerBase implements private final OWLOntology expandedOntology; final OWLOntologyManager manager; final Set cachedProperties; - private final Map cxMap; + + // map between materialized named classes and their class expression equivalent + private final Map cxMap; + private final Map cxMapRev; private boolean includeImports = false; @@ -101,14 +112,89 @@ protected ExpressionMaterializingReasoner(OWLOntology rootOntology, wrappedReasoner = reasonerFactory.createNonBufferingReasoner(expandedOntology, configuration); } - } catch (UnknownOWLOntologyException e) { throw new RuntimeException("Could not setup reasoner", e); } catch (OWLOntologyCreationException e) { throw new RuntimeException("Could not setup reasoner", e); } cachedProperties = new HashSet(); - cxMap = new HashMap(); + cxMap = new HashMap(); + cxMapRev = new HashMap(); + + // we can consider making this optional, but in general it seems to have + // no drawback in making inference slower + if (wrappedReasoner instanceof ElkReasoner) { + rewriteRangeAxioms(); + } + } + + + + /** + * Range axioms are very useful for curtailing the space of possible SVF expressions. + * E.g. forbid occurs-in SOME apoptosis + * + * Elk does not support Range axioms, so we use the range axioms to generate + * additional axioms that while not complete give us many benefits. + * + * E.g. if we have: + * - Range(occurs-in, material-entity) + * - material-entity SubClassOf* continuant + * - continuant DisjointWith occurrent + * + * then we can add an axiom + * Nothing = occurs-in some continuant + * + * Adding these do not appear to increase overall time for ontologies such as go-plus, + * and overall running is faster due to less expressions being generated. + * + * + * + */ + public void rewriteRangeAxioms() { + OWLClass nothing = dataFactory.getOWLNothing(); + Set newAxioms = new HashSet<>(); + Set raxs = new HashSet<>(); + Map> disjointMap = new HashMap<>(); + for (OWLAxiom ax : rootOntology.getAxioms(Imports.INCLUDED)) { + if (ax instanceof OWLDisjointClassesAxiom) { + OWLDisjointClassesAxiom da = (OWLDisjointClassesAxiom)ax; + for (OWLClassExpression c1 : da.getClassExpressions()) { + if (!disjointMap.containsKey(c1)) + disjointMap.put(c1, new HashSet<>()); + for (OWLClassExpression c2 : da.getClassExpressionsMinus(c1)) { + disjointMap.get(c1).add(c2); + } + + } + } + } + for (OWLAxiom ax : rootOntology.getAxioms(Imports.INCLUDED)) { + if (ax instanceof OWLObjectPropertyRangeAxiom) { + OWLObjectPropertyRangeAxiom rax = (OWLObjectPropertyRangeAxiom)ax; + raxs.add(rax); + OWLClassExpression rc = rax.getRange(); + // elk does not support disjoint classes, so we follow hierarchy + Set disjointClasses = new HashSet<>(); + Set rscs = + wrappedReasoner.getSuperClasses(rc, false).getFlattened(); + for (OWLClass rsc : rscs) { + if (disjointMap.containsKey(rsc)) + disjointClasses.addAll(disjointMap.get(rsc)); + } + if (disjointMap.containsKey(rc)) { + disjointClasses.addAll(disjointMap.get(rc)); + } + + for (OWLClassExpression d : disjointClasses) { + OWLEquivalentClassesAxiom cax = dataFactory.getOWLEquivalentClassesAxiom(nothing, + dataFactory.getOWLObjectSomeValuesFrom(rax.getProperty(), d)); + newAxioms.add(cax); + LOG.info("Translated "+rax+" --> "+cax); + } + } + } + expandedOntology.getOWLOntologyManager().addAxioms(expandedOntology, newAxioms); } /** @@ -138,6 +224,7 @@ private OWLOntology createExpandedOntologyStub(OWLOntology rootOntology) AddImport ai = new AddImport(expandedOntology, dataFactory.getOWLImportsDeclaration(rootOntologyIRI)); manager.applyChange(ai); + return expandedOntology; } @@ -173,6 +260,7 @@ public boolean isIncludeImports() { * @see ExpressionMaterializingReasoner#setIncludeImports(boolean) if it should include imports */ public void materializeExpressions() { + LOG.info("Materializing SVFs for ALL properties"); materializeExpressions(rootOntology.getObjectPropertiesInSignature(Imports.fromBoolean(includeImports))); } @@ -183,6 +271,7 @@ public void materializeExpressions() { * @see ExpressionMaterializingReasoner#setIncludeImports(boolean) if it should include imports */ public void materializeExpressions(Collection properties) { + LOG.info("Materializing SVFs for: "+properties); for (OWLObjectProperty p : properties) { if (cachedProperties.contains(p)){ continue; @@ -206,6 +295,8 @@ public void materializeExpressions(OWLObjectProperty p) { } private void materializeExpressionsInternal(OWLObjectProperty p) { + LOG.info("Materializing SVFs for: "+p); + for (OWLClass baseClass : rootOntology.getClassesInSignature(Imports.fromBoolean(includeImports))) { // only materialize for non-helper classes if (cxMap.containsKey(baseClass)) { @@ -213,13 +304,14 @@ private void materializeExpressionsInternal(OWLObjectProperty p) { } OWLObjectSomeValuesFrom x = dataFactory.getOWLObjectSomeValuesFrom(p, baseClass); IRI xciri = IRI.create(baseClass.getIRI()+"__"+saveIRItoString(p.getIRI())); - OWLClass xc = dataFactory.getOWLClass(xciri); - OWLEquivalentClassesAxiom eca = dataFactory.getOWLEquivalentClassesAxiom(xc, x); + OWLClass namedClassEquivalent = dataFactory.getOWLClass(xciri); + OWLEquivalentClassesAxiom eca = dataFactory.getOWLEquivalentClassesAxiom(namedClassEquivalent, x); String lbl = p.getIRI().getShortForm()+" "+baseClass.getIRI().getShortForm(); manager.addAxiom(expandedOntology, dataFactory.getOWLAnnotationAssertionAxiom(xciri, dataFactory.getOWLAnnotation(dataFactory.getRDFSLabel(), dataFactory.getOWLLiteral(lbl)))); - cxMap.put(xc, x); + cxMap.put(namedClassEquivalent, x); + cxMapRev.put(x, namedClassEquivalent); manager.addAxiom(expandedOntology, eca); - manager.addAxiom(expandedOntology, dataFactory.getOWLDeclarationAxiom(xc)); + manager.addAxiom(expandedOntology, dataFactory.getOWLDeclarationAxiom(namedClassEquivalent)); } cachedProperties.add(p); } @@ -254,6 +346,57 @@ public Set getSuperClassExpressions(OWLClassExpression ce, } return ces; } + + public Set getSuperClassExpressionsForGCI(OWLClass baseClass, + OWLObjectProperty p, + boolean direct) throws InconsistentOntologyException, + ClassExpressionNotInProfileException, FreshEntitiesException, + ReasonerInterruptedException, TimeOutException { + return getSomeValuesFromSuperClasses(baseClass, p, direct, false); + } + + boolean isIssuedAnonWarning = false; + public Set getSomeValuesFromSuperClasses(OWLClass baseClass, + OWLObjectProperty p, + boolean direct, boolean reflexive) throws InconsistentOntologyException, + ClassExpressionNotInProfileException, FreshEntitiesException, + ReasonerInterruptedException, TimeOutException { + + Set ces = new HashSet<>(); + wrappedReasoner.flush(); + + OWLObjectSomeValuesFrom svf = dataFactory.getOWLObjectSomeValuesFrom(p, baseClass); + OWLClassExpression queryExpression = svf; + // for efficiency, used a generated named class if available. + // Explanation: Elk is highly inefficient if queried using a class expression, as + // it needs to generate a new class and test with this. It's unnecessary to force Elk to + // do this, if we already have a NC equivalent for the query expression + // TODO: make a PR on Elk code + if (cxMapRev.containsKey(svf)) { + queryExpression = cxMapRev.get(svf); + } + else { + if (!isIssuedAnonWarning) { + LOG.warn("Unknown svf "+svf+" this forces Elk to make a new class which may be slow..."); + } + isIssuedAnonWarning = true; + } + if (!wrappedReasoner.isSatisfiable(queryExpression)) { + return ces; + } + for (OWLClass c : wrappedReasoner.getSuperClasses(queryExpression, direct).getFlattened()) { + if (cxMap.containsKey(c)) { + OWLObjectSomeValuesFrom cx = cxMap.get(c); + ces.add(cx); + } + } + if (reflexive) { + ces.add(svf); + } + return ces; + } + + public Set getSuperClassesOver(OWLClassExpression ce, OWLObjectProperty p, @@ -321,7 +464,8 @@ public Set getPendingAxiomRemovals() { } public OWLOntology getRootOntology() { - return wrappedReasoner.getRootOntology(); + return rootOntology; + //return wrappedReasoner.getRootOntology(); // note that additional classes injected here } public void interrupt() { diff --git a/src/main/java/org/geneontology/reasoner/ExtenderReasonerUtils.java b/src/main/java/org/geneontology/reasoner/ExtenderReasonerUtils.java new file mode 100644 index 0000000..38df51a --- /dev/null +++ b/src/main/java/org/geneontology/reasoner/ExtenderReasonerUtils.java @@ -0,0 +1,146 @@ +package org.geneontology.reasoner; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.io.FileUtils; +import org.apache.log4j.Logger; +import org.semanticweb.owlapi.model.OWLClass; +import org.semanticweb.owlapi.model.OWLClassExpression; +import org.semanticweb.owlapi.model.OWLDataFactory; +import org.semanticweb.owlapi.model.OWLEntity; +import org.semanticweb.owlapi.model.OWLObject; +import org.semanticweb.owlapi.model.OWLObjectProperty; +import org.semanticweb.owlapi.model.OWLObjectSomeValuesFrom; +import org.semanticweb.owlapi.model.OWLOntology; +import org.semanticweb.owlapi.model.OWLSubClassOfAxiom; +import org.semanticweb.owlapi.model.parameters.Imports; +import org.semanticweb.owlapi.reasoner.ClassExpressionNotInProfileException; +import org.semanticweb.owlapi.reasoner.FreshEntitiesException; +import org.semanticweb.owlapi.reasoner.InconsistentOntologyException; +import org.semanticweb.owlapi.reasoner.ReasonerInterruptedException; +import org.semanticweb.owlapi.reasoner.TimeOutException; + +/** + * @author cjm + * + */ +public class ExtenderReasonerUtils { + + private static Logger LOG = Logger.getLogger(ExtenderReasonerUtils.class); + + /** + * finds all inferred SubClassOf(C, R some D) using extended reasoner + * + * @param xr + * @param direct + * @return inferred axioms + */ + public static Set getInferredSubClassOfAxiomsForNamedClasses(OWLExtendedReasoner xr, boolean direct) { + Set axioms = new HashSet<>(); + OWLOntology ont = xr.getRootOntology(); + OWLDataFactory df = ont.getOWLOntologyManager().getOWLDataFactory(); + Set cs = ont.getClassesInSignature(Imports.INCLUDED); + for (OWLClass c : cs) { + + for (OWLClassExpression sc : xr.getSuperClassExpressions(c, direct)) { + axioms.add(df.getOWLSubClassOfAxiom(c, sc)); + } + + } + return axioms; + } + + + /** + * Returns all axioms (p some c) SubClassOf (q some s) + * for a given p + * + * @param xr + * @param p + * @return subClassOf axioms between someValuesFrom expressions + * @throws InconsistentOntologyException + * @throws ClassExpressionNotInProfileException + * @throws FreshEntitiesException + * @throws ReasonerInterruptedException + * @throws TimeOutException + */ + public static Set getInferredSubClassOfGCIAxioms(OWLExtendedReasoner xr, OWLObjectProperty p) throws InconsistentOntologyException, + ClassExpressionNotInProfileException, FreshEntitiesException, + ReasonerInterruptedException, TimeOutException { + Set axioms = new HashSet<>(); + int n=0; + OWLOntology ont = xr.getRootOntology(); + OWLDataFactory df = ont.getOWLOntologyManager().getOWLDataFactory(); + Set allClasses = ont.getClassesInSignature(Imports.INCLUDED); + for (OWLClass c : allClasses) { + n++; + if (n % 1000 == 0) { + LOG.info("Class "+n+"/"+allClasses.size()); + } + for (OWLObjectSomeValuesFrom sc : ((ExpressionMaterializingReasoner)xr).getSomeValuesFromSuperClasses(c, p, false, true)) { + axioms.add(df.getOWLSubClassOfAxiom( + df.getOWLObjectSomeValuesFrom(p, c), + sc)); + } + } + return axioms; + } + + public static Set getInferredSubClassOfGCIAxioms(OWLExtendedReasoner xr) throws InconsistentOntologyException, + ClassExpressionNotInProfileException, FreshEntitiesException, + ReasonerInterruptedException, TimeOutException { + OWLOntology ont = xr.getRootOntology(); + OWLDataFactory df = ont.getOWLOntologyManager().getOWLDataFactory(); + Set axioms = new HashSet<>(); + for (OWLObjectProperty p : ont.getObjectPropertiesInSignature()) { + LOG.info("Calculating svf subsumptions for "+p); + axioms.addAll(getInferredSubClassOfGCIAxioms(xr, p)); + } + return axioms; + } + + public static Set getInferredSubClassOfGCIAxioms(OWLExtendedReasoner xr, + Set props) throws InconsistentOntologyException, + ClassExpressionNotInProfileException, FreshEntitiesException, + ReasonerInterruptedException, TimeOutException { + OWLOntology ont = xr.getRootOntology(); + OWLDataFactory df = ont.getOWLOntologyManager().getOWLDataFactory(); + Set axioms = new HashSet<>(); + if (props == null) + props = ont.getObjectPropertiesInSignature(); + for (OWLObjectProperty p : props) { + LOG.info("Calculating svf subsumptions for "+p); + axioms.addAll(getInferredSubClassOfGCIAxioms(xr, p)); + } + return axioms; + + } + + public static void writeInferredSubClassOfGCIAxioms(Set axioms, String outpath) throws IOException { + File file = new File(outpath); + FileWriter writer = (new FileWriter(file)); + for (OWLSubClassOfAxiom a : axioms) { + OWLObjectSomeValuesFrom c = (OWLObjectSomeValuesFrom)a.getSubClass(); + OWLObjectSomeValuesFrom p = (OWLObjectSomeValuesFrom)a.getSuperClass(); + + writer.write(id(c.getProperty()) + "\t" + id(c.getFiller()) + "\t" + + id(p.getProperty()) + "\t" + id(p.getFiller())); + writer.write("\n"); + } + writer.close(); + } + + + private static String id(OWLObject obj) { + if (obj instanceof OWLEntity) { + return ((OWLEntity)obj).getIRI().getRemainder().get(); + } + else { + return obj.toString(); + } + } +} diff --git a/src/main/java/org/geneontology/reasoner/OWLExtendedReasoner.java b/src/main/java/org/geneontology/reasoner/OWLExtendedReasoner.java index 57cd3cb..3b84006 100644 --- a/src/main/java/org/geneontology/reasoner/OWLExtendedReasoner.java +++ b/src/main/java/org/geneontology/reasoner/OWLExtendedReasoner.java @@ -5,6 +5,8 @@ import org.semanticweb.owlapi.model.OWLClass; import org.semanticweb.owlapi.model.OWLClassExpression; import org.semanticweb.owlapi.model.OWLObjectProperty; +import org.semanticweb.owlapi.model.OWLObjectSomeValuesFrom; +import org.semanticweb.owlapi.model.OWLSubClassOfAxiom; import org.semanticweb.owlapi.reasoner.ClassExpressionNotInProfileException; import org.semanticweb.owlapi.reasoner.FreshEntitiesException; import org.semanticweb.owlapi.reasoner.InconsistentOntologyException; @@ -20,8 +22,24 @@ */ public interface OWLExtendedReasoner extends OWLReasoner { + /** + * Materialize expressions involving the property p + * + * Different implementations may handle this differently. Currently the only implementation generates + * SomeValuesFrom expressions + * + * @param p + */ + public void materializeExpressions(OWLObjectProperty p); + /** - * Note that this is not a standard reasoner method. + * Finds all superclasses of a class expression ce, where the returned + * classes may be either (a) a named (non-anonymous) superClass or (b) + * an anomymous class expression, typically of the form "P SOME Y" + * + * + * Note that this is not a standard reasoner method. The standard + * OWLReasoner API provides getSuperClasses, corresponding to (a) above. * * @param ce * @param direct @@ -57,4 +75,6 @@ public Set getSuperClassesOver(OWLClassExpression ce, ReasonerInterruptedException, TimeOutException; + + } diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties new file mode 100644 index 0000000..d1e9d6b --- /dev/null +++ b/src/main/resources/log4j.properties @@ -0,0 +1,9 @@ +log4j.appender.console=org.apache.log4j.ConsoleAppender +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=%d %-5p (%c{1}:%L) %m\n +log4j.rootLogger=INFO, console + +# silence the reasoner warnings +log4j.logger.org.semanticweb.elk = ERROR +log4j.logger.org.obolibrary.obo2owl = ERROR + diff --git a/src/test/java/org/geneontology/reasoner/ExpressionMaterializingReasonerTest.java b/src/test/java/org/geneontology/reasoner/ExpressionMaterializingReasonerTest.java index fafafc2..a4e4809 100644 --- a/src/test/java/org/geneontology/reasoner/ExpressionMaterializingReasonerTest.java +++ b/src/test/java/org/geneontology/reasoner/ExpressionMaterializingReasonerTest.java @@ -5,88 +5,207 @@ import java.io.File; import java.util.Set; +import org.apache.log4j.Logger; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.semanticweb.elk.owlapi.ElkReasonerFactory; import org.semanticweb.owlapi.apibinding.OWLManager; +import org.semanticweb.owlapi.model.IRI; import org.semanticweb.owlapi.model.OWLClass; import org.semanticweb.owlapi.model.OWLClassExpression; +import org.semanticweb.owlapi.model.OWLDataFactory; import org.semanticweb.owlapi.model.OWLObjectProperty; +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.OWLReasonerFactory; public class ExpressionMaterializingReasonerTest { + private static Logger LOG = Logger.getLogger(ExpressionMaterializingReasoner.class); + private OWLOntology ontology = null; - private ExpressionMaterializingReasonerFactory elkFactory = null; - private ExpressionMaterializingReasonerFactory hermitFactory = null; - private ExpressionMaterializingReasoner elkReasoner = null; - private ExpressionMaterializingReasoner hermitReasoner = null; + private ExpressionMaterializingReasonerFactory elkEmrFactory = null; + private ExpressionMaterializingReasonerFactory hermitEmrFactory = null; + private ExpressionMaterializingReasoner elkExtReasoner = null; + private ExpressionMaterializingReasoner hermitExtReasoner = null; + private OWLDataFactory owlDataFactory; @Before public void before() throws Exception { OWLOntologyManager m = OWLManager.createOWLOntologyManager(); ontology = m.loadOntologyFromOntologyDocument(new File("src/test/resources","neuron.owl")); - elkFactory = new ExpressionMaterializingReasonerFactory(new ElkReasonerFactory()); + elkEmrFactory = new ExpressionMaterializingReasonerFactory(new ElkReasonerFactory()); OWLReasonerFactory hermit = new org.semanticweb.HermiT.ReasonerFactory(); - hermitFactory = new ExpressionMaterializingReasonerFactory(hermit); - elkReasoner = elkFactory.createReasoner(ontology); - hermitReasoner = hermitFactory.createReasoner(ontology); + hermitEmrFactory = new ExpressionMaterializingReasonerFactory(hermit); + elkExtReasoner = elkEmrFactory.createReasoner(ontology); + hermitExtReasoner = hermitEmrFactory.createReasoner(ontology); + owlDataFactory = ontology.getOWLOntologyManager().getOWLDataFactory(); } @After public void after() throws Exception { - if (elkReasoner != null) { - elkReasoner.dispose(); + if (elkExtReasoner != null) { + elkExtReasoner.dispose(); } - if (hermitReasoner != null) { - hermitReasoner.dispose(); + if (hermitExtReasoner != null) { + hermitExtReasoner.dispose(); } - elkFactory = null; - hermitFactory = null; + elkEmrFactory = null; + hermitEmrFactory = null; ontology = null; } - // what we expect with Elk - - // the following should be the only direct results for this class-property pair when direct is set - // // true = // true = // true = // true = - @Test + /** + * This test should run in <1s easily + * + * We set a timeout of 2000ms, to check for regression errors that lead to loss of efficiency + * + * @throws Exception + */ + @Test(timeout=2000) public void test1() throws Exception { // step 1: materialize expressions, defaults to all - elkReasoner.materializeExpressions(); + // AUTOMATIC NOW: elkExtReasoner.rewriteRangeAxioms(); + elkExtReasoner.materializeExpressions(); boolean[] bools = {true, false}; + boolean okSvfTest1 = false; // step 2: check inferences for all classes (or subset) for(OWLClass cls : ontology.getClassesInSignature()) { for (OWLObjectProperty p : ontology.getObjectPropertiesInSignature()) { for (boolean isDirect : bools) { - Set parents = elkReasoner.getSuperClassesOver(cls,p, isDirect); + Set parents = elkExtReasoner.getSuperClassesOver(cls,p, isDirect); for (OWLClass par : parents) { - // sorry Heiko... - System.out.println(cls + " " + p + " "+isDirect + " = "+par); + //logger.info(cls + " " + p + " "+isDirect + " = "+par); } + Set svfparents = + elkExtReasoner.getSuperClassExpressionsForGCI(cls, p, isDirect); + for (OWLClass par : parents) { + String s = p +" SOME " + cls + " SubClassOf: " + + p + " SOME " + par; + LOG.debug("SVF: " + s +" DIRECT: "+isDirect); + if (s.equals(" SOME SubClassOf: "+ + " SOME ")) { + okSvfTest1 = true; + } + } + + } } } + testSuperClassExpression(true, "mushroom-body", "overlaps", "cell"); + testSuperClassExpression(true, "mushroom-body", "part-of", "nervous-system"); + testSuperClassExpression(false, "neuron", "overlaps", "mushroom-body"); + + + //assertTrue("SVF test failed", okSvfTest1); + Set axs = ExtenderReasonerUtils.getInferredSubClassOfGCIAxioms(elkExtReasoner); + + for (OWLSubClassOfAxiom ax : axs) { + LOG.debug("AX: "+ax); + } + testSVFSubsumption(true, "part-of", "Purkinje-cell", "overlaps", "nervous-system", axs); + testSVFSubsumption(false, "overlaps", "Purkinje-cell", "part-of", "nervous-system", axs); + testSVFSubsumption(false, "enables", "Purkinje-cell", "part-of", "organism", axs); + testSVFSubsumptionEmpty("enables", "Purkinje-cell", axs); + testSVFSubsumptionEmpty("is-active-in", "apoptosis", axs); + testSVFSubsumption(true, "enables", "receptor-activity-in-apoptosis", "involved-in", "apoptosis", axs); + testSVFSubsumption(true, "enables", "receptor-activity-in-apoptosis", "acts-upstream-of-or-within", "apoptosis", axs); + testSVFSubsumption(false, "enables", "receptor-activity-in-apoptosis", "acts-upstream-of", "apoptosis", axs); + testSVFSubsumption(true, "acts-upstream-of", "receptor-activity-in-apoptosis", "acts-upstream-of", "receptor-activity-in-apoptosis", axs); + testSVFSubsumption(true, "acts-upstream-of", "receptor-activity-in-apoptosis", "acts-upstream-of", "receptor-activity", axs); + testSVFSubsumption(true, "acts-upstream-of", "receptor-activity-in-apoptosis", "acts-upstream-of-or-within", "receptor-activity-in-apoptosis", axs); + testSVFSubsumption(true, "acts-upstream-of", "receptor-activity-in-apoptosis", "acts-upstream-of-or-within", "apoptosis", axs); + testSVFSubsumption(false, "acts-upstream-of", "receptor-activity-in-apoptosis", "involved-in", "apoptosis", axs); + testSVFSubsumption(true, "involved-in", "regulation-of-apoptosis", "involved-in-regulation-of", "apoptosis", axs); + testSVFSubsumption(false, "involved-in", "regulation-of-apoptosis", "involved-in", "apoptosis", axs); + + // this requires swrl + // todo: allow passing in chain of relations + //testSVFSubsumption(true, "involved-in-regulation-of", "regulation-of-apoptosis", "involved-in-regulation-of", "apoptosis", axs); + + testSVFSubsumption(false, "involved-in-regulation-of", "regulation-of-apoptosis", "involved-in", "apoptosis", axs); + } + + private void testSuperClassExpression(boolean isTrue, String c, + String p, String filler) { + OWLObjectSomeValuesFrom svf = getSVF(p, filler); + boolean ok = false; + for ( OWLClassExpression x : elkExtReasoner.getSuperClassExpressions(getClass(c), false) ) { + if (x.equals(svf)) { + ok = true; + } + } + assertTrue(ok == isTrue); + } + + private void testSVFSubsumptionEmpty(String p1, String c1, Set axs) { + OWLObjectSomeValuesFrom svf1 = getSVF(p1, c1); + Set supers = + elkExtReasoner.getSomeValuesFromSuperClasses(getClass(c1), getProperty(p1), false, true); + assertTrue("expected empty", supers.size() == 0); } + + private void testSVFSubsumption(boolean isTrue, String p1, String c1, + String p2, String c2, Set axs) { + OWLObjectSomeValuesFrom svf1 = getSVF(p1, c1); + OWLObjectSomeValuesFrom svf2 = getSVF(p2, c2); + + LOG.info("Expect "+svf1+ " SubClassOf "+svf2+" IS "+isTrue); + // first test: dynamic call + boolean ok1 = false; + for (OWLObjectSomeValuesFrom svf : elkExtReasoner.getSomeValuesFromSuperClasses(getClass(c1), getProperty(p1), false, true)) { + if (svf.equals(svf2)) + ok1 = true; + } + assertTrue("could not find "+svf2, ok1 == isTrue); + + // second test: check map + boolean ok2 = false; + for (OWLSubClassOfAxiom ax : axs) { + if (ax.getSubClass().equals(svf1)) { + if (ax.getSuperClass().equals(svf2)) { + ok2 = true; + } + } + } + //assertTrue("could not find "+svf2, ok2 == isTrue); + } + + // too slow, do not run //@Test - public void test2() throws Exception { + public void testCompareHermitVsElk() throws Exception { // step 1: materialize expressions, defaults to all - elkReasoner.materializeExpressions(); - hermitReasoner.materializeExpressions(); + elkExtReasoner.materializeExpressions(); + hermitExtReasoner.materializeExpressions(); // step 2: check inferences for all classes (or subset) for(OWLClass cls : ontology.getClassesInSignature()) { - Set elkSuperClassExpressions = elkReasoner.getSuperClassExpressions(cls, true); - Set hermitSuperClassExpressions = hermitReasoner.getSuperClassExpressions(cls, true); + Set elkSuperClassExpressions = elkExtReasoner.getSuperClassExpressions(cls, true); + Set hermitSuperClassExpressions = hermitExtReasoner.getSuperClassExpressions(cls, true); // we expect Elk *not* to entail overlaps based on inverse axioms (neuron overlaps neuron) //assertEquals("Check that the inferences are the same for cls: "+cls.getIRI(), hermitSuperClassExpressions, elkSuperClassExpressions); } } + + // utils + private OWLClass getClass(String id) { + return owlDataFactory.getOWLClass(IRI.create("http://x.org/"+id)); + } + private OWLObjectProperty getProperty(String id) { + return owlDataFactory.getOWLObjectProperty(IRI.create("http://x.org/"+id)); + } + + private OWLObjectSomeValuesFrom getSVF(String p, String c) { + return owlDataFactory.getOWLObjectSomeValuesFrom(getProperty(p), getClass(c)); + } + } diff --git a/src/test/resources/goprops.owl b/src/test/resources/goprops.owl new file mode 100644 index 0000000..233e74e --- /dev/null +++ b/src/test/resources/goprops.owl @@ -0,0 +1,17 @@ +[Typedef] +id: RO:0002331 ! involved in + +[Typedef] +id: RO:0002428 ! involved in regulation of + +[Typedef] +id: RO:0002263 ! acts upstream of + +[Typedef] +id: RO:0002333 ! enabled by + +[Typedef] +id: BFO:0000050 ! part of + +[Typedef] +id: RO:0002131 ! overlaps diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties index 6fb525e..d1e9d6b 100644 --- a/src/test/resources/log4j.properties +++ b/src/test/resources/log4j.properties @@ -1,6 +1,9 @@ log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=%d %-5p (%c{1}:%L) %m\n +log4j.rootLogger=INFO, console -log4j.rootLogger=ERROR, console +# silence the reasoner warnings +log4j.logger.org.semanticweb.elk = ERROR +log4j.logger.org.obolibrary.obo2owl = ERROR diff --git a/src/test/resources/neuron.owl b/src/test/resources/neuron.owl index e875c16..a602289 100644 --- a/src/test/resources/neuron.owl +++ b/src/test/resources/neuron.owl @@ -1,21 +1,38 @@ + + + for testing EMR + + - - - - -]> + + - - + + + + + + + + + + @@ -30,12 +47,59 @@ + + + + + cjm + 2018-04-29T00:02:33Z + acts-upstream-of + + + + + + + + + + + + + + + + + + + + + + cjm + 2018-04-28T18:28:22Z + acts-upstream-of-or-within + + + + + + + + + + cjm + 2018-04-28T18:25:22Z + enables + + + + - + @@ -43,6 +107,7 @@ + @@ -51,9 +116,72 @@ + + + + + + + + + cjm + 2018-04-28T18:27:49Z + involved-in + + + + + + + + + + + + + cjm + 2018-04-28T18:28:11Z + involved-in-regulation-of + + + + + + + + + + + + + + cjm + 2018-04-28T18:23:22Z + is-active-in + + + + + + + + + + + + + + cjm + 2018-04-28T18:25:37Z + occurs-in + + + + + @@ -65,8 +193,43 @@ - + + + + + + + + + + + cjm + 2018-04-28T18:27:59Z + regulates + + + + + + + + + cjm + 2018-04-28T18:32:38Z + upstream-of + + + + + + + + + + cjm + 2018-04-28T18:32:48Z + upstream-of-or-within @@ -82,6 +245,25 @@ + + + + + + + + + + + + + + + + + + + @@ -110,8 +292,8 @@ - MSC part-parent is cerebellum A fictitous class for testing + MSC part-parent is cerebellum @@ -124,6 +306,17 @@ + + + + + cjm + 2018-04-28T18:30:44Z + apoptosis + + + + @@ -135,6 +328,7 @@ + @@ -148,6 +342,7 @@ + @@ -169,6 +364,7 @@ + @@ -200,6 +396,7 @@ + @@ -213,6 +410,7 @@ + @@ -229,9 +427,39 @@ + + + + + cjm + 2018-04-28T22:00:44Z + continuant + + + + - + + + + + + + + + + + + + + + + + cjm + 2018-04-28T18:31:42Z + execution phase of apoptosis + @@ -246,6 +474,7 @@ + @@ -280,9 +509,8 @@ - redundant - + @@ -293,6 +521,7 @@ + redundant @@ -323,15 +552,15 @@ - This is an example of a redundant axiom - + + This is an example of a redundant axiom @@ -344,6 +573,17 @@ + + + + + cjm + 2018-04-28T22:01:21Z + molecular-entity + + + + @@ -355,18 +595,13 @@ + - - - - - - @@ -383,11 +618,16 @@ + + + + + + - redundant - + @@ -398,6 +638,7 @@ + redundant @@ -405,14 +646,11 @@ + - - - - - - + + @@ -423,14 +661,18 @@ - - + + + + + + - - + + @@ -440,6 +682,17 @@ + + + + + + + + + + + @@ -468,12 +721,25 @@ + + + + + + + + + + + cjm + 2018-04-28T17:27:48Z + organism part @@ -486,6 +752,68 @@ + + + + cjm + 2018-04-28T18:30:09Z + process + + + + + + + + + cjm + 2018-04-28T23:49:07Z + receptor-activity + + + + + + + + + + + + + + + + + + + + cjm + 2018-04-28T23:49:33Z + receptor-activity-in-apoptosis + + + + + + + + + + + + + + + cjm + 2018-04-28T18:31:02Z + 2018-04-28T23:54:26Z + regulation of apoptosis + regulation-of-apoptosis + + + + @@ -519,48 +847,48 @@ --> + + - + - - + + - + - - + + - + - - + + - - - + diff --git a/src/test/resources/props.owl b/src/test/resources/props.owl new file mode 100644 index 0000000..37e9a43 --- /dev/null +++ b/src/test/resources/props.owl @@ -0,0 +1,16 @@ +Ontology( +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +Declaration(ObjectProperty()) +)