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>2021-05-31 15:06:47 +0100
committerFederico Igne <federico.igne@cs.ox.ac.uk>2021-05-31 15:06:47 +0100
commite932527e33b6f4c1634995224188b26d870d92b2 (patch)
treeb84d9628e0308a5b1979149d025a821b5d8943ca /src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
parent7ab2d819947c03a6618e273d5996a171f0217119 (diff)
downloadRSAComb-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.scala305
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._
64import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} 64import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA}
65import uk.ac.ox.cs.rsacomb.util.Logger 65import uk.ac.ox.cs.rsacomb.util.Logger
66 66
67object 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
67object RSAOntology { 165object 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 */
97class RSAOntology(val original: OWLOntology, val datafiles: File*) { 244class 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(