aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFederico Igne <federico.igne@cs.ox.ac.uk>2021-02-04 18:48:56 +0000
committerFederico Igne <federico.igne@cs.ox.ac.uk>2021-02-04 18:48:56 +0000
commit5a7208dac91c31ffcec37955fff48f1046aac753 (patch)
treecd3dbe4c6cc3d529f35bce56fdbb084e564b9a1c
parent1cb2165ccbcd6b1210bee1dfb7b527b4d2440901 (diff)
downloadRSAComb-5a7208dac91c31ffcec37955fff48f1046aac753.tar.gz
RSAComb-5a7208dac91c31ffcec37955fff48f1046aac753.zip
Add revised implementation of filtering program
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala4
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/filtering/FilteringProgram.scala11
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/filtering/NaiveFilteringProgram.scala2
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/filtering/RevisedFilteringProgram.scala403
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
7sealed trait FilterType 7sealed trait FilterType
8object FilterType { 8object 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
13object FilteringProgram extends Versioned[FilterType] { 13object 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 @@
1package uk.ac.ox.cs.rsacomb.filtering
2
3//import scala.collection.JavaConverters._
4import tech.oxfordsemantic.jrdfox.logic.Datatype
5import tech.oxfordsemantic.jrdfox.logic.datalog.{
6 FilterAtom,
7 Rule,
8 TupleTableAtom,
9 TupleTableName,
10 BodyFormula,
11 Negation
12}
13import tech.oxfordsemantic.jrdfox.logic.expression.{
14 IRI,
15 FunctionCall,
16 Term,
17 Variable
18}
19import uk.ac.ox.cs.rsacomb.sparql.ConjunctiveQuery
20import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Forward, Backward}
21import uk.ac.ox.cs.rsacomb.util.{RSA, RDFoxUtil}
22
23/** Factory for [[uk.ac.ox.cs.rsacomb.FilteringProgram FilteringProgram]] */
24object 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 */
41class 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}