From 53646646f924887768688794aee46874ed194673 Mon Sep 17 00:00:00 2001 From: Federico Igne Date: Tue, 20 Jul 2021 15:09:20 +0100 Subject: Generalize dependency graph generation The code to generate the dependency graph has been moved in the companion object of the generic OWL 2 ontology wrapper Ontology. This signals that we could potentially build a dependency graph for any ontology (and not only RSA ontology). Moreover, a dependency graph can be build for an Ontology object or an arbitrary TBox and Abox. --- .../scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala | 2 +- src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala | 10 ++ .../scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala | 111 ++-------------- .../ox/cs/rsacomb/approximation/lowerbound.scala | 4 +- .../uk/ac/ox/cs/rsacomb/converter/Normalizer.scala | 17 +-- .../ox/cs/rsacomb/converter/RDFoxConverter.scala | 13 +- .../uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala | 3 +- .../uk/ac/ox/cs/rsacomb/ontologies/Ontology.scala | 141 ++++++++++++++++++++- 8 files changed, 182 insertions(+), 119 deletions(-) (limited to 'src') diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala index 6621f59..af6c463 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala @@ -76,7 +76,7 @@ class CanonicalModel(val ontology: RSAOntology) { val (facts, rules): (List[TupleTableAtom], List[Rule]) = { // Compute rules from ontology axioms val (facts, rules) = { - val term = RSAOntology.genFreshVariable() + val term = RSAUtil.genFreshVariable() val unsafe = ontology.unsafeRoles ontology.axioms .map(a => diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala index b749401..82da9df 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala @@ -103,6 +103,16 @@ object RSAConfig { /** Main entry point to the program */ object RSAComb extends App { + /* + * TODO: Aiming for this workflow: + * + * implicit val manager = new Manager(...) + * val original = manager.importFromFile("ontology.owl") + * val axioms = original.getAxioms.filter(isLogicalAxiom).normalize(normalizer) + * val ontology = new Ontology(axioms, data) + * val rsa = ontology.toRSA(approximator) + */ + /* Command-line options */ val config = RSAConfig.parse(args.toList) diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala index bbbbcf3..9902fcd 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala @@ -71,108 +71,10 @@ object RSAUtil { // manager.createOntology(axioms.asJava) // } - /** Compute the RSA dependency graph for a set of axioms - * - * @return a tuple containing the dependency graph and a map between - * the newly introduced constants and the corresponding input axioms. - * - * @note no check on the ontology language is performed since the - * construction of the dependency graph is computed regardless. The - * input axioms are assumed to be normalized. - */ - def dependencyGraph( - axioms: List[OWLLogicalAxiom], - datafiles: List[File] - ): (Graph[Resource, DiEdge], Map[String, OWLAxiom]) = { - val unsafe = RSAOntology(axioms, datafiles).unsafeRoles - var nodemap = Map.empty[String, OWLAxiom] - - object RSAConverter extends RDFoxConverter { - - override def convert( - expr: OWLClassExpression, - term: Term, - unsafe: List[OWLObjectPropertyExpression], - skolem: SkolemStrategy, - suffix: RSASuffix - ): Shards = - (expr, skolem) match { - - case (e: OWLObjectSomeValuesFrom, c: Constant) => { - nodemap.update(c.iri.getIRI, c.axiom) - val (res, ext) = super.convert(e, term, unsafe, skolem, suffix) - if (unsafe contains e.getProperty) - (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext) - else - (RSA.PE(term, c.iri) :: res, ext) - } - - case (e: OWLDataSomeValuesFrom, c: Constant) => { - nodemap.update(c.iri.getIRI, c.axiom) - val (res, ext) = super.convert(e, term, unsafe, skolem, suffix) - if (unsafe contains e.getProperty) - (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext) - else - (RSA.PE(term, c.iri) :: res, ext) - } - - case _ => super.convert(expr, term, unsafe, skolem, suffix) - } - } - - /* Ontology convertion into LP rules */ - val term = RSAOntology.genFreshVariable() - val result = axioms.map(a => - RSAConverter.convert(a, term, unsafe, new Constant(a), Empty) - ) - - val datalog = result.unzip - val facts = datalog._1.flatten - var rules = datalog._2.flatten - - /* Open connection with RDFox */ - val (server, data) = RDFoxUtil.openConnection("rsa_dependency_graph") - - /* Add additional built-in rules */ - val varX = Variable.create("X") - val varY = Variable.create("Y") - rules = Rule.create( - RSA.E(varX, varY), - RSA.PE(varX, varY), - RSA.U(varX), - RSA.U(varY) - ) :: rules - /* Load facts and rules from ontology */ - RDFoxUtil.addFacts(data, facts) - RDFoxUtil.addRules(data, rules) - /* Load data files */ - RDFoxUtil.addData(data, datafiles: _*) - - /* Build the graph */ - val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }" - val answers = RDFoxUtil.submitQuery(data, query, RSA.Prefixes).get - var edges: Seq[DiEdge[Resource]] = - answers.collect { case (_, Seq(n1, n2)) => n1 ~> n2 } - val graph = Graph(edges: _*) - - /* Close connection to RDFox */ - RDFoxUtil.closeConnection(server, data) - - (graph, nodemap) - } -} - -object RSAOntology { - - import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ - /** Manager instance to interface with OWLAPI */ val manager = OWLManager.createOWLOntologyManager() val factory = manager.getOWLDataFactory() - /** Name of the RDFox data store used for CQ answering */ - private val DataStore = "answer_computation" - /** Simple fresh variable/class generator */ private var counter = -1; def genFreshVariable(): Variable = { @@ -184,6 +86,19 @@ object RSAOntology { factory.getOWLClass(s"X$counter") } +} + +object RSAOntology { + + import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ + + /** Manager instance to interface with OWLAPI */ + val manager = OWLManager.createOWLOntologyManager() + val factory = manager.getOWLDataFactory() + + /** Name of the RDFox data store used for CQ answering */ + private val DataStore = "answer_computation" + def apply( axioms: List[OWLLogicalAxiom], datafiles: List[File] diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/lowerbound.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/lowerbound.scala index 8a86d19..2750cca 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/lowerbound.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/lowerbound.scala @@ -129,10 +129,10 @@ class LowerBound extends Approximation { sup match { case sup: OWLObjectUnionOf => { val body = sub.asConjunctSet.map((atom) => - (atom, RSAOntology.getFreshOWLClass()) + (atom, RSAUtil.getFreshOWLClass()) ) val head = sup.asDisjunctSet.map((atom) => - (atom, RSAOntology.getFreshOWLClass()) + (atom, RSAUtil.getFreshOWLClass()) ) val r1 = diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala index 4b298f4..80dd222 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala @@ -5,6 +5,7 @@ import org.semanticweb.owlapi.model._ import uk.ac.ox.cs.rsacomb.util.Logger import uk.ac.ox.cs.rsacomb.RSAOntology +import uk.ac.ox.cs.rsacomb.RSAUtil object Normalizer { @@ -53,7 +54,7 @@ class Normalizer() { * C c D -> { C c X, X c D } */ case _ if !sub.isOWLClass && !sup.isOWLClass => { - val cls = RSAOntology.getFreshOWLClass() + val cls = RSAUtil.getFreshOWLClass() Seq( factory.getOWLSubClassOfAxiom(sub, cls), factory.getOWLSubClassOfAxiom(cls, sup) @@ -74,7 +75,7 @@ class Normalizer() { if (conj.isOWLClass) (acc1 :+ conj, acc2) else { - val cls = RSAOntology.getFreshOWLClass() + val cls = RSAUtil.getFreshOWLClass() ( acc1 :+ cls, acc2 :+ factory.getOWLSubClassOfAxiom(conj, cls) @@ -133,7 +134,7 @@ class Normalizer() { */ case (sub: OWLObjectSomeValuesFrom, _) if !sub.getFiller.isOWLClass => { - val cls = RSAOntology.getFreshOWLClass() + val cls = RSAUtil.getFreshOWLClass() Seq( factory.getOWLSubClassOfAxiom(sub.getFiller, cls), factory.getOWLSubClassOfAxiom( @@ -148,7 +149,7 @@ class Normalizer() { */ case (_, sup: OWLObjectSomeValuesFrom) if !sup.getFiller.isOWLClass => { - val cls = RSAOntology.getFreshOWLClass() + val cls = RSAUtil.getFreshOWLClass() Seq( factory.getOWLSubClassOfAxiom(cls, sup.getFiller), factory.getOWLSubClassOfAxiom( @@ -293,7 +294,7 @@ class Normalizer() { ) case (_, sup: OWLObjectMaxCardinality) if sup.getCardinality == 1 && !sup.getFiller.isOWLClass => { - val cls = RSAOntology.getFreshOWLClass() + val cls = RSAUtil.getFreshOWLClass() Seq( factory.getOWLSubClassOfAxiom(cls, sup.getFiller), factory.getOWLSubClassOfAxiom( @@ -483,7 +484,7 @@ class Normalizer() { * C(a) -> { X(a), X c C } */ case a: OWLClassAssertionAxiom if !a.getClassExpression.isOWLClass => { - val cls = RSAOntology.getFreshOWLClass() + val cls = RSAUtil.getFreshOWLClass() Seq( factory.getOWLClassAssertionAxiom(cls, a.getIndividual), factory.getOWLSubClassOfAxiom(cls, a.getClassExpression) @@ -528,9 +529,9 @@ class Normalizer() { sup: OWLObjectUnionOf ): Seq[OWLLogicalAxiom] = { val body = - sub.asConjunctSet.map((atom) => (atom, RSAOntology.getFreshOWLClass())) + sub.asConjunctSet.map((atom) => (atom, RSAUtil.getFreshOWLClass())) val head = - sup.asDisjunctSet.map((atom) => (atom, RSAOntology.getFreshOWLClass())) + sup.asDisjunctSet.map((atom) => (atom, RSAUtil.getFreshOWLClass())) /* Update statistics */ shifted += 1 diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxConverter.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxConverter.scala index 3fac46f..1adf325 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxConverter.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxConverter.scala @@ -11,6 +11,7 @@ import tech.oxfordsemantic.jrdfox.logic.datalog.{ TupleTableAtom } import tech.oxfordsemantic.jrdfox.logic.expression.{Term, IRI, FunctionCall} +import uk.ac.ox.cs.rsacomb.RSAUtil import uk.ac.ox.cs.rsacomb.RSAOntology import uk.ac.ox.cs.rsacomb.suffix.{Empty, Inverse, RSASuffix} import uk.ac.ox.cs.rsacomb.util.{RSA, RDFoxUtil} @@ -143,14 +144,14 @@ trait RDFoxConverter { } case a: OWLSubObjectPropertyOfAxiom => { - val term1 = RSAOntology.genFreshVariable() + val term1 = RSAUtil.genFreshVariable() val body = convert(a.getSubProperty, term, term1, suffix) val head = convert(a.getSuperProperty, term, term1, suffix) ResultR(List(Rule.create(head, body))) } case a: OWLSubDataPropertyOfAxiom => { - val term1 = RSAOntology.genFreshVariable() + val term1 = RSAUtil.genFreshVariable() val body = convert(a.getSubProperty, term, term1, suffix) val head = convert(a.getSuperProperty, term, term1, suffix) ResultR(List(Rule.create(head, body))) @@ -160,7 +161,7 @@ trait RDFoxConverter { convert(a.asOWLSubClassOfAxiom, term, unsafe, skolem, suffix) case a: OWLObjectPropertyRangeAxiom => { - val term1 = RSAOntology.genFreshVariable() + val term1 = RSAUtil.genFreshVariable() val (res, ext) = convert(a.getRange, term, unsafe, skolem, suffix) val prop = convert(a.getProperty, term1, term, suffix) ResultR(List(Rule.create(res, prop :: ext))) @@ -327,7 +328,7 @@ trait RDFoxConverter { case e: OWLObjectSomeValuesFrom => { val cls = e.getFiller() val role = e.getProperty() - val varX = RSAOntology.genFreshVariable + val varX = RSAUtil.genFreshVariable val (bind, term1) = skolem match { case NoSkolem => (None, varX) case c: Constant => (None, c.iri) @@ -354,7 +355,7 @@ trait RDFoxConverter { // Computes the result of rule skolemization. Depending on the used // technique it might involve the introduction of additional atoms, // and/or fresh constants and variables. - val varX = RSAOntology.genFreshVariable + val varX = RSAUtil.genFreshVariable val (bind, term1) = skolem match { case NoSkolem => (None, varX) case c: Constant => (None, c.iri) @@ -379,7 +380,7 @@ trait RDFoxConverter { s"Class expression '$e' has cardinality restriction != 1." ) val vars @ (y :: z :: _) = - Seq(RSAOntology.genFreshVariable(), RSAOntology.genFreshVariable()) + Seq(RSAUtil.genFreshVariable(), RSAUtil.genFreshVariable()) val cls = e.getFiller val role = e.getProperty val (res, ext) = vars.map(convert(cls, _, unsafe, skolem, suffix)).unzip diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala index 9b04f0e..a0d1b5d 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala @@ -9,6 +9,7 @@ import tech.oxfordsemantic.jrdfox.logic.datalog.{ } import tech.oxfordsemantic.jrdfox.logic.expression.{IRI} +import uk.ac.ox.cs.rsacomb.RSAUtil import uk.ac.ox.cs.rsacomb.RSAOntology import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Nth} import uk.ac.ox.cs.rsacomb.util.RDFoxUtil @@ -78,7 +79,7 @@ object RSAAtom { if (isRDF) { (None, List(atom)) } else { - val varS = RSAOntology.genFreshVariable() + val varS = RSAUtil.genFreshVariable() val skolem = RDFoxUtil.skolem(name, (args :+ varS): _*) val atom = TupleTableAtom.rdf(varS, IRI.RDF_TYPE, name) val atoms = args.zipWithIndex diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/ontologies/Ontology.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/ontologies/Ontology.scala index d7e57dd..7856b3a 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/ontologies/Ontology.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/ontologies/Ontology.scala @@ -18,27 +18,158 @@ package uk.ac.ox.cs.rsacomb.ontology import java.io.File +import scala.collection.mutable.Map +import scala.collection.JavaConverters._ +import scalax.collection.Graph +import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._ + import org.semanticweb.owlapi.apibinding.OWLManager import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom, OWLLogicalAxiom} import org.semanticweb.owlapi.model.{OWLObjectPropertyExpression} import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory +import tech.oxfordsemantic.jrdfox.logic.expression.Resource object Ontology { - /** Manager instance to interface with OWLAPI */ + /** Type wrapper representing a dependency graph for the ontology. + * + * The graph is returned along with a map associating each node (IRI + * string of the resource), with the corresponding axiom in the + * original TBox. + */ + type DependencyGraph = (Graph[Resource, DiEdge], Map[String, OWLAxiom]) + + /** Manager instance to interface with OWLAPI + * + * TODO: turn this into an implicit class parameter. + */ val manager = OWLManager.createOWLOntologyManager() //val factory = manager.getOWLDataFactory() + /** Compute the RSA dependency graph for a set of axioms + * + * @param axioms set of input axioms (TBox) to build the dependency + * graph. + * @param datafiles data (ABox) to build the dependency graph. + * @param unsafe list of unsafe roles in the TBox. + * + * @return a tuple containing the dependency graph and a map between + * the newly introduced constants and the corresponding input axioms. + * + * @note no check on the ontology language is performed since the + * construction of the dependency graph is computed regardless. The + * input axioms are assumed to be normalized. + */ + def dependencyGraph( + axioms: List[OWLLogicalAxiom], + datafiles: List[File], + unsafe: List[OWLObjectPropertyExpression] + ): DependencyGraph = { + + import org.semanticweb.owlapi.model.{ + OWLClassExpression, + OWLObjectSomeValuesFrom, + OWLDataSomeValuesFrom + } + import tech.oxfordsemantic.jrdfox.logic.datalog.Rule + import tech.oxfordsemantic.jrdfox.logic.expression.{Term, Variable} + import uk.ac.ox.cs.rsacomb.suffix._ + import uk.ac.ox.cs.rsacomb.converter._ + import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} + import uk.ac.ox.cs.rsacomb.RSAUtil + + var nodemap = Map.empty[String, OWLAxiom] + + object RSAConverter extends RDFoxConverter { + + override def convert( + expr: OWLClassExpression, + term: Term, + unsafe: List[OWLObjectPropertyExpression], + skolem: SkolemStrategy, + suffix: RSASuffix + ): Shards = + (expr, skolem) match { + + case (e: OWLObjectSomeValuesFrom, c: Constant) => { + nodemap.update(c.iri.getIRI, c.axiom) + val (res, ext) = super.convert(e, term, unsafe, skolem, suffix) + if (unsafe contains e.getProperty) + (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext) + else + (RSA.PE(term, c.iri) :: res, ext) + } + + case (e: OWLDataSomeValuesFrom, c: Constant) => { + nodemap.update(c.iri.getIRI, c.axiom) + val (res, ext) = super.convert(e, term, unsafe, skolem, suffix) + if (unsafe contains e.getProperty) + (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext) + else + (RSA.PE(term, c.iri) :: res, ext) + } + + case _ => super.convert(expr, term, unsafe, skolem, suffix) + } + } + + /* Ontology convertion into LP rules */ + val term = RSAUtil.genFreshVariable() + val result = axioms.map(a => + RSAConverter.convert(a, term, unsafe, new Constant(a), Empty) + ) + + val datalog = result.unzip + val facts = datalog._1.flatten + var rules = datalog._2.flatten + + /* Open connection with RDFox */ + val (server, data) = RDFoxUtil.openConnection("rsa_dependency_graph") + + /* Add additional built-in rules */ + val varX = Variable.create("X") + val varY = Variable.create("Y") + rules = Rule.create( + RSA.E(varX, varY), + RSA.PE(varX, varY), + RSA.U(varX), + RSA.U(varY) + ) :: rules + /* Load facts and rules from ontology */ + RDFoxUtil.addFacts(data, facts) + RDFoxUtil.addRules(data, rules) + /* Load data files */ + RDFoxUtil.addData(data, datafiles: _*) + + /* Build the graph */ + val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }" + val answers = RDFoxUtil.submitQuery(data, query, RSA.Prefixes).get + var edges: Seq[DiEdge[Resource]] = + answers.collect { case (_, Seq(n1, n2)) => n1 ~> n2 } + val graph = Graph(edges: _*) + + /* Close connection to RDFox */ + RDFoxUtil.closeConnection(server, data) + + (graph, nodemap) + } } -/** A wrapper for +/** A wrapper for a generic OWL2 ontology */ class Ontology(val axioms: List[OWLLogicalAxiom], val datafiles: List[File]) { /** Extend OWLAxiom functionalities */ import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._ - /** OWLOntology based on input axioms */ + /** Simplify conversion between Java and Scala collections */ + import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ + + /** OWLOntology based on input axioms + * + * This is mainly used to instantiate a new reasoner to be used in + * the computation of unsafe roles. + */ private val ontology: OWLOntology = Ontology.manager.createOntology((axioms: List[OWLAxiom]).asJava) @@ -87,4 +218,8 @@ class Ontology(val axioms: List[OWLLogicalAxiom], val datafiles: List[File]) { unsafe1 ++ unsafe2 } + + lazy val dependencyGraph: Ontology.DependencyGraph = + Ontology.dependencyGraph(axioms, datafiles, this.unsafe) + } -- cgit v1.2.3