diff options
| author | Federico Igne <federico.igne@cs.ox.ac.uk> | 2021-02-04 10:38:33 +0000 |
|---|---|---|
| committer | Federico Igne <federico.igne@cs.ox.ac.uk> | 2021-02-04 10:38:33 +0000 |
| commit | b9fe66ed1b48d21f3fe6cb960c8fbe8f22f4a39c (patch) | |
| tree | 3c696ab6e635383d5277114a591a0d9a02bdecd8 /src/main | |
| parent | 26f01c74b83cf79f5af425692421380dd3eb6bea (diff) | |
| download | RSAComb-b9fe66ed1b48d21f3fe6cb960c8fbe8f22f4a39c.tar.gz RSAComb-b9fe66ed1b48d21f3fe6cb960c8fbe8f22f4a39c.zip | |
Add ontology normalizer
This also allows to define ontology approximations to RSA in a simple
way.
Diffstat (limited to 'src/main')
3 files changed, 541 insertions, 2 deletions
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala index 3777c6b..3da6c8a 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala | |||
| @@ -79,7 +79,9 @@ class CanonicalModel(val ontology: RSAOntology) { | |||
| 79 | val term = RSAOntology.genFreshVariable() | 79 | val term = RSAOntology.genFreshVariable() |
| 80 | val unsafe = ontology.unsafeRoles | 80 | val unsafe = ontology.unsafeRoles |
| 81 | ontology.axioms | 81 | ontology.axioms |
| 82 | .map(CanonicalModelConverter.convert(_, term, unsafe, NoSkolem, Empty)) | 82 | .map(a => |
| 83 | CanonicalModelConverter.convert(a, term, unsafe, Constant(a), Empty) | ||
| 84 | ) | ||
| 83 | .unzip | 85 | .unzip |
| 84 | } | 86 | } |
| 85 | ( | 87 | ( |
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 0f1552a..42a5b87 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala | |||
| @@ -99,6 +99,8 @@ class RSAOntology(_ontology: File, val datafiles: File*) { | |||
| 99 | private val reasoner = | 99 | private val reasoner = |
| 100 | (new StructuralReasonerFactory()).createReasoner(ontology) | 100 | (new StructuralReasonerFactory()).createReasoner(ontology) |
| 101 | 101 | ||
| 102 | private val normalizer = new Normalizer() | ||
| 103 | |||
| 102 | /** Imported knowledge base. */ | 104 | /** Imported knowledge base. */ |
| 103 | //lazy val kbase: OWLOntology = { | 105 | //lazy val kbase: OWLOntology = { |
| 104 | // val merger = new OWLOntologyMerger(manager) | 106 | // val merger = new OWLOntologyMerger(manager) |
| @@ -112,6 +114,7 @@ class RSAOntology(_ontology: File, val datafiles: File*) { | |||
| 112 | .tboxAxioms(Imports.INCLUDED) | 114 | .tboxAxioms(Imports.INCLUDED) |
| 113 | .collect(Collectors.toList()) | 115 | .collect(Collectors.toList()) |
| 114 | .collect { case a: OWLLogicalAxiom => a } | 116 | .collect { case a: OWLLogicalAxiom => a } |
| 117 | .flatMap(normalizer.normalize) | ||
| 115 | Logger.print(s"Original TBox: ${tbox.length}", Logger.DEBUG) | 118 | Logger.print(s"Original TBox: ${tbox.length}", Logger.DEBUG) |
| 116 | 119 | ||
| 117 | /** RBox axioms */ | 120 | /** RBox axioms */ |
| @@ -120,6 +123,7 @@ class RSAOntology(_ontology: File, val datafiles: File*) { | |||
| 120 | .rboxAxioms(Imports.INCLUDED) | 123 | .rboxAxioms(Imports.INCLUDED) |
| 121 | .collect(Collectors.toList()) | 124 | .collect(Collectors.toList()) |
| 122 | .collect { case a: OWLLogicalAxiom => a } | 125 | .collect { case a: OWLLogicalAxiom => a } |
| 126 | .flatMap(normalizer.normalize) | ||
| 123 | Logger.print(s"Original RBox: ${rbox.length}", Logger.DEBUG) | 127 | Logger.print(s"Original RBox: ${rbox.length}", Logger.DEBUG) |
| 124 | 128 | ||
| 125 | /** ABox axioms | 129 | /** ABox axioms |
| @@ -134,7 +138,8 @@ class RSAOntology(_ontology: File, val datafiles: File*) { | |||
| 134 | .aboxAxioms(Imports.INCLUDED) | 138 | .aboxAxioms(Imports.INCLUDED) |
| 135 | .collect(Collectors.toList()) | 139 | .collect(Collectors.toList()) |
| 136 | .collect { case a: OWLLogicalAxiom => a } | 140 | .collect { case a: OWLLogicalAxiom => a } |
| 137 | Logger.print(s"Original RBox: ${abox.length}", Logger.DEBUG) | 141 | .flatMap(normalizer.normalize) |
| 142 | Logger.print(s"Original ABox: ${abox.length}", Logger.DEBUG) | ||
| 138 | 143 | ||
| 139 | /** Collection of logical axioms in the input ontology */ | 144 | /** Collection of logical axioms in the input ontology */ |
| 140 | lazy val axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox | 145 | lazy val axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox |
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 new file mode 100644 index 0000000..3c4dd5c --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala | |||
| @@ -0,0 +1,532 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb.converter | ||
| 2 | |||
| 3 | import org.semanticweb.owlapi.apibinding.OWLManager | ||
| 4 | import org.semanticweb.owlapi.model._ | ||
| 5 | |||
| 6 | import uk.ac.ox.cs.rsacomb.util.Logger | ||
| 7 | |||
| 8 | object Normalizer { | ||
| 9 | |||
| 10 | /** Factory used for converting expressions when needed. | ||
| 11 | * | ||
| 12 | * @note most of the time this is used to perform some kind of | ||
| 13 | * normalization on axioms. In later versions it might be worth | ||
| 14 | * moving the normalization code in its own independent step. | ||
| 15 | */ | ||
| 16 | private val manager = OWLManager.createOWLOntologyManager() | ||
| 17 | val factory = manager.getOWLDataFactory() | ||
| 18 | |||
| 19 | } | ||
| 20 | |||
| 21 | class Normalizer() { | ||
| 22 | |||
| 23 | import Normalizer._ | ||
| 24 | |||
| 25 | /** Simplify conversion between Java and Scala collections */ | ||
| 26 | import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ | ||
| 27 | |||
| 28 | private var counter = -1 | ||
| 29 | def freshOWLClass(): OWLClass = factory.getOWLClass(s"X${counter += 1}") | ||
| 30 | |||
| 31 | /** Normalizes a | ||
| 32 | * [[org.semanticweb.owlapi.model.OWLLogicalAxiom OWLLogicalAxiom]] | ||
| 33 | * | ||
| 34 | * @note not all possible axioms are supported. Following is a list | ||
| 35 | * of all unhandled class expressions: | ||
| 36 | * - [[org.semanticweb.owlapi.model.OWLAsymmetricObjectPropertyAxiom OWLAsymmetricObjectPropertyAxiom]] | ||
| 37 | * - [[org.semanticweb.owlapi.model.OWLDatatypeDefinitionAxiom OWLDatatypeDefinitionAxiom]] | ||
| 38 | * - [[org.semanticweb.owlapi.model.OWLDisjointDataPropertiesAxiom OWLDisjointDataPropertiesAxiom]] | ||
| 39 | * - [[org.semanticweb.owlapi.model.OWLDisjointObjectPropertiesAxiom OWLDisjointObjectPropertiesAxiom]] | ||
| 40 | * - [[org.semanticweb.owlapi.model.OWLHasKeyAxiom OWLHasKeyAxiom]] | ||
| 41 | * - [[org.semanticweb.owlapi.model.SWRLRule SWRLRule]] | ||
| 42 | */ | ||
| 43 | def normalize(axiom: OWLLogicalAxiom): Seq[OWLLogicalAxiom] = | ||
| 44 | axiom match { | ||
| 45 | case a: OWLSubClassOfAxiom => { | ||
| 46 | val sub = a.getSubClass.getNNF | ||
| 47 | val sup = a.getSuperClass.getNNF | ||
| 48 | (sub, sup) match { | ||
| 49 | /** Split complex subclass axioms | ||
| 50 | * | ||
| 51 | * C c D -> { C c X, X c D } | ||
| 52 | */ | ||
| 53 | case _ if !sub.isOWLClass && !sup.isOWLClass => { | ||
| 54 | val cls = freshOWLClass() | ||
| 55 | Seq( | ||
| 56 | factory.getOWLSubClassOfAxiom(sub, cls), | ||
| 57 | factory.getOWLSubClassOfAxiom(cls, sup) | ||
| 58 | ).flatMap(normalize) | ||
| 59 | } | ||
| 60 | /** Conjunction on the lhs | ||
| 61 | * | ||
| 62 | * A1 n ... n C n ... n An c D -> { C c X, A1 n ... n X n ... n An c D } | ||
| 63 | */ | ||
| 64 | case (sub: OWLObjectIntersectionOf, _) | ||
| 65 | if sub.asConjunctSet.exists(c => !c.isOWLClass) => { | ||
| 66 | var additional = Seq() | ||
| 67 | val conjuncts = sub.asConjunctSet | ||
| 68 | if (conjuncts.length > 0) { | ||
| 69 | val acc = (Seq[OWLClassExpression](), Seq[OWLLogicalAxiom]()) | ||
| 70 | val (acc1, acc2) = conjuncts.foldLeft(acc)( | ||
| 71 | { case ((acc1, acc2), conj) => | ||
| 72 | if (conj.isOWLClass) | ||
| 73 | (acc1 :+ conj, acc2) | ||
| 74 | else { | ||
| 75 | val cls = freshOWLClass() | ||
| 76 | ( | ||
| 77 | acc1 :+ cls, | ||
| 78 | acc2 :+ factory.getOWLSubClassOfAxiom(conj, cls) | ||
| 79 | ) | ||
| 80 | } | ||
| 81 | } | ||
| 82 | ) | ||
| 83 | (acc2 :+ factory.getOWLSubClassOfAxiom( | ||
| 84 | factory.getOWLObjectIntersectionOf(acc1: _*), | ||
| 85 | sup | ||
| 86 | )) | ||
| 87 | .flatMap(normalize) | ||
| 88 | } else { | ||
| 89 | normalize(factory.getOWLSubClassOfAxiom(factory.getOWLThing, sup)) | ||
| 90 | } | ||
| 91 | } | ||
| 92 | /** Conjunction on the rhs | ||
| 93 | * | ||
| 94 | * D c A1 n ... n An -> { D c A1, ... , D c An } | ||
| 95 | */ | ||
| 96 | case (_, sup: OWLObjectIntersectionOf) => { | ||
| 97 | val conjuncts = sup.asConjunctSet | ||
| 98 | if (conjuncts.length > 0) { | ||
| 99 | conjuncts | ||
| 100 | .map(cls => factory.getOWLSubClassOfAxiom(sub, cls)) | ||
| 101 | .flatMap(normalize) | ||
| 102 | } else { | ||
| 103 | normalize(factory.getOWLSubClassOfAxiom(sub, factory.getOWLThing)) | ||
| 104 | } | ||
| 105 | } | ||
| 106 | /** Disjunction on the lhs | ||
| 107 | * | ||
| 108 | * A1 u ... u An c D -> { A1 c D, ... , An c D } | ||
| 109 | */ | ||
| 110 | case (sub: OWLObjectUnionOf, _) => { | ||
| 111 | val disjuncts = sub.asDisjunctSet | ||
| 112 | if (disjuncts.length > 0) { | ||
| 113 | disjuncts | ||
| 114 | .map(cls => factory.getOWLSubClassOfAxiom(cls, sup)) | ||
| 115 | .flatMap(normalize) | ||
| 116 | } else { | ||
| 117 | normalize( | ||
| 118 | factory.getOWLSubClassOfAxiom(factory.getOWLNothing, sup) | ||
| 119 | ) | ||
| 120 | } | ||
| 121 | } | ||
| 122 | /** Disjunction on the rhs is not supported */ | ||
| 123 | case (_, sup: OWLObjectUnionOf) => notInHornALCHOIQ(a) | ||
| 124 | /** Complex class expression on existential restriction on the lhs | ||
| 125 | * | ||
| 126 | * exists R . C c D -> { C c X, exists R . X c D } | ||
| 127 | */ | ||
| 128 | case (sub: OWLObjectSomeValuesFrom, _) | ||
| 129 | if !sub.getFiller.isOWLClass => { | ||
| 130 | val cls = freshOWLClass() | ||
| 131 | Seq( | ||
| 132 | factory.getOWLSubClassOfAxiom(sub.getFiller, cls), | ||
| 133 | factory.getOWLSubClassOfAxiom( | ||
| 134 | factory.getOWLObjectSomeValuesFrom(sub.getProperty, cls), | ||
| 135 | sup | ||
| 136 | ) | ||
| 137 | ).flatMap(normalize) | ||
| 138 | } | ||
| 139 | /** Complex class expression on existential restriction on the rhs | ||
| 140 | * | ||
| 141 | * C c exists R . D -> { X c D, C c exists R . X } | ||
| 142 | */ | ||
| 143 | case (_, sup: OWLObjectSomeValuesFrom) | ||
| 144 | if !sup.getFiller.isOWLClass => { | ||
| 145 | val cls = freshOWLClass() | ||
| 146 | Seq( | ||
| 147 | factory.getOWLSubClassOfAxiom(cls, sup.getFiller), | ||
| 148 | factory.getOWLSubClassOfAxiom( | ||
| 149 | sub, | ||
| 150 | factory.getOWLObjectSomeValuesFrom(sup.getProperty, cls) | ||
| 151 | ) | ||
| 152 | ).flatMap(normalize) | ||
| 153 | } | ||
| 154 | /** Object/Data universal quantification on the lhs not supported */ | ||
| 155 | case (sub: OWLObjectAllValuesFrom, _) => notInHornALCHOIQ(a) | ||
| 156 | case (sub: OWLDataAllValuesFrom, _) => notInHornALCHOIQ(a) | ||
| 157 | /** Object universal quantification on the rhs | ||
| 158 | * | ||
| 159 | * C c forall R . D -> exists R- . C c D | ||
| 160 | */ | ||
| 161 | case (_, sup: OWLObjectAllValuesFrom) => | ||
| 162 | normalize( | ||
| 163 | factory.getOWLSubClassOfAxiom( | ||
| 164 | factory | ||
| 165 | .getOWLObjectSomeValuesFrom( | ||
| 166 | sup.getProperty.getInverseProperty, | ||
| 167 | sub | ||
| 168 | ), | ||
| 169 | sup.getFiller | ||
| 170 | ) | ||
| 171 | ) | ||
| 172 | /** Object universal quantification on the rhs not supported */ | ||
| 173 | case (_, sup: OWLDataAllValuesFrom) => notInHornALCHOIQ(a) | ||
| 174 | /** Exact object/data cardinality restriction on the lhs/rhs | ||
| 175 | * | ||
| 176 | * = i R . C -> <= i R . C n >= i R . X | ||
| 177 | */ | ||
| 178 | case (sub: OWLObjectExactCardinality, _) => | ||
| 179 | normalize( | ||
| 180 | factory.getOWLSubClassOfAxiom(sub.asIntersectionOfMinMax, sup) | ||
| 181 | ) | ||
| 182 | case (sub: OWLDataExactCardinality, _) => | ||
| 183 | normalize( | ||
| 184 | factory.getOWLSubClassOfAxiom(sub.asIntersectionOfMinMax, sup) | ||
| 185 | ) | ||
| 186 | case (_, sup: OWLObjectExactCardinality) => | ||
| 187 | normalize( | ||
| 188 | factory.getOWLSubClassOfAxiom(sub, sup.asIntersectionOfMinMax) | ||
| 189 | ) | ||
| 190 | case (_, sup: OWLDataExactCardinality) => | ||
| 191 | normalize( | ||
| 192 | factory.getOWLSubClassOfAxiom(sub, sup.asIntersectionOfMinMax) | ||
| 193 | ) | ||
| 194 | /** Min object/data cardinality restriction on the lhs/rhs | ||
| 195 | * | ||
| 196 | * >= 0 R . C -> top | ||
| 197 | * >= 1 R . C -> exists R . C | ||
| 198 | * | ||
| 199 | * @note not supported when restriction >= 2. | ||
| 200 | */ | ||
| 201 | case (sub: OWLObjectMinCardinality, _) => | ||
| 202 | sub.getCardinality match { | ||
| 203 | case 0 => | ||
| 204 | normalize( | ||
| 205 | factory.getOWLSubClassOfAxiom(factory.getOWLThing, sup) | ||
| 206 | ) | ||
| 207 | case 1 => | ||
| 208 | normalize( | ||
| 209 | factory.getOWLSubClassOfAxiom( | ||
| 210 | factory.getOWLObjectSomeValuesFrom( | ||
| 211 | sub.getProperty, | ||
| 212 | sub.getFiller | ||
| 213 | ), | ||
| 214 | sup | ||
| 215 | ) | ||
| 216 | ) | ||
| 217 | case _ => notInHornALCHOIQ(a) | ||
| 218 | } | ||
| 219 | case (sub: OWLDataMinCardinality, _) => | ||
| 220 | sub.getCardinality match { | ||
| 221 | case 0 => | ||
| 222 | normalize( | ||
| 223 | factory.getOWLSubClassOfAxiom(factory.getOWLThing, sup) | ||
| 224 | ) | ||
| 225 | case 1 => | ||
| 226 | normalize( | ||
| 227 | factory.getOWLSubClassOfAxiom( | ||
| 228 | factory.getOWLDataSomeValuesFrom( | ||
| 229 | sub.getProperty, | ||
| 230 | sub.getFiller | ||
| 231 | ), | ||
| 232 | sup | ||
| 233 | ) | ||
| 234 | ) | ||
| 235 | case _ => notInHornALCHOIQ(a) | ||
| 236 | } | ||
| 237 | case (_, sup: OWLObjectMinCardinality) => | ||
| 238 | sup.getCardinality match { | ||
| 239 | case 0 => Seq() | ||
| 240 | case 1 => | ||
| 241 | normalize( | ||
| 242 | factory.getOWLSubClassOfAxiom( | ||
| 243 | sub, | ||
| 244 | factory.getOWLObjectSomeValuesFrom( | ||
| 245 | sup.getProperty, | ||
| 246 | sup.getFiller | ||
| 247 | ) | ||
| 248 | ) | ||
| 249 | ) | ||
| 250 | case _ => notInHornALCHOIQ(a) | ||
| 251 | } | ||
| 252 | case (_, sup: OWLDataMinCardinality) => | ||
| 253 | sup.getCardinality match { | ||
| 254 | case 0 => Seq() | ||
| 255 | case 1 => | ||
| 256 | normalize( | ||
| 257 | factory.getOWLSubClassOfAxiom( | ||
| 258 | sub, | ||
| 259 | factory.getOWLDataSomeValuesFrom( | ||
| 260 | sup.getProperty, | ||
| 261 | sup.getFiller | ||
| 262 | ) | ||
| 263 | ) | ||
| 264 | ) | ||
| 265 | case _ => notInHornALCHOIQ(a) | ||
| 266 | } | ||
| 267 | /** Max object/data cardinality restriction on the lhs not supported */ | ||
| 268 | case (sub: OWLObjectMaxCardinality, _) => notInHornALCHOIQ(a) | ||
| 269 | case (sub: OWLDataMaxCardinality, _) => notInHornALCHOIQ(a) | ||
| 270 | /** Max object/data cardinality restriction on the rhs | ||
| 271 | * | ||
| 272 | * C c <= 0 R . D -> C n exists R . D -> bot | ||
| 273 | * C c <= 1 R . D -> { X c D, C c <= 1 R . D } | ||
| 274 | * | ||
| 275 | * @note not supported when restriction >= 2. | ||
| 276 | */ | ||
| 277 | case (_, sup: OWLObjectMaxCardinality) if sup.getCardinality == 0 => | ||
| 278 | normalize( | ||
| 279 | factory.getOWLSubClassOfAxiom( | ||
| 280 | factory.getOWLObjectIntersectionOf( | ||
| 281 | sub, | ||
| 282 | factory | ||
| 283 | .getOWLObjectSomeValuesFrom(sup.getProperty, sup.getFiller) | ||
| 284 | ), | ||
| 285 | factory.getOWLNothing | ||
| 286 | ) | ||
| 287 | ) | ||
| 288 | case (_, sup: OWLObjectMaxCardinality) | ||
| 289 | if sup.getCardinality == 1 && !sup.getFiller.isOWLClass => { | ||
| 290 | val cls = freshOWLClass() | ||
| 291 | Seq( | ||
| 292 | factory.getOWLSubClassOfAxiom(cls, sup.getFiller), | ||
| 293 | factory.getOWLSubClassOfAxiom( | ||
| 294 | sub, | ||
| 295 | factory.getOWLObjectMaxCardinality(1, sup.getProperty, cls) | ||
| 296 | ) | ||
| 297 | ).flatMap(normalize) | ||
| 298 | } | ||
| 299 | case (_, sup: OWLObjectMaxCardinality) if sup.getCardinality >= 2 => | ||
| 300 | notInHornALCHOIQ(a) | ||
| 301 | case (_, sup: OWLDataMaxCardinality) if sup.getCardinality == 0 => | ||
| 302 | normalize( | ||
| 303 | factory.getOWLSubClassOfAxiom( | ||
| 304 | factory.getOWLObjectIntersectionOf( | ||
| 305 | sub, | ||
| 306 | factory | ||
| 307 | .getOWLDataSomeValuesFrom(sup.getProperty, sup.getFiller) | ||
| 308 | ), | ||
| 309 | factory.getOWLNothing | ||
| 310 | ) | ||
| 311 | ) | ||
| 312 | case (_, sup: OWLDataMaxCardinality) if sup.getCardinality >= 1 => | ||
| 313 | notInHornALCHOIQ(a) | ||
| 314 | /** HasValue expression on the lhs/rhs | ||
| 315 | * | ||
| 316 | * HasValue(R, a) -> exists R . {a} | ||
| 317 | */ | ||
| 318 | case (sub: OWLObjectHasValue, _) => | ||
| 319 | normalize( | ||
| 320 | factory.getOWLSubClassOfAxiom( | ||
| 321 | factory.getOWLObjectSomeValuesFrom( | ||
| 322 | sub.getProperty, | ||
| 323 | factory.getOWLObjectOneOf(sub.getFiller) | ||
| 324 | ), | ||
| 325 | sup | ||
| 326 | ) | ||
| 327 | ) | ||
| 328 | case (sub: OWLDataHasValue, _) => | ||
| 329 | normalize( | ||
| 330 | factory.getOWLSubClassOfAxiom( | ||
| 331 | factory.getOWLDataSomeValuesFrom( | ||
| 332 | sub.getProperty, | ||
| 333 | factory.getOWLDataOneOf(sub.getFiller) | ||
| 334 | ), | ||
| 335 | sup | ||
| 336 | ) | ||
| 337 | ) | ||
| 338 | case (_, sup: OWLObjectHasValue) => | ||
| 339 | normalize( | ||
| 340 | factory.getOWLSubClassOfAxiom( | ||
| 341 | sub, | ||
| 342 | factory.getOWLObjectSomeValuesFrom( | ||
| 343 | sup.getProperty, | ||
| 344 | factory.getOWLObjectOneOf(sup.getFiller) | ||
| 345 | ) | ||
| 346 | ) | ||
| 347 | ) | ||
| 348 | case (_, sup: OWLDataHasValue) => | ||
| 349 | normalize( | ||
| 350 | factory.getOWLSubClassOfAxiom( | ||
| 351 | sub, | ||
| 352 | factory.getOWLDataSomeValuesFrom( | ||
| 353 | sup.getProperty, | ||
| 354 | factory.getOWLDataOneOf(sup.getFiller) | ||
| 355 | ) | ||
| 356 | ) | ||
| 357 | ) | ||
| 358 | /** Enumeration of individuals on the lhs | ||
| 359 | * | ||
| 360 | * {a1, ... ,an} c D -> { D(a1), ..., D(an) } | ||
| 361 | */ | ||
| 362 | case (sub: OWLObjectOneOf, _) => | ||
| 363 | sub.getIndividuals.map(factory.getOWLClassAssertionAxiom(sup, _)) | ||
| 364 | /** Enumeration of individuals on the rhs | ||
| 365 | * It's supported only when of cardinality < 2. | ||
| 366 | */ | ||
| 367 | case (_, sup: OWLObjectOneOf) if sup.getIndividuals.length == 0 => | ||
| 368 | normalize(factory.getOWLSubClassOfAxiom(sub, factory.getOWLNothing)) | ||
| 369 | case (_, sup: OWLObjectOneOf) if sup.getIndividuals.length > 2 => | ||
| 370 | notInHornALCHOIQ(a) | ||
| 371 | /** Class complement on the lhs | ||
| 372 | * | ||
| 373 | * ~C c D -> top c C n D | ||
| 374 | */ | ||
| 375 | case (sub: OWLObjectComplementOf, _) => | ||
| 376 | normalize( | ||
| 377 | factory.getOWLSubClassOfAxiom( | ||
| 378 | factory.getOWLThing, | ||
| 379 | factory.getOWLObjectIntersectionOf(sub.getComplementNNF, sup) | ||
| 380 | ) | ||
| 381 | ) | ||
| 382 | /** Class complement on the rhs | ||
| 383 | * | ||
| 384 | * C c ~D -> C n D c bot | ||
| 385 | */ | ||
| 386 | case (_, sup: OWLObjectComplementOf) => | ||
| 387 | normalize( | ||
| 388 | factory.getOWLSubClassOfAxiom( | ||
| 389 | factory.getOWLObjectIntersectionOf(sup.getComplementNNF, sub), | ||
| 390 | factory.getOWLNothing | ||
| 391 | ) | ||
| 392 | ) | ||
| 393 | /** Axiom is already normalized */ | ||
| 394 | case _ => Seq(a) | ||
| 395 | } | ||
| 396 | } | ||
| 397 | |||
| 398 | case a: OWLEquivalentClassesAxiom => { | ||
| 399 | a.getAxiomWithoutAnnotations.asOWLSubClassOfAxioms.flatMap(normalize) | ||
| 400 | } | ||
| 401 | |||
| 402 | case a: OWLEquivalentObjectPropertiesAxiom => { | ||
| 403 | a.getAxiomWithoutAnnotations.asSubObjectPropertyOfAxioms.flatMap( | ||
| 404 | normalize | ||
| 405 | ) | ||
| 406 | } | ||
| 407 | |||
| 408 | case a: OWLEquivalentDataPropertiesAxiom => { | ||
| 409 | a.getAxiomWithoutAnnotations.asSubDataPropertyOfAxioms.flatMap( | ||
| 410 | normalize | ||
| 411 | ) | ||
| 412 | } | ||
| 413 | |||
| 414 | case a: OWLObjectPropertyDomainAxiom => | ||
| 415 | normalize(a.getAxiomWithoutAnnotations.asOWLSubClassOfAxiom) | ||
| 416 | |||
| 417 | case a: OWLObjectPropertyRangeAxiom => | ||
| 418 | normalize(a.getAxiomWithoutAnnotations.asOWLSubClassOfAxiom) | ||
| 419 | |||
| 420 | case a: OWLDataPropertyDomainAxiom => | ||
| 421 | normalize(a.getAxiomWithoutAnnotations.asOWLSubClassOfAxiom) | ||
| 422 | |||
| 423 | case a: OWLDataPropertyRangeAxiom => | ||
| 424 | normalize(a.getAxiomWithoutAnnotations.asOWLSubClassOfAxiom) | ||
| 425 | |||
| 426 | case a: OWLDisjointClassesAxiom => | ||
| 427 | a.asPairwiseAxioms.map((a) => { | ||
| 428 | val classes = a.getAxiomWithoutAnnotations.getClassExpressions | ||
| 429 | factory.getOWLSubClassOfAxiom( | ||
| 430 | factory.getOWLObjectIntersectionOf(classes), | ||
| 431 | factory.getOWLNothing | ||
| 432 | ) | ||
| 433 | }) | ||
| 434 | |||
| 435 | case a: OWLInverseObjectPropertiesAxiom => | ||
| 436 | a.getAxiomWithoutAnnotations.asSubObjectPropertyOfAxioms.flatMap( | ||
| 437 | normalize | ||
| 438 | ) | ||
| 439 | |||
| 440 | case a: OWLFunctionalObjectPropertyAxiom => | ||
| 441 | normalize(a.getAxiomWithoutAnnotations.asOWLSubClassOfAxiom) | ||
| 442 | |||
| 443 | case a: OWLFunctionalDataPropertyAxiom => | ||
| 444 | normalize(a.getAxiomWithoutAnnotations.asOWLSubClassOfAxiom) | ||
| 445 | |||
| 446 | case a: OWLInverseFunctionalObjectPropertyAxiom => | ||
| 447 | normalize(a.getAxiomWithoutAnnotations.asOWLSubClassOfAxiom) | ||
| 448 | |||
| 449 | case a: OWLSymmetricObjectPropertyAxiom => | ||
| 450 | a.getAxiomWithoutAnnotations.asSubPropertyAxioms.flatMap(normalize) | ||
| 451 | |||
| 452 | case a: OWLDifferentIndividualsAxiom => | ||
| 453 | a.asPairwiseAxioms.map((a) => { | ||
| 454 | val classes = a.getIndividuals.map(factory.getOWLObjectOneOf(_)) | ||
| 455 | factory.getOWLSubClassOfAxiom( | ||
| 456 | factory.getOWLObjectIntersectionOf(classes), | ||
| 457 | factory.getOWLNothing | ||
| 458 | ) | ||
| 459 | }) | ||
| 460 | |||
| 461 | case a: OWLIrreflexiveObjectPropertyAxiom => | ||
| 462 | normalize(a.getAxiomWithoutAnnotations.asOWLSubClassOfAxiom) | ||
| 463 | |||
| 464 | case a: OWLSameIndividualAxiom => | ||
| 465 | a.getAxiomWithoutAnnotations.asOWLSubClassOfAxioms.flatMap(normalize) | ||
| 466 | |||
| 467 | case a: OWLDisjointUnionAxiom => | ||
| 468 | Seq(a.getOWLDisjointClassesAxiom, a.getOWLEquivalentClassesAxiom) | ||
| 469 | .flatMap(normalize) | ||
| 470 | |||
| 471 | /** Complex class assertion | ||
| 472 | * | ||
| 473 | * C(a) -> { X(a), X c C } | ||
| 474 | */ | ||
| 475 | case a: OWLClassAssertionAxiom if !a.getClassExpression.isOWLClass => { | ||
| 476 | val cls = freshOWLClass() | ||
| 477 | Seq( | ||
| 478 | factory.getOWLClassAssertionAxiom(cls, a.getIndividual), | ||
| 479 | factory.getOWLSubClassOfAxiom(cls, a.getClassExpression) | ||
| 480 | ).flatMap(normalize) | ||
| 481 | } | ||
| 482 | |||
| 483 | case a: OWLNegativeObjectPropertyAssertionAxiom => | ||
| 484 | normalize(a.getAxiomWithoutAnnotations.asOWLSubClassOfAxiom) | ||
| 485 | |||
| 486 | case a: OWLNegativeDataPropertyAssertionAxiom => | ||
| 487 | normalize(a.getAxiomWithoutAnnotations.asOWLSubClassOfAxiom) | ||
| 488 | |||
| 489 | /** Not in Horn-ALCHOIQ */ | ||
| 490 | |||
| 491 | case a: OWLTransitiveObjectPropertyAxiom => notInHornALCHOIQ(a) | ||
| 492 | |||
| 493 | case a: OWLReflexiveObjectPropertyAxiom => notInHornALCHOIQ(a) | ||
| 494 | |||
| 495 | case a: OWLSubPropertyChainOfAxiom => notInHornALCHOIQ(a) | ||
| 496 | |||
| 497 | /** Unsupported */ | ||
| 498 | |||
| 499 | case a: OWLAsymmetricObjectPropertyAxiom => notSupported(a) | ||
| 500 | |||
| 501 | case a: OWLDatatypeDefinitionAxiom => notSupported(a) | ||
| 502 | |||
| 503 | case a: OWLDisjointDataPropertiesAxiom => notSupported(a) | ||
| 504 | |||
| 505 | case a: OWLDisjointObjectPropertiesAxiom => notSupported(a) | ||
| 506 | |||
| 507 | case a: OWLHasKeyAxiom => notSupported(a) | ||
| 508 | |||
| 509 | case a: SWRLRule => notSupported(a) | ||
| 510 | |||
| 511 | /** Axiom is already normalized */ | ||
| 512 | case a => Seq(a) | ||
| 513 | } | ||
| 514 | |||
| 515 | /** Approximation function for axioms out of Horn-ALCHOIQ | ||
| 516 | * | ||
| 517 | * By default discards the axiom, which guarantees a lower bound | ||
| 518 | * ontology w.r.t. CQ answering. | ||
| 519 | */ | ||
| 520 | protected def notInHornALCHOIQ( | ||
| 521 | axiom: OWLLogicalAxiom | ||
| 522 | ): Seq[OWLLogicalAxiom] = { | ||
| 523 | Logger print s"'$axiom' has been ignored because it is not in Horn-ALCHOIQ" | ||
| 524 | Seq() | ||
| 525 | } | ||
| 526 | |||
| 527 | /** Non supported axioms */ | ||
| 528 | private def notSupported(axiom: OWLLogicalAxiom): Seq[OWLLogicalAxiom] = | ||
| 529 | throw new RuntimeException( | ||
| 530 | s"'$axiom' is not currently supported." | ||
| 531 | ) | ||
| 532 | } | ||
