Note: If you have not already done so, please follow the Getting Ready instructions first before proceeding.
This tutorial provides a quick overview of the basic workflows of OML. Users will learn how to create an OML project, and within it, create a vocabulary for a simple domain (we use a Pizza domain since everyone is familiar with it), then use such vocabulary to describe knowledge (the pizzas sold by some pizza restaurant). Furthermore, users will learn how to build the project to check its logical consistency (e.g., vegetarian pizzas have no meat ingredients), and how to run queries on the described knowledge to answer business questions (e.g., how much ingredients were used? and how many vegan pizzas were sold?).
Note: the source files created in this tutorial are available for reference in this repository, but we encourage the reader to recreate them by following the instructions below.
-
Right click in the [=Model Explorer view=] and select New -> OML Project.
-
Enter the project name as
tutorial1. Press Next. -
Enter the project details as shown below. Press Finish.
-
The
tutorial1project should now be visible in the [=Model Explorer view=].
Note: The project creation process may take a few seconds. Rosetta will show the progress of project creation in the status bar (bottom-right). Wait until this process finishes.
Now, you will create a simple vocabulary for describing pizzas along with their bases and toppings. Different kinds of pizzas, bases, and toppings are modeled, along with their properties, interrelations, and restrictions.
-
Right click on the
tutorial1subfolder (highlighted in the picture above) in the [=Model Explorer view=] and select New -> OML Model. -
Enter the details of the
pizzavocabulary as shown below. Press Finish.
-
The
pizzavocabulary will be created and its OML editor opens as shown below.
-
Copy the following OML code and paste it as the new content of vocabulary
pizza.
@dc:description "A vocabulary about pizzas"
vocabulary <http://example.com/tutorial1/vocabulary/pizza#> as pizza {
extends <http://www.w3.org/2001/XMLSchema#> as xsd
extends <http://purl.org/dc/elements/1.1/> as dc
extends <http://www.w3.org/2000/01/rdf-schema#> as rdfs
// Identified Thing
@rdfs:comment "The class of things that are uniquely identified by id"
aspect IdentifiedThing [
key hasId
]
@rdfs:comment "The id property of an identified thing"
scalar property hasId [
domain IdentifiedThing
range xsd:string
functional
]
// Food
@rdfs:comment "The class of food items"
concept Food < IdentifiedThing
@rdfs:comment "A relation from a food to another used as an ingredient"
relation hasIngredient [
from Food
to Food
reverse isIngredientOf
transitive
]
// Spiciness
@rdfs:comment "An enumeration of spiciness levels"
scalar Spiciness [
oneOf "Hot", "Medium", "Mild"
]
@rdfs:comment "The spiciness property of a food item"
scalar property hasSpiciness [
domain Food
range Spiciness
functional
]
@rdfs:comment "The class of hot spiciness food"
concept HotFood = Food [
restricts hasSpiciness to "Hot"
]
@rdfs:comment "The class of medium spiciness food"
concept MediumFood = Food [
restricts hasSpiciness to "Medium"
]
@rdfs:comment "The class of mild spiciness food"
concept MildFood = Food [
restricts hasSpiciness to "Mild"
]
// Foods
@rdfs:comment "The class of pizzas"
concept Pizza < Food [
restricts hasBase to exactly 1 PizzaBase
]
@rdfs:comment "The class of pizza bases"
concept PizzaBase < Food
@rdfs:comment "The class of pizza toppings"
concept PizzaTopping < Food
@rdfs:comment "A relation from a pizza to a base"
relation hasBase [
from Pizza
to PizzaBase
reverse isBaseOf
functional
inverse functional
] < hasIngredient
@rdfs:comment "A relation from a pizza to a topping"
relation hasTopping [
from Pizza
to PizzaTopping
reverse isToppingOf
inverse functional
] < hasIngredient
// Pizzas
@rdfs:comment "The class of pizzas with some cheese toppings"
concept CheesyPizza = Pizza [
restricts some hasTopping to CheeseTopping
]
@rdfs:comment "The class of pizzas with some meat toppings"
concept MeatyPizza = Pizza [
restricts some hasTopping to MeatTopping
]
@rdfs:comment "The class of pizzas with all vegetarian toppings"
concept VegetarianPizza = Pizza [
restricts some hasTopping to VegetarianTopping
restricts all hasTopping to VegetarianTopping
]
@rdfs:comment "The class of American pizzas"
concept American < CheesyPizza, MeatyPizza [
restricts some hasTopping to MozzarellaTopping
restricts some hasTopping to SausageTopping
restricts some hasTopping to TomatoTopping
]
@rdfs:comment "The class of Veneziana pizzas"
concept Veneziana < CheesyPizza, VegetarianPizza [
restricts some hasTopping to MozzarellaTopping
restricts some hasTopping to TomatoTopping
restricts some hasTopping to SultanaTopping
]
@rdfs:comment "The class of Margherita pizzas"
concept Margherita < CheesyPizza, VegetarianPizza [
restricts some hasTopping to MozzarellaTopping
restricts some hasTopping to TomatoTopping
]
// Pizza Bases
@rdfs:comment "The class of deep pan bases"
concept DeepPanBase < PizzaBase
@rdfs:comment "The class of thin and crispy bases"
concept ThinAndCrispyBase < PizzaBase
// Pizza Toppings
@rdfs:comment "The class of meat toppings"
concept MeatTopping < PizzaTopping
@rdfs:comment "The class of vegetarian toppings"
concept VegetarianTopping < PizzaTopping
// Meat Topping
@rdfs:comment "The class sausage toppings"
concept SausageTopping < MeatTopping, MildFood
@rdfs:comment "The class spiced beef toppings"
concept SpicedBeefTopping < MeatTopping, HotFood
// Vegetarian Toppings
@rdfs:comment "The class sauce toppings"
concept SauceTopping < VegetarianTopping
@rdfs:comment "The class cheese toppings"
concept CheeseTopping < VegetarianTopping
@rdfs:comment "The class fruit toppings"
concept FruitTopping < VegetarianTopping
@rdfs:comment "The class vegetable toppings"
concept VegetableTopping < VegetarianTopping
// Sauce Toppings
@rdfs:comment "The class of tabasco toppings"
concept TabascoTopping < SauceTopping, HotFood
// Cheese Toppings
@rdfs:comment "The class of parmesan toppings"
concept ParmesanTopping < CheeseTopping, MildFood
@rdfs:comment "The class of mozzarella toppings"
concept MozzarellaTopping < CheeseTopping, MildFood
// Fruit Toppings
@rdfs:comment "The class of sultana toppings"
concept SultanaTopping < FruitTopping, MediumFood
// Vegetable Toppings
@rdfs:comment "The class of pepper toppings"
concept PepperTopping < VegetableTopping
@rdfs:comment "The class of tomato toppings"
concept TomatoTopping < VegetableTopping, MildFood
// Pepper Toppings
@rdfs:comment "The class of jalapeno pepper toppings"
concept JalapenoPepperTopping < PepperTopping, HotFood
@rdfs:comment "The class of sweet pepper toppings"
concept SweetPepperTopping < PepperTopping, MildFood
}Now, you will create a vocabulary bundle to enable logical closed-world reasoning on pizzas described using the pizza vocabulary. This automatically asserts that classes in the bundled vocabularies that do not have common subtypes are disjoint (have no intersection), which helps detect a wide class of errors that would otherwise not get detected due to the open-world assumption (whatever is not asserted may be true or false).
-
Right click on the
vocabularysubfolder in the [=Model Explorer view=] and select New -> OML Model. -
Enter the details of the
pizza-bundlevocabulary bundle as shown below. Press Finish.
-
The
pizza-bundlevocabulary bundle will be created and its OML editor opens as shown below.
-
Copy the following OML code and paste it as the new content of the vocabulary bundle.
@dc:description "A vocabulary bundle for closed-world reasoning about pizzas"
vocabulary bundle <http://example.com/tutorial1/vocabulary/pizza-bundle#> as pizza-bundle {
includes <http://purl.org/dc/elements/1.1/> as dc
// The pizza vocabulary bundle "includes" the pizza vocabulary
includes <http://example.com/tutorial1/vocabulary/pizza#>
}Now, you will create a description of the pizza instances baked by a particular pizza restaurant. The description will be done using terms from the pizza vocabulary above.
-
Right click on the
descriptionsubfolder in the [=Model Explorer view=] and select New -> OML Model. -
Enter the details of the
restaurantdescription as shown below. Press Finish.
-
The
restaurantdescription will be created and its OML editor opens as shown below.
-
Copy the following OML code and paste it as the new content of the description.
@dc:description "A description of the sales of a specific pizza restaurant"
description <http://example.com/tutorial1/description/restaurant#> as restaurant {
uses <http://purl.org/dc/elements/1.1/> as dc
// The restaurant description "uses" the pizza vocabulary terms in assertions
uses <http://example.com/tutorial1/vocabulary/pizza#> as pizza
// Pizza 1
instance pizza1 : pizza:American [
pizza:hasId "1"
pizza:hasBase : pizza:DeepPanBase []
pizza:hasTopping : pizza:TomatoTopping []
pizza:hasTopping : pizza:MozzarellaTopping []
pizza:hasTopping : pizza:SausageTopping []
]
// Pizza 2
instance pizza2 : pizza:American [
pizza:hasId "2"
pizza:hasBase : pizza:ThinAndCrispyBase []
pizza:hasTopping : pizza:TomatoTopping []
pizza:hasTopping : pizza:MozzarellaTopping []
pizza:hasTopping : pizza:SausageTopping []
]
// Pizza 3
instance pizza3 : pizza:Margherita [
pizza:hasId "3"
pizza:hasBase : pizza:ThinAndCrispyBase []
pizza:hasTopping : pizza:TomatoTopping []
pizza:hasTopping : pizza:MozzarellaTopping []
]
// Pizza 4
instance pizza4 : pizza:Margherita [
pizza:hasId "4"
pizza:hasBase : pizza:ThinAndCrispyBase []
pizza:hasTopping : pizza:TomatoTopping []
pizza:hasTopping : pizza:MozzarellaTopping []
pizza:hasTopping : pizza:SultanaTopping []
//pizza:hasTopping : pizza:SpicedBeefTopping []
]
// Pizza 5
instance pizza5 : pizza:VegetarianPizza [
pizza:hasId "5"
pizza:hasBase : pizza:DeepPanBase []
pizza:hasTopping : pizza:TabascoTopping []
pizza:hasTopping : pizza:MozzarellaTopping []
pizza:hasTopping : pizza:JalapenoPepperTopping []
]
// Pizza 6
instance pizza6 : pizza:VegetarianPizza [
pizza:hasId "6"
pizza:hasBase : pizza:DeepPanBase []
pizza:hasTopping : pizza:TomatoTopping []
pizza:hasTopping : pizza:JalapenoPepperTopping []
]
}Now, you will include the restaurant description in a description bundle that will be analyzed as a dataset with closed-world assumptions. This requires the description bundle to use the pizza vocabulary bundle above in order to reuse its closed world assumptions. Additionally, it automatically asserts that instances in the bundled descriptions are the only ones available in the world.
-
Double-click on the
description/bundle.omlfile in the [=Model Explorer view=] to open the editor (if not already open).
-
Copy the following OML code and paste it as the new content of description bundle.
@dc:description "A description bundle for closed-world reasoning about a restaurant"
description bundle <http://example.com/tutorial1/description/bundle#> as ^bundle {
uses <http://purl.org/dc/elements/1.1/> as dc
// The description bundle "uses" the vocabulary bundle world closure axioms
uses <http://example.com/tutorial1/vocabulary/pizza-bundle#>
// The description bundle "includes" the restaurant description
includes <http://example.com/tutorial1/description/restaurant#>
}Now, it is time to run the Gradle build task of the project to verify whether the description bundle is logically consistent (and uses vocabulary bundles that have satisfiable classes). The OML code we have so far should pass this test.
-
Click on the [=Gradle Tasks view=] and wait until the
tutorial1project shows up there (keep an eye on the loading message in the status bar bottom-right).
-
Expand the
tutorial1node followed by expanding theomlnode. -
Double-click on the
buildtask and wait for it to finish running in the [=Gradle Executions view=]. -
Inspect the build status in the [=Gradle Executions view=] and notice that it is all green icons.
Now, we will introduce a logical problem in the OML code above and see how the reasoner helps us detect it and explain it.
Introducing a problem
-
Click on the
restaurant.omleditor to bring it in focus. -
In line 22, change the
hasIdproperty value of instancepizza2to "1" (from "2"), to become like the value ofhasIdof instancepizza1(in line 12). Save the editor.
-
In the [=Gradle Tasks view=] double-click to rerun task
tutorial1/oml/buildagain, and wait for it to finish running in the [=Gradle Executions view=]. -
Inspect the build status in the [=Gradle Executions view=] and notice that it now shows a failure (red icons) on task
owlReason.
-
Right click on the
Execute run for :owlReasonred icon and selectShow Failuresfrom the context menu. The follow dialog shows up saying that some "Ontology is inconsistent. Check tutorial1/build/reports/reasoning.xml for more details". Click Close button.
-
In the [=Model Explorer view=], right click on the
tutorial1project and choose Refresh. Then, navigate to fileorial1/build/reports/reasoning.xmland double click on it. The file opens in theJunitview showing the problem as an inconsistency (on the left) and providing an explanation for it (on the right).
Explaining the problem
This problem demonstrates why it is useful to use a logical reasoner to detect inconsistencies that may otherwise be non-obvious or unexpected. When this occurs, the reasoner provides a brief description and a minimal ontology that demonstrates the problem.
In this case, the brief description is "an individual belongs to a type and its complement". This means there exists an individual (called an instance in OML) in the model that can be inferred, using the logical semantics of the used vocabulary, to be classified by two classes that are disjoint (i.e., do not have an intersection, or in other words are a complement of each other, hence cannot be types of the same instance).
Looking at the minimal ontology presented, we can figure out the cause of the problem. It says that relation hasBase is functional, meaning that it can have maximum one value for a given instance. Looking at the relevant snippet of the pizza vocabulary confirms that.
@rdfs:comment "A relation from a pizza to a base"
relation hasBase [
from Pizza
to PizzaBase
reverse isBaseOf
functional
inverse functional
] < hasIngredientMoreover, it says that an anonymous instance of type DeepPanBase is a value of property hasBase on instance pizza1, and an anonymous instance of type ThinAndCrispyBase is a value of property hasBase on instance pizza2. Looking at the relevant snippet of the restaurant description confirms that.
instance pizza1 : pizza:American [
pizza:hasId "1"
pizza:hasBase : pizza:DeepPanBase []
]
instance pizza2 : pizza:American [
pizza:hasId "1"
pizza:hasBase : pizza:ThinAndCrispyBase []
]So far so good, where is the issue then?
The explanation highlights that pizza1 and pizza2 have the same value ("1") of property hasId, which is defined by the pizza vocabulary to be functional (can have a maximum one value per instance). It infers from this that pizza1 and pizza2 are two names of a single pizza instance.
scalar property hasId [
domain IdentifiedThing
range xsd:string
functional
] In light of the above, and having established previously that property hasBase is functional, it follows logically that both aforementioned anonymous instances must be the same instance. But wait, we asserted in the restaurant description that those instances are typed by concepts DeepPanBase and ThinAndCrispyBase, respectively. This means now that the same base instance is typed by both these types.
However, the explanation also says that these two types are in fact disjoint. Where did it get this from? It turns out to be a generated assertion in the pizza-bundle vocabulary bundle. Such assertion is generated since the two types do not have a common subtype in the pizza vocabulary (included in the vocabulary bundle). Such closed-world semantics is a benefit of using a vocabulary bundle.
Now, let us put all those inferences together to understand the reported problem "an individual belongs to a type and its complement". It turns out that the individual here is nothing but those anonymous instances (individuas) and it is inferred to belong to type DeepPanBase and its complement type ThinAndCrispyBase, which is a logical inconsistency.
Fixing the problem
-
Let's now fix the problem by reverting the change we just did. Click on the
restauranteditor again and navigate to line 22 and restore the originalhasIdproperty value ofpizza2to "2". Save the editor. -
Click on the [=Gradle Tasks view=] and double-click to rerun the
tutorial1/oml/buildtask again and wait for it to finish running in the [=Gradle Executions view=]. -
Inspect the build status in the [=Gradle Executions view=] and notice that it is back to showing green icons.
Now that the model is consistent, it is time to get some value out of it by using it to answer business questions. To do that, we will run some queries in SPARQL on the model and inspect their results.
-
Navigate in the [=Model Explorer view=] to the
srcfolder, and right click on it and choose New -> Folder.
-
Enter the name of the folder as
sparqland press Finish. This creates a new foldersrc/sparqlin the [=Model Explorer view=].
-
Right click on the
src/sparqlfolder in the [=Model Explorer view=] and select New -> File.
-
Enter the name of the file as
NumberOfToppings.sparqland press Finish. This creates a new file under thesparqlfolder in the [=Model Explorer view=]. -
Paste the following SPARQL code as the content of the file editor and save it.
PREFIX pizza: <http://example.com/tutorial1/vocabulary/pizza#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?toppingKind (COUNT(?topping) as ?toppingCount)
WHERE {
?r pizza:hasTopping ?topping .
?topping a ?toppingKind .
?toppingKind rdfs:subClassOf pizza:PizzaTopping .
}
GROUP BY ?toppingKind- Repeat the previous steps to create a second query file called
WhichPizzaIsSpicy.sparqland this time use the following SPARQL code.
PREFIX pizza: <http://example.com/tutorial1/vocabulary/pizza#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT DISTINCT ?pizza
WHERE {
?pizza pizza:hasTopping [pizza:hasSpiceness "Hot"]
}- Repeat the previous steps to create a third query file called
WhichPizzaIsVegan.sparqland this time use the following SPARQL code.
PREFIX pizza: <http://example.com/tutorial1/vocabulary/pizza#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT DISTINCT ?pizza
WHERE {
?pizza a pizza:Pizza .
FILTER NOT EXISTS {
?pizza pizza:hasTopping ?nvt .
FILTER NOT EXISTS {
?nvt a pizza:VegetableTopping
}
}
}-
By now, you should see the following in the [=Model Explorer view=].
-
Before we can run the queries, we need to have a database server with a query endpoint running. To do that, click on the [=Gradle Tasks view=] and navigate to the task
tutorial1/oml/startFuseki. Double click the task and wait for it to finish running in the [=Gradle Executions view=].
Note: A Fuseki server should now be running locally on your machine.
-
In the [=Gradle Tasks view=], navigate to the task
tutorial1/oml/owlQueryand double click to run it and wait for it to finish running in the [=Gradle Executions view=]. This task first loads the description bundle to the Fuseki server, then runs on it all the queries from thesparqlfolder. -
In the [=Model Explorer view=], right click on the
tutorial1project and choose Refresh. Then, navigate to folderbuild/resultsto see the JSON files resulting from running the queries. Each one is named after one query. -
Double click on file
NumberOfToppings.jsonin the [=Model Explorer view=] to open its editor.
This query returned a table of two columns. The first column named
toppingKindrepresents the unique topping kinds (identified by their IRIs) that were used by the restaurant in making pizzas. The second column namedtoppingCountrepresents the total count of each topping kind that were used to make those pizzas.
This query returned a table of one column named
pizzawhich represents the pizzas (identified by their IRIs) that were considered spicy because the spiciness of one of their toppings was Hot.
This query returned a table of one column named
pizzawhich represents the pizzas (identified by their IRIs) that were considered vegan because none their toppings were non-vegetable.
- Now that we are done running queries, we can stop the Fuseki server by navigating to task
tutorial1/oml/stopFusekiin the [=Gradle Tasks view=]. Double click to run the task and wait for it to finish running in the [=Gradle Executions view=].
Note: This kills the Fuseki server process running on your machine.
This tutorial introduced the OML language, its Rosetta workbench, and its main modeling and analysis workflows. It demonstrated how OML can be used to define a semantic business vocabulary (pizza in this case) that can be used to describe knowledge (the pizzas made by a restaurant in this case), check its consistency and generate inferences with the help of a logical reasoner, and write queries to answer business questions. It also demonstrated how the Rosetta workbench can be used to author OML ontologies and run and inspect the results of analysis tasks (developed in Gradle), like the build task that invokes a logical reasoner, the startFusei and stopFuseki tasks to start/stop the Fuseki server, and the owlQuery task that runs SPARQL queries on the model.



