aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFederico Igne <federico.igne@cs.ox.ac.uk>2021-11-17 12:40:30 +0000
committerFederico Igne <federico.igne@cs.ox.ac.uk>2021-11-17 12:45:32 +0000
commit727a77044f24fe1cbeee55b3389130a58bfcfd65 (patch)
tree2c973cccdcb27bc8a4fcdc1e620bf983d77a9c16
parent28d767e7e588d5d411e06b26db1a18448e2a6aad (diff)
downloadRSAComb-727a77044f24fe1cbeee55b3389130a58bfcfd65.tar.gz
RSAComb-727a77044f24fe1cbeee55b3389130a58bfcfd65.zip
Add an optimised version of the filtering program
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala2
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/filtering/FilteringProgram.scala14
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/filtering/RevisedFilteringProgram.scala10
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/filtering/RevisedFilteringProgram2.scala423
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 @@
17package uk.ac.ox.cs.rsacomb.filtering 17package uk.ac.ox.cs.rsacomb.filtering
18 18
19import tech.oxfordsemantic.jrdfox.logic.datalog.Rule 19import tech.oxfordsemantic.jrdfox.logic.datalog.Rule
20import tech.oxfordsemantic.jrdfox.logic.expression.IRI 20import tech.oxfordsemantic.jrdfox.logic.expression.{IRI, Variable}
21import uk.ac.ox.cs.rsacomb.sparql.ConjunctiveQuery 21import uk.ac.ox.cs.rsacomb.sparql.ConjunctiveQuery
22import uk.ac.ox.cs.rsacomb.util.Versioned 22import uk.ac.ox.cs.rsacomb.util.Versioned
23 23
24object 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
29object FilterType { 39object 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
37import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Forward, Backward} 37import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Forward, Backward}
38import uk.ac.ox.cs.rsacomb.util.{RSA, RDFoxUtil} 38import uk.ac.ox.cs.rsacomb.util.{RSA, RDFoxUtil}
39 39
40object 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]] */
51object RevisedFilteringProgram { 41object 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
17package uk.ac.ox.cs.rsacomb.filtering
18
19//import scala.collection.JavaConverters._
20import tech.oxfordsemantic.jrdfox.logic.Datatype
21import tech.oxfordsemantic.jrdfox.logic.datalog.{
22 Atom,
23 FilterAtom,
24 Rule,
25 TupleTableAtom,
26 TupleTableName,
27 BodyFormula,
28 Negation
29}
30import tech.oxfordsemantic.jrdfox.logic.expression.{
31 IRI,
32 FunctionCall,
33 Term,
34 Variable
35}
36import uk.ac.ox.cs.rsacomb.sparql.ConjunctiveQuery
37import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Forward, Backward}
38import uk.ac.ox.cs.rsacomb.util.{RSA, RDFoxUtil}
39
40/** Factory for [[uk.ac.ox.cs.rsacomb.FilteringProgram FilteringProgram]] */
41object 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 */
63class 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}