aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/uk/ac/ox
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/scala/uk/ac/ox')
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala284
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala366
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala249
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala380
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxAxiomConverter.scala105
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxClassExprConverter.scala160
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxPropertyExprConverter.scala35
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxRuleShards.scala5
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/converter/SkolemStrategy.scala78
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/implicits/JavaCollections.scala13
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RDFox.scala20
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala88
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAxiom.scala155
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/suffix/RSASuffix.scala31
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxHelpers.scala100
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala100
16 files changed, 2169 insertions, 0 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
new file mode 100644
index 0000000..f0f1bf8
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala
@@ -0,0 +1,284 @@
1package uk.ac.ox.cs.rsacomb
2
3import org.semanticweb.owlapi.model.{OWLObjectInverseOf, OWLObjectProperty}
4import org.semanticweb.owlapi.model.{
5 OWLClass,
6 // OWLObjectProperty,
7 OWLSubObjectPropertyOfAxiom,
8 // OWLObjectPropertyExpression,
9 OWLObjectSomeValuesFrom,
10 OWLSubClassOfAxiom
11}
12
13import tech.oxfordsemantic.jrdfox.logic.datalog.{
14 Rule,
15 BodyFormula,
16 TupleTableAtom,
17 Negation
18}
19import tech.oxfordsemantic.jrdfox.logic.expression.{
20 Term,
21 Variable,
22 // Resource,
23 IRI
24}
25
26import uk.ac.ox.cs.rsacomb.converter.{
27 SkolemStrategy,
28 RDFoxAxiomConverter,
29 RDFoxPropertyExprConverter
30}
31import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom
32import uk.ac.ox.cs.rsacomb.suffix.{Empty, Forward, Backward, Inverse}
33import uk.ac.ox.cs.rsacomb.util.RSA
34
35class CanonicalModel(val ontology: RSAOntology) extends RSAAxiom {
36
37 import implicits.RDFox._
38 import implicits.JavaCollections._
39
40 val named: List[Rule] =
41 ontology.individuals.map(a => Rule.create(RSA.Named(a)))
42
43 val rolesAdditionalRules: List[Rule] = {
44 // Given a role (predicate) compute additional logic rules
45 def additional(pred: String): Seq[Rule] = {
46 val varX = Variable.create("X")
47 val varY = Variable.create("Y")
48 for (
49 (hSuffix, bSuffix) <- List(
50 (Empty, Forward),
51 (Empty, Backward),
52 (Inverse, Forward + Inverse),
53 (Inverse, Backward + Inverse),
54 (Backward + Inverse, Forward),
55 (Forward + Inverse, Backward),
56 (Backward, Forward + Inverse),
57 (Forward, Backward + Inverse)
58 )
59 )
60 yield Rule.create(
61 TupleTableAtom.rdf(varX, pred :: hSuffix, varY),
62 TupleTableAtom.rdf(varX, pred :: bSuffix, varY)
63 )
64 }
65 // Compute additional rules per role
66 ontology.roles
67 .collect { case prop: OWLObjectProperty => prop }
68 .map(_.getIRI.getIRIString)
69 .flatMap(additional)
70 }
71
72 private lazy val topAxioms: List[Rule] = {
73 val varX = Variable.create("X")
74 val varY = Variable.create("Y")
75 val concepts = ontology.concepts.map(c => {
76 Rule.create(
77 RSA.Thing(varX),
78 TupleTableAtom.rdf(varX, IRI.RDF_TYPE, c.getIRI)
79 )
80 })
81 val roles = ontology.roles.map(r => {
82 val name = r match {
83 case x: OWLObjectProperty => x.getIRI.getIRIString
84 case x: OWLObjectInverseOf =>
85 x.getInverse.getNamedProperty.getIRI.getIRIString :: Inverse
86 }
87 Rule.create(
88 List(RSA.Thing(varX), RSA.Thing(varY)),
89 List(TupleTableAtom.rdf(varX, name, varY))
90 )
91 })
92 concepts ::: roles
93 }
94
95 private val equalityAxioms: List[Rule] = {
96 val varX = Variable.create("X")
97 val varY = Variable.create("Y")
98 val varZ = Variable.create("Z")
99 List(
100 // Reflexivity
101 Rule.create(RSA.congruent(varX, varX), RSA.Thing(varX)),
102 // Simmetry
103 Rule.create(RSA.congruent(varY, varX), RSA.congruent(varX, varY)),
104 // Transitivity
105 Rule.create(
106 RSA.congruent(varX, varZ),
107 RSA.congruent(varX, varY),
108 RSA.congruent(varY, varZ)
109 )
110 )
111 }
112
113 val rules: List[Rule] = {
114 // Compute rules from ontology axioms
115 val rules = ontology.axioms.flatMap(_.accept(RuleGenerator))
116 // Return full set of rules
117 rules ::: rolesAdditionalRules ::: topAxioms ::: equalityAxioms ::: named
118 }
119
120 object RuleGenerator
121 extends RDFoxAxiomConverter(
122 Variable.create("X"),
123 ontology.unsafeRoles,
124 SkolemStrategy.None,
125 Empty
126 ) {
127
128 private def rules1(axiom: OWLSubClassOfAxiom): List[Rule] = {
129 val unfold = ontology.unfold(axiom).toList
130 // Fresh Variables
131 val v0 = RSA("v0_" ++ axiom.hashed)
132 val varX = Variable.create("X")
133 implicit val unfoldTerm = RSA(unfold.hashCode.toString)
134 // TODO: use axiom.toTriple instead
135 val atomA: TupleTableAtom = {
136 val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI
137 TupleTableAtom.rdf(varX, IRI.RDF_TYPE, cls)
138 }
139 val roleRf: TupleTableAtom = {
140 val visitor =
141 new RDFoxPropertyExprConverter(varX, v0, Forward)
142 axiom.getSuperClass
143 .asInstanceOf[OWLObjectSomeValuesFrom]
144 .getProperty
145 .accept(visitor)
146 .head
147 }
148 val atomB: TupleTableAtom = {
149 val cls = axiom.getSuperClass
150 .asInstanceOf[OWLObjectSomeValuesFrom]
151 .getFiller
152 .asInstanceOf[OWLClass]
153 .getIRI
154 TupleTableAtom.rdf(v0, IRI.RDF_TYPE, cls)
155 }
156 // TODO: To be consistent with the specifics of the visitor we are
157 // returning facts as `Rule`s with true body. While this is correct
158 // there is an easier way to import facts into RDFox. Are we able to
159 // do that?
160 val facts = unfold.map(x => Rule.create(RSA.In(x)))
161 val rules = List(
162 Rule.create(roleRf, atomA, RSA.notIn(varX)),
163 Rule.create(atomB, atomA, RSA.notIn(varX))
164 )
165 facts ++ rules
166 }
167
168 private def rules2(axiom: OWLSubClassOfAxiom): List[Rule] = {
169 val roleR =
170 axiom.getSuperClass
171 .asInstanceOf[OWLObjectSomeValuesFrom]
172 .getProperty
173 if (ontology.confl(roleR) contains roleR) {
174 // Fresh Variables
175 val v0 = RSA("v0_" ++ axiom.hashed)
176 val v1 = RSA("v1_" ++ axiom.hashed)
177 val v2 = RSA("v2_" ++ axiom.hashed)
178 // Predicates
179 def atomA(t: Term): TupleTableAtom = {
180 val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI
181 TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls)
182 }
183 def roleRf(t1: Term, t2: Term): TupleTableAtom = {
184 val visitor =
185 new RDFoxPropertyExprConverter(t1, t2, Forward)
186 roleR.accept(visitor).head
187 }
188 def atomB(t: Term): TupleTableAtom = {
189 val cls = axiom.getSuperClass
190 .asInstanceOf[OWLObjectSomeValuesFrom]
191 .getFiller
192 .asInstanceOf[OWLClass]
193 .getIRI
194 TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls)
195 }
196 //Rules
197 List(
198 Rule.create(roleRf(v0, v1), atomA(v0)),
199 Rule.create(atomB(v1), atomA(v0)),
200 Rule.create(roleRf(v1, v2), atomA(v1)),
201 Rule.create(atomB(v2), atomA(v1))
202 )
203 } else {
204 List()
205 }
206 }
207
208 private def rules3(axiom: OWLSubClassOfAxiom): List[Rule] = {
209 val cycle = ontology.cycle(axiom).toList
210 val roleR =
211 axiom.getSuperClass
212 .asInstanceOf[OWLObjectSomeValuesFrom]
213 .getProperty
214 // Fresh Variables
215 val v1 = RSA("v1_" ++ axiom.hashed)
216 // Predicates
217 def atomA(t: Term): TupleTableAtom = {
218 val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI
219 TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls)
220 }
221 def roleRf(t: Term): TupleTableAtom = {
222 val visitor =
223 new RDFoxPropertyExprConverter(t, v1, Forward)
224 roleR.accept(visitor).head
225 }
226 val atomB: TupleTableAtom = {
227 val cls = axiom.getSuperClass
228 .asInstanceOf[OWLObjectSomeValuesFrom]
229 .getFiller
230 .asInstanceOf[OWLClass]
231 .getIRI
232 TupleTableAtom.rdf(v1, IRI.RDF_TYPE, cls)
233 }
234 cycle.flatMap { x =>
235 List(
236 Rule.create(roleRf(x), atomA(x)),
237 Rule.create(atomB, atomA(x))
238 )
239 }
240 }
241
242 override def visit(axiom: OWLSubClassOfAxiom): List[Rule] = {
243 if (axiom.isT5) {
244 // TODO: get role in T5 axiom
245 // Assuming one role here
246 val role = axiom.objectPropertyExpressionsInSignature(0)
247 if (ontology.unsafeRoles contains role) {
248 val visitor =
249 new RDFoxAxiomConverter(
250 Variable.create("X"),
251 ontology.unsafeRoles,
252 SkolemStrategy.Standard(axiom.toString),
253 Forward
254 )
255 axiom.accept(visitor)
256 } else {
257 rules1(axiom) ::: rules2(axiom) ::: rules3(axiom)
258 }
259 } else {
260 // Fallback to standard OWL to LP translation
261 super.visit(axiom)
262 }
263 }
264
265 override def visit(axiom: OWLSubObjectPropertyOfAxiom): List[Rule] = {
266 val varX = Variable.create("X")
267 val visitorF = new RDFoxAxiomConverter(
268 varX,
269 ontology.unsafeRoles,
270 SkolemStrategy.None,
271 Forward
272 )
273 val visitorB = new RDFoxAxiomConverter(
274 varX,
275 ontology.unsafeRoles,
276 SkolemStrategy.None,
277 Backward
278 )
279 axiom.accept(visitorB) ::: axiom.accept(visitorF)
280 }
281
282 }
283
284}
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala
new file mode 100644
index 0000000..b154575
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala
@@ -0,0 +1,366 @@
1package uk.ac.ox.cs.rsacomb
2
3import tech.oxfordsemantic.jrdfox.logic.Datatype
4import tech.oxfordsemantic.jrdfox.logic.expression.{
5 Term,
6 IRI,
7 Variable,
8 Literal,
9 FunctionCall
10}
11import tech.oxfordsemantic.jrdfox.logic.datalog.{
12 Rule,
13 TupleTableAtom,
14 BindAtom,
15 TupleTableName,
16 Atom,
17 BodyFormula,
18 Negation
19}
20import tech.oxfordsemantic.jrdfox.logic.sparql.statement.{SelectQuery}
21import tech.oxfordsemantic.jrdfox.logic.sparql.pattern.{
22 GroupGraphPattern,
23 ConjunctionPattern,
24 TriplePattern,
25 QueryPattern
26}
27
28import scala.collection.JavaConverters._
29
30import uk.ac.ox.cs.rsacomb.implicits.RSAAtom
31import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Forward, Backward}
32import uk.ac.ox.cs.rsacomb.util.RSA
33
34class FilteringProgram(query: SelectQuery, constants: List[Term])
35 extends RSAAtom {
36
37 /* Makes mplicit conversion OWLAPI IRI <-> RDFox IRI available */
38 // import implicits.RDFox._
39
40 implicit val variables: (List[Term], List[Term]) = {
41 val all: Set[Variable] = query.getQueryBody.getWherePattern match {
42 case b: ConjunctionPattern => {
43 b.getConjuncts.asScala.toSet.flatMap { conj: QueryPattern =>
44 conj match {
45 case c: TriplePattern =>
46 Set(c.getSubject, c.getPredicate, c.getObject).collect {
47 case v: Variable => v
48 }
49 case _ => Set()
50 }
51 }
52 }
53 case _ => Set()
54 }
55 if (query.getAllPossibleVariables) {
56 (all.toList, List())
57 } else {
58 val answer = query.getSelection.asScala.map(_.getVariable).toSet
59 (answer.toList, (all &~ answer).toList)
60 }
61 }
62
63 val (answer, bounded) = variables
64
65 val facts: List[Rule] = constants.map(c => Rule.create(RSA.NI(c)))
66 val rules: List[Rule] =
67 this.generateFilteringProgram().map(reifyRule) ++ facts
68
69 /* NOTE: we are restricting to queries that contain conjunctions of
70 * atoms for the time being. This might need to be reviewed in the
71 * future.
72 */
73 private def queryToBody(body: GroupGraphPattern): List[TupleTableAtom] =
74 body match {
75 case b: ConjunctionPattern => {
76 val conjuncts = b.getConjuncts.asScala.toList
77 conjuncts flatMap { conj =>
78 conj match {
79 case c: TriplePattern =>
80 List(
81 TupleTableAtom.rdf(c.getSubject, c.getPredicate, c.getObject)
82 )
83 case _ => List()
84 }
85 }
86 }
87 case _ => List()
88 }
89
90 private def generateFilteringProgram(): List[Rule] = {
91 // General purpose variables
92 val varU = Variable.create("U")
93 val varV = Variable.create("V")
94 val varW = Variable.create("W")
95 // Query formula as a rule body
96 val body = queryToBody(query.getQueryBody.getWherePattern)
97 // Auxiliar predicates/helpers
98 def not(atom: TupleTableAtom): BodyFormula = Negation.create(atom)
99 // val predQM =
100 // TupleTableAtom.create(
101 // TupleTableName.create(RSA.Qm.getIRI),
102 // (answer ++ bounded): _*
103 // )
104 // def predID(t1: Term, t2: Term) =
105 // TupleTableAtom.create(
106 // TupleTableName.create(RSA.rsa("ID").getIRI),
107 // (answer ++ bounded).appended(t1).appended(t2): _*
108 // )
109 // def predNAMED(t1: Term): TupleTableAtom =
110 // TupleTableAtom.rdf(t1, IRI.RDF_TYPE, RSA.rsa("NAMED"))
111 // def predTQ(t1: Term, t2: Term, sx: RSASuffix) =
112 // TupleTableAtom.create(
113 // TupleTableName.create(RSA.rsa("TQ" :: sx).getIRI),
114 // (answer ++ bounded).appended(t1).appended(t2): _*
115 // )
116 // def predAQ(t1: Term, t2: Term, sx: RSASuffix) =
117 // TupleTableAtom.create(
118 // TupleTableName.create(RSA.rsa("AQ" :: sx).getIRI),
119 // (answer ++ bounded).appended(t1).appended(t2): _*
120 // )
121 // val predFK =
122 // TupleTableAtom.create(
123 // TupleTableName.create(RSA.rsa("FK").getIRI),
124 // (answer ++ bounded): _*
125 // )
126 // val predSP =
127 // TupleTableAtom.create(
128 // TupleTableName.create(RSA.rsa("SP").getIRI),
129 // (answer ++ bounded): _*
130 // )
131 // val predANS =
132 // TupleTableAtom.create(
133 // TupleTableName.create(RSA.rsa("ANS").getIRI),
134 // answer: _*
135 // )
136
137 /* Rule 1 */
138 val r1 = Rule.create(RSA.QM, body: _*)
139
140 /* Rules 3x */
141 val r3a =
142 for ((v, i) <- bounded.zipWithIndex)
143 yield Rule.create(RSA.ID(RSA(i), RSA(i)), RSA.QM, not(RSA.NI(v)))
144 val r3b = Rule.create(RSA.ID(varV, varU), RSA.ID(varU, varV))
145 val r3c =
146 Rule.create(RSA.ID(varU, varW), RSA.ID(varU, varV), RSA.ID(varV, varW))
147
148 /* Rules 4x */
149 val r4a = for {
150 role1 <- body.filter(_.isRoleAssertion)
151 if bounded contains (role1.getArguments.get(2))
152 role2 <- body.filter(_.isRoleAssertion)
153 if bounded contains (role2.getArguments.get(2))
154 } yield Rule.create(
155 RSA.FK,
156 role1 << Forward,
157 role2 << Forward,
158 RSA.ID(
159 RSA(bounded.indexOf(role1.getArguments.get(2))),
160 RSA(bounded.indexOf(role2.getArguments.get(2)))
161 ),
162 not(RSA.congruent(role1.getArguments.get(0), role2.getArguments.get(0)))
163 )
164 val r4b = for {
165 role1 <- body.filter(_.isRoleAssertion)
166 if bounded contains (role1.getArguments.get(2))
167 role2 <- body.filter(_.isRoleAssertion)
168 if bounded contains (role2.getArguments.get(0))
169 } yield Rule.create(
170 RSA.FK,
171 role1 << Forward,
172 role2 << Backward,
173 RSA.ID(
174 RSA(bounded.indexOf(role1.getArguments.get(2))),
175 RSA(bounded.indexOf(role2.getArguments.get(0)))
176 ),
177 not(RSA.congruent(role1.getArguments.get(0), role2.getArguments.get(2)))
178 )
179 val r4c = for {
180 role1 <- body.filter(_.isRoleAssertion)
181 if bounded contains (role1.getArguments.get(0))
182 role2 <- body.filter(_.isRoleAssertion)
183 if bounded contains (role2.getArguments.get(0))
184 } yield Rule.create(
185 RSA.FK,
186 role1 << Backward,
187 role2 << Backward,
188 RSA.ID(
189 RSA(bounded.indexOf(role1.getArguments.get(0))),
190 RSA(bounded.indexOf(role2.getArguments.get(0)))
191 ),
192 not(RSA.congruent(role1.getArguments.get(2), role2.getArguments.get(2)))
193 )
194
195 /* Rules 5x */
196 val r5a = for {
197 role1 <- body.filter(_.isRoleAssertion)
198 role1arg0 = role1.getArguments.get(0)
199 role1arg2 = role1.getArguments.get(2)
200 if bounded contains role1arg0
201 if bounded contains role1arg2
202 role2 <- body.filter(_.isRoleAssertion)
203 role2arg0 = role2.getArguments.get(0)
204 role2arg2 = role2.getArguments.get(2)
205 if bounded contains role2arg0
206 if bounded contains role2arg2
207 } yield Rule.create(
208 RSA.ID(
209 RSA(bounded indexOf role1arg0),
210 RSA(bounded indexOf role2arg0)
211 ),
212 role1 << Forward,
213 role2 << Forward,
214 RSA.ID(
215 RSA(bounded indexOf role1arg2),
216 RSA(bounded indexOf role2arg2)
217 ),
218 RSA.congruent(role1arg0, role2arg0),
219 not(RSA.NI(role1arg0))
220 )
221 val r5b = for {
222 role1 <- body.filter(_.isRoleAssertion)
223 role1arg0 = role1.getArguments.get(0)
224 role1arg2 = role1.getArguments.get(2)
225 if bounded contains role1arg0
226 if bounded contains role1arg2
227 role2 <- body.filter(_.isRoleAssertion)
228 role2arg0 = role2.getArguments.get(0)
229 role2arg2 = role2.getArguments.get(2)
230 if bounded contains role2arg0
231 if bounded contains role2arg2
232 } yield Rule.create(
233 RSA.ID(
234 RSA(bounded indexOf role1arg0),
235 RSA(bounded indexOf role2arg2)
236 ),
237 role1 << Forward,
238 role2 << Backward,
239 RSA.ID(
240 RSA(bounded indexOf role1arg2),
241 RSA(bounded indexOf role2arg0)
242 ),
243 RSA.congruent(role1arg0, role2arg2),
244 not(RSA.NI(role1arg0))
245 )
246 val r5c = for {
247 role1 <- body.filter(_.isRoleAssertion)
248 role1arg0 = role1.getArguments.get(0)
249 role1arg2 = role1.getArguments.get(2)
250 if bounded contains role1arg0
251 if bounded contains role1arg2
252 role2 <- body.filter(_.isRoleAssertion)
253 role2arg0 = role2.getArguments.get(0)
254 role2arg2 = role2.getArguments.get(2)
255 if bounded contains role2arg0
256 if bounded contains role2arg2
257 } yield Rule.create(
258 RSA.ID(
259 RSA(bounded indexOf role1arg2),
260 RSA(bounded indexOf role2arg2)
261 ),
262 role1 << Backward,
263 role2 << Backward,
264 RSA.ID(
265 RSA(bounded indexOf role1arg0),
266 RSA(bounded indexOf role2arg0)
267 ),
268 RSA.congruent(role1arg2, role2arg2),
269 not(RSA.NI(role1arg2))
270 )
271
272 /* Rules 6 */
273 val r6 = {
274 for {
275 role <- body.filter(_.isRoleAssertion)
276 arg0 = role.getArguments.get(0)
277 arg2 = role.getArguments.get(2)
278 if bounded contains arg0
279 if bounded contains arg2
280 suffix <- Seq(Forward, Backward)
281 } yield Rule.create(
282 RSA.AQ(varV, varW, suffix),
283 role << suffix,
284 RSA.ID(RSA(bounded indexOf arg0), varV),
285 RSA.ID(RSA(bounded indexOf arg2), varW)
286 )
287 }
288
289 /* Rules 7x */
290 val r7a = {
291 for (suffix <- List(Forward, Backward))
292 yield Rule.create(
293 RSA.TQ(varU, varV, suffix),
294 RSA.AQ(varU, varV, suffix)
295 )
296 }
297 val r7b = {
298 for (suffix <- List(Forward, Backward))
299 yield Rule.create(
300 RSA.TQ(varU, varW, suffix),
301 RSA.AQ(varU, varV, suffix),
302 RSA.TQ(varV, varW, suffix)
303 )
304 }
305
306 /* Rules 8x */
307 val r8a =
308 for (v <- answer) yield Rule.create(RSA.SP, RSA.QM, not(RSA.Named(v)))
309 val r8b =
310 Rule.create(RSA.SP, RSA.FK)
311 val r8c =
312 for (suffix <- List(Forward, Backward))
313 yield Rule.create(
314 RSA.SP,
315 RSA.TQ(varV, varV, suffix)
316 )
317
318 /* Rule 9 */
319 val r9 = Rule.create(RSA.Ans, RSA.QM, not(RSA.SP))
320
321 r1 ::
322 r3a ::: r3b :: r3c ::
323 r4c ::: r4b ::: r4a :::
324 r5c ::: r5b ::: r5a :::
325 r6 :::
326 r7b ::: r7a :::
327 r8a ::: r8b :: r8c :::
328 r9 ::
329 List()
330 }
331
332 private def reifyAtom(atom: Atom): (Option[BindAtom], List[Atom]) = {
333 atom match {
334 case atom: TupleTableAtom => atom.reified
335 case other => (None, List(other))
336 }
337 }
338
339 private def reifyBodyFormula(formula: BodyFormula): List[BodyFormula] = {
340 formula match {
341 case atom: TupleTableAtom => atom.reified._2
342 case neg: Negation => {
343 val (bs, as) = neg.getNegatedAtoms.asScala.toList.map(reifyAtom).unzip
344 val bind = bs.flatten.map(_.getBoundVariable).asJava
345 val atoms = as.flatten.asJava
346 List(Negation.create(bind, atoms))
347 }
348 case other => List(other)
349 }
350 }
351
352 private def reifyRule(rule: Rule): Rule = {
353 val (bs, hs) = rule.getHead.asScala.toList.map(_.reified).unzip
354 val head: List[TupleTableAtom] = hs.flatten
355 val bind: List[BodyFormula] = bs.flatten
356 val body: List[BodyFormula] =
357 rule.getBody.asScala.toList.map(reifyBodyFormula).flatten
358 Rule.create(head.asJava, (body ++ bind).asJava)
359 }
360
361} // class FilteringProgram
362
363object FilteringProgram {
364 def apply(query: SelectQuery, constants: List[Term]): FilteringProgram =
365 new FilteringProgram(query, constants)
366} // object FilteringProgram
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala
new file mode 100644
index 0000000..c3db99d
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala
@@ -0,0 +1,249 @@
1package uk.ac.ox.cs.rsacomb
2
3/* Java imports */
4import java.io.File
5import java.util.HashMap
6import scala.collection.JavaConverters._
7
8import tech.oxfordsemantic.jrdfox.client.UpdateType
9import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery
10import tech.oxfordsemantic.jrdfox.logic.expression.{IRI, Term}
11
12/* Local imports */
13import util.{RDFoxHelpers, RSA}
14
15object RSAComb extends App {
16
17 val help: String = """
18 rsacomb - combined approach for CQ answering for RSA ontologies.
19
20 USAGE
21 rsacomb <path/to/ontology.owl> <path/to/query.sparql>
22
23 where
24 the ontology is expected to be an OWL file and the (single)
25 query a SPARQL query file.
26
27 """
28
29 /* Simple arguments handling
30 *
31 * TODO: use something better later on
32 */
33
34 if (args.length < 2) {
35 println(help)
36 sys.exit;
37 }
38
39 val ontoPath = new File(args(0))
40 val queryPath = new File(args(1))
41
42 if (!ontoPath.isFile || !queryPath.isFile) {
43 println("The provided arguments are not regular files.\n\n")
44 println(help)
45 sys.exit;
46 }
47
48 /* Create RSA object from generic OWLOntology
49 *
50 * TODO: It might be required to check if the ontology in input is
51 * Horn-ALCHOIQ. At the moment we are assuming this is always the
52 * case.
53 */
54
55 val ontology = RSAOntology(ontoPath)
56 if (ontology.isRSA) {
57
58 /* Load query */
59 val query = RDFoxHelpers.parseSelectQuery(
60 """
61 PREFIX : <http://example.com/rsa_example.owl#>
62
63 SELECT ?X
64 WHERE {
65 ?X a :D ;
66 :R ?Y .
67 ?Y :S ?Z .
68 ?Z a :D .
69 }
70 """
71 )
72
73 /* Compute answers to query */
74 query match {
75 case Some(query) => {
76
77 import implicits.JavaCollections._
78
79 // Open connection to RDFox
80 val (server, data) = RDFoxHelpers.openConnection("AnswerComputation")
81
82 {
83 println("\nQuery")
84 println(query)
85 }
86
87 // Step 1. Computing the canonical model
88 val canon = ontology.canonicalModel
89 data.addRules(canon.rules)
90
91 {
92 println("\nCanonical Model rules:")
93 canon.rules.foreach(println)
94 }
95
96 // Step 2. Computing the canonical model
97 val nis = {
98 val query = "SELECT ?Y WHERE { ?X rsa:EquivTo ?Y ; a rsa:Named . }"
99 RDFoxHelpers.submitSelectQuery(data, query, RSA.Prefixes).flatten
100 }
101 val filter = ontology.filteringProgram(query, nis)
102 data.addRules(filter.rules)
103
104 {
105 println("\nFiltering rules")
106 filter.rules.foreach(println)
107 }
108
109 // Retrieve answers
110 println("\nAnswers:")
111 val ans =
112 RDFoxHelpers.queryInternalPredicate(data, "Ans", filter.answer.length)
113 println(ans)
114
115 /* DEBUG: adding additional checks
116 */
117 {
118 import suffix.{Forward, Backward}
119
120 val arity = filter.answer.length + filter.bounded.length
121
122 println("\nIndividuals:")
123 ontology.individuals.foreach(println)
124
125 println("\nThings:")
126 val things = RDFoxHelpers.submitSelectQuery(
127 data,
128 """
129 PREFIX owl: <http://www.w3.org/2002/07/owl#>
130
131 SELECT ?X {
132 ?X a owl:Thing
133 }
134 """
135 )
136 println(things)
137
138 println("\nNAMEDs:")
139 val named = RDFoxHelpers.submitSelectQuery(
140 data,
141 """
142 SELECT ?X {
143 ?X a rsa:Named
144 }
145 """,
146 RSA.Prefixes
147 )
148 println(named)
149
150 println("\nNIs:")
151 val nis = RDFoxHelpers.submitSelectQuery(
152 data,
153 """
154 SELECT ?X {
155 ?X a rsa:NI
156 }
157 """,
158 RSA.Prefixes
159 )
160 println(nis)
161
162 // ID instances
163 println("\nIDs:")
164 val ids = RDFoxHelpers.queryInternalPredicate(
165 data,
166 "ID",
167 arity + 2
168 )
169 println(ids)
170
171 println("\nEquivTo:")
172 val equivs = RDFoxHelpers.submitSelectQuery(
173 data,
174 """
175 SELECT ?X ?Y {
176 ?X rsa:EquivTo ?Y
177 }
178 """,
179 RSA.Prefixes
180 )
181 println(equivs)
182
183 // Unfiltered answers
184 println("\nPossible answers:")
185 val qms = RDFoxHelpers.queryInternalPredicate(
186 data,
187 "QM",
188 arity
189 )
190 println(qms)
191
192 // Cycle detected
193 println("\nCycle detection:")
194 val aqf = RDFoxHelpers.queryInternalPredicate(
195 data,
196 "AQ" :: Forward,
197 arity + 2
198 )
199 val aqb = RDFoxHelpers.queryInternalPredicate(
200 data,
201 "AQ" :: Backward,
202 arity + 2
203 )
204 println(aqf)
205 println(aqb)
206
207 // Forks detected
208 println("\nForks:")
209 val fk = RDFoxHelpers.queryInternalPredicate(
210 data,
211 "FK",
212 arity
213 )
214 println(fk)
215
216 // Spurious answers
217 println("\nSpurious answers")
218 val sp = RDFoxHelpers.queryInternalPredicate(
219 data,
220 "SP",
221 arity
222 )
223 println(sp)
224 }
225
226 // Close connection to RDFox
227 RDFoxHelpers.closeConnection(server, data)
228 }
229 case None => {}
230 }
231 }
232}
233
234/* Notes:
235 *
236 * To establish a connection with a local RDFox instance, do the
237 * following:
238 *
239 * ```
240 * val serverConnection : ServerConnection = ConnectionFactory.newServerConnection("rdfox:local", "", "")
241 * serverConnection.createDataStore("test","seq",new HashMap())
242 * val dataStoreConnection : DataStoreConnection = serverConnection.newDataStoreConnection("test")
243 * dataStoreConnection.importData(
244 * UpdateType.ADDITION,
245 * Prefixes.s_emptyPrefixes,
246 * new File("./path/to/file")
247 * )
248 * ```
249 */
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
new file mode 100644
index 0000000..ac86e3d
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
@@ -0,0 +1,380 @@
1package uk.ac.ox.cs.rsacomb
2
3/* Java imports */
4import java.util.HashMap
5import java.util.stream.{Collectors, Stream}
6import java.io.File
7import org.semanticweb.owlapi.apibinding.OWLManager
8
9import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom}
10import org.semanticweb.owlapi.model.{
11 OWLClass,
12 OWLObjectProperty,
13 OWLSubObjectPropertyOfAxiom,
14 OWLObjectPropertyExpression,
15 OWLObjectSomeValuesFrom,
16 OWLSubClassOfAxiom
17}
18import org.semanticweb.owlapi.model.parameters.Imports
19import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory
20import org.semanticweb.owlapi.model.{IRI => OWLIRI}
21import uk.ac.manchester.cs.owl.owlapi.OWLObjectPropertyImpl
22
23import tech.oxfordsemantic.jrdfox.client.{UpdateType, DataStoreConnection}
24import tech.oxfordsemantic.jrdfox.logic.datalog.{
25 Rule,
26 TupleTableAtom,
27 Negation,
28 BodyFormula
29}
30import tech.oxfordsemantic.jrdfox.logic.expression.{
31 Term,
32 Variable,
33 IRI,
34 Resource
35}
36import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery
37
38/* Scala imports */
39import scala.collection.JavaConverters._
40import scala.collection.mutable.Set
41import scalax.collection.immutable.Graph
42import scalax.collection.GraphEdge.UnDiEdge
43
44/* Debug only */
45import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer
46import tech.oxfordsemantic.jrdfox.logic._
47import org.semanticweb.owlapi.model.OWLObjectInverseOf
48
49import uk.ac.ox.cs.rsacomb.converter.{RDFoxAxiomConverter, SkolemStrategy}
50import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom
51import uk.ac.ox.cs.rsacomb.suffix.{Empty, Forward, Backward, Inverse}
52import uk.ac.ox.cs.rsacomb.util.{RDFoxHelpers, RSA}
53
54object RSAOntology {
55
56 // Counter used to implement a simple fresh variable generator
57 private var counter = -1;
58
59 def apply(ontology: OWLOntology): RSAOntology = new RSAOntology(ontology)
60
61 def apply(ontology: File): RSAOntology =
62 new RSAOntology(loadOntology(ontology))
63
64 def genFreshVariable(): Variable = {
65 counter += 1
66 Variable.create(f"I$counter%03d")
67 }
68
69 private def loadOntology(onto: File): OWLOntology = {
70 val manager = OWLManager.createOWLOntologyManager()
71 manager.loadOntologyFromOntologyDocument(onto)
72 }
73}
74
75class RSAOntology(val ontology: OWLOntology) extends RSAAxiom {
76
77 // Gather TBox/RBox/ABox from original ontology
78 val tbox: List[OWLAxiom] =
79 ontology
80 .tboxAxioms(Imports.INCLUDED)
81 .collect(Collectors.toList())
82 .asScala
83 .toList
84
85 val rbox: List[OWLAxiom] =
86 ontology
87 .rboxAxioms(Imports.INCLUDED)
88 .collect(Collectors.toList())
89 .asScala
90 .toList
91
92 val abox: List[OWLAxiom] =
93 ontology
94 .aboxAxioms(Imports.INCLUDED)
95 .collect(Collectors.toList())
96 .asScala
97 .toList
98
99 val axioms: List[OWLAxiom] = abox ::: tbox ::: rbox
100
101 /* Retrieve individuals in the original ontology
102 */
103 val individuals: List[IRI] =
104 ontology
105 .getIndividualsInSignature()
106 .asScala
107 .map(_.getIRI)
108 .map(implicits.RDFox.owlapiToRdfoxIri)
109 .toList
110
111 val concepts: List[OWLClass] =
112 ontology.getClassesInSignature().asScala.toList
113
114 val roles: List[OWLObjectPropertyExpression] =
115 axioms
116 .flatMap(_.objectPropertyExpressionsInSignature)
117 .distinct
118
119 // OWLAPI reasoner for same easier tasks
120 private val reasoner =
121 (new StructuralReasonerFactory()).createReasoner(ontology)
122
123 /* Steps for RSA check
124 * 1) convert ontology axioms into LP rules
125 * 2) call RDFox on the onto and compute materialization
126 * 3) build graph from E(x,y) facts
127 * 4) check if the graph is tree-like
128 * ideally this annotates the graph with info about the reasons
129 * why the ontology might not be RSA. This could help a second
130 * step of approximation of an Horn-ALCHOIQ to RSA
131 */
132 lazy val isRSA: Boolean = {
133
134 val unsafe = this.unsafeRoles
135
136 /* DEBUG: print rules in DL syntax and unsafe roles */
137 //val renderer = new DLSyntaxObjectRenderer()
138 //println("\nDL rules:")
139 //axioms.foreach(x => println(renderer.render(x)))
140 //println("\nUnsafe roles:")
141 //println(unsafe)
142
143 /* Ontology convertion into LP rules */
144 val datalog = for {
145 axiom <- axioms
146 visitor = new RDFoxAxiomConverter(
147 RSAOntology.genFreshVariable(),
148 unsafe,
149 SkolemStrategy.ConstantRSA(axiom.toString),
150 Empty
151 )
152 rule <- axiom.accept(visitor)
153 } yield rule
154
155 /* DEBUG: print datalog rules */
156 //println("\nDatalog roles:")
157 //datalog.foreach(println)
158
159 // Open connection with RDFox
160 val (server, data) = RDFoxHelpers.openConnection("RSACheck")
161 // Add Data (hardcoded for now)
162 //data.importData(UpdateType.ADDITION, RSA.Prefixes, ":a a :A .")
163
164 /* Add built-in rules
165 */
166 data.importData(
167 UpdateType.ADDITION,
168 RSA.Prefixes,
169 "rsa:E[?X,?Y] :- rsa:PE[?X,?Y], rsa:U[?X], rsa:U[?Y] ."
170 )
171
172 /* Add built-in rules
173 */
174 // data.importData(
175 // UpdateType.ADDITION,
176 // RSA.Prefixes,
177 // "[?entity, a, ?superClass] :- [?entity, a, ?class], [?class, rdfs:subClassOf, ?superClass] ."
178 // )
179
180 /* Add ontology rules
181 */
182 data.addRules(datalog.asJava)
183
184 /* Build graph
185 */
186 val graph = this.rsaGraph(data);
187 //println(graph)
188
189 // Close connection to RDFox
190 RDFoxHelpers.closeConnection(server, data)
191
192 /* To check if the graph is tree-like we check for acyclicity in a
193 * undirected graph.
194 *
195 * TODO: Implement additional checks (taking into account equality)
196 */
197 graph.isAcyclic
198 }
199
200 lazy val unsafeRoles: List[OWLObjectPropertyExpression] = {
201
202 /* DEBUG: print rules in DL syntax */
203 //val renderer = new DLSyntaxObjectRenderer()
204
205 /* Checking for (1) unsafety condition:
206 *
207 * For all roles r1 appearing in an axiom of type T5, r1 is unsafe
208 * if there exists a role r2 (different from top) appearing in an axiom
209 * of type T3 and r1 is a subproperty of the inverse of r2.
210 */
211 val unsafe1 = for {
212 axiom <- tbox
213 if axiom.isT5
214 role1 <- axiom.objectPropertyExpressionsInSignature
215 roleSuper =
216 role1 +: reasoner
217 .superObjectProperties(role1)
218 .collect(Collectors.toList())
219 .asScala
220 roleSuperInv = roleSuper.map(_.getInverseProperty)
221 axiom <- tbox
222 if axiom.isT3 && !axiom.isT3top
223 role2 <- axiom.objectPropertyExpressionsInSignature
224 if roleSuperInv.contains(role2)
225 } yield role1
226
227 /* Checking for (2) unsafety condition:
228 *
229 * For all roles p1 appearing in an axiom of type T5, p1 is unsafe if
230 * there exists a role p2 appearing in an axiom of type T4 and p1 is a
231 * subproperty of either p2 or the inverse of p2.
232 *
233 */
234 val unsafe2 = for {
235 axiom <- tbox
236 if axiom.isT5
237 role1 <- axiom.objectPropertyExpressionsInSignature
238 roleSuper =
239 role1 +: reasoner
240 .superObjectProperties(role1)
241 .collect(Collectors.toList())
242 .asScala
243 roleSuperInv = roleSuper.map(_.getInverseProperty)
244 axiom <- tbox
245 if axiom.isT4
246 role2 <- axiom.objectPropertyExpressionsInSignature
247 if roleSuper.contains(role2) || roleSuperInv.contains(role2)
248 } yield role1
249
250 (unsafe1 ++ unsafe2).toList
251 }
252
253 private def rsaGraph(
254 data: DataStoreConnection
255 ): Graph[Resource, UnDiEdge] = {
256 val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }"
257 val answers = RDFoxHelpers.submitSelectQuery(data, query, RSA.Prefixes)
258 var edges: List[UnDiEdge[Resource]] = answers.map {
259 case n1 :: n2 :: _ => UnDiEdge(n1, n2)
260 }
261 Graph(edges: _*)
262 }
263
264 def filteringProgram(
265 query: SelectQuery,
266 nis: List[Term]
267 ): FilteringProgram =
268 new FilteringProgram(query, nis)
269
270 lazy val canonicalModel = new CanonicalModel(this)
271
272 // TODO: the following functions needs testing
273 def confl(
274 role: OWLObjectPropertyExpression
275 ): Set[OWLObjectPropertyExpression] = {
276
277 val invSuperRoles = reasoner
278 .superObjectProperties(role)
279 .collect(Collectors.toSet())
280 .asScala
281 .addOne(role)
282 .map(_.getInverseProperty)
283
284 invSuperRoles
285 .flatMap(x =>
286 reasoner
287 .subObjectProperties(x)
288 .collect(Collectors.toSet())
289 .asScala
290 .addOne(x)
291 )
292 .filterNot(_.isOWLBottomObjectProperty())
293 .filterNot(_.getInverseProperty.isOWLTopObjectProperty())
294 }
295
296 def self(axiom: OWLSubClassOfAxiom): Set[Term] = {
297 // Assuming just one role in the signature of a T5 axiom
298 val role = axiom.objectPropertyExpressionsInSignature(0)
299 if (this.confl(role).contains(role)) {
300 Set(
301 RSA("v0_" ++ axiom.hashed),
302 RSA("v1_" ++ axiom.hashed)
303 )
304 } else {
305 Set()
306 }
307 }
308
309 // def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = {
310 // // Assuming just one role in the signature of a T5 axiom
311 // val roleR = axiom.objectPropertyExpressionsInSignature(0)
312 // val conflR = this.confl(roleR)
313 // // We just need the TBox to find
314 // val tbox = ontology
315 // .tboxAxioms(Imports.INCLUDED)
316 // .collect(Collectors.toSet())
317 // .asScala
318 // for {
319 // axiom1 <- tbox
320 // // TODO: is this an optimization or an error?
321 // if axiom1.isT5
322 // // We expect only one role coming out of a T5 axiom
323 // roleS <- axiom1.objectPropertyExpressionsInSignature
324 // // Triples ordering is among triples involving safe roles.
325 // if !unsafeRoles.contains(roleS)
326 // if conflR.contains(roleS)
327 // individual =
328 // if (axiom.hashCode < axiom1.hashCode) {
329 // RSA.rsa("v0_" ++ axiom1.hashCode.toString())
330 // } else {
331 // RSA.rsa("v1_" ++ axiom1.hashCode.toString())
332 // }
333 // } yield individual
334 // }
335
336 def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = {
337 // TODO: we can actually use `toTriple` from `RSAAxiom`
338 val classes =
339 axiom.classesInSignature.collect(Collectors.toList()).asScala
340 val classA = classes(0)
341 val roleR = axiom
342 .objectPropertyExpressionsInSignature(0)
343 .asInstanceOf[OWLObjectProperty]
344 val classB = classes(1)
345 cycle_aux(classA, roleR, classB)
346 }
347
348 def cycle_aux(
349 classA: OWLClass,
350 roleR: OWLObjectProperty,
351 classB: OWLClass
352 ): Set[Term] = {
353 val conflR = this.confl(roleR)
354 val classes = ontology
355 .classesInSignature(Imports.INCLUDED)
356 .collect(Collectors.toSet())
357 .asScala
358 for {
359 classD <- classes
360 roleS <- conflR
361 classC <- classes
362 // Keeping this check for now
363 if !unsafeRoles.contains(roleS)
364 tripleARB = RSAAxiom.hashed(classA, roleR, classB)
365 tripleDSC = RSAAxiom.hashed(classD, roleS, classC)
366 individual =
367 if (tripleARB > tripleDSC) {
368 RSA("v1_" ++ tripleDSC)
369 } else {
370 // Note that this is also the case for
371 // `tripleARB == tripleDSC`
372 RSA("v0_" ++ tripleDSC)
373 }
374 } yield individual
375 }
376
377 def unfold(axiom: OWLSubClassOfAxiom): Set[Term] =
378 this.self(axiom) | this.cycle(axiom)
379
380} // implicit class RSAOntology
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxAxiomConverter.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxAxiomConverter.scala
new file mode 100644
index 0000000..a8d1ffd
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxAxiomConverter.scala
@@ -0,0 +1,105 @@
1package uk.ac.ox.cs.rsacomb.converter
2
3import org.semanticweb.owlapi.model.{
4 OWLAxiom,
5 OWLSubClassOfAxiom,
6 OWLEquivalentClassesAxiom,
7 OWLObjectPropertyExpression
8}
9import org.semanticweb.owlapi.model.OWLAxiomVisitorEx
10
11import tech.oxfordsemantic.jrdfox.logic.datalog.{
12 Rule,
13 BodyFormula,
14 TupleTableAtom,
15 TupleTableName
16}
17import tech.oxfordsemantic.jrdfox.logic.expression.{
18 Term,
19 IRI,
20 Variable,
21 Literal
22}
23
24import scala.collection.JavaConverters._
25
26import org.semanticweb.owlapi.model.OWLSubObjectPropertyOfAxiom
27import org.semanticweb.owlapi.model.OWLObjectProperty
28import org.semanticweb.owlapi.model.OWLClassAssertionAxiom
29
30import uk.ac.ox.cs.rsacomb.RSAOntology
31import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Empty}
32
33object RDFoxAxiomConverter {
34
35 def apply(
36 term: Term,
37 unsafe: List[OWLObjectPropertyExpression],
38 skolem: SkolemStrategy = SkolemStrategy.None,
39 suffix: RSASuffix = Empty
40 ): RDFoxAxiomConverter =
41 new RDFoxAxiomConverter(term, unsafe, skolem, suffix)
42
43} // object RDFoxAxiomConverter
44
45class RDFoxAxiomConverter(
46 term: Term,
47 unsafe: List[OWLObjectPropertyExpression],
48 skolem: SkolemStrategy,
49 suffix: RSASuffix
50) extends OWLAxiomVisitorEx[List[Rule]] {
51
52 override def visit(axiom: OWLSubClassOfAxiom): List[Rule] = {
53 // Skolemization is needed only for the head of an axiom
54 val subVisitor =
55 new RDFoxClassExprConverter(term, unsafe, SkolemStrategy.None, suffix)
56 val superVisitor = new RDFoxClassExprConverter(term, unsafe, skolem, suffix)
57 // Each visitor returns a `RDFoxRuleShards`, a tuple (res,ext):
58 // - the `res` List is a list of atoms resulting from the conversion
59 // of the axiom.
60 // - for some Class Expressions appearing in the head of an Axiom,
61 // the conversion might produce atoms that need to appear in the
62 // body (and not in the head) of the rule. This is what the `ext`
63 // List is for.
64 val sub = axiom.getSubClass.accept(subVisitor)
65 val sup = axiom.getSuperClass.accept(superVisitor)
66 val head = sup.res.asJava
67 val body = (sub.res ++ sup.ext).asJava
68 List(Rule.create(head, body))
69 }
70
71 override def visit(axiom: OWLEquivalentClassesAxiom): List[Rule] = {
72 for {
73 axiom1 <- axiom.asPairwiseAxioms.asScala.toList
74 axiom2 <- axiom1.asOWLSubClassOfAxioms.asScala.toList
75 rule <- axiom2.accept(this)
76 } yield rule
77 }
78
79 override def visit(axiom: OWLSubObjectPropertyOfAxiom): List[Rule] = {
80 val term1 = RSAOntology.genFreshVariable()
81 val subVisitor =
82 new RDFoxPropertyExprConverter(term, term1, suffix)
83 val superVisitor = new RDFoxPropertyExprConverter(term, term1, suffix)
84 val body: List[BodyFormula] = axiom.getSubProperty.accept(subVisitor)
85 val head: List[TupleTableAtom] = axiom.getSuperProperty.accept(superVisitor)
86 List(Rule.create(head.asJava, body.asJava))
87 }
88
89 override def visit(axiom: OWLClassAssertionAxiom): List[Rule] = {
90 val ind = axiom.getIndividual
91 if (ind.isNamed) {
92 val term = IRI.create(ind.asOWLNamedIndividual().getIRI.getIRIString)
93 val cls = axiom.getClassExpression
94 val visitor =
95 new RDFoxClassExprConverter(term, unsafe, SkolemStrategy.None, suffix)
96 val shard = cls.accept(visitor)
97 List(Rule.create(shard.res.asJava, shard.ext.asJava))
98 } else {
99 List()
100 }
101 }
102
103 def doDefault(axiom: OWLAxiom): List[Rule] = List()
104
105} // class RDFoxAxiomConverter
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxClassExprConverter.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxClassExprConverter.scala
new file mode 100644
index 0000000..c151c9a
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxClassExprConverter.scala
@@ -0,0 +1,160 @@
1package uk.ac.ox.cs.rsacomb.converter
2
3import scala.collection.JavaConverters._
4import java.util.stream.{Stream, Collectors}
5
6import org.semanticweb.owlapi.model.{
7 OWLClassExpression,
8 OWLClass,
9 OWLObjectSomeValuesFrom,
10 OWLObjectIntersectionOf,
11 OWLObjectOneOf,
12 OWLObjectMaxCardinality
13}
14import org.semanticweb.owlapi.model.OWLClassExpressionVisitorEx
15import tech.oxfordsemantic.jrdfox.logic.Datatype
16import tech.oxfordsemantic.jrdfox.logic.datalog.{
17 BindAtom,
18 TupleTableName,
19 TupleTableAtom
20}
21import tech.oxfordsemantic.jrdfox.logic.expression.{
22 Term,
23 Literal,
24 Variable,
25 FunctionCall,
26 IRI
27}
28
29import org.semanticweb.owlapi.model.OWLObjectPropertyExpression
30import org.semanticweb.owlapi.model.OWLObjectProperty
31
32import uk.ac.ox.cs.rsacomb.RSAOntology
33import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Empty}
34import uk.ac.ox.cs.rsacomb.util.RSA
35
36object RDFoxClassExprConverter {
37
38 def apply(
39 term: Term,
40 unsafe: List[OWLObjectPropertyExpression] = List(),
41 skolem: SkolemStrategy = SkolemStrategy.None,
42 suffix: RSASuffix = Empty
43 ): RDFoxClassExprConverter =
44 new RDFoxClassExprConverter(term, unsafe, skolem, suffix)
45
46 def merge(rules: List[RDFoxRuleShards]): RDFoxRuleShards = {
47 rules.foldLeft(RDFoxRuleShards(List(), List())) { (r1, r2) =>
48 RDFoxRuleShards(
49 r1.res ++ r2.res,
50 r1.ext ++ r2.ext
51 )
52 }
53 }
54
55} // object RDFoxClassExprConverter
56
57class RDFoxClassExprConverter(
58 term: Term,
59 unsafe: List[OWLObjectPropertyExpression],
60 skolem: SkolemStrategy,
61 suffix: RSASuffix
62) extends OWLClassExpressionVisitorEx[RDFoxRuleShards] {
63
64 import uk.ac.ox.cs.rsacomb.implicits.RDFox._
65
66 // OWLClass
67 override def visit(expr: OWLClass): RDFoxRuleShards = {
68 val iri: IRI = if (expr.isTopEntity()) IRI.THING else expr.getIRI()
69 val atom = List(TupleTableAtom.rdf(term, IRI.RDF_TYPE, iri))
70 RDFoxRuleShards(atom, List())
71 }
72
73 // OWLObjectIntersectionOf
74 override def visit(expr: OWLObjectIntersectionOf): RDFoxRuleShards = {
75 val visitor = new RDFoxClassExprConverter(term, unsafe, skolem, suffix)
76 // TODO: maybe using `flatMap` instead of `merge` + `map` works as well
77 RDFoxClassExprConverter.merge(
78 expr.asConjunctSet.asScala.toList
79 .map((e: OWLClassExpression) => e.accept(visitor))
80 )
81 }
82
83 // OWLObjectOneOf
84 override def visit(expr: OWLObjectOneOf): RDFoxRuleShards = {
85 val visitor = RDFoxClassExprConverter(term, unsafe, skolem, suffix)
86 // TODO: review nominal handling. Here we are taking "just" one
87 val ind = expr.individuals
88 .collect(Collectors.toList())
89 .asScala
90 .filter(_.isOWLNamedIndividual)
91 .head // restricts to proper "nominals"
92 .asOWLNamedIndividual
93 .getIRI
94 val atom = List(
95 TupleTableAtom.rdf(term, IRI.SAME_AS, ind)
96 )
97 RDFoxRuleShards(atom, List())
98 }
99
100 // OWLObjectSomeValuesFrom
101 override def visit(expr: OWLObjectSomeValuesFrom): RDFoxRuleShards = {
102 val y = RSAOntology.genFreshVariable()
103 // Here we are assuming a role name
104 val prop = expr.getProperty()
105 // Computes the result of rule skolemization. Depending on the used
106 // technique it might involve the introduction of additional atoms,
107 // and/or fresh constants and variables.
108 val (head, body, term1) = skolem match {
109 case SkolemStrategy.None => (List(), List(), y)
110 case SkolemStrategy.Constant(c) => (List(), List(), c)
111 case SkolemStrategy.ConstantRSA(c) => {
112 if (unsafe.contains(prop))
113 (List(RSA.PE(term, c), RSA.U(c)), List(), c)
114 else
115 (List(), List(), c)
116 }
117 case SkolemStrategy.Standard(f) => {
118 (
119 List(),
120 List(BindAtom.create(FunctionCall.create("SKOLEM", f, term), y)),
121 y
122 )
123 }
124 }
125 val classVisitor =
126 new RDFoxClassExprConverter(term1, unsafe, skolem, suffix)
127 val classResult = expr.getFiller.accept(classVisitor)
128 val propertyVisitor = new RDFoxPropertyExprConverter(term, term1, suffix)
129 val propertyResult = expr.getProperty.accept(propertyVisitor)
130 RDFoxRuleShards(
131 classResult.res ++ propertyResult ++ head,
132 classResult.ext ++ body
133 )
134 }
135
136 // OWLObjectMaxCardinality
137 override def visit(expr: OWLObjectMaxCardinality): RDFoxRuleShards = {
138 // TODO: again, no hardcoded variables
139 val vars =
140 List(RSAOntology.genFreshVariable(), RSAOntology.genFreshVariable())
141 val classResult = RDFoxClassExprConverter.merge(
142 vars
143 .map(new RDFoxClassExprConverter(_, unsafe, skolem, suffix))
144 .map(expr.getFiller.accept(_))
145 )
146 val propertyResult =
147 vars
148 .map(new RDFoxPropertyExprConverter(term, _, suffix))
149 .map(expr.getProperty.accept(_))
150 .flatten
151 RDFoxRuleShards(
152 List(TupleTableAtom.rdf(vars(0), IRI.SAME_AS, vars(1))),
153 classResult.res ++ propertyResult
154 )
155 }
156
157 def doDefault(expr: OWLClassExpression): RDFoxRuleShards =
158 RDFoxRuleShards(List(), List())
159
160} // class RDFoxClassExprConverter
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxPropertyExprConverter.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxPropertyExprConverter.scala
new file mode 100644
index 0000000..94c7887
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxPropertyExprConverter.scala
@@ -0,0 +1,35 @@
1package uk.ac.ox.cs.rsacomb.converter
2
3import org.semanticweb.owlapi.model.{OWLPropertyExpression, OWLObjectProperty}
4import org.semanticweb.owlapi.model.OWLPropertyExpressionVisitorEx
5
6import tech.oxfordsemantic.jrdfox.logic.datalog.TupleTableAtom
7import tech.oxfordsemantic.jrdfox.logic.expression.{Term, IRI, Literal}
8
9import org.semanticweb.owlapi.model.OWLObjectInverseOf
10
11import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Inverse}
12
13class RDFoxPropertyExprConverter(
14 term1: Term,
15 term2: Term,
16 suffix: RSASuffix
17) extends OWLPropertyExpressionVisitorEx[List[TupleTableAtom]] {
18
19 // Automatically converts OWLAPI types into RDFox equivalent types.
20 import uk.ac.ox.cs.rsacomb.implicits.RDFox._
21
22 override def visit(expr: OWLObjectProperty): List[TupleTableAtom] = {
23 val base = expr.getIRI.getIRIString
24 val pred = IRI.create(base :: suffix)
25 List(TupleTableAtom.rdf(term1, pred, term2))
26 }
27
28 override def visit(expr: OWLObjectInverseOf): List[TupleTableAtom] = {
29 val visitor = new RDFoxPropertyExprConverter(term1, term2, suffix + Inverse)
30 expr.getInverse.accept(visitor)
31 }
32
33 def doDefault(expr: OWLPropertyExpression): List[TupleTableAtom] = List()
34
35} // class RDFoxPropertyExprConverter
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxRuleShards.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxRuleShards.scala
new file mode 100644
index 0000000..c88cf3c
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxRuleShards.scala
@@ -0,0 +1,5 @@
1package uk.ac.ox.cs.rsacomb.converter
2
3import tech.oxfordsemantic.jrdfox.logic.datalog.{TupleTableAtom, BodyFormula}
4
5case class RDFoxRuleShards(res: List[TupleTableAtom], ext: List[BodyFormula])
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/SkolemStrategy.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/SkolemStrategy.scala
new file mode 100644
index 0000000..0d72226
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/SkolemStrategy.scala
@@ -0,0 +1,78 @@
1package uk.ac.ox.cs.rsacomb.converter
2
3import tech.oxfordsemantic.jrdfox.logic.Datatype
4import tech.oxfordsemantic.jrdfox.logic.expression.{Literal, IRI}
5
6sealed trait SkolemStrategy
7
8object SkolemStrategy {
9 // TODO: might want to use something else other than `hashCode` as a
10 // function to generate a fresh function/constant
11
12 /* No skolemization at all.
13 *
14 * From
15 * ∃R.A ⊑ B
16 * to
17 * R(x,y), B(y) -> B(x)
18 */
19 case object None extends SkolemStrategy
20
21 /* Functional skolemization
22 *
23 * From
24 * A ⊑ ∃R.B
25 * to
26 * A(x) -> R(x,f(x)), B(f(x))
27 * for f, fresh function associated with the input axiom
28 *
29 * In RDFox this can represented combining the BIND operator with the
30 * SKOLEM operator as such:
31 * A(x), BIND(y, SKOLEM("f", x)) -> R(x,y), B(y)
32 * The first argument of a SKOLEM call is a literal string (ideally
33 * identifing the simulated function name).
34 *
35 * NOTE: this requirement for the SKOLEM operator is not enforced by
36 * RDFox, that will fail silently if omitted.
37 */
38 case class Standard(func: Literal) extends SkolemStrategy
39 object Standard {
40 def apply(axiom: String) =
41 new Standard(
42 Literal.create(genFunctionString(axiom), Datatype.XSD_STRING)
43 )
44 def genFunctionString(str: String) = "f_" ++ str.hashCode.toString
45 }
46
47 /* Constant skolemization
48 *
49 * From
50 * A ⊑ ∃R.B
51 * to
52 * A(y) -> R(x,c), B(c)
53 * for c, fresh constant associated with the input axiom
54 */
55 case class Constant(const: IRI) extends SkolemStrategy
56 object Constant {
57 def apply(axiom: String) =
58 new Constant(IRI.create(genConstantString(axiom)))
59 def genConstantString(str: String) = "c_" ++ str.hashCode.toString
60 }
61
62 /* (RSA) Constant skolemization
63 * This is a special skolemization option to introduce additional atoms for RSA
64 * checking algorithm.
65 *
66 * From
67 * A ⊑ ∃R.B
68 * to
69 * A(y) -> R(x,c), PE(x,c), B(c)
70 * for c, fresh constant associated with the input axiom and PE an internal predicate.
71 */
72 case class ConstantRSA(const: IRI) extends SkolemStrategy
73 object ConstantRSA {
74 def apply(axiom: String) =
75 new ConstantRSA(IRI.create(genConstantString(axiom)))
76 def genConstantString(str: String) = "c_" ++ str.hashCode.toString
77 }
78}
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
new file mode 100644
index 0000000..3b621f4
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/JavaCollections.scala
@@ -0,0 +1,13 @@
1package uk.ac.ox.cs.rsacomb.implicits
2
3import scala.collection.JavaConverters._
4
5object JavaCollections {
6
7 implicit def javaToScalaList[A](list: java.util.List[A]): List[A] =
8 list.asScala.toList
9
10 implicit def scalaToJavaList[A](list: List[A]): java.util.List[A] =
11 list.asJava
12
13}
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RDFox.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RDFox.scala
new file mode 100644
index 0000000..0462a47
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RDFox.scala
@@ -0,0 +1,20 @@
1package uk.ac.ox.cs.rsacomb.implicits
2
3import tech.oxfordsemantic.jrdfox.logic.expression.{IRI => RDFoxIRI}
4import org.semanticweb.owlapi.model.{IRI => OWLIRI}
5
6object RDFox {
7
8 implicit def rdfoxToOwlapiIri(iri: RDFoxIRI): OWLIRI = {
9 OWLIRI.create(iri.getIRI)
10 }
11
12 implicit def owlapiToRdfoxIri(iri: OWLIRI): RDFoxIRI = {
13 RDFoxIRI.create(iri.getIRIString())
14 }
15
16 implicit def stringToRdfoxIri(iri: String): RDFoxIRI = {
17 RDFoxIRI.create(iri)
18 }
19
20}
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala
new file mode 100644
index 0000000..a8afc72
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala
@@ -0,0 +1,88 @@
1package uk.ac.ox.cs.rsacomb.implicits
2
3import tech.oxfordsemantic.jrdfox.logic.Datatype
4import tech.oxfordsemantic.jrdfox.logic.expression.{Literal, FunctionCall}
5import tech.oxfordsemantic.jrdfox.logic.datalog.{
6 BindAtom,
7 TupleTableAtom,
8 TupleTableName
9}
10import tech.oxfordsemantic.jrdfox.logic.expression.{IRI}
11import scala.collection.JavaConverters._
12
13import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Nth}
14import uk.ac.ox.cs.rsacomb.RSAOntology
15
16/* Is this the best way to determine if an atom is an RDF triple?
17 * Note that we can't use `getNumberOfArguments()` because is not
18 * "consistent":
19 * - for an atom created with `rdf(<term1>, <term2>, <term3>)`,
20 * `getNumberOfArguments` returns 3
21 * - for an atom created with `Atom.create(<tupletablename>, <term1>,
22 * <term2>, <term3>)`, `getNumberOfArguments()` returns 3
23 *
24 * This is probably because `Atom.rdf(...) is implemented as:
25 * ```scala
26 * def rdf(term1: Term, term2: Term, term3: Term): Atom =
27 * Atom.create(TupleTableName.create("internal:triple"), term1, term2, term3)
28 * ```
29 */
30
31trait RSAAtom {
32
33 implicit class RSAAtom(val atom: TupleTableAtom) {
34
35 import RDFox._
36
37 val name: String = atom.getTupleTableName.getName
38
39 val isRDF: Boolean = name == "internal:triple"
40
41 val isClassAssertion: Boolean = {
42 isRDF && {
43 val pred = atom.getArguments.get(1)
44 pred == IRI.RDF_TYPE
45 }
46 }
47
48 val isRoleAssertion: Boolean = isRDF && !isClassAssertion
49
50 def <<(suffix: RSASuffix): TupleTableAtom =
51 if (isRDF) {
52 val subj = atom.getArguments.get(0)
53 val pred = atom.getArguments.get(1)
54 val obj = atom.getArguments.get(2)
55 if (isClassAssertion) {
56 val obj1 = obj match {
57 case iri: IRI => IRI.create(iri.getIRI :: suffix)
58 case other => other
59 }
60 TupleTableAtom.rdf(subj, pred, obj1)
61 } else {
62 val pred1 = pred match {
63 case iri: IRI => IRI.create(iri.getIRI :: suffix)
64 case other => other
65 }
66 TupleTableAtom.rdf(subj, pred1, obj)
67 }
68 } else {
69 val ttname = TupleTableName.create(name :: suffix)
70 TupleTableAtom.create(ttname, atom.getArguments())
71 }
72
73 lazy val reified: (Option[BindAtom], List[TupleTableAtom]) =
74 if (isRDF) {
75 (None, List(atom))
76 } else {
77 val bvar = RSAOntology.genFreshVariable()
78 val str = Literal.create(name, Datatype.XSD_STRING)
79 val args = atom.getArguments.asScala.toList
80 val skolem = FunctionCall.create("SKOLEM", str :: args: _*)
81 val bind = BindAtom.create(skolem, bvar)
82 val atoms = args.zipWithIndex
83 .map { case (t, i) => TupleTableAtom.rdf(bvar, name :: Nth(i), t) }
84 (Some(bind), atoms)
85 }
86 }
87
88}
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAxiom.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAxiom.scala
new file mode 100644
index 0000000..e39d5b2
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAxiom.scala
@@ -0,0 +1,155 @@
1package uk.ac.ox.cs.rsacomb.implicits
2
3/* Java imports */
4import org.semanticweb.owlapi.model.{
5 OWLAxiom,
6 OWLSubClassOfAxiom,
7 OWLEquivalentClassesAxiom
8}
9import org.semanticweb.owlapi.model.{
10 OWLObjectPropertyExpression,
11 OWLSubObjectPropertyOfAxiom,
12 OWLClass,
13 OWLClassExpression,
14 OWLObjectSomeValuesFrom,
15 OWLObjectMaxCardinality
16}
17import org.semanticweb.owlapi.model.ClassExpressionType
18import org.semanticweb.owlapi.model.{
19 OWLAxiomVisitorEx,
20 OWLClassExpressionVisitorEx
21}
22import org.semanticweb.owlapi.model.OWLObjectProperty
23import scala.collection.JavaConverters._
24
25/* Wrapper trait for the implicit class `RSAAxiom`.
26 */
27trait RSAAxiom {
28
29 /* Identifies some of the axiom types in a Horn-ALCHOIQ ontology
30 * in normal form. Refer to the paper for more details on the
31 * chosen names.
32 */
33 private sealed trait RSAAxiomType
34 private object RSAAxiomType {
35 case object T3 extends RSAAxiomType // ∃R.A ⊑ B
36 case object T3top extends RSAAxiomType // ∃R.⊤ ⊑ B
37 case object T4 extends RSAAxiomType // A ⊑ ≤1R.B
38 case object T5 extends RSAAxiomType // A ⊑ ∃R.B
39 }
40
41 object RSAAxiom {
42
43 def hashed(
44 cls1: OWLClass,
45 prop: OWLObjectPropertyExpression,
46 cls2: OWLClass
47 ): String =
48 (cls1, prop, cls2).hashCode.toString
49
50 }
51
52 /* Implements additional features on top of `OWLAxiom` from
53 * the OWLAPI.
54 */
55 implicit class RSAAxiom(axiom: OWLAxiom) {
56
57 /* Detecting axiom types:
58 *
59 * In order to reason about role unsafety in Horn-ALCHOIQ
60 * ontologies we need to detect and filter axioms by their
61 * "type".
62 *
63 * This is a simple implementation following the Visitor
64 * pattern imposed by the OWLAPI.
65 */
66 private class RSAAxiomTypeDetector(t: RSAAxiomType)
67 extends OWLAxiomVisitorEx[Boolean] {
68 override def visit(axiom: OWLSubClassOfAxiom): Boolean = {
69 val sub = axiom.getSubClass().getClassExpressionType()
70 val sup = axiom.getSuperClass().getClassExpressionType()
71 t match {
72 case RSAAxiomType.T3top => // ∃R.⊤ ⊑ B
73 axiom.isT3 && axiom
74 .getSubClass()
75 .asInstanceOf[OWLObjectSomeValuesFrom]
76 .getFiller
77 .isOWLThing
78 case RSAAxiomType.T3 => // ∃R.A ⊑ B
79 sub == ClassExpressionType.OBJECT_SOME_VALUES_FROM && sup == ClassExpressionType.OWL_CLASS
80 case RSAAxiomType.T4 => // A ⊑ ≤1R.B
81 sub == ClassExpressionType.OWL_CLASS && sup == ClassExpressionType.OBJECT_MAX_CARDINALITY
82 case RSAAxiomType.T5 => // A ⊑ ∃R.B
83 sub == ClassExpressionType.OWL_CLASS && sup == ClassExpressionType.OBJECT_SOME_VALUES_FROM
84 }
85 }
86
87 override def visit(axiom: OWLEquivalentClassesAxiom): Boolean = {
88 // TODO
89 false
90 }
91
92 def doDefault(axiom: OWLAxiom): Boolean = false
93 }
94
95 private def isOfType(t: RSAAxiomType): Boolean = {
96 val visitor = new RSAAxiomTypeDetector(t)
97 axiom.accept(visitor)
98 }
99
100 /* Exposed methods */
101 def isT3top: Boolean = isOfType(RSAAxiomType.T3top)
102 def isT3: Boolean = isOfType(RSAAxiomType.T3)
103 def isT4: Boolean = isOfType(RSAAxiomType.T4)
104 def isT5: Boolean = isOfType(RSAAxiomType.T5)
105
106 /* Extracting ObjectPropertyExpressions from axioms
107 *
108 * This extracts all ObjectPropertyExpressions from a given
109 * axiom. While the implementation is generic we use it on axioms
110 * of specific types (see above).
111 *
112 * NOTE: it is not possible to use the `objectPropertyInSignature`
113 * method of `OWLAxiom` because it returns all "role names" involved
114 * in the signature of an axiom. In particular we won't get the inverse
115 * of a role if this appears in the axiom (but we will get the role
116 * itself instead).
117 */
118 lazy val objectPropertyExpressionsInSignature
119 : List[OWLObjectPropertyExpression] =
120 axiom match {
121 case a: OWLSubClassOfAxiom =>
122 rolesInExpr(a.getSubClass) ++ rolesInExpr(a.getSuperClass)
123 case a: OWLEquivalentClassesAxiom =>
124 a.getClassExpressions.asScala.toList.flatMap(rolesInExpr(_))
125 case a: OWLSubObjectPropertyOfAxiom =>
126 List(a.getSubProperty, a.getSuperProperty)
127 case _ => List()
128 }
129
130 private def rolesInExpr(
131 expr: OWLClassExpression
132 ): List[OWLObjectPropertyExpression] =
133 expr match {
134 case e: OWLObjectSomeValuesFrom => List(e.getProperty)
135 case e: OWLObjectMaxCardinality => List(e.getProperty)
136 case _ => List()
137 }
138
139 lazy val toTriple: Option[(OWLClass, OWLObjectProperty, OWLClass)] =
140 for {
141 subClass <- Some(axiom) collect { case a: OWLSubClassOfAxiom => a }
142 cls1 <- Some(subClass.getSubClass) collect { case a: OWLClass => a }
143 someValues <- Some(subClass.getSuperClass) collect {
144 case a: OWLObjectSomeValuesFrom => a
145 }
146 prop <- Some(someValues.getProperty) collect {
147 case a: OWLObjectProperty => a
148 }
149 cls2 <- Some(someValues.getFiller) collect { case a: OWLClass => a }
150 } yield (cls1, prop, cls2)
151
152 lazy val hashed: String = (RSAAxiom.hashed _) tupled toTriple.get
153 }
154
155} // trait RSAAxiom
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/suffix/RSASuffix.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/suffix/RSASuffix.scala
new file mode 100644
index 0000000..b22910b
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/suffix/RSASuffix.scala
@@ -0,0 +1,31 @@
1package uk.ac.ox.cs.rsacomb.suffix
2
3import org.semanticweb.owlapi.model.{
4 OWLPropertyExpression,
5 OWLObjectInverseOf,
6 OWLObjectProperty
7}
8
9import tech.oxfordsemantic.jrdfox.logic.expression.{IRI}
10import tech.oxfordsemantic.jrdfox.logic.datalog.{TupleTableAtom, TupleTableName}
11
12object RSASuffix {
13
14 def apply(suffix: String => String): RSASuffix = new RSASuffix(suffix)
15
16}
17
18class RSASuffix(val suffix: String => String) {
19
20 def +(that: RSASuffix): RSASuffix =
21 new RSASuffix(this.suffix andThen that.suffix)
22
23 def ::(str: String): String = this suffix str
24
25}
26
27case object Empty extends RSASuffix(identity)
28case object Forward extends RSASuffix((s) => s"${s}_f")
29case object Backward extends RSASuffix((s) => s"${s}_b")
30case object Inverse extends RSASuffix((s) => s"${s}_inv")
31case class Nth(n: Int) extends RSASuffix((s) => s"${s}_$n")
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxHelpers.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxHelpers.scala
new file mode 100644
index 0000000..fd9e1c5
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxHelpers.scala
@@ -0,0 +1,100 @@
1package uk.ac.ox.cs.rsacomb.util
2
3import java.util.{Map => JMap, HashMap => JHashMap}
4import java.io.StringReader
5import tech.oxfordsemantic.jrdfox.Prefixes
6import tech.oxfordsemantic.jrdfox.client.{
7 ConnectionFactory,
8 ServerConnection,
9 DataStoreConnection
10}
11import tech.oxfordsemantic.jrdfox.formats.SPARQLParser
12import tech.oxfordsemantic.jrdfox.logic.expression.Resource
13import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery
14
15import uk.ac.ox.cs.rsacomb.suffix.Nth
16
17object RDFoxHelpers {
18
19 def openConnection(
20 dataStore: String,
21 opts: JMap[String, String] = new JHashMap[String, String]()
22 ): (ServerConnection, DataStoreConnection) = {
23 /* Create local server connection
24 */
25 val serverUrl = "rdfox:local"
26 val role = ""
27 val password = ""
28 val server =
29 ConnectionFactory.newServerConnection(serverUrl, role, password)
30
31 /* Create datastore connection
32 */
33 // parameters.put("owl-in-rdf-support", "relaxed")
34 // parameters.put("equality", "noUNA")
35 server.createDataStore(dataStore, "par-complex-nn", opts)
36 val data = server.newDataStoreConnection(dataStore)
37
38 (server, data)
39 }
40
41 def parseSelectQuery(
42 query: String,
43 prefixes: Prefixes = new Prefixes()
44 ): Option[SelectQuery] = {
45 val parser = new SPARQLParser(
46 prefixes,
47 new StringReader(query)
48 )
49 parser.parseSingleQuery() match {
50 case q: SelectQuery => Some(q)
51 case _ => None
52 }
53 }
54
55 def submitSelectQuery(
56 data: DataStoreConnection,
57 query: String,
58 prefixes: Prefixes = new Prefixes(),
59 opts: JMap[String, String] = new JHashMap[String, String]()
60 ): List[List[Resource]] = {
61 val cursor = data.createCursor(prefixes, query, opts)
62 var answers: List[List[Resource]] = List()
63 var mul = cursor.open()
64 while (mul > 0) {
65 val answer =
66 (0 until cursor.getArity).map(cursor.getResource(_)).toList
67 answers = answer :: answers
68 mul = cursor.advance()
69 }
70 cursor.close();
71 answers
72 }
73
74 def queryInternalPredicate(
75 data: DataStoreConnection,
76 pred: String,
77 arity: Int,
78 opts: JMap[String, String] = new JHashMap[String, String]()
79 ): List[List[Resource]] = {
80 var query = "SELECT"
81 for (i <- 0 until arity) {
82 query ++= s" ?X$i"
83 }
84 query ++= " WHERE {"
85 for (i <- 0 until arity) {
86 query ++= s" ?S rsa:${pred :: Nth(i)} ?X$i ."
87 }
88 query ++= " }"
89 submitSelectQuery(data, query, RSA.Prefixes, opts)
90 }
91
92 def closeConnection(
93 server: ServerConnection,
94 data: DataStoreConnection
95 ): Unit = {
96 server.close();
97 data.close();
98 }
99
100}
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala
new file mode 100644
index 0000000..f9ff59b
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala
@@ -0,0 +1,100 @@
1package uk.ac.ox.cs.rsacomb.util
2
3/* Java imports */
4import java.util.Map
5
6import tech.oxfordsemantic.jrdfox.formats.SPARQLParser
7import tech.oxfordsemantic.jrdfox.Prefixes
8import tech.oxfordsemantic.jrdfox.logic.datalog.{
9 TupleTableAtom,
10 TupleTableName,
11 Negation
12}
13import tech.oxfordsemantic.jrdfox.logic.expression.{Term, Variable, IRI}
14import org.semanticweb.owlapi.model.OWLOntology
15import org.semanticweb.owlapi.model.{
16 OWLAxiom,
17 OWLClass,
18 OWLObjectPropertyExpression
19}
20
21import uk.ac.ox.cs.rsacomb.suffix.RSASuffix
22
23// Debug only
24import scala.collection.JavaConverters._
25
26object RSA {
27
28 val Prefixes: Prefixes = new Prefixes()
29 Prefixes.declarePrefix("rsa:", "http://www.cs.ox.ac.uk/isg/rsa/")
30
31 private def atom(name: IRI, vars: List[Term]) =
32 TupleTableAtom.create(TupleTableName.create(name.getIRI), vars: _*)
33
34 def PE(t1: Term, t2: Term) =
35 TupleTableAtom.rdf(t1, RSA("PE"), t2)
36
37 def U(t: Term) =
38 TupleTableAtom.rdf(t, IRI.RDF_TYPE, RSA("U"))
39
40 def In(t: Term)(implicit set: Term) =
41 TupleTableAtom.rdf(t, RSA("In"), set)
42
43 def notIn(t: Term)(implicit set: Term) = Negation.create(In(t)(set))
44
45 def congruent(t1: Term, t2: Term) =
46 TupleTableAtom.rdf(t1, RSA("congruent"), t2)
47
48 def QM(implicit variables: (List[Term], List[Term])) = {
49 val (answer, bounded) = variables
50 atom(RSA("QM"), answer ::: bounded)
51 }
52
53 def ID(t1: Term, t2: Term)(implicit variables: (List[Term], List[Term])) = {
54 val (answer, bounded) = variables
55 atom(RSA("ID"), (answer ::: bounded) :+ t1 :+ t2)
56 }
57
58 def Named(t: Term) =
59 TupleTableAtom.rdf(t, IRI.RDF_TYPE, RSA("Named"))
60
61 def Thing(t: Term) =
62 TupleTableAtom.rdf(t, IRI.RDF_TYPE, IRI.THING)
63
64 def NI(t: Term) =
65 TupleTableAtom.rdf(t, IRI.RDF_TYPE, RSA("NI"))
66
67 def TQ(t1: Term, t2: Term, sx: RSASuffix)(implicit
68 variables: (List[Term], List[Term])
69 ) = {
70 val (answer, bounded) = variables
71 atom(RSA("TQ" :: sx), (answer ::: bounded) :+ t1 :+ t2)
72 }
73
74 def AQ(t1: Term, t2: Term, sx: RSASuffix)(implicit
75 variables: (List[Term], List[Term])
76 ) = {
77 val (answer, bounded) = variables
78 atom(RSA("AQ" :: sx), (answer ::: bounded) :+ t1 :+ t2)
79 }
80
81 def FK(implicit variables: (List[Term], List[Term])) = {
82 val (answer, bounded) = variables
83 atom(RSA("FK"), answer ::: bounded)
84 }
85
86 def SP(implicit variables: (List[Term], List[Term])) = {
87 val (answer, bounded) = variables
88 atom(RSA("SP"), answer ::: bounded)
89 }
90
91 def Ans(implicit variables: (List[Term], List[Term])) = {
92 val (answer, _) = variables
93 atom(RSA("Ans"), answer)
94 }
95
96 def apply(name: Any): IRI =
97 IRI.create(
98 Prefixes.getPrefixIRIsByPrefixName.get("rsa:").getIRI + name.toString
99 )
100}