diff options
author | Federico Igne <federico.igne@cs.ox.ac.uk> | 2020-12-09 11:20:37 +0000 |
---|---|---|
committer | Federico Igne <federico.igne@cs.ox.ac.uk> | 2020-12-09 11:20:37 +0000 |
commit | 2ec1731da852ee17432c909ed2864f40449c2665 (patch) | |
tree | cefb51f4c5f835cf6f19268971af16842f2fe152 | |
parent | 8af52541d00dc576bac20f9366be979a94d542c1 (diff) | |
parent | 43327d8a986e41ba26b6f2b5c911646c7c9a254b (diff) | |
download | RSAComb-2ec1731da852ee17432c909ed2864f40449c2665.tar.gz RSAComb-2ec1731da852ee17432c909ed2864f40449c2665.zip |
Merge branch 'logger' into master
5 files changed, 298 insertions, 143 deletions
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala index c7ace0f..eaacedc 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala | |||
@@ -10,7 +10,7 @@ import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery | |||
10 | import tech.oxfordsemantic.jrdfox.logic.expression.{IRI, Term} | 10 | import tech.oxfordsemantic.jrdfox.logic.expression.{IRI, Term} |
11 | 11 | ||
12 | /* Local imports */ | 12 | /* Local imports */ |
13 | import util.{RDFoxUtil, RSA} | 13 | import util.{Logger, RDFoxUtil, RSA} |
14 | import sparql.ConjunctiveQuery | 14 | import sparql.ConjunctiveQuery |
15 | 15 | ||
16 | object RSAComb extends App { | 16 | object RSAComb extends App { |
@@ -53,15 +53,38 @@ object RSAComb extends App { | |||
53 | 53 | ||
54 | val ontology = RSAOntology(ontoPaths: _*) | 54 | val ontology = RSAOntology(ontoPaths: _*) |
55 | if (ontology.isRSA) { | 55 | if (ontology.isRSA) { |
56 | //println("ONTOLOGY IS RSA") | 56 | |
57 | Logger print "Ontology is RSA!" | ||
57 | 58 | ||
58 | /** Read SPARQL query from file */ | 59 | /** Read SPARQL query from file */ |
59 | val source = io.Source.fromFile(queryPath.getAbsoluteFile) | 60 | val strQuery = RDFoxUtil.loadQueryFromFile(queryPath.getAbsoluteFile) |
60 | val query = source.getLines mkString "\n" | 61 | val query = ConjunctiveQuery parse strQuery |
61 | source.close() | 62 | |
63 | query match { | ||
64 | case Some(query) => { | ||
65 | val answers = ontology ask query | ||
66 | Logger.print(s"$answers", Logger.QUIET) | ||
67 | Logger print s"Number of answer: ${answers.length}" | ||
68 | |||
69 | val unfiltered = ontology askUnfiltered query | ||
70 | val percentage = unfiltered match { | ||
71 | case Some(u) => { | ||
72 | Logger.print( | ||
73 | s"Number of spurious answers: ${u.length}.", | ||
74 | Logger.DEBUG | ||
75 | ) | ||
76 | if (u.length > 0) (1 - answers.length / u.length) * 100 else 0 | ||
77 | } | ||
78 | case None => 0 | ||
79 | } | ||
80 | Logger.print( | ||
81 | s"Percentage of spurious answers: $percentage%", | ||
82 | Logger.DEBUG | ||
83 | ) | ||
84 | } | ||
85 | case None => | ||
86 | throw new RuntimeException("Submitted query is not conjunctive") | ||
87 | } | ||
62 | 88 | ||
63 | /* Compute answers to query */ | ||
64 | val answers = ConjunctiveQuery.parse(query).map(ontology ask _) | ||
65 | answers map (_.toString) foreach println | ||
66 | } | 89 | } |
67 | } | 90 | } |
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 0fb6c96..0f1cd5e 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala | |||
@@ -56,6 +56,7 @@ import uk.ac.ox.cs.rsacomb.converter._ | |||
56 | import uk.ac.ox.cs.rsacomb.suffix._ | 56 | import uk.ac.ox.cs.rsacomb.suffix._ |
57 | import uk.ac.ox.cs.rsacomb.sparql._ | 57 | import uk.ac.ox.cs.rsacomb.sparql._ |
58 | import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} | 58 | import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} |
59 | import uk.ac.ox.cs.rsacomb.util.Logger | ||
59 | 60 | ||
60 | object RSAOntology { | 61 | object RSAOntology { |
61 | 62 | ||
@@ -109,6 +110,10 @@ class RSAOntology(val ontology: OWLOntology) { | |||
109 | 110 | ||
110 | val axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox | 111 | val axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox |
111 | 112 | ||
113 | Logger.print(s"Original TBox: ${tbox.length} axioms", Logger.DEBUG) | ||
114 | Logger.print(s"Original RBox: ${rbox.length} axioms", Logger.DEBUG) | ||
115 | Logger.print(s"Original ABox: ${abox.length} axioms", Logger.DEBUG) | ||
116 | |||
112 | /* Retrieve individuals in the original ontology | 117 | /* Retrieve individuals in the original ontology |
113 | */ | 118 | */ |
114 | val individuals: List[IRI] = | 119 | val individuals: List[IRI] = |
@@ -149,87 +154,91 @@ class RSAOntology(val ontology: OWLOntology) { | |||
149 | * why the ontology might not be RSA. This could help a second | 154 | * why the ontology might not be RSA. This could help a second |
150 | * step of approximation of an Horn-ALCHOIQ to RSA | 155 | * step of approximation of an Horn-ALCHOIQ to RSA |
151 | */ | 156 | */ |
152 | lazy val isRSA: Boolean = { | 157 | lazy val isRSA: Boolean = Logger.timed( |
153 | 158 | { | |
154 | val unsafe = this.unsafeRoles | 159 | val unsafe = this.unsafeRoles |
155 | 160 | ||
156 | /* DEBUG: print rules in DL syntax and unsafe roles */ | 161 | // val renderer = new DLSyntaxObjectRenderer() |
157 | //val renderer = new DLSyntaxObjectRenderer() | 162 | // println() |
158 | //println("\nDL rules:") | 163 | // println("Unsafe roles:") |
159 | //axioms.foreach(x => println(renderer.render(x))) | 164 | // println(unsafe) |
160 | //println("\nUnsafe roles:") | 165 | // println() |
161 | //println(unsafe) | 166 | // println("DL rules:") |
162 | 167 | // tbox.foreach(x => println(renderer.render(x))) | |
163 | object RSAConverter extends RDFoxConverter { | 168 | |
164 | 169 | object RSAConverter extends RDFoxConverter { | |
165 | override def convert( | 170 | |
166 | expr: OWLClassExpression, | 171 | override def convert( |
167 | term: Term, | 172 | expr: OWLClassExpression, |
168 | unsafe: List[OWLObjectPropertyExpression], | 173 | term: Term, |
169 | skolem: SkolemStrategy, | 174 | unsafe: List[OWLObjectPropertyExpression], |
170 | suffix: RSASuffix | 175 | skolem: SkolemStrategy, |
171 | ): Shards = | 176 | suffix: RSASuffix |
172 | (expr, skolem) match { | 177 | ): Shards = |
173 | 178 | (expr, skolem) match { | |
174 | case (e: OWLObjectSomeValuesFrom, c: Constant) | 179 | |
175 | if unsafe contains e.getProperty => { | 180 | case (e: OWLObjectSomeValuesFrom, c: Constant) |
176 | val (res, ext) = super.convert(e, term, unsafe, skolem, suffix) | 181 | if unsafe contains e.getProperty => { |
177 | (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext) | 182 | val (res, ext) = super.convert(e, term, unsafe, skolem, suffix) |
183 | (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext) | ||
184 | } | ||
185 | |||
186 | case (e: OWLDataSomeValuesFrom, c: Constant) | ||
187 | if unsafe contains e.getProperty => { | ||
188 | val (res, ext) = super.convert(e, term, unsafe, skolem, suffix) | ||
189 | (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext) | ||
190 | } | ||
191 | |||
192 | case _ => super.convert(expr, term, unsafe, skolem, suffix) | ||
178 | } | 193 | } |
179 | 194 | ||
180 | case (e: OWLDataSomeValuesFrom, c: Constant) | 195 | } |
181 | if unsafe contains e.getProperty => { | 196 | |
182 | val (res, ext) = super.convert(e, term, unsafe, skolem, suffix) | 197 | /* Ontology convertion into LP rules */ |
183 | (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext) | 198 | val term = RSAOntology.genFreshVariable() |
184 | } | 199 | val datalog = axioms |
185 | 200 | .map(a => RSAConverter.convert(a, term, unsafe, new Constant(a), Empty)) | |
186 | case _ => super.convert(expr, term, unsafe, skolem, suffix) | 201 | .unzip |
187 | } | 202 | val facts = datalog._1.flatten |
188 | 203 | val rules = datalog._2.flatten | |
189 | } | 204 | |
190 | 205 | //println("Datalog rules:") | |
191 | /* Ontology convertion into LP rules */ | 206 | //rules foreach println |
192 | val term = RSAOntology.genFreshVariable() | 207 | |
193 | val datalog = axioms | 208 | // Open connection with RDFox |
194 | .map(a => RSAConverter.convert(a, term, unsafe, new Constant(a), Empty)) | 209 | val (server, data) = RDFoxUtil.openConnection("RSACheck") |
195 | .unzip | 210 | |
196 | val facts = datalog._1.flatten | 211 | /* Add built-in rules |
197 | val rules = datalog._2.flatten | 212 | * TODO: substitute with RDFoxUtil.addRules |
198 | 213 | */ | |
199 | /* DEBUG: print datalog rules */ | 214 | data.importData( |
200 | //println("\nDatalog rules:") | 215 | UpdateType.ADDITION, |
201 | //rules.foreach(println) | 216 | RSA.Prefixes, |
202 | 217 | "rsa:E[?X,?Y] :- rsa:PE[?X,?Y], rsa:U[?X], rsa:U[?Y] ." | |
203 | // Open connection with RDFox | 218 | ) |
204 | val (server, data) = RDFoxUtil.openConnection("RSACheck") | ||
205 | |||
206 | /* Add built-in rules | ||
207 | * TODO: substitute with RDFoxUtil.addRules | ||
208 | */ | ||
209 | data.importData( | ||
210 | UpdateType.ADDITION, | ||
211 | RSA.Prefixes, | ||
212 | "rsa:E[?X,?Y] :- rsa:PE[?X,?Y], rsa:U[?X], rsa:U[?Y] ." | ||
213 | ) | ||
214 | |||
215 | /* Add ontology facts and rules */ | ||
216 | RDFoxUtil.addFacts(data, facts) | ||
217 | RDFoxUtil.addRules(data, rules) | ||
218 | |||
219 | /* Build graph */ | ||
220 | val graph = this.rsaGraph(data); | ||
221 | //println(graph) | ||
222 | |||
223 | // Close connection to RDFox | ||
224 | RDFoxUtil.closeConnection(server, data) | ||
225 | 219 | ||
226 | /* To check if the graph is tree-like we check for acyclicity in a | 220 | /* Add ontology facts and rules */ |
227 | * undirected graph. | 221 | RDFoxUtil.addFacts(data, facts) |
228 | * | 222 | RDFoxUtil.addRules(data, rules) |
229 | * TODO: Implement additional checks (taking into account equality) | 223 | |
230 | */ | 224 | /* Build graph */ |
231 | graph.isAcyclic | 225 | val graph = this.rsaGraph(data); |
232 | } | 226 | //println("Graph:") |
227 | //println(graph) | ||
228 | |||
229 | // Close connection to RDFox | ||
230 | RDFoxUtil.closeConnection(server, data) | ||
231 | |||
232 | /* To check if the graph is tree-like we check for acyclicity in a | ||
233 | * undirected graph. | ||
234 | * | ||
235 | * TODO: Implement additional checks (taking into account equality) | ||
236 | */ | ||
237 | graph.isAcyclic | ||
238 | }, | ||
239 | "RSA check", | ||
240 | Logger.DEBUG | ||
241 | ) | ||
233 | 242 | ||
234 | lazy val unsafeRoles: List[OWLObjectPropertyExpression] = { | 243 | lazy val unsafeRoles: List[OWLObjectPropertyExpression] = { |
235 | 244 | ||
@@ -289,16 +298,23 @@ class RSAOntology(val ontology: OWLOntology) { | |||
289 | ): Graph[Resource, UnDiEdge] = { | 298 | ): Graph[Resource, UnDiEdge] = { |
290 | val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }" | 299 | val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }" |
291 | val answers = RDFoxUtil.submitQuery(data, query, RSA.Prefixes).get | 300 | val answers = RDFoxUtil.submitQuery(data, query, RSA.Prefixes).get |
292 | var edges: Seq[UnDiEdge[Resource]] = answers.map { case Seq(n1, n2) => | 301 | var edges: Seq[UnDiEdge[Resource]] = |
293 | UnDiEdge(n1, n2) | 302 | answers.collect { case (_, Seq(n1, n2)) => UnDiEdge(n1, n2) } |
294 | } | ||
295 | Graph(edges: _*) | 303 | Graph(edges: _*) |
296 | } | 304 | } |
297 | 305 | ||
298 | def filteringProgram(query: ConjunctiveQuery): FilteringProgram = | 306 | def filteringProgram(query: ConjunctiveQuery): FilteringProgram = |
299 | new FilteringProgram(query, individuals ++ literals) | 307 | Logger.timed( |
308 | new FilteringProgram(query, individuals ++ literals), | ||
309 | "Generating filtering program", | ||
310 | Logger.DEBUG | ||
311 | ) | ||
300 | 312 | ||
301 | lazy val canonicalModel = new CanonicalModel(this) | 313 | lazy val canonicalModel = Logger.timed( |
314 | new CanonicalModel(this), | ||
315 | "Generating canonical model program", | ||
316 | Logger.DEBUG | ||
317 | ) | ||
302 | 318 | ||
303 | // TODO: the following functions needs testing | 319 | // TODO: the following functions needs testing |
304 | def confl( | 320 | def confl( |
@@ -329,27 +345,42 @@ class RSAOntology(val ontology: OWLOntology) { | |||
329 | * @param query query to execute | 345 | * @param query query to execute |
330 | * @return a collection of answers | 346 | * @return a collection of answers |
331 | */ | 347 | */ |
332 | def ask(query: ConjunctiveQuery): ConjunctiveQueryAnswers = { | 348 | def ask(query: ConjunctiveQuery): ConjunctiveQueryAnswers = Logger.timed( |
333 | import implicits.JavaCollections._ | 349 | { |
334 | val (server, data) = RDFoxUtil.openConnection(RSAOntology.DataStore) | 350 | import implicits.JavaCollections._ |
335 | val filter = this.filteringProgram(query) | 351 | val (server, data) = RDFoxUtil.openConnection(RSAOntology.DataStore) |
336 | RDFoxUtil.addRules(data, this.canonicalModel.rules) | 352 | val canon = this.canonicalModel |
337 | RDFoxUtil.addFacts(data, this.canonicalModel.facts) | 353 | val filter = this.filteringProgram(query) |
338 | RDFoxUtil.addRules(data, filter.rules) | 354 | |
339 | RDFoxUtil.addFacts(data, filter.facts) | 355 | Logger print s"Canonical model: ${canon.rules.length} rules" |
340 | val answers = RDFoxUtil | 356 | RDFoxUtil.addRules(data, this.canonicalModel.rules) |
341 | .submitQuery( | 357 | |
342 | data, | 358 | Logger print s"Canonical model: ${canon.facts.length} facts" |
343 | RDFoxUtil.buildDescriptionQuery("Ans", query.answer.size), | 359 | RDFoxUtil.addFacts(data, this.canonicalModel.facts) |
344 | RSA.Prefixes | 360 | |
345 | ) | 361 | RDFoxUtil printStatisticsFor data |
346 | .map( | 362 | |
347 | new ConjunctiveQueryAnswers(query.bcq, query.variables, _) | 363 | Logger print s"Filtering program: ${filter.rules.length} rules" |
348 | ) | 364 | RDFoxUtil.addRules(data, filter.rules) |
349 | .get | 365 | |
350 | RDFoxUtil.closeConnection(server, data) | 366 | Logger print s"Filtering program: ${filter.facts.length} facts" |
351 | answers | 367 | RDFoxUtil.addFacts(data, filter.facts) |
352 | } | 368 | |
369 | RDFoxUtil printStatisticsFor data | ||
370 | |||
371 | val answers = { | ||
372 | val ans = RDFoxUtil.buildDescriptionQuery("Ans", query.answer.size) | ||
373 | RDFoxUtil | ||
374 | .submitQuery(data, ans, RSA.Prefixes) | ||
375 | .map(new ConjunctiveQueryAnswers(query.bcq, query.variables, _)) | ||
376 | .get | ||
377 | } | ||
378 | RDFoxUtil.closeConnection(server, data) | ||
379 | answers | ||
380 | }, | ||
381 | "Answers computation", | ||
382 | Logger.DEBUG | ||
383 | ) | ||
353 | 384 | ||
354 | /** Query the RDFox data store used for query answering. | 385 | /** Query the RDFox data store used for query answering. |
355 | * | 386 | * |
@@ -370,13 +401,29 @@ class RSAOntology(val ontology: OWLOntology) { | |||
370 | query: String, | 401 | query: String, |
371 | prefixes: Prefixes = new Prefixes(), | 402 | prefixes: Prefixes = new Prefixes(), |
372 | opts: ju.Map[String, String] = new ju.HashMap[String, String]() | 403 | opts: ju.Map[String, String] = new ju.HashMap[String, String]() |
373 | ): Option[Seq[Seq[Resource]]] = { | 404 | ): Option[Seq[(Long, Seq[Resource])]] = { |
374 | val (server, data) = RDFoxUtil.openConnection(RSAOntology.DataStore) | 405 | val (server, data) = RDFoxUtil.openConnection(RSAOntology.DataStore) |
375 | val answers = RDFoxUtil.submitQuery(data, query, prefixes, opts) | 406 | val answers = RDFoxUtil.submitQuery(data, query, prefixes, opts) |
376 | RDFoxUtil.closeConnection(server, data) | 407 | RDFoxUtil.closeConnection(server, data) |
377 | answers | 408 | answers |
378 | } | 409 | } |
379 | 410 | ||
411 | /** Returns set of unfiltered answers. | ||
412 | * | ||
413 | * This is equivalent to quering just the canonical model. | ||
414 | * | ||
415 | * @note this method does not load any data to RDFox. The return | ||
416 | * value is considered well defined only after | ||
417 | * [[uk.ac.ox.cs.rsacomb.RSAOntology.ask RSAOntology.ask]] | ||
418 | * for the corresponding query has been called. | ||
419 | */ | ||
420 | def askUnfiltered( | ||
421 | cq: ConjunctiveQuery | ||
422 | ): Option[Seq[(Long, Seq[Resource])]] = { | ||
423 | val query = RDFoxUtil.buildDescriptionQuery("QM", cq.variables.length) | ||
424 | queryDataStore(cq, query, RSA.Prefixes) | ||
425 | } | ||
426 | |||
380 | def self(axiom: OWLSubClassOfAxiom): Set[Term] = { | 427 | def self(axiom: OWLSubClassOfAxiom): Set[Term] = { |
381 | // Assuming just one role in the signature of a T5 axiom | 428 | // Assuming just one role in the signature of a T5 axiom |
382 | val role = axiom.objectPropertyExpressionsInSignature(0) | 429 | val role = axiom.objectPropertyExpressionsInSignature(0) |
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/sparql/ConjunctiveQueryAnswers.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/sparql/ConjunctiveQueryAnswers.scala index 3db5500..7edc867 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/sparql/ConjunctiveQueryAnswers.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/sparql/ConjunctiveQueryAnswers.scala | |||
@@ -19,9 +19,15 @@ import tech.oxfordsemantic.jrdfox.logic.expression.{ | |||
19 | class ConjunctiveQueryAnswers( | 19 | class ConjunctiveQueryAnswers( |
20 | bcq: Boolean, | 20 | bcq: Boolean, |
21 | val variables: Seq[Variable], | 21 | val variables: Seq[Variable], |
22 | val answers: Seq[Seq[Resource]] | 22 | val answers: Seq[(Long, Seq[Resource])] |
23 | ) { | 23 | ) { |
24 | 24 | ||
25 | /** Returns number of distinct answers. */ | ||
26 | val length: Int = if (bcq) 0 else answers.length | ||
27 | |||
28 | /** Returns number of answers taking into account multiplicity. */ | ||
29 | val lengthWithMultiplicity: Long = answers.map(_._1).sum | ||
30 | |||
25 | override def toString(): String = | 31 | override def toString(): String = |
26 | if (bcq) { | 32 | if (bcq) { |
27 | if (answers.isEmpty) "FALSE" else "TRUE" | 33 | if (answers.isEmpty) "FALSE" else "TRUE" |
@@ -31,11 +37,15 @@ class ConjunctiveQueryAnswers( | |||
31 | else { | 37 | else { |
32 | val header = variables map (_.getName) mkString "\t" | 38 | val header = variables map (_.getName) mkString "\t" |
33 | val body = answers | 39 | val body = answers |
34 | .map(_.map { | 40 | .map( |
35 | case x: IRI => x.getIRI | 41 | _._2 |
36 | case x: Literal => x.getLexicalForm | 42 | .map { |
37 | case x => x.toString | 43 | case x: IRI => x.getIRI |
38 | }.mkString("\t")) | 44 | case x: Literal => x.getLexicalForm |
45 | case x => x.toString | ||
46 | } | ||
47 | .mkString("\t") | ||
48 | ) | ||
39 | .mkString("\n") | 49 | .mkString("\n") |
40 | s"$header\n$body" | 50 | s"$header\n$body" |
41 | } | 51 | } |
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 new file mode 100644 index 0000000..74797a2 --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/util/Logger.scala | |||
@@ -0,0 +1,45 @@ | |||
1 | package uk.ac.ox.cs.rsacomb.util | ||
2 | |||
3 | import java.util.Calendar | ||
4 | import java.io.PrintStream | ||
5 | |||
6 | /** Rough implementation of a logger. | ||
7 | * | ||
8 | * This is a WIP class for debugging and benchmarking. | ||
9 | */ | ||
10 | object Logger { | ||
11 | |||
12 | /** Output stream for the logger. */ | ||
13 | var output: PrintStream = System.out | ||
14 | |||
15 | /** Logger levels (i.e., verbosity of output) */ | ||
16 | sealed abstract class Level(val level: Int, val name: String) | ||
17 | extends Ordered[Level] { | ||
18 | def compare(that: Level) = this.level - that.level | ||
19 | override def toString = name | ||
20 | } | ||
21 | case object QUIET extends Level(0, "normal") | ||
22 | case object NORMAL extends Level(1, "normal") | ||
23 | case object DEBUG extends Level(2, "debug") | ||
24 | case object VERBOSE extends Level(3, "verbose") | ||
25 | |||
26 | /** Currend logger level */ | ||
27 | var level: Level = DEBUG | ||
28 | |||
29 | def print(str: Any, lvl: Level = NORMAL): Unit = { | ||
30 | if (lvl <= level) { | ||
31 | val time = Calendar.getInstance.getTime | ||
32 | output println s"[$lvl][$time] $str" | ||
33 | } | ||
34 | } | ||
35 | |||
36 | def timed[A](expr: => A, desc: String = "", lvl: Level = NORMAL): A = { | ||
37 | val t0 = System.currentTimeMillis() | ||
38 | print(s"$desc (START)", lvl) | ||
39 | val result = expr | ||
40 | val t1 = System.currentTimeMillis() | ||
41 | print(s"$desc (END): ${(t1 - t0).toFloat / 1000}s", lvl) | ||
42 | result | ||
43 | } | ||
44 | |||
45 | } | ||
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala index 9dd52cf..76f720c 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala | |||
@@ -1,5 +1,6 @@ | |||
1 | package uk.ac.ox.cs.rsacomb.util | 1 | package uk.ac.ox.cs.rsacomb.util |
2 | 2 | ||
3 | import java.io.File | ||
3 | import java.io.StringReader | 4 | import java.io.StringReader |
4 | import tech.oxfordsemantic.jrdfox.Prefixes | 5 | import tech.oxfordsemantic.jrdfox.Prefixes |
5 | import tech.oxfordsemantic.jrdfox.client.{ | 6 | import tech.oxfordsemantic.jrdfox.client.{ |
@@ -20,6 +21,7 @@ import tech.oxfordsemantic.jrdfox.logic.datalog.{ | |||
20 | import tech.oxfordsemantic.jrdfox.logic.expression.{Resource} | 21 | import tech.oxfordsemantic.jrdfox.logic.expression.{Resource} |
21 | import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery | 22 | import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery |
22 | import uk.ac.ox.cs.rsacomb.suffix.Nth | 23 | import uk.ac.ox.cs.rsacomb.suffix.Nth |
24 | import uk.ac.ox.cs.rsacomb.util.Logger | ||
23 | 25 | ||
24 | /** A collection of helper methods for RDFox */ | 26 | /** A collection of helper methods for RDFox */ |
25 | object RDFoxUtil { | 27 | object RDFoxUtil { |
@@ -35,8 +37,8 @@ object RDFoxUtil { | |||
35 | /** Type alias for a collection of answers to a | 37 | /** Type alias for a collection of answers to a |
36 | * [[tech.oxfordsemantic.jrdfox.logic.sparql.statement.Query]]. | 38 | * [[tech.oxfordsemantic.jrdfox.logic.sparql.statement.Query]]. |
37 | */ | 39 | */ |
38 | private type QueryAnswers = Seq[Seq[Resource]] | 40 | private type QueryAnswers = Seq[(Long, Seq[Resource])] |
39 | private def QueryAnswers() = List.empty[Seq[Resource]] | 41 | private def QueryAnswers() = List.empty[(Long, Seq[Resource])] |
40 | 42 | ||
41 | /** Type alias for <option => value> RDFox options. */ | 43 | /** Type alias for <option => value> RDFox options. */ |
42 | private type RDFoxOpts = java.util.Map[String, String] | 44 | private type RDFoxOpts = java.util.Map[String, String] |
@@ -67,18 +69,21 @@ object RDFoxUtil { | |||
67 | (server, data) | 69 | (server, data) |
68 | } | 70 | } |
69 | 71 | ||
70 | /** Gather statistics from RDFox datastore. | 72 | /** Prints statistics from RDFox datastore. |
73 | * | ||
74 | * Prints something only when Logger level is set to DEBUG or more. | ||
71 | * | 75 | * |
72 | * @see [[https://docs.oxfordsemantic.tech/programmatic-access-APIs.html#in-depth-diagnostic-information]] | 76 | * @see [[https://docs.oxfordsemantic.tech/programmatic-access-APIs.html#in-depth-diagnostic-information]] |
73 | * and [[https://docs.oxfordsemantic.tech/programmatic-access-APIs.html#managing-statistics]] | 77 | * and [[https://docs.oxfordsemantic.tech/programmatic-access-APIs.html#managing-statistics]] |
74 | * for more ways of gathering diagnostics from RDFox. | 78 | * for more ways of gathering diagnostics from RDFox. |
75 | */ | 79 | */ |
76 | def gatherStatistics(data: DataStoreConnection): String = { | 80 | def printStatisticsFor(data: DataStoreConnection): Unit = { |
77 | val info = data.getComponentInfo(true) | 81 | val info = data.getComponentInfo(true) |
78 | s"${info.getName}: ${info.getPropertyValues}" | 82 | val stats = s"${info.getName}: ${info.getPropertyValues}" |
79 | .replaceAll("\\{", "{\n ") | 83 | .replaceAll("\\{", "{\n ") |
80 | .replaceAll(", ", ",\n ") | 84 | .replaceAll(", ", ",\n ") |
81 | .replaceAll("\\}", "\n}") | 85 | .replaceAll("\\}", "\n}") |
86 | Logger.print(stats, Logger.DEBUG) | ||
82 | } | 87 | } |
83 | 88 | ||
84 | /** Adds a collection of rules to a data store. | 89 | /** Adds a collection of rules to a data store. |
@@ -87,7 +92,11 @@ object RDFoxUtil { | |||
87 | * @param rules collection of rules to be added to the data store | 92 | * @param rules collection of rules to be added to the data store |
88 | */ | 93 | */ |
89 | def addRules(data: DataStoreConnection, rules: Seq[Rule]): Unit = | 94 | def addRules(data: DataStoreConnection, rules: Seq[Rule]): Unit = |
90 | data addRules rules | 95 | Logger.timed( |
96 | data addRules rules, | ||
97 | "Loading rules", | ||
98 | Logger.DEBUG | ||
99 | ) | ||
91 | 100 | ||
92 | /** Adds a collection of facts to a data store. | 101 | /** Adds a collection of facts to a data store. |
93 | * | 102 | * |
@@ -95,12 +104,29 @@ object RDFoxUtil { | |||
95 | * @param facts collection of facts to be added to the data store | 104 | * @param facts collection of facts to be added to the data store |
96 | */ | 105 | */ |
97 | def addFacts(data: DataStoreConnection, facts: Seq[TupleTableAtom]): Unit = | 106 | def addFacts(data: DataStoreConnection, facts: Seq[TupleTableAtom]): Unit = |
98 | data.importData( | 107 | Logger.timed( |
99 | UpdateType.ADDITION, | 108 | data.importData( |
100 | RSA.Prefixes, | 109 | UpdateType.ADDITION, |
101 | facts.map(_.toString(Prefixes.s_emptyPrefixes)).mkString("", ".\n", ".") | 110 | RSA.Prefixes, |
111 | facts.map(_.toString(Prefixes.s_emptyPrefixes)).mkString("", ".\n", ".") | ||
112 | ), | ||
113 | "Loading facts", | ||
114 | Logger.DEBUG | ||
102 | ) | 115 | ) |
103 | 116 | ||
117 | /** Force materialization in RDFox. */ | ||
118 | def materialize(data: DataStoreConnection): Unit = | ||
119 | Logger.timed(data.updateMaterialization(), "Materialization", Logger.DEBUG) | ||
120 | |||
121 | /** Load SPARQL query from file. */ | ||
122 | def loadQueryFromFile(file: File): String = { | ||
123 | val source = io.Source.fromFile(file) | ||
124 | val query = source.getLines mkString "\n" | ||
125 | Logger print s"Loaded query:\n$query" | ||
126 | source.close() | ||
127 | query | ||
128 | } | ||
129 | |||
104 | /** Parse a SELECT query from a string in SPARQL format. | 130 | /** Parse a SELECT query from a string in SPARQL format. |
105 | * | 131 | * |
106 | * @param query the string containing the SPARQL query | 132 | * @param query the string containing the SPARQL query |
@@ -136,19 +162,23 @@ object RDFoxUtil { | |||
136 | data: DataStoreConnection, | 162 | data: DataStoreConnection, |
137 | query: SelectQuery, | 163 | query: SelectQuery, |
138 | opts: RDFoxOpts = RDFoxOpts() | 164 | opts: RDFoxOpts = RDFoxOpts() |
139 | ): QueryAnswers = { | 165 | ): QueryAnswers = Logger.timed( |
140 | val cursor = data.createCursor(query, opts) | 166 | { |
141 | var answers = QueryAnswers() | 167 | val cursor = data.createCursor(query, opts) |
142 | var mul = cursor.open() | 168 | var answers = QueryAnswers() |
143 | while (mul > 0) { | 169 | var mul = cursor.open() |
144 | val answer = | 170 | while (mul > 0) { |
145 | (0 until cursor.getArity).map(cursor.getResource(_)).toList | 171 | val answer = |
146 | answers = answer :: answers | 172 | (0 until cursor.getArity).map(cursor.getResource(_)).toList |
147 | mul = cursor.advance() | 173 | answers = (mul, answer) :: answers |
148 | } | 174 | mul = cursor.advance() |
149 | cursor.close(); | 175 | } |
150 | answers | 176 | cursor.close(); |
151 | } | 177 | answers |
178 | }, | ||
179 | "Answer query", | ||
180 | Logger.DEBUG | ||
181 | ) | ||
152 | 182 | ||
153 | /** Execute a query over a given datastore connection. | 183 | /** Execute a query over a given datastore connection. |
154 | * | 184 | * |
@@ -188,7 +218,7 @@ object RDFoxUtil { | |||
188 | .map(i => s"?S rsa:${pred :: Nth(i)} ?X$i .") | 218 | .map(i => s"?S rsa:${pred :: Nth(i)} ?X$i .") |
189 | .mkString("WHERE {\n", "\n", "\n}") | 219 | .mkString("WHERE {\n", "\n", "\n}") |
190 | } else { | 220 | } else { |
191 | s"ASK { ?X a rsa:$pred }" | 221 | s"ASK { ?X a rsa:${pred :: Nth(0)} }" |
192 | } | 222 | } |
193 | } | 223 | } |
194 | 224 | ||