aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
diff options
context:
space:
mode:
authorFederico Igne <federico.igne@cs.ox.ac.uk>2021-01-30 10:59:38 +0000
committerFederico Igne <federico.igne@cs.ox.ac.uk>2021-01-30 10:59:38 +0000
commit7d6021c6c706c108b5b11d52071acd104c7d4ff8 (patch)
tree573146018eba8bc1cc16bc4b39edcab7c2c53ace /src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
parentd04e2839689c4291afb4beb9a1913bb38fac1cd1 (diff)
downloadRSAComb-7d6021c6c706c108b5b11d52071acd104c7d4ff8.tar.gz
RSAComb-7d6021c6c706c108b5b11d52071acd104c7d4ff8.zip
Delay import of data files (#7)
This should partially solve the issue with data import through OWLAPI being too slow.
Diffstat (limited to 'src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala')
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala297
1 files changed, 199 insertions, 98 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 8d5bf4c..0f1552a 100644
--- a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
@@ -46,6 +46,7 @@ import tech.oxfordsemantic.jrdfox.logic.expression.{
46import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery 46import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery
47 47
48/* Scala imports */ 48/* Scala imports */
49import scala.util.{Try, Success, Failure}
49import scala.collection.JavaConverters._ 50import scala.collection.JavaConverters._
50import scala.collection.mutable.Set 51import scala.collection.mutable.Set
51import scalax.collection.immutable.Graph 52import scalax.collection.immutable.Graph
@@ -70,56 +71,75 @@ object RSAOntology {
70 /** Name of the RDFox data store used for CQ answering */ 71 /** Name of the RDFox data store used for CQ answering */
71 private val DataStore = "answer_computation" 72 private val DataStore = "answer_computation"
72 73
73 def apply(ontology: OWLOntology): RSAOntology = new RSAOntology(ontology) 74 def apply(ontology: File, data: File*): RSAOntology =
74 75 new RSAOntology(ontology, data: _*)
75 def apply(ontologies: File*): RSAOntology =
76 new RSAOntology(loadOntology(ontologies: _*))
77 76
78 def genFreshVariable(): Variable = { 77 def genFreshVariable(): Variable = {
79 counter += 1 78 counter += 1
80 Variable.create(f"I$counter%03d") 79 Variable.create(f"I$counter%03d")
81 } 80 }
82 81
83 private def loadOntology(ontologies: File*): OWLOntology = {
84 val manager = OWLManager.createOWLOntologyManager()
85 ontologies.foreach { manager.loadOntologyFromOntologyDocument(_) }
86 val merger = new OWLOntologyMerger(manager)
87 merger.createMergedOntology(manager, OWLIRI.create("_:merged"))
88 }
89} 82}
90 83
91class RSAOntology(val ontology: OWLOntology) { 84class RSAOntology(_ontology: File, val datafiles: File*) {
92 85
86 /** Simplify conversion between OWLAPI and RDFox concepts */
87 import implicits.RDFox._
93 import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._ 88 import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._
94 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ 89 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._
95 90
96 // Gather TBox/RBox/ABox from original ontology 91 /** Manager instance to interface with OWLAPI */
92 private val manager = OWLManager.createOWLOntologyManager()
93
94 /** TBox + RBox of the input knowledge base. */
95 val ontology: OWLOntology =
96 manager.loadOntologyFromOntologyDocument(_ontology)
97
98 /** OWLAPI internal reasoner some preliminary reasoning task. */
99 private val reasoner =
100 (new StructuralReasonerFactory()).createReasoner(ontology)
101
102 /** Imported knowledge base. */
103 //lazy val kbase: OWLOntology = {
104 // val merger = new OWLOntologyMerger(manager)
105 // _data.foreach { manager.loadOntologyFromOntologyDocument(_) }
106 // merger.createMergedOntology(manager, OWLIRI.create("_:merged"))
107 //}
108
109 /** TBox axioms */
97 val tbox: List[OWLLogicalAxiom] = 110 val tbox: List[OWLLogicalAxiom] =
98 ontology 111 ontology
99 .tboxAxioms(Imports.INCLUDED) 112 .tboxAxioms(Imports.INCLUDED)
100 .collect(Collectors.toList()) 113 .collect(Collectors.toList())
101 .collect { case a: OWLLogicalAxiom => a } 114 .collect { case a: OWLLogicalAxiom => a }
115 Logger.print(s"Original TBox: ${tbox.length}", Logger.DEBUG)
102 116
117 /** RBox axioms */
103 val rbox: List[OWLLogicalAxiom] = 118 val rbox: List[OWLLogicalAxiom] =
104 ontology 119 ontology
105 .rboxAxioms(Imports.INCLUDED) 120 .rboxAxioms(Imports.INCLUDED)
106 .collect(Collectors.toList()) 121 .collect(Collectors.toList())
107 .collect { case a: OWLLogicalAxiom => a } 122 .collect { case a: OWLLogicalAxiom => a }
123 Logger.print(s"Original RBox: ${rbox.length}", Logger.DEBUG)
108 124
125 /** ABox axioms
126 *
127 * @note this represents only the set of assertions contained in the
128 * ontology file. Data files specified in `datafiles` are directly
129 * imported in RDFox due to performance issues when trying to import
130 * large data files via OWLAPI.
131 */
109 val abox: List[OWLLogicalAxiom] = 132 val abox: List[OWLLogicalAxiom] =
110 ontology 133 ontology
111 .aboxAxioms(Imports.INCLUDED) 134 .aboxAxioms(Imports.INCLUDED)
112 .collect(Collectors.toList()) 135 .collect(Collectors.toList())
113 .collect { case a: OWLLogicalAxiom => a } 136 .collect { case a: OWLLogicalAxiom => a }
137 Logger.print(s"Original RBox: ${abox.length}", Logger.DEBUG)
114 138
115 val axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox 139 /** Collection of logical axioms in the input ontology */
116 140 lazy val axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox
117 Logger.print(s"Original TBox: ${tbox.length}", Logger.DEBUG)
118 Logger.print(s"Original RBox: ${rbox.length}", Logger.DEBUG)
119 Logger.print(s"Original ABox: ${abox.length}", Logger.DEBUG)
120 141
121 /* Retrieve individuals in the original ontology 142 /* Retrieve individuals in the original ontology */
122 */
123 val individuals: List[IRI] = 143 val individuals: List[IRI] =
124 ontology 144 ontology
125 .getIndividualsInSignature() 145 .getIndividualsInSignature()
@@ -129,7 +149,7 @@ class RSAOntology(val ontology: OWLOntology) {
129 .toList 149 .toList
130 150
131 val literals: List[Literal] = 151 val literals: List[Literal] =
132 abox 152 axioms
133 .collect { case a: OWLDataPropertyAssertionAxiom => a } 153 .collect { case a: OWLDataPropertyAssertionAxiom => a }
134 .map(_.getObject) 154 .map(_.getObject)
135 .map(implicits.RDFox.owlapiToRdfoxLiteral) 155 .map(implicits.RDFox.owlapiToRdfoxLiteral)
@@ -137,18 +157,13 @@ class RSAOntology(val ontology: OWLOntology) {
137 val concepts: List[OWLClass] = 157 val concepts: List[OWLClass] =
138 ontology.getClassesInSignature().asScala.toList 158 ontology.getClassesInSignature().asScala.toList
139 159
160 // This is needed in the computation of rules in the canonical model.
161 // Can we avoid this using RDFox built-in functions?
140 val roles: List[OWLObjectPropertyExpression] = 162 val roles: List[OWLObjectPropertyExpression] =
141 axioms 163 (tbox ++ rbox)
142 .flatMap(_.objectPropertyExpressionsInSignature) 164 .flatMap(_.objectPropertyExpressionsInSignature)
143 .distinct 165 .distinct
144 166
145 /** OWLAPI reasoner
146 *
147 * Used to carry out some preliminary reasoning task.
148 */
149 private val reasoner =
150 (new StructuralReasonerFactory()).createReasoner(ontology)
151
152 /* Steps for RSA check 167 /* Steps for RSA check
153 * 1) convert ontology axioms into LP rules 168 * 1) convert ontology axioms into LP rules
154 * 2) call RDFox on the onto and compute materialization 169 * 2) call RDFox on the onto and compute materialization
@@ -157,19 +172,16 @@ class RSAOntology(val ontology: OWLOntology) {
157 * ideally this annotates the graph with info about the reasons 172 * ideally this annotates the graph with info about the reasons
158 * why the ontology might not be RSA. This could help a second 173 * why the ontology might not be RSA. This could help a second
159 * step of approximation of an Horn-ALCHOIQ to RSA 174 * step of approximation of an Horn-ALCHOIQ to RSA
175 *
176 * TODO: Implement additional checks (taking into account equality)
177 *
178 * To check if the graph is tree-like we check for acyclicity in a
179 * undirected graph.
160 */ 180 */
161 lazy val isRSA: Boolean = Logger.timed( 181 lazy val isRSA: Boolean = Logger.timed(
162 { 182 {
163 val unsafe = this.unsafeRoles 183 val unsafe = this.unsafeRoles
164 184
165 // val renderer = new DLSyntaxObjectRenderer()
166 // println()
167 // println("Unsafe roles:")
168 // println(unsafe)
169 // println()
170 // println("DL rules:")
171 // tbox.foreach(x => println(renderer.render(x)))
172
173 object RSAConverter extends RDFoxConverter { 185 object RSAConverter extends RDFoxConverter {
174 186
175 override def convert( 187 override def convert(
@@ -195,66 +207,78 @@ class RSAOntology(val ontology: OWLOntology) {
195 207
196 case _ => super.convert(expr, term, unsafe, skolem, suffix) 208 case _ => super.convert(expr, term, unsafe, skolem, suffix)
197 } 209 }
198
199 } 210 }
200 211
201 /* Ontology convertion into LP rules */ 212 /* Ontology convertion into LP rules */
202 val term = RSAOntology.genFreshVariable() 213 val term = RSAOntology.genFreshVariable()
203 val datalog = axioms 214 val conversion = Try(
204 .map(a => RSAConverter.convert(a, term, unsafe, new Constant(a), Empty)) 215 axioms.map(a =>
205 .unzip 216 RSAConverter.convert(a, term, unsafe, new Constant(a), Empty)
206 val facts = datalog._1.flatten 217 )
207 val rules = datalog._2.flatten
208
209 //println("Datalog rules:")
210 //rules foreach println
211
212 // Open connection with RDFox
213 val (server, data) = RDFoxUtil.openConnection("RSACheck")
214
215 /* Add built-in rules
216 * TODO: substitute with RDFoxUtil.addRules
217 */
218 data.importData(
219 UpdateType.ADDITION,
220 RSA.Prefixes,
221 "rsa:E[?X,?Y] :- rsa:PE[?X,?Y], rsa:U[?X], rsa:U[?Y] ."
222 ) 218 )
223 219
224 /* Add ontology facts and rules */ 220 conversion match {
225 RDFoxUtil.addFacts(data, facts) 221 case Success(result) => {
226 RDFoxUtil.addRules(data, rules) 222 val datalog = result.unzip
227 223 val facts = datalog._1.flatten
228 /* Build graph */ 224 var rules = datalog._2.flatten
229 val graph = this.rsaGraph(data); 225
230 //println("Graph:") 226 /* Open connection with RDFox */
231 //println(graph) 227 val (server, data) = RDFoxUtil.openConnection("RSACheck")
232 228
233 // Close connection to RDFox 229 /* Add additional built-in rules */
234 RDFoxUtil.closeConnection(server, data) 230 val varX = Variable.create("X")
235 231 val varY = Variable.create("Y")
236 /* To check if the graph is tree-like we check for acyclicity in a 232 rules = Rule.create(
237 * undirected graph. 233 RSA.E(varX, varY),
238 * 234 RSA.PE(varX, varY),
239 * TODO: Implement additional checks (taking into account equality) 235 RSA.U(varX),
240 */ 236 RSA.U(varY)
241 graph.isAcyclic 237 ) :: rules
238
239 /* Load facts and rules from ontology */
240 RDFoxUtil.addFacts(data, facts)
241 RDFoxUtil.addRules(data, rules)
242 /* Load data files */
243 RDFoxUtil.addData(data, datafiles: _*)
244
245 /* Build graph */
246 val graph = this.rsaGraph(data);
247
248 /* Close connection to RDFox */
249 RDFoxUtil.closeConnection(server, data)
250
251 /* Acyclicity test */
252 graph.isAcyclic
253 }
254 case Failure(e) => {
255 Logger print s"Unsupported axiom: $e"
256 false
257 }
258 }
242 }, 259 },
243 "RSA check", 260 "RSA check",
244 Logger.DEBUG 261 Logger.DEBUG
245 ) 262 )
246 263
264 /** Unsafe roles of a given ontology.
265 *
266 * Unsafety conditions are the following:
267 *
268 * 1) For all roles r1 appearing in an axiom of type T5, r1 is unsafe
269 * if there exists a role r2 (different from top) appearing in an
270 * axiom of type T3 and r1 is a subproperty of the inverse of r2.
271 *
272 * 2) For all roles p1 appearing in an axiom of type T5, p1 is unsafe
273 * if there exists a role p2 appearing in an axiom of type T4 and
274 * p1 is a subproperty of either p2 or the inverse of p2.
275 */
247 lazy val unsafeRoles: List[OWLObjectPropertyExpression] = { 276 lazy val unsafeRoles: List[OWLObjectPropertyExpression] = {
248 277
249 /* DEBUG: print rules in DL syntax */ 278 /* DEBUG: print rules in DL syntax */
250 //val renderer = new DLSyntaxObjectRenderer() 279 //val renderer = new DLSyntaxObjectRenderer()
251 280
252 /* Checking for (1) unsafety condition: 281 /* Checking for unsafety condition (1) */
253 *
254 * For all roles r1 appearing in an axiom of type T5, r1 is unsafe
255 * if there exists a role r2 (different from top) appearing in an axiom
256 * of type T3 and r1 is a subproperty of the inverse of r2.
257 */
258 val unsafe1 = for { 282 val unsafe1 = for {
259 axiom <- tbox 283 axiom <- tbox
260 if axiom.isT5 284 if axiom.isT5
@@ -271,13 +295,7 @@ class RSAOntology(val ontology: OWLOntology) {
271 if roleSuperInv.contains(role2) 295 if roleSuperInv.contains(role2)
272 } yield role1 296 } yield role1
273 297
274 /* Checking for (2) unsafety condition: 298 /* Checking for unsafety condition (2) */
275 *
276 * For all roles p1 appearing in an axiom of type T5, p1 is unsafe if
277 * there exists a role p2 appearing in an axiom of type T4 and p1 is a
278 * subproperty of either p2 or the inverse of p2.
279 *
280 */
281 val unsafe2 = for { 299 val unsafe2 = for {
282 axiom <- tbox 300 axiom <- tbox
283 if axiom.isT5 301 if axiom.isT5
@@ -307,12 +325,76 @@ class RSAOntology(val ontology: OWLOntology) {
307 Graph(edges: _*) 325 Graph(edges: _*)
308 } 326 }
309 327
310 def filteringProgram(query: ConjunctiveQuery): FilteringProgram = 328 /** Top axiomatization rules
311 Logger.timed( 329 *
312 new FilteringProgram(query, individuals ++ literals), 330 * For each concept/role *in the ontology file* introduce a rule to
313 "Generating filtering program", 331 * derive `owl:Thing`.
314 Logger.DEBUG 332 *
333 * @note this might not be enough in cases where data files contain
334 * concept/roles that are not in the ontology file. While this is
335 * non-standard, it is not forbidden either and may cause problems
336 * since not all individuals are considered part of `owl:Thing`.
337 *
338 * @note this is a naïve implementation of top axiomatization and
339 * might change in the future. The ideal solution would be for RDFox
340 * to take care of this, but at the time of writing this is not
341 * compatible with the way we are using the tool.
342 */
343 private val topAxioms: List[Rule] = {
344 val varX = Variable.create("X")
345 val varY = Variable.create("Y")
346 concepts
347 .map(c => {
348 Rule.create(
349 RSA.Thing(varX),
350 TupleTableAtom.rdf(varX, IRI.RDF_TYPE, c.getIRI)
351 )
352 }) ++ roles.map(r => {
353 val name = r match {
354 case x: OWLObjectProperty => x.getIRI.getIRIString
355 case x: OWLObjectInverseOf =>
356 x.getInverse.getNamedProperty.getIRI.getIRIString :: Inverse
357 }
358 Rule.create(
359 List(RSA.Thing(varX), RSA.Thing(varY)),
360 List(TupleTableAtom.rdf(varX, name, varY))
361 )
362 })
363 }
364
365 /** Equality axiomatization rules
366 *
367 * Introduce reflexivity, simmetry and transitivity rules for a naïve
368 * equality axiomatization.
369 *
370 * @note that we are using a custom `congruent` predicate to indicate
371 * equality. This is to avoid interfering with the standard
372 * `owl:sameAs`.
373 *
374 * @note RDFox is able to handle equality in a "smart" way, but this
375 * behaviour is incompatible with other needed features like
376 * negation-as-failure and aggregates.
377 *
378 * @todo to complete the equality axiomatization we need to introduce
379 * substitution rules to explicate a complete "equality" semantics.
380 */
381 private val equalityAxioms: List[Rule] = {
382 val varX = Variable.create("X")
383 val varY = Variable.create("Y")
384 val varZ = Variable.create("Z")
385 List(
386 // Reflexivity
387 Rule.create(RSA.Congruent(varX, varX), RSA.Thing(varX)),
388 // Simmetry
389 Rule.create(RSA.Congruent(varY, varX), RSA.Congruent(varX, varY)),
390 // Transitivity
391 Rule.create(
392 RSA.Congruent(varX, varZ),
393 RSA.Congruent(varX, varY),
394 RSA.Congruent(varY, varZ)
395 )
315 ) 396 )
397 }
316 398
317 lazy val canonicalModel = Logger.timed( 399 lazy val canonicalModel = Logger.timed(
318 new CanonicalModel(this), 400 new CanonicalModel(this),
@@ -320,6 +402,13 @@ class RSAOntology(val ontology: OWLOntology) {
320 Logger.DEBUG 402 Logger.DEBUG
321 ) 403 )
322 404
405 def filteringProgram(query: ConjunctiveQuery): FilteringProgram =
406 Logger.timed(
407 new FilteringProgram(query),
408 "Generating filtering program",
409 Logger.DEBUG
410 )
411
323 // TODO: the following functions needs testing 412 // TODO: the following functions needs testing
324 def confl( 413 def confl(
325 role: OWLObjectPropertyExpression 414 role: OWLObjectPropertyExpression
@@ -356,18 +445,30 @@ class RSAOntology(val ontology: OWLOntology) {
356 val canon = this.canonicalModel 445 val canon = this.canonicalModel
357 val filter = this.filteringProgram(query) 446 val filter = this.filteringProgram(query)
358 447
359 //data.beginTransaction(TransactionType.READ_WRITE) 448 /* Upload data from data file */
449 RDFoxUtil.addData(data, datafiles: _*)
450
451 /* Top / equality axiomatization */
452 RDFoxUtil.addRules(data, topAxioms ++ equalityAxioms)
453
454 /* Generate `named` predicates */
455 RDFoxUtil.addFacts(data, (individuals ++ literals) map RSA.Named)
456 data.evaluateUpdate(
457 RSA.Prefixes,
458 "INSERT { ?X a rsa:Named } WHERE { ?X a owl:Thing }",
459 new java.util.HashMap[String, String]
460 )
360 461
361 Logger print s"Canonical model rules: ${canon.rules.length}" 462 Logger print s"Canonical model rules: ${canon.rules.length}"
362 RDFoxUtil.addRules(data, this.canonicalModel.rules) 463 RDFoxUtil.addRules(data, canon.rules)
363 464
364 Logger print s"Canonical model facts: ${canon.facts.length}" 465 Logger print s"Canonical model facts: ${canon.facts.length}"
365 RDFoxUtil.addFacts(data, this.canonicalModel.facts) 466 RDFoxUtil.addFacts(data, canon.facts)
366 467
367 RDFoxUtil printStatisticsFor data 468 //canon.facts.foreach(println)
469 //canon.rules.foreach(println)
368 470
369 Logger print s"Filtering program facts: ${filter.facts.length}" 471 RDFoxUtil printStatisticsFor data
370 RDFoxUtil.addFacts(data, filter.facts)
371 472
372 Logger print s"Filtering program rules: ${filter.rules.length}" 473 Logger print s"Filtering program rules: ${filter.rules.length}"
373 RDFoxUtil.addRules(data, filter.rules) 474 RDFoxUtil.addRules(data, filter.rules)