aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
diff options
context:
space:
mode:
authorFederico Igne <git@federicoigne.com>2021-07-27 10:34:57 +0100
committerFederico Igne <git@federicoigne.com>2021-07-27 10:34:57 +0100
commitd017662e2d65ec72e7decde3b76591c198da9819 (patch)
tree57193f145cb39223db0b0da6055556aca7d04622 /src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
parentc597b5efbe9e351a4313ef8fc1215f9e188b1ffd (diff)
parent7d619706551117a485d93d0d6847a25afa6a359d (diff)
downloadRSAComb-d017662e2d65ec72e7decde3b76591c198da9819.tar.gz
RSAComb-d017662e2d65ec72e7decde3b76591c198da9819.zip
Merge branch 'approximation'v0.2.0
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.scala663
1 files changed, 288 insertions, 375 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 3e10697..30e1305 100644
--- a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
@@ -66,117 +66,159 @@ import scala.collection.JavaConverters._
66import scala.collection.mutable.{Set, Map} 66import scala.collection.mutable.{Set, Map}
67import scalax.collection.Graph 67import scalax.collection.Graph
68import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._ 68import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._
69import scalax.collection.GraphTraversal._
70 69
71/* Debug only */ 70/* Debug only */
72import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer 71import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer
73import tech.oxfordsemantic.jrdfox.logic._ 72import tech.oxfordsemantic.jrdfox.logic._
74import org.semanticweb.owlapi.model.OWLObjectInverseOf 73import org.semanticweb.owlapi.model.OWLObjectInverseOf
75 74
75import uk.ac.ox.cs.rsacomb.approximation.Approximation
76import uk.ac.ox.cs.rsacomb.converter._ 76import uk.ac.ox.cs.rsacomb.converter._
77import uk.ac.ox.cs.rsacomb.filtering.{FilteringProgram, FilterType} 77import uk.ac.ox.cs.rsacomb.filtering.{FilteringProgram, FilterType}
78import uk.ac.ox.cs.rsacomb.suffix._ 78import uk.ac.ox.cs.rsacomb.suffix._
79import uk.ac.ox.cs.rsacomb.sparql._ 79import uk.ac.ox.cs.rsacomb.sparql._
80import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} 80import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA}
81import uk.ac.ox.cs.rsacomb.util.Logger 81import uk.ac.ox.cs.rsacomb.util.Logger
82import uk.ac.ox.cs.rsacomb.ontology.Ontology
82 83
83object RSAOntology { 84object RSAUtil {
84 85
85 /** Name of the RDFox data store used for CQ answering */ 86 // implicit def axiomsToOntology(axioms: Seq[OWLAxiom]) = {
86 private val DataStore = "answer_computation" 87 // val manager = OWLManager.createOWLOntologyManager()
88 // manager.createOntology(axioms.asJava)
89 // }
87 90
88 /** Simple fresh variable generator */ 91 /** Manager instance to interface with OWLAPI */
92 val manager = OWLManager.createOWLOntologyManager()
93 val factory = manager.getOWLDataFactory()
94
95 /** Simple fresh variable/class generator */
89 private var counter = -1; 96 private var counter = -1;
90 def genFreshVariable(): Variable = { 97 def genFreshVariable(): Variable = {
91 counter += 1 98 counter += 1
92 Variable.create(f"I$counter%05d") 99 Variable.create(f"I$counter%05d")
93 } 100 }
101 def getFreshOWLClass(): OWLClass = {
102 counter += 1
103 factory.getOWLClass(s"X$counter")
104 }
105
106}
107
108object RSAOntology {
109
110 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._
94 111
95 /** Manager instance to interface with OWLAPI */ 112 /** Manager instance to interface with OWLAPI */
96 val manager = OWLManager.createOWLOntologyManager() 113 val manager = OWLManager.createOWLOntologyManager()
97 114
98 def apply(ontology: File, data: File*): RSAOntology = 115 /** Name of the RDFox data store used for CQ answering */
99 new RSAOntology( 116 private val DataStore = "answer_computation"
100 manager.loadOntologyFromOntologyDocument(ontology), 117
101 data: _* 118 /** Filtering program for a given query
119 *
120 * @param query the query to derive the filtering program
121 * @return the filtering program for the given query
122 */
123 def filteringProgram(query: ConjunctiveQuery): FilteringProgram =
124 Logger.timed(
125 FilteringProgram(FilterType.REVISED)(query),
126 "Generating filtering program",
127 Logger.DEBUG
102 ) 128 )
103 129
104 def apply(ontology: OWLOntology, data: File*): RSAOntology = 130 def apply(
105 new RSAOntology(ontology, data: _*) 131 axioms: List[OWLLogicalAxiom],
132 datafiles: List[File]
133 ): RSAOntology = new RSAOntology(axioms, datafiles)
134
135 // def apply(
136 // ontofile: File,
137 // datafiles: List[File],
138 // approx: Option[Approximation]
139 // ): RSAOntology = {
140 // val ontology = manager.loadOntologyFromOntologyDocument(ontofile)
141 // RSAOntology(ontology, datafiles, approx)
142 // }
143
144 // def apply(
145 // ontology: OWLOntology,
146 // datafiles: List[File],
147 // approx: Option[Approximation]
148 // ): RSAOntology = {
149 // val normalizer = new Normalizer()
150
151 // /** TBox axioms */
152 // var tbox: List[OWLLogicalAxiom] =
153 // ontology
154 // .tboxAxioms(Imports.INCLUDED)
155 // .collect(Collectors.toList())
156 // .collect { case a: OWLLogicalAxiom => a }
157 // .flatMap(normalizer.normalize)
158
159 // /** RBox axioms */
160 // var rbox: List[OWLLogicalAxiom] =
161 // ontology
162 // .rboxAxioms(Imports.INCLUDED)
163 // .collect(Collectors.toList())
164 // .collect { case a: OWLLogicalAxiom => a }
165 // .flatMap(normalizer.normalize)
166
167 // /** ABox axioms
168 // *
169 // * @note this represents only the set of assertions contained in the
170 // * ontology file. Data files specified in `datafiles` are directly
171 // * imported in RDFox due to performance issues when trying to import
172 // * large data files via OWLAPI.
173 // */
174 // var abox: List[OWLLogicalAxiom] =
175 // ontology
176 // .aboxAxioms(Imports.INCLUDED)
177 // .collect(Collectors.toList())
178 // .collect { case a: OWLLogicalAxiom => a }
179 // .flatMap(normalizer.normalize)
180
181 // /** Collection of logical axioms in the input ontology */
182 // var axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox
183
184 // new RSAOntology(
185 // approx match {
186 // case Some(a) => a.approximate(axioms, datafiles)
187 // case None => axioms
188 // },
189 // datafiles: _*
190 // )
191 // }
192
106} 193}
107 194
108/** Wrapper class for an ontology in RSA 195/** A wrapper for an RSA ontology
109 * 196 *
110 * @param ontology the input OWL2 ontology. 197 * @param ontology the input OWL2 ontology.
111 * @param datafiles additinal data (treated as part of the ABox) 198 * @param datafiles additinal data (treated as part of the ABox)
112 */ 199 */
113class RSAOntology(val original: OWLOntology, val datafiles: File*) { 200class RSAOntology(axioms: List[OWLLogicalAxiom], datafiles: List[File])
201 extends Ontology(axioms, datafiles) {
114 202
115 /** Simplify conversion between OWLAPI and RDFox concepts */ 203 /** Simplify conversion between OWLAPI and RDFox concepts */
116 import implicits.RDFox._ 204 import implicits.RDFox._
117 import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._ 205 import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._
206
207 /** Simplify conversion between Java and Scala collections */
118 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ 208 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._
119 209
120 /** Set of axioms removed during the approximation to RSA */ 210 /** Set of axioms removed during the approximation to RSA */
121 private var removed: Seq[OWLAxiom] = Seq.empty 211 //private var removed: Seq[OWLAxiom] = Seq.empty
122
123 /** The normalizer normalizes the ontology and approximate it to
124 * Horn-ALCHOIQ. A further step is needed to obtain an RSA
125 * approximation of the input ontology `original`.
126 */
127 private val normalizer = new Normalizer()
128
129 /** TBox axioms */
130 var tbox: List[OWLLogicalAxiom] =
131 original
132 .tboxAxioms(Imports.INCLUDED)
133 .collect(Collectors.toList())
134 .collect { case a: OWLLogicalAxiom => a }
135 .flatMap(normalizer.normalize)
136
137 /** RBox axioms */
138 var rbox: List[OWLLogicalAxiom] =
139 original
140 .rboxAxioms(Imports.INCLUDED)
141 .collect(Collectors.toList())
142 .collect { case a: OWLLogicalAxiom => a }
143 .flatMap(normalizer.normalize)
144
145 /** ABox axioms
146 *
147 * @note this represents only the set of assertions contained in the
148 * ontology file. Data files specified in `datafiles` are directly
149 * imported in RDFox due to performance issues when trying to import
150 * large data files via OWLAPI.
151 */
152 var abox: List[OWLLogicalAxiom] =
153 original
154 .aboxAxioms(Imports.INCLUDED)
155 .collect(Collectors.toList())
156 .collect { case a: OWLLogicalAxiom => a }
157 .flatMap(normalizer.normalize)
158
159 /** Collection of logical axioms in the input ontology */
160 var axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox
161
162 /** Normalized Horn-ALCHOIQ ontology */
163 val ontology = RSAOntology.manager.createOntology(
164 axioms.asInstanceOf[List[OWLAxiom]].asJava
165 )
166
167 /** OWLAPI internal reasoner instantiated over the approximated ontology */
168 private val reasoner =
169 (new StructuralReasonerFactory()).createReasoner(ontology)
170 212
171 /** Retrieve individuals/literals in the ontology */ 213 /** Retrieve individuals/literals in the ontology */
172 val individuals: List[IRI] = 214 private val individuals: List[IRI] =
173 ontology 215 ontology
174 .getIndividualsInSignature() 216 .getIndividualsInSignature()
175 .asScala 217 .asScala
176 .map(_.getIRI) 218 .map(_.getIRI)
177 .map(implicits.RDFox.owlapiToRdfoxIri) 219 .map(implicits.RDFox.owlapiToRdfoxIri)
178 .toList 220 .toList
179 val literals: List[Literal] = 221 private val literals: List[Literal] =
180 axioms 222 axioms
181 .collect { case a: OWLDataPropertyAssertionAxiom => a } 223 .collect { case a: OWLDataPropertyAssertionAxiom => a }
182 .map(_.getObject) 224 .map(_.getObject)
@@ -186,7 +228,7 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
186 val concepts: List[OWLClass] = 228 val concepts: List[OWLClass] =
187 ontology.getClassesInSignature().asScala.toList 229 ontology.getClassesInSignature().asScala.toList
188 val roles: List[OWLObjectPropertyExpression] = 230 val roles: List[OWLObjectPropertyExpression] =
189 (tbox ++ rbox) 231 axioms
190 .flatMap(_.objectPropertyExpressionsInSignature) 232 .flatMap(_.objectPropertyExpressionsInSignature)
191 .distinct 233 .distinct
192 234
@@ -202,123 +244,36 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
202 * if there exists a role p2 appearing in an axiom of type T4 and 244 * if there exists a role p2 appearing in an axiom of type T4 and
203 * p1 is a subproperty of either p2 or the inverse of p2. 245 * p1 is a subproperty of either p2 or the inverse of p2.
204 */ 246 */
205 val unsafeRoles: List[OWLObjectPropertyExpression] = { 247 // val unsafeRoles: List[OWLObjectPropertyExpression] = {
206 248
207 /* Checking for unsafety condition (1) */ 249 // /* Checking for unsafety condition (1) */
208 val unsafe1 = for { 250 // val unsafe1 = for {
209 axiom <- tbox 251 // axiom <- axioms
210 if axiom.isT5 252 // if axiom.isT5
211 role1 <- axiom.objectPropertyExpressionsInSignature 253 // role1 <- axiom.objectPropertyExpressionsInSignature
212 roleSuper = role1 +: reasoner.superObjectProperties(role1) 254 // roleSuper = role1 +: reasoner.superObjectProperties(role1)
213 roleSuperInv = roleSuper.map(_.getInverseProperty) 255 // roleSuperInv = roleSuper.map(_.getInverseProperty)
214 axiom <- tbox 256 // axiom <- axioms
215 if axiom.isT3 && !axiom.isT3top 257 // if axiom.isT3 && !axiom.isT3top
216 role2 <- axiom.objectPropertyExpressionsInSignature 258 // role2 <- axiom.objectPropertyExpressionsInSignature
217 if roleSuperInv contains role2 259 // if roleSuperInv contains role2
218 } yield role1 260 // } yield role1
219 261
220 /* Checking for unsafety condition (2) */ 262 // /* Checking for unsafety condition (2) */
221 val unsafe2 = for { 263 // val unsafe2 = for {
222 axiom <- tbox 264 // axiom <- axioms
223 if axiom.isT5 265 // if axiom.isT5
224 role1 <- axiom.objectPropertyExpressionsInSignature 266 // role1 <- axiom.objectPropertyExpressionsInSignature
225 roleSuper = role1 +: reasoner.superObjectProperties(role1) 267 // roleSuper = role1 +: reasoner.superObjectProperties(role1)
226 roleSuperInv = roleSuper.map(_.getInverseProperty) 268 // roleSuperInv = roleSuper.map(_.getInverseProperty)
227 axiom <- tbox 269 // axiom <- axioms
228 if axiom.isT4 270 // if axiom.isT4
229 role2 <- axiom.objectPropertyExpressionsInSignature 271 // role2 <- axiom.objectPropertyExpressionsInSignature
230 if roleSuper.contains(role2) || roleSuperInv.contains(role2) 272 // if roleSuper.contains(role2) || roleSuperInv.contains(role2)
231 } yield role1 273 // } yield role1
232 274
233 unsafe1 ++ unsafe2 275 // unsafe1 ++ unsafe2
234 } 276 // }
235
236 /** Compute the RSA dependency graph
237 *
238 * This is used to approximate the input ontology to RSA.
239 *
240 * @return a tuple containing the dependency graph and a map between
241 * the constants newly introduced and the corresponding axioms in the
242 * ontology.
243 */
244 private def dependencyGraph()
245 : (Graph[Resource, DiEdge], Map[String, OWLAxiom]) = {
246 val unsafe = this.unsafeRoles
247 var nodemap = Map.empty[String, OWLAxiom]
248
249 object RSAConverter extends RDFoxConverter {
250
251 override def convert(
252 expr: OWLClassExpression,
253 term: Term,
254 unsafe: List[OWLObjectPropertyExpression],
255 skolem: SkolemStrategy,
256 suffix: RSASuffix
257 ): Shards =
258 (expr, skolem) match {
259
260 case (e: OWLObjectSomeValuesFrom, c: Constant) => {
261 nodemap.update(c.iri.getIRI, c.axiom)
262 val (res, ext) = super.convert(e, term, unsafe, skolem, suffix)
263 if (unsafe contains e.getProperty)
264 (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext)
265 else
266 (RSA.PE(term, c.iri) :: res, ext)
267 }
268
269 case (e: OWLDataSomeValuesFrom, c: Constant) => {
270 nodemap.update(c.iri.getIRI, c.axiom)
271 val (res, ext) = super.convert(e, term, unsafe, skolem, suffix)
272 if (unsafe contains e.getProperty)
273 (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext)
274 else
275 (RSA.PE(term, c.iri) :: res, ext)
276 }
277
278 case _ => super.convert(expr, term, unsafe, skolem, suffix)
279 }
280 }
281
282 /* Ontology convertion into LP rules */
283 val term = RSAOntology.genFreshVariable()
284 val result = axioms.map(a =>
285 RSAConverter.convert(a, term, unsafe, new Constant(a), Empty)
286 )
287
288 val datalog = result.unzip
289 val facts = datalog._1.flatten
290 var rules = datalog._2.flatten
291
292 /* Open connection with RDFox */
293 val (server, data) = RDFoxUtil.openConnection("rsa_dependency_graph")
294
295 /* Add additional built-in rules */
296 val varX = Variable.create("X")
297 val varY = Variable.create("Y")
298 rules = Rule.create(
299 RSA.E(varX, varY),
300 RSA.PE(varX, varY),
301 RSA.U(varX),
302 RSA.U(varY)
303 ) :: rules
304 /* Load facts and rules from ontology */
305 RDFoxUtil.addFacts(data, facts)
306 RDFoxUtil.addRules(data, rules)
307 /* Load data files */
308 RDFoxUtil.addData(data, datafiles: _*)
309
310 /* Build the graph */
311 val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }"
312 val answers = RDFoxUtil.submitQuery(data, query, RSA.Prefixes).get
313 var edges: Seq[DiEdge[Resource]] =
314 answers.collect { case (_, Seq(n1, n2)) => n1 ~> n2 }
315 val graph = Graph(edges: _*)
316
317 /* Close connection to RDFox */
318 RDFoxUtil.closeConnection(server, data)
319
320 (graph, nodemap)
321 }
322 277
323 /** Approximate a Horn-ALCHOIQ ontology to RSA 278 /** Approximate a Horn-ALCHOIQ ontology to RSA
324 * 279 *
@@ -329,61 +284,61 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
329 * @param graph the graph used to compute the axioms to remove. 284 * @param graph the graph used to compute the axioms to remove.
330 * @param nodemap map from graph nodes to ontology axioms. 285 * @param nodemap map from graph nodes to ontology axioms.
331 */ 286 */
332 def toRSA(): RSAOntology = Logger.timed( 287 // def toRSA(): RSAOntology = Logger.timed(
333 { 288 // {
334 289
335 /* Compute the dependency graph for the ontology */ 290 // /* Compute the dependency graph for the ontology */
336 val (graph, nodemap) = this.dependencyGraph() 291 // val (graph, nodemap) = this.dependencyGraph()
337 292
338 /* Define node colors for the graph visit */ 293 // /* Define node colors for the graph visit */
339 sealed trait NodeColor 294 // sealed trait NodeColor
340 case object Unvisited extends NodeColor 295 // case object Unvisited extends NodeColor
341 case object Visited extends NodeColor 296 // case object Visited extends NodeColor
342 case object ToDelete extends NodeColor 297 // case object ToDelete extends NodeColor
343 298
344 /* Keep track of node colors during graph visit */ 299 // /* Keep track of node colors during graph visit */
345 var color = Map.from[Resource, NodeColor]( 300 // var color = Map.from[Resource, NodeColor](
346 graph.nodes.toOuter.map(k => (k, Unvisited)) 301 // graph.nodes.toOuter.map(k => (k, Unvisited))
347 ) 302 // )
348 303
349 for { 304 // for {
350 component <- graph.componentTraverser().map(_ to Graph) 305 // component <- graph.componentTraverser().map(_ to Graph)
351 edge <- component 306 // edge <- component
352 .outerEdgeTraverser(component.nodes.head) 307 // .outerEdgeTraverser(component.nodes.head)
353 .withKind(BreadthFirst) 308 // .withKind(BreadthFirst)
354 } yield { 309 // } yield {
355 val source = edge._1 310 // val source = edge._1
356 val target = edge._2 311 // val target = edge._2
357 color(source) match { 312 // color(source) match {
358 case Unvisited | Visited => { 313 // case Unvisited | Visited => {
359 color(target) match { 314 // color(target) match {
360 case Unvisited => 315 // case Unvisited =>
361 color(source) = Visited; 316 // color(source) = Visited;
362 color(target) = Visited 317 // color(target) = Visited
363 case Visited => 318 // case Visited =>
364 color(source) = ToDelete 319 // color(source) = ToDelete
365 case ToDelete => 320 // case ToDelete =>
366 color(source) = Visited 321 // color(source) = Visited
367 } 322 // }
368 } 323 // }
369 case ToDelete => 324 // case ToDelete =>
370 } 325 // }
371 } 326 // }
372 327
373 val toDelete = color.iterator.collect { case (resource: IRI, ToDelete) => 328 // val toDelete = color.iterator.collect { case (resource: IRI, ToDelete) =>
374 nodemap(resource.getIRI) 329 // nodemap(resource.getIRI)
375 }.toSeq 330 // }.toSeq
376 331
377 /* Remove axioms from approximated ontology */ 332 // /* Remove axioms from approximated ontology */
378 ontology.removeAxioms(toDelete: _*) 333 // ontology.removeAxioms(toDelete: _*)
379 this.removed = toDelete 334 // this.removed = toDelete
380 335
381 /* Return RSA ontology */ 336 // /* Return RSA ontology */
382 RSAOntology(ontology, datafiles: _*) 337 // RSAOntology(ontology, datafiles: _*)
383 }, 338 // },
384 "Horn-ALCHOIQ to RSA approximation:", 339 // "Horn-ALCHOIQ to RSA approximation:",
385 Logger.DEBUG 340 // Logger.DEBUG
386 ) 341 // )
387 // val edges1 = Seq('A ~> 'B, 'B ~> 'C, 'C ~> 'D, 'D ~> 'H, 'H ~> 342 // val edges1 = Seq('A ~> 'B, 'B ~> 'C, 'C ~> 'D, 'D ~> 'H, 'H ~>
388 // 'G, 'G ~> 'F, 'E ~> 'A, 'E ~> 'F, 'B ~> 'E, 'F ~> 'G, 'B ~> 'F, 343 // 'G, 'G ~> 'F, 'E ~> 'A, 'E ~> 'F, 'B ~> 'E, 'F ~> 'G, 'B ~> 'F,
389 // 'C ~> 'G, 'D ~> 'C, 'H ~> 'D) 344 // 'C ~> 'G, 'D ~> 'C, 'H ~> 'D)
@@ -462,31 +417,27 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
462 ) 417 )
463 } 418 }
464 419
420 /** Canonical model of the ontology */
465 lazy val canonicalModel = Logger.timed( 421 lazy val canonicalModel = Logger.timed(
466 new CanonicalModel(this), 422 new CanonicalModel(this),
467 "Generating canonical model program", 423 "Generating canonical model program",
468 Logger.DEBUG 424 Logger.DEBUG
469 ) 425 )
470 426
471 def filteringProgram(query: ConjunctiveQuery): FilteringProgram = 427 /** Computes all roles conflicting with a given role
472 Logger.timed( 428 *
473 FilteringProgram(FilterType.REVISED)(query), 429 * @param role a role (object property expression).
474 "Generating filtering program", 430 * @return a set of roles conflicting with `role`.
475 Logger.DEBUG 431 */
476 )
477
478 def confl( 432 def confl(
479 role: OWLObjectPropertyExpression 433 role: OWLObjectPropertyExpression
480 ): Set[OWLObjectPropertyExpression] = { 434 ): Set[OWLObjectPropertyExpression] = {
481 435 reasoner
482 val invSuperRoles = reasoner
483 .superObjectProperties(role) 436 .superObjectProperties(role)
484 .collect(Collectors.toSet()) 437 .collect(Collectors.toSet())
485 .asScala 438 .asScala
486 .addOne(role) 439 .addOne(role)
487 .map(_.getInverseProperty) 440 .map(_.getInverseProperty)
488
489 invSuperRoles
490 .flatMap(x => 441 .flatMap(x =>
491 reasoner 442 reasoner
492 .subObjectProperties(x) 443 .subObjectProperties(x)
@@ -498,6 +449,77 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
498 .filterNot(_.getInverseProperty.isOWLTopObjectProperty()) 449 .filterNot(_.getInverseProperty.isOWLTopObjectProperty())
499 } 450 }
500 451
452 /** Selfloop detection for a given axiom
453 *
454 * @param axiom an axiom of type [[OWLSubClassOfAxiom]]
455 * @return unfold set for the axiom
456 */
457 def self(axiom: OWLSubClassOfAxiom): Set[Term] = {
458 val role = axiom.objectPropertyExpressionsInSignature(0)
459 if (this.confl(role).contains(role)) {
460 Set(RSA("v0_" ++ axiom.hashed), RSA("v1_" ++ axiom.hashed))
461 } else {
462 Set()
463 }
464 }
465
466 /** Cycle detection for a give axiom
467 *
468 * @param axiom an axiom of type [[OWLSubClassOfAxiom]]
469 * @return unfold set for the axiom
470 *
471 * @todo we can actually use `toTriple` from `RSAAxiom` to get the
472 * classes and the role for a given axiom
473 */
474 def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = {
475 val classes =
476 axiom.classesInSignature.collect(Collectors.toList()).asScala
477 val classA = classes(0)
478 val roleR = axiom
479 .objectPropertyExpressionsInSignature(0)
480 .asInstanceOf[OWLObjectProperty]
481 val classB = classes(1)
482 cycle_aux(classA, roleR, classB)
483 }
484
485 /** Auxiliary function for [[RSAOntology.cycle]] */
486 private def cycle_aux(
487 classA: OWLClass,
488 roleR: OWLObjectProperty,
489 classB: OWLClass
490 ): Set[Term] = {
491 val conflR = this.confl(roleR)
492 // TODO: technically we just need the TBox here
493 val terms = for {
494 axiom1 <- axioms
495 if axiom1.isT5
496 // We expect only one role coming out of a T5 axiom
497 roleS <- axiom1.objectPropertyExpressionsInSignature
498 // Triples ordering is among triples involving safe roles.
499 if !unsafe.contains(roleS)
500 if conflR.contains(roleS)
501 tripleARB = RSAAxiom.hashed(classA, roleR, classB)
502 tripleDSC = axiom1.hashed
503 individual =
504 if (tripleARB > tripleDSC) {
505 RSA("v1_" ++ tripleDSC)
506 } else {
507 // Note that this is also the case for
508 // `tripleARB == tripleDSC`
509 RSA("v0_" ++ tripleDSC)
510 }
511 } yield individual
512 terms to Set
513 }
514
515 /** Returns unfold set for self-loop and cycle for the input axiom
516 *
517 * @param axiom an axiom of type [[OWLSubClassOfAxiom]]
518 * @return unfold set for the axiom
519 */
520 def unfold(axiom: OWLSubClassOfAxiom): Set[Term] =
521 this.self(axiom) | this.cycle(axiom)
522
501 /** Returns the answers to a query 523 /** Returns the answers to a query
502 * 524 *
503 * @param query query to execute 525 * @param query query to execute
@@ -505,10 +527,9 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
505 */ 527 */
506 def ask(query: ConjunctiveQuery): ConjunctiveQueryAnswers = Logger.timed( 528 def ask(query: ConjunctiveQuery): ConjunctiveQueryAnswers = Logger.timed(
507 { 529 {
508 import implicits.JavaCollections._
509 val (server, data) = RDFoxUtil.openConnection(RSAOntology.DataStore) 530 val (server, data) = RDFoxUtil.openConnection(RSAOntology.DataStore)
510 val canon = this.canonicalModel 531 val canon = this.canonicalModel
511 val filter = this.filteringProgram(query) 532 val filter = RSAOntology.filteringProgram(query)
512 533
513 /* Upload data from data file */ 534 /* Upload data from data file */
514 RDFoxUtil.addData(data, datafiles: _*) 535 RDFoxUtil.addData(data, datafiles: _*)
@@ -526,12 +547,15 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
526 new java.util.HashMap[String, String] 547 new java.util.HashMap[String, String]
527 ) 548 )
528 549
550 /* Add canonical model */
529 Logger print s"Canonical model rules: ${canon.rules.length}" 551 Logger print s"Canonical model rules: ${canon.rules.length}"
530 RDFoxUtil.addRules(data, canon.rules) 552 RDFoxUtil.addRules(data, canon.rules)
531 553
532 Logger print s"Canonical model facts: ${canon.facts.length}" 554 Logger print s"Canonical model facts: ${canon.facts.length}"
533 RDFoxUtil.addFacts(data, canon.facts) 555 RDFoxUtil.addFacts(data, canon.facts)
534 556
557 RDFoxUtil printStatisticsFor data
558
535 //{ 559 //{
536 // import java.io.{PrintStream, FileOutputStream, File} 560 // import java.io.{PrintStream, FileOutputStream, File}
537 // val rules1 = new FileOutputStream(new File("rules1-lubm200.dlog")) 561 // val rules1 = new FileOutputStream(new File("rules1-lubm200.dlog"))
@@ -541,16 +565,13 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
541 // rules2.print(filter.rules.mkString("\n")) 565 // rules2.print(filter.rules.mkString("\n"))
542 //} 566 //}
543 567
544 //canon.facts.foreach(println) 568 /* Add filtering program */
545 //filter.rules.foreach(println)
546
547 RDFoxUtil printStatisticsFor data
548
549 Logger print s"Filtering program rules: ${filter.rules.length}" 569 Logger print s"Filtering program rules: ${filter.rules.length}"
550 RDFoxUtil.addRules(data, filter.rules) 570 RDFoxUtil.addRules(data, filter.rules)
551 571
552 RDFoxUtil printStatisticsFor data 572 RDFoxUtil printStatisticsFor data
553 573
574 /* Gather answers to the query */
554 val answers = { 575 val answers = {
555 val ans = filter.answerQuery 576 val ans = filter.answerQuery
556 RDFoxUtil 577 RDFoxUtil
@@ -558,7 +579,9 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
558 .map(new ConjunctiveQueryAnswers(query.bcq, query.variables, _)) 579 .map(new ConjunctiveQueryAnswers(query.bcq, query.variables, _))
559 .get 580 .get
560 } 581 }
582
561 RDFoxUtil.closeConnection(server, data) 583 RDFoxUtil.closeConnection(server, data)
584
562 answers 585 answers
563 }, 586 },
564 "Answers computation", 587 "Answers computation",
@@ -569,14 +592,15 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
569 * 592 *
570 * @note This method does not add any facts or rules to the data 593 * @note This method does not add any facts or rules to the data
571 * store. It is most useful after the execution of a query using 594 * store. It is most useful after the execution of a query using
572 * [[uk.ac.ox.cs.rsacomb.RSAOntology.ask RSAOntology.ask]]. 595 * [[RSAOntology.ask]].
573 * @note This method has been introduced mostly for debugging purposes.
574 * 596 *
575 * @param query query to be executed against the environment 597 * @param query query to be executed against the environment
576 * @param prefixes additional prefixes for the query. It defaults to 598 * @param prefixes additional prefixes for the query. It defaults to
577 * an empty set. 599 * an empty set.
578 * @param opts additional options to RDFox. 600 * @param opts additional options to RDFox.
579 * @return a collection of answers to the input query. 601 * @return a collection of answers to the input query.
602 *
603 * @note This method has been introduced mostly for debugging purposes.
580 */ 604 */
581 def queryDataStore( 605 def queryDataStore(
582 query: String, 606 query: String,
@@ -598,122 +622,11 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
598 * [[uk.ac.ox.cs.rsacomb.RSAOntology.ask RSAOntology.ask]] 622 * [[uk.ac.ox.cs.rsacomb.RSAOntology.ask RSAOntology.ask]]
599 * for the corresponding query has been called. 623 * for the corresponding query has been called.
600 */ 624 */
601 def askUnfiltered( 625 // def askUnfiltered(
602 cq: ConjunctiveQuery 626 // cq: ConjunctiveQuery
603 ): Option[Seq[(Long, Seq[Resource])]] = { 627 // ): Option[Seq[(Long, Seq[Resource])]] = {
604 val query = RDFoxUtil.buildDescriptionQuery("QM", cq.variables.length) 628 // val query = RDFoxUtil.buildDescriptionQuery("QM", cq.variables.length)
605 queryDataStore(query, RSA.Prefixes) 629 // queryDataStore(query, RSA.Prefixes)
606 } 630 // }
607 631
608 def self(axiom: OWLSubClassOfAxiom): Set[Term] = { 632}
609 // Assuming just one role in the signature of a T5 axiom
610 val role = axiom.objectPropertyExpressionsInSignature(0)
611 if (this.confl(role).contains(role)) {
612 Set(
613 RSA("v0_" ++ axiom.hashed),
614 RSA("v1_" ++ axiom.hashed)
615 )
616 } else {
617 Set()
618 }
619 }
620
621 def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = {
622 // TODO: we can actually use `toTriple` from `RSAAxiom`
623 val classes =
624 axiom.classesInSignature.collect(Collectors.toList()).asScala
625 val classA = classes(0)
626 val roleR = axiom
627 .objectPropertyExpressionsInSignature(0)
628 .asInstanceOf[OWLObjectProperty]
629 val classB = classes(1)
630 cycle_aux1(classA, roleR, classB)
631 }
632
633 def cycle_aux0(
634 classA: OWLClass,
635 roleR: OWLObjectProperty,
636 classB: OWLClass
637 ): Set[Term] = {
638 val conflR = this.confl(roleR)
639 val classes = ontology
640 .classesInSignature(Imports.INCLUDED)
641 .collect(Collectors.toSet())
642 .asScala
643 for {
644 classD <- classes
645 roleS <- conflR
646 classC <- classes
647 // Keeping this check for now
648 if !unsafeRoles.contains(roleS)
649 tripleARB = RSAAxiom.hashed(classA, roleR, classB)
650 tripleDSC = RSAAxiom.hashed(classD, roleS, classC)
651 individual =
652 if (tripleARB > tripleDSC) {
653 RSA("v1_" ++ tripleDSC)
654 } else {
655 // Note that this is also the case for
656 // `tripleARB == tripleDSC`
657 RSA("v0_" ++ tripleDSC)
658 }
659 } yield individual
660 }
661
662 def cycle_aux1(
663 classA: OWLClass,
664 roleR: OWLObjectProperty,
665 classB: OWLClass
666 ): Set[Term] = {
667 val conflR = this.confl(roleR)
668 // We just need the TBox to find
669 val terms = for {
670 axiom1 <- tbox
671 if axiom1.isT5
672 // We expect only one role coming out of a T5 axiom
673 roleS <- axiom1.objectPropertyExpressionsInSignature
674 // Triples ordering is among triples involving safe roles.
675 if !unsafeRoles.contains(roleS)
676 if conflR.contains(roleS)
677 tripleARB = RSAAxiom.hashed(classA, roleR, classB)
678 tripleDSC = axiom1.hashed
679 individual =
680 if (tripleARB > tripleDSC) {
681 RSA("v1_" ++ tripleDSC)
682 } else {
683 // Note that this is also the case for
684 // `tripleARB == tripleDSC`
685 RSA("v0_" ++ tripleDSC)
686 }
687 } yield individual
688 terms to Set
689 }
690
691 def unfold(axiom: OWLSubClassOfAxiom): Set[Term] =
692 this.self(axiom) | this.cycle(axiom)
693
694 /** Log normalization/approximation statistics */
695 def statistics(level: Logger.Level = Logger.DEBUG): Unit = {
696 Logger.print(
697 s"Logical axioms in original input ontology: ${original.getLogicalAxiomCount(true)}",
698 level
699 )
700 Logger.print(
701 s"Logical axioms discarded in Horn-ALCHOIQ approximation: ${normalizer.discarded}",
702 level
703 )
704 Logger.print(
705 s"Logical axioms shifted in Horn-ALCHOIQ approximation: ${normalizer.shifted}",
706 level
707 )
708 Logger.print(
709 s"Logical axioms in Horn-ALCHOIQ ontology: ${ontology
710 .getLogicalAxiomCount(true)} (${tbox.length}/${rbox.length}/${abox.length})",
711 level
712 )
713 Logger.print(
714 s"Logical axioms discarded in RSA approximation: ${removed.length}",
715 level
716 )
717 }
718
719} // class RSAOntology