diff options
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 | } | ||
