Tutorial: Ontology interface

Binder

This tutorial introduces the interface to the installed ontologies. The code presented is based on this example.

Background

In an ontological framework, ontology entities are used as a knowledge representation form. Those can be further categorized in two groups: ontology individuals (assertional knowledge), and ontology classes, relationships and attributes (terminological knowledge).

In a previous tutorial, we have discussed how to work with CUDS objects, which represent ontology individuals. In this tutorial, we present the API of all the other entities instead: ontology classes, relationships and attributes. These are defined in an ontology installation file in YAML or OWL format. The presented API enables you to access the entities and navigate within an ontology.

In this tutorial, we will work both with the city namespace, the example namespace from OSP-core, and the math namespace from the Elementary Multiperspective Material Ontology (EMMO), for which an installation file is also provided with OSP-core.

Please install the ontologies running the commands below if you have not installed them yet.

[1]:
# Install the ontologies
!pico install city
!pico install emmo
INFO 2021-03-31 16:16:53,174 [osp.core.ontology.installation]: Will install the following namespaces: ['city']
INFO 2021-03-31 16:16:53,187 [osp.core.ontology.yml.yml_parser]: Parsing YAML ontology file /home/jose/.local/lib/python3.9/site-packages/osp/core/ontology/docs/city.ontology.yml
INFO 2021-03-31 16:16:53,209 [osp.core.ontology.yml.yml_parser]: You can now use `from osp.core.namespaces import city`.
INFO 2021-03-31 16:16:53,209 [osp.core.ontology.parser]: Loaded 202 ontology triples in total
INFO 2021-03-31 16:16:53,223 [osp.core.ontology.installation]: Installation successful
INFO 2021-03-31 16:16:53,753 [osp.core.ontology.installation]: Will install the following namespaces: ['emmo']
INFO 2021-03-31 16:16:53,756 [osp.core.ontology.parser]: Parsing /home/jose/.local/lib/python3.9/site-packages/osp/core/ontology/docs/emmo.yml
INFO 2021-03-31 16:16:53,756 [osp.core.ontology.parser]: Downloading https://raw.githubusercontent.com/emmo-repo/emmo-repo.github.io/master/versions/1.0.0-alpha2/emmo-inferred.owl
INFO 2021-03-31 16:16:54,299 [osp.core.ontology.parser]: Parsing /tmp/tmpvqey417g-emmo
INFO 2021-03-31 16:16:54,897 [osp.core.ontology.parser]: You can now use `from osp.core.namespaces import mereotopology`.
INFO 2021-03-31 16:16:54,897 [osp.core.ontology.parser]: You can now use `from osp.core.namespaces import physical`.
INFO 2021-03-31 16:16:54,897 [osp.core.ontology.parser]: You can now use `from osp.core.namespaces import top`.
INFO 2021-03-31 16:16:54,897 [osp.core.ontology.parser]: You can now use `from osp.core.namespaces import semiotics`.
INFO 2021-03-31 16:16:54,897 [osp.core.ontology.parser]: You can now use `from osp.core.namespaces import perceptual`.
INFO 2021-03-31 16:16:54,897 [osp.core.ontology.parser]: You can now use `from osp.core.namespaces import reductionistic`.
INFO 2021-03-31 16:16:54,897 [osp.core.ontology.parser]: You can now use `from osp.core.namespaces import holistic`.
INFO 2021-03-31 16:16:54,897 [osp.core.ontology.parser]: You can now use `from osp.core.namespaces import physicalistic`.
INFO 2021-03-31 16:16:54,897 [osp.core.ontology.parser]: You can now use `from osp.core.namespaces import math`.
INFO 2021-03-31 16:16:54,897 [osp.core.ontology.parser]: You can now use `from osp.core.namespaces import properties`.
INFO 2021-03-31 16:16:54,897 [osp.core.ontology.parser]: You can now use `from osp.core.namespaces import materials`.
INFO 2021-03-31 16:16:54,897 [osp.core.ontology.parser]: You can now use `from osp.core.namespaces import metrology`.
INFO 2021-03-31 16:16:54,897 [osp.core.ontology.parser]: You can now use `from osp.core.namespaces import models`.
INFO 2021-03-31 16:16:54,897 [osp.core.ontology.parser]: You can now use `from osp.core.namespaces import manufacturing`.
INFO 2021-03-31 16:16:54,898 [osp.core.ontology.parser]: You can now use `from osp.core.namespaces import isq`.
INFO 2021-03-31 16:16:54,898 [osp.core.ontology.parser]: You can now use `from osp.core.namespaces import siunits`.
INFO 2021-03-31 16:16:54,901 [osp.core.ontology.parser]: Loaded 4664 ontology triples in total
INFO 2021-03-31 16:16:55,083 [osp.core.ontology.installation]: Installation successful

Accessing entities: the namespace object

To access ontology entities, we first need to know the aliases of the installed ontology namespaces. In each ontology YAML installation file, the namespace(s) that it contains is(are) stated at the top of the file. For example, at the top of the city ontology installation file you may find:

---
version: "0.0.3"

namespace: "city"

ontology:
   ...

Alternatively, you can use pico ontology installation tool to see the installed namespaces:

[2]:
!pico list
Packages:
        - city
        - emmo
Namespaces:
        - xml
        - rdf
        - rdfs
        - xsd
        - cuba
        - isq
        - ns1
        - ns2
        - owl
        - city
        - mereotopology
        - physical
        - top
        - semiotics
        - perceptual
        - reductionistic
        - holistic
        - physicalistic
        - math
        - properties
        - materials
        - metrology
        - models
        - manufacturing
        - siunits

Once we know the name of the namespace that we want to use, we import it in python. For this tutorial, we are importing the namespaces city and math. Through those imported namespace python objects, the entities within the namespaces can be accessed:

[3]:
from osp.core.namespaces import city
from osp.core.namespaces import math

There are several ways to access an ontology entity in OSP-core, which are summarized by the following list and will be demonstrated shortly after.

  • By suffix. For example, for the namespace city, whose IRI is http://www.osp-core.com/city#, fetching by the suffix Citizen would return the ontology entity with IRI http://www.osp-core.com/city#Citizen.

  • By label. Fetchs the entity by the label that has been assigned to it using either the rdfs:label or skos:prefLabel predicates.

  • By IRI. The full IRI of an ontology entity is provided in order to fetch it.

  • By string. Using a string, for example "city.LivingBeing". This is only useful in some special cases.

The most convenient way to access an ontology entity is using the dot notation in python. For example, city.Citizen. This method is a shorthand for fetching by suffix or label:

  • When the keyword reference_by_label is set to True (enabled) in the ontology YAML installation file, the dot notation is a shorthand for fetching by label. This keyword is enabled in the math namespace.

  • When the keyword reference_by_label is set to False (disabled) or not set, the dot notation is a shorthand for fetching by suffix instead. This keyword is disabled in the city namespace.

To get a list of all the entities available within a namespace, run list(namespace).

Tip

The dot notation supports IPython autocompletion. For example, when working on a Jupyter notebook, once the namespace has been imported, it is possible to get suggestions for the entity names by writing namespace. and pressing TAB.

Accessing an ontology entity by suffix

Let’s fetch the Citizen class, whose IRI is http://www.osp-core.com/city#Citizen.

The keyword, reference_by_label is set to False, so one can just use the dot notation.

[4]:
city.Citizen
[4]:
<OntologyClass city.Citizen>

Another alternative is using the get_from_suffix method from the namespace object. This is useful when the suffix contains characters that Python does not accept as property names, such as spaces or dashes.

[5]:
city.get_from_suffix('Citizen')
[5]:
<OntologyClass city.Citizen>

Note that the suffix is case sensitive, and therefore the following would produce an error.

[6]:
#city.citizen  # -> Fails.

Accessing an ontology entity by label

Let’s fetch the Integer class, whose IRI is http://emmo.info/emmo/middle/math#EMMO_f8bd64d5_5d3e_4ad4_a46e_c30714fecb7f.

The keyword reference_by_label is set to True, so we just use the dot notation.

[7]:
math.Integer
[7]:
<OntologyClass math.Integer>

Another alternative is using the square bracket notation on the namespace object. This is useful when the suffix contains characters that Python does not accept as property names, such as spaces or dashes.

[8]:
math['Integer']
[8]:
<OntologyClass math.Integer>

Fetching by label is NOT case sensitive when using the dot notation, but it is when using square brackets, so the following behavior is expected.

[9]:
#math['integer']  # -> Fails.
math.integer  # -> Works.
[9]:
<OntologyClass math.integer>

Accessing an ontology entity by IRI

This is only possible using the get_from_iri method from the namespace object. For example, let’s fetch the Integer entity again.

[10]:
math.get_from_iri('http://emmo.info/emmo/middle/math#EMMO_f8bd64d5_5d3e_4ad4_a46e_c30714fecb7f')
[10]:
<OntologyClass math.Integer>

Access entities using a string

Sometimes you only have a string refering to an entity. Using the get_entity function you can get the corresponding python object easily:

[11]:
from osp.core.namespaces import get_entity  # noqa: E402

print("\nYou can get an entity with a string")
print(get_entity("city.LivingBeing"))
print(get_entity("city.LivingBeing") == city.LivingBeing)

You can get an entity with a string
city.LivingBeing
True

Accessing an entity’s name, IRI and namespace

Each ontology entity has an associated name which can be accessed using the name property.

[12]:
city.LivingBeing.name
[12]:
'LivingBeing'

The IRI of an entity might be accessed using the iri property.

[13]:
math.Real.iri
[13]:
rdflib.term.URIRef('http://emmo.info/emmo/middle/math#EMMO_18d180e4_5e3e_42f7_820c_e08951223486')

In addition, it is possible to get the namespace object to which the entity belongs using the namespace property.

[14]:
math.Equation.namespace
[14]:
<math: http://emmo.info/emmo/middle/math#>

Accessing super- and subclasses

Using the properties superclasses and subclasses it is easy to navigate the ontology. Direct superclasses and subclasses can also be accessed:

[15]:
print("\nYou can access the superclasses and the subclasses")
print(city.LivingBeing.superclasses)
print(city.LivingBeing.subclasses)

print("\nYou can access the direct superclasses and subclasses")
print(city.LivingBeing.direct_superclasses)
print(city.LivingBeing.direct_subclasses)

print("\nYou can access a description of the entities")
print(city.LivingBeing.description)

print("\nYou can test if one entity is a subclass / superclass of another")
print(city.Person.is_subclass_of(city.LivingBeing))
print(city.LivingBeing.is_superclass_of(city.Person))

You can access the superclasses and the subclasses
{<OntologyClass cuba.Entity>, <OntologyClass city.LivingBeing>}
{<OntologyClass city.Person>, <OntologyClass city.Citizen>, <OntologyClass city.LivingBeing>}

You can access the direct superclasses and subclasses
{<OntologyClass cuba.Entity>}
{<OntologyClass city.Person>}

You can access a description of the entities
A being that lives

You can test if one entity is a subclass / superclass of another
True
True

Testing the type of the entities

In the ontology, three types of entities can be defined: classes, relationships and attributes. OSP-core has its own vocabulary, the CUBA namespace, which describes, among other things, such entity types. Relationships are subclasses of CUBA.RELATIONSHIP and attributes are subclasses of CUBA.ATTRIBUTE. There are different Python objects for the different entity types. You can use both to check which type of entity you are dealing with:

[16]:
from osp.core.namespaces import cuba  # noqa: E402

# These are the classes for the ontology entities
from osp.core.ontology import (  # noqa: F401, E402
    OntologyEntity,
    OntologyClass,
    OntologyRelationship,
    OntologyAttribute
)

print("\nYou can test if an entity is a class")
print(isinstance(city.LivingBeing, OntologyClass))
print(not city.LivingBeing.is_subclass_of(cuba.relationship)
      and not city.LivingBeing.is_subclass_of(cuba.attribute))

print("\nYou can test if an entity is a relationship")
print(isinstance(city.hasInhabitant, OntologyRelationship))
print(city.hasInhabitant.is_subclass_of(cuba.relationship))

print("\nYou can test if an entity is a attribute")
print(isinstance(city.name, OntologyAttribute))
print(city.name.is_subclass_of(cuba.attribute))



You can test if an entity is a class
True
True

You can test if an entity is a relationship
True
True

You can test if an entity is a attribute
True
True

Operations specific to ontology classes

The different types of entities differ in the operations they offer. For classes, you can access the attributes:

[17]:
print("\nYou can get the attributes of an ontology class and their defaults")
print(city.Citizen.attributes)

print("\nYou can get the non-inherited attributes and their defaults")
print(city.Citizen.own_attributes)
print(city.LivingBeing.own_attributes)

You can get the attributes of an ontology class and their defaults
{<OntologyAttribute city.name>: (rdflib.term.Literal('John Smith'), False, None), <OntologyAttribute city.age>: (rdflib.term.Literal('25', datatype=rdflib.term.URIRef('http://www.w3.org/2001/XMLSchema#integer')), False, None)}

You can get the non-inherited attributes and their defaults
{}
{<OntologyAttribute city.name>: (rdflib.term.Literal('John Smith'), False, None), <OntologyAttribute city.age>: (rdflib.term.Literal('25', datatype=rdflib.term.URIRef('http://www.w3.org/2001/XMLSchema#integer')), False, None)}

In addition, OSP-core has special support for the owl:Restriction and owl:Composition classes of the Web Ontology Language (OWL) (check the OWL ontology specification for more details). Such OWL classes are represented by the python classes Restriction and Composition. See operations specific to ontology axioms for more information.

For example, in the city ontology, the citizens have a restriction on the name and age attributes: a citizen must have exactly one name and one age. These axioms can be accessed using the axioms property, which returns both the restriction and compositions affecting the class.

[18]:
tuple(str(x) for x in city.Citizen.axioms)
[18]:
('city.name QUANTIFIER.EXACTLY 1', 'city.age QUANTIFIER.EXACTLY 1')

Operations specific to ontology axioms

For restrictions, the quantifier, the target, the restriction type and the relationship/attribute (depending on whether it is a restriction of the relationship type or attribute type) may be accessed.

[19]:
restriction = city.Citizen.axioms[0]
print(restriction)
print(restriction.quantifier)
print(restriction.target)
print(restriction.rtype)
print(restriction.attribute)
city.name QUANTIFIER.EXACTLY 1
QUANTIFIER.EXACTLY
1
RTYPE.ATTRIBUTE_RESTRICTION
city.name

For compositions, both the operator and operands can be accesed.

[20]:
from osp.core.ontology.oclass_composition import Composition
composition = tuple(x for x in math.Integer.axioms if type(x) is Composition)[0]
print(composition)
print(composition.operator)
print(composition.operands)
(math.Mathematical OPERATOR.AND perceptual.Symbol)
OPERATOR.AND
[<OntologyClass math.Mathematical>, <OntologyClass perceptual.Symbol>]

Operations specific to ontology relationships

You can access the inverse of relationships.

[21]:
print("\nYou can get the inverse of a relationship")
print(city.hasInhabitant.inverse)

You can get the inverse of a relationship
city.INVERSE_OF_hasInhabitant

Operations specific to attributes

You can acces the datatype and the argument name of attributes.

[22]:
print("\nYou can get the argument name of an attribute. "
      "The argument name is used as keyword argument when instantiating CUDS objects.")
print(city.age.argname)

print("\nYou can get the datatype of attributes")
print(city.age.datatype)

print("\nYou can use the attribute to convert values "
      "to the datatype of the attribute")
result = city.age.convert_to_datatype("10")
print(type(result), result)

print("\nAnd likewise to convert values to the python basic type "
      "associated with the datatype of the attribute.")
result = city.name.convert_to_basic_type(5)
print(type(result), result)

You can get the argument name of an attribute. The argument name is used as keyword argument when instantiating CUDS objects.
age

You can get the datatype of attributes
http://www.w3.org/2001/XMLSchema#integer

You can use the attribute to convert values to the datatype of the attribute
<class 'int'> 10

And likewise to convert values to the python basic type associated with the datatype of the attribute.
<class 'str'> 5

Check the API Reference for more details on the methods *convert_to_datatype* and *convert_to_basic_type*.

Creating CUDS using ontology classes

You can call ontology classes to create CUDS objects. To learn more, have a look at the CUDS API tutorial.

[23]:
print("\nYou can instantiate CUDS objects using ontology classes")
print(city.Citizen(name="Test Person", age=42))

print("\nYou can check if a CUDS object is an instance of a ontology class")
print(city.Citizen(name="Test Person", age=42).is_a(city.Citizen))
print(city.Citizen(name="Test Person", age=42).is_a(city.LivingBeing))

print("\nYou can get the ontology class of a CUDS object.")
print(city.Citizen(name="Test Person", age=42).oclass)


You can instantiate CUDS objects using ontology classes
city.Citizen: e0947100-9c40-415f-92c8-a86b796dbb01

You can check if a CUDS object is an instance of a ontology class
True
True

You can get the ontology class of a CUDS object.
city.Citizen