From 40a4f3275a36c37de459a2dce9be717f5edade71 Mon Sep 17 00:00:00 2001 From: Jim Balhoff Date: Thu, 17 Jun 2021 14:17:39 -0400 Subject: [PATCH] Implement arbitrary length property chains. --- .../scala/org/geneontology/whelk/Model.scala | 8 +++- .../org/geneontology/whelk/Reasoner.scala | 2 +- .../whelk/archimedes/Bridge.scala | 37 +++++++++------- .../scala/org/geneontology/whelk/Bridge.scala | 37 +++++++++------- .../org/geneontology/whelk/long-chains.ofn | 43 +++++++++++++++++++ .../geneontology/whelk/TestInferences.scala | 1 + 6 files changed, 94 insertions(+), 34 deletions(-) create mode 100644 modules/owlapi/src/test/resources/org/geneontology/whelk/long-chains.ofn diff --git a/modules/core/src/main/scala/org/geneontology/whelk/Model.scala b/modules/core/src/main/scala/org/geneontology/whelk/Model.scala index 2706610..4407718 100644 --- a/modules/core/src/main/scala/org/geneontology/whelk/Model.scala +++ b/modules/core/src/main/scala/org/geneontology/whelk/Model.scala @@ -2,7 +2,11 @@ package org.geneontology.whelk sealed trait QueueExpression -sealed trait Entity +sealed trait Entity { + + def id: String + +} trait HasSignature { @@ -20,6 +24,8 @@ object Role { def apply(id: String): Role = new Role(id.intern()) + val CompositionRolePrefix: String = "http://whelk.geneontology.org/composition_role/" + } final case class DataRole(id: String) extends Entity { diff --git a/modules/core/src/main/scala/org/geneontology/whelk/Reasoner.scala b/modules/core/src/main/scala/org/geneontology/whelk/Reasoner.scala index 08e54e4..b534c37 100644 --- a/modules/core/src/main/scala/org/geneontology/whelk/Reasoner.scala +++ b/modules/core/src/main/scala/org/geneontology/whelk/Reasoner.scala @@ -52,7 +52,7 @@ final case class ReasonerState( def roleAssertions: Set[RoleAssertion] = (for { (Nominal(target), links) <- linksByTarget - (role, subjects) <- links + (role, subjects) <- links.view.filterKeys(!_.id.startsWith(Role.CompositionRolePrefix)) Nominal(subject) <- subjects } yield RoleAssertion(role, subject, target)).toSet diff --git a/modules/core/src/main/scala/org/geneontology/whelk/archimedes/Bridge.scala b/modules/core/src/main/scala/org/geneontology/whelk/archimedes/Bridge.scala index 147e3bb..5b74bc7 100644 --- a/modules/core/src/main/scala/org/geneontology/whelk/archimedes/Bridge.scala +++ b/modules/core/src/main/scala/org/geneontology/whelk/archimedes/Bridge.scala @@ -4,6 +4,7 @@ import org.geneontology.archimedes.owl.OWLVocabulary._ import org.geneontology.archimedes.owl.{Atom => SWRLAtom, Axiom => OWLAxiom, DataHasValue => OWLDataHasValue, NamedIndividual => OWLNamedIndividual, Variable => OWLVariable, _} import org.geneontology.archimedes.util.Lists.PluralList import org.geneontology.whelk.BuiltIn._ +import org.geneontology.whelk.Role.CompositionRolePrefix import org.geneontology.whelk._ import scala.annotation.tailrec @@ -13,41 +14,41 @@ object Bridge { def ontologyToAxioms(ont: Ontology): Set[Axiom] = ont.axioms.flatMap(convertAxiom) def convertAxiom(owlAxiom: OWLAxiom): Set[Axiom] = owlAxiom match { - case SubClassOf(subclass, superclass, _) => (convertExpression(subclass), convertExpression(superclass)) match { + case SubClassOf(subclass, superclass, _) => (convertExpression(subclass), convertExpression(superclass)) match { case (Some(subConcept), Some(superConcept)) => Set(ConceptInclusion(subConcept, superConcept)) case _ => Set.empty } - case EquivalentClasses(operands, _) => + case EquivalentClasses(operands, _) => val converted = operands.items.map(convertExpression).toList.collect { case Some(concept) => concept } converted.combinations(2).flatMap { case first :: second :: Nil => Set(ConceptInclusion(first, second), ConceptInclusion(second, first)) case _ => Set.empty //impossible }.toSet - case DisjointClasses(operands, _) if operands.items.size == 2 => //FIXME handle >2 + case DisjointClasses(operands, _) if operands.items.size == 2 => //FIXME handle >2 val converted = operands.items.map(convertExpression).toList.collect { case Some(concept) => concept } converted.combinations(2).flatMap { case first :: second :: Nil => Set(ConceptInclusion(Conjunction(first, second), Bottom)) case _ => Set.empty //impossible }.toSet - case ClassAssertion(cls, OWLNamedIndividual(iri), _) => convertExpression(cls).map(concept => + case ClassAssertion(cls, OWLNamedIndividual(iri), _) => convertExpression(cls).map(concept => ConceptInclusion(Nominal(Individual(iri.id)), concept)).toSet - case ObjectPropertyAssertion(ObjectProperty(prop), OWLNamedIndividual(subj), OWLNamedIndividual(obj), _) => + case ObjectPropertyAssertion(ObjectProperty(prop), OWLNamedIndividual(subj), OWLNamedIndividual(obj), _) => Set(ConceptInclusion(Nominal(Individual(subj.id)), ExistentialRestriction(Role(prop.id), Nominal(Individual(obj.id))))) - case ObjectPropertyAssertion(ObjectInverseOf(ObjectProperty(prop)), OWLNamedIndividual(obj), OWLNamedIndividual(subj), _) => + case ObjectPropertyAssertion(ObjectInverseOf(ObjectProperty(prop)), OWLNamedIndividual(obj), OWLNamedIndividual(subj), _) => Set(ConceptInclusion(Nominal(Individual(subj.id)), ExistentialRestriction(Role(prop.id), Nominal(Individual(obj.id))))) - case EquivalentObjectProperties(propertyExpressions, _) => + case EquivalentObjectProperties(propertyExpressions, _) => val properties = propertyExpressions.items.collect { case p @ ObjectProperty(_) => p }.toList properties.combinations(2).flatMap { case ObjectProperty(first) :: ObjectProperty(second) :: Nil => Set(RoleInclusion(Role(first.id), Role(second.id))) case _ => Set.empty //impossible }.toSet - case SubObjectPropertyOf(ObjectProperty(subproperty), ObjectProperty(superproperty), _) => + case SubObjectPropertyOf(ObjectProperty(subproperty), ObjectProperty(superproperty), _) => val sub = Role(subproperty.id) val sup = Role(superproperty.id) Set( RoleInclusion(sub, sup), Rule(List(RoleAtom(sub, Variable("x1"), Variable("x2"))), List(RoleAtom(sup, Variable("x1"), Variable("x2"))))) - case SubObjectPropertyOf(ObjectPropertyChain(PluralList(ObjectProperty(first), ObjectProperty(second), Nil)), ObjectProperty(superproperty), _) => //FIXME handle >2 + case SubObjectPropertyOf(ObjectPropertyChain(PluralList(ObjectProperty(first), ObjectProperty(second), Nil)), ObjectProperty(superproperty), _) => val supRole = Role(superproperty.id) def makeSubject(level: Int): Variable = if (level == 0) Variable("x") else Variable(s"x$level") @@ -62,30 +63,34 @@ object Bridge { Set( RoleComposition(Role(first.id), Role(second.id), supRole), Rule(body = atoms, head = List(RoleAtom(supRole, start, end)))) - case TransitiveObjectProperty(ObjectProperty(property), _) => + case SubObjectPropertyOf(ObjectPropertyChain(PluralList(first @ ObjectProperty(_), second @ ObjectProperty(_), (third @ ObjectProperty(_)) :: rest)), superproperty @ ObjectProperty(_), _) => + val compositionProperty = ObjectProperty(IRI(s"$CompositionRolePrefix${first.iri.id}${second.iri.id}")) + convertAxiom(SubObjectPropertyOf(ObjectPropertyChain(PluralList(first, second, Nil)), compositionProperty)) ++ + convertAxiom(SubObjectPropertyOf(ObjectPropertyChain(PluralList(compositionProperty, third, rest)), superproperty)) + case TransitiveObjectProperty(ObjectProperty(property), _) => val role = Role(property.id) Set( RoleComposition(role, role, role), Rule(body = List(RoleAtom(role, Variable("x1"), Variable("x2")), RoleAtom(role, Variable("x2"), Variable("x3"))), head = List(RoleAtom(role, Variable("x1"), Variable("x3"))))) - case ReflexiveObjectProperty(ObjectProperty(property), _) => Set( + case ReflexiveObjectProperty(ObjectProperty(property), _) => Set( ConceptInclusion(Top, SelfRestriction(Role(property.id))) ) - case ObjectPropertyDomain(ObjectProperty(property), ce, _) => convertExpression(ce).map(concept => + case ObjectPropertyDomain(ObjectProperty(property), ce, _) => convertExpression(ce).map(concept => ConceptInclusion(ExistentialRestriction(Role(property.id), Top), concept)).toSet - case ObjectPropertyRange(ObjectProperty(property), ce, _) => convertExpression(ce).map(concept => + case ObjectPropertyRange(ObjectProperty(property), ce, _) => convertExpression(ce).map(concept => //TODO only supporting in rules for now Rule(body = List(RoleAtom(Role(property.id), Variable("x1"), Variable("x2"))), head = List(ConceptAtom(concept, Variable("x2"))))).toSet - case InverseObjectProperties(ObjectProperty(p), ObjectProperty(q), _) => + case InverseObjectProperties(ObjectProperty(p), ObjectProperty(q), _) => val (roleP, roleQ) = (Role(p.id), Role(q.id)) val (x1, x2) = (Variable("x1"), Variable("x2")) Set( Rule(body = List(RoleAtom(roleP, x1, x2)), head = List(RoleAtom(roleQ, x2, x1))), Rule(body = List(RoleAtom(roleQ, x1, x2)), head = List(RoleAtom(roleP, x2, x1)))) - case DLSafeRule(body, head, _) => (for { + case DLSafeRule(body, head, _) => (for { bodyAtoms <- convertAtomSet(body) headAtoms <- convertAtomSet(head) } yield Rule(bodyAtoms, headAtoms)).toSet - case _ => + case _ => //println(s"Not supported: $other") Set.empty } diff --git a/modules/owlapi/src/main/scala/org/geneontology/whelk/Bridge.scala b/modules/owlapi/src/main/scala/org/geneontology/whelk/Bridge.scala index 6f6cccb..68f73eb 100644 --- a/modules/owlapi/src/main/scala/org/geneontology/whelk/Bridge.scala +++ b/modules/owlapi/src/main/scala/org/geneontology/whelk/Bridge.scala @@ -1,6 +1,7 @@ package org.geneontology.whelk import org.geneontology.whelk.BuiltIn._ +import org.geneontology.whelk.Role.CompositionRolePrefix import org.geneontology.whelk.owlapi.SWRLUtil import org.geneontology.whelk.{Individual => WIndividual, Variable => WVariable} import org.phenoscape.scowl._ @@ -21,41 +22,41 @@ object Bridge { def ontologyToAxioms(ont: OWLOntology): Set[Axiom] = ont.getAxioms(Imports.INCLUDED).asScala.flatMap(convertAxiom).toSet def convertAxiom(owlAxiom: OWLAxiom): Set[Axiom] = owlAxiom match { - case SubClassOf(_, subclass, superclass) => (convertExpression(subclass), convertExpression(superclass)) match { + case SubClassOf(_, subclass, superclass) => (convertExpression(subclass), convertExpression(superclass)) match { case (Some(subConcept), Some(superConcept)) => Set(ConceptInclusion(subConcept, superConcept)) case _ => Set.empty } - case EquivalentClasses(_, operands) => + case EquivalentClasses(_, operands) => val converted = operands.map(convertExpression).toList.collect { case Some(concept) => concept } converted.combinations(2).flatMap { case first :: second :: Nil => Set(ConceptInclusion(first, second), ConceptInclusion(second, first)) case _ => ??? //impossible }.toSet - case DisjointClasses(_, operands) if operands.size == 2 => //FIXME handle >2 + case DisjointClasses(_, operands) if operands.size == 2 => //FIXME handle >2 val converted = operands.map(convertExpression).toList.collect { case Some(concept) => concept } converted.combinations(2).flatMap { case first :: second :: Nil => Set(ConceptInclusion(Conjunction(first, second), Bottom)) case _ => ??? //impossible }.toSet - case ClassAssertion(_, cls, NamedIndividual(iri)) => convertExpression(cls).map(concept => + case ClassAssertion(_, cls, NamedIndividual(iri)) => convertExpression(cls).map(concept => ConceptInclusion(Nominal(WIndividual(iri.toString)), concept)).toSet - case ObjectPropertyAssertion(_, ObjectProperty(prop), NamedIndividual(subj), NamedIndividual(obj)) => + case ObjectPropertyAssertion(_, ObjectProperty(prop), NamedIndividual(subj), NamedIndividual(obj)) => Set(ConceptInclusion(Nominal(WIndividual(subj.toString)), ExistentialRestriction(Role(prop.toString), Nominal(WIndividual(obj.toString))))) - case ObjectPropertyAssertion(_, ObjectInverseOf(ObjectProperty(prop)), NamedIndividual(obj), NamedIndividual(subj)) => + case ObjectPropertyAssertion(_, ObjectInverseOf(ObjectProperty(prop)), NamedIndividual(obj), NamedIndividual(subj)) => Set(ConceptInclusion(Nominal(WIndividual(subj.toString)), ExistentialRestriction(Role(prop.toString), Nominal(WIndividual(obj.toString))))) - case EquivalentObjectProperties(_, propertyExpressions) => + case EquivalentObjectProperties(_, propertyExpressions) => val properties = propertyExpressions.collect { case p @ ObjectProperty(_) => p }.toList properties.combinations(2).flatMap { case ObjectProperty(first) :: ObjectProperty(second) :: Nil => Set(RoleInclusion(Role(first.toString), Role(second.toString))) case _ => ??? //impossible }.toSet - case SubObjectPropertyOf(_, ObjectProperty(subproperty), ObjectProperty(superproperty)) => + case SubObjectPropertyOf(_, ObjectProperty(subproperty), ObjectProperty(superproperty)) => val sub = Role(subproperty.toString) val sup = Role(superproperty.toString) Set( RoleInclusion(sub, sup), Rule(List(RoleAtom(sub, WVariable("x1"), WVariable("x2"))), List(RoleAtom(sup, WVariable("x1"), WVariable("x2"))))) - case SubObjectPropertyChainOf(_, ObjectProperty(first) :: ObjectProperty(second) :: Nil, ObjectProperty(superproperty)) => //FIXME handle >2 + case SubObjectPropertyChainOf(_, ObjectProperty(first) :: ObjectProperty(second) :: Nil, ObjectProperty(superproperty)) => val supRole = Role(superproperty.toString) def makeSubject(level: Int): WVariable = if (level == 0) WVariable("x") else WVariable(s"x$level") @@ -70,30 +71,34 @@ object Bridge { Set( RoleComposition(Role(first.toString), Role(second.toString), supRole), Rule(body = atoms, head = List(RoleAtom(supRole, start, end)))) - case TransitiveObjectProperty(_, ObjectProperty(property)) => + case SubObjectPropertyChainOf(_, (first @ ObjectProperty(_)) :: (second @ ObjectProperty(_)) :: more, (superproperty @ ObjectProperty(_))) => + val compositionProperty: OWLObjectProperty = ObjectProperty(s"$CompositionRolePrefix${first.getIRI}${second.getIRI}") + convertAxiom(SubObjectPropertyChainOf(List(first, second), compositionProperty)) ++ + convertAxiom(SubObjectPropertyChainOf(compositionProperty :: more, superproperty)) + case TransitiveObjectProperty(_, ObjectProperty(property)) => val role = Role(property.toString) Set( RoleComposition(role, role, role), Rule(body = List(RoleAtom(role, WVariable("x1"), WVariable("x2")), RoleAtom(role, WVariable("x2"), WVariable("x3"))), head = List(RoleAtom(role, WVariable("x1"), WVariable("x3"))))) - case ReflexiveObjectProperty(_, ObjectProperty(property)) => Set( + case ReflexiveObjectProperty(_, ObjectProperty(property)) => Set( ConceptInclusion(Top, SelfRestriction(Role(property.toString))) ) - case ObjectPropertyDomain(_, ObjectProperty(property), ce) => convertExpression(ce).map(concept => + case ObjectPropertyDomain(_, ObjectProperty(property), ce) => convertExpression(ce).map(concept => ConceptInclusion(ExistentialRestriction(Role(property.toString), Top), concept)).toSet - case ObjectPropertyRange(_, ObjectProperty(property), ce) => convertExpression(ce).map(concept => + case ObjectPropertyRange(_, ObjectProperty(property), ce) => convertExpression(ce).map(concept => //TODO only supporting in rules for now Rule(body = List(RoleAtom(Role(property.toString), WVariable("x1"), WVariable("x2"))), head = List(ConceptAtom(concept, WVariable("x2"))))).toSet - case InverseObjectProperties(_, ObjectProperty(p), ObjectProperty(q)) => + case InverseObjectProperties(_, ObjectProperty(p), ObjectProperty(q)) => val (roleP, roleQ) = (Role(p.toString), Role(q.toString)) val (x1, x2) = (WVariable("x1"), WVariable("x2")) Set( Rule(body = List(RoleAtom(roleP, x1, x2)), head = List(RoleAtom(roleQ, x2, x1))), Rule(body = List(RoleAtom(roleQ, x1, x2)), head = List(RoleAtom(roleP, x2, x1)))) - case DLSafeRule(_, body, head) => (for { + case DLSafeRule(_, body, head) => (for { bodyAtoms <- convertAtomSet(body) headAtoms <- convertAtomSet(head) } yield Rule(bodyAtoms, headAtoms)).toSet - case _ => + case _ => //println(s"Not supported: $other") Set.empty } diff --git a/modules/owlapi/src/test/resources/org/geneontology/whelk/long-chains.ofn b/modules/owlapi/src/test/resources/org/geneontology/whelk/long-chains.ofn new file mode 100644 index 0000000..d80db22 --- /dev/null +++ b/modules/owlapi/src/test/resources/org/geneontology/whelk/long-chains.ofn @@ -0,0 +1,43 @@ +Prefix(:=) +Prefix(owl:=) +Prefix(rdf:=) +Prefix(xml:=) +Prefix(xsd:=) +Prefix(rdfs:=) + + +Ontology( + +Declaration(Class(:B)) +Declaration(Class(:C)) +Declaration(Class(:D)) +Declaration(Class(:E)) +Declaration(Class()) +Declaration(ObjectProperty(:p)) +Declaration(ObjectProperty(:q)) +Declaration(ObjectProperty(:r)) +Declaration(ObjectProperty(:s)) + +############################ +# Classes +############################ + +# Class: :B (:B) + +EquivalentClasses(:B ObjectSomeValuesFrom(:q :C)) + +# Class: :C (:C) + +SubClassOf(:C ObjectSomeValuesFrom(:r :D)) + +# Class: :E (:E) + +EquivalentClasses(:E ObjectSomeValuesFrom(:s :D)) + +# Class: () + +SubClassOf( ObjectSomeValuesFrom(:p :B)) + + +SubObjectPropertyOf(ObjectPropertyChain(:p :q :r) :s) +) \ No newline at end of file diff --git a/modules/owlapi/src/test/scala/org/geneontology/whelk/TestInferences.scala b/modules/owlapi/src/test/scala/org/geneontology/whelk/TestInferences.scala index 1f80bd9..2af975e 100644 --- a/modules/owlapi/src/test/scala/org/geneontology/whelk/TestInferences.scala +++ b/modules/owlapi/src/test/scala/org/geneontology/whelk/TestInferences.scala @@ -30,6 +30,7 @@ object TestInferences extends TestSuite { "unsatisfiable.ofn" - compareWhelkAndELK() "taxon-unions.ofn" - compareWhelkAndHermiT(false) "unions.ofn" - compareWhelkAndHermiT(false) + "long-chains.ofn" - compareWhelkAndELK() } }