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 | ) |