aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFederico Igne <federico.igne@cs.ox.ac.uk>2021-04-07 17:51:38 +0100
committerFederico Igne <federico.igne@cs.ox.ac.uk>2021-04-07 17:51:38 +0100
commit6f5c82982248e823f2dd6f9eaf87f552d1616ca4 (patch)
treee453115058e9b11c248607136107d8052649884b
parentb622e151905d9a3b8fff14748cc840696dc85f16 (diff)
downloadRSAComb-6f5c82982248e823f2dd6f9eaf87f552d1616ca4.tar.gz
RSAComb-6f5c82982248e823f2dd6f9eaf87f552d1616ca4.zip
Add approximation to RSA
Note that while the code is complete for approximating an input OWL2 ontology to RSA, the final steps are not "connected" yet, and the approximation won't fire automatically. We still need to find a way to include the approximation in the workflow in an efficient and transparent way.
-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