diff options
author | Federico Igne <federico.igne@cs.ox.ac.uk> | 2021-02-04 18:48:56 +0000 |
---|---|---|
committer | Federico Igne <federico.igne@cs.ox.ac.uk> | 2021-02-04 18:48:56 +0000 |
commit | 5a7208dac91c31ffcec37955fff48f1046aac753 (patch) | |
tree | cd3dbe4c6cc3d529f35bce56fdbb084e564b9a1c | |
parent | 1cb2165ccbcd6b1210bee1dfb7b527b4d2440901 (diff) | |
download | RSAComb-5a7208dac91c31ffcec37955fff48f1046aac753.tar.gz RSAComb-5a7208dac91c31ffcec37955fff48f1046aac753.zip |
Add revised implementation of filtering program
4 files changed, 414 insertions, 6 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 b0b52c7..4243b7b 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala | |||
@@ -410,7 +410,7 @@ class RSAOntology(_ontology: File, val datafiles: File*) { | |||
410 | 410 | ||
411 | def filteringProgram(query: ConjunctiveQuery): FilteringProgram = | 411 | def filteringProgram(query: ConjunctiveQuery): FilteringProgram = |
412 | Logger.timed( | 412 | Logger.timed( |
413 | FilteringProgram(FilterType.FILTER_NAIVE)(query), | 413 | FilteringProgram(FilterType.REVISED)(query), |
414 | "Generating filtering program", | 414 | "Generating filtering program", |
415 | Logger.DEBUG | 415 | Logger.DEBUG |
416 | ) | 416 | ) |
@@ -491,7 +491,7 @@ class RSAOntology(_ontology: File, val datafiles: File*) { | |||
491 | //} | 491 | //} |
492 | 492 | ||
493 | val answers = { | 493 | val answers = { |
494 | val ans = RDFoxUtil.buildDescriptionQuery("Ans", query.answer.size) | 494 | val ans = filter.answerQuery |
495 | RDFoxUtil | 495 | RDFoxUtil |
496 | .submitQuery(data, ans, RSA.Prefixes) | 496 | .submitQuery(data, ans, RSA.Prefixes) |
497 | .map(new ConjunctiveQueryAnswers(query.bcq, query.variables, _)) | 497 | .map(new ConjunctiveQueryAnswers(query.bcq, query.variables, _)) |
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/filtering/FilteringProgram.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/filtering/FilteringProgram.scala index 9c8cbaa..a79a3f2 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/filtering/FilteringProgram.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/filtering/FilteringProgram.scala | |||
@@ -6,8 +6,8 @@ import uk.ac.ox.cs.rsacomb.util.Versioned | |||
6 | 6 | ||
7 | sealed trait FilterType | 7 | sealed trait FilterType |
8 | object FilterType { | 8 | object FilterType { |
9 | case object FILTER_NAIVE extends FilterType | 9 | case object NAIVE extends FilterType |
10 | case object FILTER_REVISED_V1 extends FilterType | 10 | case object REVISED extends FilterType |
11 | } | 11 | } |
12 | 12 | ||
13 | object FilteringProgram extends Versioned[FilterType] { | 13 | object FilteringProgram extends Versioned[FilterType] { |
@@ -18,8 +18,8 @@ object FilteringProgram extends Versioned[FilterType] { | |||
18 | 18 | ||
19 | def apply(t: FilterType): (ConjunctiveQuery) => FilteringProgram = | 19 | def apply(t: FilterType): (ConjunctiveQuery) => FilteringProgram = |
20 | t match { | 20 | t match { |
21 | case FILTER_NAIVE => NaiveFilteringProgram(_) | 21 | case NAIVE => NaiveFilteringProgram(_) |
22 | case FILTER_REVISED_V1 => NaiveFilteringProgram(_) | 22 | case REVISED => RevisedFilteringProgram(_) |
23 | } | 23 | } |
24 | } | 24 | } |
25 | 25 | ||
@@ -36,6 +36,9 @@ trait FilteringProgram { | |||
36 | /** Collection of filtering program rules. */ | 36 | /** Collection of filtering program rules. */ |
37 | def rules: List[Rule] | 37 | def rules: List[Rule] |
38 | 38 | ||
39 | /** Query to be used to retrieve the answers */ | ||
40 | def answerQuery: String | ||
41 | |||
39 | /** Pretty-print filtering rule */ | 42 | /** Pretty-print filtering rule */ |
40 | override def toString(): String = rules mkString "\n" | 43 | override def toString(): String = rules mkString "\n" |
41 | } | 44 | } |
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/filtering/NaiveFilteringProgram.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/filtering/NaiveFilteringProgram.scala index 57898a8..d8fb488 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/filtering/NaiveFilteringProgram.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/filtering/NaiveFilteringProgram.scala | |||
@@ -304,4 +304,6 @@ class NaiveFilteringProgram(val query: ConjunctiveQuery) | |||
304 | r8a ::: r8b :: r8c ::: | 304 | r8a ::: r8b :: r8c ::: |
305 | r9 :: List()) map RDFoxUtil.reify | 305 | r9 :: List()) map RDFoxUtil.reify |
306 | } | 306 | } |
307 | |||
308 | val answerQuery = RDFoxUtil.buildDescriptionQuery("Ans", query.answer.size) | ||
307 | } | 309 | } |
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/filtering/RevisedFilteringProgram.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/filtering/RevisedFilteringProgram.scala new file mode 100644 index 0000000..b75bc81 --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/filtering/RevisedFilteringProgram.scala | |||
@@ -0,0 +1,403 @@ | |||
1 | package uk.ac.ox.cs.rsacomb.filtering | ||
2 | |||
3 | //import scala.collection.JavaConverters._ | ||
4 | import tech.oxfordsemantic.jrdfox.logic.Datatype | ||
5 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ | ||
6 | FilterAtom, | ||
7 | Rule, | ||
8 | TupleTableAtom, | ||
9 | TupleTableName, | ||
10 | BodyFormula, | ||
11 | Negation | ||
12 | } | ||
13 | import tech.oxfordsemantic.jrdfox.logic.expression.{ | ||
14 | IRI, | ||
15 | FunctionCall, | ||
16 | Term, | ||
17 | Variable | ||
18 | } | ||
19 | import uk.ac.ox.cs.rsacomb.sparql.ConjunctiveQuery | ||
20 | import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Forward, Backward} | ||
21 | import uk.ac.ox.cs.rsacomb.util.{RSA, RDFoxUtil} | ||
22 | |||
23 | /** Factory for [[uk.ac.ox.cs.rsacomb.FilteringProgram FilteringProgram]] */ | ||
24 | object RevisedFilteringProgram { | ||
25 | |||
26 | /** Create a new FilteringProgram instance. | ||
27 | * | ||
28 | * @param query CQ to be converted into logic rules. | ||
29 | */ | ||
30 | def apply(query: ConjunctiveQuery): RevisedFilteringProgram = | ||
31 | new RevisedFilteringProgram(query) | ||
32 | } | ||
33 | |||
34 | /** Filtering Program generator | ||
35 | * | ||
36 | * Handles the conversion of a CQ into a set of logic rules, | ||
37 | * representing the filtering step of the RSA combined approach. | ||
38 | * | ||
39 | * Instances can be created using the companion object. | ||
40 | */ | ||
41 | class RevisedFilteringProgram(val query: ConjunctiveQuery) | ||
42 | extends FilteringProgram { | ||
43 | |||
44 | /** Extends capabilities of | ||
45 | * [[tech.oxfordsemantic.jrdfox.logic.datalog.TupleTableAtom TupleTableAtom]] | ||
46 | */ | ||
47 | import uk.ac.ox.cs.rsacomb.implicits.RSAAtom._ | ||
48 | |||
49 | /** Implicit parameter used in RSA internal predicates. | ||
50 | * | ||
51 | * @see [[uk.ac.ox.cs.rsacomb.util.RSA]] for more information. | ||
52 | */ | ||
53 | implicit private[this] val _query = query | ||
54 | |||
55 | /** Helpers */ | ||
56 | |||
57 | private def ?(name: String): Term = Variable.create(s"${name}i") | ||
58 | private def not(atom: TupleTableAtom): BodyFormula = Negation.create(atom) | ||
59 | private def skolem(terms: List[Term]): TupleTableAtom = | ||
60 | TupleTableAtom.create(TupleTableName.SKOLEM, terms: _*) | ||
61 | |||
62 | private def QM(x: Term): TupleTableAtom = | ||
63 | TupleTableAtom.rdf(x, IRI.RDF_TYPE, RSA("QM")) | ||
64 | private def FK(x: Term): TupleTableAtom = | ||
65 | TupleTableAtom.rdf(x, IRI.RDF_TYPE, RSA("FK")) | ||
66 | private def SP(x: Term): TupleTableAtom = | ||
67 | TupleTableAtom.rdf(x, IRI.RDF_TYPE, RSA("SP")) | ||
68 | private def NI(x: Term): TupleTableAtom = | ||
69 | TupleTableAtom.rdf(x, IRI.RDF_TYPE, RSA("NI")) | ||
70 | private def Ans(x: Term): TupleTableAtom = | ||
71 | TupleTableAtom.rdf(x, IRI.RDF_TYPE, RSA("Ans")) | ||
72 | private def ID(x: Term, y: Term): TupleTableAtom = | ||
73 | TupleTableAtom.rdf(x, RSA("ID"), y) | ||
74 | private def AQ(suffix: RSASuffix, x: Term, y: Term): TupleTableAtom = | ||
75 | TupleTableAtom.rdf(x, RSA("AQ"), y) << suffix | ||
76 | private def TQ(suffix: RSASuffix, x: Term, y: Term): TupleTableAtom = | ||
77 | TupleTableAtom.rdf(x, RSA("TQ"), y) << suffix | ||
78 | |||
79 | /** Rule generating the instances of the predicate `rsa:NI`. | ||
80 | * | ||
81 | * According to the original paper, the set of `rsa:NI` is defined as | ||
82 | * the set of constants that are equal (w.r.t. the congruence | ||
83 | * relation represented by `rsa:Congruent`) to a constant in the | ||
84 | * original ontology. | ||
85 | * | ||
86 | * @note that the set of `rsa:Named` constants is always a subset of | ||
87 | * the set of `rsa:NI`s. | ||
88 | * | ||
89 | * @note in the paper, instances of `rsa:NI` are introduced as facts | ||
90 | * during the canonical model computation. By definition of the | ||
91 | * predicate, this is not feasible, and the instances are instead | ||
92 | * generate in the filtering program using a logic rule. | ||
93 | */ | ||
94 | val nis: Rule = | ||
95 | Rule.create(NI(?("X")), RSA.Congruent(?("X"), ?("Y")), RSA.Named(?("Y"))) | ||
96 | |||
97 | /** Collection of filtering program rules. */ | ||
98 | val rules: List[Rule] = | ||
99 | nis :: { | ||
100 | |||
101 | val variables = query.answer ::: query.bounded | ||
102 | |||
103 | /** Generates all possible, unfiltered answers. | ||
104 | * | ||
105 | * @note corresponds to rule 1 in Table 3 in the paper. | ||
106 | */ | ||
107 | val r1 = | ||
108 | Rule.create( | ||
109 | QM(?("K")), | ||
110 | (query.atoms :+ skolem(variables :+ ?("K"))): _* | ||
111 | ) | ||
112 | |||
113 | /** Initializes instances of `rsa:ID`. | ||
114 | * | ||
115 | * They are initialized as a minimal congruence relation over the | ||
116 | * positions of the existential variables in the query which are | ||
117 | * mapped to anonymous terms. | ||
118 | * | ||
119 | * @note corresponds to rules 3x in Table 3. | ||
120 | */ | ||
121 | val r3a = | ||
122 | for ((v, i) <- query.bounded.zipWithIndex) | ||
123 | yield Rule.create( | ||
124 | ID(?("K"), ?("S")), | ||
125 | QM(?("K")), | ||
126 | skolem(variables :+ ?("K")), | ||
127 | not(NI(v)), | ||
128 | skolem(variables :+ RSA(i) :+ RSA(i) :+ ?("S")) | ||
129 | ) | ||
130 | val r3b = Rule.create( | ||
131 | ID(?("K"), ?("T")), | ||
132 | ID(?("K"), ?("S")), | ||
133 | skolem(variables :+ ?("U") :+ ?("V") :+ ?("S")), | ||
134 | skolem(variables :+ ?("V") :+ ?("U") :+ ?("T")) | ||
135 | ) | ||
136 | val r3c = Rule.create( | ||
137 | ID(?("K1"), ?("Q")), | ||
138 | QM(?("K1")), | ||
139 | ID(?("K2"), ?("S")), | ||
140 | FilterAtom.create(FunctionCall.equal(?("K1"), ?("K2"))), | ||
141 | skolem(variables :+ ?("U") :+ ?("V") :+ ?("S")), | ||
142 | ID(?("K3"), ?("T")), | ||
143 | FilterAtom.create(FunctionCall.equal(?("K1"), ?("K3"))), | ||
144 | skolem(variables :+ ?("V") :+ ?("W") :+ ?("T")), | ||
145 | skolem(variables :+ ?("U") :+ ?("W") :+ ?("Q")) | ||
146 | ) | ||
147 | |||
148 | /** Detects forks in the canonical model. | ||
149 | * | ||
150 | * @note corresponds to rules 4x in Table 3. | ||
151 | */ | ||
152 | val r4a = for { | ||
153 | role1 <- query.atoms filter (_.isRoleAssertion) | ||
154 | index1 = query.bounded indexOf (role1.getArguments get 2) | ||
155 | if index1 >= 0 | ||
156 | role2 <- query.atoms filter (_.isRoleAssertion) | ||
157 | index2 = query.bounded indexOf (role2.getArguments get 2) | ||
158 | if index2 >= 0 | ||
159 | } yield Rule.create( | ||
160 | FK(?("K")), | ||
161 | ID(?("K"), ?("S")), | ||
162 | skolem(variables :+ RSA(index1) :+ RSA(index2) :+ ?("S")), | ||
163 | role1 << Forward, | ||
164 | role2 << Forward, | ||
165 | not(RSA.Congruent(role1.getArguments get 0, role2.getArguments get 0)) | ||
166 | ) | ||
167 | val r4b = for { | ||
168 | role1 <- query.atoms filter (_.isRoleAssertion) | ||
169 | index1 = query.bounded indexOf (role1.getArguments get 2) | ||
170 | if index1 >= 0 | ||
171 | role2 <- query.atoms filter (_.isRoleAssertion) | ||
172 | index2 = query.bounded indexOf (role2.getArguments get 0) | ||
173 | if index2 >= 0 | ||
174 | } yield Rule.create( | ||
175 | FK(?("K")), | ||
176 | ID(?("K"), ?("S")), | ||
177 | skolem(variables :+ RSA(index1) :+ RSA(index2) :+ ?("S")), | ||
178 | role1 << Forward, | ||
179 | role2 << Backward, | ||
180 | not(RSA.Congruent(role1.getArguments get 0, role2.getArguments get 2)) | ||
181 | ) | ||
182 | val r4c = for { | ||
183 | role1 <- query.atoms filter (_.isRoleAssertion) | ||
184 | index1 = query.bounded indexOf (role1.getArguments get 0) | ||
185 | if index1 >= 0 | ||
186 | role2 <- query.atoms filter (_.isRoleAssertion) | ||
187 | index2 = query.bounded indexOf (role2.getArguments get 0) | ||
188 | if index2 >= 0 | ||
189 | } yield Rule.create( | ||
190 | FK(?("K")), | ||
191 | ID(?("K"), ?("S")), | ||
192 | skolem(variables :+ RSA(index1) :+ RSA(index2) :+ ?("S")), | ||
193 | role1 << Backward, | ||
194 | role2 << Backward, | ||
195 | not(RSA.Congruent(role1.getArguments get 2, role2.getArguments get 2)) | ||
196 | ) | ||
197 | |||
198 | /** Recursively propagates `rsa:ID` predicate. | ||
199 | * | ||
200 | * @note corresponds to rules 5x in Table 3. | ||
201 | */ | ||
202 | val r5a = for { | ||
203 | role1 <- query.atoms filter (_.isRoleAssertion) | ||
204 | r1arg0 = role1.getArguments get 0 | ||
205 | if query.bounded contains r1arg0 | ||
206 | r1arg2 = role1.getArguments get 2 | ||
207 | if query.bounded contains r1arg2 | ||
208 | role2 <- query.atoms filter (_.isRoleAssertion) | ||
209 | r2arg0 = role2.getArguments get 0 | ||
210 | if query.bounded contains r2arg0 | ||
211 | r2arg2 = role2.getArguments get 2 | ||
212 | if query.bounded contains r2arg2 | ||
213 | } yield Rule.create( | ||
214 | ID(?("K"), ?("T")), | ||
215 | ID(?("K"), ?("S")), | ||
216 | skolem( | ||
217 | variables :+ | ||
218 | RSA(query.bounded indexOf r1arg2) :+ | ||
219 | RSA(query.bounded indexOf r2arg2) :+ | ||
220 | ?("S") | ||
221 | ), | ||
222 | RSA.Congruent(r1arg0, r2arg0), | ||
223 | role1 << Forward, | ||
224 | role2 << Forward, | ||
225 | not(NI(r1arg0)), | ||
226 | skolem( | ||
227 | variables :+ | ||
228 | RSA(query.bounded indexOf r1arg0) :+ | ||
229 | RSA(query.bounded indexOf r2arg0) :+ | ||
230 | ?("T") | ||
231 | ) | ||
232 | ) | ||
233 | val r5b = for { | ||
234 | role1 <- query.atoms filter (_.isRoleAssertion) | ||
235 | r1arg0 = role1.getArguments get 0 | ||
236 | if query.bounded contains r1arg0 | ||
237 | r1arg2 = role1.getArguments get 2 | ||
238 | if query.bounded contains r1arg2 | ||
239 | role2 <- query.atoms filter (_.isRoleAssertion) | ||
240 | r2arg0 = role2.getArguments get 0 | ||
241 | if query.bounded contains r2arg0 | ||
242 | r2arg2 = role2.getArguments get 2 | ||
243 | if query.bounded contains r2arg2 | ||
244 | } yield Rule.create( | ||
245 | ID(?("K"), ?("T")), | ||
246 | ID(?("K"), ?("S")), | ||
247 | skolem( | ||
248 | variables :+ | ||
249 | RSA(query.bounded indexOf r1arg2) :+ | ||
250 | RSA(query.bounded indexOf r2arg0) :+ | ||
251 | ?("S") | ||
252 | ), | ||
253 | RSA.Congruent(r1arg0, r2arg2), | ||
254 | role1 << Forward, | ||
255 | role2 << Backward, | ||
256 | not(RSA.NI(r1arg0)), | ||
257 | skolem( | ||
258 | variables :+ | ||
259 | RSA(query.bounded indexOf r1arg0) :+ | ||
260 | RSA(query.bounded indexOf r2arg2) :+ | ||
261 | ?("T") | ||
262 | ) | ||
263 | ) | ||
264 | val r5c = for { | ||
265 | role1 <- query.atoms filter (_.isRoleAssertion) | ||
266 | r1arg0 = role1.getArguments get 0 | ||
267 | if query.bounded contains r1arg0 | ||
268 | r1arg2 = role1.getArguments get 2 | ||
269 | if query.bounded contains r1arg2 | ||
270 | role2 <- query.atoms filter (_.isRoleAssertion) | ||
271 | r2arg0 = role2.getArguments get 0 | ||
272 | if query.bounded contains r2arg0 | ||
273 | r2arg2 = role2.getArguments get 2 | ||
274 | if query.bounded contains r2arg2 | ||
275 | } yield Rule.create( | ||
276 | ID(?("K"), ?("T")), | ||
277 | ID(?("K"), ?("S")), | ||
278 | skolem( | ||
279 | variables :+ | ||
280 | RSA(query.bounded indexOf r1arg0) :+ | ||
281 | RSA(query.bounded indexOf r2arg0) :+ | ||
282 | ?("S") | ||
283 | ), | ||
284 | RSA.Congruent(r1arg2, r2arg2), | ||
285 | role1 << Backward, | ||
286 | role2 << Backward, | ||
287 | not(RSA.NI(r1arg2)), | ||
288 | skolem( | ||
289 | variables :+ | ||
290 | RSA(query.bounded indexOf r1arg2) :+ | ||
291 | RSA(query.bounded indexOf r2arg2) :+ | ||
292 | ?("T") | ||
293 | ) | ||
294 | ) | ||
295 | |||
296 | /** Detect cycles in the canonical model. | ||
297 | * | ||
298 | * Cycles are detected by introducing a new predicate `rsa:AQ` | ||
299 | * and computing its transitive closure. Cycles are computed from | ||
300 | * forward and backward roles separately. | ||
301 | * | ||
302 | * @note corresponds to rules 6,7x in Table 3. | ||
303 | */ | ||
304 | val r6 = for { | ||
305 | role <- query.atoms filter (_.isRoleAssertion) | ||
306 | index0 = query.bounded indexOf (role.getArguments get 0) | ||
307 | if index0 >= 0 | ||
308 | index2 = query.bounded indexOf (role.getArguments get 2) | ||
309 | if index2 >= 0 | ||
310 | suffix <- Seq(Forward, Backward) | ||
311 | } yield Rule.create( | ||
312 | AQ(suffix, ?("K1"), ?("Q")), | ||
313 | ID(?("K1"), ?("S")), | ||
314 | skolem(variables :+ RSA(index0) :+ ?("V") :+ ?("S")), | ||
315 | ID(?("K2"), ?("T")), | ||
316 | FilterAtom.create(FunctionCall.equal(?("K1"), ?("K2"))), | ||
317 | skolem(variables :+ RSA(index2) :+ ?("W") :+ ?("T")), | ||
318 | role << suffix, | ||
319 | skolem(variables :+ ?("V") :+ ?("W") :+ ?("Q")) | ||
320 | ) | ||
321 | val r7a = | ||
322 | for (suffix <- List(Forward, Backward)) | ||
323 | yield Rule.create( | ||
324 | TQ(suffix, ?("K"), ?("S")), | ||
325 | AQ(suffix, ?("K"), ?("S")) | ||
326 | ) | ||
327 | val r7b = | ||
328 | for (suffix <- List(Forward, Backward)) | ||
329 | yield Rule.create( | ||
330 | TQ(suffix, ?("K1"), ?("Q")), | ||
331 | AQ(suffix, ?("K1"), ?("S")), | ||
332 | skolem(variables :+ ?("U") :+ ?("V") :+ ?("S")), | ||
333 | TQ(suffix, ?("K2"), ?("T")), | ||
334 | FilterAtom.create(FunctionCall.equal(?("K1"), ?("K2"))), | ||
335 | skolem(variables :+ ?("V") :+ ?("W") :+ ?("T")), | ||
336 | skolem(variables :+ ?("U") :+ ?("W") :+ ?("Q")) | ||
337 | ) | ||
338 | |||
339 | /** Flag spurious answers. | ||
340 | * | ||
341 | * @note corresponds to rules 8x in Table 3. | ||
342 | */ | ||
343 | val r8a = | ||
344 | for (v <- query.answer) | ||
345 | yield Rule.create( | ||
346 | SP(?("K")), | ||
347 | QM(?("K")), | ||
348 | skolem(variables :+ ?("K")), | ||
349 | not(RSA.Named(v)) | ||
350 | ) | ||
351 | val r8b = Rule.create( | ||
352 | SP(?("K")), | ||
353 | FK(?("K")) | ||
354 | ) | ||
355 | val r8c = | ||
356 | for (suffix <- List(Forward, Backward)) | ||
357 | yield Rule.create( | ||
358 | SP(?("K")), | ||
359 | TQ(suffix, ?("K"), ?("S")), | ||
360 | skolem(variables :+ ?("V") :+ ?("V") :+ ?("S")) | ||
361 | ) | ||
362 | |||
363 | /** Determine answers to the query | ||
364 | * | ||
365 | * Answers are identified by predicate `rsa:Ans`. In case the | ||
366 | * input query is a BCQ (answer is just true/false), we derive | ||
367 | * `rsa:Ans` for a fresh constant `c`. Later on we can query for | ||
368 | * instances of `rsa:Ans` as follows | ||
369 | * | ||
370 | * {{{ | ||
371 | * ASK { ?X a rsa:Ans } | ||
372 | * }}} | ||
373 | * | ||
374 | * to determine whether the query is true or false. | ||
375 | * | ||
376 | * @note corresponds to rule 9 in Table 3. | ||
377 | */ | ||
378 | val r9 = Rule.create( | ||
379 | Ans(?("K")), | ||
380 | QM(?("K")), | ||
381 | not(SP(?("K"))) | ||
382 | ) | ||
383 | |||
384 | (r1 :: r3a ::: r3b :: r3c :: r4a ::: r4b ::: r4c ::: r5a ::: r5b ::: r5c ::: r6 ::: r7b ::: r7a ::: r8a ::: r8b :: r8c ::: r9 :: List()) | ||
385 | } | ||
386 | |||
387 | val answerQuery: String = { | ||
388 | val arity = query.answer.size | ||
389 | if (arity > 0) { | ||
390 | val variables = (0 until arity).mkString("?X", " ?X", "") | ||
391 | s""" | ||
392 | SELECT ${query.answer mkString ""} | ||
393 | WHERE { | ||
394 | ?K a rsa:Ans . | ||
395 | TT <http://oxfordsemantic.tech/RDFox#SKOLEM> { ${query.answer mkString ""} ${query.bounded mkString ""} ?K } . | ||
396 | } | ||
397 | """ | ||
398 | } else { | ||
399 | "ASK { ?X a rsa:Ans }" | ||
400 | } | ||
401 | } | ||
402 | |||
403 | } | ||