aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/uk/ac/ox/cs/rsacomb/ontology/Ontology.scala
blob: 7f9638e52174fc1da68a5e1612340b1cc4d25bdf (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
/*
 * Copyright 2020-2022 KRR Oxford
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package uk.ac.ox.cs.rsacomb.ontology

import java.util.stream.Collectors

import scala.collection.JavaConverters._
import scala.collection.mutable.Map
import scalax.collection.Graph
import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._

import org.semanticweb.owlapi.apibinding.OWLManager
import org.semanticweb.owlapi.model._
import org.semanticweb.owlapi.model.parameters.Imports
import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory
import tech.oxfordsemantic.jrdfox.logic.datalog.{
  FilterAtom,
  Rule,
  TupleTableAtom,
  TupleTableName,
}
import tech.oxfordsemantic.jrdfox.logic.expression.{
  FunctionCall,
  IRI,
  Literal,
  Resource,
  Term,
}

import uk.ac.ox.cs.rsacomb.approximation.Approximation
import uk.ac.ox.cs.rsacomb.converter._
import uk.ac.ox.cs.rsacomb.suffix._
import uk.ac.ox.cs.rsacomb.util.{DataFactory,RDFoxUtil,RSA}

object Ontology {

  /** Simplify conversion between Java and Scala collections */
  import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._
  
  /** Name of the RDFox data store used for the RSA check */
  val DataStore = "rsa_dependency_graph"
  /* RSA check named graph */
  val RSACheck: IRI = RDFoxUtil.getNamedGraph(DataStore, "RSACheck")
  /* Named graphs for some necessary RBox reasoning */
  private val RBoxProxy: IRI = RDFoxUtil.getNamedGraph(DataStore, "RBoxProxy")
  val RBoxReasoning: IRI = RDFoxUtil.getNamedGraph(DataStore, "RBoxReasoning")

  /** Manager instance to interface with OWLAPI
    *
    * TODO: turn this into an implicit class parameter.
    */
  val manager = OWLManager.createOWLOntologyManager()
  val factory = manager.getOWLDataFactory()

  def apply(ontology: OWLOntology, datafiles: List[os.Path]): Ontology = {

    /** TBox axioms */
    var tbox: List[OWLLogicalAxiom] =
      ontology
        .tboxAxioms(Imports.INCLUDED)
        .collect(Collectors.toList())
        .collect { case a: OWLLogicalAxiom => a }

    /** RBox axioms */
    var rbox: List[OWLLogicalAxiom] =
      ontology
        .rboxAxioms(Imports.INCLUDED)
        .collect(Collectors.toList())
        .collect { case a: OWLLogicalAxiom => a }

    /** ABox axioms
      *
      * @note this represents only the set of assertions contained in the
      * ontology file. Data files specified in `datafiles` are directly
      * imported in RDFox due to performance issues when trying to import
      * large data files via OWLAPI.
      */
    var abox: List[OWLLogicalAxiom] =
      ontology
        .aboxAxioms(Imports.INCLUDED)
        .collect(Collectors.toList())
        .collect { case a: OWLLogicalAxiom => a }

    new Ontology(ontology, abox ::: tbox ::: rbox, datafiles)
  }

  def apply(ontofile: os.Path, datafiles: List[os.Path]): Ontology = {
    val ontology = manager.loadOntologyFromOntologyDocument(ontofile.toIO)
    Ontology(ontology, datafiles)
  }

}

/** A wrapper for a generic OWL2 ontology
  *
  * @param origin reference to the wrapped [[OWLOntology]]
  * @param axioms list of axioms (TBox + RBox) in the ontology
  * @param datafiles files containing ABox data
  */
class Ontology (
    val origin: OWLOntology,
    val axioms: List[OWLLogicalAxiom],
    val datafiles: List[os.Path]
) {

  import uk.ac.ox.cs.rsacomb.util.RDFoxDSL._

  import uk.ac.ox.cs.rsacomb.implicits.RDFox._
  import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._
  import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._


  /** OWLOntology based on input axioms
    *
    * This is mainly used to instantiate a new reasoner to be used in
    * the computation of unsafe roles.
    */
  protected val ontology: OWLOntology =
    Ontology.manager.createOntology((axioms: List[OWLAxiom]).asJava)

  /** OWLAPI internal reasoner for the current ontology */
  protected val reasoner =
    (new StructuralReasonerFactory()).createReasoner(ontology)

  /** Returns individuals in ontology */
  val individuals: List[IRI] =
    ontology.getIndividualsInSignature(Imports.INCLUDED).map(_.getIRI)

  /** Returns literals in ontology */
  val literals: List[Literal] =
    axioms.collect { case a: OWLDataPropertyAssertionAxiom => a } .map(_.getObject)

  /** Returns concepts in ontology */
  val concepts: List[OWLClass] = ontology.getClassesInSignature()

  /** Retrieve (object) roles in ontology */
  val objroles: List[OWLObjectPropertyExpression] =
    axioms.flatMap(_.objectPropertyExpressionsInSignature).distinct

  /** Retrieve (data) roles in ontology */
  val dataroles: List[OWLDataProperty] = origin.getDataPropertiesInSignature

  /** Unsafe roles in the ontology
    *
    * Unsafety conditions are the following:
    *
    * 1) For all roles r1 appearing in an axiom of type T5, r1 is unsafe
    *    if there exists a role r2 (different from top) appearing in an
    *    axiom of type T3 and r1 is a subproperty of the inverse of r2.
    *
    * 2) For all roles p1 appearing in an axiom of type T5, p1 is unsafe
    *    if there exists a role p2 appearing in an axiom of type T4 and
    *    p1 is a subproperty of either p2 or the inverse of p2.
    */
  lazy val unsafe: List[OWLObjectPropertyExpression] = {

    /* Checking for unsafety condition (1) */
    val unsafe1 = for {
      axiom <- axioms
      if axiom.isT5
      role1 <- axiom.objectPropertyExpressionsInSignature
      roleSuper = role1 +: reasoner.superObjectProperties(role1)
      axiom <- axioms
      if axiom.isT3 && !axiom.isT3top
      role2 <- axiom.objectPropertyExpressionsInSignature
      if roleSuper contains role2.getInverseProperty
    } yield role1

    /* Checking for unsafety condition (2) */
    val unsafe2 = for {
      axiom <- axioms
      if axiom.isT5
      role1 <- axiom.objectPropertyExpressionsInSignature
      roleSuper = role1 +: reasoner.superObjectProperties(role1)
      axiom <- axioms
      if axiom.isT4
      role2 <- axiom.objectPropertyExpressionsInSignature
      if roleSuper.contains(role2) ||
        roleSuper.contains(role2.getInverseProperty)
    } yield role1

    unsafe1 ++ unsafe2
  }

  /** Top axiomatization rules.
    *
    * For each concept/role *in the ontology file* introduce a rule to
    * derive `owl:Thing`.
    *
    * @note this might not be enough in cases where data files contain
    * concept/roles that are not in the ontology file. While this is
    * non-standard, it is not forbidden either and may cause problems
    * since not all individuals are considered part of `owl:Thing`.
    *
    * @note this is a naïve implementation of top axiomatization and
    * might change in the future. The ideal solution would be for RDFox
    * to take care of this, but at the time of writing this is not
    * compatible with the way we are using the tool.
    *
    * TODO: use RDFox DSL
    */
  protected def topAxioms(tt: IRI): List[Rule] = {
    val graph = TupleTableName.create(tt.getIRI)
    Rule.create(
      TupleTableAtom.create(graph, v"X", IRI.RDF_TYPE, IRI.THING),
      TupleTableAtom.create(graph, v"X", IRI.RDF_TYPE, v"Y")
    ) :: objroles.map(r => {
      val name = r match {
        case x: OWLObjectProperty => x.getIRI.getIRIString
        case x: OWLObjectInverseOf =>
          x.getInverse.getNamedProperty.getIRI.getIRIString :: Inverse
      }
      Rule.create(
        List(
          TupleTableAtom.create(graph, v"X", IRI.RDF_TYPE, IRI.THING),
          TupleTableAtom.create(graph, v"Y", IRI.RDF_TYPE, IRI.THING)
        ),
        List(TupleTableAtom.create(graph, v"X", name, v"Y"))
      )
    }) ::: dataroles.map(r => {
      val name = r.getIRI.getIRIString
      Rule.create(
        List(
          TupleTableAtom.create(graph, v"X", IRI.RDF_TYPE, IRI.THING),
          TupleTableAtom.create(graph, v"Y", IRI.RDF_TYPE, IRI.THING)
        ),
        List(TupleTableAtom.create(graph, v"X", name, v"Y"))
      )
    })
  }

  /** Equality axiomatization rules.
    *
    * Introduce reflexivity, simmetry and transitivity rules for a naïve
    * equality axiomatization.
    *
    * @note that we are using a custom `congruent` predicate to indicate
    * equality. This is to avoid interfering with the standard
    * `owl:sameAs`.
    *
    * @note RDFox is able to handle equality in a "smart" way, but this
    * behaviour is incompatible with other needed features like
    * negation-as-failure and aggregates.
    *
    * @todo naïve substitution rules might not be very efficient. We
    * should look into other ways of implementing this (e.g.,
    * singularization).
    */
  protected def equalityAxioms(tt: IRI): List[Rule] = {
    val graph = RDFoxGraph(tt)
    val g = TupleTableName.create(tt.getIRI)
    // Equality properties
    val properties = List(
      // Reflexivity
      graph(v"X", RSA.CONGRUENT, v"X") :- graph(v"X", IRI.RDF_TYPE, IRI.THING),
      // Simmetry
      graph(v"Y", RSA.CONGRUENT, v"X") :- graph(v"X", RSA.CONGRUENT, v"Y"),
      // Transitivity
      graph(v"X", RSA.CONGRUENT, v"Z") :-
        graph(v"X", RSA.CONGRUENT, v"Y") + graph(v"Y", RSA.CONGRUENT, v"Z"),
    )
    /* Equality substitution rules */
    val conceptSub =
      graph(v"Y", IRI.RDF_TYPE, v"Z") :-
        graph(v"X", RSA.CONGRUENT, v"Y") + graph(v"X", IRI.RDF_TYPE, v"Z") +
        FilterAtom.create(FunctionCall.notEqual(v"Z", RSA.NAMED))
    val roleSub = objroles.flatMap(r => {
        val name = r match {
          case x: OWLObjectProperty => x.getIRI.getIRIString
          case x: OWLObjectInverseOf =>
            x.getInverse.getNamedProperty.getIRI.getIRIString :: Inverse
        }
        List(
          graph(v"Z", name, v"Y") :-
            graph(v"X", RSA.CONGRUENT, v"Z") + graph(v"X", name, v"Y"),
          graph(v"Y", name, v"Z") :-
            graph(v"X", RSA.CONGRUENT, v"Z") + graph(v"Y", name, v"X")
        )
      })
    conceptSub :: properties ::: roleSub
  }

  /** Type wrapper representing a dependency graph for the ontology. */
  type DependencyGraph = Graph[Resource, DiEdge]

  /** Compute the RSA dependency graph for a set of axioms
    *
    * After calling this at least once, the materialisation M_{RSA} is
    * available in RDFox in the named graph [[Ontology.RSACheck]].
    * From this, the dependency graph can be derived by looking at the
    * instances of the predicate `E`.
    *
    * Furthermore, the [[Ontology.RBoxReasoning]] named graph is
    * populated with all relevant rules to reason over the RBox of the
    * ontology.
    *
    * @return a map between the newly introduced constants and the
    * corresponding input axioms.
    *
    * TODO: use RDFox DSL
    */
  lazy val dependencyGraph: (DependencyGraph, Map[String, OWLAxiom]) = {
    /* Keep track of the mapping between nodes and axioms */
    var nodemap = Map.empty[String, OWLAxiom]
    /* Computation of the dependency graph is confined to the
     * [[Ontology.RSACheck]] named graph.
     */
    val tt = TupleTableName.create(Ontology.RSACheck.getIRI)

    /** Custom converter to generate dependency graph.
      *
      * @note this corresponds to Definition 3 in the RSA paper.
      */
    object RSACheckConverter extends RDFoxConverter {
      import org.semanticweb.owlapi.model.{
        OWLClassExpression,
        OWLObjectSomeValuesFrom,
        OWLDataSomeValuesFrom
      }

      override val graph = tt
      override def convert(
          expr: OWLClassExpression,
          term: Term,
          unsafe: List[OWLObjectPropertyExpression],
          skolem: SkolemStrategy,
          suffix: RSASuffix
      )(implicit fresh: DataFactory): Shards =
        (expr, skolem) match {

          case (e: OWLObjectSomeValuesFrom, c: Constant) => {
            nodemap.update(c.iri.getIRI, c.axiom)
            val (res, ext) =
              super.convert(e, term, unsafe, skolem, suffix)(fresh)
            if (unsafe contains e.getProperty)
              (RSA.PE(tt)(term, c.iri) :: RSA.U(tt)(c.iri) :: res, ext)
            else
              (RSA.PE(tt)(term, c.iri) :: res, ext)
          }

          //case (e: OWLDataSomeValuesFrom, c: Constant) => {
          //  nodemap.update(c.iri.getIRI, c.axiom)
          //  val (res, ext) =
          //    super.convert(e, term, unsafe, skolem, suffix)(fresh)
          //  if (unsafe contains e.getProperty)
          //    (RSA.PE(tt)(term, c.iri) :: RSA.U(tt)(c.iri) :: res, ext)
          //  else
          //    (RSA.PE(tt)(term, c.iri) :: res, ext)
          //}

          case _ => super.convert(expr, term, unsafe, skolem, suffix)(fresh)
        }
    }

    /* Ontology convertion into LP rules */
    val result = axioms.map(a =>
      RSACheckConverter.convert(a, v"X", unsafe, new Constant(a), Empty)
    )
    val datalog = result.unzip
    val facts = datalog._1.flatten
    var rules = datalog._2.flatten

    /* Open connection with RDFox */
    val (server, data) = RDFoxUtil.openConnection(Ontology.DataStore)
    /* Upload data from data files */
    RDFoxUtil.addData(data, Ontology.RSACheck, datafiles: _*)
    /* Top/equality axiomatization */
    RDFoxUtil.updateData(data, 
      s"""
      INSERT { 
        GRAPH ${Ontology.RSACheck} { ?X a ${IRI.THING} }
      } WHERE {
        GRAPH ${Ontology.RSACheck} { ?X ?Y ?Z }
      }
      """
    )
    RDFoxUtil.updateData(data, 
      s"""
      INSERT { 
        GRAPH ${Ontology.RSACheck} { ?Z a ${IRI.THING} }
      } WHERE {
        GRAPH ${Ontology.RSACheck} { ?X ?Y ?Z }.
        FILTER( ?Y != a )
      }
      """
    )
    RDFoxUtil.addRules(data,
      topAxioms(Ontology.RSACheck) ++ equalityAxioms(Ontology.RSACheck)
    )
    /* Introduce `rsacomb:Named` concept */
    /* From data */
    RDFoxUtil.updateData(data, 
      s"""
      INSERT { 
        GRAPH ${Ontology.RSACheck} { ?X a ${RSA.NAMED} }
      } WHERE {
        GRAPH ${Ontology.RSACheck} { ?X a ${IRI.THING} }
      }
      """
    )
    /* From ontology */
    val named = individuals.map(RSA.Named(Ontology.RSACheck)(_))
    RDFoxUtil.addFacts(data, Ontology.RSACheck, named)
    /* Add rule to build dependency graph */
    rules = Rule.create(
      RSA.E(tt)(v"X", v"Y"),
      RSA.PE(tt)(v"X", v"Y"),
      RSA.U(tt)(v"X"),
      RSA.U(tt)(v"Y")
    ) :: rules
    /* Add logic program */
    RDFoxUtil.addFacts(data, Ontology.RSACheck, facts)
    RDFoxUtil.addRules(data, rules)

    /* Build the dependency graph */
    val query = s"SELECT ?X ?Y WHERE { graph ${Ontology.RSACheck} { ?X ${RSA("E")} ?Y } }"
    val answers = RDFoxUtil.submitQuery(data, query, RSA.Prefixes).getOrElse(RDFoxUtil.QueryAnswers())
    var edges: Seq[DiEdge[Resource]] =
      answers.collect { case (_, Seq(n1, n2)) => n1 ~> n2 }
    val graph = Graph(edges: _*)

    /* Setup RBox reasoner */
    RDFoxUtil.addAxioms(data, Ontology.RBoxProxy, Ontology.RBoxReasoning, axioms)
    /* Add reflexive `subObjectProperty`s */
    val refls = objroles.map(r => Ontology.factory.getOWLSubObjectPropertyOfAxiom(r, r))
    RDFoxUtil.addAxioms(data, Ontology.RBoxProxy, Ontology.RBoxReasoning, refls)
    /* Add RBox reasoning rules */
    RDFoxUtil.addRules(data, RSA.RBoxReasoning(Ontology.RBoxReasoning))

    /* Close connection to RDFox */
    RDFoxUtil.closeConnection(server, data)

    (graph, nodemap)
  }

  /** Check whether the ontology is RSA
    *
    * The following checks are performed:
    * 1) The ontology is Horn-ALCHOIQ
    * 1) The dependency graph is an oriented forest
    * 2) The ontology is equality safe.
    * 
    * @note at the moment this assumes that the ontology is normalized.
    *
    * @see Def.3 in RSA paper for a formal definition of these concepts.
    */
  lazy val isRSA: Boolean = {
    this.isHornALCHOIQ && {
      val (graph,nodemap) = dependencyGraph
      val (server,data) = RDFoxUtil.openConnection(Ontology.DataStore)
      /* The dependecy graph is an oriented forest, i.e., is
       * an *acyclic* graph with max in-degree 1.
       */
      val check1 = graph.isAcyclic && graph.nodes.forall(_.inDegree <= 1)
      /* Equality safety: condition 1 */
      val check2 = RDFoxUtil.submitQuery(data, s"""
        ASK {
          graph ${Ontology.RSACheck} { ?w ${RSA.CONGRUENT} ?t } .
          filter ( ?w != ?t ) .
          graph ${Ontology.RSACheck} { ?t ?r [ a ${RSA.U} ] } .
          graph ${Ontology.RBoxReasoning} {
            ?r rdfs:subPropertyOf [ owl:inverseOf ?s ] .
            ?x rdf:type owl:Restriction ;
               owl:onProperty ?s ;
               owl:maxQualifiedCardinality "1"^^xsd:nonNegativeInteger ;
               owl:onClass ?b .
            ?a rdfs:subClassOf ?b .
          } .
        }
      """).get
      /* Equality safety: condition 2 */
      val check3 = RDFoxUtil.submitQuery(data, s"""
        ASK {
          graph ${Ontology.RSACheck} { 
            ?u ?s ?a ; a ${RSA.U} .
            ?a ?r ?u ; a ${RSA.NI} .
          } .
          graph ${Ontology.RBoxReasoning} {
            ?r rdfs:subPropertyOf ?r1 .
            ?r1 ${RSA("subPropertyOfTrans")} ?t .
            ?t owl:inverseOf ?ti .
            ?s rdfs:subPropertyOf ?s1 .
            ?s1 ${RSA("subPropertyOfTrans")} ?ti .
          }
        }
      """).get
      check1 && check2.isEmpty && check3.isEmpty
    }
  }

  /** Checks whether the ontology is Horn-ALCHOIQ.
    *
    * @note at the moment this assumes that the ontology is normalized.
    *
    * @see [[uk.ac.ox.cs.rsacomb.ontology.Ontology.normalize]]
    *
    * TODO: make the function independed of normalization, or
    * alternatively force normalization.
    */
  lazy val isHornALCHOIQ: Boolean =
    axioms.forall(axiom => 
      axiom match {
        case a: OWLSubClassOfAxiom => {
          val sub = a.getSubClass.getNNF
          val sup = a.getSuperClass.getNNF
          (sub, sup) match {
            case (sub: OWLObjectAllValuesFrom, _) => false
            case (sub: OWLDataAllValuesFrom, _)   => false
            case (_, sup: OWLDataAllValuesFrom)   => false
            case (sub: OWLObjectMinCardinality, _) if sub.getCardinality >= 2 =>
              false
            case (sub: OWLDataMinCardinality, _) if sub.getCardinality >= 2 =>
              false
            case (_, sup: OWLObjectMinCardinality) if sup.getCardinality >= 2 =>
              false
            case (_, sup: OWLDataMinCardinality) if sup.getCardinality >= 2 =>
              false
            case (sub: OWLObjectMaxCardinality, _) => false
            case (sub: OWLDataMaxCardinality, _)   => false
            case (_, sup: OWLObjectMaxCardinality) if sup.getCardinality >= 2 =>
              false
            case (_, sup: OWLDataMaxCardinality) if sup.getCardinality >= 1 =>
              false
            case (_, sup: OWLObjectOneOf) if sup.getIndividuals.length > 2 =>
              false
            case (sub: OWLObjectHasSelf, _) => false
            case (_, sup: OWLObjectHasSelf) => false
            case (_, sup: OWLObjectUnionOf) => false
            case _                          => true
          }
        }
        case a: OWLTransitiveObjectPropertyAxiom => false
        case a: OWLReflexiveObjectPropertyAxiom  => false
        case a: OWLSubPropertyChainOfAxiom       => false
        case a: OWLAsymmetricObjectPropertyAxiom => false
        case a                                   => true
      }
  )

  /** Normalize the ontology according to the given normalizer
    *
    * @param normalizer the normalization technique to be used.
    * @return a new normalized [[Ontology]].
    */
  def normalize(normalizer: Normalizer): Ontology =
    new Ontology(
      origin,
      axioms flatMap normalizer.normalize,
      datafiles
    )

  /** Approximate the ontology according to the given approximation
    * technique.
    *
    * @param approximation the approximation to be used on the ontology.
    * @return the result of the approximation.
    */
  def approximate[T](approximation: Approximation[T]): T =
    approximation approximate this
}