From 59213b39c97a0454f4846f2a899f8471f6ec698d Mon Sep 17 00:00:00 2001 From: Chris Mungall Date: Thu, 12 Jan 2017 08:52:10 -0800 Subject: [PATCH] matching between ontology classes (not instances) Fixes #40 --- .../owlsim/compute/classmatch/ClassMatcher.java | 93 ++++++++++++++++ .../compute/classmatch/SimpleClassMatch.java | 124 +++++++++++++++++++++ .../owlsim/compute/classmatch/package-info.java | 8 ++ .../compute/classmatch/ClassMatcherTest.java | 59 ++++++++++ .../services/resources/OntologyMatchResource.java | 66 +++++++++++ 5 files changed, 350 insertions(+) create mode 100644 owlsim-core/src/main/java/org/monarchinitiative/owlsim/compute/classmatch/ClassMatcher.java create mode 100644 owlsim-core/src/main/java/org/monarchinitiative/owlsim/compute/classmatch/SimpleClassMatch.java create mode 100644 owlsim-core/src/main/java/org/monarchinitiative/owlsim/compute/classmatch/package-info.java create mode 100644 owlsim-core/src/test/java/org/monarchinitiative/owlsim/compute/classmatch/ClassMatcherTest.java create mode 100644 owlsim-services/src/main/java/org/monarchinitiative/owlsim/services/resources/OntologyMatchResource.java diff --git a/owlsim-core/src/main/java/org/monarchinitiative/owlsim/compute/classmatch/ClassMatcher.java b/owlsim-core/src/main/java/org/monarchinitiative/owlsim/compute/classmatch/ClassMatcher.java new file mode 100644 index 0000000..876dd53 --- /dev/null +++ b/owlsim-core/src/main/java/org/monarchinitiative/owlsim/compute/classmatch/ClassMatcher.java @@ -0,0 +1,93 @@ +package org.monarchinitiative.owlsim.compute.classmatch; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.monarchinitiative.owlsim.kb.BMKnowledgeBase; +import org.monarchinitiative.owlsim.kb.LabelMapper; + +import com.googlecode.javaewah.EWAHCompressedBitmap; + +/** + * Finds matches between classes in the KB + * + * @author cjm + * + */ +public class ClassMatcher { + + BMKnowledgeBase kb; + + + @Inject + public ClassMatcher(BMKnowledgeBase kb) { + super(); + this.kb = kb; + } + + /** + * Find best match for every class in ont1, where the best + * match is in ont2 + * + * @param qOnt + * @param tOnt + * @return + */ + public List matchOntologies(String qOnt, String tOnt) { + Set qids = getClassIdsByOntology(qOnt); + Set tids = getClassIdsByOntology(tOnt); + return matchClassSets(qids, tids); + } + + public List matchClassSets(Set qids, + Set tids) { + ArrayList matches = new ArrayList<>(); + for (String q : qids) { + matches.add(getBestMatch(q, tids)); + } + return matches; + } + + private SimpleClassMatch getBestMatch(String q, Set tids) { + EWAHCompressedBitmap qbm = kb.getSuperClassesBM(q); + double bestEqScore = 0.0; + String best = null; + for (String t : tids) { + EWAHCompressedBitmap tbm = kb.getSuperClassesBM(t); + int numInQueryAndInTarget = qbm.andCardinality(tbm); + int numInQueryOrInTarget = qbm.orCardinality(tbm); + double eqScore = numInQueryAndInTarget / (double) numInQueryOrInTarget; + if (eqScore > bestEqScore) { + bestEqScore = eqScore; + best = t; + } + } + + EWAHCompressedBitmap tbm = kb.getSuperClassesBM(best); + int numInQueryAndInTarget = qbm.andCardinality(tbm); + double subClassScore = numInQueryAndInTarget / (double) qbm.cardinality(); + double superClassScore = numInQueryAndInTarget / (double) tbm.cardinality(); + + LabelMapper lm = kb.getLabelMapper(); + return new SimpleClassMatch(q, best, + lm.getArbitraryLabel(q), + lm.getArbitraryLabel(best), + bestEqScore, + subClassScore, + superClassScore); + } + + public Set getClassIdsByOntology(String ont) { + return kb.getClassIdsInSignature().stream().filter(x -> isIn(x, ont)).collect(Collectors.toSet()); + } + + public boolean isIn(String id, String ont) { + // TODO - use curie util + return id.startsWith(ont+":") || id.contains("/"+ont+"_"); + } + +} diff --git a/owlsim-core/src/main/java/org/monarchinitiative/owlsim/compute/classmatch/SimpleClassMatch.java b/owlsim-core/src/main/java/org/monarchinitiative/owlsim/compute/classmatch/SimpleClassMatch.java new file mode 100644 index 0000000..8238a32 --- /dev/null +++ b/owlsim-core/src/main/java/org/monarchinitiative/owlsim/compute/classmatch/SimpleClassMatch.java @@ -0,0 +1,124 @@ +package org.monarchinitiative.owlsim.compute.classmatch; + +public class SimpleClassMatch { + + private String queryClassId; + private String matchClassId; + private String queryClassLabel; + private String matchClassLabel; + + private double eqScore; + private double subClassScore; + private double superClassScore; + public SimpleClassMatch(String queryClassId, String matchClassId, + String queryClassLabel, String matchClassLabel, double eqScore, + double subClassScore, double superClassScore) { + super(); + this.queryClassId = queryClassId; + this.matchClassId = matchClassId; + this.queryClassLabel = queryClassLabel; + this.matchClassLabel = matchClassLabel; + this.eqScore = eqScore; + this.subClassScore = subClassScore; + this.superClassScore = superClassScore; + } + /** + * @return the queryClassId + */ + public String getQueryClassId() { + return queryClassId; + } + /** + * @param queryClassId the queryClassId to set + */ + public void setQueryClassId(String queryClassId) { + this.queryClassId = queryClassId; + } + /** + * @return the matchClassId + */ + public String getMatchClassId() { + return matchClassId; + } + /** + * @param matchClassId the matchClassId to set + */ + public void setMatchClassId(String matchClassId) { + this.matchClassId = matchClassId; + } + /** + * @return the queryClassLabel + */ + public String getQueryClassLabel() { + return queryClassLabel; + } + /** + * @param queryClassLabel the queryClassLabel to set + */ + public void setQueryClassLabel(String queryClassLabel) { + this.queryClassLabel = queryClassLabel; + } + /** + * @return the matchClassLabel + */ + public String getMatchClassLabel() { + return matchClassLabel; + } + /** + * @param matchClassLabel the matchClassLabel to set + */ + public void setMatchClassLabel(String matchClassLabel) { + this.matchClassLabel = matchClassLabel; + } + /** + * @return the eqScore + */ + public double getEqScore() { + return eqScore; + } + /** + * @param eqScore the eqScore to set + */ + public void setEqScore(double eqScore) { + this.eqScore = eqScore; + } + /** + * @return the subClassScore + */ + public double getSubClassScore() { + return subClassScore; + } + /** + * @param subClassScore the subClassScore to set + */ + public void setSubClassScore(double subClassScore) { + this.subClassScore = subClassScore; + } + /** + * @return the superClassScore + */ + public double getSuperClassScore() { + return superClassScore; + } + /** + * @param superClassScore the superClassScore to set + */ + public void setSuperClassScore(double superClassScore) { + this.superClassScore = superClassScore; + } + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "SimpleClassMatch [queryClassId=" + queryClassId + + ", matchClassId=" + matchClassId + ", queryClassLabel=" + + queryClassLabel + ", matchClassLabel=" + matchClassLabel + + ", eqScore=" + eqScore + ", subClassScore=" + subClassScore + + ", superClassScore=" + superClassScore + "]"; + } + + + + +} diff --git a/owlsim-core/src/main/java/org/monarchinitiative/owlsim/compute/classmatch/package-info.java b/owlsim-core/src/main/java/org/monarchinitiative/owlsim/compute/classmatch/package-info.java new file mode 100644 index 0000000..a1b2a88 --- /dev/null +++ b/owlsim-core/src/main/java/org/monarchinitiative/owlsim/compute/classmatch/package-info.java @@ -0,0 +1,8 @@ +/** + * + */ +/** + * @author cjm + * + */ +package org.monarchinitiative.owlsim.compute.classmatch; \ No newline at end of file diff --git a/owlsim-core/src/test/java/org/monarchinitiative/owlsim/compute/classmatch/ClassMatcherTest.java b/owlsim-core/src/test/java/org/monarchinitiative/owlsim/compute/classmatch/ClassMatcherTest.java new file mode 100644 index 0000000..cd8b00f --- /dev/null +++ b/owlsim-core/src/test/java/org/monarchinitiative/owlsim/compute/classmatch/ClassMatcherTest.java @@ -0,0 +1,59 @@ +package org.monarchinitiative.owlsim.compute.classmatch; + +import static org.junit.Assert.*; + +import java.net.URISyntaxException; +import java.net.URL; +import java.util.List; + +import org.apache.log4j.Logger; +import org.junit.Test; +import org.monarchinitiative.owlsim.compute.mica.AbstractMICAStoreTest; +import org.monarchinitiative.owlsim.compute.mica.MICAStore; +import org.monarchinitiative.owlsim.compute.mica.impl.MICAStoreImpl; +import org.monarchinitiative.owlsim.compute.mica.impl.NoRootException; +import org.monarchinitiative.owlsim.compute.stats.KBStatsCalculator; +import org.monarchinitiative.owlsim.io.OWLLoader; +import org.monarchinitiative.owlsim.kb.BMKnowledgeBase; +import org.monarchinitiative.owlsim.kb.LabelMapper; +import org.semanticweb.owlapi.model.IRI; +import org.semanticweb.owlapi.model.OWLOntologyCreationException; + +import com.google.monitoring.runtime.instrumentation.common.com.google.common.io.Resources; + +public class ClassMatcherTest { + + protected BMKnowledgeBase kb; + protected ClassMatcher classMatcher; + private Logger LOG = Logger.getLogger(ClassMatcherTest.class); + + protected void load(String fn, String... ontfns) throws OWLOntologyCreationException, URISyntaxException, NoRootException { + OWLLoader loader = new OWLLoader(); + LOG.info("Loading: "+fn); + loader.load(IRI.create(Resources.getResource(fn))); + for (String ontfn : ontfns) { + URL res = getClass().getResource(ontfn); + LOG.info("RES="+res); + loader.loadOntologies(res.getFile()); + } + kb = loader.createKnowledgeBaseInterface(); + classMatcher = new ClassMatcher(kb); + } + + @Test + public void selfTest() throws OWLOntologyCreationException, URISyntaxException, NoRootException { + load("mp-subset.ttl"); + LabelMapper lm = kb.getLabelMapper(); + + List matches = classMatcher.matchOntologies("MP", "MP"); + + int numNonSelfMatches = 0; + for (SimpleClassMatch m : matches) { + if (!m.getQueryClassId().equals(m.getMatchClassId())) { + numNonSelfMatches++; + } + } + assertEquals(0, numNonSelfMatches); + } + +} diff --git a/owlsim-services/src/main/java/org/monarchinitiative/owlsim/services/resources/OntologyMatchResource.java b/owlsim-services/src/main/java/org/monarchinitiative/owlsim/services/resources/OntologyMatchResource.java new file mode 100644 index 0000000..7d40da2 --- /dev/null +++ b/owlsim-services/src/main/java/org/monarchinitiative/owlsim/services/resources/OntologyMatchResource.java @@ -0,0 +1,66 @@ +package org.monarchinitiative.owlsim.services.resources; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; + +import org.monarchinitiative.owlsim.compute.classmatch.ClassMatcher; +import org.monarchinitiative.owlsim.compute.classmatch.SimpleClassMatch; +import org.monarchinitiative.owlsim.compute.cpt.IncoherentStateException; +import org.monarchinitiative.owlsim.compute.matcher.NegationAwareProfileMatcher; +import org.monarchinitiative.owlsim.compute.matcher.ProfileMatcher; +import org.monarchinitiative.owlsim.kb.filter.AnonIndividualFilter; +import org.monarchinitiative.owlsim.kb.filter.TypeFilter; +import org.monarchinitiative.owlsim.kb.filter.UnknownFilterException; +import org.monarchinitiative.owlsim.model.match.MatchSet; +import org.monarchinitiative.owlsim.model.match.ProfileQuery; +import org.monarchinitiative.owlsim.model.match.ProfileQueryFactory; +import org.monarchinitiative.owlsim.services.exceptions.NonNegatedMatcherException; +import org.monarchinitiative.owlsim.services.exceptions.UnknownMatcherException; + +import com.codahale.metrics.annotation.Timed; +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; +import com.wordnik.swagger.annotations.ApiParam; + +import io.dropwizard.jersey.caching.CacheControl; + +@Path("/ontomatch") +@Api(value = "/ontomatch", description = "ontology match services") +@Produces({MediaType.APPLICATION_JSON}) +public class OntologyMatchResource { + + @Inject + ClassMatcher classMatcher; + + @GET + @Path("/{queryOntology}/{targetOntology}") + @Timed + @CacheControl(maxAge = 2, maxAgeUnit = TimeUnit.HOURS) + @ApiOperation(value = "Match", response = MatchSet.class, + notes = "Additional notes on the match resource.") + public List getMatches( + @ApiParam(value = "base ontology, e.g. MP", + required = true) @PathParam("queryOntology") String queryOntology, + @ApiParam(value = "ontology to be matched, e.g. HP", + required = true) @PathParam("targetOntology") String targetOntology) + throws UnknownFilterException, IncoherentStateException { + List matches = + classMatcher.matchOntologies(queryOntology, targetOntology); + return matches; + } + + // TODO - API for comparing two entities + +}