aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFederico Igne <federico.igne@cs.ox.ac.uk>2022-05-13 12:18:09 +0100
committerFederico Igne <federico.igne@cs.ox.ac.uk>2022-05-19 15:12:21 +0100
commit24fd3ba575e4857eead4fc33d728a2938746d7ef (patch)
treec861507c927fc38dbbc97ba612cd66ce25f44636
parent98312cd3c355a2b036edf5236dfcba755da9a17a (diff)
downloadRSAComb-24fd3ba575e4857eead4fc33d728a2938746d7ef.tar.gz
RSAComb-24fd3ba575e4857eead4fc33d728a2938746d7ef.zip
refactor: internal implementation of Ontology class
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/ontology/Ontology.scala542
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/util/Logger.scala33
2 files changed, 420 insertions, 155 deletions
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/ontology/Ontology.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/ontology/Ontology.scala
index 0aceb01..7b849c3 100644
--- a/src/main/scala/uk/ac/ox/cs/rsacomb/ontology/Ontology.scala
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/ontology/Ontology.scala
@@ -1,5 +1,5 @@
1/* 1/*
2 * Copyright 2020, 2021 KRR Oxford 2 * Copyright 2020-2022 KRR Oxford
3 * 3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License. 5 * you may not use this file except in compliance with the License.
@@ -16,159 +16,55 @@
16 16
17package uk.ac.ox.cs.rsacomb.ontology 17package uk.ac.ox.cs.rsacomb.ontology
18 18
19import java.io.File
20import java.util.stream.Collectors 19import java.util.stream.Collectors
21 20
22import scala.collection.mutable.Map
23import scala.collection.JavaConverters._ 21import scala.collection.JavaConverters._
22import scala.collection.mutable.Map
24import scalax.collection.Graph 23import scalax.collection.Graph
25import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._ 24import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._
26 25
27import org.semanticweb.owlapi.model.parameters.Imports
28import org.semanticweb.owlapi.apibinding.OWLManager 26import org.semanticweb.owlapi.apibinding.OWLManager
29import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom, OWLLogicalAxiom} 27import org.semanticweb.owlapi.model._
30import org.semanticweb.owlapi.model.{OWLObjectPropertyExpression} 28import org.semanticweb.owlapi.model.parameters.Imports
31import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory 29import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory
32import tech.oxfordsemantic.jrdfox.logic.datalog.{Rule, TupleTableName} 30import tech.oxfordsemantic.jrdfox.logic.datalog.{
31 FilterAtom,
32 Rule,
33 TupleTableAtom,
34 TupleTableName,
35}
33import tech.oxfordsemantic.jrdfox.logic.expression.{ 36import tech.oxfordsemantic.jrdfox.logic.expression.{
37 FunctionCall,
34 IRI, 38 IRI,
39 Literal,
35 Resource, 40 Resource,
36 Term, 41 Term,
37 Variable
38} 42}
39 43
40import uk.ac.ox.cs.rsacomb.approximation.Approximation 44import uk.ac.ox.cs.rsacomb.approximation.Approximation
41import uk.ac.ox.cs.rsacomb.converter._ 45import uk.ac.ox.cs.rsacomb.converter._
42import uk.ac.ox.cs.rsacomb.suffix._ 46import uk.ac.ox.cs.rsacomb.suffix._
43import uk.ac.ox.cs.rsacomb.util.{DataFactory, RDFoxUtil, RSA} 47import uk.ac.ox.cs.rsacomb.util.{DataFactory,RDFoxUtil,RSA}
44 48
45object Ontology { 49object Ontology {
46 50
47 /** Simplify conversion between Java and Scala collections */ 51 /** Simplify conversion between Java and Scala collections */
48 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ 52 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._
49 53
50 /** Type wrapper representing a dependency graph for the ontology. 54 /** Name of the RDFox data store used for the RSA check */
51 * 55 val DataStore = "rsa_dependency_graph"
52 * The graph is returned along with a map associating each node (IRI 56 /* RSA check named graph */
53 * string of the resource), with the corresponding axiom in the 57 val RSACheck: IRI = RDFoxUtil.getNamedGraph(DataStore, "RSACheck")
54 * original TBox. 58 /* Named graphs for some necessary RBox reasoning */
55 */ 59 private val RBoxProxy: IRI = RDFoxUtil.getNamedGraph(DataStore, "RBoxProxy")
56 type DependencyGraph = (Graph[Resource, DiEdge], Map[String, OWLAxiom]) 60 val RBoxReasoning: IRI = RDFoxUtil.getNamedGraph(DataStore, "RBoxReasoning")
57 61
58 /** Manager instance to interface with OWLAPI 62 /** Manager instance to interface with OWLAPI
59 * 63 *
60 * TODO: turn this into an implicit class parameter. 64 * TODO: turn this into an implicit class parameter.
61 */ 65 */
62 val manager = OWLManager.createOWLOntologyManager() 66 val manager = OWLManager.createOWLOntologyManager()
63 67 val factory = manager.getOWLDataFactory()
64 /** Compute the RSA dependency graph for a set of axioms
65 *
66 * @param axioms set of input axioms (TBox) to build the dependency
67 * graph.
68 * @param datafiles data (ABox) to build the dependency graph.
69 * @param unsafe list of unsafe roles in the TBox.
70 *
71 * @return a tuple containing the dependency graph and a map between
72 * the newly introduced constants and the corresponding input axioms.
73 *
74 * @note no check on the ontology language is performed since the
75 * construction of the dependency graph is computed regardless. The
76 * input axioms are assumed to be normalized.
77 */
78 def dependencyGraph(
79 axioms: List[OWLLogicalAxiom],
80 datafiles: List[os.Path],
81 unsafe: List[OWLObjectPropertyExpression]
82 ): DependencyGraph = {
83
84 var nodemap = Map.empty[String, OWLAxiom]
85
86 /* Create custom converter */
87 object RSAConverter extends RDFoxConverter {
88
89 import org.semanticweb.owlapi.model.{
90 OWLClassExpression,
91 OWLObjectSomeValuesFrom,
92 OWLDataSomeValuesFrom
93 }
94
95 override def convert(
96 expr: OWLClassExpression,
97 term: Term,
98 unsafe: List[OWLObjectPropertyExpression],
99 skolem: SkolemStrategy,
100 suffix: RSASuffix
101 )(implicit fresh: DataFactory): Shards =
102 (expr, skolem) match {
103
104 case (e: OWLObjectSomeValuesFrom, c: Constant) => {
105 nodemap.update(c.iri.getIRI, c.axiom)
106 val (res, ext) =
107 super.convert(e, term, unsafe, skolem, suffix)(fresh)
108 if (unsafe contains e.getProperty)
109 (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext)
110 else
111 (RSA.PE(term, c.iri) :: res, ext)
112 }
113
114 case (e: OWLDataSomeValuesFrom, c: Constant) => {
115 nodemap.update(c.iri.getIRI, c.axiom)
116 val (res, ext) =
117 super.convert(e, term, unsafe, skolem, suffix)(fresh)
118 if (unsafe contains e.getProperty)
119 (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext)
120 else
121 (RSA.PE(term, c.iri) :: res, ext)
122 }
123
124 case _ => super.convert(expr, term, unsafe, skolem, suffix)(fresh)
125 }
126 }
127
128 /* Ontology convertion into LP rules */
129 val term = Variable.create("X")
130 val result = axioms.map(a =>
131 RSAConverter.convert(a, term, unsafe, new Constant(a), Empty)
132 )
133
134 val datalog = result.unzip
135 val facts = datalog._1.flatten
136 var rules = datalog._2.flatten
137
138 /* Open connection with RDFox */
139 val (server, data) = RDFoxUtil.openConnection("rsa_dependency_graph")
140
141 /* Add additional built-in rules */
142 val varX = Variable.create("X")
143 val varY = Variable.create("Y")
144 rules = Rule.create(
145 RSA.E(varX, varY),
146 RSA.PE(varX, varY),
147 RSA.U(varX),
148 RSA.U(varY)
149 ) :: rules
150 /* Load facts and rules from ontology */
151 val ttn = IRI.create(TupleTableName.DEFAULT_TRIPLES.getName)
152 RDFoxUtil.addFacts(data, ttn, facts)
153 RDFoxUtil.addRules(data, rules)
154 /* Load data files */
155 RDFoxUtil.addData(data, ttn, datafiles: _*)
156
157 /* Build the graph */
158 val query = "SELECT ?X ?Y WHERE { ?X rsacomb:E ?Y }"
159 val answers = RDFoxUtil.submitQuery(data, query, RSA.Prefixes).get
160 var edges: Seq[DiEdge[Resource]] =
161 answers.collect { case (_, Seq(n1, n2)) => n1 ~> n2 }
162 val graph = Graph(edges: _*)
163
164 /* Close connection to RDFox */
165 RDFoxUtil.closeConnection(server, data)
166
167 (graph, nodemap)
168 }
169
170 // def apply(axioms: List[OWLLogicalAxiom], datafiles: List[os.Path]): Ontology =
171 // new Ontology(axioms, datafiles)
172 68
173 def apply(ontology: OWLOntology, datafiles: List[os.Path]): Ontology = { 69 def apply(ontology: OWLOntology, datafiles: List[os.Path]): Ontology = {
174 70
@@ -211,21 +107,23 @@ object Ontology {
211 107
212/** A wrapper for a generic OWL2 ontology 108/** A wrapper for a generic OWL2 ontology
213 * 109 *
214 * @param axioms list of axioms (roughly) corresponding to the TBox. 110 * @param origin reference to the wrapped [[OWLOntology]]
215 * @param datafiles files containing ABox data. 111 * @param axioms list of axioms (TBox + RBox) in the ontology
112 * @param datafiles files containing ABox data
216 */ 113 */
217class Ontology( 114class Ontology (
218 val origin: OWLOntology, 115 val origin: OWLOntology,
219 val axioms: List[OWLLogicalAxiom], 116 val axioms: List[OWLLogicalAxiom],
220 val datafiles: List[os.Path] 117 val datafiles: List[os.Path]
221) { 118) {
222 119
223 /** Extend OWLAxiom functionalities */ 120 import uk.ac.ox.cs.rsacomb.util.RDFoxDSL._
224 import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._
225 121
226 /** Simplify conversion between Java and Scala collections */ 122 import uk.ac.ox.cs.rsacomb.implicits.RDFox._
123 import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._
227 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ 124 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._
228 125
126
229 /** OWLOntology based on input axioms 127 /** OWLOntology based on input axioms
230 * 128 *
231 * This is mainly used to instantiate a new reasoner to be used in 129 * This is mainly used to instantiate a new reasoner to be used in
@@ -234,10 +132,28 @@ class Ontology(
234 protected val ontology: OWLOntology = 132 protected val ontology: OWLOntology =
235 Ontology.manager.createOntology((axioms: List[OWLAxiom]).asJava) 133 Ontology.manager.createOntology((axioms: List[OWLAxiom]).asJava)
236 134
237 /** OWLAPI internal reasoner for ontology */ 135 /** OWLAPI internal reasoner for the current ontology */
238 protected val reasoner = 136 protected val reasoner =
239 (new StructuralReasonerFactory()).createReasoner(ontology) 137 (new StructuralReasonerFactory()).createReasoner(ontology)
240 138
139 /** Returns individuals in ontology */
140 val individuals: List[IRI] =
141 ontology.getIndividualsInSignature(Imports.INCLUDED).map(_.getIRI)
142
143 /** Returns literals in ontology */
144 val literals: List[Literal] =
145 axioms.collect { case a: OWLDataPropertyAssertionAxiom => a } .map(_.getObject)
146
147 /** Returns concepts in ontology */
148 val concepts: List[OWLClass] = ontology.getClassesInSignature()
149
150 /** Retrieve (object) roles in ontology */
151 val objroles: List[OWLObjectPropertyExpression] =
152 axioms.flatMap(_.objectPropertyExpressionsInSignature).distinct
153
154 /** Retrieve (data) roles in ontology */
155 val dataroles: List[OWLDataProperty] = origin.getDataPropertiesInSignature
156
241 /** Unsafe roles in the ontology 157 /** Unsafe roles in the ontology
242 * 158 *
243 * Unsafety conditions are the following: 159 * Unsafety conditions are the following:
@@ -280,12 +196,360 @@ class Ontology(
280 unsafe1 ++ unsafe2 196 unsafe1 ++ unsafe2
281 } 197 }
282 198
283 /** Compute the dependency graph for the ontology */ 199 /** Top axiomatization rules.
284 lazy val dependencyGraph: Ontology.DependencyGraph = 200 *
285 Ontology.dependencyGraph(axioms, datafiles, this.unsafe) 201 * For each concept/role *in the ontology file* introduce a rule to
202 * derive `owl:Thing`.
203 *
204 * @note this might not be enough in cases where data files contain
205 * concept/roles that are not in the ontology file. While this is
206 * non-standard, it is not forbidden either and may cause problems
207 * since not all individuals are considered part of `owl:Thing`.
208 *
209 * @note this is a naïve implementation of top axiomatization and
210 * might change in the future. The ideal solution would be for RDFox
211 * to take care of this, but at the time of writing this is not
212 * compatible with the way we are using the tool.
213 *
214 * TODO: use RDFox DSL
215 */
216 protected def topAxioms(tt: IRI): List[Rule] = {
217 val graph = TupleTableName.create(tt.getIRI)
218 Rule.create(
219 TupleTableAtom.create(graph, v"X", IRI.RDF_TYPE, IRI.THING),
220 TupleTableAtom.create(graph, v"X", IRI.RDF_TYPE, v"Y")
221 ) :: objroles.map(r => {
222 val name = r match {
223 case x: OWLObjectProperty => x.getIRI.getIRIString
224 case x: OWLObjectInverseOf =>
225 x.getInverse.getNamedProperty.getIRI.getIRIString :: Inverse
226 }
227 Rule.create(
228 List(
229 TupleTableAtom.create(graph, v"X", IRI.RDF_TYPE, IRI.THING),
230 TupleTableAtom.create(graph, v"Y", IRI.RDF_TYPE, IRI.THING)
231 ),
232 List(TupleTableAtom.create(graph, v"X", name, v"Y"))
233 )
234 }) ::: dataroles.map(r => {
235 val name = r.getIRI.getIRIString
236 Rule.create(
237 List(
238 TupleTableAtom.create(graph, v"X", IRI.RDF_TYPE, IRI.THING),
239 TupleTableAtom.create(graph, v"Y", IRI.RDF_TYPE, IRI.THING)
240 ),
241 List(TupleTableAtom.create(graph, v"X", name, v"Y"))
242 )
243 })
244 }
245
246 /** Equality axiomatization rules.
247 *
248 * Introduce reflexivity, simmetry and transitivity rules for a naïve
249 * equality axiomatization.
250 *
251 * @note that we are using a custom `congruent` predicate to indicate
252 * equality. This is to avoid interfering with the standard
253 * `owl:sameAs`.
254 *
255 * @note RDFox is able to handle equality in a "smart" way, but this
256 * behaviour is incompatible with other needed features like
257 * negation-as-failure and aggregates.
258 *
259 * @todo naïve substitution rules might not be very efficient. We
260 * should look into other ways of implementing this (e.g.,
261 * singularization).
262 */
263 protected def equalityAxioms(tt: IRI): List[Rule] = {
264 val graph = RDFoxGraph(tt)
265 val g = TupleTableName.create(tt.getIRI)
266 // Equality properties
267 val properties = List(
268 // Reflexivity
269 graph(v"X", RSA.CONGRUENT, v"X") :- graph(v"X", IRI.RDF_TYPE, IRI.THING),
270 // Simmetry
271 graph(v"Y", RSA.CONGRUENT, v"X") :- graph(v"X", RSA.CONGRUENT, v"Y"),
272 // Transitivity
273 graph(v"X", RSA.CONGRUENT, v"Z") :-
274 graph(v"X", RSA.CONGRUENT, v"Y") + graph(v"Y", RSA.CONGRUENT, v"Z"),
275 )
276 /* Equality substitution rules */
277 val conceptSub =
278 graph(v"Y", IRI.RDF_TYPE, v"Z") :-
279 graph(v"X", RSA.CONGRUENT, v"Y") + graph(v"X", IRI.RDF_TYPE, v"Z") +
280 FilterAtom.create(FunctionCall.notEqual(v"Z", RSA.NAMED))
281 val roleSub = objroles.flatMap(r => {
282 val name = r match {
283 case x: OWLObjectProperty => x.getIRI.getIRIString
284 case x: OWLObjectInverseOf =>
285 x.getInverse.getNamedProperty.getIRI.getIRIString :: Inverse
286 }
287 List(
288 graph(v"Z", name, v"Y") :-
289 graph(v"X", RSA.CONGRUENT, v"Z") + graph(v"X", name, v"Y"),
290 graph(v"Y", name, v"Z") :-
291 graph(v"X", RSA.CONGRUENT, v"Z") + graph(v"Y", name, v"X")
292 )
293 })
294 conceptSub :: properties ::: roleSub
295 }
296
297 /** Type wrapper representing a dependency graph for the ontology. */
298 type DependencyGraph = Graph[Resource, DiEdge]
299
300 /** Compute the RSA dependency graph for a set of axioms
301 *
302 * After calling this at least once, the materialisation M_{RSA} is
303 * available in RDFox in the named graph [[Ontology.RSACheck]].
304 * From this, the dependency graph can be derived by looking at the
305 * instances of the predicate `E`.
306 *
307 * Furthermore, the [[Ontology.RBoxReasoning]] named graph is
308 * populated with all relevant rules to reason over the RBox of the
309 * ontology.
310 *
311 * @return a map between the newly introduced constants and the
312 * corresponding input axioms.
313 *
314 * TODO: use RDFox DSL
315 */
316 lazy val dependencyGraph: (DependencyGraph, Map[String, OWLAxiom]) = {
317 /* Keep track of the mapping between nodes and axioms */
318 var nodemap = Map.empty[String, OWLAxiom]
319 /* Computation of the dependency graph is confined to the
320 * [[Ontology.RSACheck]] named graph.
321 */
322 val tt = TupleTableName.create(Ontology.RSACheck.getIRI)
323
324 /** Custom converter to generate dependency graph.
325 *
326 * @note this corresponds to Definition 3 in the RSA paper.
327 */
328 object RSACheckConverter extends RDFoxConverter {
329 import org.semanticweb.owlapi.model.{
330 OWLClassExpression,
331 OWLObjectSomeValuesFrom,
332 OWLDataSomeValuesFrom
333 }
334
335 override val graph = tt
336 override def convert(
337 expr: OWLClassExpression,
338 term: Term,
339 unsafe: List[OWLObjectPropertyExpression],
340 skolem: SkolemStrategy,
341 suffix: RSASuffix
342 )(implicit fresh: DataFactory): Shards =
343 (expr, skolem) match {
344
345 case (e: OWLObjectSomeValuesFrom, c: Constant) => {
346 nodemap.update(c.iri.getIRI, c.axiom)
347 val (res, ext) =
348 super.convert(e, term, unsafe, skolem, suffix)(fresh)
349 if (unsafe contains e.getProperty)
350 (RSA.PE(tt)(term, c.iri) :: RSA.U(tt)(c.iri) :: res, ext)
351 else
352 (RSA.PE(tt)(term, c.iri) :: res, ext)
353 }
354
355 //case (e: OWLDataSomeValuesFrom, c: Constant) => {
356 // nodemap.update(c.iri.getIRI, c.axiom)
357 // val (res, ext) =
358 // super.convert(e, term, unsafe, skolem, suffix)(fresh)
359 // if (unsafe contains e.getProperty)
360 // (RSA.PE(tt)(term, c.iri) :: RSA.U(tt)(c.iri) :: res, ext)
361 // else
362 // (RSA.PE(tt)(term, c.iri) :: res, ext)
363 //}
364
365 case _ => super.convert(expr, term, unsafe, skolem, suffix)(fresh)
366 }
367 }
286 368
287 /** RSA check */ 369 /* Ontology convertion into LP rules */
288 lazy val isRSA: Boolean = ??? 370 val result = axioms.map(a =>
371 RSACheckConverter.convert(a, v"X", unsafe, new Constant(a), Empty)
372 )
373 val datalog = result.unzip
374 val facts = datalog._1.flatten
375 var rules = datalog._2.flatten
376
377 /* Open connection with RDFox */
378 val (server, data) = RDFoxUtil.openConnection(Ontology.DataStore)
379 /* Upload data from data files */
380 RDFoxUtil.addData(data, Ontology.RSACheck, datafiles: _*)
381 /* Top/equality axiomatization */
382 RDFoxUtil.updateData(data,
383 s"""
384 INSERT {
385 GRAPH ${Ontology.RSACheck} { ?X a ${IRI.THING} }
386 } WHERE {
387 GRAPH ${Ontology.RSACheck} { ?X ?Y ?Z }
388 }
389 """
390 )
391 RDFoxUtil.updateData(data,
392 s"""
393 INSERT {
394 GRAPH ${Ontology.RSACheck} { ?Z a ${IRI.THING} }
395 } WHERE {
396 GRAPH ${Ontology.RSACheck} { ?X ?Y ?Z }.
397 FILTER( ?Y != a )
398 }
399 """
400 )
401 RDFoxUtil.addRules(data,
402 topAxioms(Ontology.RSACheck) ++ equalityAxioms(Ontology.RSACheck)
403 )
404 /* Introduce `rsacomb:Named` concept */
405 /* From data */
406 RDFoxUtil.updateData(data,
407 s"""
408 INSERT {
409 GRAPH ${Ontology.RSACheck} { ?X a ${RSA.NAMED} }
410 } WHERE {
411 GRAPH ${Ontology.RSACheck} { ?X a ${IRI.THING} }
412 }
413 """
414 )
415 /* From ontology */
416 val named = individuals.map(RSA.Named(Ontology.RSACheck)(_))
417 RDFoxUtil.addFacts(data, Ontology.RSACheck, named)
418 /* Add rule to build dependency graph */
419 rules = Rule.create(
420 RSA.E(tt)(v"X", v"Y"),
421 RSA.PE(tt)(v"X", v"Y"),
422 RSA.U(tt)(v"X"),
423 RSA.U(tt)(v"Y")
424 ) :: rules
425 /* Add logic program */
426 RDFoxUtil.addFacts(data, Ontology.RSACheck, facts)
427 RDFoxUtil.addRules(data, rules)
428
429 /* Build the dependency graph */
430 val query = s"SELECT ?X ?Y WHERE { graph ${Ontology.RSACheck} { ?X ${RSA("E")} ?Y }"
431 val answers = RDFoxUtil.submitQuery(data, query, RSA.Prefixes).getOrElse(RDFoxUtil.QueryAnswers())
432 var edges: Seq[DiEdge[Resource]] =
433 answers.collect { case (_, Seq(n1, n2)) => n1 ~> n2 }
434 val graph = Graph(edges: _*)
435
436 /* Setup RBox reasoner */
437 RDFoxUtil.addAxioms(data, Ontology.RBoxProxy, Ontology.RBoxReasoning, axioms)
438 /* Add reflexive `subObjectProperty`s */
439 val refls = objroles.map(r => Ontology.factory.getOWLSubObjectPropertyOfAxiom(r, r))
440 RDFoxUtil.addAxioms(data, Ontology.RBoxProxy, Ontology.RBoxReasoning, refls)
441 /* Add RBox reasoning rules */
442 RDFoxUtil.addRules(data, RSA.RBoxReasoning(Ontology.RBoxReasoning))
443
444 /* Close connection to RDFox */
445 RDFoxUtil.closeConnection(server, data)
446
447 (graph, nodemap)
448 }
449
450 /** Check whether the ontology is RSA
451 *
452 * The following checks are performed:
453 * 1) The ontology is Horn-ALCHOIQ
454 * 1) The dependency graph is an oriented forest
455 * 2) The ontology is equality safe.
456 *
457 * @note at the moment this assumes that the ontology is normalized.
458 *
459 * @see Def.3 in RSA paper for a formal definition of these concepts.
460 */
461 lazy val isRSA: Boolean = {
462 this.isHornALCHOIQ && {
463 val (graph,nodemap) = dependencyGraph
464 val (server,data) = RDFoxUtil.openConnection(Ontology.DataStore)
465 /* The dependecy graph is an oriented forest, i.e., is
466 * an *acyclic* graph with max in-degree 1.
467 */
468 val check1 = graph.isAcyclic && graph.nodes.forall(_.inDegree <= 1)
469 /* Equality safety: condition 1 */
470 val check2 = RDFoxUtil.submitQuery(data, s"""
471 ASK {
472 graph ${Ontology.RSACheck} { ?w ${RSA.CONGRUENT} ?t } .
473 filter ( ?w != ?t ) .
474 graph ${Ontology.RSACheck} { ?t ?r [ a ${RSA.U} ] } .
475 graph ${Ontology.RBoxReasoning} {
476 ?r rdfs:subPropertyOf [ owl:inverseOf ?s ] .
477 ?x rdf:type owl:Restriction ;
478 owl:onProperty ?s ;
479 owl:maxQualifiedCardinality "1"^^xsd:nonNegativeInteger ;
480 owl:onClass ?b .
481 ?a rdfs:subClassOf ?b .
482 } .
483 }
484 """).get
485 /* Equality safety: condition 2 */
486 val check3 = RDFoxUtil.submitQuery(data, s"""
487 ASK {
488 graph ${Ontology.RSACheck} {
489 ?u ?s ?a ; a ${RSA.U} .
490 ?a ?r ?u ; a ${RSA.NI} .
491 } .
492 graph ${Ontology.RBoxReasoning} {
493 ?r rdfs:subPropertyOf ?r1 .
494 ?r1 ${RSA("subPropertyOfTrans")} ?t .
495 ?t owl:inverseOf ?ti .
496 ?s rdfs:subPropertyOf ?s1 .
497 ?s1 ${RSA("subPropertyOfTrans")} ?ti .
498 }
499 }
500 """).get
501 check1 && check2.isEmpty && check3.isEmpty
502 }
503 }
504
505 /** Checks whether the ontology is Horn-ALCHOIQ.
506 *
507 * @note at the moment this assumes that the ontology is normalized.
508 *
509 * @see [[uk.ac.ox.cs.rsacomb.ontology.Ontology.normalize]]
510 *
511 * TODO: make the function independed of normalization, or
512 * alternatively force normalization.
513 */
514 lazy val isHornALCHOIQ: Boolean =
515 axioms.forall(axiom =>
516 axiom match {
517 case a: OWLSubClassOfAxiom => {
518 val sub = a.getSubClass.getNNF
519 val sup = a.getSuperClass.getNNF
520 (sub, sup) match {
521 case (sub: OWLObjectAllValuesFrom, _) => false
522 case (sub: OWLDataAllValuesFrom, _) => false
523 case (_, sup: OWLDataAllValuesFrom) => false
524 case (sub: OWLObjectMinCardinality, _) if sub.getCardinality >= 2 =>
525 false
526 case (sub: OWLDataMinCardinality, _) if sub.getCardinality >= 2 =>
527 false
528 case (_, sup: OWLObjectMinCardinality) if sup.getCardinality >= 2 =>
529 false
530 case (_, sup: OWLDataMinCardinality) if sup.getCardinality >= 2 =>
531 false
532 case (sub: OWLObjectMaxCardinality, _) => false
533 case (sub: OWLDataMaxCardinality, _) => false
534 case (_, sup: OWLObjectMaxCardinality) if sup.getCardinality >= 2 =>
535 false
536 case (_, sup: OWLDataMaxCardinality) if sup.getCardinality >= 1 =>
537 false
538 case (_, sup: OWLObjectOneOf) if sup.getIndividuals.length > 2 =>
539 false
540 case (sub: OWLObjectHasSelf, _) => false
541 case (_, sup: OWLObjectHasSelf) => false
542 case (_, sup: OWLObjectUnionOf) => false
543 case _ => true
544 }
545 }
546 case a: OWLTransitiveObjectPropertyAxiom => false
547 case a: OWLReflexiveObjectPropertyAxiom => false
548 case a: OWLSubPropertyChainOfAxiom => false
549 case a: OWLAsymmetricObjectPropertyAxiom => false
550 case a => true
551 }
552 )
289 553
290 /** Normalize the ontology according to the given normalizer 554 /** Normalize the ontology according to the given normalizer
291 * 555 *
@@ -306,5 +570,5 @@ class Ontology(
306 * @return the result of the approximation. 570 * @return the result of the approximation.
307 */ 571 */
308 def approximate[T](approximation: Approximation[T]): T = 572 def approximate[T](approximation: Approximation[T]): T =
309 approximation.approximate(this) 573 approximation approximate this
310} 574}
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/util/Logger.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/util/Logger.scala
index 20fd4c3..4ac85f9 100644
--- a/src/main/scala/uk/ac/ox/cs/rsacomb/util/Logger.scala
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/util/Logger.scala
@@ -1,5 +1,5 @@
1/* 1/*
2 * Copyright 2020, 2021 KRR Oxford 2 * Copyright 2020-2022 KRR Oxford
3 * 3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License. 5 * you may not use this file except in compliance with the License.
@@ -19,25 +19,12 @@ package uk.ac.ox.cs.rsacomb.util
19import java.util.Calendar 19import java.util.Calendar
20import java.text.SimpleDateFormat 20import java.text.SimpleDateFormat
21import java.io.PrintStream 21import java.io.PrintStream
22
22import uk.ac.ox.cs.rsacomb.sparql.{ConjunctiveQuery, ConjunctiveQueryAnswers} 23import uk.ac.ox.cs.rsacomb.sparql.{ConjunctiveQuery, ConjunctiveQueryAnswers}
23 24
24/** Simple logger */ 25/** Simple logger */
25object Logger { 26object Logger {
26 27
27 /** Main directory for logger output for the current run */
28 val dir = {
29 val timestamp = (new SimpleDateFormat("yyyyMMddHHmmss")).format(
30 Calendar.getInstance().getTime
31 )
32 os.pwd / s"rsacomb-$timestamp"
33 }
34
35 /** Output stream for the logger. */
36 var output: PrintStream = System.out
37
38 /** Path to answers output file */
39 var answers: os.Path = dir / "answers.json"
40
41 /** Logger levels (i.e., verbosity of output) */ 28 /** Logger levels (i.e., verbosity of output) */
42 sealed abstract class Level(val level: Int, val name: String) 29 sealed abstract class Level(val level: Int, val name: String)
43 extends Ordered[Level] { 30 extends Ordered[Level] {
@@ -52,6 +39,20 @@ object Logger {
52 /** Currend logger level */ 39 /** Currend logger level */
53 var level: Level = DEBUG 40 var level: Level = DEBUG
54 41
42 /** Directory for logger output for the current run */
43 val dir = {
44 val timestamp = (new SimpleDateFormat("yyyyMMddHHmmss")).format(
45 Calendar.getInstance().getTime
46 )
47 os.pwd / s"rsacomb-$timestamp"
48 }
49
50 /** Output stream for the logger used by [[Logger.print]] */
51 var output: PrintStream = System.out
52
53 /** Path to answers output file */
54 var answers: os.Path = dir / "answers.json"
55
55 /** Print a line padded with logger level and timestamp. 56 /** Print a line padded with logger level and timestamp.
56 * 57 *
57 * @param str object to be printed. 58 * @param str object to be printed.
@@ -101,7 +102,7 @@ object Logger {
101 result 102 result
102 } 103 }
103 104
104 /** Generate simulation scripts for current run 105 /** Generate RDFox simulation scripts for current run.
105 * 106 *
106 * @param data data files to be imported. 107 * @param data data files to be imported.
107 * @param queries collection of executed queries. 108 * @param queries collection of executed queries.