aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/uk/ac/ox/cs/rsacomb/ontology
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/scala/uk/ac/ox/cs/rsacomb/ontology')
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/ontology/Ontology.scala301
1 files changed, 301 insertions, 0 deletions
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/ontology/Ontology.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/ontology/Ontology.scala
new file mode 100644
index 0000000..ba44605
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/ontology/Ontology.scala
@@ -0,0 +1,301 @@
1/*
2 * Copyright 2020, 2021 KRR Oxford
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package uk.ac.ox.cs.rsacomb.ontology
18
19import java.io.File
20import java.util.stream.Collectors
21
22import scala.collection.mutable.Map
23import scala.collection.JavaConverters._
24import scalax.collection.Graph
25import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._
26
27import org.semanticweb.owlapi.model.parameters.Imports
28import org.semanticweb.owlapi.apibinding.OWLManager
29import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom, OWLLogicalAxiom}
30import org.semanticweb.owlapi.model.{OWLObjectPropertyExpression}
31import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory
32import tech.oxfordsemantic.jrdfox.logic.datalog.Rule
33import tech.oxfordsemantic.jrdfox.logic.expression.{Resource, Term, Variable}
34
35import uk.ac.ox.cs.rsacomb.approximation.Approximation
36import uk.ac.ox.cs.rsacomb.converter._
37import uk.ac.ox.cs.rsacomb.suffix._
38import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA}
39
40import uk.ac.ox.cs.rsacomb.RSAUtil
41
42object Ontology {
43
44 /** Simplify conversion between Java and Scala collections */
45 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._
46
47 /** Type wrapper representing a dependency graph for the ontology.
48 *
49 * The graph is returned along with a map associating each node (IRI
50 * string of the resource), with the corresponding axiom in the
51 * original TBox.
52 */
53 type DependencyGraph = (Graph[Resource, DiEdge], Map[String, OWLAxiom])
54
55 /** Manager instance to interface with OWLAPI
56 *
57 * TODO: turn this into an implicit class parameter.
58 */
59 val manager = OWLManager.createOWLOntologyManager()
60
61 /** Compute the RSA dependency graph for a set of axioms
62 *
63 * @param axioms set of input axioms (TBox) to build the dependency
64 * graph.
65 * @param datafiles data (ABox) to build the dependency graph.
66 * @param unsafe list of unsafe roles in the TBox.
67 *
68 * @return a tuple containing the dependency graph and a map between
69 * the newly introduced constants and the corresponding input axioms.
70 *
71 * @note no check on the ontology language is performed since the
72 * construction of the dependency graph is computed regardless. The
73 * input axioms are assumed to be normalized.
74 */
75 def dependencyGraph(
76 axioms: List[OWLLogicalAxiom],
77 datafiles: List[File],
78 unsafe: List[OWLObjectPropertyExpression]
79 ): DependencyGraph = {
80
81 var nodemap = Map.empty[String, OWLAxiom]
82
83 /* Create custom converter */
84 object RSAConverter extends RDFoxConverter {
85
86 import org.semanticweb.owlapi.model.{
87 OWLClassExpression,
88 OWLObjectSomeValuesFrom,
89 OWLDataSomeValuesFrom
90 }
91
92 override def convert(
93 expr: OWLClassExpression,
94 term: Term,
95 unsafe: List[OWLObjectPropertyExpression],
96 skolem: SkolemStrategy,
97 suffix: RSASuffix
98 ): Shards =
99 (expr, skolem) match {
100
101 case (e: OWLObjectSomeValuesFrom, c: Constant) => {
102 nodemap.update(c.iri.getIRI, c.axiom)
103 val (res, ext) = super.convert(e, term, unsafe, skolem, suffix)
104 if (unsafe contains e.getProperty)
105 (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext)
106 else
107 (RSA.PE(term, c.iri) :: res, ext)
108 }
109
110 case (e: OWLDataSomeValuesFrom, c: Constant) => {
111 nodemap.update(c.iri.getIRI, c.axiom)
112 val (res, ext) = super.convert(e, term, unsafe, skolem, suffix)
113 if (unsafe contains e.getProperty)
114 (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext)
115 else
116 (RSA.PE(term, c.iri) :: res, ext)
117 }
118
119 case _ => super.convert(expr, term, unsafe, skolem, suffix)
120 }
121 }
122
123 /* Ontology convertion into LP rules */
124 val term = RSAUtil.genFreshVariable()
125 val result = axioms.map(a =>
126 RSAConverter.convert(a, term, unsafe, new Constant(a), Empty)
127 )
128
129 val datalog = result.unzip
130 val facts = datalog._1.flatten
131 var rules = datalog._2.flatten
132
133 /* Open connection with RDFox */
134 val (server, data) = RDFoxUtil.openConnection("rsa_dependency_graph")
135
136 /* Add additional built-in rules */
137 val varX = Variable.create("X")
138 val varY = Variable.create("Y")
139 rules = Rule.create(
140 RSA.E(varX, varY),
141 RSA.PE(varX, varY),
142 RSA.U(varX),
143 RSA.U(varY)
144 ) :: rules
145 /* Load facts and rules from ontology */
146 RDFoxUtil.addFacts(data, facts)
147 RDFoxUtil.addRules(data, rules)
148 /* Load data files */
149 RDFoxUtil.addData(data, datafiles: _*)
150
151 /* Build the graph */
152 val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }"
153 val answers = RDFoxUtil.submitQuery(data, query, RSA.Prefixes).get
154 var edges: Seq[DiEdge[Resource]] =
155 answers.collect { case (_, Seq(n1, n2)) => n1 ~> n2 }
156 val graph = Graph(edges: _*)
157
158 /* Close connection to RDFox */
159 RDFoxUtil.closeConnection(server, data)
160
161 (graph, nodemap)
162 }
163
164 def apply(axioms: List[OWLLogicalAxiom], datafiles: List[File]): Ontology =
165 new Ontology(axioms, datafiles)
166
167 def apply(ontology: OWLOntology, datafiles: List[File]): Ontology = {
168
169 /** TBox axioms */
170 var tbox: List[OWLLogicalAxiom] =
171 ontology
172 .tboxAxioms(Imports.INCLUDED)
173 .collect(Collectors.toList())
174 .collect { case a: OWLLogicalAxiom => a }
175
176 /** RBox axioms */
177 var rbox: List[OWLLogicalAxiom] =
178 ontology
179 .rboxAxioms(Imports.INCLUDED)
180 .collect(Collectors.toList())
181 .collect { case a: OWLLogicalAxiom => a }
182
183 /** ABox axioms
184 *
185 * @note this represents only the set of assertions contained in the
186 * ontology file. Data files specified in `datafiles` are directly
187 * imported in RDFox due to performance issues when trying to import
188 * large data files via OWLAPI.
189 */
190 var abox: List[OWLLogicalAxiom] =
191 ontology
192 .aboxAxioms(Imports.INCLUDED)
193 .collect(Collectors.toList())
194 .collect { case a: OWLLogicalAxiom => a }
195
196 Ontology(abox ::: tbox ::: rbox, datafiles)
197 }
198
199 def apply(ontofile: File, datafiles: List[File]): Ontology = {
200 val ontology = manager.loadOntologyFromOntologyDocument(ontofile)
201 Ontology(ontology, datafiles)
202 }
203
204}
205
206/** A wrapper for a generic OWL2 ontology
207 *
208 * @param axioms list of axioms (roughly) corresponding to the TBox.
209 * @param datafiles files containing ABox data.
210 */
211class Ontology(val axioms: List[OWLLogicalAxiom], val datafiles: List[File]) {
212
213 /** Extend OWLAxiom functionalities */
214 import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._
215
216 /** Simplify conversion between Java and Scala collections */
217 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._
218
219 println(s"Axioms: ${axioms.length}")
220
221 /** OWLOntology based on input axioms
222 *
223 * This is mainly used to instantiate a new reasoner to be used in
224 * the computation of unsafe roles.
225 */
226 protected val ontology: OWLOntology =
227 Ontology.manager.createOntology((axioms: List[OWLAxiom]).asJava)
228
229 /** OWLAPI internal reasoner for ontology */
230 protected val reasoner =
231 (new StructuralReasonerFactory()).createReasoner(ontology)
232
233 /** Unsafe roles in the ontology
234 *
235 * Unsafety conditions are the following:
236 *
237 * 1) For all roles r1 appearing in an axiom of type T5, r1 is unsafe
238 * if there exists a role r2 (different from top) appearing in an
239 * axiom of type T3 and r1 is a subproperty of the inverse of r2.
240 *
241 * 2) For all roles p1 appearing in an axiom of type T5, p1 is unsafe
242 * if there exists a role p2 appearing in an axiom of type T4 and
243 * p1 is a subproperty of either p2 or the inverse of p2.
244 */
245 lazy val unsafe: List[OWLObjectPropertyExpression] = {
246
247 /* Checking for unsafety condition (1) */
248 val unsafe1 = for {
249 axiom <- axioms
250 if axiom.isT5
251 role1 <- axiom.objectPropertyExpressionsInSignature
252 roleSuper = role1 +: reasoner.superObjectProperties(role1)
253 axiom <- axioms
254 if axiom.isT3 && !axiom.isT3top
255 role2 <- axiom.objectPropertyExpressionsInSignature
256 if roleSuper contains role2.getInverseProperty
257 } yield role1
258
259 /* Checking for unsafety condition (2) */
260 val unsafe2 = for {
261 axiom <- axioms
262 if axiom.isT5
263 role1 <- axiom.objectPropertyExpressionsInSignature
264 roleSuper = role1 +: reasoner.superObjectProperties(role1)
265 axiom <- axioms
266 if axiom.isT4
267 role2 <- axiom.objectPropertyExpressionsInSignature
268 if roleSuper.contains(role2) ||
269 roleSuper.contains(role2.getInverseProperty)
270 } yield role1
271
272 unsafe1 ++ unsafe2
273 }
274
275 /** Compute the dependency graph for the ontology */
276 lazy val dependencyGraph: Ontology.DependencyGraph =
277 Ontology.dependencyGraph(axioms, datafiles, this.unsafe)
278
279 /** RSA check */
280 lazy val isRSA: Boolean = ???
281
282 /** Normalize the ontology according to the given normalizer
283 *
284 * @param normalizer the normalization technique to be used.
285 * @return a new normalized [[Ontology]].
286 */
287 def normalize(normalizer: Normalizer): Ontology =
288 new Ontology(
289 axioms flatMap normalizer.normalize,
290 datafiles
291 )
292
293 /** Approximate the ontology according to the given approximation
294 * technique.
295 *
296 * @param approximation the approximation to be used on the ontology.
297 * @return the result of the approximation.
298 */
299 def approximate[T](approximation: Approximation[T]): T =
300 approximation.approximate(this)
301}