© Lamy Jean-Baptiste 2021
L. Jean-BaptisteOntologies with Pythonhttps://doi.org/10.1007/978-1-4842-6552-9_6

6. Constructs, restrictions, and class properties

Lamy Jean-Baptiste1  
(1)
Université Sorbonne Paris Nord, LIMICS, Sorbonne Université INSERM, UMR 1142, Bobigny, France
 

In this chapter, we will see how to handle all the OWL constructors in Python with Owlready. We will also see the different “shortcuts” that Owlready offers to facilitate the use of constructors and in particular restrictions.

6.1 Creating constructs

OWL constructors allow you to define logical constructions from classes, individuals, and properties (see sections 3.4.6 and 3.4.7).

In Owlready, restrictions are created with the syntax “property.restriction_type(value)”, using the same keywords for restriction types as in Protected:
  • property.some(Class) for an existential restriction

  • property.only(Class) for a universal restriction

  • property.value(individual or data) for a value restriction (also called role-filler)

  • property.exactly(cardinality, Class) for an exact cardinality restriction

  • property.min(cardinality, Class) and property.max(cardinality, Class) for minimal and maximal cardinality restrictions, respectively

The logical operators NOT (complement), AND (intersection), and OR (union) are obtained as follows:
  • Not(Class)

  • And([Class1, Class2,...]) or Class1 & Class2 & ...

  • Or([Class1, Class2,...]) or Class1 | Class2 | ...

A set of individuals is obtained as follows:
  • OneOf([individual1, individual2,...])

The inverse of a property is obtained as follows:
  • Inverse(Property)

A property chain (also called composition) is obtained as follows:
  • PropertyChain([Property1, Property2,...])

In the previous definitions, the classes can be entities, but also other constructors. Constructors can therefore be nested within each other.

Constructors can be used in the is_a and equivalent_to attributes of classes. For example, we can create the Coccus class (which groups round bacteria; see 3.3), entirely in Python, as follows:
>>> from owlready2 import *
>>> onto = get_ontology("bacteria.owl").load()
>>> with onto:
...     class Coccus(onto.Bacterium):
...         equivalent_to = [
...         onto.Bacterium & onto.has_shape.some(onto.Round)
...              & onto.has_shape.only(onto.Round)
...         ]
Similarly, we can define the Pseudomonas class with four restrictions:
>>> with onto:
...     class Pseudomonas(onto.Bacterium):
...         is_a = [
...           onto.has_shape.some(onto.Rod),
...           onto.has_shape.only(onto.Rod),
...           onto.has_grouping.some(onto.Isolated |↲onto.InPair),
...           onto.gram_positive.value(False)
...         ]
Owlready will automatically complete the is_a list of the class created with the class (or classes) declared as the parent class in Python (here, the Bacterium class). We can verify that as follows:
>>> Pseudomonas.is_a
[bacteria.Bacterium,
 bacteria.has_shape.some(bacteria.Rod),
 bacteria.has_shape.only(bacteria.Rod),
 bacteria.has_grouping.some(bacteria.Isolated | bacteria.InPair),
 bacteria.gram_positive.value(False)]

6.2 Accessing construct parameters

The following attributes provide access to the information contained in the main constructors (for the full list of constructors and their attributes, refer to C.6 in the reference manual, Appendix C):
  • Logical operators AND and OR (intersection and union, class And and Or, respectively):
    • Attribute Classes: The list of classes to which the intersection or union relates

  • Restrictions (class Restriction):
    • Attribute property: The property to which the restriction relates

    • Attribute type: The type of restriction (a value chosen among the constants SOME, ONLY, VALUE, MAX, MIN, and EXACTLY)

    • Attribute value: The value to which the restriction relates (a class for types SOME, ONLY, MAX, MIN, and EXACTLY, an individual or a value for type VALUE)

    • Attribute cardinality : The number of relationships concerned (only available for MAX, MIN, and EXACTLY restrictions)

For example, if we take the class Streptococcus and its equivalent definition, we can analyze it as follows in Python:
>>> onto.Streptococcus.equivalent_to[0]
bacteria.Bacterium
& bacteria.has_shape.some(bacteria.Round)
& bacteria.has_shape.only(bacteria.Round)
& bacteria.has_grouping.some(bacteria.InSmallChain)
& bacteria.has_grouping.only(Not(bacteria.Isolated))
& bacteria.gram_positive.value(True)
>>> onto.Streptococcus.equivalent_to[0].Classes[1]
bacteria.has_shape.some(bacteria.Round)
>>> onto.Streptococcus.equivalent_to[0].Classes[1].property
bacteria.has_shape
>>> onto.Streptococcus.equivalent_to[0].Classes[1].type == SOME
True
>>> onto.Streptococcus.equivalent_to[0].Classes[1].value
bacteria.Round
If we do not know the category of constructors used, the isinstance() Python function allows us to test it, for example:
>>> constructor = onto.Streptococcus.equivalent_to[0]
>>> if   isinstance(constructor, And):
...     print("And", constructor.Classes)
... elif isinstance(constructor, Or):
...     print("Or", constructor.Classes)
... elif isinstance(constructor, Restriction):
...     print("Restriction", constructor.property,↲ constructor.type, constructor.value)
And [bacteria.Bacterium,
     bacteria.has_shape.some(bacteria.Round),
     bacteria.has_shape.only(bacteria.Round),
     bacteria.has_grouping.some(bacteria.InSmallChain),
     bacteria.has_grouping.only(Not(bacteria.Isolated)),
     bacteria.gram_positive.value(True)]
In addition, the attributes listed here are all modifiable: Owlready automatically updates the quadstore when the attributes are modified. For example, we can change the restriction on the Gram status of the Streptococcus class as follows:
>>> onto.Streptococcus.equivalent_to[0].Classes[-1].value = False

6.3 Restrictions as class properties

Owlready provides access to all of the OWL constructors, as we saw in the two previous sections. However, the creation of constructors or the access to the information contained in them is often complex and tedious. This is why Owlready also offers several “shortcuts” to facilitate the use of constructors. We have already seen an example of a shortcut for accessing existential restrictions as if they were class properties in 4.5.4.

Indeed, restrictions are often used to represent relationships between classes. The relationships between two classes are more complex than the relationships between two individuals: between two individuals, either the relationship exists (which corresponds to a triple in the quadstore), or it does not exist. On the contrary, a class brings together several individuals, which leads to several scenarios:
  • All the individuals of the first class are in relation with at least one individual of the second class: it is the existential restriction (“some” in Protégé).

  • All individuals of the first class are in relation with only individuals of the second class: this is the universal restriction (“only” in Protégé).

  • Each individual of the first class is in relation to each individual of the second class: OWL does not directly allow this type of relationship between classes to be created. However, it is possible to obtain an equivalent result by reifying the property, that is to say, by transforming it into a class associated with two properties.

Owlready allows translating the relations between two classes into class properties, and vice versa. This allows you to easily create or read constructors corresponding to the following forms:
  • (Property some Class)

  • (Property value individual)

  • (Property only (Class or ... ))

  • (Property only ({ individual,... }))

  • (Property only (Class or ... or { individual,... }))

The special annotation “class_property_typeindicates which type of restriction is used for a given property. The possible values are
  • ["some"]: When this value is used, class properties correspond to existential restrictions (“some”). This is the default value for a property if the annotation “class_property_type” is not specified.

  • ["only"]: When this value is used, class properties correspond to universal restrictions (“only”).

  • ["some, only"]: When this value is used, class properties correspond to both existential and universal restrictions.

  • ["relation"]: This value leads to creating direct relationships between classes, using an RDF triple. Please note these direct relationships are not valid in OWL, and they will not be taken into account by the reasoners. However, many RDF graph databases use direct relationships between classes; this value makes it possible to read or produce such databases. These RDF databases are devoid of formal semantics and therefore are not OWL ontologies.

We can use these class properties to more easily define classes of bacteria in the ontology of bacteria from Chapter 3. We will start by modifying three properties, gram_positive, has_shape, and has_grouping, in order to specify the type of class property associated with each. We have chosen to keep the same modeling choices as in Chapter 3: we will use existential restrictions for gram_positive and has_grouping and both existential and universal restrictions for has_shape:
>>> with onto:
...     onto.gram_positive.class_property_type = ["some"]
...     onto.has_shape.class_property_type = ["some, only"]
...     onto.has_grouping.class_property_type = ["some"]
Then, we can create a new Pseudomonas class, called Pseudomonas2 , in a simpler way than before (see 6.1), by simply defining the values of the class properties:
>>> with onto:
...     class Pseudomonas2(onto.Bacterium): pass
...     Pseudomonas2.gram_positive = False
...     Pseudomonas2.has_shape = onto.Rod
...     Pseudomonas2.has_grouping = [onto.Isolated | onto.InPair]
The following syntax is equivalent, but simpler. It consists in defining the class properties in the body of the class (see 2.9 for the syntax of the class statement in Python and the use of class properties):
>>> with onto:
...     class Pseudomonas3(onto.Bacterium):
...         gram_positive = False
...         has_shape = onto.Rod
...         has_grouping = [onto.Isolated | onto.InPair]
We can then verify that the restrictions have been created as expected:
>>> Pseudomonas3.is_a
[bacteria.Bacterium,
 bacteria.has_shape.some(bacteria.Rod),
 bacteria.has_shape.only(bacteria.Rod),
 bacteria.has_grouping.some(bacteria.Isolated | bacteria.InPair),
 bacteria.gram_positive.value(False)]

The Pseudomonas3 class obtained is identical to that defined here using the constructor (see 6.1).

We can also create new classes of bacteria using class properties:
>>> with onto:
...     class Listeria(onto.Bacterium):
...         gram_positive = True
...         has_shape     = onto.Rod
...         has_grouping  = [onto.InLongChain]
>>> Listeria.is_a
[bacteria.Bacterium,
 bacteria.has_shape.some(bacteria.Rod),
 bacteria.has_shape.only(bacteria.Rod),
 bacteria.has_grouping.some(bacteria.InLongChain),
 bacteria.gram_positive.value(True)]
Classes and their class properties can be changed; Owlready will automatically update the restrictions in the quadstore. In the following example, we add a grouping to the Listeria class:
>>> Listeria.has_grouping.append(onto.Isolated)
>>> Listeria.is_a
[bacteria.Bacterium,
 bacteria.has_shape.some(bacteria.Rod),
 bacteria.has_shape.only(bacteria.Rod),
 bacteria.has_grouping.some(bacteria.InLongChain),
 bacteria.has_grouping.some(bacteria.Isolated),
 bacteria.gram_positive.value(True)]
Class properties also work for “reading”, that is, to analyze the constructors of a class, even if they were not created via Owlready class properties. For example, if we create the Listeria2 class like this, using constructors:
>>> with onto:
...     class Listeria2(onto.Bacterium):
...         is_a = [onto.has_shape.some(onto.Rod),
...                 onto.has_shape.only(onto.Rod),
...                 onto.has_grouping.some(onto.InLongChain),
...                 onto.gram_positive.value(True)]
We can still use class properties to analyze constructors (or modify them):
>>> Listeria2.has_grouping
[bacteria.InLongChain]

This explains why we were able to use the class properties in Chapter 4 to access an external ontology.

The following table summarizes the types of class properties supported by Owlready and the constructors it generates. In this table, C, C1, and C2 are classes, i1 and i2 are individuals, and p is a property.

p.class_property_type

Translation of <<C.p = [C1, C2,..., i1, i2,...]>>

["some"]

C subclassof: p some C1

C subclassof: p some C2

...

C subclassof: p value i1

C subclassof: p value i2

...

["only"]

C subclassof: p only (C1 or C2... or {i1, i2...})

["some, only"]

C subclassof: p some C1

C subclassof: p some C2

...

C subclassof: p value i1

C subclassof: p value i2

...

C subclassof: p only (C1 or C2... or {i1, i2...})

["relation"]

Assert the following RDF triples:

(C, p, C1)

(C, p, C2)

...

(C, p, i1)

(C, p, i2)

...

6.4 Defined classes

Owlready also allows you to use class properties to create defined classes, with definitions of the following form:
  • Parent_class1 and Parent_class2 ...

and (Property some Class) ...

and (Property value individual) ...

and (Property only (Class ... or { individual,... }))

To do this, Owlready uses the special boolean annotation “defined_class” to indicate that a class is defined. If this annotation is True for a class, Owlready will generate a definition from the class properties instead of the restrictions seen in the previous section. The default value is False for this annotation.

The following example creates a new defined class of Bacterium:
>>> with onto:
...     class Corynebacterium(onto.Bacterium):
...         defined_class = True
...         gram_positive = False
...         has_shape = onto.Rod
...         has_grouping = [onto.InCluster]

Note the first line of the class body, “defined_class = True”, which indicates that it is a defined class.

We can verify that the definition has been created:
>>> Corynebacterium.equivalent_to
[bacteria.Bacterium
& bacteria.gram_positive.value(False)
& bacteria.has_shape.some(bacteria.Rod)
& bacteria.has_shape.only(bacteria.Rod)
& bacteria.has_grouping.some(bacteria.InCluster)]
On the other hand, Owlready did not create a simple restriction (that is to say, apart from those present in the definition):
>>> Corynebacterium.is_a
[bacteria.Bacterium]

As before, the class properties can be modified, and Owlready will update the definition automatically. Similarly, the properties of the class also allow access to the information present in the definition, even if it was not created with Owlready.

When creating the definition, Owlready combines the different values of the class properties into a single definition. Generally, if C, P1, P2, S1, S2, O1, and O2 are classes, s is a property that uses existential restrictions, o is a property that uses universal restrictions, and s1, s2, o1, and o2 are individuals, when we define:
C.is_a = [P1, P2,...]
C.s = [S1, S2,..., s1, s2,...]
C.o = [O1, O2,..., o1, o2,...]
Owlready will generate the following definition:
C equivalent_to P1 and P2...
            and (s some S1) and (s some S2)...
            and (s value s1) and (s value s2)...
            and (o only (O1 or O2... or {o1, o2...}))

6.5 Example: creating the ontology of bacteria in Python

The following program, given by way of illustration, makes it possible to recreate the ontology of bacteria of Chapter 3 from scratch, entirely in Python, using constructors. Creating an ontology in Python may seem more laborious than with Protégé, but it also has advantages: in particular, it is possible to copy and paste definitions, which allows you to quickly create similar classes.
# File create_onto.py
from owlready2 import *
onto = get_ontology("http://lesfleursdunormal.fr/static/↲
_downloads/bacteria.owl#")
with onto:
    class Shape(Thing): pass
    class Round(Shape): pass
    class Rod(Shape): pass
    AllDisjoint([Round, Rod])
    class Grouping(Thing): pass
    class Isolated(Grouping): pass
    class InPair(Grouping): pass
    class InCluster(Grouping): pass
    class InChain(Grouping): pass
    class InSmallChain(InChain): pass
    class InLongChain(InChain): pass
    AllDisjoint([Isolated, InPair, InCluster, InChain])
    AllDisjoint([InSmallChain, InLongChain])
    class Bacterium(Thing): pass
    AllDisjoint([Bacterium, Shape, Grouping])
    class gram_positive(Bacterium >> bool, FunctionalProperty):        pass
    class nb_colonies(Bacterium >> int, FunctionalProperty):        pass
    class has_shape(Bacterium >> Shape, FunctionalProperty):        pass
    class has_grouping(Bacterium >> Grouping): pass
    class is_shape_of(Shape >> Bacterium):
        inverse = has_shape
    class is_grouping_of(Grouping >> Bacterium):
        inverse = has_grouping
    class Pseudomonas(Bacterium):
        is_a = [ has_shape.some(Rod),
                 has_shape.only(Rod),
                 has_grouping.some(Isolated | InPair),
                 gram_positive.value(False) ]
    class Coccus(Bacterium):
        equivalent_to = [ Bacterium
                        & has_shape.some(Round)
                        & has_shape.only(Round) ]
    class Bacillus(Bacterium):
        equivalent_to = [ Bacterium
                        & has_shape.some(Rod)
                        & has_shape.only(Rod) ]
    class Staphylococcus(Coccus):
        equivalent_to = [ Bacterium
                        & has_shape.some(Round)
                        & has_shape.only(Round)
                        & has_grouping.some(InCluster)
                        & gram_positive.value(True) ]
    class Streptococcus(Coccus):
        equivalent_to = [ Bacterium
                        & has_shape.some(Round)
                        & has_shape.only(Round)
                        & has_grouping.some(InSmallChain)
                        & has_grouping.only( Not(Isolated) )
                        & gram_positive.value(True) ]
    unknown_bacterium = Bacterium(
        "unknown_bacterium",
        has_shape = Round(),
        has_grouping = [ InCluster("in_cluster1") ],
        gram_positive = True,
        nb_colonies = 6
    )
onto.save("bacteria.owl")

6.6 Example: populating an ontology with defined classes

In this example, we will continue the population of the ontology of bacteria. This time, we will populate the ontology with classes (as in 5.14.2), but using definitions with equivalence relations. We will reuse the same CSV file as before (called “population_classes.csv”):

../images/502592_1_En_6_Chapter/502592_1_En_6_Figa_HTML.jpg

We can populate the ontology in two ways: either by using class properties (which is the simplest option) or by using constructors (which is more complicated, but may be necessary if you want to create definitions more complex than those generated by Owlready).

6.6.1 Populating using class properties

The following program uses class properties to populate the ontology of bacteria with defined classes, from the data in the preceding CSV file:
# File population_defined_classes1.py
from owlready2 import *
import csv, types
onto = get_ontology("bacteria.owl").load()
onto.gram_positive.class_property_type = ["some"]
onto.has_shape.class_property_type = ["some", "only"]
onto.has_grouping.class_property_type = ["some"]
onto_classes = get_ontology("http://lesfleursdunormal.fr/↲static/_downloads/bacteria_defined_classes.owl")
onto_classes.imported_ontologies.append(onto)
f = open("population_classes.csv")
reader = csv.reader(f)
next(reader)
with onto_classes:
    for row in reader:
        id, parent, gram_positive, shape, grouping = row
        if parent: parent = onto[parent]
        else:      parent = Thing
        Class = types.new_class(id, (parent,))
        Class.defined_class = True
        if gram_positive:
            if gram_positive == "True": gram_positive = True
            else:                       gram_positive = False
            Class.gram_positive = gram_positive
        if shape:
            shape_class = onto[shape]
            Class.has_shape = shape_class
        if grouping:
            grouping_class = onto[grouping]
            Class.has_grouping.append(grouping_class)
onto_classes.save("bacteria_defined_classes.owl")
This program is very similar to the one we saw in the previous chapter to create undefined classes (see 5.14.2). It only differs in two points:
  • At the start of the program, for each of the three properties, we indicated the type of the associated class properties.

  • When we create a class, we indicate that it is a defined class (with “Class.defined_class = True”).

To verify the proper functioning of the program after its execution, we can open the ontology created in Python in the Protégé editor, as shown in the following screenshot:

../images/502592_1_En_6_Chapter/502592_1_En_6_Figb_HTML.jpg

6.6.2 Populating using constructs

The following program also populates the ontology of bacteria with defined classes, from data in the CSV file. Unlike the previous one, it does not use class properties but directly creates constructors. This second program is more complex than the previous one, which shows the interest of Owlready’s class properties!
# File population_defined_classes2.py
from owlready2 import *
import csv, types
onto = get_ontology("bacteria.owl").load()
onto_classes = get_ontology("http://lesfleursdunormal.fr/↲static/
_downloads/bacteria_defined_classes.owl")
onto_classes.imported_ontologies.append(onto)
f = open("population_classes.csv")
reader = csv.reader(f)
next(reader)
id_2_parents       = defaultdict(list)
id_2_gram_positive = {}
id_2_shape         = {}
id_2_groupings     = defaultdict(list)
for row in reader:
    id, parent, gram_positive, shape, grouping = row
    if parent:
        id_2_parents[id].append(onto[parent])
    if gram_positive :
        if gram_positive == "True": gram_positive = True
        else:                       gram_positive = False
        id_2_gram_positive[id] = gram_positive
    if shape:
        shape_class = onto[shape]
        id_2_shape[id] = shape_class
    if grouping:
        grouping_class = onto[grouping]
        id_2_groupings[id].append(grouping_class)
with onto_classes:
    for id in id_2_parents:
        if id_2_parents[id]:
            Class = types.new_class(id,↲tuple(id_2_parents[id]))
        else:
            Class = types.new_class(id, (Thing,))
        conditions = []
        if id in id_2_gram_positive:
            conditions.append(onto.gram_positive.value(↲
                              id_2_gram_positive[id]))
        if id in id_2_shape :
            conditions.append(onto.has_shape.some↲(id_2_shape[id]))
            conditions.append(onto.has_shape.only↲(id_2_shape[id]))
        for grouping in id_2_groupings[id]:
            conditions.append(onto.has_grouping.some(grouping))
        if   len(conditions) == 1:
            Class.equivalent_to.append(conditions[0])
        elif len(conditions) > 1:
            Class.equivalent_to.append( And(conditions) )
onto_classes.save("bacteria_defined_classes.owl")

The program has two parts. The first part reads the entire CSV file and stores all the data in dictionaries, and the second creates the classes and the equivalence relationships. Indeed, the equivalence relations must be defined “in one piece” (as we saw in 3.4.8). It is therefore necessary to have all the information on a class to be able to create its definition. However, in our CSV file, a class can be defined on several lines (e.g., here, the class Bifidobacterium). In this case, we cannot create the definition after only reading the first line.

The first part uses standard dictionaries and defaultdict, that is, dictionaries with a default value (see 2.4.6), to simplify the program. This dictionary automatically creates the missing entries and initializes them with the value of an empty list.

The second part of the program goes through all the class identifiers in the id_2_parents dictionary. For each identifier, we create a class inheriting from the parent classes or, failing that, from Thing. Next, we create a list of conditions called conditions, initially empty. Then we look at the values available in the dictionaries for the properties gram_positive, has_shape, and has_grouping, and we add in the list conditions the corresponding restrictions:
  • For the gram_positive property, we used a value restriction because it is a data property.

  • For the has_form property, we used two restrictions, an existential restriction and a universal restriction, in the manner of what we had done in Chapter 3.

  • For the has_grouping property, we used a single, existential, restriction in order to leave open the possibility of other groupings.

Finally, we add an equivalence relation in Class.equivalent_to . If the conditions list contains only one condition, we add this unique condition in Class.equivalent_to. If the conditions list contains conditions, we perform the intersection of these conditions with the operator And(), and then we add this intersection to Class.equivalent_to. This means that when several conditions are present, all of them must be satisfied.

The ontology created by this program is equivalent to that created by the previous program.

6.7 Summary

In this chapter, you have learned how to handle OWL constructs and class restrictions, a major feature in OWL. We also presented the various shortcuts Owlready proposes, such as class properties or defined classes. Finally, we have seen how to create defined classes from CSV files and how to create the ontology of bacteria of Chapter 3 entirely in Python. You are now able to create in Python almost everything that can be created in Protégé.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.66.206