Terminological knowledge#

Binder

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, attributes and annotations (terminological knowledge). This page focuses on how to access and navigate the terminological knowledge of an ontology using SimPhoNy.

Such functionality is presented in the form of a tutorial, in which both the city namespace from SimPhoNy’s example City ontology, and the emmo namespace from the Elementary Multiperspective Material Ontology (EMMO) are used as examples.

If you want to follow the tutorial along, please make sure that such ontologies are installed. If you have not installed them yet, you can do so running the commands below.

[1]:
# Install the ontologies
!pico install city emmo

Note

SimPhoNy does not feature the ability to edit the classes, relationships, attributes and annotations of ontologies, only to read them. This is the reason why ontologies need to be installed using pico.

Namespace objects: accessing entities#

To access ontology entities, it is first needed to know the aliases of the installed ontology namespaces. The pico ontology management tool to can list the installed namespaces. Note that each namespace is provided by a specific ontology package.

[2]:
!pico list
Packages:
        - simlammps
        - city
        - emmo
Namespaces:
        - simphony
        - owl
        - rdfs
        - simlammps
        - city
        - emmo

Once the names of the namespaces to be used are known, they can be imported in Python. In this tutorial, the namespaces city and emmo are imported. Through those imported namespace Python objects, the entities within the namespaces can be accessed:

[3]:
from simphony_osp.namespaces import city, emmo

The namespace objects are Python iterables. This implies that it is possible to get a list of all the entities available within a namespace running list(namespace).

To get the IRI of a namespace object, use the iri property. This will yield an URIRef object from the RDFLib library, that SimPhoNy makes use of.

[4]:
city.iri
[4]:
rdflib.term.URIRef('https://www.simphony-project.eu/city#')

There are several ways to reference an ontology entity to be retrieved, which are summarized in the following list.

  • By suffix. For example, for the namespace city, whose IRI is http://www.simphony-osp.eu/city#, requesting the suffix Citizen would return the ontology entity with IRI http://www.simphony-osp.eu/city#Citizen.

  • By label. Retrieves the entity by the *main label* that has been assigned to it in the ontology using either the rdfs:label or skos:prefLabel predicates.

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

Although it is not the only possible approach, the most convenient way to access an ontology entity is to use the dot notation on the imported namespace objects (e.g. city.Citizen) to reference it either by suffix or label.

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 or label

Let’s retrieve the Living Being class from the city namespace, whose IRI is https://www.simphony-project.eu/city#LivingBeing.

To retrieve the class using its suffix or its label, just use the dot notation

[5]:
city.LivingBeing  # by suffix
[5]:
<OntologyClass: Living Being https://www.simphony-project.eu/city#LivingBeing>

However, sometimes the suffix or label contains characters that Python does not accept as attribute names, such as dashes or spaces. In such cases, it is not possible to use the dot notation. An alternative way to retrieve entitites using the Python’s index operator [] exists

[6]:
city['Living Being']  # by label (contains a space)
[6]:
<OntologyClass: Living Being https://www.simphony-project.eu/city#LivingBeing>

Tip

The index notation also supports IPython autocompletion.

Note that both operations are case-sensitive, and therefore the following would produce an error.

[7]:
# city.Livingbeing  # -> Fails.

In addition, the namespace object has some advanced methods, from_suffix, from_label and get that can also be used to retrieve entities by suffix or label.

Accessing an ontology entity by IRI

Let’s now retrieve the Integer class from the emmo namespace using its IRI http://emmo.info/emmo#EMMO_f8bd64d5_5d3e_4ad4_a46e_c30714fecb7f.

To do so, use the method from_iri of the corresponding namespace object

[8]:
emmo.from_iri('http://emmo.info/emmo#EMMO_f8bd64d5_5d3e_4ad4_a46e_c30714fecb7f')
[8]:
<OntologyClass: Integer http://emmo.info/emmo#EMMO_f8bd64d5_5d3e_4ad4_a46e_c30714fecb7f>

Ontology entity objects#

Accessing an entity’s label#

Labels are tipically assigned to ontology entities through the rdfs:label or skos:prefLabel predicates. In particular, it is possible for a single entity to have several labels.

In SimPhoNy, all such labels can be seen, but one label is considered to be the main label. When SimPhoNy has to select exclusively one label among all the available ones, it will first retrieve all the available labels for an entity an then sort them based on the following criteria:

  • The predicate used to assign the label. Both rdfs:label and skos:prefLabel are considered, but labels assigned using rdfs:label are preferred to labels assigned to skos:prefLabel.

  • The language of the label. English labels are preferred to labels with no language assigned to them, and labels with no language to labels in any other language.

So for example, assume that the ontology class

[9]:
city.City
[9]:
<OntologyClass: City https://www.simphony-project.eu/city#City>

has as labels (which is actually not the case): Municipality (language: “en”, predicate: ``skos:prefLabel``), City (language: None, predicate: ``rdfs:label``), Città (language: ‘it’, predicate: ``rdfs:label``). Then the second one, City, would be the main label.

The main label of an ontology entity can be accessed using the label attribute.

[10]:
city.City.label
[10]:
'City'

It is also possible to access the language of the main label,

[11]:
city.City.label_lang
[11]:
'en'

and even the main label in the form of an RDFLib Literal, which contains both the label itself and its language information.

[12]:
city.City.label_literal
[12]:
rdflib.term.Literal('City', lang='en')

To retrieve all labels as RDFLib Literals, use the iter_labels method.

[13]:
list(city.City.iter_labels())
[13]:
[rdflib.term.Literal('City', lang='en')]

As you can see, actually the City class has only one label!

Accessing an entity’s identifier and namespace#

The identifier (IRI or blank node identifier) of an entity may be accessed using the identifier property.

[14]:
emmo.Real.identifier
[14]:
rdflib.term.URIRef('http://emmo.info/emmo#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.

[15]:
emmo.Equation.namespace
[15]:
<emmo: http://emmo.info/emmo#>

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:

[16]:
from IPython.display import display

print(f"Superclasses of \"Living Being\": {city.LivingBeing.superclasses}", end="\n"*2)
print(f"Subclasses of \"Living Being\": {city.LivingBeing.subclasses}", end="\n"*2)

print(f"Direct superclasses of \"Living Being\": {city.LivingBeing.direct_superclasses}", end="\n"*2)
print(f"Direct subclasses of \"Living Being\": {city.LivingBeing.direct_subclasses}", end="\n"*2)

print("Is \"Person\" a subclass of \"Living Being\"?", city.Person.is_subclass_of(city.LivingBeing))
print("Is \"Living Being\" a superclass of \"Person\"?", city.LivingBeing.is_superclass_of(city.Person))
Superclasses of "Living Being": frozenset({<OntologyClass: Living Being https://www.simphony-project.eu/city#LivingBeing>, <OntologyClass: http://www.w3.org/2002/07/owl#Thing>})

Subclasses of "Living Being": frozenset({<OntologyClass: Person https://www.simphony-project.eu/city#Person>, <OntologyClass: Living Being https://www.simphony-project.eu/city#LivingBeing>, <OntologyClass: Citizen https://www.simphony-project.eu/city#Citizen>})

Direct superclasses of "Living Being": frozenset()

Direct subclasses of "Living Being": frozenset({<OntologyClass: Person https://www.simphony-project.eu/city#Person>})

Is "Person" a subclass of "Living Being"? True
Is "Living Being" a superclass of "Person"? True

Checking the type of entity#

In the terminological knowledge side of the ontology, four types of entities can be defined: classes, relationships, attributes and annotations. There are different Python objects for the different entity types. Both can be used to check to which type an entity belongs:

[17]:
# These are the Python classes for the different types of ontology entities.
# They all inherit from `simphony_osp.ontology.OntologyEntity`.
from simphony_osp.namespaces import owl, rdfs
from simphony_osp.ontology import (
    OntologyAnnotation,
    OntologyAttribute,
    OntologyClass,
    OntologyRelationship,
)

print("\nIs the entity is a class?")
print(isinstance(city.LivingBeing, OntologyClass))
print(city.LivingBeing.is_subclass_of(owl.Thing))
print(not city.LivingBeing.is_subclass_of(owl.topObjectProperty)
      and not city.LivingBeing.is_subclass_of(owl.topDataProperty))

print("\nIs the entity is a relationship?")
print(isinstance(city.hasInhabitant, OntologyRelationship))
print(city.hasInhabitant.is_subclass_of(owl.topObjectProperty))

print("\nIs the entity an attribute?")
print(isinstance(city['name'], OntologyAttribute))
print(city['name'].is_subclass_of(owl.topDataProperty))

print("\nIs the entity an annotation?")
print(isinstance(rdfs.label, OntologyAnnotation))

Is the entity is a class?
True
True
True

Is the entity is a relationship?
True
True

Is the entity an attribute?
True
True

Is the entity an annotation?
True

All such objects inherit from the ontology entity object, and thus, share its common functionality, but each differs in the extra operations they offer.

Ontology class objects#

One of the extra functionalities offered by ontology class objects is the access to their attributes. The attributes of an ontology class are those that all instances of a class are expected to share. For example, in OWL ontologies they are defined using the owl:hasValue, owl:someValuesFrom, owl:cardinality or owl:minCardinality predicates.

[18]:
city.Citizen.attributes  # returns a dictionary whose keys are attributes and whose values are their default values (if defined)
[18]:
mappingproxy({<OntologyAttribute: name https://www.simphony-project.eu/city#name>: None,
              <OntologyAttribute: age https://www.simphony-project.eu/city#age>: None})

In addition, SimPhoNy 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.

[19]:
tuple(str(x) for x in city.Citizen.axioms)
[19]:
('is child of QUANTIFIER.MAX 2',
 'name QUANTIFIER.EXACTLY 1',
 'works in QUANTIFIER.MAX 1',
 'age QUANTIFIER.EXACTLY 1')

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.

[20]:
from simphony_osp.ontology import RESTRICTION_QUANTIFIER, RESTRICTION_TYPE

restriction = next(iter(city.Citizen.axioms))
print(restriction)
print(restriction.quantifier)
print(restriction.target)
print(restriction.rtype)
print(
    restriction.attribute
    if restriction.rtype == RESTRICTION_TYPE.ATTRIBUTE_RESTRICTION else
    restriction.relationship
)
is child of QUANTIFIER.MAX 2
QUANTIFIER.MAX
2
RTYPE.RELATIONSHIP_RESTRICTION
is child of

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

[21]:
from simphony_osp.ontology import COMPOSITION_OPERATOR, Composition

composition = tuple(x for x in emmo.Integer.axioms if isinstance(x, Composition))[0]
print(composition)
print(composition.operator)
print(composition.operands)
(SymbolicConstruct OPERATOR.OR Symbol)
OPERATOR.OR
(<OntologyClass: SymbolicConstruct http://emmo.info/emmo#EMMO_89a0c87c_0804_4013_937a_6fe234d9499c>, <OntologyClass: Symbol http://emmo.info/emmo#EMMO_a1083d0a_c1fb_471f_8e20_a98f881ad527>)

Ontology relationship objects#

You can access the inverse of relationships.

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

You can get the inverse of a relationship
None

Ontology attribute objects#

The datatype of attributes can be accessed.

[23]:
city.age.datatype
[23]:
rdflib.term.URIRef('http://www.w3.org/2001/XMLSchema#integer')

Ontology annotation objects#

There is no specific extra functionality offered by ontology annotation objects.