aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFederico Igne <git@federicoigne.com>2021-08-02 09:44:50 +0100
committerFederico Igne <git@federicoigne.com>2021-08-02 18:03:23 +0100
commit71367fb626710dcdca0fa09f1902b521c966ef71 (patch)
treef0af6ef75d0851b661b3f9e171ed9444ccf78b50
parent9256241855a2b0eb21c5af01cf5ca0e3b25524d3 (diff)
downloadRSAComb-71367fb626710dcdca0fa09f1902b521c966ef71.tar.gz
RSAComb-71367fb626710dcdca0fa09f1902b521c966ef71.zip
Update normalizer to handle additional OWL axioms
Also the normalizer should not distinguish ALCHOIQ axioms from non-ALCHOIQ axioms, and should try its best to return a normalised axiom.
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala208
1 files changed, 97 insertions, 111 deletions
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala
index 285040e..b5da3cc 100644
--- a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala
@@ -43,21 +43,7 @@ class Normalizer() {
43 /** Simplify conversion between Java and Scala collections */ 43 /** Simplify conversion between Java and Scala collections */
44 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ 44 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._
45 45
46 /** Statistics */ 46 /** Normalizes a [[OWLLogicalAxiom]]
47 var discarded = 0
48 var shifted = 0
49
50 /** Normalizes a
51 * [[org.semanticweb.owlapi.model.OWLLogicalAxiom OWLLogicalAxiom]]
52 *
53 * @note not all possible axioms are supported. Following is a list
54 * of all unhandled class expressions:
55 * - [[org.semanticweb.owlapi.model.OWLAsymmetricObjectPropertyAxiom OWLAsymmetricObjectPropertyAxiom]]
56 * - [[org.semanticweb.owlapi.model.OWLDatatypeDefinitionAxiom OWLDatatypeDefinitionAxiom]]
57 * - [[org.semanticweb.owlapi.model.OWLDisjointDataPropertiesAxiom OWLDisjointDataPropertiesAxiom]]
58 * - [[org.semanticweb.owlapi.model.OWLDisjointObjectPropertiesAxiom OWLDisjointObjectPropertiesAxiom]]
59 * - [[org.semanticweb.owlapi.model.OWLHasKeyAxiom OWLHasKeyAxiom]]
60 * - [[org.semanticweb.owlapi.model.SWRLRule SWRLRule]]
61 */ 47 */
62 def normalize(axiom: OWLLogicalAxiom): Seq[OWLLogicalAxiom] = 48 def normalize(axiom: OWLLogicalAxiom): Seq[OWLLogicalAxiom] =
63 axiom match { 49 axiom match {
@@ -138,12 +124,37 @@ class Normalizer() {
138 ) 124 )
139 } 125 }
140 } 126 }
141 /** Disjunction on the rhs is not supported directly 127 /** Disjunction on the rhs
142 * 128 *
143 * Instead we `shift` the rule to eliminate the disjunction. 129 * B c A1 u ... u C u ... u An -> { X c C, B c A1 u ... u X u ... u An }
144 */ 130 */
145 case (_, sup: OWLObjectUnionOf) => 131 case (_, sup: OWLObjectUnionOf)
146 shift(sub, sup) flatMap normalize 132 if sup.asDisjunctSet.exists(c => !c.isOWLClass) => {
133 var additional = Seq()
134 val disjuncts = sup.asDisjunctSet
135 // BUG: why test for legth if this branch gets triggered only
136 // when there exists a ClassExpression in the disjuncts?
137 if (disjuncts.length > 0) {
138 val acc = (Seq[OWLClassExpression](), Seq[OWLLogicalAxiom]())
139 val (acc1, acc2) = disjuncts.foldLeft(acc)(
140 { case ((acc1, acc2), disj: OWLClass) => (acc1 :+ disj, acc2)
141 case ((acc1, acc2), disj) => {
142 val cls = RSAUtil.getFreshOWLClass()
143 (
144 acc1 :+ cls,
145 acc2 :+ factory.getOWLSubClassOfAxiom(cls, disj)
146 )
147 }
148 }
149 )
150 (acc2 :+ factory.getOWLSubClassOfAxiom(
151 sub,
152 factory.getOWLObjectUnionOf(acc1: _*)
153 )).flatMap(normalize)
154 } else {
155 normalize(factory.getOWLSubClassOfAxiom(sub, factory.getOWLNothing))
156 }
157 }
147 /** Complex class expression on existential restriction on the lhs 158 /** Complex class expression on existential restriction on the lhs
148 * 159 *
149 * exists R . C c D -> { C c X, exists R . X c D } 160 * exists R . C c D -> { C c X, exists R . X c D }
@@ -174,9 +185,35 @@ class Normalizer() {
174 ) 185 )
175 ).flatMap(normalize) 186 ).flatMap(normalize)
176 } 187 }
177 /** Object/Data universal quantification on the lhs not supported */ 188 /** Object universal quantification on the lhs
178 case (sub: OWLObjectAllValuesFrom, _) => notInHornALCHOIQ(a) 189 *
179 case (sub: OWLDataAllValuesFrom, _) => notInHornALCHOIQ(a) 190 * forall R . B c A
191 * ¬ A c ¬∀forall R . B
192 * ¬ A c exists R . ¬ B
193 * ¬ A c C, C c R . ¬ B
194 * top c A u C, D c ¬ B, C c exists R . D
195 * top c A u C, D n B c bot, C c exists R . D
196 */
197 case (sub: OWLObjectAllValuesFrom, _) => {
198 val role = sub.getProperty
199 val filler = sub.getFiller
200 val (c, d) = (RSAUtil.getFreshOWLClass, RSAUtil.getFreshOWLClass)
201 Seq(
202 factory.getOWLSubClassOfAxiom(
203 factory.getOWLThing,
204 factory.getOWLObjectUnionOf(sup, c)
205 ),
206 factory.getOWLSubClassOfAxiom(
207 factory.getOWLObjectIntersectionOf(d, filler),
208 factory.getOWLNothing
209 ),
210 factory.getOWLSubClassOfAxiom(
211 c, factory.getOWLObjectSomeValuesFrom(role, d)
212 )
213 )
214 }
215 /** Object/Data universal quantification on the lhs */
216 case (sub: OWLDataAllValuesFrom, _) => notSupported(a)
180 /** Object universal quantification on the rhs 217 /** Object universal quantification on the rhs
181 * 218 *
182 * C c forall R . D -> exists R- . C c D 219 * C c forall R . D -> exists R- . C c D
@@ -193,7 +230,7 @@ class Normalizer() {
193 ) 230 )
194 ) 231 )
195 /** Object universal quantification on the rhs not supported */ 232 /** Object universal quantification on the rhs not supported */
196 case (_, sup: OWLDataAllValuesFrom) => notInHornALCHOIQ(a) 233 case (_, sup: OWLDataAllValuesFrom) => notSupported(a)
197 /** Exact object/data cardinality restriction on the lhs/rhs 234 /** Exact object/data cardinality restriction on the lhs/rhs
198 * 235 *
199 * = i R . C -> <= i R . C n >= i R . X 236 * = i R . C -> <= i R . C n >= i R . X
@@ -237,7 +274,7 @@ class Normalizer() {
237 sup 274 sup
238 ) 275 )
239 ) 276 )
240 case _ => notInHornALCHOIQ(a) 277 case _ => notSupported(a)
241 } 278 }
242 case (sub: OWLDataMinCardinality, _) => 279 case (sub: OWLDataMinCardinality, _) =>
243 sub.getCardinality match { 280 sub.getCardinality match {
@@ -255,7 +292,7 @@ class Normalizer() {
255 sup 292 sup
256 ) 293 )
257 ) 294 )
258 case _ => notInHornALCHOIQ(a) 295 case _ => notSupported(a)
259 } 296 }
260 case (_, sup: OWLObjectMinCardinality) => 297 case (_, sup: OWLObjectMinCardinality) =>
261 sup.getCardinality match { 298 sup.getCardinality match {
@@ -270,7 +307,7 @@ class Normalizer() {
270 ) 307 )
271 ) 308 )
272 ) 309 )
273 case _ => notInHornALCHOIQ(a) 310 case _ => notSupported(a)
274 } 311 }
275 case (_, sup: OWLDataMinCardinality) => 312 case (_, sup: OWLDataMinCardinality) =>
276 sup.getCardinality match { 313 sup.getCardinality match {
@@ -285,11 +322,11 @@ class Normalizer() {
285 ) 322 )
286 ) 323 )
287 ) 324 )
288 case _ => notInHornALCHOIQ(a) 325 case _ => notSupported(a)
289 } 326 }
290 /** Max object/data cardinality restriction on the lhs not supported */ 327 /** Max object/data cardinality restriction on the lhs not supported */
291 case (sub: OWLObjectMaxCardinality, _) => notInHornALCHOIQ(a) 328 case (sub: OWLObjectMaxCardinality, _) => notSupported(a)
292 case (sub: OWLDataMaxCardinality, _) => notInHornALCHOIQ(a) 329 case (sub: OWLDataMaxCardinality, _) => notSupported(a)
293 /** Max object/data cardinality restriction on the rhs 330 /** Max object/data cardinality restriction on the rhs
294 * 331 *
295 * C c <= 0 R . D -> C n exists R . D -> bot 332 * C c <= 0 R . D -> C n exists R . D -> bot
@@ -320,7 +357,7 @@ class Normalizer() {
320 ).flatMap(normalize) 357 ).flatMap(normalize)
321 } 358 }
322 case (_, sup: OWLObjectMaxCardinality) if sup.getCardinality >= 2 => 359 case (_, sup: OWLObjectMaxCardinality) if sup.getCardinality >= 2 =>
323 notInHornALCHOIQ(a) 360 notSupported(a)
324 case (_, sup: OWLDataMaxCardinality) if sup.getCardinality == 0 => 361 case (_, sup: OWLDataMaxCardinality) if sup.getCardinality == 0 =>
325 normalize( 362 normalize(
326 factory.getOWLSubClassOfAxiom( 363 factory.getOWLSubClassOfAxiom(
@@ -333,7 +370,7 @@ class Normalizer() {
333 ) 370 )
334 ) 371 )
335 case (_, sup: OWLDataMaxCardinality) if sup.getCardinality >= 1 => 372 case (_, sup: OWLDataMaxCardinality) if sup.getCardinality >= 1 =>
336 notInHornALCHOIQ(a) 373 notSupported(a)
337 /** HasValue expression on the lhs/rhs 374 /** HasValue expression on the lhs/rhs
338 * 375 *
339 * HasValue(R, a) -> exists R . {a} 376 * HasValue(R, a) -> exists R . {a}
@@ -385,21 +422,27 @@ class Normalizer() {
385 case (sub: OWLObjectOneOf, _) => 422 case (sub: OWLObjectOneOf, _) =>
386 sub.getIndividuals.map(factory.getOWLClassAssertionAxiom(sup, _)) 423 sub.getIndividuals.map(factory.getOWLClassAssertionAxiom(sup, _))
387 /** Enumeration of individuals on the rhs 424 /** Enumeration of individuals on the rhs
388 * It's supported only when of cardinality < 2. 425 *
426 * A c {a1, ... ,an} -> { A c {a1} u ... u {an} }
389 */ 427 */
390 case (_, sup: OWLObjectOneOf) if sup.getIndividuals.length == 0 =>
391 normalize(factory.getOWLSubClassOfAxiom(sub, factory.getOWLNothing))
392 case (_, sup: OWLObjectOneOf) if sup.getIndividuals.length > 2 => 428 case (_, sup: OWLObjectOneOf) if sup.getIndividuals.length > 2 =>
393 notInHornALCHOIQ(a) 429 normalize(
430 factory.getOWLSubClassOfAxiom(
431 sub,
432 factory.getOWLObjectUnionOf(
433 sup.getIndividuals.map(factory.getOWLObjectOneOf(_))
434 )
435 )
436 )
394 /** Class complement on the lhs 437 /** Class complement on the lhs
395 * 438 *
396 * ~C c D -> top c C n D 439 * ~C c D -> top c C u D
397 */ 440 */
398 case (sub: OWLObjectComplementOf, _) => 441 case (sub: OWLObjectComplementOf, _) =>
399 normalize( 442 normalize(
400 factory.getOWLSubClassOfAxiom( 443 factory.getOWLSubClassOfAxiom(
401 factory.getOWLThing, 444 factory.getOWLThing,
402 factory.getOWLObjectIntersectionOf(sub.getComplementNNF, sup) 445 factory.getOWLObjectUnionOf(sub.getComplementNNF, sup)
403 ) 446 )
404 ) 447 )
405 /** Class complement on the rhs 448 /** Class complement on the rhs
@@ -414,8 +457,8 @@ class Normalizer() {
414 ) 457 )
415 ) 458 )
416 /** Self-restriction over an object property */ 459 /** Self-restriction over an object property */
417 case (sub: OWLObjectHasSelf, _) => notInHornALCHOIQ(a) 460 case (sub: OWLObjectHasSelf, _) => notSupported(a)
418 case (_, sup: OWLObjectHasSelf) => notInHornALCHOIQ(a) 461 case (_, sup: OWLObjectHasSelf) => notSupported(a)
419 462
420 /** Axiom is already normalized */ 463 /** Axiom is already normalized */
421 case _ => Seq(a) 464 case _ => Seq(a)
@@ -513,17 +556,17 @@ class Normalizer() {
513 case a: OWLNegativeDataPropertyAssertionAxiom => 556 case a: OWLNegativeDataPropertyAssertionAxiom =>
514 normalize(a.getAxiomWithoutAnnotations.asOWLSubClassOfAxiom) 557 normalize(a.getAxiomWithoutAnnotations.asOWLSubClassOfAxiom)
515 558
516 /** Not in Horn-ALCHOIQ */ 559 case a: OWLTransitiveObjectPropertyAxiom => {
517 560 val role = a.getProperty
518 case a: OWLTransitiveObjectPropertyAxiom => notInHornALCHOIQ(a) 561 normalize(
519 562 factory.getOWLSubPropertyChainOfAxiom( List(role, role), role)
520 case a: OWLReflexiveObjectPropertyAxiom => notInHornALCHOIQ(a) 563 )
521 564 }
522 case a: OWLSubPropertyChainOfAxiom => notInHornALCHOIQ(a)
523 565
524 /** Unsupported */ 566 case a: OWLReflexiveObjectPropertyAxiom =>
567 normalize(a.getAxiomWithoutAnnotations.asOWLSubClassOfAxiom)
525 568
526 case a: OWLAsymmetricObjectPropertyAxiom => notInHornALCHOIQ(a) 569 case a: OWLAsymmetricObjectPropertyAxiom => notSupported(a)
527 570
528 case a: OWLDatatypeDefinitionAxiom => notSupported(a) 571 case a: OWLDatatypeDefinitionAxiom => notSupported(a)
529 572
@@ -536,74 +579,17 @@ class Normalizer() {
536 case a: SWRLRule => notSupported(a) 579 case a: SWRLRule => notSupported(a)
537 580
538 /** Axiom is already normalized */ 581 /** Axiom is already normalized */
582 //case a: OWLSubPropertyChainOfAxiom => notSupported(a)
539 case a => Seq(a) 583 case a => Seq(a)
540 } 584 }
541 585
542 /** Shift an axiom with disjunction on the rhs */
543 private def shift(
544 sub: OWLClassExpression,
545 sup: OWLObjectUnionOf
546 ): Seq[OWLLogicalAxiom] = {
547 val body =
548 sub.asConjunctSet.map((atom) => (atom, RSAUtil.getFreshOWLClass()))
549 val head =
550 sup.asDisjunctSet.map((atom) => (atom, RSAUtil.getFreshOWLClass()))
551
552 /* Update statistics */
553 shifted += 1
554
555 val r1 =
556 factory.getOWLSubClassOfAxiom(
557 factory.getOWLObjectIntersectionOf(
558 (body.map(_._1) ++ head.map(_._2)): _*
559 ),
560 factory.getOWLNothing
561 )
562
563 val r2s =
564 for {
565 (a, na) <- head
566 hs = head.map(_._2).filterNot(_ equals na)
567 } yield factory.getOWLSubClassOfAxiom(
568 factory.getOWLObjectIntersectionOf(
569 (body.map(_._1) ++ hs): _*
570 ),
571 a
572 )
573
574 val r3s =
575 for {
576 (a, na) <- body
577 bs = body.map(_._1).filterNot(_ equals a)
578 } yield factory.getOWLSubClassOfAxiom(
579 factory.getOWLObjectIntersectionOf(
580 (bs ++ head.map(_._2)): _*
581 ),
582 na
583 )
584
585 Seq(r1) ++ r2s ++ r3s
586 }
587
588 /** Approximation function for axioms out of Horn-ALCHOIQ
589 *
590 * By default discards the axiom, which guarantees a lower bound
591 * ontology w.r.t. CQ answering.
592 */
593 protected def notInHornALCHOIQ(
594 axiom: OWLLogicalAxiom
595 ): Seq[OWLLogicalAxiom] = {
596 /* Update statistics */
597 discarded += 1
598 Logger.print(
599 s"'$axiom' has been ignored because it is not in Horn-ALCHOIQ",
600 Logger.VERBOSE
601 )
602 Seq()
603 }
604
605 /** Non supported axioms */ 586 /** Non supported axioms */
606 private def notSupported(axiom: OWLLogicalAxiom): Seq[OWLLogicalAxiom] = 587 private def notSupported(axiom: OWLLogicalAxiom): Seq[OWLLogicalAxiom] =
588 // Logger.print(
589 // s"'$axiom' has been ignored because it is not in Horn-ALCHOIQ",
590 // Logger.VERBOSE
591 // )
592 // Seq()
607 throw new RuntimeException( 593 throw new RuntimeException(
608 s"'$axiom' is not currently supported." 594 s"'$axiom' is not currently supported."
609 ) 595 )