diff options
author | Federico Igne <git@federicoigne.com> | 2021-07-27 10:34:57 +0100 |
---|---|---|
committer | Federico Igne <git@federicoigne.com> | 2021-07-27 10:34:57 +0100 |
commit | d017662e2d65ec72e7decde3b76591c198da9819 (patch) | |
tree | 57193f145cb39223db0b0da6055556aca7d04622 /src/main/scala/uk/ac/ox/cs/rsacomb/ontology | |
parent | c597b5efbe9e351a4313ef8fc1215f9e188b1ffd (diff) | |
parent | 7d619706551117a485d93d0d6847a25afa6a359d (diff) | |
download | RSAComb-0.2.0.tar.gz RSAComb-0.2.0.zip |
Merge branch 'approximation'v0.2.0
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.scala | 301 |
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 | |||
17 | package uk.ac.ox.cs.rsacomb.ontology | ||
18 | |||
19 | import java.io.File | ||
20 | import java.util.stream.Collectors | ||
21 | |||
22 | import scala.collection.mutable.Map | ||
23 | import scala.collection.JavaConverters._ | ||
24 | import scalax.collection.Graph | ||
25 | import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._ | ||
26 | |||
27 | import org.semanticweb.owlapi.model.parameters.Imports | ||
28 | import org.semanticweb.owlapi.apibinding.OWLManager | ||
29 | import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom, OWLLogicalAxiom} | ||
30 | import org.semanticweb.owlapi.model.{OWLObjectPropertyExpression} | ||
31 | import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory | ||
32 | import tech.oxfordsemantic.jrdfox.logic.datalog.Rule | ||
33 | import tech.oxfordsemantic.jrdfox.logic.expression.{Resource, Term, Variable} | ||
34 | |||
35 | import uk.ac.ox.cs.rsacomb.approximation.Approximation | ||
36 | import uk.ac.ox.cs.rsacomb.converter._ | ||
37 | import uk.ac.ox.cs.rsacomb.suffix._ | ||
38 | import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} | ||
39 | |||
40 | import uk.ac.ox.cs.rsacomb.RSAUtil | ||
41 | |||
42 | object 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 | */ | ||
211 | class 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 | } | ||