diff options
Diffstat (limited to 'src/main/scala')
6 files changed, 641 insertions, 129 deletions
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala index 0f3b16a..bcc336a 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala | |||
| @@ -3,9 +3,10 @@ package uk.ac.ox.cs.rsacomb | |||
| 3 | import org.semanticweb.owlapi.model.{OWLObjectInverseOf, OWLObjectProperty} | 3 | import org.semanticweb.owlapi.model.{OWLObjectInverseOf, OWLObjectProperty} |
| 4 | import org.semanticweb.owlapi.model.{ | 4 | import org.semanticweb.owlapi.model.{ |
| 5 | OWLClass, | 5 | OWLClass, |
| 6 | OWLLogicalAxiom, | ||
| 6 | // OWLObjectProperty, | 7 | // OWLObjectProperty, |
| 7 | OWLSubObjectPropertyOfAxiom, | 8 | OWLSubObjectPropertyOfAxiom, |
| 8 | // OWLObjectPropertyExpression, | 9 | OWLObjectPropertyExpression, |
| 9 | OWLObjectSomeValuesFrom, | 10 | OWLObjectSomeValuesFrom, |
| 10 | OWLSubClassOfAxiom | 11 | OWLSubClassOfAxiom |
| 11 | } | 12 | } |
| @@ -25,10 +26,11 @@ import tech.oxfordsemantic.jrdfox.logic.expression.{ | |||
| 25 | 26 | ||
| 26 | import uk.ac.ox.cs.rsacomb.converter.{ | 27 | import uk.ac.ox.cs.rsacomb.converter.{ |
| 27 | SkolemStrategy, | 28 | SkolemStrategy, |
| 28 | RDFoxAxiomConverter, | 29 | RDFoxConverter |
| 29 | RDFoxPropertyExprConverter | 30 | // RDFoxAxiomConverter, |
| 31 | // RDFoxPropertyExprConverter | ||
| 30 | } | 32 | } |
| 31 | import uk.ac.ox.cs.rsacomb.suffix.{Empty, Forward, Backward, Inverse} | 33 | import uk.ac.ox.cs.rsacomb.suffix._ |
| 32 | import uk.ac.ox.cs.rsacomb.util.RSA | 34 | import uk.ac.ox.cs.rsacomb.util.RSA |
| 33 | 35 | ||
| 34 | class CanonicalModel(val ontology: RSAOntology) { | 36 | class CanonicalModel(val ontology: RSAOntology) { |
| @@ -107,22 +109,28 @@ class CanonicalModel(val ontology: RSAOntology) { | |||
| 107 | ) | 109 | ) |
| 108 | } | 110 | } |
| 109 | 111 | ||
| 110 | val rules: List[Rule] = { | 112 | val (facts, rules): (List[TupleTableAtom], List[Rule]) = { |
| 111 | // Compute rules from ontology axioms | 113 | // Compute rules from ontology axioms |
| 112 | val rules = ontology.axioms.flatMap(_.accept(RuleGenerator)) | 114 | val (facts, rules) = { |
| 113 | // Return full set of rules | 115 | val term = RSAOntology.genFreshVariable() |
| 114 | rules ::: rolesAdditionalRules ::: topAxioms ::: equalityAxioms | 116 | val unsafe = ontology.unsafeRoles |
| 117 | val skolem = SkolemStrategy.None | ||
| 118 | val suffix = Empty | ||
| 119 | ontology.axioms | ||
| 120 | .map(CanonicalModelConverter.convert(_, term, unsafe, skolem, suffix)) | ||
| 121 | .unzip | ||
| 122 | } | ||
| 123 | ( | ||
| 124 | facts.flatten, | ||
| 125 | rolesAdditionalRules ::: topAxioms ::: equalityAxioms ::: rules.flatten | ||
| 126 | ) | ||
| 115 | } | 127 | } |
| 116 | 128 | ||
| 117 | object RuleGenerator | 129 | object CanonicalModelConverter extends RDFoxConverter { |
| 118 | extends RDFoxAxiomConverter( | ||
| 119 | Variable.create("X"), | ||
| 120 | ontology.unsafeRoles, | ||
| 121 | SkolemStrategy.None, | ||
| 122 | Empty | ||
| 123 | ) { | ||
| 124 | 130 | ||
| 125 | private def rules1(axiom: OWLSubClassOfAxiom): List[Rule] = { | 131 | private def rules1( |
| 132 | axiom: OWLSubClassOfAxiom | ||
| 133 | ): Result = { | ||
| 126 | val unfold = ontology.unfold(axiom).toList | 134 | val unfold = ontology.unfold(axiom).toList |
| 127 | // Fresh Variables | 135 | // Fresh Variables |
| 128 | val v0 = RSA("v0_" ++ axiom.hashed) | 136 | val v0 = RSA("v0_" ++ axiom.hashed) |
| @@ -134,13 +142,9 @@ class CanonicalModel(val ontology: RSAOntology) { | |||
| 134 | TupleTableAtom.rdf(varX, IRI.RDF_TYPE, cls) | 142 | TupleTableAtom.rdf(varX, IRI.RDF_TYPE, cls) |
| 135 | } | 143 | } |
| 136 | val roleRf: TupleTableAtom = { | 144 | val roleRf: TupleTableAtom = { |
| 137 | val visitor = | 145 | val prop = |
| 138 | new RDFoxPropertyExprConverter(varX, v0, Forward) | 146 | axiom.getSuperClass.asInstanceOf[OWLObjectSomeValuesFrom].getProperty |
| 139 | axiom.getSuperClass | 147 | super.convert(prop, varX, v0, Forward) |
| 140 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
| 141 | .getProperty | ||
| 142 | .accept(visitor) | ||
| 143 | .head | ||
| 144 | } | 148 | } |
| 145 | val atomB: TupleTableAtom = { | 149 | val atomB: TupleTableAtom = { |
| 146 | val cls = axiom.getSuperClass | 150 | val cls = axiom.getSuperClass |
| @@ -154,12 +158,12 @@ class CanonicalModel(val ontology: RSAOntology) { | |||
| 154 | // returning facts as `Rule`s with true body. While this is correct | 158 | // returning facts as `Rule`s with true body. While this is correct |
| 155 | // there is an easier way to import facts into RDFox. Are we able to | 159 | // there is an easier way to import facts into RDFox. Are we able to |
| 156 | // do that? | 160 | // do that? |
| 157 | val facts = unfold.map(x => Rule.create(RSA.In(x))) | 161 | val facts = unfold map RSA.In |
| 158 | val rules = List( | 162 | val rules = List( |
| 159 | Rule.create(roleRf, atomA, RSA.NotIn(varX)), | 163 | Rule.create(roleRf, atomA, RSA.NotIn(varX)), |
| 160 | Rule.create(atomB, atomA, RSA.NotIn(varX)) | 164 | Rule.create(atomB, atomA, RSA.NotIn(varX)) |
| 161 | ) | 165 | ) |
| 162 | facts ++ rules | 166 | (facts, rules) |
| 163 | } | 167 | } |
| 164 | 168 | ||
| 165 | private def rules2(axiom: OWLSubClassOfAxiom): List[Rule] = { | 169 | private def rules2(axiom: OWLSubClassOfAxiom): List[Rule] = { |
| @@ -177,11 +181,8 @@ class CanonicalModel(val ontology: RSAOntology) { | |||
| 177 | val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI | 181 | val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI |
| 178 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) | 182 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) |
| 179 | } | 183 | } |
| 180 | def roleRf(t1: Term, t2: Term): TupleTableAtom = { | 184 | def roleRf(t1: Term, t2: Term): TupleTableAtom = |
| 181 | val visitor = | 185 | super.convert(roleR, t1, t2, Forward) |
| 182 | new RDFoxPropertyExprConverter(t1, t2, Forward) | ||
| 183 | roleR.accept(visitor).head | ||
| 184 | } | ||
| 185 | def atomB(t: Term): TupleTableAtom = { | 186 | def atomB(t: Term): TupleTableAtom = { |
| 186 | val cls = axiom.getSuperClass | 187 | val cls = axiom.getSuperClass |
| 187 | .asInstanceOf[OWLObjectSomeValuesFrom] | 188 | .asInstanceOf[OWLObjectSomeValuesFrom] |
| @@ -215,11 +216,8 @@ class CanonicalModel(val ontology: RSAOntology) { | |||
| 215 | val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI | 216 | val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI |
| 216 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) | 217 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) |
| 217 | } | 218 | } |
| 218 | def roleRf(t: Term): TupleTableAtom = { | 219 | def roleRf(t: Term): TupleTableAtom = |
| 219 | val visitor = | 220 | super.convert(roleR, t, v1, Forward) |
| 220 | new RDFoxPropertyExprConverter(t, v1, Forward) | ||
| 221 | roleR.accept(visitor).head | ||
| 222 | } | ||
| 223 | val atomB: TupleTableAtom = { | 221 | val atomB: TupleTableAtom = { |
| 224 | val cls = axiom.getSuperClass | 222 | val cls = axiom.getSuperClass |
| 225 | .asInstanceOf[OWLObjectSomeValuesFrom] | 223 | .asInstanceOf[OWLObjectSomeValuesFrom] |
| @@ -236,46 +234,37 @@ class CanonicalModel(val ontology: RSAOntology) { | |||
| 236 | } | 234 | } |
| 237 | } | 235 | } |
| 238 | 236 | ||
| 239 | override def visit(axiom: OWLSubClassOfAxiom): List[Rule] = { | 237 | override def convert( |
| 240 | if (axiom.isT5) { | 238 | axiom: OWLLogicalAxiom, |
| 241 | // TODO: get role in T5 axiom | 239 | term: Term, |
| 242 | // Assuming one role here | 240 | unsafe: List[OWLObjectPropertyExpression], |
| 243 | val role = axiom.objectPropertyExpressionsInSignature(0) | 241 | skolem: SkolemStrategy, |
| 244 | if (ontology.unsafeRoles contains role) { | 242 | suffix: RSASuffix |
| 245 | val visitor = | 243 | ): Result = |
| 246 | new RDFoxAxiomConverter( | 244 | axiom match { |
| 247 | Variable.create("X"), | 245 | |
| 248 | ontology.unsafeRoles, | 246 | case a: OWLSubClassOfAxiom if a.isT5 => { |
| 249 | SkolemStrategy.Standard(axiom.toString), | 247 | val role = axiom.objectPropertyExpressionsInSignature(0) |
| 250 | Forward | 248 | if (unsafe contains role) { |
| 251 | ) | 249 | val skolem = SkolemStrategy.Standard(a.toString) |
| 252 | axiom.accept(visitor) | 250 | super.convert(a, term, unsafe, skolem, Forward) |
| 253 | } else { | 251 | } else { |
| 254 | rules1(axiom) ::: rules2(axiom) ::: rules3(axiom) | 252 | val (f1, r1) = rules1(a) |
| 253 | (f1, r1 ::: rules2(a) ::: rules3(a)) | ||
| 254 | } | ||
| 255 | } | 255 | } |
| 256 | } else { | ||
| 257 | // Fallback to standard OWL to LP translation | ||
| 258 | super.visit(axiom) | ||
| 259 | } | ||
| 260 | } | ||
| 261 | 256 | ||
| 262 | override def visit(axiom: OWLSubObjectPropertyOfAxiom): List[Rule] = { | 257 | case a: OWLSubObjectPropertyOfAxiom => { |
| 263 | val varX = Variable.create("X") | 258 | val (factsF, rulesF) = |
| 264 | val visitorF = new RDFoxAxiomConverter( | 259 | super.convert(a, term, unsafe, SkolemStrategy.None, Forward) |
| 265 | varX, | 260 | val (factsB, rulesB) = |
| 266 | ontology.unsafeRoles, | 261 | super.convert(a, term, unsafe, SkolemStrategy.None, Backward) |
| 267 | SkolemStrategy.None, | 262 | (factsF ::: factsB, rulesF ::: rulesB) |
| 268 | Forward | 263 | } |
| 269 | ) | ||
| 270 | val visitorB = new RDFoxAxiomConverter( | ||
| 271 | varX, | ||
| 272 | ontology.unsafeRoles, | ||
| 273 | SkolemStrategy.None, | ||
| 274 | Backward | ||
| 275 | ) | ||
| 276 | axiom.accept(visitorB) ::: axiom.accept(visitorF) | ||
| 277 | } | ||
| 278 | 264 | ||
| 265 | case a => super.convert(a, term, unsafe, skolem, suffix) | ||
| 266 | |||
| 267 | } | ||
| 279 | } | 268 | } |
| 280 | 269 | ||
| 281 | } | 270 | } |
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 52be937..4e533c6 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala | |||
| @@ -76,6 +76,15 @@ class FilteringProgram(query: ConjunctiveQuery, constants: List[Term]) { | |||
| 76 | val nis: Rule = | 76 | val nis: Rule = |
| 77 | Rule.create(RSA.NI(varX), RSA.Congruent(varX, varY), RSA.Named(varY)) | 77 | Rule.create(RSA.NI(varX), RSA.Congruent(varX, varY), RSA.Named(varY)) |
| 78 | 78 | ||
| 79 | /** Initializes instances of `rsa:Named`. | ||
| 80 | * | ||
| 81 | * They represent the set of constants appearing in the original | ||
| 82 | * ontology. | ||
| 83 | * | ||
| 84 | * @note corresponds to rules 2 in Table 3. | ||
| 85 | */ | ||
| 86 | val facts = constants map RSA.Named | ||
| 87 | |||
| 79 | /** Collection of filtering program rules. */ | 88 | /** Collection of filtering program rules. */ |
| 80 | val rules: List[Rule] = | 89 | val rules: List[Rule] = |
| 81 | nis :: { | 90 | nis :: { |
| @@ -89,15 +98,6 @@ class FilteringProgram(query: ConjunctiveQuery, constants: List[Term]) { | |||
| 89 | */ | 98 | */ |
| 90 | val r1 = Rule.create(RSA.QM, query.atoms: _*) | 99 | val r1 = Rule.create(RSA.QM, query.atoms: _*) |
| 91 | 100 | ||
| 92 | /** Initializes instances of `rsa:Named`. | ||
| 93 | * | ||
| 94 | * They represent the set of constants appearing in the original | ||
| 95 | * ontology. | ||
| 96 | * | ||
| 97 | * @note corresponds to rules 2 in Table 3. | ||
| 98 | */ | ||
| 99 | val r2 = constants.map(c => Rule.create(RSA.Named(c))) | ||
| 100 | |||
| 101 | /** Initializes instances of `rsa:ID`. | 101 | /** Initializes instances of `rsa:ID`. |
| 102 | * | 102 | * |
| 103 | * They are initialized as a minimal congruence relation over the | 103 | * They are initialized as a minimal congruence relation over the |
| @@ -307,7 +307,7 @@ class FilteringProgram(query: ConjunctiveQuery, constants: List[Term]) { | |||
| 307 | */ | 307 | */ |
| 308 | val r9 = Rule.create(RSA.Ans, RSA.QM, not(RSA.SP)) | 308 | val r9 = Rule.create(RSA.Ans, RSA.QM, not(RSA.SP)) |
| 309 | 309 | ||
| 310 | (r1 :: r2 ::: | 310 | (r1 :: |
| 311 | r3a ::: r3b :: r3c :: | 311 | r3a ::: r3b :: r3c :: |
| 312 | r4a ::: r4b ::: r4c ::: | 312 | r4a ::: r4b ::: r4c ::: |
| 313 | r5a ::: r5b ::: r5c ::: | 313 | r5a ::: r5b ::: r5c ::: |
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 4dd554a..a965ef9 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala | |||
| @@ -7,13 +7,15 @@ import java.util.stream.{Collectors, Stream} | |||
| 7 | import java.io.File | 7 | import java.io.File |
| 8 | import org.semanticweb.owlapi.apibinding.OWLManager | 8 | import org.semanticweb.owlapi.apibinding.OWLManager |
| 9 | import org.semanticweb.owlapi.util.OWLOntologyMerger | 9 | import org.semanticweb.owlapi.util.OWLOntologyMerger |
| 10 | import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom} | 10 | import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom, OWLLogicalAxiom} |
| 11 | import org.semanticweb.owlapi.model.{ | 11 | import org.semanticweb.owlapi.model.{ |
| 12 | OWLClass, | 12 | OWLClass, |
| 13 | OWLClassExpression, | ||
| 13 | OWLObjectProperty, | 14 | OWLObjectProperty, |
| 14 | OWLSubObjectPropertyOfAxiom, | 15 | OWLSubObjectPropertyOfAxiom, |
| 15 | OWLObjectPropertyExpression, | 16 | OWLObjectPropertyExpression, |
| 16 | OWLObjectSomeValuesFrom, | 17 | OWLObjectSomeValuesFrom, |
| 18 | OWLDataSomeValuesFrom, | ||
| 17 | OWLSubClassOfAxiom | 19 | OWLSubClassOfAxiom |
| 18 | } | 20 | } |
| 19 | import org.semanticweb.owlapi.model.parameters.Imports | 21 | import org.semanticweb.owlapi.model.parameters.Imports |
| @@ -48,7 +50,7 @@ import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer | |||
| 48 | import tech.oxfordsemantic.jrdfox.logic._ | 50 | import tech.oxfordsemantic.jrdfox.logic._ |
| 49 | import org.semanticweb.owlapi.model.OWLObjectInverseOf | 51 | import org.semanticweb.owlapi.model.OWLObjectInverseOf |
| 50 | 52 | ||
| 51 | import uk.ac.ox.cs.rsacomb.converter.{RDFoxAxiomConverter, SkolemStrategy} | 53 | import uk.ac.ox.cs.rsacomb.converter.{RDFoxConverter, SkolemStrategy} |
| 52 | import uk.ac.ox.cs.rsacomb.suffix._ | 54 | import uk.ac.ox.cs.rsacomb.suffix._ |
| 53 | import uk.ac.ox.cs.rsacomb.sparql._ | 55 | import uk.ac.ox.cs.rsacomb.sparql._ |
| 54 | import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} | 56 | import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} |
| @@ -82,30 +84,28 @@ object RSAOntology { | |||
| 82 | class RSAOntology(val ontology: OWLOntology) { | 84 | class RSAOntology(val ontology: OWLOntology) { |
| 83 | 85 | ||
| 84 | import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._ | 86 | import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._ |
| 87 | import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ | ||
| 85 | 88 | ||
| 86 | // Gather TBox/RBox/ABox from original ontology | 89 | // Gather TBox/RBox/ABox from original ontology |
| 87 | val tbox: List[OWLAxiom] = | 90 | val tbox: List[OWLLogicalAxiom] = |
| 88 | ontology | 91 | ontology |
| 89 | .tboxAxioms(Imports.INCLUDED) | 92 | .tboxAxioms(Imports.INCLUDED) |
| 90 | .collect(Collectors.toList()) | 93 | .collect(Collectors.toList()) |
| 91 | .asScala | 94 | .collect { case a: OWLLogicalAxiom => a } |
| 92 | .toList | ||
| 93 | 95 | ||
| 94 | val rbox: List[OWLAxiom] = | 96 | val rbox: List[OWLLogicalAxiom] = |
| 95 | ontology | 97 | ontology |
| 96 | .rboxAxioms(Imports.INCLUDED) | 98 | .rboxAxioms(Imports.INCLUDED) |
| 97 | .collect(Collectors.toList()) | 99 | .collect(Collectors.toList()) |
| 98 | .asScala | 100 | .collect { case a: OWLLogicalAxiom => a } |
| 99 | .toList | ||
| 100 | 101 | ||
| 101 | val abox: List[OWLAxiom] = | 102 | val abox: List[OWLLogicalAxiom] = |
| 102 | ontology | 103 | ontology |
| 103 | .aboxAxioms(Imports.INCLUDED) | 104 | .aboxAxioms(Imports.INCLUDED) |
| 104 | .collect(Collectors.toList()) | 105 | .collect(Collectors.toList()) |
| 105 | .asScala | 106 | .collect { case a: OWLLogicalAxiom => a } |
| 106 | .toList | ||
| 107 | 107 | ||
| 108 | val axioms: List[OWLAxiom] = abox ::: tbox ::: rbox | 108 | val axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox |
| 109 | 109 | ||
| 110 | /* Retrieve individuals in the original ontology | 110 | /* Retrieve individuals in the original ontology |
| 111 | */ | 111 | */ |
| @@ -152,28 +152,54 @@ class RSAOntology(val ontology: OWLOntology) { | |||
| 152 | //println("\nUnsafe roles:") | 152 | //println("\nUnsafe roles:") |
| 153 | //println(unsafe) | 153 | //println(unsafe) |
| 154 | 154 | ||
| 155 | object RSAConverter extends RDFoxConverter { | ||
| 156 | |||
| 157 | override def convert( | ||
| 158 | expr: OWLClassExpression, | ||
| 159 | term: Term, | ||
| 160 | unsafe: List[OWLObjectPropertyExpression], | ||
| 161 | skolem: SkolemStrategy, | ||
| 162 | suffix: RSASuffix | ||
| 163 | ): Shards = | ||
| 164 | (expr, skolem) match { | ||
| 165 | |||
| 166 | case (e: OWLObjectSomeValuesFrom, SkolemStrategy.Constant(c)) | ||
| 167 | if unsafe contains e.getProperty => { | ||
| 168 | val (res, ext) = super.convert(e, term, unsafe, skolem, suffix) | ||
| 169 | (RSA.PE(term, c) :: RSA.U(c) :: res, ext) | ||
| 170 | } | ||
| 171 | |||
| 172 | case (e: OWLDataSomeValuesFrom, SkolemStrategy.Constant(c)) | ||
| 173 | if unsafe contains e.getProperty => { | ||
| 174 | val (res, ext) = super.convert(e, term, unsafe, skolem, suffix) | ||
| 175 | (RSA.PE(term, c) :: RSA.U(c) :: res, ext) | ||
| 176 | } | ||
| 177 | |||
| 178 | case _ => super.convert(expr, term, unsafe, skolem, suffix) | ||
| 179 | } | ||
| 180 | |||
| 181 | } | ||
| 182 | |||
| 155 | /* Ontology convertion into LP rules */ | 183 | /* Ontology convertion into LP rules */ |
| 156 | val datalog = for { | 184 | val term = RSAOntology.genFreshVariable() |
| 157 | axiom <- axioms | 185 | val datalog = axioms |
| 158 | visitor = new RDFoxAxiomConverter( | 186 | .map(a => { |
| 159 | RSAOntology.genFreshVariable(), | 187 | val skolem = SkolemStrategy.Constant(a.toString) |
| 160 | unsafe, | 188 | RSAConverter.convert(a, term, unsafe, skolem, Empty) |
| 161 | SkolemStrategy.ConstantRSA(axiom.toString), | 189 | }) |
| 162 | Empty | 190 | .unzip |
| 163 | ) | 191 | val facts = datalog._1.flatten |
| 164 | rule <- axiom.accept(visitor) | 192 | val rules = datalog._2.flatten |
| 165 | } yield rule | ||
| 166 | 193 | ||
| 167 | /* DEBUG: print datalog rules */ | 194 | /* DEBUG: print datalog rules */ |
| 168 | println("\nDatalog roles:") | 195 | //println("\nDatalog rules:") |
| 169 | datalog.foreach(println) | 196 | //rules.foreach(println) |
| 170 | 197 | ||
| 171 | // Open connection with RDFox | 198 | // Open connection with RDFox |
| 172 | val (server, data) = RDFoxUtil.openConnection("RSACheck") | 199 | val (server, data) = RDFoxUtil.openConnection("RSACheck") |
| 173 | // Add Data (hardcoded for now) | ||
| 174 | //data.importData(UpdateType.ADDITION, RSA.Prefixes, ":a a :A .") | ||
| 175 | 200 | ||
| 176 | /* Add built-in rules | 201 | /* Add built-in rules |
| 202 | * TODO: substitute with RDFoxUtil.addRules | ||
| 177 | */ | 203 | */ |
| 178 | data.importData( | 204 | data.importData( |
| 179 | UpdateType.ADDITION, | 205 | UpdateType.ADDITION, |
| @@ -181,20 +207,11 @@ class RSAOntology(val ontology: OWLOntology) { | |||
| 181 | "rsa:E[?X,?Y] :- rsa:PE[?X,?Y], rsa:U[?X], rsa:U[?Y] ." | 207 | "rsa:E[?X,?Y] :- rsa:PE[?X,?Y], rsa:U[?X], rsa:U[?Y] ." |
| 182 | ) | 208 | ) |
| 183 | 209 | ||
| 184 | /* Add built-in rules | 210 | /* Add ontology facts and rules */ |
| 185 | */ | 211 | RDFoxUtil.addFacts(data, facts) |
| 186 | // data.importData( | 212 | RDFoxUtil.addRules(data, rules) |
| 187 | // UpdateType.ADDITION, | ||
| 188 | // RSA.Prefixes, | ||
| 189 | // "[?entity, a, ?superClass] :- [?entity, a, ?class], [?class, rdfs:subClassOf, ?superClass] ." | ||
| 190 | // ) | ||
| 191 | |||
| 192 | /* Add ontology rules | ||
| 193 | */ | ||
| 194 | data.addRules(datalog.asJava) | ||
| 195 | 213 | ||
| 196 | /* Build graph | 214 | /* Build graph */ |
| 197 | */ | ||
| 198 | val graph = this.rsaGraph(data); | 215 | val graph = this.rsaGraph(data); |
| 199 | //println(graph) | 216 | //println(graph) |
| 200 | 217 | ||
| @@ -267,8 +284,8 @@ class RSAOntology(val ontology: OWLOntology) { | |||
| 267 | ): Graph[Resource, UnDiEdge] = { | 284 | ): Graph[Resource, UnDiEdge] = { |
| 268 | val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }" | 285 | val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }" |
| 269 | val answers = RDFoxUtil.submitQuery(data, query, RSA.Prefixes).get | 286 | val answers = RDFoxUtil.submitQuery(data, query, RSA.Prefixes).get |
| 270 | var edges: Seq[UnDiEdge[Resource]] = answers.map { | 287 | var edges: Seq[UnDiEdge[Resource]] = answers.map { case Seq(n1, n2) => |
| 271 | case Seq(n1, n2) => UnDiEdge(n1, n2) | 288 | UnDiEdge(n1, n2) |
| 272 | } | 289 | } |
| 273 | Graph(edges: _*) | 290 | Graph(edges: _*) |
| 274 | } | 291 | } |
| @@ -310,8 +327,11 @@ class RSAOntology(val ontology: OWLOntology) { | |||
| 310 | def ask(query: ConjunctiveQuery): ConjunctiveQueryAnswers = { | 327 | def ask(query: ConjunctiveQuery): ConjunctiveQueryAnswers = { |
| 311 | import implicits.JavaCollections._ | 328 | import implicits.JavaCollections._ |
| 312 | val (server, data) = RDFoxUtil.openConnection(RSAOntology.DataStore) | 329 | val (server, data) = RDFoxUtil.openConnection(RSAOntology.DataStore) |
| 313 | data.addRules(this.canonicalModel.rules) | 330 | val filter = this.filteringProgram(query) |
| 314 | data.addRules(this.filteringProgram(query).rules) | 331 | RDFoxUtil.addRules(data, this.canonicalModel.rules) |
| 332 | RDFoxUtil.addFacts(data, this.canonicalModel.facts) | ||
| 333 | RDFoxUtil.addRules(data, filter.rules) | ||
| 334 | RDFoxUtil.addFacts(data, filter.facts) | ||
| 315 | val answers = RDFoxUtil | 335 | val answers = RDFoxUtil |
| 316 | .submitQuery( | 336 | .submitQuery( |
| 317 | data, | 337 | data, |
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxConverter.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxConverter.scala new file mode 100644 index 0000000..7fd4dbe --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxConverter.scala | |||
| @@ -0,0 +1,479 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb.converter | ||
| 2 | |||
| 3 | import java.util.stream.Collectors | ||
| 4 | import org.semanticweb.owlapi.model.{ | ||
| 5 | OWLAnnotationProperty, | ||
| 6 | OWLLogicalAxiom, | ||
| 7 | OWLClass, | ||
| 8 | OWLClassAssertionAxiom, | ||
| 9 | OWLClassExpression, | ||
| 10 | OWLDataProperty, | ||
| 11 | OWLDataPropertyDomainAxiom, | ||
| 12 | OWLDataPropertyExpression, | ||
| 13 | OWLDataSomeValuesFrom, | ||
| 14 | OWLEquivalentClassesAxiom, | ||
| 15 | OWLEquivalentObjectPropertiesAxiom, | ||
| 16 | OWLInverseObjectPropertiesAxiom, | ||
| 17 | OWLNamedIndividual, | ||
| 18 | OWLObjectIntersectionOf, | ||
| 19 | OWLObjectInverseOf, | ||
| 20 | OWLObjectMaxCardinality, | ||
| 21 | OWLObjectOneOf, | ||
| 22 | OWLObjectProperty, | ||
| 23 | OWLObjectPropertyAssertionAxiom, | ||
| 24 | OWLObjectPropertyDomainAxiom, | ||
| 25 | OWLObjectPropertyExpression, | ||
| 26 | OWLObjectPropertyRangeAxiom, | ||
| 27 | OWLObjectSomeValuesFrom, | ||
| 28 | OWLPropertyExpression, | ||
| 29 | OWLSubClassOfAxiom, | ||
| 30 | OWLSubObjectPropertyOfAxiom | ||
| 31 | } | ||
| 32 | import scala.collection.JavaConverters._ | ||
| 33 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ | ||
| 34 | BindAtom, | ||
| 35 | BodyFormula, | ||
| 36 | Rule, | ||
| 37 | TupleTableAtom | ||
| 38 | } | ||
| 39 | import tech.oxfordsemantic.jrdfox.logic.expression.{Term, IRI, FunctionCall} | ||
| 40 | import uk.ac.ox.cs.rsacomb.RSAOntology | ||
| 41 | import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Inverse} | ||
| 42 | import uk.ac.ox.cs.rsacomb.util.RSA | ||
| 43 | |||
| 44 | /** Horn-ALCHOIQ to RDFox axiom converter. | ||
| 45 | * | ||
| 46 | * Provides the tools to translate Horn-ALCHOIQ axioms into logic rules | ||
| 47 | * using RDFox syntax. | ||
| 48 | * | ||
| 49 | * @note the input axioms are assumed to be normalized. Trying to | ||
| 50 | * convert non normalized axioms might result in undefined behavious. | ||
| 51 | * We use the normalization defined in the main paper. | ||
| 52 | * | ||
| 53 | * @see [[https://github.com/KRR-Oxford/RSA-combined-approach GitHub repository]] | ||
| 54 | * for more information on the theoretical aspects of the system. | ||
| 55 | * | ||
| 56 | * @todo this is not ideal and it would be more sensible to prepend a | ||
| 57 | * normalization procedure that will prevent errors or unexpected | ||
| 58 | * results. | ||
| 59 | */ | ||
| 60 | trait RDFoxConverter { | ||
| 61 | |||
| 62 | /** Simplify conversion between Java and Scala collections */ | ||
| 63 | import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ | ||
| 64 | |||
| 65 | /** Simplify conversion between similar concepts in OWLAPI and RDFox | ||
| 66 | * abstract syntax. | ||
| 67 | */ | ||
| 68 | import uk.ac.ox.cs.rsacomb.implicits.RDFox._ | ||
| 69 | |||
| 70 | /** Represents the result of the conversion of a | ||
| 71 | * [[org.semanticweb.owlapi.model.OWLClassExpression OWLClassExpression]]. | ||
| 72 | * | ||
| 73 | * In general a class expression is translated into a list of | ||
| 74 | * [[tech.oxfordsemantic.jrdfox.logic.datalog.TupleTableAtom TupleTableAtoms]]. | ||
| 75 | * In some cases a class appearing on the right of a GCI might | ||
| 76 | * generate additional atoms that will appear in the body of the | ||
| 77 | * resulting formula. | ||
| 78 | * | ||
| 79 | * @example | ||
| 80 | * In `A ⊑ ≤1R.B`, translated as | ||
| 81 | * ``` | ||
| 82 | * y = z <- A(x), R(x,y), B(y), R(x,z), B(z) | ||
| 83 | * ``` | ||
| 84 | * the atom `≤1R.B` produces `y = z` to appear as head of the rule, | ||
| 85 | * along with a set of atoms for the body of the rule (namely | ||
| 86 | * `R(x,y), B(y), R(x,z), B(z)`). | ||
| 87 | */ | ||
| 88 | protected type Shards = (List[TupleTableAtom], List[BodyFormula]) | ||
| 89 | |||
| 90 | /** Represent the result of the conversion of | ||
| 91 | * [[org.semanticweb.owlapi.model.OWLLogicalAxiom OWLLogicalAxiom]]. | ||
| 92 | * | ||
| 93 | * In general we have assertion returning (a collection of) atoms, | ||
| 94 | * while other axioms that generate rules. | ||
| 95 | */ | ||
| 96 | protected type Result = (List[TupleTableAtom], List[Rule]) | ||
| 97 | protected def Result(): Result = (List(), List()) | ||
| 98 | protected def ResultF(atoms: List[TupleTableAtom]): Result = (atoms, List()) | ||
| 99 | protected def ResultR(rules: List[Rule]): Result = (List(), rules) | ||
| 100 | |||
| 101 | /** Converts a | ||
| 102 | * [[org.semanticweb.owlapi.model.OWLLogicalAxiom OWLLogicalAxiom]] | ||
| 103 | * into a collection of | ||
| 104 | * [[tech.oxfordsemantic.jrdfox.logic.datalog.TupleTableAtom TupleTableAtoms]] | ||
| 105 | * and | ||
| 106 | * [[tech.oxfordsemantic.jrdfox.logic.datalog.Rule Rules]]. | ||
| 107 | * | ||
| 108 | * @note not all possible axioms are handled correctly, and in | ||
| 109 | * general they are assumed to be normalised. Following is a list of | ||
| 110 | * all unhandled class expressions: | ||
| 111 | * - [[org.semanticweb.owlapi.model.OWLAsymmetricObjectPropertyAxiom OWLAsymmetricObjectPropertyAxiom]] | ||
| 112 | * - [[org.semanticweb.owlapi.model.OWLDataPropertyAssertionAxiom OWLDataPropertyAssertionAxiom]] | ||
| 113 | * - [[org.semanticweb.owlapi.model.OWLDataPropertyRangeAxiom OWLDataPropertyRangeAxiom]] | ||
| 114 | * - [[org.semanticweb.owlapi.model.OWLDatatypeDefinitionAxiom OWLDatatypeDefinitionAxiom]] | ||
| 115 | * - [[org.semanticweb.owlapi.model.OWLDifferentIndividualsAxiom OWLDifferentIndividualsAxiom]] | ||
| 116 | * - [[org.semanticweb.owlapi.model.OWLDisjointClassesAxiom OWLDisjointClassesAxiom]] | ||
| 117 | * - [[org.semanticweb.owlapi.model.OWLDisjointDataPropertiesAxiom OWLDisjointDataPropertiesAxiom]] | ||
| 118 | * - [[org.semanticweb.owlapi.model.OWLDisjointObjectPropertiesAxiom OWLDisjointObjectPropertiesAxiom]] | ||
| 119 | * - [[org.semanticweb.owlapi.model.OWLDisjointUnionAxiom OWLDisjointUnionAxiom]] | ||
| 120 | * - [[org.semanticweb.owlapi.model.OWLEquivalentDataPropertiesAxiom OWLEquivalentDataPropertiesAxiom]] | ||
| 121 | * - [[org.semanticweb.owlapi.model.OWLFunctionalDataPropertyAxiom OWLFunctionalDataPropertyAxiom]] | ||
| 122 | * - [[org.semanticweb.owlapi.model.OWLFunctionalObjectPropertyAxiom OWLFunctionalObjectPropertyAxiom]] | ||
| 123 | * - [[org.semanticweb.owlapi.model.OWLHasKeyAxiom OWLHasKeyAxiom]] | ||
| 124 | * - [[org.semanticweb.owlapi.model.OWLInverseFunctionalObjectPropertyAxiom OWLInverseFunctionalObjectPropertyAxiom]] | ||
| 125 | * - [[org.semanticweb.owlapi.model.OWLIrreflexiveObjectPropertyAxiom OWLIrreflexiveObjectPropertyAxiom]] | ||
| 126 | * - [[org.semanticweb.owlapi.model.OWLNegativeDataPropertyAssertionAxiom OWLNegativeDataPropertyAssertionAxiom]] | ||
| 127 | * - [[org.semanticweb.owlapi.model.OWLNegativeObjectPropertyAssertionAxiom OWLNegativeObjectPropertyAssertionAxiom]] | ||
| 128 | * - [[org.semanticweb.owlapi.model.OWLReflexiveObjectPropertyAxiom OWLReflexiveObjectPropertyAxiom]] | ||
| 129 | * - [[org.semanticweb.owlapi.model.OWLSameIndividualAxiom OWLSameIndividualAxiom]] | ||
| 130 | * - [[org.semanticweb.owlapi.model.OWLSubDataPropertyOfAxiom OWLSubDataPropertyOfAxiom]] | ||
| 131 | * - [[org.semanticweb.owlapi.model.OWLSubPropertyChainOfAxiom OWLSubPropertyChainOfAxiom]] | ||
| 132 | * - [[org.semanticweb.owlapi.model.OWLSymmetricObjectPropertyAxiom OWLSymmetricObjectPropertyAxiom]] | ||
| 133 | * - [[org.semanticweb.owlapi.model.OWLTransitiveObjectPropertyAxiom OWLTransitiveObjectPropertyAxiom]] | ||
| 134 | * - [[org.semanticweb.owlapi.model.SWRLRule SWRLRule]] | ||
| 135 | */ | ||
| 136 | def convert( | ||
| 137 | axiom: OWLLogicalAxiom, | ||
| 138 | term: Term, | ||
| 139 | unsafe: List[OWLObjectPropertyExpression], | ||
| 140 | skolem: SkolemStrategy, | ||
| 141 | suffix: RSASuffix | ||
| 142 | ): Result = | ||
| 143 | axiom match { | ||
| 144 | |||
| 145 | case a: OWLSubClassOfAxiom => { | ||
| 146 | val (sub, _) = | ||
| 147 | convert(a.getSubClass, term, unsafe, SkolemStrategy.None, suffix) | ||
| 148 | val (sup, ext) = | ||
| 149 | convert(a.getSuperClass, term, unsafe, skolem, suffix) | ||
| 150 | val rule = Rule.create(sup, ext ::: sub) | ||
| 151 | ResultR(List(rule)) | ||
| 152 | } | ||
| 153 | |||
| 154 | // cannot be left | ||
| 155 | // http://www.w3.org/TR/owl2-syntax/#Equivalent_Classes | ||
| 156 | case a: OWLEquivalentClassesAxiom => { | ||
| 157 | val (atoms, rules) = a.asPairwiseAxioms | ||
| 158 | .flatMap(_.asOWLSubClassOfAxioms) | ||
| 159 | .map(convert(_, term, unsafe, skolem, suffix)) | ||
| 160 | .unzip | ||
| 161 | (atoms.flatten, rules.flatten) | ||
| 162 | } | ||
| 163 | |||
| 164 | case a: OWLEquivalentObjectPropertiesAxiom => { | ||
| 165 | val (atoms, rules) = a.asPairwiseAxioms | ||
| 166 | .flatMap(_.asSubObjectPropertyOfAxioms) | ||
| 167 | .map(convert(_, term, unsafe, skolem, suffix)) | ||
| 168 | .unzip | ||
| 169 | (atoms.flatten, rules.flatten) | ||
| 170 | } | ||
| 171 | |||
| 172 | case a: OWLSubObjectPropertyOfAxiom => { | ||
| 173 | val term1 = RSAOntology.genFreshVariable() | ||
| 174 | val body = convert(a.getSubProperty, term, term1, suffix) | ||
| 175 | val head = convert(a.getSuperProperty, term, term1, suffix) | ||
| 176 | ResultR(List(Rule.create(head, body))) | ||
| 177 | } | ||
| 178 | |||
| 179 | case a: OWLObjectPropertyDomainAxiom => | ||
| 180 | convert(a.asOWLSubClassOfAxiom, term, unsafe, skolem, suffix) | ||
| 181 | |||
| 182 | case a: OWLObjectPropertyRangeAxiom => { | ||
| 183 | val term1 = RSAOntology.genFreshVariable() | ||
| 184 | val (res, ext) = convert(a.getRange, term, unsafe, skolem, suffix) | ||
| 185 | val prop = convert(a.getProperty, term1, term, suffix) | ||
| 186 | ResultR(List(Rule.create(res, prop :: ext))) | ||
| 187 | } | ||
| 188 | |||
| 189 | case a: OWLDataPropertyDomainAxiom => | ||
| 190 | convert(a.asOWLSubClassOfAxiom, term, unsafe, skolem, suffix) | ||
| 191 | |||
| 192 | case a: OWLInverseObjectPropertiesAxiom => { | ||
| 193 | val (atoms, rules) = a.asSubObjectPropertyOfAxioms | ||
| 194 | .map(convert(_, term, unsafe, skolem, suffix)) | ||
| 195 | .unzip | ||
| 196 | (atoms.flatten, rules.flatten) | ||
| 197 | } | ||
| 198 | |||
| 199 | case a: OWLClassAssertionAxiom => { | ||
| 200 | val ind = a.getIndividual | ||
| 201 | ind match { | ||
| 202 | case i: OWLNamedIndividual => { | ||
| 203 | val cls = a.getClassExpression | ||
| 204 | val (res, _) = | ||
| 205 | convert(cls, i.getIRI, unsafe, SkolemStrategy.None, suffix) | ||
| 206 | ResultF(res) | ||
| 207 | } | ||
| 208 | case _ => Result() | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | case a: OWLObjectPropertyAssertionAxiom => | ||
| 213 | if (!a.getSubject.isNamed || !a.getObject.isNamed) | ||
| 214 | Result() | ||
| 215 | else { | ||
| 216 | val subj = a.getSubject.asOWLNamedIndividual.getIRI | ||
| 217 | val obj = a.getObject.asOWLNamedIndividual.getIRI | ||
| 218 | val prop = convert(a.getProperty, subj, obj, suffix) | ||
| 219 | ResultF(List(prop)) | ||
| 220 | } | ||
| 221 | |||
| 222 | /** Catch-all case for all unhandled axiom types. */ | ||
| 223 | case a => | ||
| 224 | throw new RuntimeException( | ||
| 225 | s"Axiom '$a' is not supported (yet?)" | ||
| 226 | ) | ||
| 227 | |||
| 228 | } | ||
| 229 | |||
| 230 | /** Converts a class expression into a collection of atoms. | ||
| 231 | * | ||
| 232 | * @note not all possible class expressions are handled correctly. | ||
| 233 | * Following is a list of all unhandled class expressions: | ||
| 234 | * - [[org.semanticweb.owlapi.model.OWLDataAllValuesFrom OWLDataAllValuesFrom]] | ||
| 235 | * - [[org.semanticweb.owlapi.model.OWLDataExactCardinality OWLDataExactCardinality]] | ||
| 236 | * - [[org.semanticweb.owlapi.model.OWLDataMaxCardinality OWLDataMaxCardinality]] | ||
| 237 | * - [[org.semanticweb.owlapi.model.OWLDataMinCardinality OWLDataMinCardinality]] | ||
| 238 | * - [[org.semanticweb.owlapi.model.OWLDataHasValue OWLDataHasValue]] | ||
| 239 | * - [[org.semanticweb.owlapi.model.OWLObjectAllValuesFrom OWLObjectAllValuesFrom]] | ||
| 240 | * - [[org.semanticweb.owlapi.model.OWLObjectComplementOf OWLObjectComplementOf]] | ||
| 241 | * - [[org.semanticweb.owlapi.model.OWLObjectExactCardinality OWLObjectExactCardinality]] | ||
| 242 | * - [[org.semanticweb.owlapi.model.OWLObjectHasSelf OWLObjectHasSelf]] | ||
| 243 | * - [[org.semanticweb.owlapi.model.OWLObjectHasValue OWLObjectHasValue]] | ||
| 244 | * - [[org.semanticweb.owlapi.model.OWLObjectMinCardinality OWLObjectMinCardinality]] | ||
| 245 | * - [[org.semanticweb.owlapi.model.OWLObjectUnionOf OWLObjectUnionOf]] | ||
| 246 | * | ||
| 247 | * Moreover: | ||
| 248 | * - [[org.semanticweb.owlapi.model.OWLObjectMaxCardinality OWLObjectMaxCardinality]] | ||
| 249 | * is accepted only when cardinality is set to 1; | ||
| 250 | * - [[org.semanticweb.owlapi.model.OWLObjectOneOf OWLObjectOneOf]] | ||
| 251 | * is accepted only when its arity is 1. | ||
| 252 | */ | ||
| 253 | def convert( | ||
| 254 | expr: OWLClassExpression, | ||
| 255 | term: Term, | ||
| 256 | unsafe: List[OWLObjectPropertyExpression], | ||
| 257 | skolem: SkolemStrategy, | ||
| 258 | suffix: RSASuffix | ||
| 259 | ): Shards = | ||
| 260 | expr match { | ||
| 261 | |||
| 262 | /** Simple class name. | ||
| 263 | * | ||
| 264 | * @see [[http://www.w3.org/TR/owl2-syntax/#Classes]] | ||
| 265 | */ | ||
| 266 | case e: OWLClass => { | ||
| 267 | val iri: IRI = if (e.isTopEntity()) IRI.THING else e.getIRI | ||
| 268 | val atom = TupleTableAtom.rdf(term, IRI.RDF_TYPE, iri) | ||
| 269 | (List(atom), List()) | ||
| 270 | } | ||
| 271 | |||
| 272 | /** Conjunction of class expressions. | ||
| 273 | * | ||
| 274 | * @see [[http://www.w3.org/TR/owl2-syntax/#Intersection_of_Class_Expressions]] | ||
| 275 | */ | ||
| 276 | case e: OWLObjectIntersectionOf => { | ||
| 277 | val (res, ext) = e.asConjunctSet | ||
| 278 | .map(convert(_, term, unsafe, skolem, suffix)) | ||
| 279 | .unzip | ||
| 280 | (res.flatten, ext.flatten) | ||
| 281 | } | ||
| 282 | |||
| 283 | /** Enumeration of individuals. | ||
| 284 | * | ||
| 285 | * @note we only admit enumerations of arity 1. | ||
| 286 | * | ||
| 287 | * @throws `RuntimeException` when dealing with an enumeration | ||
| 288 | * with arity != 1. | ||
| 289 | * | ||
| 290 | * @see [[http://www.w3.org/TR/owl2-syntax/#Enumeration_of_Individuals]] | ||
| 291 | */ | ||
| 292 | case e: OWLObjectOneOf => { | ||
| 293 | val named = e.individuals | ||
| 294 | .collect(Collectors.toList()) | ||
| 295 | .collect { case x: OWLNamedIndividual => x } | ||
| 296 | if (named.length != 1) | ||
| 297 | throw new RuntimeException(s"Class expression '$e' has arity != 1.") | ||
| 298 | val atom = TupleTableAtom.rdf(term, IRI.SAME_AS, named.head.getIRI) | ||
| 299 | (List(atom), List()) | ||
| 300 | } | ||
| 301 | |||
| 302 | /** Existential class expression (for data properties). | ||
| 303 | * | ||
| 304 | * Parameter `skolem` is used to determine the skolemization | ||
| 305 | * technique (if any) to use for the translation. | ||
| 306 | * | ||
| 307 | * @see [[http://www.w3.org/TR/owl2-syntax/#Existential_Quantification]] | ||
| 308 | */ | ||
| 309 | case e: OWLObjectSomeValuesFrom => { | ||
| 310 | val cls = e.getFiller() | ||
| 311 | val role = e.getProperty() | ||
| 312 | // TODO: simplify this: | ||
| 313 | // Computes the result of rule skolemization. Depending on the used | ||
| 314 | // technique it might involve the introduction of additional atoms, | ||
| 315 | // and/or fresh constants and variables. | ||
| 316 | val (head, body, term1) = skolem match { | ||
| 317 | case SkolemStrategy.None => | ||
| 318 | (List(), List(), RSAOntology.genFreshVariable) | ||
| 319 | case SkolemStrategy.Constant(c) => (List(), List(), c) | ||
| 320 | case SkolemStrategy.ConstantRSA(c) => { | ||
| 321 | if (unsafe.contains(role)) | ||
| 322 | (List(RSA.PE(term, c), RSA.U(c)), List(), c) | ||
| 323 | else | ||
| 324 | (List(), List(), c) | ||
| 325 | } | ||
| 326 | case SkolemStrategy.Standard(f) => { | ||
| 327 | val x = RSAOntology.genFreshVariable | ||
| 328 | ( | ||
| 329 | List(), | ||
| 330 | List(BindAtom.create(FunctionCall.create("SKOLEM", f, term), x)), | ||
| 331 | x | ||
| 332 | ) | ||
| 333 | } | ||
| 334 | } | ||
| 335 | val (res, ext) = convert(cls, term1, unsafe, skolem, suffix) | ||
| 336 | val prop = convert(role, term, term1, suffix) | ||
| 337 | (prop :: head ::: res, body ::: ext) | ||
| 338 | } | ||
| 339 | |||
| 340 | /** Existential class expression (for data properties). | ||
| 341 | * | ||
| 342 | * Parameter `skolem` is used to determine the skolemization | ||
| 343 | * technique (if any) to use for the translation. | ||
| 344 | * | ||
| 345 | * @todo the "filler" of this OWL expression is currently ignored. | ||
| 346 | * This, in general might not be how we want to handle | ||
| 347 | * [[org.semanticweb.owlapi.model.OWLDataRange OWLDataRanges]]. | ||
| 348 | * | ||
| 349 | * @see [[http://www.w3.org/TR/owl2-syntax/#Existential_Quantification_2]] | ||
| 350 | */ | ||
| 351 | case e: OWLDataSomeValuesFrom => { | ||
| 352 | val role = e.getProperty() | ||
| 353 | // TODO: simplify this: | ||
| 354 | // Computes the result of rule skolemization. Depending on the used | ||
| 355 | // technique it might involve the introduction of additional atoms, | ||
| 356 | // and/or fresh constants and variables. | ||
| 357 | val (head, body, term1) = skolem match { | ||
| 358 | case SkolemStrategy.None => | ||
| 359 | (List(), List(), RSAOntology.genFreshVariable) | ||
| 360 | case SkolemStrategy.Constant(c) => (List(), List(), c) | ||
| 361 | case SkolemStrategy.ConstantRSA(c) => { | ||
| 362 | if (unsafe.contains(role)) | ||
| 363 | (List(RSA.PE(term, c), RSA.U(c)), List(), c) | ||
| 364 | else | ||
| 365 | (List(), List(), c) | ||
| 366 | } | ||
| 367 | case SkolemStrategy.Standard(f) => { | ||
| 368 | val y = RSAOntology.genFreshVariable() | ||
| 369 | ( | ||
| 370 | List(), | ||
| 371 | List(BindAtom.create(FunctionCall.create("SKOLEM", f, term), y)), | ||
| 372 | y | ||
| 373 | ) | ||
| 374 | } | ||
| 375 | } | ||
| 376 | val prop = convert(role, term, term1, suffix) | ||
| 377 | (prop :: head, body) | ||
| 378 | } | ||
| 379 | |||
| 380 | /** Maximum cardinality restriction class | ||
| 381 | * | ||
| 382 | * @note we only admit classes with cardinality set to 1. | ||
| 383 | * | ||
| 384 | * @throws `RuntimeException` when dealing with a restriction | ||
| 385 | * with cardinality != 1. | ||
| 386 | * | ||
| 387 | * @see [[http://www.w3.org/TR/owl2-syntax/#Maximum_Cardinality_2]] | ||
| 388 | */ | ||
| 389 | case e: OWLObjectMaxCardinality => { | ||
| 390 | if (e.getCardinality != 1) | ||
| 391 | throw new RuntimeException( | ||
| 392 | s"Class expression '$e' has cardinality restriction != 1." | ||
| 393 | ) | ||
| 394 | val vars @ (y :: z :: _) = | ||
| 395 | Seq(RSAOntology.genFreshVariable(), RSAOntology.genFreshVariable()) | ||
| 396 | val cls = e.getFiller | ||
| 397 | val role = e.getProperty | ||
| 398 | val (res, ext) = vars.map(convert(cls, _, unsafe, skolem, suffix)).unzip | ||
| 399 | val props = vars.map(convert(role, term, _, suffix)) | ||
| 400 | val eq = TupleTableAtom.rdf(y, IRI.SAME_AS, z) | ||
| 401 | (List(eq), res.flatten ++ props) | ||
| 402 | } | ||
| 403 | |||
| 404 | /** Catch-all case for all unhandled class expressions. */ | ||
| 405 | case e => | ||
| 406 | throw new RuntimeException( | ||
| 407 | s"Class expression '$e' is not supported (yet?)" | ||
| 408 | ) | ||
| 409 | } | ||
| 410 | |||
| 411 | /** Converts an object property expression into an atom. */ | ||
| 412 | def convert( | ||
| 413 | expr: OWLObjectPropertyExpression, | ||
| 414 | term1: Term, | ||
| 415 | term2: Term, | ||
| 416 | suffix: RSASuffix | ||
| 417 | ): TupleTableAtom = | ||
| 418 | expr match { | ||
| 419 | |||
| 420 | /** Simple named role/object property. | ||
| 421 | * | ||
| 422 | * @see [[http://www.w3.org/TR/owl2-syntax/#Object_Properties Object Properties]] | ||
| 423 | */ | ||
| 424 | case e: OWLObjectProperty => { | ||
| 425 | val role = IRI.create(e.getIRI.getIRIString :: suffix) | ||
| 426 | TupleTableAtom.rdf(term1, role, term2) | ||
| 427 | } | ||
| 428 | |||
| 429 | /** Inverse of a named role/property | ||
| 430 | * | ||
| 431 | * OWLAPI does not admit nesting of negation, and double | ||
| 432 | * negations are always simplified. | ||
| 433 | * | ||
| 434 | * @see [[https://www.w3.org/TR/owl2-syntax/#Inverse_Object_Properties Inverse Object Properties]] | ||
| 435 | */ | ||
| 436 | case e: OWLObjectInverseOf => | ||
| 437 | convert(e.getInverse, term1, term2, suffix + Inverse) | ||
| 438 | |||
| 439 | /** The infamous impossible case. | ||
| 440 | * | ||
| 441 | * @note all relevant cases are taken care of, and this branch | ||
| 442 | * throws a runtime exception to notify of the problem. | ||
| 443 | */ | ||
| 444 | case e => | ||
| 445 | throw new RuntimeException( | ||
| 446 | s"Unable to convert '$e' into a logic expression. This should be happening (TM)." | ||
| 447 | ) | ||
| 448 | } | ||
| 449 | |||
| 450 | /** Converts a data property expression into an atom. */ | ||
| 451 | def convert( | ||
| 452 | expr: OWLDataPropertyExpression, | ||
| 453 | term1: Term, | ||
| 454 | term2: Term, | ||
| 455 | suffix: RSASuffix | ||
| 456 | ): TupleTableAtom = | ||
| 457 | expr match { | ||
| 458 | |||
| 459 | /** Simple named role/data property | ||
| 460 | * | ||
| 461 | * @see [[https://www.w3.org/TR/owl2-syntax/#Datatypes Data Properties]] | ||
| 462 | */ | ||
| 463 | case e: OWLDataProperty => { | ||
| 464 | val role = IRI.create(e.getIRI.getIRIString :: suffix) | ||
| 465 | TupleTableAtom.rdf(term1, role, term2) | ||
| 466 | } | ||
| 467 | |||
| 468 | /** The infamous impossible case. | ||
| 469 | * | ||
| 470 | * @note all relevant cases are taken care of, and this branch | ||
| 471 | * throws a runtime exception to notify of the problem. | ||
| 472 | */ | ||
| 473 | case e => | ||
| 474 | throw new RuntimeException( | ||
| 475 | s"Unable to convert '$e' into a logic expression. This should be happening (TM)." | ||
| 476 | ) | ||
| 477 | } | ||
| 478 | |||
| 479 | } | ||
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/JavaCollections.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/JavaCollections.scala index 4565017..8c513fd 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/JavaCollections.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/JavaCollections.scala | |||
| @@ -17,4 +17,8 @@ object JavaCollections { | |||
| 17 | set: java.util.Collection[A] | 17 | set: java.util.Collection[A] |
| 18 | ): List[A] = | 18 | ): List[A] = |
| 19 | set.asScala.toList | 19 | set.asScala.toList |
| 20 | |||
| 21 | implicit def scalaSeqTojavaCollection[A]( | ||
| 22 | seq: Seq[A] | ||
| 23 | ): java.util.Collection[A] = seq.asJavaCollection | ||
| 20 | } | 24 | } |
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala index f7abde3..193119f 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala | |||
| @@ -5,7 +5,8 @@ import tech.oxfordsemantic.jrdfox.Prefixes | |||
| 5 | import tech.oxfordsemantic.jrdfox.client.{ | 5 | import tech.oxfordsemantic.jrdfox.client.{ |
| 6 | ConnectionFactory, | 6 | ConnectionFactory, |
| 7 | ServerConnection, | 7 | ServerConnection, |
| 8 | DataStoreConnection | 8 | DataStoreConnection, |
| 9 | UpdateType | ||
| 9 | } | 10 | } |
| 10 | import tech.oxfordsemantic.jrdfox.formats.SPARQLParser | 11 | import tech.oxfordsemantic.jrdfox.formats.SPARQLParser |
| 11 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ | 12 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ |
| @@ -47,9 +48,8 @@ object RDFoxUtil { | |||
| 47 | * @return a tuple with the newly opened server and data store | 48 | * @return a tuple with the newly opened server and data store |
| 48 | * connections. | 49 | * connections. |
| 49 | * | 50 | * |
| 50 | * @see [[uk.ac.ox.cs.rsacomb.util.RDFoxUtil.closeConnection | 51 | * @see [[uk.ac.ox.cs.rsacomb.util.RDFoxUtil.closeConnection RDFoxUtil.closeConnection]] |
| 51 | * RDFoxUtil.closeConnection]] for | 52 | * for details on how to close an open connection. |
| 52 | * details on how to close an open connection. | ||
| 53 | */ | 53 | */ |
| 54 | def openConnection( | 54 | def openConnection( |
| 55 | datastore: String, | 55 | datastore: String, |
| @@ -66,6 +66,26 @@ object RDFoxUtil { | |||
| 66 | (server, data) | 66 | (server, data) |
| 67 | } | 67 | } |
| 68 | 68 | ||
| 69 | /** Adds a collection of rules to a data store. | ||
| 70 | * | ||
| 71 | * @param data datastore connection | ||
| 72 | * @param rules collection of rules to be added to the data store | ||
| 73 | */ | ||
| 74 | def addRules(data: DataStoreConnection, rules: Seq[Rule]): Unit = | ||
| 75 | data addRules rules | ||
| 76 | |||
| 77 | /** Adds a collection of facts to a data store. | ||
| 78 | * | ||
| 79 | * @param data datastore connection | ||
| 80 | * @param facts collection of facts to be added to the data store | ||
| 81 | */ | ||
| 82 | def addFacts(data: DataStoreConnection, facts: Seq[TupleTableAtom]): Unit = | ||
| 83 | data.importData( | ||
| 84 | UpdateType.ADDITION, | ||
| 85 | RSA.Prefixes, | ||
| 86 | facts.map(_.toString(Prefixes.s_emptyPrefixes)).mkString("", ".\n", ".") | ||
| 87 | ) | ||
| 88 | |||
| 69 | /** Parse a SELECT query from a string in SPARQL format. | 89 | /** Parse a SELECT query from a string in SPARQL format. |
| 70 | * | 90 | * |
| 71 | * @param query the string containing the SPARQL query | 91 | * @param query the string containing the SPARQL query |
