RDFClosure.OWLRLExtras

Package RDFClosure :: Module OWLRLExtras |
[hide private]
[frames] | no frames]

# Source Code for Module RDFClosure.OWLRLExtras

      1  #!/d/Bin/Python/python.exe
      2  # -*- coding: utf-8 -*-
      3  #
      4  """
      5
      6  Extension to OWL 2 RL, ie, some additional rules added to the system from OWL 2 Full. It is implemented through
      7  the L{OWLRL_Extension} class, whose reference has to be passed to the relevant semantic class (ie, either the OWL 2 RL or the combined
      8  closure class) as an 'extension'.
      9
     10  The added rules and features are:
     11
     12   - self restriction
     13   - owl:rational datatype
     14   - datatype restrictions via facets
     15
     16  In more details, the rules that are added:
     17
     18   1. self restriction 1: C{?z owl:hasSelf ?x. ?x owl:onProperty ?p. ?y rdf:type ?z. => ?y ?p ?y.}
     19   2. self restriction 2: C{?z owl:hasSelf ?x. ?x owl:onProperty ?p. ?y ?p ?y. => ?y rdf:type ?z. }
     20
     21  @requires: U{RDFLib<https://github.com/RDFLib/rdflib>}, 4.0.0 and higher
     22  @license: This software is available for use under the U{W3C Software License<http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231>}
     23  @organization: U{World Wide Web Consortium<http://www.w3.org>}
     24  @author: U{Ivan Herman<a href="http://www.w3.org/People/Ivan/">}
     25
     26  """
     27
     28  __author__  = 'Ivan Herman'
     29  __contact__ = 'Ivan Herman, ivan@w3.org'
     30  __license__ = u'W3C® SOFTWARE NOTICE AND LICENSE, http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231'
     31
     32  import rdflib
     33  # noinspection PyPep8Naming
     34  from rdflib.namespace   import XSD as ns_xsd
     35
     36  from .RDFS      import  Property
     37  # noinspection PyPep8Naming
     38  from .RDFS      import type as rdfType
     39  from .RDFS      import Resource, Class, subClassOf, subPropertyOf, domain
     40  from .RDFS      import Datatype
     41
     42  from fractions import Fraction as Rational
     43
     44  from .DatatypeHandling import AltXSDToPYTHON
     45
     46  from .OWL import *
     47  # noinspection PyPep8Naming
     48  from .OWL import OWLNS as ns_owl
     49  from .CombinedClosure import RDFS_OWLRL_Semantics
     50  from .OWLRL import OWLRL_Annotation_properties
     51
     52  from .XsdDatatypes import OWL_RL_Datatypes, OWL_Datatype_Subsumptions
     53
     54  from RestrictedDatatype import extract_faceted_datatypes
     55
     56  ###########################################################################################################################
     57  # Rational datatype
     58
     59  # noinspection PyPep8Naming
     60 -def _strToRational(v):
     61          """Converting a string to a rational.
     62
     63          According to the OWL spec: numerator must be an integer, denominator a positive integer (ie, xsd['integer'] type), and the denominator
     64          should not have a '+' sign.
     65
     66          @param v: the literal string defined as boolean
     67          @return corresponding Rational value
     68          @rtype: Rational
     69          @raise ValueError: invalid rational string literal
     70          """
     71          try :
     72                  r = v.split('/')
     73                  if len(r) == 2:
     74                          n_str = r[0]
     75                          d_str = r[1]
     76                  else:
     77                          n_str = r[0]
     78                          d_str = "1"
     79                  if d_str.strip()[0] == '+':
     80                          raise ValueError("Invalid Rational literal value %s" % v)
     81                  else :
     82                          return Rational(AltXSDToPYTHON[ns_xsd["integer"]](n_str), AltXSDToPYTHON[ns_xsd["positiveInteger"]](d_str))
     83          except:
     84                  raise ValueError("Invalid Rational literal value %s" % v)
     85
     86  ###########################################################################################################################
     87
     88
     89  # noinspection PyPep8Naming,PyBroadException
     90 -class OWLRL_Extension(RDFS_OWLRL_Semantics) :
     91          """
     92          Additional rules to OWL 2 RL. The initialization method also adds the C{owl:rational} datatype to the set of allowed
     93          datatypes with the L{_strToRational} function as a conversion between the literal form and a Rational. The C{xsd:decimal} datatype
     94          is also set to be a subclass of C{owl:rational}. Furthermore, the restricted datatypes are extracted from the graph
     95          using a L{separate method in a different module<RestrictedDatatype.extract_faceted_datatypes>}, and all those datatypes are also
     96          added to the set of allowed datatypes. In the case of the restricted datatypes and extra subsumption relationship is set up
     97          between the restricted and the base datatypes.
     98
     99          @cvar extra_axioms: additional axioms that have to be added to the deductive closure (in case the axiomatic triples are required)
    100          @ivar restricted_datatypes : list of the datatype restriction
    101          @type restricted_datatypes : array of L{restricted datatype<RestrictedDatatype.RestrictedDatatype>} instances
    102          """
    103          extra_axioms = [
    104                  (hasSelf, rdfType, Property),
    105                  (hasSelf, domain, Property),
    106          ]
    107 -        def __init__(self, graph, axioms, daxioms, rdfs=False):
    108                  """
    109                  @param graph: the RDF graph to be extended
    110                  @type graph: rdflib.Graph
    111                  @param axioms: whether (non-datatype) axiomatic triples should be added or not
    112                  @type axioms: Boolean
    113                  @param daxioms: whether datatype axiomatic triples should be added or not
    114                  @type daxioms: Boolean
    115                  @param rdfs: whether RDFS extension is done
    116                  @type rdfs: boolean
    117                  """
    118                  RDFS_OWLRL_Semantics.__init__(self, graph, axioms, daxioms, rdfs)
    119                  self.rdfs = rdfs
    120                  self.add_new_datatype(ns_owl["rational"], _strToRational, OWL_RL_Datatypes,
    121                                                        subsumption_dict = OWL_Datatype_Subsumptions,
    122                                                        subsumption_key  = ns_xsd["decimal"],
    123                                                        subsumption_list = [ns_owl["rational"]])
    124
    125                  self.restricted_datatypes = extract_faceted_datatypes(self, graph)
    126                  for dt in self.restricted_datatypes:
    127                          self.add_new_datatype(dt.datatype, dt.toPython, OWL_RL_Datatypes,
    128                                                                    subsumption_dict = OWL_Datatype_Subsumptions,
    129                                                                    subsumption_key  = dt.datatype,
    130                                                                    subsumption_list = [dt.base_type])
    131
    132          # noinspection PyShadowingNames
    133 -        def _subsume_restricted_datatypes(self):
    134                  """
    135                  A one-time-rule: all the literals are checked whether they are (a) of type restricted by a
    136                  faceted (restricted) datatype and (b) whether
    137                  the corresponding value abides to the restrictions. If true, then the literal gets an extra
    138                  tag as being of type of the restricted datatype, too.
    139                  """
    140                  for rt in self.restricted_datatypes:
    141                          # This is a recorded restriction. The base type is:
    142                          base_type = rt.base_type
    143                          # Look through all the literals; more precisely, through the
    144                          # proxy bnodes:
    145                          for bn in self.literal_proxies.bnode_to_lit:
    146                                  # check if the type of that proxy matches. Note that this also takes
    147                                  # into account the subsumption datatypes, that have been taken
    148                                  # care of by the 'regular' OWL RL process
    149                                  if (bn, rdfType, base_type) in self.graph:
    150                                          # yep, that is a good candidate!
    151                                          lt = self.literal_proxies.bnode_to_lit[bn]
    152                                          try :
    153                                                  # the conversion or the check may go wrong and raise an exception; then simply move on
    154                                                  value = lt.lit.toPython()
    155                                                  if rt.checkValue(value):
    156                                                          # yep, this is also of type 'rt'
    157                                                          self.store_triple((bn, rdfType, rt.datatype))
    158                                          except:
    159                                                  continue
    160
    161 -        def restriction_typing_check(self, v, t):
    162                  """Helping method to check whether a type statement is in line with a possible
    163                  restriction. This method is invoked by rule "cls-avf" before setting a type
    164                  on an allValuesFrom restriction.
    165
    166                  The method is a placeholder at this level. It is typically implemented by subclasses for
    167                  extra checks, eg, for datatype facet checks.
    168                  @param v: the resource that is to be 'typed'
    169                  @param t: the targeted type (ie, Class)
    170                  @return: boolean
    171                  """
    172                  # Look through the restricted datatypes to see if 't' corresponds to one of those...
    173                  # There are a bunch of possible exceptions here with datatypes, but they can all
    174                  # be ignored...
    175                  try :
    176                          for rt in self.restricted_datatypes:
    177                                  if rt.datatype == t:
    178                                          # bingo
    179                                          if v in self.literal_proxies.bnode_to_lit:
    180                                                  return rt.checkValue(self.literal_proxies.bnode_to_lit[v].lit.toPython())
    181                                          else :
    182                                                  return True
    183                          # if we got here, no restriction applies
    184                          return True
    185                  except :
    186                          return True
    187
    188 -        def one_time_rules(self):
    189                  """
    190                  This method is invoked only once at the beginning, and prior of, the forward chaining process.
    191
    192                  At present, only the L{subsumption} of restricted datatypes<_subsume_restricted_datatypes>} is performed.
    193                  """
    194                  RDFS_OWLRL_Semantics.one_time_rules(self)
    195                  # it is important to flush the triples at this point, because
    196                  # the handling of the restriction datatypes rely on the datatype
    197                  # subsumption triples added by the superclass
    198                  self.flush_stored_triples()
    199                  self._subsume_restricted_datatypes()
    200
    201 -        def add_axioms(self):
    202                  """
    203                  Add the L{extra axioms<OWLRL_Extension.extra_axioms>}, related to the self restrictions.
    204                  """
    205                  RDFS_OWLRL_Semantics.add_axioms(self)
    206                  for t in self.extra_axioms : self.graph.add(t)
    207
    208 -        def rules(self, t, cycle_num):
    209                  """
    210                  Go through the additional rules implemented by this module.
    211                  @param t: a triple (in the form of a tuple)
    212                  @param cycle_num: which cycle are we in, starting with 1. This value is forwarded to all local rules; it is also used
    213                  locally to collect the bnodes in the graph.
    214                  """
    215                  RDFS_OWLRL_Semantics.rules(self, t, cycle_num)
    216                  z, q, x = t
    217                  if q == hasSelf:
    218                          for p in self.graph.objects(z, onProperty):
    219                                  for y in self.graph.subjects(rdfType, z):
    220                                          self.store_triple((y, p, y))
    221                                  for y1, y2 in self.graph.subject_objects(p):
    222                                          if y1 == y2 :
    223                                                  self.store_triple((y1, rdfType, z))
    224
    225
    226  # noinspection PyPep8Naming
    227 -class OWLRL_Extension_Trimming(OWLRL_Extension):
    228          """
    229          This Class adds only one feature to L{OWLRL_Extension}: to initialize with a trimming flag set to C{True} by default.
    230
    231          This is pretty experimental and probably contentious: this class I{removes} a number of triples from the Graph at the very end of the processing steps.
    232          These triples are either the by-products of the deductive closure calculation or are axiom like triples that are added following the rules of OWL 2 RL.
    233          While these triples I{are necessary} for the correct inference of really 'useful' triples, they may not be of interest for the application
    234          for the end result. The triples that are removed are of the form (following a SPARQL-like notation):
    235
    236           - C{?x owl:sameAs ?x}, C{?x rdfs:subClassOf ?x}, C{?x rdfs:subPropertyOf ?x}, C{?x owl:equivalentClass ?x} type triples
    237           - C{?x rdfs:subClassOf rdf:Resource}, C{?x rdfs:subClassOf owl:Thing}, C{?x rdf:type rdf:Resource}, C{owl:Nothing rdfs:subClassOf ?x} type triples
    238           - For a datatype that does I{not} appear explicitly in a type assignments (ie, in a C{?x rdf:type dt}) the corresponding C{dt rdf:type owl:Datatype} and C{dt rdf:type owl:DataRange} triples, as well as the disjointness statements with other datatypes
    239           - annotation property axioms
    240           - a number of axiomatic triples on C{owl:Thing}, C{owl:Nothing} and C{rdf:Resource} (eg, C{owl:Nothing rdf:type owl:Class}, C{owl:Thing owl:equivalentClass rdf:Resource}, etc.)
    241
    242          Trimming is the only feature of this class, done in the L{post_process} step. If users extend L{OWLRL_Extension}, this class can be safely mixed in via multiple
    243          inheritance.
    244          """
    245 -        def __init__(self, graph, axioms, daxioms, rdfs=False):
    246                  """
    247                  @param graph: the RDF graph to be extended
    248                  @type graph: rdflib.Graph
    249                  @param axioms: whether (non-datatype) axiomatic triples should be added or not
    250                  @type axioms: Boolean
    251                  @param daxioms: whether datatype axiomatic triples should be added or not
    252                  @type daxioms: Boolean
    253                  @param rdfs: whether RDFS extension is done
    254                  @type rdfs: boolean
    255                  """
    256                  OWLRL_Extension.__init__(self, graph, axioms, daxioms, rdfs=False)
    257
    258 -        def post_process(self) :
    259                  """
    260                  Do some post-processing step performing the trimming of the graph. See the L{class description<OWLRL_Extension_Trimming>} for further details.
    261
    262                  """
    263                  OWLRL_Extension.post_process(self)
    264                  self.flush_stored_triples()
    265
    266                  to_be_removed = set()
    267                  for t in self.graph:
    268                          s, p, o = t
    269                          if s == o :
    270                                  if p == sameAs or p == equivalentClass or p == subClassOf or p == subPropertyOf:
    271                                          to_be_removed.add(t)
    272                          if (p == subClassOf and (o == Thing or o == Resource)) or (p == rdfType and o == Resource) or (s == Nothing and p == subClassOf):
    273                                  to_be_removed.add(t)
    274
    275                  for dt in OWL_RL_Datatypes :
    276                          # see if this datatype appears explicitly in the graph as the type of a symbol
    277                          if len([s for s in self.graph.subjects(rdfType, dt)]) == 0:
    278                                  to_be_removed.add((dt, rdfType, Datatype))
    279                                  to_be_removed.add((dt, rdfType, DataRange))
    280
    281                                  for t in self.graph.triples((dt, disjointWith, None)):
    282                                          to_be_removed.add(t)
    283                                  for t in self.graph.triples((None, disjointWith, dt)):
    284                                          to_be_removed.add(t)
    285
    286                  for an in OWLRL_Annotation_properties:
    287                          self.graph.remove((an, rdfType, AnnotationProperty))
    288
    289                  to_be_removed.add((Nothing, rdfType, OWLClass))
    290                  to_be_removed.add((Nothing, rdfType, Class))
    291                  to_be_removed.add((Thing, rdfType, OWLClass))
    292                  to_be_removed.add((Thing, rdfType, Class))
    293                  to_be_removed.add((Thing, equivalentClass, Resource))
    294                  to_be_removed.add((Resource, equivalentClass, Thing))
    295                  to_be_removed.add((OWLClass, equivalentClass, Class))
    296                  to_be_removed.add((OWLClass, subClassOf, Class))
    297                  to_be_removed.add((Class, equivalentClass, OWLClass))
    298                  to_be_removed.add((Class, subClassOf, OWLClass))
    299                  to_be_removed.add((Datatype, subClassOf, DataRange))
    300                  to_be_removed.add((Datatype, equivalentClass, DataRange))
    301                  to_be_removed.add((DataRange, subClassOf, Datatype))
    302                  to_be_removed.add((DataRange, equivalentClass, Datatype))
    303
    304                  for t in to_be_removed:
    305                          self.graph.remove(t)
    306