{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Assertional knowledge\n", "\n", "
\n", "\n", "[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/simphony/docs/v4.0.0?filepath=docs%2Fusage%2Fassertional_knowledge.ipynb \"Click to run this tutorial yourself!\")\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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](https://en.wikipedia.org/wiki/Abox)), and ontology classes, relationships, attributes and annotations ([terminological knowledge](https://en.wikipedia.org/wiki/Tbox)). This page **focuses on** how to access, edit and navigate the **assertional knowledge** of an ontology using SimPhoNy." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Such functionality is presented in the form of a tutorial, in which the city namespace from SimPhoNy’s example City ontology, 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 both ontologies are installed. If it is not the case, you can install them by running the command below." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# Install the ontologies\n", "!pico install city emmo" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Moreover, this tutorial concentrates on how to interact with [ontology individual objects](../api_reference.md#simphony_osp.ontology.OntologyIndividual). Each ontology individual object represents a single individual in the ontology." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Instantiating ontology individuals" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On this page, examples are based **exclusively on newly created ontology individuals**. You can learn how to retrieve existing ontology individuals from a data source in the next sections." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To instantiate a new ontology individual, just call an ontology class object as shown below. If the words \"ontology class object\" sound new to you, please read the [previous section](terminological_knowledge.ipynb). " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Certain attributes of the ontology individual can already be set at creation time by passing their values as keyword arguments, where the keyword is any of the attribute labels or its namespace suffix. Such attributes are, specifically, the ones returned by the [attributes property](../api_reference.md#simphony_osp.ontology.OntologyClass.attributes) and the [optional attributes property](../api_reference.md#simphony_osp.ontology.OntologyClass.optional_attributes) of the ontology class being called." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The following attributes of a new Citizen individual can be set using keyword arguments:\n", " - name\n", " - age\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from simphony_osp.namespaces import city, emmo, owl, rdfs, simphony\n", "\n", "print(\n", " f'The following attributes of a new {city.Citizen} '\n", " f'individual can be set using keyword arguments:'\n", ")\n", "for attribute in set(city.Citizen.attributes) | city.Citizen.optional_attributes:\n", " print(f' - {attribute}')\n", "\n", "city.Citizen(name=\"Test Person\", age=42)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In fact, if any of the attributes is defined in the ontology as _mandatory_ using [ontology axioms](terminological_knowledge.ipynb#Operations-specific-to-ontology-axioms), you will be forced to provide them in the function call (otherwise an exception will be raised)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "
Tip
\n", " \n", "In Python, you can pass keyword arguments with spaces or other characters not typically allowed in keyword arguments by unpacking a dictionary in the function call: `city.Citizen(name=\"Test Person\", **{\"age\": 42})`.\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "
Note
\n", "\n", "At the moment, it is not possible to instantiate multi-class individuals. We [are aware of this issue](https://github.com/simphony/simphony-osp/issues/669), and planning to include this functionality in a future minor release.\n", "\n", "Until this is fixed, the suggested workaround is to instantiate an ontology individual of any class and change the classes _a posteriori_, just as shown below.\n", " \n", "```python\n", "person = owl.Thing()\n", "\n", "person.classes = city.Citizen, emmo.Cogniser\n", "```\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By default, new ontology individuals are assigned a random IRI from the _simphony-osp.eu_ domain." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "rdflib.term.URIRef('https://www.simphony-osp.eu/entity#6b7cf472-9dbe-4d9e-93e2-0f56ee308d27')" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "city.Citizen(name=\"Test Person\", age=42).identifier" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, it is possible to fix the identifier using the `iri` keyword argument." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "rdflib.term.URIRef('http://example.org/entity#test_person')" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "city.Citizen(\n", " name=\"Test Person\", age=42,\n", " iri='http://example.org/entity#test_person'\n", ").identifier" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "An individual can also be instantiated in a session different from the default one using the `session` keyword argument (see the sessions section)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## [Ontology individual objects](../api_reference.md#simphony_osp.ontology.OntologyIndividual)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ontology individuals are a special type of ontology entities, and thus, the ontology individual objects inherit from [ontology entity objects](terminological_knowledge.ipynb#Ontology-entity-objects), meaning that they share their functionality.\n", "\n", "In SimPhoNy, an ontology individual is characterized by\n", "\n", "- the information about the ontology individual itself such as the classes it belongs to, its label and its attributes;\n", "- the connections to other ontology individuals.\n", "\n", "Moreover, such information is stored on a so-called _session_ (see [next section](sessions.ipynb))." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As said, ontology individual objects inherit from [ontology entity objects](terminological_knowledge.ipynb#Ontology-entity-objects). Therefore, it is also possible to access their label, identifier, namespace and super- or subclasses. Below you can find an example. Head to the [terminological knowledge](terminological_knowledge.ipynb#Ontology-entity-objects) section for more details. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "
Note
\n", " \n", "Even though ontology individual objects share the functionality of [ontology entity objects](terminological_knowledge.ipynb#Ontology-entity-objects), there are some slight differences to consider:\n", "\n", "- The [namespace property](../api_reference.md#simphony_osp.ontology.OntologyEntity.namespace) tipically returns `None`, regardless of the IRI of the ontology individual. This happens because in order to belong to a namespace, an ontology entity needs not only to have an IRI that contains the namespace IRI, but also to belong to the same session. Ontologies installed with [pico](ontology/pico.md) live in their own, separate session.\n", "- The [superclasses](../api_reference.md#simphony_osp.ontology.OntologyEntity.superclasses), [direct_superclasses](../api_reference.md#simphony_osp.ontology.OntologyEntity.direct_superclasses), [subclasses](../api_reference.md#simphony_osp.ontology.OntologyEntity.subclasses) and [direct_subclasses](../api_reference.md#simphony_osp.ontology.OntologyEntity.direct_subclasses) properties, as well as the [is_subclass_of](../api_reference.md#simphony_osp.ontology.OntologyEntity.is_subclass_of) method refer to the superclasses and subclasses of all the classes the ontology individual belongs to, as illustrated in the example.\n", "- The properties [label](../api_reference.md#simphony_osp.ontology.OntologyEntity.label), [label_lang](../api_reference.md#simphony_osp.ontology.OntologyEntity.label_lang), [label_literal](../api_reference.md#simphony_osp.ontology.OntologyEntity.label_literal) and [session](../api_reference.md#simphony_osp.ontology.OntologyEntity.session) are **writable**. This means that both the [main label](terminological_knowledge.ipynb#Accessing-an-entity%E2%80%99s-label) of ontology individuals can be changed and the individuals themselves may be moved from one session to another by changing the value of such properties.\n", " \n", "
" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Label: My neighbor (en)\n", "\n", "Label literal: rdflib.term.Literal('My neighbor', lang='en')\n", "\n", "List of labels: [rdflib.term.Literal('My neighbor', lang='en')]\n", "\n", "Identifier: rdflib.term.URIRef('https://www.simphony-osp.eu/entity#03659fa1-5c91-44a0-a73c-f475d3b328fe')\n", "\n", "Namespace: None\n", "\n", "Superclasses: frozenset({, , , , , , , , , })\n", "\n", "Subclasses: frozenset({})\n", "\n", "Direct superclasses: frozenset({})\n", "\n", "Direct subclasses: frozenset()\n", "\n", "Does any of the classes of the individual belong the \"Semiotics\" branch of EMMO? True\n", "\n", "Is the entity an individual? True\n" ] } ], "source": [ "person = emmo.Cogniser()\n", "# Instantiate an ontology individual of class Cogniser. According to the EMMO's\n", "# documentation, a Cogniser is defined as:\n", "# > An interpreter who establish the connection between an icon an an object \n", "# > recognizing their resemblance (e.g. logical, pictorial)\n", "# The following example for a Cogniser is provided:\n", "# > The scientist that connects an equation to a physical phenomenon.\n", "\n", "person.label, person.label_lang = \"My neighbor\", \"en\"\n", "\n", "print(\"Label:\", f\"{person.label} ({person.label_lang})\", end='\\n'*2)\n", "print(\"Label literal:\", person.label_literal.__repr__(), end='\\n'*2)\n", "print(\"List of labels:\", list(person.iter_labels()).__repr__(), end='\\n'*2)\n", "print(\"Identifier:\", person.identifier.__repr__(), end='\\n'*2)\n", "print(\"Namespace:\", person.namespace.__repr__(), end='\\n'*2)\n", "\n", "print('Superclasses:', person.superclasses, end='\\n'*2)\n", "print('Subclasses:', person.subclasses, end='\\n'*2)\n", "print('Direct superclasses:', person.direct_superclasses, end='\\n'*2)\n", "print('Direct subclasses:', person.direct_subclasses, end='\\n'*2)\n", "\n", "print(\"Does any of the classes of the individual belong the \\\"Semiotics\\\" branch of EMMO?\", person.is_subclass_of(emmo.Semiotics))\n", "\n", "from simphony_osp.ontology import OntologyIndividual\n", "print(\"\\nIs the entity an individual?\", isinstance(person, OntologyIndividual))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In addition, ontology individuals have extra functionality that is specific to them." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For example, there is an extra method to verify whether they are an instance of a specific ontology class (which is just an alias for [is_subclass_of](..#api_reference.md#simphony_osp.ontology.OntologyEntity.is_subclass_of))." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "person.is_a(emmo.Semiotics)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It is also is possible not only to verify the classes that the individual belongs to," ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "frozenset({})" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "person.classes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "but also to **change** them." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "frozenset({,\n", " })" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "person.classes = city.Citizen, emmo.Cogniser\n", "\n", "person.classes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To get the [session](sessions.ipynb) an individual belongs to, use the [session property](../api_reference.md#simphony_osp.ontology.OntologyEntity.session). Remember that this property can be also changed in order to transfer the individual from one session to another." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "person.session" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Managing attributes, relationships and annotations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Using the index operator `[]`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "SimPhoNy features a single, unified syntax based on the Python index `[]` operator to manage the relationships between ontology individuals, the values of the attributes of an individual, and the values of ontology annotations." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For example, assume one wants to create a city with several neighborhoods and inhabitants. The first step is to instantiate the ontology individuals that represent such elements." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "freiburg = city.City(name=\"Freiburg\", coordinates=[47.997791, 7.842609])\n", "\n", "neighborhoods = {\n", " city.Neighborhood(name=name, coordinates=coordinates)\n", " for name, coordinates in [\n", " ('Altstadt', [47.99525, 7.84726]),\n", " ('Stühlinger', [47.99888, 7.83774]),\n", " ('Neuburg', [48.00021, 7.86084]),\n", " ('Herdern', [48.00779, 7.86268]),\n", " ('Brühl', [48.01684, 7.843]),\n", " ]\n", "}\n", "\n", "citizen_1 = city.Citizen(name='Nikola', age=35)\n", "citizen_2 = city.Citizen(name='Lena', age=70)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The next step is connecting them, modifying the values of their attributes and adding annotations.\n", "\n", "Let's start trying to declare that the neighborhoods are part of the city and that the citizens are inhabitants of the city using the `city.hasPart` and `city.hasInhabitant` relationships.\n", "\n", "The individuals that are already connected to the city through this relationship can be consulted as follows." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "set() " ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "freiburg[city.hasPart]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The above statement yields a [relationship set object](../api_reference.md#simphony_osp.ontology.RelationshipSet). Relationship sets are [set-like](https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableSet) objects that manage the ontology individuals that are linked to the given individual and relationship (in this example, `freiburg` and `city.hasPart`). You will notice in the following examples, that relationship set objects have a few extra capabilities that [Python sets](https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset) do not have that make the interaction with them more natural." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "
Note
\n", "\n", "Set-like objects are objects compatible with the standard Python sets, meaning that all the methods and functionality from Python sets are available for set-like objects.\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In order to attach items through the given relationship, all that is needed is an **in-place** set union." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "set()\n", "{, , , , }\n" ] } ], "source": [ "freiburg[city.hasPart] | neighborhoods # does not attach the neighborhoods\n", "print(freiburg[city.hasPart])\n", "freiburg[city.hasPart] |= neighborhoods # attaches the neighborhoods (in-place union)\n", "print(freiburg[city.hasPart])\n", "\n", "freiburg[city.hasInhabitant] += citizen_1, citizen_2 # attaches the citizens\n", "# the '+=` operator is not available in standard Python sets and is a shorthand for\n", "# the following operations:\n", "# - `+= citizen_1, citizen_2` is equivalent to `|= {citizen_1, citizen_2}`\n", "# - `+= {citizen_1, citizen_2}` is equivalent to `|= {citizen_1, citizen_2}`\n", "# - `+= [citizen_1, citizen_2]` raises a TypeError (this shortcut only works for tuples and set-like objects)\n", "# - `+= citizen_3` is equivalent to `|= {citizen_3}\n", "# the `-=` operator is avilable in standard Python sets, but has been extended\n", "# to work like in the above examples when used together with non set-like objects." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Exactly in the same way, when ontology attributes or ontology annotations are passed to the index operator `[]`, [attribute sets](../api_reference.md#simphony_osp.ontology.AttributeSet) and [annotation sets](../api_reference.md#simphony_osp.ontology.AnnotationSet) are spawned, which behave similarly to relationship sets." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'Lena', 'Helena'} \n", "\n", "{70} \n", "{55} \n", "{'Lena was born in Berlin, but moved to Freiburg when she was 28 years old.', 'She likes to go into the woods and get lost in her thoughts.'} \n" ] } ], "source": [ "# ATTRIBUTES\n", "# - assign one more name to Lena\n", "citizen_2[city['name']] += 'Helena'\n", "print(citizen_2[city['name']].__repr__(), end='\\n'*2)\n", "\n", "# - change the age of Lena (`=` replaces all the values of the attribute)\n", "print(citizen_2[city.age].__repr__())\n", "citizen_2[city.age] = 55\n", "print(citizen_2[city.age].__repr__())\n", "\n", "# ANNOTATIONS\n", "citizen_1[rdfs.comment] = (\n", " 'Lena was born in Berlin, but moved to Freiburg when she was 28 years old.',\n", " 'She likes to go into the woods and get lost in her thoughts.'\n", ")\n", "print(citizen_1[rdfs.comment].__repr__())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "
Note
\n", "\n", "In SimPhoNy, relationships, attributes and annotations are treated in an ontological sense. This means that when using the corresponding Python object to access or modify them, one is referring not only to such ontology entity, but also to all of its subclasses. You can verify this fact noting that `freiburg[owl.topObjectProperty]` returns all individuals attached to `freiburg`, as all relationships are a subclass of `owl:topObjectProperty`.\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Strings can also be used with the index notation `[]` as a shorthand in certain cases" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- to access the [attributes of any of the classes](terminological_knowledge.ipynb#Ontology-class-objects) that the individual belongs to, or other attributes that have already been assigned to the individual,\n", "- to access relationships that have already been used to link the inidividual to others,\n", "- to access annotations whose value has been already assigned." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{, } " ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "freiburg[\"hasInhabitant\"]" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{35} " ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "citizen_1[\"age\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Therefore the most relevant use-case of passing strings is accessing information from existing individuals, rather than constructing new ones." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "
Tip
\n", " \n", "The index notation `[]` supports IPython autocompletion for strings. When working on a Jupyter notebook, it is possible to get suggestions for the strings that will work for that specific individual by writing `individual[\"` and pressing TAB.\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Even though in this example only a few possibilities of the relationship-, attribute- and annotation sets have been covered, remember that they are compatible with standard Python sets. So hopefully, this introduction should be enough to consider the remaining possibilities on your own: remove elements with `-=`, check if a certain relationship is being used `if freiburg[city.hasInhabitant]:`, loop over elements `for connected_individual in freiburg[city.hasInhabitant]:`, etc.\n", "\n", "`del freiburg[city.hasInhabitant]` and `freiburg[city.hasInhabitant] = None` can also be used and are equivalent to `freiburg[city.hasInhabitant] = set()`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When it comes to **accessing single values** from a relationship-, attribute- or annotation set, there are three built-in shortcuts to make it easier than iterating over them:\n", "\n", "- `any()` returns an element from the set in a non-deterministic way. Returns `None` if the set is empty.\n", "- `one()` returns the single element in the set. If the set is empty or has multiple elements, thein the exceptions [ResultEmptyError](../api_reference.md#simphony_osp.ontology.ResultEmptyError) or [MultipleResultsError](../api_reference.md#simphony_osp.ontology.MultipleResultsError) are respectively raised.\n", "- `all()` returns the set itself, and is therefore redundant. Can be used to improve code readability if needed." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Nikola\n", "{'Nikola'}\n", "None\n", "set() \n" ] } ], "source": [ "# print(citizen_2['name'].one()) # Raises `MultipleResultsError`, as Lena has multiple names.\n", "print(citizen_1['name'].any())\n", "print(citizen_1['name'].all())\n", "\n", "# print(citizen_2[city.hasChild].one()) # Raises `ResultEmptyError`, as Nikola has not been declared to have children.\n", "print(citizen_2[city.hasChild].any())\n", "print(citizen_2[city.hasChild].all().__repr__())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, if it is needed to find individuals that are connected through an _inverse relationship_, the `.inverse` attribute of the relationship sets can be used." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Freiburg'" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "citizen_1[city.hasInhabitant].inverse.one()['name'].one()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Using the Python dot notation (attributes only)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The Python dot notation can be used to access and set the attributes of individuals in all cases when both strings can be passed to the index notation `[]` and the string is compatible with the Python syntax (e.g. it contains no spaces). See the [previous section for more details](#Using-the-index-operator-[])." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('Nikola', 35)" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "citizen_1.name, citizen_1.age" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "34" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "citizen_1.age = 34\n", "citizen_1.age" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "mappingproxy({: frozenset({'Nikola'}),\n", " : frozenset({34})})" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "citizen_1.attributes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "
Tip
\n", " \n", "The dot notation also supports IPython autocompletion.\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The dot notation is limited to attributes with a single value. When several values are assigned to the same attribute, a `RuntimeError` is raised." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It is possible to get a dictionary with all the attributes of an individual and its values using the `attributes` attribute." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "mappingproxy({: frozenset({'Helena',\n", " 'Lena'}),\n", " : frozenset({55})})" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "citizen_2.attributes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Using the `get`, `iter`, `connect`, and `disconnect` methods (relationships only)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The method [connect](../api_reference.md#simphony_osp.ontology.OntologyIndividual.connect) connects individuals using the given relationship." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "set() \n", "{, } \n" ] } ], "source": [ "# remove the existing connections between Freiburg and its citizens\n", "del freiburg[city.hasInhabitant]\n", "print(freiburg[city.hasInhabitant].__repr__())\n", "\n", "# use the connect method to restore them\n", "freiburg.connect(citizen_1, citizen_2, rel=city.hasInhabitant)\n", "print(freiburg[city.hasInhabitant].__repr__())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The method [disconnect](../api_reference.md#simphony_osp.ontology.OntologyIndividual.disconnect) disconnects ontology individuals. Optionally a relationship and class filter can be given." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{}\n", "set()\n", "{}\n", "{}\n", "set()\n" ] } ], "source": [ "citizen_3 = city.Citizen(name='Lukas', age=2)\n", "citizen_1.connect(citizen_3, rel=city.hasChild)\n", "print(citizen_1[city.hasChild])\n", "\n", "citizen_1.disconnect(citizen_3) # disconnects citizen_3\n", "print(citizen_1[city.hasChild])\n", "\n", "citizen_1.connect(citizen_3, rel=city.hasChild)\n", "\n", "citizen_1.disconnect(rel=city.worksIn) # does not disconnect citizen_3, as the relationship does not match the filter\n", "print(citizen_1[city.hasChild])\n", "citizen_1.disconnect(rel=city.hasChild, oclass=city.Building) # does not disconnect citizen_3, as the its class does not match the filter\n", "print(citizen_1[city.hasChild])\n", "citizen_1.disconnect(citizen_3, oclass=city.Citizen) # disconnect works, as the filters match now\n", "print(citizen_1[city.hasChild])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The method [get](../api_reference.md#simphony_osp.ontology.OntologyIndividual.get) is used to obtain the individuals linked through a given relationship. Filters to restrict the results only to specific individuals, relationships and classes, as well as any combination of them can optinally be provided. The [iter](../api_reference.md#simphony_osp.ontology.OntologyIndividual.iter) method behaves similarly, but returns an interator instead." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{, , , , , , } \n", "\n", "{, } \n", "\n", "{, } \n", "\n", "\n", "(, )\n", "\n", "None\n", "None\n", "\n", "None\n" ] } ], "source": [ "print(freiburg.get().__repr__(), end='\\n'*2) # returns everything attached to Freiburg (a relationship set)\n", "\n", "print(freiburg.get(rel=city.hasInhabitant).__repr__(), end='\\n'*2) # returns only the citizens (a relationship set)\n", "\n", "print(freiburg.get(oclass=city.Citizen).__repr__(), end='\\n'*2) # also returns only the citizens (a relationship set)\n", "\n", "# filtering specific individuals (can be combined with class and relationship filters)\n", "print(freiburg.get(citizen_1).__repr__())\n", "print(freiburg.get(citizen_1, citizen_2).__repr__())\n", "print(freiburg.get(citizen_1.identifier).__repr__())\n", "print(freiburg.get('https://example.org/city#unknown_citizen').__repr__())\n", "print(freiburg.get(citizen_1, rel=city.hasChild).__repr__())\n", "print(freiburg.get(citizen_1, rel=city.hasInhabitant).__repr__())\n", "print(freiburg.get(citizen_1, rel=city.hasInhabitant, oclass=city.Building).__repr__())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Using the `get` and `iter` methods, it is also possible to discover the specific relationships that connect two individuals when a superclass of them is given." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "((,\n", " ),\n", " (,\n", " ),\n", " (,\n", " ),\n", " (,\n", " ),\n", " (,\n", " ),\n", " (,\n", " ),\n", " (,\n", " ))" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "freiburg.get(rel=owl.topObjectProperty, return_rel=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Operations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Operations are actions (written in Python) that can be executed on instances of specific ontology classes that they are defined for." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A great example of the applications of operations is the interaction with file objects in SimPhoNy wrappers that support it, for example, the included dataspace wrapper." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "from pathlib import Path\n", "from tempfile import TemporaryDirectory\n", "from urllib import request\n", "\n", "from IPython.display import Image\n", "\n", "from simphony_osp.wrappers import Dataspace\n", "\n", "dataspace_directory = TemporaryDirectory()\n", "example_directory = TemporaryDirectory()\n", "\n", "# Download a picture of Freiburg using urllib\n", "# from _Visit Freiburg_ - https://visit.freiburg.de\n", "url = (\n", " \"https://visit.freiburg.de/extension/portal-freiburg\"\n", " \"/var/storage/images/media/bibliothek/teaser-bilder-startseite\"\n", " \"/freiburg-kunst-kultur-copyright-fwtm-polkowski/225780-1-ger-DE\"\n", " \"/freiburg-kunst-kultur-copyright-fwtm-polkowski_grid_medium.jpg\"\n", ")\n", "file, response = request.urlretrieve(url)\n", "\n", "# Open a dataspace session in a temporary directory\n", "with Dataspace(dataspace_directory.name, True) as session:\n", " # Create an individual belonging to SimPhoNy's file class\n", " picture = simphony.File(\n", " iri='http://example.org/freiburg#my_picture'\n", " )\n", " \n", " # Use the `upload` operation to assign data to the file object\n", " picture.operations.upload(file)\n", " \n", " # Commit the changes\n", " session.commit()\n", "\n", "# Access the saved data and retrieve the Picture using the `download` operation\n", "with Dataspace(dataspace_directory.name, True) as session:\n", " picture = session.from_identifier('http://example.org/freiburg#my_picture')\n", " download_path = Path(example_directory.name) / 'my_picture.jpg'\n", " picture.operations.download(download_path)\n", " \n", "# Uncomment this line to show the downloaded picture\n", "# (you can do so by running the tutorial yourself using Binder)\n", "# Image(download_path)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.12" } }, "nbformat": 4, "nbformat_minor": 4 }