aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/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/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/rsacomb/RSAOntology.scala')
-rw-r--r--src/main/scala/rsacomb/RSAOntology.scala378
1 files changed, 0 insertions, 378 deletions
diff --git a/src/main/scala/rsacomb/RSAOntology.scala b/src/main/scala/rsacomb/RSAOntology.scala
deleted file mode 100644
index 53bc560..0000000
--- a/src/main/scala/rsacomb/RSAOntology.scala
+++ /dev/null
@@ -1,378 +0,0 @@
1package 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 suffix.{Empty, Forward, Backward, Inverse}
50import util.{RDFoxHelpers, RSA}
51
52object RSAOntology {
53
54 // Counter used to implement a simple fresh variable generator
55 private var counter = -1;
56
57 def apply(ontology: OWLOntology): RSAOntology = new RSAOntology(ontology)
58
59 def apply(ontology: File): RSAOntology =
60 new RSAOntology(loadOntology(ontology))
61
62 def genFreshVariable(): Variable = {
63 counter += 1
64 Variable.create(f"I$counter%03d")
65 }
66
67 private def loadOntology(onto: File): OWLOntology = {
68 val manager = OWLManager.createOWLOntologyManager()
69 manager.loadOntologyFromOntologyDocument(onto)
70 }
71}
72
73class RSAOntology(val ontology: OWLOntology) extends RSAAxiom {
74
75 // Gather TBox/RBox/ABox from original ontology
76 val tbox: List[OWLAxiom] =
77 ontology
78 .tboxAxioms(Imports.INCLUDED)
79 .collect(Collectors.toList())
80 .asScala
81 .toList
82
83 val rbox: List[OWLAxiom] =
84 ontology
85 .rboxAxioms(Imports.INCLUDED)
86 .collect(Collectors.toList())
87 .asScala
88 .toList
89
90 val abox: List[OWLAxiom] =
91 ontology
92 .aboxAxioms(Imports.INCLUDED)
93 .collect(Collectors.toList())
94 .asScala
95 .toList
96
97 val axioms: List[OWLAxiom] = abox ::: tbox ::: rbox
98
99 /* Retrieve individuals in the original ontology
100 */
101 val individuals: List[IRI] =
102 ontology
103 .getIndividualsInSignature()
104 .asScala
105 .map(_.getIRI)
106 .map(implicits.RDFox.owlapiToRdfoxIri)
107 .toList
108
109 val concepts: List[OWLClass] =
110 ontology.getClassesInSignature().asScala.toList
111
112 val roles: List[OWLObjectPropertyExpression] =
113 axioms
114 .flatMap(_.objectPropertyExpressionsInSignature)
115 .distinct
116
117 // OWLAPI reasoner for same easier tasks
118 private val reasoner =
119 (new StructuralReasonerFactory()).createReasoner(ontology)
120
121 /* Steps for RSA check
122 * 1) convert ontology axioms into LP rules
123 * 2) call RDFox on the onto and compute materialization
124 * 3) build graph from E(x,y) facts
125 * 4) check if the graph is tree-like
126 * ideally this annotates the graph with info about the reasons
127 * why the ontology might not be RSA. This could help a second
128 * step of approximation of an Horn-ALCHOIQ to RSA
129 */
130 lazy val isRSA: Boolean = {
131
132 val unsafe = this.unsafeRoles
133
134 /* DEBUG: print rules in DL syntax and unsafe roles */
135 //val renderer = new DLSyntaxObjectRenderer()
136 //println("\nDL rules:")
137 //axioms.foreach(x => println(renderer.render(x)))
138 //println("\nUnsafe roles:")
139 //println(unsafe)
140
141 /* Ontology convertion into LP rules */
142 val datalog = for {
143 axiom <- axioms
144 visitor = new RDFoxAxiomConverter(
145 RSAOntology.genFreshVariable(),
146 unsafe,
147 SkolemStrategy.ConstantRSA(axiom.toString),
148 Empty
149 )
150 rule <- axiom.accept(visitor)
151 } yield rule
152
153 /* DEBUG: print datalog rules */
154 //println("\nDatalog roles:")
155 //datalog.foreach(println)
156
157 // Open connection with RDFox
158 val (server, data) = RDFoxHelpers.openConnection("RSACheck")
159 // Add Data (hardcoded for now)
160 //data.importData(UpdateType.ADDITION, RSA.Prefixes, ":a a :A .")
161
162 /* Add built-in rules
163 */
164 data.importData(
165 UpdateType.ADDITION,
166 RSA.Prefixes,
167 "rsa:E[?X,?Y] :- rsa:PE[?X,?Y], rsa:U[?X], rsa:U[?Y] ."
168 )
169
170 /* Add built-in rules
171 */
172 // data.importData(
173 // UpdateType.ADDITION,
174 // RSA.Prefixes,
175 // "[?entity, a, ?superClass] :- [?entity, a, ?class], [?class, rdfs:subClassOf, ?superClass] ."
176 // )
177
178 /* Add ontology rules
179 */
180 data.addRules(datalog.asJava)
181
182 /* Build graph
183 */
184 val graph = this.rsaGraph(data);
185 //println(graph)
186
187 // Close connection to RDFox
188 RDFoxHelpers.closeConnection(server, data)
189
190 /* To check if the graph is tree-like we check for acyclicity in a
191 * undirected graph.
192 *
193 * TODO: Implement additional checks (taking into account equality)
194 */
195 graph.isAcyclic
196 }
197
198 lazy val unsafeRoles: List[OWLObjectPropertyExpression] = {
199
200 /* DEBUG: print rules in DL syntax */
201 //val renderer = new DLSyntaxObjectRenderer()
202
203 /* Checking for (1) unsafety condition:
204 *
205 * For all roles r1 appearing in an axiom of type T5, r1 is unsafe
206 * if there exists a role r2 (different from top) appearing in an axiom
207 * of type T3 and r1 is a subproperty of the inverse of r2.
208 */
209 val unsafe1 = for {
210 axiom <- tbox
211 if axiom.isT5
212 role1 <- axiom.objectPropertyExpressionsInSignature
213 roleSuper =
214 role1 +: reasoner
215 .superObjectProperties(role1)
216 .collect(Collectors.toList())
217 .asScala
218 roleSuperInv = roleSuper.map(_.getInverseProperty)
219 axiom <- tbox
220 if axiom.isT3 && !axiom.isT3top
221 role2 <- axiom.objectPropertyExpressionsInSignature
222 if roleSuperInv.contains(role2)
223 } yield role1
224
225 /* Checking for (2) unsafety condition:
226 *
227 * For all roles p1 appearing in an axiom of type T5, p1 is unsafe if
228 * there exists a role p2 appearing in an axiom of type T4 and p1 is a
229 * subproperty of either p2 or the inverse of p2.
230 *
231 */
232 val unsafe2 = for {
233 axiom <- tbox
234 if axiom.isT5
235 role1 <- axiom.objectPropertyExpressionsInSignature
236 roleSuper =
237 role1 +: reasoner
238 .superObjectProperties(role1)
239 .collect(Collectors.toList())
240 .asScala
241 roleSuperInv = roleSuper.map(_.getInverseProperty)
242 axiom <- tbox
243 if axiom.isT4
244 role2 <- axiom.objectPropertyExpressionsInSignature
245 if roleSuper.contains(role2) || roleSuperInv.contains(role2)
246 } yield role1
247
248 (unsafe1 ++ unsafe2).toList
249 }
250
251 private def rsaGraph(
252 data: DataStoreConnection
253 ): Graph[Resource, UnDiEdge] = {
254 val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }"
255 val answers = RDFoxHelpers.submitSelectQuery(data, query, RSA.Prefixes)
256 var edges: List[UnDiEdge[Resource]] = answers.map {
257 case n1 :: n2 :: _ => UnDiEdge(n1, n2)
258 }
259 Graph(edges: _*)
260 }
261
262 def filteringProgram(
263 query: SelectQuery,
264 nis: List[Term]
265 ): FilteringProgram =
266 new FilteringProgram(query, nis)
267
268 lazy val canonicalModel = new CanonicalModel(this)
269
270 // TODO: the following functions needs testing
271 def confl(
272 role: OWLObjectPropertyExpression
273 ): Set[OWLObjectPropertyExpression] = {
274
275 val invSuperRoles = reasoner
276 .superObjectProperties(role)
277 .collect(Collectors.toSet())
278 .asScala
279 .addOne(role)
280 .map(_.getInverseProperty)
281
282 invSuperRoles
283 .flatMap(x =>
284 reasoner
285 .subObjectProperties(x)
286 .collect(Collectors.toSet())
287 .asScala
288 .addOne(x)
289 )
290 .filterNot(_.isOWLBottomObjectProperty())
291 .filterNot(_.getInverseProperty.isOWLTopObjectProperty())
292 }
293
294 def self(axiom: OWLSubClassOfAxiom): Set[Term] = {
295 // Assuming just one role in the signature of a T5 axiom
296 val role = axiom.objectPropertyExpressionsInSignature(0)
297 if (this.confl(role).contains(role)) {
298 Set(
299 RSA("v0_" ++ axiom.hashed),
300 RSA("v1_" ++ axiom.hashed)
301 )
302 } else {
303 Set()
304 }
305 }
306
307 // def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = {
308 // // Assuming just one role in the signature of a T5 axiom
309 // val roleR = axiom.objectPropertyExpressionsInSignature(0)
310 // val conflR = this.confl(roleR)
311 // // We just need the TBox to find
312 // val tbox = ontology
313 // .tboxAxioms(Imports.INCLUDED)
314 // .collect(Collectors.toSet())
315 // .asScala
316 // for {
317 // axiom1 <- tbox
318 // // TODO: is this an optimization or an error?
319 // if axiom1.isT5
320 // // We expect only one role coming out of a T5 axiom
321 // roleS <- axiom1.objectPropertyExpressionsInSignature
322 // // Triples ordering is among triples involving safe roles.
323 // if !unsafeRoles.contains(roleS)
324 // if conflR.contains(roleS)
325 // individual =
326 // if (axiom.hashCode < axiom1.hashCode) {
327 // RSA.rsa("v0_" ++ axiom1.hashCode.toString())
328 // } else {
329 // RSA.rsa("v1_" ++ axiom1.hashCode.toString())
330 // }
331 // } yield individual
332 // }
333
334 def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = {
335 // TODO: we can actually use `toTriple` from `RSAAxiom`
336 val classes =
337 axiom.classesInSignature.collect(Collectors.toList()).asScala
338 val classA = classes(0)
339 val roleR = axiom
340 .objectPropertyExpressionsInSignature(0)
341 .asInstanceOf[OWLObjectProperty]
342 val classB = classes(1)
343 cycle_aux(classA, roleR, classB)
344 }
345
346 def cycle_aux(
347 classA: OWLClass,
348 roleR: OWLObjectProperty,
349 classB: OWLClass
350 ): Set[Term] = {
351 val conflR = this.confl(roleR)
352 val classes = ontology
353 .classesInSignature(Imports.INCLUDED)
354 .collect(Collectors.toSet())
355 .asScala
356 for {
357 classD <- classes
358 roleS <- conflR
359 classC <- classes
360 // Keeping this check for now
361 if !unsafeRoles.contains(roleS)
362 tripleARB = RSAAxiom.hashed(classA, roleR, classB)
363 tripleDSC = RSAAxiom.hashed(classD, roleS, classC)
364 individual =
365 if (tripleARB > tripleDSC) {
366 RSA("v1_" ++ tripleDSC)
367 } else {
368 // Note that this is also the case for
369 // `tripleARB == tripleDSC`
370 RSA("v0_" ++ tripleDSC)
371 }
372 } yield individual
373 }
374
375 def unfold(axiom: OWLSubClassOfAxiom): Set[Term] =
376 this.self(axiom) | this.cycle(axiom)
377
378} // implicit class RSAOntology