aboutsummaryrefslogtreecommitdiff
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
parent7ab2d819947c03a6618e273d5996a171f0217119 (diff)
downloadRSAComb-e932527e33b6f4c1634995224188b26d870d92b2.tar.gz
RSAComb-e932527e33b6f4c1634995224188b26d870d92b2.zip
Add scafolding for generic approximation support
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala305
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/approximation/approximation.scala16
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/approximation/lowerbound.scala207
3 files changed, 385 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(
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/approximation.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/approximation.scala
new file mode 100644
index 0000000..073d0d9
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/approximation.scala
@@ -0,0 +1,16 @@
1package uk.ac.ox.cs.rsacomb.approximation
2
3import java.io.File
4import org.semanticweb.owlapi.model.OWLAxiom
5
6/** Ontology approximation technique. */
7trait Approximation {
8
9 /** Approximate an ontology.
10 *
11 * @param ontology input ontology
12 * @return a new approximated ontology
13 */
14 def approximate(ontology: Seq[OWLAxiom], datafiles: Seq[File]): Seq[OWLAxiom]
15
16}
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/lowerbound.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/lowerbound.scala
new file mode 100644
index 0000000..8036250
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/lowerbound.scala
@@ -0,0 +1,207 @@
1package uk.ac.ox.cs.rsacomb.approximation
2
3/** Approximation algorithm that mantains soundness for CQ answering.
4 *
5 * The input OWL 2 ontology is assumed to be normalized and the output
6 * ontology is guaranteed to be in RSA.
7 *
8 * The algorithm is performed in three steps:
9 * 1. the ontology is reduced to ALCHOIQ by discarding any axiom
10 * that is not in the language;
11 * 2. the ontology is further reduced to Horn-ALCHOIQ by shifting
12 * axioms with disjunction on the rhs;
13 * 3. the ontology is approximated to RSA by manipulating its
14 * dependency graph.
15 *
16 * @see [[uk.ac.ox.cs.rsacomb.converter.Normalizer]]
17 */
18class LowerBound extends Approximation {
19
20 val normalizer = new Normalizer()
21
22 /** Main entry point for the approximation algorithm */
23 def approximate(
24 ontology: Seq[OWLAxiom],
25 datafiles: Seq[File]
26 ): Seq[OWLAxiom] = {
27 /* Normalize axioms */
28 val axioms1 = axioms flatMap normalizer.normalize
29 /* Delete any axiom outside of ALCHOIQ */
30 val axioms2 = axioms1 filterNot inHornLACHOIQ
31 /* Shift any axiom with disjunction on the rhs */
32 val axioms3 = for {
33 a1 <- axioms1
34 a2 <- shift(a1)
35 a3 <- normalize(a2)
36 } yield a3
37 /* Approximate to RSA */
38 toRSA(axioms3, datafiles)
39 }
40
41 /** Discards all axioms outside ALCHOIQ */
42 def inALCHOIQ(axiom: OWLLogicalAxiom): Boolean =
43 axiom match {
44 case a: OWLSubClassOfAxiom => {
45 val sub = a.getSubClass.getNNF
46 val sup = a.getSuperClass.getNNF
47 (sub, sup) match {
48 case (sub: OWLObjectAllValuesFrom, _) => false
49 case (sub: OWLDataAllValuesFrom, _) => false
50 case (_, sup: OWLDataAllValuesFrom) => false
51 case (sub: OWLObjectMinCardinality, _) if sub.getCardinality >= 2 =>
52 false
53 case (sub: OWLDataMinCardinality, _) if sub.getCardinality >= 2 =>
54 false
55 case (_, sup: OWLObjectMinCardinality) if sup.getCardinality >= 2 =>
56 false
57 case (_, sup: OWLDataMinCardinality) if sup.getCardinality >= 2 =>
58 false
59 case (sub: OWLObjectMaxCardinality, _) => false
60 case (sub: OWLDataMaxCardinality, _) => false
61 case (_, sup: OWLObjectMaxCardinality) if sup.getCardinality >= 2 =>
62 false
63 case (_, sup: OWLDataMaxCardinality) if sup.getCardinality >= 1 =>
64 false
65 case (_, sup: OWLObjectOneOf) if sup.getIndividuals.length > 2 =>
66 false
67 case (sub: OWLObjectHasSelf, _) => false
68 case (_, sup: OWLObjectHasSelf) => false
69 case _ => true
70 }
71 }
72 case a: OWLTransitiveObjectPropertyAxiom => false
73 case a: OWLReflexiveObjectPropertyAxiom => false
74 case a: OWLSubPropertyChainOfAxiom => false
75 case a: OWLAsymmetricObjectPropertyAxiom => false
76 case a => true
77 }
78
79 /** Shifting axioms with disjunction on the rhs.
80 *
81 * The process of shifting presenves soundness but completenes w.r.t.
82 * CQ answering is lost.
83 *
84 * @example
85 *
86 * A -> B1 u B2 u B3 .
87 *
88 * becomes
89 *
90 * A n nB1 n nB2 -> B3 .
91 * A n nB1 n nB3 -> B2 .
92 * A n nB2 n nB3 -> B1 .
93 * nB1 n nB2 n nB3 -> nA .
94 *
95 * where nA, nB1, nB2, nB3 are fresh predicates "corresponding" to
96 * the negation of A, B1, B2, B3 respectively.
97 */
98 def shift(axiom: OWLLogicalAxiom): Seq[OWLLogicalAxiom] =
99 axiom match {
100 case a: OWLSubClassOfAxiom => {
101 val sub = a.getSubClass.getNNF
102 val sup = a.getSuperClass.getNNF
103 sup match {
104 case sup: OWLObjectUnionOf => {
105 val body = sub.asConjunctSet.map((atom) => (atom, freshOWLClass()))
106 val head = sup.asDisjunctSet.map((atom) => (atom, freshOWLClass()))
107
108 val r1 =
109 factory.getOWLSubClassOfAxiom(
110 factory.getOWLObjectIntersectionOf(
111 (body.map(_._1) ++ head.map(_._2)): _*
112 ),
113 factory.getOWLNothing
114 )
115
116 val r2s =
117 for {
118 (a, na) <- head
119 hs = head.map(_._2).filterNot(_ equals na)
120 } yield factory.getOWLSubClassOfAxiom(
121 factory.getOWLObjectIntersectionOf(
122 (body.map(_._1) ++ hs): _*
123 ),
124 a
125 )
126
127 val r3s =
128 for {
129 (a, na) <- body
130 bs = body.map(_._1).filterNot(_ equals a)
131 } yield factory.getOWLSubClassOfAxiom(
132 factory.getOWLObjectIntersectionOf(
133 (bs ++ head.map(_._2)): _*
134 ),
135 na
136 )
137
138 Seq(r1) ++ r2s ++ r3s
139 }
140 case _ => Seq(axiom)
141 }
142 }
143 case _ => Seq(axiom)
144 }
145
146 /** Approximate a Horn-ALCHOIQ ontology to RSA
147 *
148 * This is done by gathering those axioms that prevent the ontology
149 * dependency graph from being tree-shaped, and removing them.
150 *
151 * @param axioms the set of axioms to approximate.
152 * @return the approximated set of axioms.
153 */
154 def toRSA(axioms: Seq[OWLAxiom], datafiles: Seq[File]): Seq[OWLAxiom] = {
155 /* Compute the dependency graph for the ontology */
156 val (graph, nodemap) = RSAUtil.dependencyGraph(axioms, datafiles)
157
158 /* Define node colors for the graph visit */
159 sealed trait NodeColor
160 case object Unvisited extends NodeColor
161 case object Visited extends NodeColor
162 case object ToDelete extends NodeColor
163
164 /* Keep track of node colors during graph visit */
165 var color = Map.from[Resource, NodeColor](
166 graph.nodes.toOuter.map(k => (k, Unvisited))
167 )
168
169 for {
170 component <- graph.componentTraverser().map(_ to Graph)
171 edge <- component
172 .outerEdgeTraverser(component.nodes.head)
173 .withKind(BreadthFirst)
174 } yield {
175 val source = edge._1
176 val target = edge._2
177 color(source) match {
178 case Unvisited | Visited => {
179 color(target) match {
180 case Unvisited =>
181 color(source) = Visited;
182 color(target) = Visited
183 case Visited =>
184 color(source) = ToDelete
185 case ToDelete =>
186 color(source) = Visited
187 }
188 }
189 case ToDelete =>
190 }
191 }
192
193 val toDelete = color.iterator.collect { case (resource: IRI, ToDelete) =>
194 nodemap(resource.getIRI)
195 }.toSeq
196
197 /* Remove axioms from approximated ontology */
198 axioms diff toDelete
199 }
200 // val edges1 = Seq('A ~> 'B, 'B ~> 'C, 'C ~> 'D, 'D ~> 'H, 'H ~>
201 // 'G, 'G ~> 'F, 'E ~> 'A, 'E ~> 'F, 'B ~> 'E, 'F ~> 'G, 'B ~> 'F,
202 // 'C ~> 'G, 'D ~> 'C, 'H ~> 'D)
203 // val edges2 = Seq('I ~> 'M, 'I ~> 'L, 'L ~> 'N, 'M ~> 'N)
204 // val edges3 = Seq('P ~> 'O)
205 // val graph = Graph.from(edges = edges1 ++ edges2 ++ edges3)
206 //
207}