diff options
-rw-r--r-- | src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala | 266 |
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 | } |
22 | import org.semanticweb.owlapi.model.parameters.Imports | 22 | import org.semanticweb.owlapi.model.parameters.Imports |
23 | import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory | 23 | import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory |
24 | import org.semanticweb.owlapi.model.{IRI => OWLIRI} | ||
25 | import uk.ac.manchester.cs.owl.owlapi.OWLObjectPropertyImpl | 24 | import uk.ac.manchester.cs.owl.owlapi.OWLObjectPropertyImpl |
26 | 25 | ||
27 | import tech.oxfordsemantic.jrdfox.client.{ | 26 | import tech.oxfordsemantic.jrdfox.client.{ |
@@ -48,9 +47,10 @@ import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery | |||
48 | /* Scala imports */ | 47 | /* Scala imports */ |
49 | import scala.util.{Try, Success, Failure} | 48 | import scala.util.{Try, Success, Failure} |
50 | import scala.collection.JavaConverters._ | 49 | import scala.collection.JavaConverters._ |
51 | import scala.collection.mutable.Set | 50 | import scala.collection.mutable.{Set, Map} |
52 | import scalax.collection.immutable.Graph | 51 | import scalax.collection.Graph |
53 | import scalax.collection.GraphEdge.UnDiEdge | 52 | import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._ |
53 | import scalax.collection.GraphTraversal._ | ||
54 | 54 | ||
55 | /* Debug only */ | 55 | /* Debug only */ |
56 | import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer | 56 | import 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 |