diff options
author | Federico Igne <federico.igne@cs.ox.ac.uk> | 2020-11-23 22:06:33 +0000 |
---|---|---|
committer | Federico Igne <federico.igne@cs.ox.ac.uk> | 2020-11-23 22:06:33 +0000 |
commit | aeb5ad23e5f13952efdeaf4aec2e97a96b469655 (patch) | |
tree | b9f76f4ba035fb33eddaf0d206ba768d512c1f1c | |
parent | 43be594ec3cd6f16e1ccfc31ce226f5266a7a19b (diff) | |
download | RSAComb-aeb5ad23e5f13952efdeaf4aec2e97a96b469655.tar.gz RSAComb-aeb5ad23e5f13952efdeaf4aec2e97a96b469655.zip |
Include BCQ case in filtering program generation
The FilteringProgram module has been slightly reworked.
3 files changed, 316 insertions, 305 deletions
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala index 6df30fd..055cf2a 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala | |||
@@ -32,298 +32,304 @@ import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Forward, Backward} | |||
32 | import uk.ac.ox.cs.rsacomb.util.RSA | 32 | import uk.ac.ox.cs.rsacomb.util.RSA |
33 | import uk.ac.ox.cs.rsacomb.sparql.ConjunctiveQuery | 33 | import uk.ac.ox.cs.rsacomb.sparql.ConjunctiveQuery |
34 | 34 | ||
35 | /** Factory for [[uk.ac.ox.cs.rsacomb.FilteringProgram FilteringProgram]] */ | ||
35 | object FilteringProgram { | 36 | object FilteringProgram { |
36 | 37 | ||
38 | /** Create a new FilteringProgram instance. | ||
39 | * | ||
40 | * @param query CQ to be converted into logic rules. | ||
41 | * @param constants constants in the original ontology. They will be | ||
42 | * used to initialize predicate `rsa:Named`. | ||
43 | */ | ||
37 | def apply(query: ConjunctiveQuery, constants: List[Term]): FilteringProgram = | 44 | def apply(query: ConjunctiveQuery, constants: List[Term]): FilteringProgram = |
38 | new FilteringProgram(query, constants) | 45 | new FilteringProgram(query, constants) |
39 | 46 | ||
40 | } // object FilteringProgram | 47 | } |
41 | 48 | ||
49 | /** Filtering Program generator | ||
50 | * | ||
51 | * Handles the conversion of a CQ into a set of logic rules, | ||
52 | * representing the filtering step of the RSA combined approach. | ||
53 | * | ||
54 | * Instances can be created using the companion object. | ||
55 | */ | ||
42 | class FilteringProgram(query: ConjunctiveQuery, constants: List[Term]) | 56 | class FilteringProgram(query: ConjunctiveQuery, constants: List[Term]) |
43 | extends RSAAtom { | 57 | extends RSAAtom { |
44 | 58 | ||
45 | /* Makes mplicit conversion OWLAPI IRI <-> RDFox IRI available */ | 59 | /** Implicit parameter used in RSA internal predicates. |
46 | // import implicits.RDFox._ | 60 | * |
47 | 61 | * @see [[uk.ac.ox.cs.rsacomb.util.RSA]] for more information. | |
48 | val answer: List[Term] = query.answer.toList | 62 | */ |
49 | val bounded: List[Term] = query.bounded.toList | 63 | implicit private[this] val _query = query |
50 | implicit val variables = (answer, bounded) | ||
51 | 64 | ||
52 | val named: List[Rule] = | 65 | /** General purpose variables used in rule instantiation. |
53 | constants.map(a => Rule.create(RSA.Named(a))) | 66 | * |
67 | * This is done to avoid creating new variables every time. | ||
68 | */ | ||
69 | private val varX = Variable.create("X") | ||
70 | private val varY = Variable.create("Y") | ||
71 | private val varZ = Variable.create("Z") | ||
72 | private val varV = Variable.create("V") | ||
73 | private val varU = Variable.create("U") | ||
74 | private val varW = Variable.create("W") | ||
54 | 75 | ||
55 | val nis: Rule = { | 76 | /** Rule generating the instances of the predicate `rsa:NI`. |
56 | val varX = Variable.create("X") | 77 | * |
57 | val varY = Variable.create("Y") | 78 | * According to the original paper, the set of `rsa:NI` is defined as |
79 | * the set of constants that are equal (w.r.t. the congruence | ||
80 | * relation represented by `rsa:Congruent`) to a constant in the | ||
81 | * original ontology. | ||
82 | * | ||
83 | * @note that the set of `rsa:Named` constants is always a subset of | ||
84 | * the set of `rsa:NI`s. | ||
85 | * | ||
86 | * @note in the paper, instances of `rsa:NI` are introduced as facts | ||
87 | * during the canonical model computation. By definition of the | ||
88 | * predicate, this is not feasible, and the instances are instead | ||
89 | * generate in the filtering program using a logic rule. | ||
90 | */ | ||
91 | val nis: Rule = | ||
58 | Rule.create(RSA.NI(varX), RSA.Congruent(varX, varY), RSA.Named(varY)) | 92 | Rule.create(RSA.NI(varX), RSA.Congruent(varX, varY), RSA.Named(varY)) |
59 | } | ||
60 | 93 | ||
94 | /** Collection of filtering program rules. */ | ||
61 | val rules: List[Rule] = | 95 | val rules: List[Rule] = |
62 | nis :: named ::: this.generateFilteringProgram().map(reifyRule) | 96 | nis :: { |
63 | 97 | ||
64 | /* NOTE: we are restricting to queries that contain conjunctions of | 98 | /** Negates a [[tech.oxfordsemantic.jrdfox.logic.datalog.TupleTableAtom TupleTableAtom]] */ |
65 | * atoms for the time being. This might need to be reviewed in the | 99 | def not(atom: TupleTableAtom): BodyFormula = Negation.create(atom) |
66 | * future. | ||
67 | */ | ||
68 | private def queryToBody(body: GroupGraphPattern): List[TupleTableAtom] = | ||
69 | body match { | ||
70 | case b: ConjunctionPattern => { | ||
71 | val conjuncts = b.getConjuncts.asScala.toList | ||
72 | conjuncts flatMap { conj => | ||
73 | conj match { | ||
74 | case c: TriplePattern => | ||
75 | List( | ||
76 | TupleTableAtom.rdf(c.getSubject, c.getPredicate, c.getObject) | ||
77 | ) | ||
78 | case _ => List() | ||
79 | } | ||
80 | } | ||
81 | } | ||
82 | case _ => List() | ||
83 | } | ||
84 | 100 | ||
85 | private def generateFilteringProgram(): List[Rule] = { | 101 | /** Generates all possible, unfiltered answers. |
86 | // General purpose variables | 102 | * |
87 | val varU = Variable.create("U") | 103 | * @note corresponds to rule 1 in Table 3 in the paper. |
88 | val varV = Variable.create("V") | 104 | */ |
89 | val varW = Variable.create("W") | 105 | val r1 = reifyRule(Rule.create(RSA.QM, query.atoms: _*)) |
90 | // Query formula as a rule body | ||
91 | val body = queryToBody(query.where) | ||
92 | // Auxiliar predicates/helpers | ||
93 | def not(atom: TupleTableAtom): BodyFormula = Negation.create(atom) | ||
94 | // val predQM = | ||
95 | // TupleTableAtom.create( | ||
96 | // TupleTableName.create(RSA.Qm.getIRI), | ||
97 | // (answer ++ bounded): _* | ||
98 | // ) | ||
99 | // def predID(t1: Term, t2: Term) = | ||
100 | // TupleTableAtom.create( | ||
101 | // TupleTableName.create(RSA.rsa("ID").getIRI), | ||
102 | // (answer ++ bounded).appended(t1).appended(t2): _* | ||
103 | // ) | ||
104 | // def predNAMED(t1: Term): TupleTableAtom = | ||
105 | // TupleTableAtom.rdf(t1, IRI.RDF_TYPE, RSA.rsa("NAMED")) | ||
106 | // def predTQ(t1: Term, t2: Term, sx: RSASuffix) = | ||
107 | // TupleTableAtom.create( | ||
108 | // TupleTableName.create(RSA.rsa("TQ" :: sx).getIRI), | ||
109 | // (answer ++ bounded).appended(t1).appended(t2): _* | ||
110 | // ) | ||
111 | // def predAQ(t1: Term, t2: Term, sx: RSASuffix) = | ||
112 | // TupleTableAtom.create( | ||
113 | // TupleTableName.create(RSA.rsa("AQ" :: sx).getIRI), | ||
114 | // (answer ++ bounded).appended(t1).appended(t2): _* | ||
115 | // ) | ||
116 | // val predFK = | ||
117 | // TupleTableAtom.create( | ||
118 | // TupleTableName.create(RSA.rsa("FK").getIRI), | ||
119 | // (answer ++ bounded): _* | ||
120 | // ) | ||
121 | // val predSP = | ||
122 | // TupleTableAtom.create( | ||
123 | // TupleTableName.create(RSA.rsa("SP").getIRI), | ||
124 | // (answer ++ bounded): _* | ||
125 | // ) | ||
126 | // val predANS = | ||
127 | // TupleTableAtom.create( | ||
128 | // TupleTableName.create(RSA.rsa("ANS").getIRI), | ||
129 | // answer: _* | ||
130 | // ) | ||
131 | 106 | ||
132 | /* Rule 1 */ | 107 | /** Initializes instances of `rsa:Named`. |
133 | val r1 = Rule.create(RSA.QM, body: _*) | 108 | * |
109 | * They represent the set of constants appearing in the original | ||
110 | * ontology. | ||
111 | * | ||
112 | * @note corresponds to rules 2 in Table 3. | ||
113 | */ | ||
114 | val r2 = constants.map(c => Rule.create(RSA.Named(c))) | ||
134 | 115 | ||
135 | /* Rules 3x */ | 116 | /** Initializes instances of `rsa:ID`. |
136 | val r3a = | 117 | * |
137 | for ((v, i) <- bounded.zipWithIndex) | 118 | * They are initialized as a minimal congruence relation over the |
138 | yield Rule.create(RSA.ID(RSA(i), RSA(i)), RSA.QM, not(RSA.NI(v))) | 119 | * positions of the existential variables in the query which are |
139 | val r3b = Rule.create(RSA.ID(varV, varU), RSA.ID(varU, varV)) | 120 | * mapped to anonymous terms. |
140 | val r3c = | 121 | * |
141 | Rule.create(RSA.ID(varU, varW), RSA.ID(varU, varV), RSA.ID(varV, varW)) | 122 | * @note corresponds to rules 3x in Table 3. |
123 | */ | ||
124 | val r3a = | ||
125 | for ((v, i) <- query.bounded.zipWithIndex) | ||
126 | yield Rule.create(RSA.ID(RSA(i), RSA(i)), RSA.QM, not(RSA.NI(v))) | ||
127 | val r3b = Rule.create(RSA.ID(varV, varU), RSA.ID(varU, varV)) | ||
128 | val r3c = | ||
129 | Rule.create(RSA.ID(varU, varW), RSA.ID(varU, varV), RSA.ID(varV, varW)) | ||
142 | 130 | ||
143 | /* Rules 4x */ | 131 | /** Detects forks in the canonical model. |
144 | val r4a = for { | 132 | * |
145 | role1 <- body.filter(_.isRoleAssertion) | 133 | * @note corresponds to rules 4x in Table 3. |
146 | if bounded contains (role1.getArguments.get(2)) | 134 | */ |
147 | role2 <- body.filter(_.isRoleAssertion) | 135 | val r4a = for { |
148 | if bounded contains (role2.getArguments.get(2)) | 136 | role1 <- query.atoms filter (_.isRoleAssertion) |
149 | } yield Rule.create( | 137 | index1 = query.bounded indexOf (role1.getArguments get 2) |
150 | RSA.FK, | 138 | if index1 >= 0 |
151 | role1 << Forward, | 139 | role2 <- query.atoms filter (_.isRoleAssertion) |
152 | role2 << Forward, | 140 | index2 = query.bounded indexOf (role2.getArguments get 2) |
153 | RSA.ID( | 141 | if index2 >= 0 |
154 | RSA(bounded.indexOf(role1.getArguments.get(2))), | 142 | } yield Rule.create( |
155 | RSA(bounded.indexOf(role2.getArguments.get(2))) | 143 | RSA.FK, |
156 | ), | 144 | role1 << Forward, |
157 | not(RSA.Congruent(role1.getArguments.get(0), role2.getArguments.get(0))) | 145 | role2 << Forward, |
158 | ) | 146 | RSA.ID(RSA(index1), RSA(index2)), |
159 | val r4b = for { | 147 | not(RSA.Congruent(role1.getArguments get 0, role2.getArguments get 0)) |
160 | role1 <- body.filter(_.isRoleAssertion) | 148 | ) |
161 | if bounded contains (role1.getArguments.get(2)) | 149 | val r4b = for { |
162 | role2 <- body.filter(_.isRoleAssertion) | 150 | role1 <- query.atoms filter (_.isRoleAssertion) |
163 | if bounded contains (role2.getArguments.get(0)) | 151 | index1 = query.bounded indexOf (role1.getArguments get 2) |
164 | } yield Rule.create( | 152 | if index1 >= 0 |
165 | RSA.FK, | 153 | role2 <- query.atoms filter (_.isRoleAssertion) |
166 | role1 << Forward, | 154 | index2 = query.bounded indexOf (role2.getArguments get 0) |
167 | role2 << Backward, | 155 | if index2 >= 0 |
168 | RSA.ID( | 156 | } yield Rule.create( |
169 | RSA(bounded.indexOf(role1.getArguments.get(2))), | 157 | RSA.FK, |
170 | RSA(bounded.indexOf(role2.getArguments.get(0))) | 158 | role1 << Forward, |
171 | ), | 159 | role2 << Backward, |
172 | not(RSA.Congruent(role1.getArguments.get(0), role2.getArguments.get(2))) | 160 | RSA.ID(RSA(index1), RSA(index2)), |
173 | ) | 161 | not(RSA.Congruent(role1.getArguments get 0, role2.getArguments get 2)) |
174 | val r4c = for { | 162 | ) |
175 | role1 <- body.filter(_.isRoleAssertion) | 163 | val r4c = for { |
176 | if bounded contains (role1.getArguments.get(0)) | 164 | role1 <- query.atoms filter (_.isRoleAssertion) |
177 | role2 <- body.filter(_.isRoleAssertion) | 165 | index1 = query.bounded indexOf (role1.getArguments get 0) |
178 | if bounded contains (role2.getArguments.get(0)) | 166 | if index1 >= 0 |
179 | } yield Rule.create( | 167 | role2 <- query.atoms filter (_.isRoleAssertion) |
180 | RSA.FK, | 168 | index2 = query.bounded indexOf (role2.getArguments get 0) |
181 | role1 << Backward, | 169 | if index2 >= 0 |
182 | role2 << Backward, | 170 | } yield Rule.create( |
183 | RSA.ID( | 171 | RSA.FK, |
184 | RSA(bounded.indexOf(role1.getArguments.get(0))), | 172 | role1 << Backward, |
185 | RSA(bounded.indexOf(role2.getArguments.get(0))) | 173 | role2 << Backward, |
186 | ), | 174 | RSA.ID(RSA(index1), RSA(index2)), |
187 | not(RSA.Congruent(role1.getArguments.get(2), role2.getArguments.get(2))) | 175 | not(RSA.Congruent(role1.getArguments get 2, role2.getArguments get 2)) |
188 | ) | 176 | ) |
189 | 177 | ||
190 | /* Rules 5x */ | 178 | /** Recursively propagates `rsa:ID` predicate. |
191 | val r5a = for { | 179 | * |
192 | role1 <- body.filter(_.isRoleAssertion) | 180 | * @note corresponds to rules 5x in Table 3. |
193 | role1arg0 = role1.getArguments.get(0) | 181 | */ |
194 | role1arg2 = role1.getArguments.get(2) | 182 | val r5a = for { |
195 | if bounded contains role1arg0 | 183 | role1 <- query.atoms filter (_.isRoleAssertion) |
196 | if bounded contains role1arg2 | 184 | r1arg0 = role1.getArguments get 0 |
197 | role2 <- body.filter(_.isRoleAssertion) | 185 | if query.bounded contains r1arg0 |
198 | role2arg0 = role2.getArguments.get(0) | 186 | r1arg2 = role1.getArguments get 2 |
199 | role2arg2 = role2.getArguments.get(2) | 187 | if query.bounded contains r1arg2 |
200 | if bounded contains role2arg0 | 188 | role2 <- query.atoms filter (_.isRoleAssertion) |
201 | if bounded contains role2arg2 | 189 | r2arg0 = role2.getArguments get 0 |
202 | } yield Rule.create( | 190 | if query.bounded contains r2arg0 |
203 | RSA.ID( | 191 | r2arg2 = role2.getArguments get 2 |
204 | RSA(bounded indexOf role1arg0), | 192 | if query.bounded contains r2arg2 |
205 | RSA(bounded indexOf role2arg0) | 193 | } yield Rule.create( |
206 | ), | 194 | RSA.ID( |
207 | role1 << Forward, | 195 | RSA(query.bounded indexOf r1arg0), |
208 | role2 << Forward, | 196 | RSA(query.bounded indexOf r2arg0) |
209 | RSA.ID( | 197 | ), |
210 | RSA(bounded indexOf role1arg2), | 198 | role1 << Forward, |
211 | RSA(bounded indexOf role2arg2) | 199 | role2 << Forward, |
212 | ), | 200 | RSA.ID( |
213 | RSA.Congruent(role1arg0, role2arg0), | 201 | RSA(query.bounded indexOf r1arg2), |
214 | not(RSA.NI(role1arg0)) | 202 | RSA(query.bounded indexOf r2arg2) |
215 | ) | 203 | ), |
216 | val r5b = for { | 204 | RSA.Congruent(r1arg0, r2arg0), |
217 | role1 <- body.filter(_.isRoleAssertion) | 205 | not(RSA.NI(r1arg0)) |
218 | role1arg0 = role1.getArguments.get(0) | 206 | ) |
219 | role1arg2 = role1.getArguments.get(2) | 207 | val r5b = for { |
220 | if bounded contains role1arg0 | 208 | role1 <- query.atoms filter (_.isRoleAssertion) |
221 | if bounded contains role1arg2 | 209 | r1arg0 = role1.getArguments get 0 |
222 | role2 <- body.filter(_.isRoleAssertion) | 210 | if query.bounded contains r1arg0 |
223 | role2arg0 = role2.getArguments.get(0) | 211 | r1arg2 = role1.getArguments get 2 |
224 | role2arg2 = role2.getArguments.get(2) | 212 | if query.bounded contains r1arg2 |
225 | if bounded contains role2arg0 | 213 | role2 <- query.atoms filter (_.isRoleAssertion) |
226 | if bounded contains role2arg2 | 214 | r2arg0 = role2.getArguments get 0 |
227 | } yield Rule.create( | 215 | if query.bounded contains r2arg0 |
228 | RSA.ID( | 216 | r2arg2 = role2.getArguments get 2 |
229 | RSA(bounded indexOf role1arg0), | 217 | if query.bounded contains r2arg2 |
230 | RSA(bounded indexOf role2arg2) | 218 | } yield Rule.create( |
231 | ), | 219 | RSA.ID( |
232 | role1 << Forward, | 220 | RSA(query.bounded indexOf r1arg0), |
233 | role2 << Backward, | 221 | RSA(query.bounded indexOf r2arg2) |
234 | RSA.ID( | 222 | ), |
235 | RSA(bounded indexOf role1arg2), | 223 | role1 << Forward, |
236 | RSA(bounded indexOf role2arg0) | 224 | role2 << Backward, |
237 | ), | 225 | RSA.ID( |
238 | RSA.Congruent(role1arg0, role2arg2), | 226 | RSA(query.bounded indexOf r1arg2), |
239 | not(RSA.NI(role1arg0)) | 227 | RSA(query.bounded indexOf r2arg0) |
240 | ) | 228 | ), |
241 | val r5c = for { | 229 | RSA.Congruent(r1arg0, r2arg2), |
242 | role1 <- body.filter(_.isRoleAssertion) | 230 | not(RSA.NI(r1arg0)) |
243 | role1arg0 = role1.getArguments.get(0) | 231 | ) |
244 | role1arg2 = role1.getArguments.get(2) | 232 | val r5c = for { |
245 | if bounded contains role1arg0 | 233 | role1 <- query.atoms filter (_.isRoleAssertion) |
246 | if bounded contains role1arg2 | 234 | r1arg0 = role1.getArguments get 0 |
247 | role2 <- body.filter(_.isRoleAssertion) | 235 | if query.bounded contains r1arg0 |
248 | role2arg0 = role2.getArguments.get(0) | 236 | r1arg2 = role1.getArguments get 2 |
249 | role2arg2 = role2.getArguments.get(2) | 237 | if query.bounded contains r1arg2 |
250 | if bounded contains role2arg0 | 238 | role2 <- query.atoms filter (_.isRoleAssertion) |
251 | if bounded contains role2arg2 | 239 | r2arg0 = role2.getArguments get 0 |
252 | } yield Rule.create( | 240 | if query.bounded contains r2arg0 |
253 | RSA.ID( | 241 | r2arg2 = role2.getArguments get 2 |
254 | RSA(bounded indexOf role1arg2), | 242 | if query.bounded contains r2arg2 |
255 | RSA(bounded indexOf role2arg2) | 243 | } yield Rule.create( |
256 | ), | 244 | RSA.ID( |
257 | role1 << Backward, | 245 | RSA(query.bounded indexOf r1arg2), |
258 | role2 << Backward, | 246 | RSA(query.bounded indexOf r2arg2) |
259 | RSA.ID( | 247 | ), |
260 | RSA(bounded indexOf role1arg0), | 248 | role1 << Backward, |
261 | RSA(bounded indexOf role2arg0) | 249 | role2 << Backward, |
262 | ), | 250 | RSA.ID( |
263 | RSA.Congruent(role1arg2, role2arg2), | 251 | RSA(query.bounded indexOf r1arg0), |
264 | not(RSA.NI(role1arg2)) | 252 | RSA(query.bounded indexOf r2arg0) |
265 | ) | 253 | ), |
254 | RSA.Congruent(r1arg2, r2arg2), | ||
255 | not(RSA.NI(r1arg2)) | ||
256 | ) | ||
266 | 257 | ||
267 | /* Rules 6 */ | 258 | /** Detect cycles in the canonical model. |
268 | val r6 = { | 259 | * |
269 | for { | 260 | * Cycles are detected by introducing a new predicate `rsa:AQ` |
270 | role <- body.filter(_.isRoleAssertion) | 261 | * and computing its transitive closure. Cycles are computed from |
271 | arg0 = role.getArguments.get(0) | 262 | * forward and backward roles separately. |
272 | arg2 = role.getArguments.get(2) | 263 | * |
273 | if bounded contains arg0 | 264 | * @note corresponds to rules 6,7x in Table 3. |
274 | if bounded contains arg2 | 265 | */ |
266 | val r6 = for { | ||
267 | role <- query.atoms filter (_.isRoleAssertion) | ||
268 | index0 = query.bounded indexOf (role.getArguments get 0) | ||
269 | if index0 >= 0 | ||
270 | index2 = query.bounded indexOf (role.getArguments get 2) | ||
271 | if index2 >= 0 | ||
275 | suffix <- Seq(Forward, Backward) | 272 | suffix <- Seq(Forward, Backward) |
276 | } yield Rule.create( | 273 | } yield Rule.create( |
277 | RSA.AQ(varV, varW, suffix), | 274 | RSA.AQ(varV, varW, suffix), |
278 | role << suffix, | 275 | role << suffix, |
279 | RSA.ID(RSA(bounded indexOf arg0), varV), | 276 | RSA.ID(RSA(index0), varV), |
280 | RSA.ID(RSA(bounded indexOf arg2), varW) | 277 | RSA.ID(RSA(index2), varW) |
281 | ) | 278 | ) |
282 | } | 279 | val r7a = |
280 | for (suffix <- List(Forward, Backward)) | ||
281 | yield Rule.create( | ||
282 | RSA.TQ(varU, varV, suffix), | ||
283 | RSA.AQ(varU, varV, suffix) | ||
284 | ) | ||
285 | val r7b = | ||
286 | for (suffix <- List(Forward, Backward)) | ||
287 | yield Rule.create( | ||
288 | RSA.TQ(varU, varW, suffix), | ||
289 | RSA.AQ(varU, varV, suffix), | ||
290 | RSA.TQ(varV, varW, suffix) | ||
291 | ) | ||
283 | 292 | ||
284 | /* Rules 7x */ | 293 | /** Flag spurious answers. |
285 | val r7a = { | 294 | * |
286 | for (suffix <- List(Forward, Backward)) | 295 | * @note corresponds to rules 8x in Table 3. |
287 | yield Rule.create( | 296 | */ |
288 | RSA.TQ(varU, varV, suffix), | 297 | val r8a = |
289 | RSA.AQ(varU, varV, suffix) | 298 | for (v <- query.answer) |
290 | ) | 299 | yield Rule.create(RSA.SP, RSA.QM, not(RSA.Named(v))) |
291 | } | 300 | val r8b = Rule.create(RSA.SP, RSA.FK) |
292 | val r7b = { | 301 | val r8c = |
293 | for (suffix <- List(Forward, Backward)) | 302 | for (suffix <- List(Forward, Backward)) |
294 | yield Rule.create( | 303 | yield Rule.create( |
295 | RSA.TQ(varU, varW, suffix), | 304 | RSA.SP, |
296 | RSA.AQ(varU, varV, suffix), | 305 | RSA.TQ(varV, varV, suffix) |
297 | RSA.TQ(varV, varW, suffix) | 306 | ) |
298 | ) | ||
299 | } | ||
300 | |||
301 | /* Rules 8x */ | ||
302 | val r8a = | ||
303 | for (v <- answer) | ||
304 | yield Rule.create(RSA.SP, RSA.QM, not(RSA.Named(v))) | ||
305 | val r8b = | ||
306 | Rule.create(RSA.SP, RSA.FK) | ||
307 | val r8c = | ||
308 | for (suffix <- List(Forward, Backward)) | ||
309 | yield Rule.create( | ||
310 | RSA.SP, | ||
311 | RSA.TQ(varV, varV, suffix) | ||
312 | ) | ||
313 | 307 | ||
314 | /* Rule 9 */ | 308 | /** Determine answers to the query |
315 | val r9 = Rule.create(RSA.Ans, RSA.QM, not(RSA.SP)) | 309 | * |
310 | * Answers are identified by predicate `rsa:Ans`. In case the | ||
311 | * input query is a BCQ (answer is just true/false), we derive | ||
312 | * `rsa:Ans` for a fresh constant `c`. Later on we can query for | ||
313 | * instances of `rsa:Ans` as follows | ||
314 | * | ||
315 | * {{{ | ||
316 | * ASK { ?X a rsa:Ans } | ||
317 | * }}} | ||
318 | * | ||
319 | * to determine whether the query is true or false. | ||
320 | * | ||
321 | * @note corresponds to rule 9 in Table 3. | ||
322 | */ | ||
323 | val r9 = Rule.create(RSA.Ans, RSA.QM, not(RSA.SP)) | ||
316 | 324 | ||
317 | r1 :: | 325 | (r1 :: r2 ::: |
318 | r3a ::: r3b :: r3c :: | 326 | r3a ::: r3b :: r3c :: |
319 | r4c ::: r4b ::: r4a ::: | 327 | r4a ::: r4b ::: r4c ::: |
320 | r5c ::: r5b ::: r5a ::: | 328 | // r5c ::: r5b ::: r5a ::: |
321 | r6 ::: | 329 | r6 ::: r7b ::: r7a ::: |
322 | r7b ::: r7a ::: | 330 | r8a ::: r8b :: r8c ::: |
323 | r8a ::: r8b :: r8c ::: | 331 | r9 :: List()) map reifyRule |
324 | r9 :: | 332 | } |
325 | List() | ||
326 | } | ||
327 | 333 | ||
328 | private def reifyAtom(atom: Atom): (Option[BindAtom], List[Atom]) = { | 334 | private def reifyAtom(atom: Atom): (Option[BindAtom], List[Atom]) = { |
329 | atom match { | 335 | atom match { |
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/sparql/ConjunctiveQuery.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/sparql/ConjunctiveQuery.scala index e77a913..59eb626 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/sparql/ConjunctiveQuery.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/sparql/ConjunctiveQuery.scala | |||
@@ -3,6 +3,7 @@ package uk.ac.ox.cs.rsacomb.sparql | |||
3 | import java.util.{Map => JMap, HashMap => JHashMap} | 3 | import java.util.{Map => JMap, HashMap => JHashMap} |
4 | import tech.oxfordsemantic.jrdfox.Prefixes | 4 | import tech.oxfordsemantic.jrdfox.Prefixes |
5 | import tech.oxfordsemantic.jrdfox.client.DataStoreConnection | 5 | import tech.oxfordsemantic.jrdfox.client.DataStoreConnection |
6 | import tech.oxfordsemantic.jrdfox.logic.datalog.TupleTableAtom | ||
6 | import tech.oxfordsemantic.jrdfox.logic.expression.Variable | 7 | import tech.oxfordsemantic.jrdfox.logic.expression.Variable |
7 | import tech.oxfordsemantic.jrdfox.logic.sparql.pattern.{ | 8 | import tech.oxfordsemantic.jrdfox.logic.sparql.pattern.{ |
8 | ConjunctionPattern, | 9 | ConjunctionPattern, |
@@ -77,32 +78,48 @@ class ConjunctiveQuery( | |||
77 | */ | 78 | */ |
78 | val bcq: Boolean = select.isEmpty && !query.getAllPossibleVariables | 79 | val bcq: Boolean = select.isEmpty && !query.getAllPossibleVariables |
79 | 80 | ||
80 | /** Returns the full set of variables involved in the query. */ | 81 | /** Returns the query body as a sequence of atoms (triples). */ |
81 | val variables: Set[Variable] = | 82 | val atoms: List[TupleTableAtom] = |
82 | where match { | 83 | where match { |
83 | case b: ConjunctionPattern => { | 84 | case b: ConjunctionPattern => { |
84 | b.getConjuncts.toSet.flatMap { conj: QueryPattern => | 85 | b.getConjuncts.toList.flatMap { conj: QueryPattern => |
85 | conj match { | 86 | conj match { |
86 | case c: TriplePattern => | 87 | case c: TriplePattern => |
87 | Set(c.getSubject, c.getPredicate, c.getObject).collect { | 88 | Seq( |
88 | case v: Variable => v | 89 | TupleTableAtom.rdf(c.getSubject, c.getPredicate, c.getObject) |
89 | } | 90 | ) |
90 | case _ => Set() | 91 | case _ => List() |
91 | } | 92 | } |
92 | } | 93 | } |
93 | } | 94 | } |
94 | case _ => Set() | 95 | case _ => List() |
95 | } | 96 | } |
96 | 97 | ||
98 | /** Returns the full collection of variables involved in the query. */ | ||
99 | val variables: List[Variable] = (where match { | ||
100 | case b: ConjunctionPattern => { | ||
101 | b.getConjuncts.toList.flatMap { conj: QueryPattern => | ||
102 | conj match { | ||
103 | case c: TriplePattern => | ||
104 | Set(c.getSubject, c.getPredicate, c.getObject).collect { | ||
105 | case v: Variable => v | ||
106 | } | ||
107 | case _ => List() | ||
108 | } | ||
109 | } | ||
110 | } | ||
111 | case _ => List() | ||
112 | }).distinct | ||
113 | |||
97 | /** Returns the collection of answer variables in the query. */ | 114 | /** Returns the collection of answer variables in the query. */ |
98 | val answer: Set[Variable] = | 115 | val answer: List[Variable] = |
99 | if (query.getAllPossibleVariables) | 116 | if (query.getAllPossibleVariables) |
100 | variables | 117 | variables |
101 | else | 118 | else |
102 | select.map(_.getVariable).toSet | 119 | select.map(_.getVariable).toList.distinct |
103 | 120 | ||
104 | /** Returns the collection of bounded (existential) variables in the query. */ | 121 | /** Returns the collection of bounded (existential) variables in the query. */ |
105 | val bounded: Set[Variable] = variables &~ answer | 122 | val bounded: List[Variable] = variables diff answer |
106 | 123 | ||
107 | override def toString(): String = query.toString | 124 | override def toString(): String = query.toString |
108 | } | 125 | } |
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala index 6c8d42d..4b04c40 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala | |||
@@ -18,6 +18,7 @@ import org.semanticweb.owlapi.model.{ | |||
18 | OWLObjectPropertyExpression | 18 | OWLObjectPropertyExpression |
19 | } | 19 | } |
20 | 20 | ||
21 | import uk.ac.ox.cs.rsacomb.sparql.ConjunctiveQuery | ||
21 | import uk.ac.ox.cs.rsacomb.suffix.RSASuffix | 22 | import uk.ac.ox.cs.rsacomb.suffix.RSASuffix |
22 | 23 | ||
23 | // Debug only | 24 | // Debug only |
@@ -28,7 +29,7 @@ object RSA { | |||
28 | val Prefixes: Prefixes = new Prefixes() | 29 | val Prefixes: Prefixes = new Prefixes() |
29 | Prefixes.declarePrefix("rsa:", "http://www.cs.ox.ac.uk/isg/rsa/") | 30 | Prefixes.declarePrefix("rsa:", "http://www.cs.ox.ac.uk/isg/rsa/") |
30 | 31 | ||
31 | private def atom(name: IRI, vars: List[Term]) = | 32 | private def atom(name: IRI, vars: List[Term]): TupleTableAtom = |
32 | TupleTableAtom.create(TupleTableName.create(name.getIRI), vars: _*) | 33 | TupleTableAtom.create(TupleTableName.create(name.getIRI), vars: _*) |
33 | 34 | ||
34 | def PE(t1: Term, t2: Term) = | 35 | def PE(t1: Term, t2: Term) = |
@@ -45,14 +46,11 @@ object RSA { | |||
45 | def Congruent(t1: Term, t2: Term) = | 46 | def Congruent(t1: Term, t2: Term) = |
46 | TupleTableAtom.rdf(t1, RSA("congruent"), t2) | 47 | TupleTableAtom.rdf(t1, RSA("congruent"), t2) |
47 | 48 | ||
48 | def QM(implicit variables: (List[Term], List[Term])) = { | 49 | def QM(implicit q: ConjunctiveQuery) = |
49 | val (answer, bounded) = variables | 50 | atom(RSA("QM"), q.answer ::: q.bounded) |
50 | atom(RSA("QM"), answer ::: bounded) | ||
51 | } | ||
52 | 51 | ||
53 | def ID(t1: Term, t2: Term)(implicit variables: (List[Term], List[Term])) = { | 52 | def ID(t1: Term, t2: Term)(implicit q: ConjunctiveQuery) = { |
54 | val (answer, bounded) = variables | 53 | atom(RSA("ID"), (q.answer ::: q.bounded) :+ t1 :+ t2) |
55 | atom(RSA("ID"), (answer ::: bounded) :+ t1 :+ t2) | ||
56 | } | 54 | } |
57 | 55 | ||
58 | def Named(t: Term) = | 56 | def Named(t: Term) = |
@@ -64,33 +62,23 @@ object RSA { | |||
64 | def NI(t: Term) = | 62 | def NI(t: Term) = |
65 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, RSA("NI")) | 63 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, RSA("NI")) |
66 | 64 | ||
67 | def TQ(t1: Term, t2: Term, sx: RSASuffix)(implicit | 65 | def TQ(t1: Term, t2: Term, sx: RSASuffix)(implicit q: ConjunctiveQuery) = |
68 | variables: (List[Term], List[Term]) | 66 | atom(RSA("TQ" :: sx), (q.answer ::: q.bounded) :+ t1 :+ t2) |
69 | ) = { | ||
70 | val (answer, bounded) = variables | ||
71 | atom(RSA("TQ" :: sx), (answer ::: bounded) :+ t1 :+ t2) | ||
72 | } | ||
73 | 67 | ||
74 | def AQ(t1: Term, t2: Term, sx: RSASuffix)(implicit | 68 | def AQ(t1: Term, t2: Term, sx: RSASuffix)(implicit q: ConjunctiveQuery) = |
75 | variables: (List[Term], List[Term]) | 69 | atom(RSA("AQ" :: sx), (q.answer ::: q.bounded) :+ t1 :+ t2) |
76 | ) = { | ||
77 | val (answer, bounded) = variables | ||
78 | atom(RSA("AQ" :: sx), (answer ::: bounded) :+ t1 :+ t2) | ||
79 | } | ||
80 | 70 | ||
81 | def FK(implicit variables: (List[Term], List[Term])) = { | 71 | def FK(implicit q: ConjunctiveQuery) = |
82 | val (answer, bounded) = variables | 72 | atom(RSA("FK"), q.answer ::: q.bounded) |
83 | atom(RSA("FK"), answer ::: bounded) | ||
84 | } | ||
85 | 73 | ||
86 | def SP(implicit variables: (List[Term], List[Term])) = { | 74 | def SP(implicit q: ConjunctiveQuery) = |
87 | val (answer, bounded) = variables | 75 | atom(RSA("SP"), q.answer ::: q.bounded) |
88 | atom(RSA("SP"), answer ::: bounded) | ||
89 | } | ||
90 | 76 | ||
91 | def Ans(implicit variables: (List[Term], List[Term])) = { | 77 | def Ans(implicit q: ConjunctiveQuery) = { |
92 | val (answer, _) = variables | 78 | if (q.bcq) |
93 | atom(RSA("Ans"), answer) | 79 | TupleTableAtom.rdf(RSA("blank"), IRI.RDF_TYPE, RSA("Ans")) |
80 | else | ||
81 | atom(RSA("Ans"), q.answer) | ||
94 | } | 82 | } |
95 | 83 | ||
96 | def apply(name: Any): IRI = | 84 | def apply(name: Any): IRI = |