diff options
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 | |||
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 |
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 | |||
15 | import scala.collection.JavaConverters._ | 15 | import scala.collection.JavaConverters._ |
16 | 16 | ||
17 | import uk.ac.ox.cs.rsacomb.RSAOntology | 17 | import uk.ac.ox.cs.rsacomb.RSAOntology |
18 | import uk.ac.ox.cs.rsacomb.converter.SkolemStrategy | ||
19 | import uk.ac.ox.cs.rsacomb.suffix.Empty | ||
18 | import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} | 20 | import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} |
19 | 21 | ||
20 | object Ontology1_CanonicalModelSpec { | 22 | object 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 @@ | |||
1 | package rsacomb | ||
2 | |||
3 | import org.scalatest.LoneElement | ||
4 | import org.scalatest.flatspec.AnyFlatSpec | ||
5 | import org.scalatest.matchers.should.Matchers | ||
6 | import org.semanticweb.owlapi.apibinding.OWLManager | ||
7 | import org.semanticweb.owlapi.model.OWLOntologyManager | ||
8 | |||
9 | import tech.oxfordsemantic.jrdfox.logic.datalog.TupleTableAtom | ||
10 | import tech.oxfordsemantic.jrdfox.logic.expression.{Variable, IRI} | ||
11 | import uk.ac.ox.cs.rsacomb.converter.RDFoxConverter | ||
12 | import uk.ac.ox.cs.rsacomb.suffix.{Empty, Forward, Backward, Inverse} | ||
13 | import uk.ac.ox.cs.rsacomb.converter.SkolemStrategy | ||
14 | |||
15 | object 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 | |||
35 | class 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 | } | ||