diff options
| author | Federico Igne <git@federicoigne.com> | 2021-08-02 09:44:50 +0100 |
|---|---|---|
| committer | Federico Igne <git@federicoigne.com> | 2021-08-02 18:03:23 +0100 |
| commit | 71367fb626710dcdca0fa09f1902b521c966ef71 (patch) | |
| tree | f0af6ef75d0851b661b3f9e171ed9444ccf78b50 | |
| parent | 9256241855a2b0eb21c5af01cf5ca0e3b25524d3 (diff) | |
| download | RSAComb-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.scala | 208 |
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 | ) |
