aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
diff options
context:
space:
mode:
authorFederico Igne <federico.igne@cs.ox.ac.uk>2020-11-18 19:13:25 +0000
committerFederico Igne <federico.igne@cs.ox.ac.uk>2020-11-18 19:13:25 +0000
commit1efc189e90240c162b54cbc50362b46786643dad (patch)
tree9beabe0a2af7ba1674aea0060787782aa72e8a83 /src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
parenta45aeff72b82bbc9a52f10929bf15b414c868525 (diff)
downloadRSAComb-1efc189e90240c162b54cbc50362b46786643dad.tar.gz
RSAComb-1efc189e90240c162b54cbc50362b46786643dad.zip
Reorganize project with Java-like folder structure
Diffstat (limited to 'src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala')
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala380
1 files changed, 380 insertions, 0 deletions
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