aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala133
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala20
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala106
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxConverter.scala479
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/implicits/JavaCollections.scala4
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala28
-rw-r--r--src/test/scala/uk/ac/ox/cs/rsacomb/CanonicalModelSpec.scala99
-rw-r--r--src/test/scala/uk/ac/ox/cs/rsacomb/FilteringProgramSpecs.scala18
-rw-r--r--src/test/scala/uk/ac/ox/cs/rsacomb/RDFoxConverterSpec.scala108
9 files changed, 833 insertions, 162 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
3import org.semanticweb.owlapi.model.{OWLObjectInverseOf, OWLObjectProperty} 3import org.semanticweb.owlapi.model.{OWLObjectInverseOf, OWLObjectProperty}
4import org.semanticweb.owlapi.model.{ 4import 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
26import uk.ac.ox.cs.rsacomb.converter.{ 27import uk.ac.ox.cs.rsacomb.converter.{
27 SkolemStrategy, 28 SkolemStrategy,
28 RDFoxAxiomConverter, 29 RDFoxConverter
29 RDFoxPropertyExprConverter 30 // RDFoxAxiomConverter,
31 // RDFoxPropertyExprConverter
30} 32}
31import uk.ac.ox.cs.rsacomb.suffix.{Empty, Forward, Backward, Inverse} 33import uk.ac.ox.cs.rsacomb.suffix._
32import uk.ac.ox.cs.rsacomb.util.RSA 34import uk.ac.ox.cs.rsacomb.util.RSA
33 35
34class CanonicalModel(val ontology: RSAOntology) { 36class 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}
7import java.io.File 7import java.io.File
8import org.semanticweb.owlapi.apibinding.OWLManager 8import org.semanticweb.owlapi.apibinding.OWLManager
9import org.semanticweb.owlapi.util.OWLOntologyMerger 9import org.semanticweb.owlapi.util.OWLOntologyMerger
10import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom} 10import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom, OWLLogicalAxiom}
11import org.semanticweb.owlapi.model.{ 11import 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}
19import org.semanticweb.owlapi.model.parameters.Imports 21import org.semanticweb.owlapi.model.parameters.Imports
@@ -48,7 +50,7 @@ import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer
48import tech.oxfordsemantic.jrdfox.logic._ 50import tech.oxfordsemantic.jrdfox.logic._
49import org.semanticweb.owlapi.model.OWLObjectInverseOf 51import org.semanticweb.owlapi.model.OWLObjectInverseOf
50 52
51import uk.ac.ox.cs.rsacomb.converter.{RDFoxAxiomConverter, SkolemStrategy} 53import uk.ac.ox.cs.rsacomb.converter.{RDFoxConverter, SkolemStrategy}
52import uk.ac.ox.cs.rsacomb.suffix._ 54import uk.ac.ox.cs.rsacomb.suffix._
53import uk.ac.ox.cs.rsacomb.sparql._ 55import uk.ac.ox.cs.rsacomb.sparql._
54import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} 56import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA}
@@ -82,30 +84,28 @@ object RSAOntology {
82class RSAOntology(val ontology: OWLOntology) { 84class 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 @@
1package uk.ac.ox.cs.rsacomb.converter
2
3import java.util.stream.Collectors
4import 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}
32import scala.collection.JavaConverters._
33import tech.oxfordsemantic.jrdfox.logic.datalog.{
34 BindAtom,
35 BodyFormula,
36 Rule,
37 TupleTableAtom
38}
39import tech.oxfordsemantic.jrdfox.logic.expression.{Term, IRI, FunctionCall}
40import uk.ac.ox.cs.rsacomb.RSAOntology
41import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Inverse}
42import 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 */
60trait 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
5import tech.oxfordsemantic.jrdfox.client.{ 5import tech.oxfordsemantic.jrdfox.client.{
6 ConnectionFactory, 6 ConnectionFactory,
7 ServerConnection, 7 ServerConnection,
8 DataStoreConnection 8 DataStoreConnection,
9 UpdateType
9} 10}
10import tech.oxfordsemantic.jrdfox.formats.SPARQLParser 11import tech.oxfordsemantic.jrdfox.formats.SPARQLParser
11import tech.oxfordsemantic.jrdfox.logic.datalog.{ 12import 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
diff --git a/src/test/scala/uk/ac/ox/cs/rsacomb/CanonicalModelSpec.scala b/src/test/scala/uk/ac/ox/cs/rsacomb/CanonicalModelSpec.scala
index 3070ce3..0d07923 100644
--- a/src/test/scala/uk/ac/ox/cs/rsacomb/CanonicalModelSpec.scala
+++ b/src/test/scala/uk/ac/ox/cs/rsacomb/CanonicalModelSpec.scala
@@ -15,6 +15,8 @@ import tech.oxfordsemantic.jrdfox.logic.expression.Variable
15import scala.collection.JavaConverters._ 15import scala.collection.JavaConverters._
16 16
17import uk.ac.ox.cs.rsacomb.RSAOntology 17import uk.ac.ox.cs.rsacomb.RSAOntology
18import uk.ac.ox.cs.rsacomb.converter.SkolemStrategy
19import uk.ac.ox.cs.rsacomb.suffix.Empty
18import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} 20import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA}
19 21
20object Ontology1_CanonicalModelSpec { 22object Ontology1_CanonicalModelSpec {
@@ -28,6 +30,7 @@ object Ontology1_CanonicalModelSpec {
28 val ontology_path: File = new File("examples/example1.ttl") 30 val ontology_path: File = new File("examples/example1.ttl")
29 val ontology = RSAOntology(ontology_path) 31 val ontology = RSAOntology(ontology_path)
30 val program = ontology.canonicalModel 32 val program = ontology.canonicalModel
33 val converter = program.CanonicalModelConverter
31 34
32 val roleR = new OWLObjectPropertyImpl(base("R")) 35 val roleR = new OWLObjectPropertyImpl(base("R"))
33 val roleS = new OWLObjectPropertyImpl(base("S")) 36 val roleS = new OWLObjectPropertyImpl(base("S"))
@@ -89,9 +92,13 @@ class Ontology1_CanonicalModelSpec
89 } 92 }
90 93
91 renderer.render(AsubClassOfD) should "be converted into a single Rule" in { 94 renderer.render(AsubClassOfD) should "be converted into a single Rule" in {
92 val varX = Variable.create("X") 95 val term = Variable.create("X")
93 val visitor = program.RuleGenerator 96 val unsafe = ontology.unsafeRoles
94 val rules = AsubClassOfD.accept(visitor) 97 val skolem = SkolemStrategy.None
98 val suffix = Empty
99 val (facts, rules) =
100 converter.convert(AsubClassOfD, term, unsafe, skolem, suffix)
101 facts shouldBe empty
95 rules.loneElement shouldBe a[Rule] 102 rules.loneElement shouldBe a[Rule]
96 } 103 }
97 104
@@ -154,10 +161,14 @@ class Ontology1_CanonicalModelSpec
154 renderer.render( 161 renderer.render(
155 AsomeValuesFromSiC 162 AsomeValuesFromSiC
156 ) should "produce 1 rule" in { 163 ) should "produce 1 rule" in {
157 val varX = Variable.create("X") 164 val term = Variable.create("X")
158 val visitor = program.RuleGenerator 165 val unsafe = ontology.unsafeRoles
159 val rules = AsomeValuesFromSiC.accept(visitor) 166 val skolem = SkolemStrategy.None
160 rules should have length 1 167 val suffix = Empty
168 val (facts, rules) =
169 converter.convert(AsomeValuesFromSiC, term, unsafe, skolem, suffix)
170 facts shouldBe empty
171 rules.loneElement shouldBe a[Rule]
161 } 172 }
162 173
163 renderer.render( 174 renderer.render(
@@ -172,15 +183,18 @@ class Ontology1_CanonicalModelSpec
172 ontology.cycle(DsomeValuesFromRB) should have size 48 183 ontology.cycle(DsomeValuesFromRB) should have size 48
173 } 184 }
174 185
175 it should "produce 5 rules" in { 186 it should "produce 48 facts and 98 rules" in {
176 // Rule 1 provides 1 rule (split in 2) + 48 fact 187 // Rule 1 provides 1 rule (split in 2) + 48 fact
177 // Rule 2 provides 0 rules 188 // Rule 2 provides 0 rules
178 // Rule 3 provides 48 rule (split in 2) 189 // Rule 3 provides 48 rule (split in 2)
179 // Then (1*2 + 48) + (0) + (48*2) = 146 190 val term = Variable.create("X")
180 val varX = Variable.create("X") 191 val unsafe = ontology.unsafeRoles
181 val visitor = program.RuleGenerator 192 val skolem = SkolemStrategy.None
182 val rules = DsomeValuesFromRB.accept(visitor) 193 val suffix = Empty
183 rules should have length 146 194 val (facts, rules) =
195 converter.convert(DsomeValuesFromRB, term, unsafe, skolem, suffix)
196 facts should have length 48
197 rules should have length 98
184 } 198 }
185 199
186 renderer.render( 200 renderer.render(
@@ -200,18 +214,26 @@ class Ontology1_CanonicalModelSpec
200 // Rule 2 provides 0 rules 214 // Rule 2 provides 0 rules
201 // Rule 3 provides 32 rule (split in 2) 215 // Rule 3 provides 32 rule (split in 2)
202 // Then (1*2 + 32) + (0) + (32*2) = 98 216 // Then (1*2 + 32) + (0) + (32*2) = 98
203 val varX = Variable.create("X") 217 val term = Variable.create("X")
204 val visitor = program.RuleGenerator 218 val unsafe = ontology.unsafeRoles
205 val rules = DsomeValuesFromRB.accept(visitor) 219 val skolem = SkolemStrategy.None
206 rules should have length 146 220 val suffix = Empty
221 val (facts, rules) =
222 converter.convert(BsomeValuesFromSD, term, unsafe, skolem, suffix)
223 facts should have length 32
224 rules should have length 66
207 } 225 }
208 226
209 renderer.render( 227 renderer.render(
210 SsubPropertyOfT 228 SsubPropertyOfT
211 ) should "produce 2 rules" in { 229 ) should "produce 2 rules" in {
212 val varX = Variable.create("X") 230 val term = Variable.create("X")
213 val visitor = program.RuleGenerator 231 val unsafe = ontology.unsafeRoles
214 val rules = SsubPropertyOfT.accept(visitor) 232 val skolem = SkolemStrategy.None
233 val suffix = Empty
234 val (facts, rules) =
235 converter.convert(SsubPropertyOfT, term, unsafe, skolem, suffix)
236 facts shouldBe empty
215 rules should have length 2 237 rules should have length 2
216 } 238 }
217 239
@@ -228,6 +250,7 @@ object Ontology2_CanonicalModelSpec {
228 val ontology_path: File = new File("examples/example2.owl") 250 val ontology_path: File = new File("examples/example2.owl")
229 val ontology = RSAOntology(ontology_path) 251 val ontology = RSAOntology(ontology_path)
230 val program = ontology.canonicalModel 252 val program = ontology.canonicalModel
253 val converter = program.CanonicalModelConverter
231 254
232 val roleR = new OWLObjectPropertyImpl(base("R")) 255 val roleR = new OWLObjectPropertyImpl(base("R"))
233 val roleS = new OWLObjectPropertyImpl(base("S")) 256 val roleS = new OWLObjectPropertyImpl(base("S"))
@@ -332,8 +355,13 @@ class Ontology2_CanonicalModelSpec
332 renderer.render( 355 renderer.render(
333 AsomeValuesFromRB 356 AsomeValuesFromRB
334 ) should "produce 1 rule" in { 357 ) should "produce 1 rule" in {
335 val visitor = program.RuleGenerator 358 val term = Variable.create("X")
336 val rules = AsomeValuesFromRB.accept(visitor) 359 val unsafe = ontology.unsafeRoles
360 val skolem = SkolemStrategy.None
361 val suffix = Empty
362 val (facts, rules) =
363 converter.convert(AsomeValuesFromRB, term, unsafe, skolem, suffix)
364 facts shouldBe empty
337 rules should have length 1 365 rules should have length 1
338 } 366 }
339 367
@@ -342,8 +370,13 @@ class Ontology2_CanonicalModelSpec
342 renderer.render( 370 renderer.render(
343 BsomeValuesFromSC 371 BsomeValuesFromSC
344 ) should "produce 1 rule" in { 372 ) should "produce 1 rule" in {
345 val visitor = program.RuleGenerator 373 val term = Variable.create("X")
346 val rules = BsomeValuesFromSC.accept(visitor) 374 val unsafe = ontology.unsafeRoles
375 val skolem = SkolemStrategy.None
376 val suffix = Empty
377 val (facts, rules) =
378 converter.convert(BsomeValuesFromSC, term, unsafe, skolem, suffix)
379 facts shouldBe empty
347 rules should have length 1 380 rules should have length 1
348 } 381 }
349 382
@@ -352,8 +385,13 @@ class Ontology2_CanonicalModelSpec
352 renderer.render( 385 renderer.render(
353 CsomeValuesFromTD 386 CsomeValuesFromTD
354 ) should "produce 1 rule" in { 387 ) should "produce 1 rule" in {
355 val visitor = program.RuleGenerator 388 val term = Variable.create("X")
356 val rules = CsomeValuesFromTD.accept(visitor) 389 val unsafe = ontology.unsafeRoles
390 val skolem = SkolemStrategy.None
391 val suffix = Empty
392 val (facts, rules) =
393 converter.convert(CsomeValuesFromTD, term, unsafe, skolem, suffix)
394 facts shouldBe empty
357 rules should have length 1 395 rules should have length 1
358 } 396 }
359 397
@@ -362,8 +400,13 @@ class Ontology2_CanonicalModelSpec
362 renderer.render( 400 renderer.render(
363 DsomeValuesFromPA 401 DsomeValuesFromPA
364 ) should "produce 1 rule" in { 402 ) should "produce 1 rule" in {
365 val visitor = program.RuleGenerator 403 val term = Variable.create("X")
366 val rules = DsomeValuesFromPA.accept(visitor) 404 val unsafe = ontology.unsafeRoles
405 val skolem = SkolemStrategy.None
406 val suffix = Empty
407 val (facts, rules) =
408 converter.convert(DsomeValuesFromPA, term, unsafe, skolem, suffix)
409 facts shouldBe empty
367 rules should have length 1 410 rules should have length 1
368 } 411 }
369 412
diff --git a/src/test/scala/uk/ac/ox/cs/rsacomb/FilteringProgramSpecs.scala b/src/test/scala/uk/ac/ox/cs/rsacomb/FilteringProgramSpecs.scala
index 71c9a99..86e0253 100644
--- a/src/test/scala/uk/ac/ox/cs/rsacomb/FilteringProgramSpecs.scala
+++ b/src/test/scala/uk/ac/ox/cs/rsacomb/FilteringProgramSpecs.scala
@@ -65,24 +65,32 @@ class FilteringProgramSpec extends AnyFlatSpec with Matchers {
65 65
66 import FilteringProgramSpec._ 66 import FilteringProgramSpec._
67 67
68 "CQ 0" should "generate 30 rules" in { 68 "CQ 0" should "generate 27 rules and 3 facts" in {
69 val cq = ConjunctiveQuery(cq0).get 69 val cq = ConjunctiveQuery(cq0).get
70 FilteringProgram(cq, constants).rules should have length 30 70 val filter = FilteringProgram(cq, constants)
71 filter.facts should have length 3
72 filter.rules should have length 27
71 } 73 }
72 74
73 "CQ 1" should "generate 15 rules" in { 75 "CQ 1" should "generate 15 rules" in {
74 val cq = ConjunctiveQuery(cq1).get 76 val cq = ConjunctiveQuery(cq1).get
75 FilteringProgram(cq, List()).rules should have length 15 77 val filter = FilteringProgram(cq, List())
78 filter.facts shouldBe empty
79 filter.rules should have length 15
76 } 80 }
77 81
78 "CQ 2" should "generate 51 rules" in { 82 "CQ 2" should "generate 51 rules" in {
79 val cq = ConjunctiveQuery(cq2).get 83 val cq = ConjunctiveQuery(cq2).get
80 FilteringProgram(cq, List()).rules should have length 51 84 val filter = FilteringProgram(cq, List())
85 filter.facts shouldBe empty
86 filter.rules should have length 51
81 } 87 }
82 88
83 "BCQ 0" should "generate 46 rules" in { 89 "BCQ 0" should "generate 46 rules" in {
84 val cq = ConjunctiveQuery(bcq0).get 90 val cq = ConjunctiveQuery(bcq0).get
85 FilteringProgram(cq, constants).rules should have length 46 91 val filter = FilteringProgram(cq, constants)
92 filter.facts should have length 3
93 filter.rules should have length 43
86 } 94 }
87 95
88} 96}
diff --git a/src/test/scala/uk/ac/ox/cs/rsacomb/RDFoxConverterSpec.scala b/src/test/scala/uk/ac/ox/cs/rsacomb/RDFoxConverterSpec.scala
new file mode 100644
index 0000000..e2da6e4
--- /dev/null
+++ b/src/test/scala/uk/ac/ox/cs/rsacomb/RDFoxConverterSpec.scala
@@ -0,0 +1,108 @@
1package rsacomb
2
3import org.scalatest.LoneElement
4import org.scalatest.flatspec.AnyFlatSpec
5import org.scalatest.matchers.should.Matchers
6import org.semanticweb.owlapi.apibinding.OWLManager
7import org.semanticweb.owlapi.model.OWLOntologyManager
8
9import tech.oxfordsemantic.jrdfox.logic.datalog.TupleTableAtom
10import tech.oxfordsemantic.jrdfox.logic.expression.{Variable, IRI}
11import uk.ac.ox.cs.rsacomb.converter.RDFoxConverter
12import uk.ac.ox.cs.rsacomb.suffix.{Empty, Forward, Backward, Inverse}
13import uk.ac.ox.cs.rsacomb.converter.SkolemStrategy
14
15object RDFoxConverterSpec {
16
17 val manager = OWLManager.createOWLOntologyManager()
18 val factory = manager.getOWLDataFactory
19
20 val term0 = Variable.create("X")
21 val term1 = Variable.create("Y")
22 val iriString0 = "http://example.com/rsacomb/iri0"
23 val iriString1 = "http://example.com/rsacomb/iri1"
24 val iriString2 = "http://example.com/rsacomb/iri2"
25 val suffixes = Seq(
26 Empty,
27 Forward,
28 Backward,
29 Inverse,
30 Forward + Inverse,
31 Backward + Inverse
32 )
33}
34
35class RDFoxConverterSpec
36 extends AnyFlatSpec
37 with Matchers
38 with LoneElement
39 with RDFoxConverter {
40
41 import RDFoxConverterSpec._
42
43 "A class name" should "be converted into a single atom" in {
44 val cls = factory.getOWLClass(iriString0)
45 val atom = TupleTableAtom.rdf(term0, IRI.RDF_TYPE, IRI.create(iriString0))
46 val (res, ext) =
47 convert(cls, term0, List(), SkolemStrategy.None, Empty)
48 res.loneElement shouldEqual atom
49 ext shouldBe empty
50 }
51
52 "A intersection of classes" should "be converted into the union of the conversion of the classes" in {
53 val cls0 = factory.getOWLClass(iriString0)
54 val cls1 = factory.getOWLClass(iriString1)
55 val cls2 = factory.getOWLClass(iriString2)
56 val conj = factory.getOWLObjectIntersectionOf(cls0, cls1, cls2)
57 val (res0, ext0) =
58 convert(cls0, term0, List(), SkolemStrategy.None, Empty)
59 val (res1, ext1) =
60 convert(cls1, term0, List(), SkolemStrategy.None, Empty)
61 val (res2, ext2) =
62 convert(cls2, term0, List(), SkolemStrategy.None, Empty)
63 val (res, ext) =
64 convert(conj, term0, List(), SkolemStrategy.None, Empty)
65 res should contain theSameElementsAs (res0 ::: res1 ::: res2)
66 ext should contain theSameElementsAs (ext0 ::: ext1 ::: ext2)
67 }
68
69 "A singleton intersection" should "correspond to the conversion of the internal class" in {
70 val cls0 = factory.getOWLClass(iriString0)
71 val conj = factory.getOWLObjectIntersectionOf(cls0)
72 val (res0, ext0) =
73 convert(cls0, term0, List(), SkolemStrategy.None, Empty)
74 val (res, ext) =
75 convert(conj, term0, List(), SkolemStrategy.None, Empty)
76 res should contain theSameElementsAs res0
77 ext should contain theSameElementsAs ext0
78 }
79
80 "An object property" should "be converted into an atom with matching predicate" in {
81 val prop = factory.getOWLObjectProperty(iriString0)
82 for (sx <- suffixes) {
83 val atom =
84 TupleTableAtom.rdf(term0, IRI.create(iriString0 :: sx), term1)
85 convert(prop, term0, term1, sx) shouldEqual atom
86 }
87 }
88
89 "The inverse of an object property" should "be converted into an atom with matching negated predicate" in {
90 val prop = factory.getOWLObjectProperty(iriString0)
91 val inv = factory.getOWLObjectInverseOf(prop)
92 for (sx <- Seq(Empty, Forward, Backward)) {
93 val atom =
94 TupleTableAtom.rdf(term0, IRI.create(iriString0 :: sx + Inverse), term1)
95 convert(inv, term0, term1, sx) shouldEqual atom
96 }
97 }
98
99 "A data property" should "be converted into an atom with matching predicate" in {
100 val prop = factory.getOWLDataProperty(iriString0)
101 for (suffix <- suffixes) {
102 val atom =
103 TupleTableAtom.rdf(term0, IRI.create(iriString0 :: suffix), term1)
104 convert(prop, term0, term1, suffix) shouldEqual atom
105 }
106 }
107
108}