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