diff options
author | Federico Igne <federico.igne@cs.ox.ac.uk> | 2021-05-31 15:06:47 +0100 |
---|---|---|
committer | Federico Igne <federico.igne@cs.ox.ac.uk> | 2021-05-31 15:06:47 +0100 |
commit | e932527e33b6f4c1634995224188b26d870d92b2 (patch) | |
tree | b84d9628e0308a5b1979149d025a821b5d8943ca /src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala | |
parent | 7ab2d819947c03a6618e273d5996a171f0217119 (diff) | |
download | RSAComb-e932527e33b6f4c1634995224188b26d870d92b2.tar.gz RSAComb-e932527e33b6f4c1634995224188b26d870d92b2.zip |
Add scafolding for generic approximation support
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.scala | 305 |
1 files changed, 162 insertions, 143 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 index c7b3bf0..e048c28 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala | |||
@@ -64,6 +64,104 @@ import uk.ac.ox.cs.rsacomb.sparql._ | |||
64 | import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} | 64 | import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} |
65 | import uk.ac.ox.cs.rsacomb.util.Logger | 65 | import uk.ac.ox.cs.rsacomb.util.Logger |
66 | 66 | ||
67 | object RSAUtil { | ||
68 | |||
69 | implicit def axiomsToOntology(axioms: Seq[OWLAxiom]) = { | ||
70 | val manager = OWLManager.createOWLOntologyManager() | ||
71 | manager.createOntology(axioms.asJava) | ||
72 | } | ||
73 | |||
74 | /** Compute the RSA dependency graph for a set of axioms | ||
75 | * | ||
76 | * @return a tuple containing the dependency graph and a map between | ||
77 | * the newly introduced constants and the corresponding input axioms. | ||
78 | * | ||
79 | * @note no check on the ontology language is performed since the | ||
80 | * construction of the dependency graph is computed regardless. The | ||
81 | * input axioms are assumed to be normalized. | ||
82 | */ | ||
83 | private def dependencyGraph( | ||
84 | axioms: Seq[OWLAxiom], | ||
85 | datafiles: Seq[File] | ||
86 | ): (Graph[Resource, DiEdge], Map[String, OWLAxiom]) = { | ||
87 | val unsafe = this.unsafeRoles | ||
88 | var nodemap = Map.empty[String, OWLAxiom] | ||
89 | |||
90 | object RSAConverter extends RDFoxConverter { | ||
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 = RSAOntology.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 | |||
67 | object RSAOntology { | 165 | object RSAOntology { |
68 | 166 | ||
69 | /** Name of the RDFox data store used for CQ answering */ | 167 | /** Name of the RDFox data store used for CQ answering */ |
@@ -79,14 +177,63 @@ object RSAOntology { | |||
79 | /** Manager instance to interface with OWLAPI */ | 177 | /** Manager instance to interface with OWLAPI */ |
80 | val manager = OWLManager.createOWLOntologyManager() | 178 | val manager = OWLManager.createOWLOntologyManager() |
81 | 179 | ||
82 | def apply(ontology: File, data: File*): RSAOntology = | 180 | def apply( |
181 | ontofile: File, | ||
182 | datafiles: Seq[File], | ||
183 | approx: Option[Approximation] = None | ||
184 | ): RSAOntology = { | ||
185 | val ontology = manager.loadOntologyFromOntologyDocument(ontofile) | ||
186 | RSAOntology(ontology, datafiles, approx) | ||
187 | } | ||
188 | |||
189 | def apply( | ||
190 | ontology: OWLOntology, | ||
191 | datafiles: Seq[File], | ||
192 | approx: Option[Approximation] = None | ||
193 | ): RSAOntology = { | ||
194 | val normalizer = new Normalizer() | ||
195 | |||
196 | /** TBox axioms */ | ||
197 | var tbox: List[OWLLogicalAxiom] = | ||
198 | original | ||
199 | .tboxAxioms(Imports.INCLUDED) | ||
200 | .collect(Collectors.toList()) | ||
201 | .collect { case a: OWLLogicalAxiom => a } | ||
202 | .flatMap(normalizer.normalize) | ||
203 | |||
204 | /** RBox axioms */ | ||
205 | var rbox: List[OWLLogicalAxiom] = | ||
206 | original | ||
207 | .rboxAxioms(Imports.INCLUDED) | ||
208 | .collect(Collectors.toList()) | ||
209 | .collect { case a: OWLLogicalAxiom => a } | ||
210 | .flatMap(normalizer.normalize) | ||
211 | |||
212 | /** ABox axioms | ||
213 | * | ||
214 | * @note this represents only the set of assertions contained in the | ||
215 | * ontology file. Data files specified in `datafiles` are directly | ||
216 | * imported in RDFox due to performance issues when trying to import | ||
217 | * large data files via OWLAPI. | ||
218 | */ | ||
219 | var abox: List[OWLLogicalAxiom] = | ||
220 | original | ||
221 | .aboxAxioms(Imports.INCLUDED) | ||
222 | .collect(Collectors.toList()) | ||
223 | .collect { case a: OWLLogicalAxiom => a } | ||
224 | .flatMap(normalizer.normalize) | ||
225 | |||
226 | /** Collection of logical axioms in the input ontology */ | ||
227 | var axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox | ||
228 | |||
83 | new RSAOntology( | 229 | new RSAOntology( |
84 | manager.loadOntologyFromOntologyDocument(ontology), | 230 | approx match { |
85 | data: _* | 231 | case Some(a) => a.approximate(axioms, datafiles) |
232 | case None => axioms | ||
233 | }, | ||
234 | datafiles | ||
86 | ) | 235 | ) |
87 | 236 | } | |
88 | def apply(ontology: OWLOntology, data: File*): RSAOntology = | ||
89 | new RSAOntology(ontology, data: _*) | ||
90 | } | 237 | } |
91 | 238 | ||
92 | /** Wrapper class for an ontology in RSA | 239 | /** Wrapper class for an ontology in RSA |
@@ -94,7 +241,7 @@ object RSAOntology { | |||
94 | * @param ontology the input OWL2 ontology. | 241 | * @param ontology the input OWL2 ontology. |
95 | * @param datafiles additinal data (treated as part of the ABox) | 242 | * @param datafiles additinal data (treated as part of the ABox) |
96 | */ | 243 | */ |
97 | class RSAOntology(val original: OWLOntology, val datafiles: File*) { | 244 | class RSAOntology(val axioms: Seq[OWLAxiom], val datafiles: File*) { |
98 | 245 | ||
99 | /** Simplify conversion between OWLAPI and RDFox concepts */ | 246 | /** Simplify conversion between OWLAPI and RDFox concepts */ |
100 | import implicits.RDFox._ | 247 | import implicits.RDFox._ |
@@ -104,49 +251,8 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) { | |||
104 | /** Set of axioms removed during the approximation to RSA */ | 251 | /** Set of axioms removed during the approximation to RSA */ |
105 | private var removed: Seq[OWLAxiom] = Seq.empty | 252 | private var removed: Seq[OWLAxiom] = Seq.empty |
106 | 253 | ||
107 | /** The normalizer normalizes the ontology and approximate it to | ||
108 | * Horn-ALCHOIQ. A further step is needed to obtain an RSA | ||
109 | * approximation of the input ontology `original`. | ||
110 | */ | ||
111 | private val normalizer = new Normalizer() | ||
112 | |||
113 | /** TBox axioms */ | ||
114 | var tbox: List[OWLLogicalAxiom] = | ||
115 | original | ||
116 | .tboxAxioms(Imports.INCLUDED) | ||
117 | .collect(Collectors.toList()) | ||
118 | .collect { case a: OWLLogicalAxiom => a } | ||
119 | .flatMap(normalizer.normalize) | ||
120 | |||
121 | /** RBox axioms */ | ||
122 | var rbox: List[OWLLogicalAxiom] = | ||
123 | original | ||
124 | .rboxAxioms(Imports.INCLUDED) | ||
125 | .collect(Collectors.toList()) | ||
126 | .collect { case a: OWLLogicalAxiom => a } | ||
127 | .flatMap(normalizer.normalize) | ||
128 | |||
129 | /** ABox axioms | ||
130 | * | ||
131 | * @note this represents only the set of assertions contained in the | ||
132 | * ontology file. Data files specified in `datafiles` are directly | ||
133 | * imported in RDFox due to performance issues when trying to import | ||
134 | * large data files via OWLAPI. | ||
135 | */ | ||
136 | var abox: List[OWLLogicalAxiom] = | ||
137 | original | ||
138 | .aboxAxioms(Imports.INCLUDED) | ||
139 | .collect(Collectors.toList()) | ||
140 | .collect { case a: OWLLogicalAxiom => a } | ||
141 | .flatMap(normalizer.normalize) | ||
142 | |||
143 | /** Collection of logical axioms in the input ontology */ | ||
144 | var axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox | ||
145 | |||
146 | /** Normalized Horn-ALCHOIQ ontology */ | 254 | /** Normalized Horn-ALCHOIQ ontology */ |
147 | val ontology = RSAOntology.manager.createOntology( | 255 | val ontology = RSAOntology.manager.createOntology(axioms.asJava) |
148 | axioms.asInstanceOf[List[OWLAxiom]].asJava | ||
149 | ) | ||
150 | 256 | ||
151 | /** OWLAPI internal reasoner instantiated over the approximated ontology */ | 257 | /** OWLAPI internal reasoner instantiated over the approximated ontology */ |
152 | private val reasoner = | 258 | private val reasoner = |
@@ -170,7 +276,7 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) { | |||
170 | val concepts: List[OWLClass] = | 276 | val concepts: List[OWLClass] = |
171 | ontology.getClassesInSignature().asScala.toList | 277 | ontology.getClassesInSignature().asScala.toList |
172 | val roles: List[OWLObjectPropertyExpression] = | 278 | val roles: List[OWLObjectPropertyExpression] = |
173 | (tbox ++ rbox) | 279 | axioms |
174 | .flatMap(_.objectPropertyExpressionsInSignature) | 280 | .flatMap(_.objectPropertyExpressionsInSignature) |
175 | .distinct | 281 | .distinct |
176 | 282 | ||
@@ -190,12 +296,12 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) { | |||
190 | 296 | ||
191 | /* Checking for unsafety condition (1) */ | 297 | /* Checking for unsafety condition (1) */ |
192 | val unsafe1 = for { | 298 | val unsafe1 = for { |
193 | axiom <- tbox | 299 | axiom <- axioms |
194 | if axiom.isT5 | 300 | if axiom.isT5 |
195 | role1 <- axiom.objectPropertyExpressionsInSignature | 301 | role1 <- axiom.objectPropertyExpressionsInSignature |
196 | roleSuper = role1 +: reasoner.superObjectProperties(role1) | 302 | roleSuper = role1 +: reasoner.superObjectProperties(role1) |
197 | roleSuperInv = roleSuper.map(_.getInverseProperty) | 303 | roleSuperInv = roleSuper.map(_.getInverseProperty) |
198 | axiom <- tbox | 304 | axiom <- axioms |
199 | if axiom.isT3 && !axiom.isT3top | 305 | if axiom.isT3 && !axiom.isT3top |
200 | role2 <- axiom.objectPropertyExpressionsInSignature | 306 | role2 <- axiom.objectPropertyExpressionsInSignature |
201 | if roleSuperInv contains role2 | 307 | if roleSuperInv contains role2 |
@@ -203,12 +309,12 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) { | |||
203 | 309 | ||
204 | /* Checking for unsafety condition (2) */ | 310 | /* Checking for unsafety condition (2) */ |
205 | val unsafe2 = for { | 311 | val unsafe2 = for { |
206 | axiom <- tbox | 312 | axiom <- axioms |
207 | if axiom.isT5 | 313 | if axiom.isT5 |
208 | role1 <- axiom.objectPropertyExpressionsInSignature | 314 | role1 <- axiom.objectPropertyExpressionsInSignature |
209 | roleSuper = role1 +: reasoner.superObjectProperties(role1) | 315 | roleSuper = role1 +: reasoner.superObjectProperties(role1) |
210 | roleSuperInv = roleSuper.map(_.getInverseProperty) | 316 | roleSuperInv = roleSuper.map(_.getInverseProperty) |
211 | axiom <- tbox | 317 | axiom <- axioms |
212 | if axiom.isT4 | 318 | if axiom.isT4 |
213 | role2 <- axiom.objectPropertyExpressionsInSignature | 319 | role2 <- axiom.objectPropertyExpressionsInSignature |
214 | if roleSuper.contains(role2) || roleSuperInv.contains(role2) | 320 | if roleSuper.contains(role2) || roleSuperInv.contains(role2) |
@@ -217,93 +323,6 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) { | |||
217 | unsafe1 ++ unsafe2 | 323 | unsafe1 ++ unsafe2 |
218 | } | 324 | } |
219 | 325 | ||
220 | /** Compute the RSA dependency graph | ||
221 | * | ||
222 | * This is used to approximate the input ontology to RSA. | ||
223 | * | ||
224 | * @return a tuple containing the dependency graph and a map between | ||
225 | * the constants newly introduced and the corresponding axioms in the | ||
226 | * ontology. | ||
227 | */ | ||
228 | private def dependencyGraph() | ||
229 | : (Graph[Resource, DiEdge], Map[String, OWLAxiom]) = { | ||
230 | val unsafe = this.unsafeRoles | ||
231 | var nodemap = Map.empty[String, OWLAxiom] | ||
232 | |||
233 | object RSAConverter extends RDFoxConverter { | ||
234 | |||
235 | override def convert( | ||
236 | expr: OWLClassExpression, | ||
237 | term: Term, | ||
238 | unsafe: List[OWLObjectPropertyExpression], | ||
239 | skolem: SkolemStrategy, | ||
240 | suffix: RSASuffix | ||
241 | ): Shards = | ||
242 | (expr, skolem) match { | ||
243 | |||
244 | case (e: OWLObjectSomeValuesFrom, c: Constant) => { | ||
245 | nodemap.update(c.iri.getIRI, c.axiom) | ||
246 | val (res, ext) = super.convert(e, term, unsafe, skolem, suffix) | ||
247 | if (unsafe contains e.getProperty) | ||
248 | (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext) | ||
249 | else | ||
250 | (RSA.PE(term, c.iri) :: res, ext) | ||
251 | } | ||
252 | |||
253 | case (e: OWLDataSomeValuesFrom, c: Constant) => { | ||
254 | nodemap.update(c.iri.getIRI, c.axiom) | ||
255 | val (res, ext) = super.convert(e, term, unsafe, skolem, suffix) | ||
256 | if (unsafe contains e.getProperty) | ||
257 | (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext) | ||
258 | else | ||
259 | (RSA.PE(term, c.iri) :: res, ext) | ||
260 | } | ||
261 | |||
262 | case _ => super.convert(expr, term, unsafe, skolem, suffix) | ||
263 | } | ||
264 | } | ||
265 | |||
266 | /* Ontology convertion into LP rules */ | ||
267 | val term = RSAOntology.genFreshVariable() | ||
268 | val result = axioms.map(a => | ||
269 | RSAConverter.convert(a, term, unsafe, new Constant(a), Empty) | ||
270 | ) | ||
271 | |||
272 | val datalog = result.unzip | ||
273 | val facts = datalog._1.flatten | ||
274 | var rules = datalog._2.flatten | ||
275 | |||
276 | /* Open connection with RDFox */ | ||
277 | val (server, data) = RDFoxUtil.openConnection("rsa_dependency_graph") | ||
278 | |||
279 | /* Add additional built-in rules */ | ||
280 | val varX = Variable.create("X") | ||
281 | val varY = Variable.create("Y") | ||
282 | rules = Rule.create( | ||
283 | RSA.E(varX, varY), | ||
284 | RSA.PE(varX, varY), | ||
285 | RSA.U(varX), | ||
286 | RSA.U(varY) | ||
287 | ) :: rules | ||
288 | /* Load facts and rules from ontology */ | ||
289 | RDFoxUtil.addFacts(data, facts) | ||
290 | RDFoxUtil.addRules(data, rules) | ||
291 | /* Load data files */ | ||
292 | RDFoxUtil.addData(data, datafiles: _*) | ||
293 | |||
294 | /* Build the graph */ | ||
295 | val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }" | ||
296 | val answers = RDFoxUtil.submitQuery(data, query, RSA.Prefixes).get | ||
297 | var edges: Seq[DiEdge[Resource]] = | ||
298 | answers.collect { case (_, Seq(n1, n2)) => n1 ~> n2 } | ||
299 | val graph = Graph(edges: _*) | ||
300 | |||
301 | /* Close connection to RDFox */ | ||
302 | RDFoxUtil.closeConnection(server, data) | ||
303 | |||
304 | (graph, nodemap) | ||
305 | } | ||
306 | |||
307 | /** Approximate a Horn-ALCHOIQ ontology to RSA | 326 | /** Approximate a Horn-ALCHOIQ ontology to RSA |
308 | * | 327 | * |
309 | * This is done by gathering those axioms that prevent the ontology | 328 | * This is done by gathering those axioms that prevent the ontology |
@@ -653,7 +672,7 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) { | |||
653 | val conflR = this.confl(roleR) | 672 | val conflR = this.confl(roleR) |
654 | // We just need the TBox to find | 673 | // We just need the TBox to find |
655 | val terms = for { | 674 | val terms = for { |
656 | axiom1 <- tbox | 675 | axiom1 <- axioms |
657 | if axiom1.isT5 | 676 | if axiom1.isT5 |
658 | // We expect only one role coming out of a T5 axiom | 677 | // We expect only one role coming out of a T5 axiom |
659 | roleS <- axiom1.objectPropertyExpressionsInSignature | 678 | roleS <- axiom1.objectPropertyExpressionsInSignature |
@@ -693,7 +712,7 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) { | |||
693 | ) | 712 | ) |
694 | Logger.print( | 713 | Logger.print( |
695 | s"Logical axioms in Horn-ALCHOIQ ontology: ${ontology | 714 | s"Logical axioms in Horn-ALCHOIQ ontology: ${ontology |
696 | .getLogicalAxiomCount(true)} (${tbox.length}/${rbox.length}/${abox.length})", | 715 | .getLogicalAxiomCount(true)} (${axioms.length}/${axioms.length}/${axioms.length})", |
697 | level | 716 | level |
698 | ) | 717 | ) |
699 | Logger.print( | 718 | Logger.print( |