diff options
Diffstat (limited to 'src/main/scala/rsacomb/RSAOntology.scala')
-rw-r--r-- | src/main/scala/rsacomb/RSAOntology.scala | 951 |
1 files changed, 308 insertions, 643 deletions
diff --git a/src/main/scala/rsacomb/RSAOntology.scala b/src/main/scala/rsacomb/RSAOntology.scala index dd65116..52bff37 100644 --- a/src/main/scala/rsacomb/RSAOntology.scala +++ b/src/main/scala/rsacomb/RSAOntology.scala | |||
@@ -3,6 +3,8 @@ package rsacomb | |||
3 | /* Java imports */ | 3 | /* Java imports */ |
4 | import java.util.HashMap | 4 | import java.util.HashMap |
5 | import java.util.stream.{Collectors, Stream} | 5 | import java.util.stream.{Collectors, Stream} |
6 | import java.io.File | ||
7 | import org.semanticweb.owlapi.apibinding.OWLManager | ||
6 | 8 | ||
7 | import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom} | 9 | import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom} |
8 | import org.semanticweb.owlapi.model.{ | 10 | import org.semanticweb.owlapi.model.{ |
@@ -46,663 +48,326 @@ import org.semanticweb.owlapi.model.OWLObjectInverseOf | |||
46 | 48 | ||
47 | import suffix.{Empty, Forward, Backward, Inverse} | 49 | import suffix.{Empty, Forward, Backward, Inverse} |
48 | 50 | ||
49 | object RSAOntology {} | 51 | object RSAOntology { |
50 | /* Wrapper trait for the implicit class `RSAOntology`. | ||
51 | */ | ||
52 | trait RSAOntology { | ||
53 | 52 | ||
54 | /* Implements additional features to reason about RSA ontologies | 53 | def apply(ontology: OWLOntology): RSAOntology = new RSAOntology(ontology) |
55 | * on top of `OWLOntology` from the OWLAPI. | ||
56 | */ | ||
57 | implicit class RSAOntology(ontology: OWLOntology) extends RSAAxiom { | ||
58 | |||
59 | // Gather TBox/RBox/ABox from original ontology | ||
60 | lazy val tbox: List[OWLAxiom] = | ||
61 | ontology | ||
62 | .tboxAxioms(Imports.INCLUDED) | ||
63 | .collect(Collectors.toList()) | ||
64 | .asScala | ||
65 | .toList | ||
66 | |||
67 | lazy val rbox: List[OWLAxiom] = | ||
68 | ontology | ||
69 | .rboxAxioms(Imports.INCLUDED) | ||
70 | .collect(Collectors.toList()) | ||
71 | .asScala | ||
72 | .toList | ||
73 | |||
74 | lazy val abox: List[OWLAxiom] = | ||
75 | ontology | ||
76 | .aboxAxioms(Imports.INCLUDED) | ||
77 | .collect(Collectors.toList()) | ||
78 | .asScala | ||
79 | .toList | ||
80 | |||
81 | lazy val axioms: List[OWLAxiom] = abox ++ tbox ++ rbox | ||
82 | |||
83 | /* Retrieve individuals in the original ontology | ||
84 | */ | ||
85 | lazy val individuals: List[IRI] = | ||
86 | ontology | ||
87 | .getIndividualsInSignature() | ||
88 | .asScala | ||
89 | .map(_.getIRI) | ||
90 | .map(RDFoxUtil.owlapi2rdfox) | ||
91 | .toList | ||
92 | |||
93 | lazy val concepts: List[OWLClass] = | ||
94 | ontology.getClassesInSignature().asScala.toList | ||
95 | |||
96 | lazy val roles: List[OWLObjectPropertyExpression] = | ||
97 | axioms | ||
98 | .flatMap(_.objectPropertyExpressionsInSignature) | ||
99 | .distinct | ||
100 | |||
101 | // OWLAPI reasoner for same easier tasks | ||
102 | private val reasoner = | ||
103 | (new StructuralReasonerFactory()).createReasoner(ontology) | ||
104 | |||
105 | /* Steps for RSA check | ||
106 | * 1) convert ontology axioms into LP rules | ||
107 | * 2) call RDFox on the onto and compute materialization | ||
108 | * 3) build graph from E(x,y) facts | ||
109 | * 4) check if the graph is tree-like | ||
110 | * ideally this annotates the graph with info about the reasons | ||
111 | * why the ontology might not be RSA. This could help a second | ||
112 | * step of approximation of an Horn-ALCHOIQ to RSA | ||
113 | */ | ||
114 | lazy val isRSA: Boolean = { | ||
115 | |||
116 | val unsafe = this.unsafeRoles | ||
117 | |||
118 | /* DEBUG: print rules in DL syntax and unsafe roles */ | ||
119 | //val renderer = new DLSyntaxObjectRenderer() | ||
120 | //println("\nDL rules:") | ||
121 | //axioms.foreach(x => println(renderer.render(x))) | ||
122 | //println("\nUnsafe roles:") | ||
123 | //println(unsafe) | ||
124 | |||
125 | /* Ontology convertion into LP rules */ | ||
126 | val datalog = for { | ||
127 | axiom <- axioms | ||
128 | visitor = new RDFoxAxiomConverter( | ||
129 | RSA.getFreshVariable(), | ||
130 | unsafe, | ||
131 | SkolemStrategy.ConstantRSA(axiom.toString), | ||
132 | Empty | ||
133 | ) | ||
134 | rule <- axiom.accept(visitor) | ||
135 | } yield rule | ||
136 | |||
137 | /* DEBUG: print datalog rules */ | ||
138 | //println("\nDatalog roles:") | ||
139 | //datalog.foreach(println) | ||
140 | |||
141 | // Open connection with RDFox | ||
142 | val (server, data) = RDFoxUtil.openConnection("RSACheck") | ||
143 | // Add Data (hardcoded for now) | ||
144 | data.importData(UpdateType.ADDITION, RSA.Prefixes, ":a a :A .") | ||
145 | |||
146 | /* Add built-in rules | ||
147 | */ | ||
148 | data.importData( | ||
149 | UpdateType.ADDITION, | ||
150 | RSA.Prefixes, | ||
151 | "<http://127.0.0.1/E>[?X,?Y] :- <http://127.0.0.1/PE>[?X,?Y], <http://127.0.0.1/U>[?X], <http://127.0.0.1/U>[?Y] ." | ||
152 | ) | ||
153 | 54 | ||
154 | /* Add built-in rules | 55 | def apply(ontology: File): RSAOntology = |
155 | */ | 56 | new RSAOntology(loadOntology(ontology)) |
156 | // data.importData( | ||
157 | // UpdateType.ADDITION, | ||
158 | // RSA.Prefixes, | ||
159 | // "[?entity, a, ?superClass] :- [?entity, a, ?class], [?class, rdfs:subClassOf, ?superClass] ." | ||
160 | // ) | ||
161 | |||
162 | /* Add ontology rules | ||
163 | */ | ||
164 | data.addRules(datalog.asJava) | ||
165 | |||
166 | /* Build graph | ||
167 | */ | ||
168 | val graph = this.rsaGraph(data); | ||
169 | //println(graph) | ||
170 | |||
171 | // Close connection to RDFox | ||
172 | RDFoxUtil.closeConnection(server, data) | ||
173 | |||
174 | /* To check if the graph is tree-like we check for acyclicity in a | ||
175 | * undirected graph. | ||
176 | * | ||
177 | * TODO: Implement additional checks (taking into account equality) | ||
178 | */ | ||
179 | graph.isAcyclic | ||
180 | } | ||
181 | 57 | ||
182 | lazy val unsafeRoles: List[OWLObjectPropertyExpression] = { | 58 | private def loadOntology(onto: File): OWLOntology = { |
183 | 59 | val manager = OWLManager.createOWLOntologyManager() | |
184 | /* DEBUG: print rules in DL syntax */ | 60 | manager.loadOntologyFromOntologyDocument(onto) |
185 | //val renderer = new DLSyntaxObjectRenderer() | 61 | } |
186 | 62 | } | |
187 | /* Checking for (1) unsafety condition: | ||
188 | * | ||
189 | * For all roles r1 appearing in an axiom of type T5, r1 is unsafe | ||
190 | * if there exists a role r2 (different from top) appearing in an axiom | ||
191 | * of type T3 and r1 is a subproperty of the inverse of r2. | ||
192 | */ | ||
193 | val unsafe1 = for { | ||
194 | axiom <- tbox | ||
195 | if axiom.isT5 | ||
196 | role1 <- axiom.objectPropertyExpressionsInSignature | ||
197 | roleSuper = | ||
198 | role1 +: reasoner | ||
199 | .superObjectProperties(role1) | ||
200 | .collect(Collectors.toList()) | ||
201 | .asScala | ||
202 | roleSuperInv = roleSuper.map(_.getInverseProperty) | ||
203 | axiom <- tbox | ||
204 | if axiom.isT3 && !axiom.isT3top | ||
205 | role2 <- axiom.objectPropertyExpressionsInSignature | ||
206 | if roleSuperInv.contains(role2) | ||
207 | } yield role1 | ||
208 | |||
209 | /* Checking for (2) unsafety condition: | ||
210 | * | ||
211 | * For all roles p1 appearing in an axiom of type T5, p1 is unsafe if | ||
212 | * there exists a role p2 appearing in an axiom of type T4 and p1 is a | ||
213 | * subproperty of either p2 or the inverse of p2. | ||
214 | * | ||
215 | */ | ||
216 | val unsafe2 = for { | ||
217 | axiom <- tbox | ||
218 | if axiom.isT5 | ||
219 | role1 <- axiom.objectPropertyExpressionsInSignature | ||
220 | roleSuper = | ||
221 | role1 +: reasoner | ||
222 | .superObjectProperties(role1) | ||
223 | .collect(Collectors.toList()) | ||
224 | .asScala | ||
225 | roleSuperInv = roleSuper.map(_.getInverseProperty) | ||
226 | axiom <- tbox | ||
227 | if axiom.isT4 | ||
228 | role2 <- axiom.objectPropertyExpressionsInSignature | ||
229 | if roleSuper.contains(role2) || roleSuperInv.contains(role2) | ||
230 | } yield role1 | ||
231 | |||
232 | (unsafe1 ++ unsafe2).toList | ||
233 | } | ||
234 | 63 | ||
235 | private def rsaGraph( | 64 | class RSAOntology(val ontology: OWLOntology) extends RSAAxiom { |
236 | data: DataStoreConnection | 65 | |
237 | ): Graph[Resource, UnDiEdge] = { | 66 | // Gather TBox/RBox/ABox from original ontology |
238 | val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }" | 67 | val tbox: List[OWLAxiom] = |
239 | val cursor = | 68 | ontology |
240 | data.createCursor(RSA.Prefixes, query, new HashMap[String, String]()); | 69 | .tboxAxioms(Imports.INCLUDED) |
241 | var mul = cursor.open() | 70 | .collect(Collectors.toList()) |
242 | var edges: List[UnDiEdge[Resource]] = List() | 71 | .asScala |
243 | while (mul > 0) { | 72 | .toList |
244 | edges = UnDiEdge(cursor.getResource(0), cursor.getResource(1)) :: edges | 73 | |
245 | mul = cursor.advance() | 74 | val rbox: List[OWLAxiom] = |
246 | } | 75 | ontology |
247 | Graph(edges: _*) | 76 | .rboxAxioms(Imports.INCLUDED) |
248 | } | 77 | .collect(Collectors.toList()) |
78 | .asScala | ||
79 | .toList | ||
80 | |||
81 | val abox: List[OWLAxiom] = | ||
82 | ontology | ||
83 | .aboxAxioms(Imports.INCLUDED) | ||
84 | .collect(Collectors.toList()) | ||
85 | .asScala | ||
86 | .toList | ||
87 | |||
88 | val axioms: List[OWLAxiom] = abox ::: tbox ::: rbox | ||
89 | |||
90 | /* Retrieve individuals in the original ontology | ||
91 | */ | ||
92 | val individuals: List[IRI] = | ||
93 | ontology | ||
94 | .getIndividualsInSignature() | ||
95 | .asScala | ||
96 | .map(_.getIRI) | ||
97 | .map(RDFoxUtil.owlapi2rdfox) | ||
98 | .toList | ||
99 | |||
100 | val concepts: List[OWLClass] = | ||
101 | ontology.getClassesInSignature().asScala.toList | ||
102 | |||
103 | val roles: List[OWLObjectPropertyExpression] = | ||
104 | axioms | ||
105 | .flatMap(_.objectPropertyExpressionsInSignature) | ||
106 | .distinct | ||
107 | |||
108 | // OWLAPI reasoner for same easier tasks | ||
109 | private val reasoner = | ||
110 | (new StructuralReasonerFactory()).createReasoner(ontology) | ||
111 | |||
112 | /* Steps for RSA check | ||
113 | * 1) convert ontology axioms into LP rules | ||
114 | * 2) call RDFox on the onto and compute materialization | ||
115 | * 3) build graph from E(x,y) facts | ||
116 | * 4) check if the graph is tree-like | ||
117 | * ideally this annotates the graph with info about the reasons | ||
118 | * why the ontology might not be RSA. This could help a second | ||
119 | * step of approximation of an Horn-ALCHOIQ to RSA | ||
120 | */ | ||
121 | lazy val isRSA: Boolean = { | ||
122 | |||
123 | val unsafe = this.unsafeRoles | ||
124 | |||
125 | /* DEBUG: print rules in DL syntax and unsafe roles */ | ||
126 | //val renderer = new DLSyntaxObjectRenderer() | ||
127 | //println("\nDL rules:") | ||
128 | //axioms.foreach(x => println(renderer.render(x))) | ||
129 | //println("\nUnsafe roles:") | ||
130 | //println(unsafe) | ||
131 | |||
132 | /* Ontology convertion into LP rules */ | ||
133 | val datalog = for { | ||
134 | axiom <- axioms | ||
135 | visitor = new RDFoxAxiomConverter( | ||
136 | RSA.getFreshVariable(), | ||
137 | unsafe, | ||
138 | SkolemStrategy.ConstantRSA(axiom.toString), | ||
139 | Empty | ||
140 | ) | ||
141 | rule <- axiom.accept(visitor) | ||
142 | } yield rule | ||
249 | 143 | ||
250 | def filteringProgram( | 144 | /* DEBUG: print datalog rules */ |
251 | query: SelectQuery, | 145 | //println("\nDatalog roles:") |
252 | nis: List[Term] | 146 | //datalog.foreach(println) |
253 | ): FilteringProgram = | ||
254 | new FilteringProgram(query, nis) | ||
255 | |||
256 | // TODO: the following functions needs testing | ||
257 | def confl( | ||
258 | role: OWLObjectPropertyExpression | ||
259 | ): Set[OWLObjectPropertyExpression] = { | ||
260 | |||
261 | val invSuperRoles = reasoner | ||
262 | .superObjectProperties(role) | ||
263 | .collect(Collectors.toSet()) | ||
264 | .asScala | ||
265 | .addOne(role) | ||
266 | .map(_.getInverseProperty) | ||
267 | |||
268 | invSuperRoles | ||
269 | .flatMap(x => | ||
270 | reasoner | ||
271 | .subObjectProperties(x) | ||
272 | .collect(Collectors.toSet()) | ||
273 | .asScala | ||
274 | .addOne(x) | ||
275 | ) | ||
276 | .filterNot(_.isOWLBottomObjectProperty()) | ||
277 | .filterNot(_.getInverseProperty.isOWLTopObjectProperty()) | ||
278 | } | ||
279 | 147 | ||
280 | def self(axiom: OWLSubClassOfAxiom): Set[Term] = { | 148 | // Open connection with RDFox |
281 | // Assuming just one role in the signature of a T5 axiom | 149 | val (server, data) = RDFoxUtil.openConnection("RSACheck") |
282 | val role = axiom.objectPropertyExpressionsInSignature(0) | 150 | // Add Data (hardcoded for now) |
283 | if (this.confl(role).contains(role)) { | 151 | data.importData(UpdateType.ADDITION, RSA.Prefixes, ":a a :A .") |
284 | Set( | ||
285 | RSA.rsa("v0_" ++ RSA.hashed(axiom)), | ||
286 | RSA.rsa("v1_" ++ RSA.hashed(axiom)) | ||
287 | ) | ||
288 | } else { | ||
289 | Set() | ||
290 | } | ||
291 | } | ||
292 | 152 | ||
293 | // def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = { | 153 | /* Add built-in rules |
294 | // // Assuming just one role in the signature of a T5 axiom | 154 | */ |
295 | // val roleR = axiom.objectPropertyExpressionsInSignature(0) | 155 | data.importData( |
296 | // val conflR = this.confl(roleR) | 156 | UpdateType.ADDITION, |
297 | // // We just need the TBox to find | 157 | RSA.Prefixes, |
298 | // val tbox = ontology | 158 | "<http://127.0.0.1/E>[?X,?Y] :- <http://127.0.0.1/PE>[?X,?Y], <http://127.0.0.1/U>[?X], <http://127.0.0.1/U>[?Y] ." |
299 | // .tboxAxioms(Imports.INCLUDED) | 159 | ) |
300 | // .collect(Collectors.toSet()) | ||
301 | // .asScala | ||
302 | // for { | ||
303 | // axiom1 <- tbox | ||
304 | // // TODO: is this an optimization or an error? | ||
305 | // if axiom1.isT5 | ||
306 | // // We expect only one role coming out of a T5 axiom | ||
307 | // roleS <- axiom1.objectPropertyExpressionsInSignature | ||
308 | // // Triples ordering is among triples involving safe roles. | ||
309 | // if !unsafeRoles.contains(roleS) | ||
310 | // if conflR.contains(roleS) | ||
311 | // individual = | ||
312 | // if (axiom.hashCode < axiom1.hashCode) { | ||
313 | // RSA.rsa("v0_" ++ axiom1.hashCode.toString()) | ||
314 | // } else { | ||
315 | // RSA.rsa("v1_" ++ axiom1.hashCode.toString()) | ||
316 | // } | ||
317 | // } yield individual | ||
318 | // } | ||
319 | |||
320 | def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = { | ||
321 | // TODO: we can actually use `toTriple` from `RSAAxiom` | ||
322 | val classes = | ||
323 | axiom.classesInSignature.collect(Collectors.toList()).asScala | ||
324 | val classA = classes(0) | ||
325 | val roleR = axiom | ||
326 | .objectPropertyExpressionsInSignature(0) | ||
327 | .asInstanceOf[OWLObjectProperty] | ||
328 | val classB = classes(1) | ||
329 | cycle_aux(classA, roleR, classB) | ||
330 | } | ||
331 | 160 | ||
332 | def cycle_aux( | 161 | /* Add built-in rules |
333 | classA: OWLClass, | 162 | */ |
334 | roleR: OWLObjectProperty, | 163 | // data.importData( |
335 | classB: OWLClass | 164 | // UpdateType.ADDITION, |
336 | ): Set[Term] = { | 165 | // RSA.Prefixes, |
337 | val conflR = this.confl(roleR) | 166 | // "[?entity, a, ?superClass] :- [?entity, a, ?class], [?class, rdfs:subClassOf, ?superClass] ." |
338 | val classes = ontology | 167 | // ) |
339 | .classesInSignature(Imports.INCLUDED) | ||
340 | .collect(Collectors.toSet()) | ||
341 | .asScala | ||
342 | for { | ||
343 | classD <- classes | ||
344 | roleS <- conflR | ||
345 | classC <- classes | ||
346 | // Keeping this check for now | ||
347 | if !unsafeRoles.contains(roleS) | ||
348 | tripleARB = RSA.hashed(classA, roleR, classB) | ||
349 | tripleDSC = RSA.hashed(classD, roleS, classC) | ||
350 | individual = | ||
351 | if (tripleARB > tripleDSC) { | ||
352 | RSA.rsa("v1_" ++ tripleDSC) | ||
353 | } else { | ||
354 | // Note that this is also the case for | ||
355 | // `tripleARB == tripleDSC` | ||
356 | RSA.rsa("v0_" ++ tripleDSC) | ||
357 | } | ||
358 | } yield individual | ||
359 | } | ||
360 | 168 | ||
361 | def unfold(axiom: OWLSubClassOfAxiom): Set[Term] = | 169 | /* Add ontology rules |
362 | this.self(axiom) | this.cycle(axiom) | 170 | */ |
363 | 171 | data.addRules(datalog.asJava) | |
364 | object canonicalModel { | ||
365 | |||
366 | import RDFoxUtil._ | ||
367 | |||
368 | val named: List[Rule] = | ||
369 | individuals.map(a => | ||
370 | Rule.create( | ||
371 | TupleTableAtom.rdf(a, IRI.RDF_TYPE, RSA.rsa("NAMED")) | ||
372 | ) | ||
373 | ) | ||
374 | |||
375 | val rolesAdditionalRules: List[Rule] = { | ||
376 | // Given a role (predicate) compute additional logic rules | ||
377 | def additional(pred: String): Seq[Rule] = { | ||
378 | val varX = Variable.create("X") | ||
379 | val varY = Variable.create("Y") | ||
380 | List( | ||
381 | Rule.create( | ||
382 | TupleTableAtom.rdf(varX, IRI.create(pred), varY), | ||
383 | TupleTableAtom | ||
384 | .rdf( | ||
385 | varX, | ||
386 | IRI.create(pred :: Forward), | ||
387 | varY | ||
388 | ) | ||
389 | ), | ||
390 | Rule.create( | ||
391 | TupleTableAtom.rdf(varX, IRI.create(pred), varY), | ||
392 | TupleTableAtom | ||
393 | .rdf( | ||
394 | varX, | ||
395 | IRI.create(pred :: Backward), | ||
396 | varY | ||
397 | ) | ||
398 | ), | ||
399 | Rule.create( | ||
400 | TupleTableAtom.rdf(varX, IRI.create(pred ++ "_inv"), varY), | ||
401 | TupleTableAtom | ||
402 | .rdf( | ||
403 | varX, | ||
404 | IRI.create(pred :: Forward + Inverse), | ||
405 | varY | ||
406 | ) | ||
407 | ), | ||
408 | Rule.create( | ||
409 | TupleTableAtom.rdf(varX, IRI.create(pred ++ "_inv"), varY), | ||
410 | TupleTableAtom | ||
411 | .rdf( | ||
412 | varX, | ||
413 | IRI.create(pred :: Backward + Inverse), | ||
414 | varY | ||
415 | ) | ||
416 | ), | ||
417 | Rule.create( | ||
418 | TupleTableAtom.rdf( | ||
419 | varY, | ||
420 | IRI.create(pred :: Backward + Inverse), | ||
421 | varX | ||
422 | ), | ||
423 | TupleTableAtom | ||
424 | .rdf( | ||
425 | varX, | ||
426 | IRI.create(pred :: Forward), | ||
427 | varY | ||
428 | ) | ||
429 | ), | ||
430 | Rule.create( | ||
431 | TupleTableAtom.rdf( | ||
432 | varY, | ||
433 | IRI.create(pred :: Forward + Inverse), | ||
434 | varX | ||
435 | ), | ||
436 | TupleTableAtom.rdf( | ||
437 | varX, | ||
438 | IRI.create(pred :: Backward), | ||
439 | varY | ||
440 | ) | ||
441 | ), | ||
442 | Rule.create( | ||
443 | TupleTableAtom.rdf( | ||
444 | varY, | ||
445 | IRI.create(pred :: Backward), | ||
446 | varX | ||
447 | ), | ||
448 | TupleTableAtom | ||
449 | .rdf( | ||
450 | varX, | ||
451 | IRI.create(pred :: Forward + Inverse), | ||
452 | varY | ||
453 | ) | ||
454 | ), | ||
455 | Rule.create( | ||
456 | TupleTableAtom.rdf( | ||
457 | varY, | ||
458 | IRI.create(pred :: Forward), | ||
459 | varX | ||
460 | ), | ||
461 | TupleTableAtom.rdf( | ||
462 | varX, | ||
463 | IRI.create(pred :: Backward + Inverse), | ||
464 | varY | ||
465 | ) | ||
466 | ) | ||
467 | ) | ||
468 | } | ||
469 | // Compute additional rules per role | ||
470 | axioms | ||
471 | .flatMap( | ||
472 | _.objectPropertiesInSignature.collect(Collectors.toSet()).asScala | ||
473 | ) | ||
474 | .distinct | ||
475 | .map(_.getIRI.getIRIString) | ||
476 | .flatMap(additional) | ||
477 | } | ||
478 | |||
479 | private lazy val topAxioms: List[Rule] = | ||
480 | concepts.map(c => { | ||
481 | val x = Variable.create("X") | ||
482 | Rule.create( | ||
483 | TupleTableAtom.rdf(x, IRI.RDF_TYPE, IRI.THING), | ||
484 | TupleTableAtom.rdf(x, IRI.RDF_TYPE, c.getIRI) | ||
485 | ) | ||
486 | }) ++ | ||
487 | roles.map(r => { | ||
488 | val x = Variable.create("X") | ||
489 | val y = Variable.create("Y") | ||
490 | val iri: IRI = r match { | ||
491 | case x: OWLObjectProperty => x.getIRI | ||
492 | case x: OWLObjectInverseOf => | ||
493 | IRI.create(x.getNamedProperty.getIRI.getIRIString ++ "_inv") | ||
494 | case x => x.getNamedProperty.getIRI | ||
495 | } | ||
496 | Rule.create( | ||
497 | List( | ||
498 | TupleTableAtom.rdf(x, IRI.RDF_TYPE, IRI.THING), | ||
499 | TupleTableAtom.rdf(y, IRI.RDF_TYPE, IRI.THING) | ||
500 | ).asJava, | ||
501 | List[BodyFormula](TupleTableAtom.rdf(x, iri, y)).asJava | ||
502 | ) | ||
503 | }) | ||
504 | |||
505 | private val equalityAxioms: List[Rule] = { | ||
506 | val x = Variable.create("X") | ||
507 | val y = Variable.create("Y") | ||
508 | val z = Variable.create("Z") | ||
509 | List( | ||
510 | Rule.create( | ||
511 | TupleTableAtom.rdf(x, RSA.EquivTo, x), | ||
512 | TupleTableAtom.rdf(x, IRI.RDF_TYPE, IRI.THING) | ||
513 | ), | ||
514 | Rule.create( | ||
515 | TupleTableAtom.rdf(y, RSA.EquivTo, x), | ||
516 | TupleTableAtom.rdf(x, RSA.EquivTo, y) | ||
517 | ), | ||
518 | Rule.create( | ||
519 | TupleTableAtom.rdf(x, RSA.EquivTo, z), | ||
520 | TupleTableAtom.rdf(x, RSA.EquivTo, y), | ||
521 | TupleTableAtom.rdf(y, RSA.EquivTo, z) | ||
522 | ) | ||
523 | ) | ||
524 | } | ||
525 | |||
526 | val rules: List[Rule] = { | ||
527 | // Compute rules from ontology axioms | ||
528 | val rules = axioms.flatMap(_.accept(this.ProgramGenerator)) | ||
529 | // Return full set of rules | ||
530 | rules ++ rolesAdditionalRules ++ topAxioms ++ equalityAxioms ++ named | ||
531 | } | ||
532 | |||
533 | object ProgramGenerator | ||
534 | extends RDFoxAxiomConverter( | ||
535 | Variable.create("X"), | ||
536 | unsafeRoles, | ||
537 | SkolemStrategy.None, | ||
538 | Empty | ||
539 | ) { | ||
540 | |||
541 | private def rules1(axiom: OWLSubClassOfAxiom): List[Rule] = { | ||
542 | val unfold = ontology.unfold(axiom).toList | ||
543 | // Fresh Variables | ||
544 | val v0 = RSA.rsa("v0_" ++ RSA.hashed(axiom)) | ||
545 | val varX = Variable.create("X") | ||
546 | // Predicates | ||
547 | val atomA: TupleTableAtom = { | ||
548 | val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI | ||
549 | TupleTableAtom.rdf(varX, IRI.RDF_TYPE, cls) | ||
550 | } | ||
551 | def in(t: Term): TupleTableAtom = { | ||
552 | TupleTableAtom.rdf( | ||
553 | t, | ||
554 | RSA.rsa("IN"), | ||
555 | RSA.rsa(unfold.hashCode.toString) | ||
556 | ) | ||
557 | } | ||
558 | def notIn(t: Term): Negation = Negation.create(in(t)) | ||
559 | val roleRf: TupleTableAtom = { | ||
560 | val visitor = | ||
561 | new RDFoxPropertyExprConverter(varX, v0, Forward) | ||
562 | axiom.getSuperClass | ||
563 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
564 | .getProperty | ||
565 | .accept(visitor) | ||
566 | .head | ||
567 | } | ||
568 | val atomB: TupleTableAtom = { | ||
569 | val cls = axiom.getSuperClass | ||
570 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
571 | .getFiller | ||
572 | .asInstanceOf[OWLClass] | ||
573 | .getIRI | ||
574 | TupleTableAtom.rdf(v0, IRI.RDF_TYPE, cls) | ||
575 | } | ||
576 | // TODO: To be consistent with the specifics of the visitor we are | ||
577 | // returning facts as `Rule`s with true body. While this is correct | ||
578 | // there is an easier way to import facts into RDFox. Are we able to | ||
579 | // do that? | ||
580 | val facts = unfold.map(x => Rule.create(in(x))) | ||
581 | val rules = List( | ||
582 | Rule.create(roleRf, atomA, notIn(varX)), | ||
583 | Rule.create(atomB, atomA, notIn(varX)) | ||
584 | ) | ||
585 | facts ++ rules | ||
586 | } | ||
587 | 172 | ||
588 | private def rules2(axiom: OWLSubClassOfAxiom): List[Rule] = { | 173 | /* Build graph |
589 | val roleR = | 174 | */ |
590 | axiom.getSuperClass | 175 | val graph = this.rsaGraph(data); |
591 | .asInstanceOf[OWLObjectSomeValuesFrom] | 176 | //println(graph) |
592 | .getProperty | ||
593 | if (ontology.confl(roleR) contains roleR) { | ||
594 | // Fresh Variables | ||
595 | val v0 = RSA.rsa("v0_" ++ RSA.hashed(axiom)) | ||
596 | val v1 = RSA.rsa("v1_" ++ RSA.hashed(axiom)) | ||
597 | val v2 = RSA.rsa("v2_" ++ RSA.hashed(axiom)) | ||
598 | // Predicates | ||
599 | def atomA(t: Term): TupleTableAtom = { | ||
600 | val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI | ||
601 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) | ||
602 | } | ||
603 | def roleRf(t1: Term, t2: Term): TupleTableAtom = { | ||
604 | val visitor = | ||
605 | new RDFoxPropertyExprConverter(t1, t2, Forward) | ||
606 | roleR.accept(visitor).head | ||
607 | } | ||
608 | def atomB(t: Term): TupleTableAtom = { | ||
609 | val cls = axiom.getSuperClass | ||
610 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
611 | .getFiller | ||
612 | .asInstanceOf[OWLClass] | ||
613 | .getIRI | ||
614 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) | ||
615 | } | ||
616 | //Rules | ||
617 | List( | ||
618 | Rule.create(roleRf(v0, v1), atomA(v0)), | ||
619 | Rule.create(atomB(v1), atomA(v0)), | ||
620 | Rule.create(roleRf(v1, v2), atomA(v1)), | ||
621 | Rule.create(atomB(v2), atomA(v1)) | ||
622 | ) | ||
623 | } else { | ||
624 | List() | ||
625 | } | ||
626 | } | ||
627 | 177 | ||
628 | private def rules3(axiom: OWLSubClassOfAxiom): List[Rule] = { | 178 | // Close connection to RDFox |
629 | val cycle = ontology.cycle(axiom).toList | 179 | RDFoxUtil.closeConnection(server, data) |
630 | val roleR = | ||
631 | axiom.getSuperClass | ||
632 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
633 | .getProperty | ||
634 | // Fresh Variables | ||
635 | val v1 = RSA.rsa("v1_" ++ RSA.hashed(axiom)) | ||
636 | // Predicates | ||
637 | def atomA(t: Term): TupleTableAtom = { | ||
638 | val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI | ||
639 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) | ||
640 | } | ||
641 | def roleRf(t: Term): TupleTableAtom = { | ||
642 | val visitor = | ||
643 | new RDFoxPropertyExprConverter(t, v1, Forward) | ||
644 | roleR.accept(visitor).head | ||
645 | } | ||
646 | val atomB: TupleTableAtom = { | ||
647 | val cls = axiom.getSuperClass | ||
648 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
649 | .getFiller | ||
650 | .asInstanceOf[OWLClass] | ||
651 | .getIRI | ||
652 | TupleTableAtom.rdf(v1, IRI.RDF_TYPE, cls) | ||
653 | } | ||
654 | cycle.flatMap { x => | ||
655 | List( | ||
656 | Rule.create(roleRf(x), atomA(x)), | ||
657 | Rule.create(atomB, atomA(x)) | ||
658 | ) | ||
659 | } | ||
660 | } | ||
661 | 180 | ||
662 | override def visit(axiom: OWLSubClassOfAxiom): List[Rule] = { | 181 | /* To check if the graph is tree-like we check for acyclicity in a |
663 | if (axiom.isT5) { | 182 | * undirected graph. |
664 | // TODO: get role in T5 axiom | 183 | * |
665 | // Assuming one role here | 184 | * TODO: Implement additional checks (taking into account equality) |
666 | val role = axiom.objectPropertyExpressionsInSignature(0) | 185 | */ |
667 | if (ontology.unsafeRoles.contains(role)) { | 186 | graph.isAcyclic |
668 | val visitor = | 187 | } |
669 | new RDFoxAxiomConverter( | ||
670 | Variable.create("X"), | ||
671 | ontology.unsafeRoles, | ||
672 | SkolemStrategy.Standard(axiom.toString), | ||
673 | Forward | ||
674 | ) | ||
675 | axiom.accept(visitor) | ||
676 | } else { | ||
677 | rules1(axiom) ++ rules2(axiom) ++ rules3(axiom) | ||
678 | } | ||
679 | } else { | ||
680 | // Fallback to standard OWL to LP translation | ||
681 | super.visit(axiom) | ||
682 | } | ||
683 | } | ||
684 | 188 | ||
685 | override def visit(axiom: OWLSubObjectPropertyOfAxiom): List[Rule] = { | 189 | lazy val unsafeRoles: List[OWLObjectPropertyExpression] = { |
686 | val varX = Variable.create("X") | ||
687 | val varY = Variable.create("Y") | ||
688 | val visitorF = new RDFoxAxiomConverter( | ||
689 | Variable.create("X"), | ||
690 | ontology.unsafeRoles, | ||
691 | SkolemStrategy.None, | ||
692 | Forward | ||
693 | ) | ||
694 | val visitorB = new RDFoxAxiomConverter( | ||
695 | Variable.create("X"), | ||
696 | ontology.unsafeRoles, | ||
697 | SkolemStrategy.None, | ||
698 | Backward | ||
699 | ) | ||
700 | axiom.accept(visitorB) ++ axiom.accept(visitorF) | ||
701 | } | ||
702 | 190 | ||
703 | } | 191 | /* DEBUG: print rules in DL syntax */ |
192 | //val renderer = new DLSyntaxObjectRenderer() | ||
704 | 193 | ||
194 | /* Checking for (1) unsafety condition: | ||
195 | * | ||
196 | * For all roles r1 appearing in an axiom of type T5, r1 is unsafe | ||
197 | * if there exists a role r2 (different from top) appearing in an axiom | ||
198 | * of type T3 and r1 is a subproperty of the inverse of r2. | ||
199 | */ | ||
200 | val unsafe1 = for { | ||
201 | axiom <- tbox | ||
202 | if axiom.isT5 | ||
203 | role1 <- axiom.objectPropertyExpressionsInSignature | ||
204 | roleSuper = | ||
205 | role1 +: reasoner | ||
206 | .superObjectProperties(role1) | ||
207 | .collect(Collectors.toList()) | ||
208 | .asScala | ||
209 | roleSuperInv = roleSuper.map(_.getInverseProperty) | ||
210 | axiom <- tbox | ||
211 | if axiom.isT3 && !axiom.isT3top | ||
212 | role2 <- axiom.objectPropertyExpressionsInSignature | ||
213 | if roleSuperInv.contains(role2) | ||
214 | } yield role1 | ||
215 | |||
216 | /* Checking for (2) unsafety condition: | ||
217 | * | ||
218 | * For all roles p1 appearing in an axiom of type T5, p1 is unsafe if | ||
219 | * there exists a role p2 appearing in an axiom of type T4 and p1 is a | ||
220 | * subproperty of either p2 or the inverse of p2. | ||
221 | * | ||
222 | */ | ||
223 | val unsafe2 = for { | ||
224 | axiom <- tbox | ||
225 | if axiom.isT5 | ||
226 | role1 <- axiom.objectPropertyExpressionsInSignature | ||
227 | roleSuper = | ||
228 | role1 +: reasoner | ||
229 | .superObjectProperties(role1) | ||
230 | .collect(Collectors.toList()) | ||
231 | .asScala | ||
232 | roleSuperInv = roleSuper.map(_.getInverseProperty) | ||
233 | axiom <- tbox | ||
234 | if axiom.isT4 | ||
235 | role2 <- axiom.objectPropertyExpressionsInSignature | ||
236 | if roleSuper.contains(role2) || roleSuperInv.contains(role2) | ||
237 | } yield role1 | ||
238 | |||
239 | (unsafe1 ++ unsafe2).toList | ||
240 | } | ||
241 | |||
242 | private def rsaGraph( | ||
243 | data: DataStoreConnection | ||
244 | ): Graph[Resource, UnDiEdge] = { | ||
245 | val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }" | ||
246 | val cursor = | ||
247 | data.createCursor(RSA.Prefixes, query, new HashMap[String, String]()); | ||
248 | var mul = cursor.open() | ||
249 | var edges: List[UnDiEdge[Resource]] = List() | ||
250 | while (mul > 0) { | ||
251 | edges = UnDiEdge(cursor.getResource(0), cursor.getResource(1)) :: edges | ||
252 | mul = cursor.advance() | ||
253 | } | ||
254 | Graph(edges: _*) | ||
255 | } | ||
256 | |||
257 | def filteringProgram( | ||
258 | query: SelectQuery, | ||
259 | nis: List[Term] | ||
260 | ): FilteringProgram = | ||
261 | new FilteringProgram(query, nis) | ||
262 | |||
263 | lazy val canonicalModel = new CanonicalModel(this) | ||
264 | |||
265 | // TODO: the following functions needs testing | ||
266 | def confl( | ||
267 | role: OWLObjectPropertyExpression | ||
268 | ): Set[OWLObjectPropertyExpression] = { | ||
269 | |||
270 | val invSuperRoles = reasoner | ||
271 | .superObjectProperties(role) | ||
272 | .collect(Collectors.toSet()) | ||
273 | .asScala | ||
274 | .addOne(role) | ||
275 | .map(_.getInverseProperty) | ||
276 | |||
277 | invSuperRoles | ||
278 | .flatMap(x => | ||
279 | reasoner | ||
280 | .subObjectProperties(x) | ||
281 | .collect(Collectors.toSet()) | ||
282 | .asScala | ||
283 | .addOne(x) | ||
284 | ) | ||
285 | .filterNot(_.isOWLBottomObjectProperty()) | ||
286 | .filterNot(_.getInverseProperty.isOWLTopObjectProperty()) | ||
287 | } | ||
288 | |||
289 | def self(axiom: OWLSubClassOfAxiom): Set[Term] = { | ||
290 | // Assuming just one role in the signature of a T5 axiom | ||
291 | val role = axiom.objectPropertyExpressionsInSignature(0) | ||
292 | if (this.confl(role).contains(role)) { | ||
293 | Set( | ||
294 | RSA.rsa("v0_" ++ RSA.hashed(axiom)), | ||
295 | RSA.rsa("v1_" ++ RSA.hashed(axiom)) | ||
296 | ) | ||
297 | } else { | ||
298 | Set() | ||
705 | } | 299 | } |
706 | } // implicit class RSAOntology | 300 | } |
301 | |||
302 | // def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = { | ||
303 | // // Assuming just one role in the signature of a T5 axiom | ||
304 | // val roleR = axiom.objectPropertyExpressionsInSignature(0) | ||
305 | // val conflR = this.confl(roleR) | ||
306 | // // We just need the TBox to find | ||
307 | // val tbox = ontology | ||
308 | // .tboxAxioms(Imports.INCLUDED) | ||
309 | // .collect(Collectors.toSet()) | ||
310 | // .asScala | ||
311 | // for { | ||
312 | // axiom1 <- tbox | ||
313 | // // TODO: is this an optimization or an error? | ||
314 | // if axiom1.isT5 | ||
315 | // // We expect only one role coming out of a T5 axiom | ||
316 | // roleS <- axiom1.objectPropertyExpressionsInSignature | ||
317 | // // Triples ordering is among triples involving safe roles. | ||
318 | // if !unsafeRoles.contains(roleS) | ||
319 | // if conflR.contains(roleS) | ||
320 | // individual = | ||
321 | // if (axiom.hashCode < axiom1.hashCode) { | ||
322 | // RSA.rsa("v0_" ++ axiom1.hashCode.toString()) | ||
323 | // } else { | ||
324 | // RSA.rsa("v1_" ++ axiom1.hashCode.toString()) | ||
325 | // } | ||
326 | // } yield individual | ||
327 | // } | ||
328 | |||
329 | def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = { | ||
330 | // TODO: we can actually use `toTriple` from `RSAAxiom` | ||
331 | val classes = | ||
332 | axiom.classesInSignature.collect(Collectors.toList()).asScala | ||
333 | val classA = classes(0) | ||
334 | val roleR = axiom | ||
335 | .objectPropertyExpressionsInSignature(0) | ||
336 | .asInstanceOf[OWLObjectProperty] | ||
337 | val classB = classes(1) | ||
338 | cycle_aux(classA, roleR, classB) | ||
339 | } | ||
340 | |||
341 | def cycle_aux( | ||
342 | classA: OWLClass, | ||
343 | roleR: OWLObjectProperty, | ||
344 | classB: OWLClass | ||
345 | ): Set[Term] = { | ||
346 | val conflR = this.confl(roleR) | ||
347 | val classes = ontology | ||
348 | .classesInSignature(Imports.INCLUDED) | ||
349 | .collect(Collectors.toSet()) | ||
350 | .asScala | ||
351 | for { | ||
352 | classD <- classes | ||
353 | roleS <- conflR | ||
354 | classC <- classes | ||
355 | // Keeping this check for now | ||
356 | if !unsafeRoles.contains(roleS) | ||
357 | tripleARB = RSA.hashed(classA, roleR, classB) | ||
358 | tripleDSC = RSA.hashed(classD, roleS, classC) | ||
359 | individual = | ||
360 | if (tripleARB > tripleDSC) { | ||
361 | RSA.rsa("v1_" ++ tripleDSC) | ||
362 | } else { | ||
363 | // Note that this is also the case for | ||
364 | // `tripleARB == tripleDSC` | ||
365 | RSA.rsa("v0_" ++ tripleDSC) | ||
366 | } | ||
367 | } yield individual | ||
368 | } | ||
369 | |||
370 | def unfold(axiom: OWLSubClassOfAxiom): Set[Term] = | ||
371 | this.self(axiom) | this.cycle(axiom) | ||
707 | 372 | ||
708 | } // trait RSAOntology | 373 | } // implicit class RSAOntology |