From 79ca4d0b3fe7df238bede90b96226e72707d05b7 Mon Sep 17 00:00:00 2001 From: Federico Igne Date: Thu, 22 Jul 2021 17:15:42 +0100 Subject: Fix file naming --- .../cs/rsacomb/approximation/Approximation.scala | 18 ++ .../ox/cs/rsacomb/approximation/Lowerbound.scala | 232 ++++++++++++++++ .../cs/rsacomb/approximation/approximation.scala | 18 -- .../ox/cs/rsacomb/approximation/lowerbound.scala | 232 ---------------- .../uk/ac/ox/cs/rsacomb/ontologies/Ontology.scala | 299 --------------------- .../uk/ac/ox/cs/rsacomb/ontology/Ontology.scala | 299 +++++++++++++++++++++ 6 files changed, 549 insertions(+), 549 deletions(-) create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Approximation.scala create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Lowerbound.scala delete mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/approximation/approximation.scala delete mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/approximation/lowerbound.scala delete mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/ontologies/Ontology.scala create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/ontology/Ontology.scala (limited to 'src/main/scala/uk') diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Approximation.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Approximation.scala new file mode 100644 index 0000000..344f0fe --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Approximation.scala @@ -0,0 +1,18 @@ +package uk.ac.ox.cs.rsacomb.approximation + +import java.io.File +import org.semanticweb.owlapi.model.OWLLogicalAxiom + +import uk.ac.ox.cs.rsacomb.ontology.Ontology + +/** Ontology approximation technique. */ +trait Approximation[T] { + + /** Approximate an ontology. + * + * @param ontology input ontology as a list of axioms + * @return the approximated ontology + */ + def approximate(ontology: Ontology): T + +} 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 new file mode 100644 index 0000000..3fc4988 --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Lowerbound.scala @@ -0,0 +1,232 @@ +package uk.ac.ox.cs.rsacomb.approximation + +import java.io.File + +import org.semanticweb.owlapi.apibinding.OWLManager +import org.semanticweb.owlapi.model.{IRI => _, _} + +import tech.oxfordsemantic.jrdfox.logic.expression.{Resource, IRI} + +import scala.collection.mutable.{Set, Map} +import scalax.collection.Graph +import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._ +import scalax.collection.GraphTraversal._ + +import uk.ac.ox.cs.rsacomb.RSAOntology +import uk.ac.ox.cs.rsacomb.RSAUtil +import uk.ac.ox.cs.rsacomb.ontology.Ontology + +object LowerBound { + + private val manager = OWLManager.createOWLOntologyManager() + private val factory = manager.getOWLDataFactory() + +} + +/** Approximation algorithm that mantains soundness for CQ answering. + * + * The input OWL 2 ontology is assumed to be normalized and the output + * ontology is guaranteed to be in RSA. + * + * The algorithm is performed in three steps: + * 1. the ontology is reduced to ALCHOIQ by discarding any axiom + * that is not in the language; + * 2. the ontology is further reduced to Horn-ALCHOIQ by shifting + * axioms with disjunction on the rhs; + * 3. the ontology is approximated to RSA by manipulating its + * dependency graph. + * + * @see [[uk.ac.ox.cs.rsacomb.converter.Normalizer]] + */ +class LowerBound extends Approximation[RSAOntology] { + + /** Simplify conversion between Java and Scala collections */ + import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ + + /** Simplify conversion between OWLAPI and RDFox concepts */ + import uk.ac.ox.cs.rsacomb.implicits.RDFox._ + + /** Main entry point for the approximation algorithm */ + def approximate(ontology: Ontology): RSAOntology = + toRSA( + new Ontology( + ontology.axioms filterNot inALCHOIQ flatMap shift, + ontology.datafiles + ) + ) + + /** Discards all axioms outside ALCHOIQ */ + private def inALCHOIQ(axiom: OWLLogicalAxiom): Boolean = + axiom match { + case a: OWLSubClassOfAxiom => { + val sub = a.getSubClass.getNNF + val sup = a.getSuperClass.getNNF + (sub, sup) match { + case (sub: OWLObjectAllValuesFrom, _) => false + case (sub: OWLDataAllValuesFrom, _) => false + case (_, sup: OWLDataAllValuesFrom) => false + case (sub: OWLObjectMinCardinality, _) if sub.getCardinality >= 2 => + false + case (sub: OWLDataMinCardinality, _) if sub.getCardinality >= 2 => + false + case (_, sup: OWLObjectMinCardinality) if sup.getCardinality >= 2 => + false + case (_, sup: OWLDataMinCardinality) if sup.getCardinality >= 2 => + false + case (sub: OWLObjectMaxCardinality, _) => false + case (sub: OWLDataMaxCardinality, _) => false + case (_, sup: OWLObjectMaxCardinality) if sup.getCardinality >= 2 => + false + case (_, sup: OWLDataMaxCardinality) if sup.getCardinality >= 1 => + false + case (_, sup: OWLObjectOneOf) if sup.getIndividuals.length > 2 => + false + case (sub: OWLObjectHasSelf, _) => false + case (_, sup: OWLObjectHasSelf) => false + case _ => true + } + } + case a: OWLTransitiveObjectPropertyAxiom => false + case a: OWLReflexiveObjectPropertyAxiom => false + case a: OWLSubPropertyChainOfAxiom => false + case a: OWLAsymmetricObjectPropertyAxiom => false + case a => true + } + + /** Shifting axioms with disjunction on the rhs. + * + * The process of shifting presenves soundness but completenes w.r.t. + * CQ answering is lost. + * + * @example + * + * A -> B1 u B2 u B3 . + * + * becomes + * + * A n nB1 n nB2 n nB3 -> bot . + * A n nB1 n nB2 -> B3 . + * A n nB1 n nB3 -> B2 . + * A n nB2 n nB3 -> B1 . + * nB1 n nB2 n nB3 -> nA . + * + * where nA, nB1, nB2, nB3 are fresh predicates "corresponding" to + * the negation of A, B1, B2, B3 respectively. + * + * @note this method maintains the normal form of the input axiom. + */ + private def shift(axiom: OWLLogicalAxiom): List[OWLLogicalAxiom] = + axiom match { + case a: OWLSubClassOfAxiom => { + val sub = a.getSubClass.getNNF + val sup = a.getSuperClass.getNNF + sup match { + case sup: OWLObjectUnionOf => { + val body = sub.asConjunctSet.map((atom) => + (atom, RSAUtil.getFreshOWLClass()) + ) + val head = sup.asDisjunctSet.map((atom) => + (atom, RSAUtil.getFreshOWLClass()) + ) + + val r1 = + LowerBound.factory.getOWLSubClassOfAxiom( + LowerBound.factory.getOWLObjectIntersectionOf( + (body.map(_._1) ++ head.map(_._2)): _* + ), + LowerBound.factory.getOWLNothing + ) + + val r2s = + for { + (a, na) <- head + hs = head.map(_._2).filterNot(_ equals na) + } yield LowerBound.factory.getOWLSubClassOfAxiom( + LowerBound.factory.getOWLObjectIntersectionOf( + (body.map(_._1) ++ hs): _* + ), + a + ) + + val r3s = + for { + (a, na) <- body + bs = body.map(_._1).filterNot(_ equals a) + } yield LowerBound.factory.getOWLSubClassOfAxiom( + LowerBound.factory.getOWLObjectIntersectionOf( + (bs ++ head.map(_._2)): _* + ), + na + ) + + List(r1) ++ r2s ++ r3s + } + case _ => List(axiom) + } + } + case _ => List(axiom) + } + + /** Approximate a Horn-ALCHOIQ ontology to RSA + * + * This is done by gathering those axioms that prevent the ontology + * dependency graph from being tree-shaped, and removing them. + * + * @param ontology the set of axioms to approximate. + * @return the approximated RSA ontology + */ + private def toRSA(ontology: Ontology): RSAOntology = { + /* Compute the dependency graph for the ontology */ + val (graph, nodemap) = ontology.dependencyGraph + + /* Define node colors for the graph visit */ + sealed trait NodeColor + case object Unvisited extends NodeColor + case object Visited extends NodeColor + case object ToDelete extends NodeColor + + /* Keep track of node colors during graph visit */ + var color = Map.from[Resource, NodeColor]( + graph.nodes.toOuter.map(k => (k, Unvisited)) + ) + + for { + component <- graph.componentTraverser().map(_ to Graph) + edge <- component + .outerEdgeTraverser(component.nodes.head) + .withKind(BreadthFirst) + } yield { + val source = edge._1 + val target = edge._2 + color(source) match { + case Unvisited | Visited => { + color(target) match { + case Unvisited => + color(source) = Visited; + color(target) = Visited + case Visited => + color(source) = ToDelete + case ToDelete => + color(source) = Visited + } + } + case ToDelete => + } + } + + val toDelete = color.collect { case (resource: IRI, ToDelete) => + nodemap(resource.getIRI) + }.toList + + /* Remove axioms from approximated ontology */ + RSAOntology(ontology.axioms diff toDelete, ontology.datafiles) + } + + // val edges1 = Seq('A ~> 'B, 'B ~> 'C, 'C ~> 'D, 'D ~> 'H, 'H ~> + // 'G, 'G ~> 'F, 'E ~> 'A, 'E ~> 'F, 'B ~> 'E, 'F ~> 'G, 'B ~> 'F, + // 'C ~> 'G, 'D ~> 'C, 'H ~> 'D) + // val edges2 = Seq('I ~> 'M, 'I ~> 'L, 'L ~> 'N, 'M ~> 'N) + // val edges3 = Seq('P ~> 'O) + // val graph = Graph.from(edges = edges1 ++ edges2 ++ edges3) + // +} diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/approximation.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/approximation.scala deleted file mode 100644 index 344f0fe..0000000 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/approximation.scala +++ /dev/null @@ -1,18 +0,0 @@ -package uk.ac.ox.cs.rsacomb.approximation - -import java.io.File -import org.semanticweb.owlapi.model.OWLLogicalAxiom - -import uk.ac.ox.cs.rsacomb.ontology.Ontology - -/** Ontology approximation technique. */ -trait Approximation[T] { - - /** Approximate an ontology. - * - * @param ontology input ontology as a list of axioms - * @return the approximated ontology - */ - def approximate(ontology: Ontology): T - -} 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 deleted file mode 100644 index 766ea0e..0000000 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/lowerbound.scala +++ /dev/null @@ -1,232 +0,0 @@ -package uk.ac.ox.cs.rsacomb.approximation - -import java.io.File - -import org.semanticweb.owlapi.apibinding.OWLManager -import org.semanticweb.owlapi.model.{IRI => _, _} - -import tech.oxfordsemantic.jrdfox.logic.expression.{Resource, IRI} - -import scala.collection.mutable.{Set, Map} -import scalax.collection.Graph -import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._ -import scalax.collection.GraphTraversal._ - -import uk.ac.ox.cs.rsacomb.RSAOntology -import uk.ac.ox.cs.rsacomb.RSAUtil -import uk.ac.ox.cs.rsacomb.ontology.Ontology - -object LowerBound { - - private val manager = OWLManager.createOWLOntologyManager() - private val factory = manager.getOWLDataFactory() - -} - -/** Approximation algorithm that mantains soundness for CQ answering. - * - * The input OWL 2 ontology is assumed to be normalized and the output - * ontology is guaranteed to be in RSA. - * - * The algorithm is performed in three steps: - * 1. the ontology is reduced to ALCHOIQ by discarding any axiom - * that is not in the language; - * 2. the ontology is further reduced to Horn-ALCHOIQ by shifting - * axioms with disjunction on the rhs; - * 3. the ontology is approximated to RSA by manipulating its - * dependency graph. - * - * @see [[uk.ac.ox.cs.rsacomb.converter.Normalizer]] - */ -class LowerBound extends Approximation[RSAOntology] { - - /** Simplify conversion between Java and Scala collections */ - import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ - - /** Simplify conversion between OWLAPI and RDFox concepts */ - import uk.ac.ox.cs.rsacomb.implicits.RDFox._ - - /** Main entry point for the approximation algorithm */ - def approximate(ontology: Ontology): RSAOntology = - toRSA( - new Ontology( - ontology.axioms filterNot inALCHOIQ flatMap shift, - ontology.datafiles - ) - ) - - /** Discards all axioms outside ALCHOIQ */ - private def inALCHOIQ(axiom: OWLLogicalAxiom): Boolean = - axiom match { - case a: OWLSubClassOfAxiom => { - val sub = a.getSubClass.getNNF - val sup = a.getSuperClass.getNNF - (sub, sup) match { - case (sub: OWLObjectAllValuesFrom, _) => false - case (sub: OWLDataAllValuesFrom, _) => false - case (_, sup: OWLDataAllValuesFrom) => false - case (sub: OWLObjectMinCardinality, _) if sub.getCardinality >= 2 => - false - case (sub: OWLDataMinCardinality, _) if sub.getCardinality >= 2 => - false - case (_, sup: OWLObjectMinCardinality) if sup.getCardinality >= 2 => - false - case (_, sup: OWLDataMinCardinality) if sup.getCardinality >= 2 => - false - case (sub: OWLObjectMaxCardinality, _) => false - case (sub: OWLDataMaxCardinality, _) => false - case (_, sup: OWLObjectMaxCardinality) if sup.getCardinality >= 2 => - false - case (_, sup: OWLDataMaxCardinality) if sup.getCardinality >= 1 => - false - case (_, sup: OWLObjectOneOf) if sup.getIndividuals.length > 2 => - false - case (sub: OWLObjectHasSelf, _) => false - case (_, sup: OWLObjectHasSelf) => false - case _ => true - } - } - case a: OWLTransitiveObjectPropertyAxiom => false - case a: OWLReflexiveObjectPropertyAxiom => false - case a: OWLSubPropertyChainOfAxiom => false - case a: OWLAsymmetricObjectPropertyAxiom => false - case a => true - } - - /** Shifting axioms with disjunction on the rhs. - * - * The process of shifting presenves soundness but completenes w.r.t. - * CQ answering is lost. - * - * @example - * - * A -> B1 u B2 u B3 . - * - * becomes - * - * A n nB1 n nB2 n nB3 -> bot . - * A n nB1 n nB2 -> B3 . - * A n nB1 n nB3 -> B2 . - * A n nB2 n nB3 -> B1 . - * nB1 n nB2 n nB3 -> nA . - * - * where nA, nB1, nB2, nB3 are fresh predicates "corresponding" to - * the negation of A, B1, B2, B3 respectively. - * - * @note this method maintains the normal form of the input axiom. - */ - private def shift(axiom: OWLLogicalAxiom): List[OWLLogicalAxiom] = - axiom match { - case a: OWLSubClassOfAxiom => { - val sub = a.getSubClass.getNNF - val sup = a.getSuperClass.getNNF - sup match { - case sup: OWLObjectUnionOf => { - val body = sub.asConjunctSet.map((atom) => - (atom, RSAUtil.getFreshOWLClass()) - ) - val head = sup.asDisjunctSet.map((atom) => - (atom, RSAUtil.getFreshOWLClass()) - ) - - val r1 = - LowerBound.factory.getOWLSubClassOfAxiom( - LowerBound.factory.getOWLObjectIntersectionOf( - (body.map(_._1) ++ head.map(_._2)): _* - ), - LowerBound.factory.getOWLNothing - ) - - val r2s = - for { - (a, na) <- head - hs = head.map(_._2).filterNot(_ equals na) - } yield LowerBound.factory.getOWLSubClassOfAxiom( - LowerBound.factory.getOWLObjectIntersectionOf( - (body.map(_._1) ++ hs): _* - ), - a - ) - - val r3s = - for { - (a, na) <- body - bs = body.map(_._1).filterNot(_ equals a) - } yield LowerBound.factory.getOWLSubClassOfAxiom( - LowerBound.factory.getOWLObjectIntersectionOf( - (bs ++ head.map(_._2)): _* - ), - na - ) - - List(r1) ++ r2s ++ r3s - } - case _ => List(axiom) - } - } - case _ => List(axiom) - } - - /** Approximate a Horn-ALCHOIQ ontology to RSA - * - * This is done by gathering those axioms that prevent the ontology - * dependency graph from being tree-shaped, and removing them. - * - * @param axioms the set of axioms to approximate. - * @return the approximated RSA ontology - */ - private def toRSA(ontology: Ontology): RSAOntology = { - /* Compute the dependency graph for the ontology */ - val (graph, nodemap) = ontology.dependencyGraph - - /* Define node colors for the graph visit */ - sealed trait NodeColor - case object Unvisited extends NodeColor - case object Visited extends NodeColor - case object ToDelete extends NodeColor - - /* Keep track of node colors during graph visit */ - var color = Map.from[Resource, NodeColor]( - graph.nodes.toOuter.map(k => (k, Unvisited)) - ) - - for { - component <- graph.componentTraverser().map(_ to Graph) - edge <- component - .outerEdgeTraverser(component.nodes.head) - .withKind(BreadthFirst) - } yield { - val source = edge._1 - val target = edge._2 - color(source) match { - case Unvisited | Visited => { - color(target) match { - case Unvisited => - color(source) = Visited; - color(target) = Visited - case Visited => - color(source) = ToDelete - case ToDelete => - color(source) = Visited - } - } - case ToDelete => - } - } - - val toDelete = color.iterator.collect { case (resource: IRI, ToDelete) => - nodemap(resource.getIRI) - }.toList - - /* Remove axioms from approximated ontology */ - RSAOntology(ontology.axioms diff toDelete, ontology.datafiles) - } - - // val edges1 = Seq('A ~> 'B, 'B ~> 'C, 'C ~> 'D, 'D ~> 'H, 'H ~> - // 'G, 'G ~> 'F, 'E ~> 'A, 'E ~> 'F, 'B ~> 'E, 'F ~> 'G, 'B ~> 'F, - // 'C ~> 'G, 'D ~> 'C, 'H ~> 'D) - // val edges2 = Seq('I ~> 'M, 'I ~> 'L, 'L ~> 'N, 'M ~> 'N) - // val edges3 = Seq('P ~> 'O) - // val graph = Graph.from(edges = edges1 ++ edges2 ++ edges3) - // -} 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 deleted file mode 100644 index d73704f..0000000 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/ontologies/Ontology.scala +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright 2020, 2021 KRR Oxford - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package uk.ac.ox.cs.rsacomb.ontology - -import java.io.File -import java.util.stream.Collectors - -import scala.collection.mutable.Map -import scala.collection.JavaConverters._ -import scalax.collection.Graph -import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._ - -import org.semanticweb.owlapi.model.parameters.Imports -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.datalog.Rule -import tech.oxfordsemantic.jrdfox.logic.expression.{Resource, Term, Variable} - -import uk.ac.ox.cs.rsacomb.approximation.Approximation -import uk.ac.ox.cs.rsacomb.converter._ -import uk.ac.ox.cs.rsacomb.suffix._ -import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} - -import uk.ac.ox.cs.rsacomb.RSAUtil - -object Ontology { - - /** Simplify conversion between Java and Scala collections */ - import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ - - /** 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() - - /** 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 = { - - var nodemap = Map.empty[String, OWLAxiom] - - /* Create custom converter */ - object RSAConverter extends RDFoxConverter { - - import org.semanticweb.owlapi.model.{ - OWLClassExpression, - OWLObjectSomeValuesFrom, - OWLDataSomeValuesFrom - } - - 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) - } - - def apply(axioms: List[OWLLogicalAxiom], datafiles: List[File]): Ontology = - new Ontology(axioms, datafiles) - - def apply(ontology: OWLOntology, datafiles: List[File]): Ontology = { - - /** TBox axioms */ - var tbox: List[OWLLogicalAxiom] = - ontology - .tboxAxioms(Imports.INCLUDED) - .collect(Collectors.toList()) - .collect { case a: OWLLogicalAxiom => a } - - /** RBox axioms */ - var rbox: List[OWLLogicalAxiom] = - ontology - .rboxAxioms(Imports.INCLUDED) - .collect(Collectors.toList()) - .collect { case a: OWLLogicalAxiom => a } - - /** ABox axioms - * - * @note this represents only the set of assertions contained in the - * ontology file. Data files specified in `datafiles` are directly - * imported in RDFox due to performance issues when trying to import - * large data files via OWLAPI. - */ - var abox: List[OWLLogicalAxiom] = - ontology - .aboxAxioms(Imports.INCLUDED) - .collect(Collectors.toList()) - .collect { case a: OWLLogicalAxiom => a } - - Ontology(abox ::: tbox ::: rbox, datafiles) - } - - def apply(ontofile: File, datafiles: List[File]): Ontology = { - val ontology = manager.loadOntologyFromOntologyDocument(ontofile) - Ontology(ontology, datafiles) - } - -} - -/** A wrapper for a generic OWL2 ontology - * - * @param axioms list of axioms (roughly) corresponding to the TBox. - * @param datafiles files containing ABox data. - */ -class Ontology(val axioms: List[OWLLogicalAxiom], val datafiles: List[File]) { - - /** Extend OWLAxiom functionalities */ - import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._ - - /** 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. - */ - protected val ontology: OWLOntology = - Ontology.manager.createOntology((axioms: List[OWLAxiom]).asJava) - - /** OWLAPI internal reasoner for ontology */ - protected val reasoner = - (new StructuralReasonerFactory()).createReasoner(ontology) - - /** Unsafe roles in the ontology - * - * Unsafety conditions are the following: - * - * 1) For all roles r1 appearing in an axiom of type T5, r1 is unsafe - * if there exists a role r2 (different from top) appearing in an - * axiom of type T3 and r1 is a subproperty of the inverse of r2. - * - * 2) For all roles p1 appearing in an axiom of type T5, p1 is unsafe - * if there exists a role p2 appearing in an axiom of type T4 and - * p1 is a subproperty of either p2 or the inverse of p2. - */ - lazy val unsafe: List[OWLObjectPropertyExpression] = { - - /* Checking for unsafety condition (1) */ - val unsafe1 = for { - axiom <- axioms - if axiom.isT5 - role1 <- axiom.objectPropertyExpressionsInSignature - roleSuper = role1 +: reasoner.superObjectProperties(role1) - axiom <- axioms - if axiom.isT3 && !axiom.isT3top - role2 <- axiom.objectPropertyExpressionsInSignature - if roleSuper contains role2.getInverseProperty - } yield role1 - - /* Checking for unsafety condition (2) */ - val unsafe2 = for { - axiom <- axioms - if axiom.isT5 - role1 <- axiom.objectPropertyExpressionsInSignature - roleSuper = role1 +: reasoner.superObjectProperties(role1) - axiom <- axioms - if axiom.isT4 - role2 <- axiom.objectPropertyExpressionsInSignature - if roleSuper.contains(role2) || - roleSuper.contains(role2.getInverseProperty) - } yield role1 - - unsafe1 ++ unsafe2 - } - - /** Compute the dependency graph for the ontology */ - lazy val dependencyGraph: Ontology.DependencyGraph = - Ontology.dependencyGraph(axioms, datafiles, this.unsafe) - - /** RSA check */ - lazy val isRSA: Boolean = ??? - - /** Normalize the ontology according to the given normalizer - * - * @param normalizer the normalization technique to be used. - * @return a new normalized [[Ontology]]. - */ - def normalize(normalizer: Normalizer): Ontology = - new Ontology( - axioms flatMap normalizer.normalize, - datafiles - ) - - /** Approximate the ontology according to the given approximation - * technique. - * - * @param approximation the approximation to be used on the ontology. - * @return the result of the approximation. - */ - def approximate[T](approximation: Approximation[T]): T = - approximation.approximate(this) -} diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/ontology/Ontology.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/ontology/Ontology.scala new file mode 100644 index 0000000..d73704f --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/ontology/Ontology.scala @@ -0,0 +1,299 @@ +/* + * Copyright 2020, 2021 KRR Oxford + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.ac.ox.cs.rsacomb.ontology + +import java.io.File +import java.util.stream.Collectors + +import scala.collection.mutable.Map +import scala.collection.JavaConverters._ +import scalax.collection.Graph +import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._ + +import org.semanticweb.owlapi.model.parameters.Imports +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.datalog.Rule +import tech.oxfordsemantic.jrdfox.logic.expression.{Resource, Term, Variable} + +import uk.ac.ox.cs.rsacomb.approximation.Approximation +import uk.ac.ox.cs.rsacomb.converter._ +import uk.ac.ox.cs.rsacomb.suffix._ +import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} + +import uk.ac.ox.cs.rsacomb.RSAUtil + +object Ontology { + + /** Simplify conversion between Java and Scala collections */ + import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ + + /** 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() + + /** 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 = { + + var nodemap = Map.empty[String, OWLAxiom] + + /* Create custom converter */ + object RSAConverter extends RDFoxConverter { + + import org.semanticweb.owlapi.model.{ + OWLClassExpression, + OWLObjectSomeValuesFrom, + OWLDataSomeValuesFrom + } + + 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) + } + + def apply(axioms: List[OWLLogicalAxiom], datafiles: List[File]): Ontology = + new Ontology(axioms, datafiles) + + def apply(ontology: OWLOntology, datafiles: List[File]): Ontology = { + + /** TBox axioms */ + var tbox: List[OWLLogicalAxiom] = + ontology + .tboxAxioms(Imports.INCLUDED) + .collect(Collectors.toList()) + .collect { case a: OWLLogicalAxiom => a } + + /** RBox axioms */ + var rbox: List[OWLLogicalAxiom] = + ontology + .rboxAxioms(Imports.INCLUDED) + .collect(Collectors.toList()) + .collect { case a: OWLLogicalAxiom => a } + + /** ABox axioms + * + * @note this represents only the set of assertions contained in the + * ontology file. Data files specified in `datafiles` are directly + * imported in RDFox due to performance issues when trying to import + * large data files via OWLAPI. + */ + var abox: List[OWLLogicalAxiom] = + ontology + .aboxAxioms(Imports.INCLUDED) + .collect(Collectors.toList()) + .collect { case a: OWLLogicalAxiom => a } + + Ontology(abox ::: tbox ::: rbox, datafiles) + } + + def apply(ontofile: File, datafiles: List[File]): Ontology = { + val ontology = manager.loadOntologyFromOntologyDocument(ontofile) + Ontology(ontology, datafiles) + } + +} + +/** A wrapper for a generic OWL2 ontology + * + * @param axioms list of axioms (roughly) corresponding to the TBox. + * @param datafiles files containing ABox data. + */ +class Ontology(val axioms: List[OWLLogicalAxiom], val datafiles: List[File]) { + + /** Extend OWLAxiom functionalities */ + import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._ + + /** 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. + */ + protected val ontology: OWLOntology = + Ontology.manager.createOntology((axioms: List[OWLAxiom]).asJava) + + /** OWLAPI internal reasoner for ontology */ + protected val reasoner = + (new StructuralReasonerFactory()).createReasoner(ontology) + + /** Unsafe roles in the ontology + * + * Unsafety conditions are the following: + * + * 1) For all roles r1 appearing in an axiom of type T5, r1 is unsafe + * if there exists a role r2 (different from top) appearing in an + * axiom of type T3 and r1 is a subproperty of the inverse of r2. + * + * 2) For all roles p1 appearing in an axiom of type T5, p1 is unsafe + * if there exists a role p2 appearing in an axiom of type T4 and + * p1 is a subproperty of either p2 or the inverse of p2. + */ + lazy val unsafe: List[OWLObjectPropertyExpression] = { + + /* Checking for unsafety condition (1) */ + val unsafe1 = for { + axiom <- axioms + if axiom.isT5 + role1 <- axiom.objectPropertyExpressionsInSignature + roleSuper = role1 +: reasoner.superObjectProperties(role1) + axiom <- axioms + if axiom.isT3 && !axiom.isT3top + role2 <- axiom.objectPropertyExpressionsInSignature + if roleSuper contains role2.getInverseProperty + } yield role1 + + /* Checking for unsafety condition (2) */ + val unsafe2 = for { + axiom <- axioms + if axiom.isT5 + role1 <- axiom.objectPropertyExpressionsInSignature + roleSuper = role1 +: reasoner.superObjectProperties(role1) + axiom <- axioms + if axiom.isT4 + role2 <- axiom.objectPropertyExpressionsInSignature + if roleSuper.contains(role2) || + roleSuper.contains(role2.getInverseProperty) + } yield role1 + + unsafe1 ++ unsafe2 + } + + /** Compute the dependency graph for the ontology */ + lazy val dependencyGraph: Ontology.DependencyGraph = + Ontology.dependencyGraph(axioms, datafiles, this.unsafe) + + /** RSA check */ + lazy val isRSA: Boolean = ??? + + /** Normalize the ontology according to the given normalizer + * + * @param normalizer the normalization technique to be used. + * @return a new normalized [[Ontology]]. + */ + def normalize(normalizer: Normalizer): Ontology = + new Ontology( + axioms flatMap normalizer.normalize, + datafiles + ) + + /** Approximate the ontology according to the given approximation + * technique. + * + * @param approximation the approximation to be used on the ontology. + * @return the result of the approximation. + */ + def approximate[T](approximation: Approximation[T]): T = + approximation.approximate(this) +} -- cgit v1.2.3