aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala266
1 files changed, 159 insertions, 107 deletions
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 763f4f5..2e055b9 100644
--- a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
@@ -21,7 +21,6 @@ import org.semanticweb.owlapi.model.{
21} 21}
22import org.semanticweb.owlapi.model.parameters.Imports 22import org.semanticweb.owlapi.model.parameters.Imports
23import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory 23import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory
24import org.semanticweb.owlapi.model.{IRI => OWLIRI}
25import uk.ac.manchester.cs.owl.owlapi.OWLObjectPropertyImpl 24import uk.ac.manchester.cs.owl.owlapi.OWLObjectPropertyImpl
26 25
27import tech.oxfordsemantic.jrdfox.client.{ 26import tech.oxfordsemantic.jrdfox.client.{
@@ -48,9 +47,10 @@ import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery
48/* Scala imports */ 47/* Scala imports */
49import scala.util.{Try, Success, Failure} 48import scala.util.{Try, Success, Failure}
50import scala.collection.JavaConverters._ 49import scala.collection.JavaConverters._
51import scala.collection.mutable.Set 50import scala.collection.mutable.{Set, Map}
52import scalax.collection.immutable.Graph 51import scalax.collection.Graph
53import scalax.collection.GraphEdge.UnDiEdge 52import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._
53import scalax.collection.GraphTraversal._
54 54
55/* Debug only */ 55/* Debug only */
56import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer 56import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer
@@ -170,103 +170,6 @@ class RSAOntology(_ontology: File, val datafiles: File*) {
170 .flatMap(_.objectPropertyExpressionsInSignature) 170 .flatMap(_.objectPropertyExpressionsInSignature)
171 .distinct 171 .distinct
172 172
173 /* Steps for RSA check
174 * 1) convert ontology axioms into LP rules
175 * 2) call RDFox on the onto and compute materialization
176 * 3) build graph from E(x,y) facts
177 * 4) check if the graph is tree-like
178 * ideally this annotates the graph with info about the reasons
179 * why the ontology might not be RSA. This could help a second
180 * step of approximation of an Horn-ALCHOIQ to RSA
181 *
182 * TODO: Implement additional checks (taking into account equality)
183 *
184 * To check if the graph is tree-like we check for acyclicity in a
185 * undirected graph.
186 */
187 lazy val isRSA: Boolean = Logger.timed(
188 {
189 val unsafe = this.unsafeRoles
190
191 object RSAConverter extends RDFoxConverter {
192
193 override def convert(
194 expr: OWLClassExpression,
195 term: Term,
196 unsafe: List[OWLObjectPropertyExpression],
197 skolem: SkolemStrategy,
198 suffix: RSASuffix
199 ): Shards =
200 (expr, skolem) match {
201
202 case (e: OWLObjectSomeValuesFrom, c: Constant)
203 if unsafe contains e.getProperty => {
204 val (res, ext) = super.convert(e, term, unsafe, skolem, suffix)
205 (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext)
206 }
207
208 case (e: OWLDataSomeValuesFrom, c: Constant)
209 if unsafe contains e.getProperty => {
210 val (res, ext) = super.convert(e, term, unsafe, skolem, suffix)
211 (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext)
212 }
213
214 case _ => super.convert(expr, term, unsafe, skolem, suffix)
215 }
216 }
217
218 /* Ontology convertion into LP rules */
219 val term = RSAOntology.genFreshVariable()
220 val conversion = Try(
221 axioms.map(a =>
222 RSAConverter.convert(a, term, unsafe, new Constant(a), Empty)
223 )
224 )
225
226 conversion match {
227 case Success(result) => {
228 val datalog = result.unzip
229 val facts = datalog._1.flatten
230 var rules = datalog._2.flatten
231
232 /* Open connection with RDFox */
233 val (server, data) = RDFoxUtil.openConnection("RSACheck")
234
235 /* Add additional built-in rules */
236 val varX = Variable.create("X")
237 val varY = Variable.create("Y")
238 rules = Rule.create(
239 RSA.E(varX, varY),
240 RSA.PE(varX, varY),
241 RSA.U(varX),
242 RSA.U(varY)
243 ) :: rules
244
245 /* Load facts and rules from ontology */
246 RDFoxUtil.addFacts(data, facts)
247 RDFoxUtil.addRules(data, rules)
248 /* Load data files */
249 RDFoxUtil.addData(data, datafiles: _*)
250
251 /* Build graph */
252 val graph = this.rsaGraph(data);
253
254 /* Close connection to RDFox */
255 RDFoxUtil.closeConnection(server, data)
256
257 /* Acyclicity test */
258 graph.isAcyclic
259 }
260 case Failure(e) => {
261 Logger print s"Unsupported axiom: $e"
262 false
263 }
264 }
265 },
266 "RSA check",
267 Logger.DEBUG
268 )
269
270 /** Unsafe roles of a given ontology. 173 /** Unsafe roles of a given ontology.
271 * 174 *
272 * Unsafety conditions are the following: 175 * Unsafety conditions are the following:
@@ -321,16 +224,165 @@ class RSAOntology(_ontology: File, val datafiles: File*) {
321 (unsafe1 ++ unsafe2).toList 224 (unsafe1 ++ unsafe2).toList
322 } 225 }
323 226
324 private def rsaGraph( 227 /** RSA dependency graph
325 data: DataStoreConnection 228 *
326 ): Graph[Resource, UnDiEdge] = { 229 * This is used to check whether the input ontology is RSA. This also
230 * helps us determine a suitable approximation of the ontology to
231 * RSA.
232 */
233 private lazy val dependencyGraph: Graph[Resource, DiEdge] = {
234 val unsafe = this.unsafeRoles
235 var nodemap = Map.empty[String, OWLAxiom]
236
237 object RSAConverter extends RDFoxConverter {
238
239 override def convert(
240 expr: OWLClassExpression,
241 term: Term,
242 unsafe: List[OWLObjectPropertyExpression],
243 skolem: SkolemStrategy,
244 suffix: RSASuffix
245 ): Shards =
246 (expr, skolem) match {
247
248 case (e: OWLObjectSomeValuesFrom, c: Constant) => {
249 nodemap.update(c.iri.getIRI, c.axiom)
250 val (res, ext) = super.convert(e, term, unsafe, skolem, suffix)
251 if (unsafe contains e.getProperty)
252 (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext)
253 else
254 (RSA.PE(term, c.iri) :: res, ext)
255 }
256
257 case (e: OWLDataSomeValuesFrom, c: Constant) => {
258 nodemap.update(c.iri.getIRI, c.axiom)
259 val (res, ext) = super.convert(e, term, unsafe, skolem, suffix)
260 if (unsafe contains e.getProperty)
261 (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext)
262 else
263 (RSA.PE(term, c.iri) :: res, ext)
264 }
265
266 case _ => super.convert(expr, term, unsafe, skolem, suffix)
267 }
268 }
269
270 /* Ontology convertion into LP rules */
271 val term = RSAOntology.genFreshVariable()
272 val result = axioms.map(a =>
273 RSAConverter.convert(a, term, unsafe, new Constant(a), Empty)
274 )
275
276 val datalog = result.unzip
277 val facts = datalog._1.flatten
278 var rules = datalog._2.flatten
279
280 /* Open connection with RDFox */
281 val (server, data) = RDFoxUtil.openConnection("RSACheck")
282
283 /* Add additional built-in rules */
284 val varX = Variable.create("X")
285 val varY = Variable.create("Y")
286 rules = Rule.create(
287 RSA.E(varX, varY),
288 RSA.PE(varX, varY),
289 RSA.U(varX),
290 RSA.U(varY)
291 ) :: rules
292
293 /* Load facts and rules from ontology */
294 RDFoxUtil.addFacts(data, facts)
295 RDFoxUtil.addRules(data, rules)
296 /* Load data files */
297 RDFoxUtil.addData(data, datafiles: _*)
298
299 /* Build graph */
327 val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }" 300 val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }"
328 val answers = RDFoxUtil.submitQuery(data, query, RSA.Prefixes).get 301 val answers = RDFoxUtil.submitQuery(data, query, RSA.Prefixes).get
329 var edges: Seq[UnDiEdge[Resource]] = 302 var edges: Seq[DiEdge[Resource]] =
330 answers.collect { case (_, Seq(n1, n2)) => UnDiEdge(n1, n2) } 303 answers.collect { case (_, Seq(n1, n2)) => n1 ~> n2 }
331 Graph(edges: _*) 304 val graph = Graph(edges: _*)
305
306 /* Close connection to RDFox */
307 RDFoxUtil.closeConnection(server, data)
308
309 /* Approximate the ontology to RSA */
310 approximate(graph, nodemap)
311
312 graph
332 } 313 }
333 314
315 /** Approximate a Horn-ALCHOIQ ontology to RSA
316 *
317 * This is done by gathering those axioms that prevent the ontology
318 * dependency graph `dependencyGraph` from being tree-shaped, and
319 * removing them.
320 *
321 * @param graph the graph used to compute the axioms to remove.
322 * @param nodemap map from graph nodes to ontology axioms.
323 */
324 private def approximate(
325 graph: Graph[Resource, DiEdge],
326 nodemap: Map[String, OWLAxiom]
327 ): Unit = {
328
329 /* Define node colors for the graph visit */
330 sealed trait NodeColor
331 case object Unvisited extends NodeColor
332 case object Visited extends NodeColor
333 case object ToDelete extends NodeColor
334
335 /* Keep track of node colors during graph visit */
336 var color = Map.from[Resource, NodeColor](
337 graph.nodes.toOuter.map(k => (k, Unvisited))
338 )
339
340 for {
341 component <- graph.componentTraverser().map(_ to Graph)
342 edge <- component
343 .outerEdgeTraverser(component.nodes.head)
344 .withKind(BreadthFirst)
345 } yield {
346 val source = edge._1
347 val target = edge._2
348 color(source) match {
349 case Unvisited | Visited => {
350 color(target) match {
351 case Unvisited =>
352 color(source) = Visited;
353 color(target) = Visited
354 case Visited =>
355 color(source) = ToDelete
356 case ToDelete =>
357 color(source) = Visited
358 }
359 }
360 case ToDelete =>
361 }
362 }
363
364 val toDelete = color.iterator.collect { case (resource: IRI, ToDelete) =>
365 nodemap(resource.getIRI)
366 }.toSeq
367 ontology.removeAxioms(toDelete: _*)
368 }
369 // val edges1 = Seq('A ~> 'B, 'B ~> 'C, 'C ~> 'D, 'D ~> 'H, 'H ~>
370 // 'G, 'G ~> 'F, 'E ~> 'A, 'E ~> 'F, 'B ~> 'E, 'F ~> 'G, 'B ~> 'F,
371 // 'C ~> 'G, 'D ~> 'C, 'H ~> 'D)
372 // val edges2 = Seq('I ~> 'M, 'I ~> 'L, 'L ~> 'N, 'M ~> 'N)
373 // val edges3 = Seq('P ~> 'O)
374 // val graph = Graph.from(edges = edges1 ++ edges2 ++ edges3)
375
376 /** RSA check
377 *
378 * Acyclicity check over *undirected* dependency graph.
379 * NOTE: at the moment we are using the direct version of the graph.
380 *
381 * @deprecated
382 */
383 lazy val isRSA: Boolean =
384 Logger.timed(dependencyGraph.isAcyclic, "RSA check", Logger.DEBUG)
385
334 /** Top axiomatization rules 386 /** Top axiomatization rules
335 * 387 *
336 * For each concept/role *in the ontology file* introduce a rule to 388 * For each concept/role *in the ontology file* introduce a rule to