diff options
Diffstat (limited to 'src/main/scala/uk/ac/ox')
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 @@ | |||
1 | package uk.ac.ox.cs.rsacomb | ||
2 | |||
3 | import org.semanticweb.owlapi.model.{OWLObjectInverseOf, OWLObjectProperty} | ||
4 | import org.semanticweb.owlapi.model.{ | ||
5 | OWLClass, | ||
6 | // OWLObjectProperty, | ||
7 | OWLSubObjectPropertyOfAxiom, | ||
8 | // OWLObjectPropertyExpression, | ||
9 | OWLObjectSomeValuesFrom, | ||
10 | OWLSubClassOfAxiom | ||
11 | } | ||
12 | |||
13 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ | ||
14 | Rule, | ||
15 | BodyFormula, | ||
16 | TupleTableAtom, | ||
17 | Negation | ||
18 | } | ||
19 | import tech.oxfordsemantic.jrdfox.logic.expression.{ | ||
20 | Term, | ||
21 | Variable, | ||
22 | // Resource, | ||
23 | IRI | ||
24 | } | ||
25 | |||
26 | import uk.ac.ox.cs.rsacomb.converter.{ | ||
27 | SkolemStrategy, | ||
28 | RDFoxAxiomConverter, | ||
29 | RDFoxPropertyExprConverter | ||
30 | } | ||
31 | import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom | ||
32 | import uk.ac.ox.cs.rsacomb.suffix.{Empty, Forward, Backward, Inverse} | ||
33 | import uk.ac.ox.cs.rsacomb.util.RSA | ||
34 | |||
35 | class 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 @@ | |||
1 | package uk.ac.ox.cs.rsacomb | ||
2 | |||
3 | import tech.oxfordsemantic.jrdfox.logic.Datatype | ||
4 | import tech.oxfordsemantic.jrdfox.logic.expression.{ | ||
5 | Term, | ||
6 | IRI, | ||
7 | Variable, | ||
8 | Literal, | ||
9 | FunctionCall | ||
10 | } | ||
11 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ | ||
12 | Rule, | ||
13 | TupleTableAtom, | ||
14 | BindAtom, | ||
15 | TupleTableName, | ||
16 | Atom, | ||
17 | BodyFormula, | ||
18 | Negation | ||
19 | } | ||
20 | import tech.oxfordsemantic.jrdfox.logic.sparql.statement.{SelectQuery} | ||
21 | import tech.oxfordsemantic.jrdfox.logic.sparql.pattern.{ | ||
22 | GroupGraphPattern, | ||
23 | ConjunctionPattern, | ||
24 | TriplePattern, | ||
25 | QueryPattern | ||
26 | } | ||
27 | |||
28 | import scala.collection.JavaConverters._ | ||
29 | |||
30 | import uk.ac.ox.cs.rsacomb.implicits.RSAAtom | ||
31 | import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Forward, Backward} | ||
32 | import uk.ac.ox.cs.rsacomb.util.RSA | ||
33 | |||
34 | class 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 | |||
363 | object 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 @@ | |||
1 | package uk.ac.ox.cs.rsacomb | ||
2 | |||
3 | /* Java imports */ | ||
4 | import java.io.File | ||
5 | import java.util.HashMap | ||
6 | import scala.collection.JavaConverters._ | ||
7 | |||
8 | import tech.oxfordsemantic.jrdfox.client.UpdateType | ||
9 | import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery | ||
10 | import tech.oxfordsemantic.jrdfox.logic.expression.{IRI, Term} | ||
11 | |||
12 | /* Local imports */ | ||
13 | import util.{RDFoxHelpers, RSA} | ||
14 | |||
15 | object 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 @@ | |||
1 | package uk.ac.ox.cs.rsacomb | ||
2 | |||
3 | /* Java imports */ | ||
4 | import java.util.HashMap | ||
5 | import java.util.stream.{Collectors, Stream} | ||
6 | import java.io.File | ||
7 | import org.semanticweb.owlapi.apibinding.OWLManager | ||
8 | |||
9 | import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom} | ||
10 | import org.semanticweb.owlapi.model.{ | ||
11 | OWLClass, | ||
12 | OWLObjectProperty, | ||
13 | OWLSubObjectPropertyOfAxiom, | ||
14 | OWLObjectPropertyExpression, | ||
15 | OWLObjectSomeValuesFrom, | ||
16 | OWLSubClassOfAxiom | ||
17 | } | ||
18 | import org.semanticweb.owlapi.model.parameters.Imports | ||
19 | import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory | ||
20 | import org.semanticweb.owlapi.model.{IRI => OWLIRI} | ||
21 | import uk.ac.manchester.cs.owl.owlapi.OWLObjectPropertyImpl | ||
22 | |||
23 | import tech.oxfordsemantic.jrdfox.client.{UpdateType, DataStoreConnection} | ||
24 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ | ||
25 | Rule, | ||
26 | TupleTableAtom, | ||
27 | Negation, | ||
28 | BodyFormula | ||
29 | } | ||
30 | import tech.oxfordsemantic.jrdfox.logic.expression.{ | ||
31 | Term, | ||
32 | Variable, | ||
33 | IRI, | ||
34 | Resource | ||
35 | } | ||
36 | import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery | ||
37 | |||
38 | /* Scala imports */ | ||
39 | import scala.collection.JavaConverters._ | ||
40 | import scala.collection.mutable.Set | ||
41 | import scalax.collection.immutable.Graph | ||
42 | import scalax.collection.GraphEdge.UnDiEdge | ||
43 | |||
44 | /* Debug only */ | ||
45 | import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer | ||
46 | import tech.oxfordsemantic.jrdfox.logic._ | ||
47 | import org.semanticweb.owlapi.model.OWLObjectInverseOf | ||
48 | |||
49 | import uk.ac.ox.cs.rsacomb.converter.{RDFoxAxiomConverter, SkolemStrategy} | ||
50 | import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom | ||
51 | import uk.ac.ox.cs.rsacomb.suffix.{Empty, Forward, Backward, Inverse} | ||
52 | import uk.ac.ox.cs.rsacomb.util.{RDFoxHelpers, RSA} | ||
53 | |||
54 | object 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 | |||
75 | class 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 @@ | |||
1 | package uk.ac.ox.cs.rsacomb.converter | ||
2 | |||
3 | import org.semanticweb.owlapi.model.{ | ||
4 | OWLAxiom, | ||
5 | OWLSubClassOfAxiom, | ||
6 | OWLEquivalentClassesAxiom, | ||
7 | OWLObjectPropertyExpression | ||
8 | } | ||
9 | import org.semanticweb.owlapi.model.OWLAxiomVisitorEx | ||
10 | |||
11 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ | ||
12 | Rule, | ||
13 | BodyFormula, | ||
14 | TupleTableAtom, | ||
15 | TupleTableName | ||
16 | } | ||
17 | import tech.oxfordsemantic.jrdfox.logic.expression.{ | ||
18 | Term, | ||
19 | IRI, | ||
20 | Variable, | ||
21 | Literal | ||
22 | } | ||
23 | |||
24 | import scala.collection.JavaConverters._ | ||
25 | |||
26 | import org.semanticweb.owlapi.model.OWLSubObjectPropertyOfAxiom | ||
27 | import org.semanticweb.owlapi.model.OWLObjectProperty | ||
28 | import org.semanticweb.owlapi.model.OWLClassAssertionAxiom | ||
29 | |||
30 | import uk.ac.ox.cs.rsacomb.RSAOntology | ||
31 | import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Empty} | ||
32 | |||
33 | object 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 | |||
45 | class 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 @@ | |||
1 | package uk.ac.ox.cs.rsacomb.converter | ||
2 | |||
3 | import scala.collection.JavaConverters._ | ||
4 | import java.util.stream.{Stream, Collectors} | ||
5 | |||
6 | import org.semanticweb.owlapi.model.{ | ||
7 | OWLClassExpression, | ||
8 | OWLClass, | ||
9 | OWLObjectSomeValuesFrom, | ||
10 | OWLObjectIntersectionOf, | ||
11 | OWLObjectOneOf, | ||
12 | OWLObjectMaxCardinality | ||
13 | } | ||
14 | import org.semanticweb.owlapi.model.OWLClassExpressionVisitorEx | ||
15 | import tech.oxfordsemantic.jrdfox.logic.Datatype | ||
16 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ | ||
17 | BindAtom, | ||
18 | TupleTableName, | ||
19 | TupleTableAtom | ||
20 | } | ||
21 | import tech.oxfordsemantic.jrdfox.logic.expression.{ | ||
22 | Term, | ||
23 | Literal, | ||
24 | Variable, | ||
25 | FunctionCall, | ||
26 | IRI | ||
27 | } | ||
28 | |||
29 | import org.semanticweb.owlapi.model.OWLObjectPropertyExpression | ||
30 | import org.semanticweb.owlapi.model.OWLObjectProperty | ||
31 | |||
32 | import uk.ac.ox.cs.rsacomb.RSAOntology | ||
33 | import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Empty} | ||
34 | import uk.ac.ox.cs.rsacomb.util.RSA | ||
35 | |||
36 | object 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 | |||
57 | class 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 @@ | |||
1 | package uk.ac.ox.cs.rsacomb.converter | ||
2 | |||
3 | import org.semanticweb.owlapi.model.{OWLPropertyExpression, OWLObjectProperty} | ||
4 | import org.semanticweb.owlapi.model.OWLPropertyExpressionVisitorEx | ||
5 | |||
6 | import tech.oxfordsemantic.jrdfox.logic.datalog.TupleTableAtom | ||
7 | import tech.oxfordsemantic.jrdfox.logic.expression.{Term, IRI, Literal} | ||
8 | |||
9 | import org.semanticweb.owlapi.model.OWLObjectInverseOf | ||
10 | |||
11 | import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Inverse} | ||
12 | |||
13 | class 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 @@ | |||
1 | package uk.ac.ox.cs.rsacomb.converter | ||
2 | |||
3 | import tech.oxfordsemantic.jrdfox.logic.datalog.{TupleTableAtom, BodyFormula} | ||
4 | |||
5 | case 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 @@ | |||
1 | package uk.ac.ox.cs.rsacomb.converter | ||
2 | |||
3 | import tech.oxfordsemantic.jrdfox.logic.Datatype | ||
4 | import tech.oxfordsemantic.jrdfox.logic.expression.{Literal, IRI} | ||
5 | |||
6 | sealed trait SkolemStrategy | ||
7 | |||
8 | object 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 @@ | |||
1 | package uk.ac.ox.cs.rsacomb.implicits | ||
2 | |||
3 | import scala.collection.JavaConverters._ | ||
4 | |||
5 | object 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 @@ | |||
1 | package uk.ac.ox.cs.rsacomb.implicits | ||
2 | |||
3 | import tech.oxfordsemantic.jrdfox.logic.expression.{IRI => RDFoxIRI} | ||
4 | import org.semanticweb.owlapi.model.{IRI => OWLIRI} | ||
5 | |||
6 | object 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 @@ | |||
1 | package uk.ac.ox.cs.rsacomb.implicits | ||
2 | |||
3 | import tech.oxfordsemantic.jrdfox.logic.Datatype | ||
4 | import tech.oxfordsemantic.jrdfox.logic.expression.{Literal, FunctionCall} | ||
5 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ | ||
6 | BindAtom, | ||
7 | TupleTableAtom, | ||
8 | TupleTableName | ||
9 | } | ||
10 | import tech.oxfordsemantic.jrdfox.logic.expression.{IRI} | ||
11 | import scala.collection.JavaConverters._ | ||
12 | |||
13 | import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Nth} | ||
14 | import 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 | |||
31 | trait 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 @@ | |||
1 | package uk.ac.ox.cs.rsacomb.implicits | ||
2 | |||
3 | /* Java imports */ | ||
4 | import org.semanticweb.owlapi.model.{ | ||
5 | OWLAxiom, | ||
6 | OWLSubClassOfAxiom, | ||
7 | OWLEquivalentClassesAxiom | ||
8 | } | ||
9 | import org.semanticweb.owlapi.model.{ | ||
10 | OWLObjectPropertyExpression, | ||
11 | OWLSubObjectPropertyOfAxiom, | ||
12 | OWLClass, | ||
13 | OWLClassExpression, | ||
14 | OWLObjectSomeValuesFrom, | ||
15 | OWLObjectMaxCardinality | ||
16 | } | ||
17 | import org.semanticweb.owlapi.model.ClassExpressionType | ||
18 | import org.semanticweb.owlapi.model.{ | ||
19 | OWLAxiomVisitorEx, | ||
20 | OWLClassExpressionVisitorEx | ||
21 | } | ||
22 | import org.semanticweb.owlapi.model.OWLObjectProperty | ||
23 | import scala.collection.JavaConverters._ | ||
24 | |||
25 | /* Wrapper trait for the implicit class `RSAAxiom`. | ||
26 | */ | ||
27 | trait 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 @@ | |||
1 | package uk.ac.ox.cs.rsacomb.suffix | ||
2 | |||
3 | import org.semanticweb.owlapi.model.{ | ||
4 | OWLPropertyExpression, | ||
5 | OWLObjectInverseOf, | ||
6 | OWLObjectProperty | ||
7 | } | ||
8 | |||
9 | import tech.oxfordsemantic.jrdfox.logic.expression.{IRI} | ||
10 | import tech.oxfordsemantic.jrdfox.logic.datalog.{TupleTableAtom, TupleTableName} | ||
11 | |||
12 | object RSASuffix { | ||
13 | |||
14 | def apply(suffix: String => String): RSASuffix = new RSASuffix(suffix) | ||
15 | |||
16 | } | ||
17 | |||
18 | class 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 | |||
27 | case object Empty extends RSASuffix(identity) | ||
28 | case object Forward extends RSASuffix((s) => s"${s}_f") | ||
29 | case object Backward extends RSASuffix((s) => s"${s}_b") | ||
30 | case object Inverse extends RSASuffix((s) => s"${s}_inv") | ||
31 | case 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 @@ | |||
1 | package uk.ac.ox.cs.rsacomb.util | ||
2 | |||
3 | import java.util.{Map => JMap, HashMap => JHashMap} | ||
4 | import java.io.StringReader | ||
5 | import tech.oxfordsemantic.jrdfox.Prefixes | ||
6 | import tech.oxfordsemantic.jrdfox.client.{ | ||
7 | ConnectionFactory, | ||
8 | ServerConnection, | ||
9 | DataStoreConnection | ||
10 | } | ||
11 | import tech.oxfordsemantic.jrdfox.formats.SPARQLParser | ||
12 | import tech.oxfordsemantic.jrdfox.logic.expression.Resource | ||
13 | import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery | ||
14 | |||
15 | import uk.ac.ox.cs.rsacomb.suffix.Nth | ||
16 | |||
17 | object 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 @@ | |||
1 | package uk.ac.ox.cs.rsacomb.util | ||
2 | |||
3 | /* Java imports */ | ||
4 | import java.util.Map | ||
5 | |||
6 | import tech.oxfordsemantic.jrdfox.formats.SPARQLParser | ||
7 | import tech.oxfordsemantic.jrdfox.Prefixes | ||
8 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ | ||
9 | TupleTableAtom, | ||
10 | TupleTableName, | ||
11 | Negation | ||
12 | } | ||
13 | import tech.oxfordsemantic.jrdfox.logic.expression.{Term, Variable, IRI} | ||
14 | import org.semanticweb.owlapi.model.OWLOntology | ||
15 | import org.semanticweb.owlapi.model.{ | ||
16 | OWLAxiom, | ||
17 | OWLClass, | ||
18 | OWLObjectPropertyExpression | ||
19 | } | ||
20 | |||
21 | import uk.ac.ox.cs.rsacomb.suffix.RSASuffix | ||
22 | |||
23 | // Debug only | ||
24 | import scala.collection.JavaConverters._ | ||
25 | |||
26 | object 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 | } | ||