OML Tutorials

Living Document,

This version:
http://www.opencaesar.io/oml-tutorials/
Previous Versions:
Issue Tracking:
GitHub
Editor:
Maged Elaasar (JPL)
Copyright:
Copyright 2022, by the California Institute of Technology. ALL RIGHTS RESERVED. United States Government Sponsorship acknowledged. Any commercial use must be negotiated with the Office of Technology Transfer at the California Institute of Technology. This software may be subject to U.S. export control laws. By accepting this software, the user agrees to comply with all applicable U.S. export laws and regulations. User has the responsibility to obtain export licenses, or other export authority as may be required before exporting such information to foreign countries or providing access to foreign persons.

Abstract

This set of tutorials introduces the ontological approach to systems modeling and analysis using the Ontological Modeling Language (OML), developed by the Jet Propulsion Laboratory (JPL), and provided by the openCAESAR project. The overarching objective of the tutorials is to teach users about OML, its features, its tools, and its workflows. Each individual tutorial describes its own learning objectives. The kind of users who may find these tutorials interesting include a) methodology/domain architects and b) systems engineers. Previous experience using modeling languages (such as UML), ontology languages (such as RDF or OWL), or formal languages (such as Description Logic) would help but is not required. Also, prior experience using common tools (such as Eclipse, Terminal, Docker, and Gradle) helps but is not required.

Getting Ready

OML has a user-friendly textual grammar that can be edited using a simple text editor. However, for extra support like wizards, syntax highlighting, live validation, and content-assist, a user can use one of the OML editors provided by openCAESAR project. One such editor, called OML Rosetta, is a plugin to the Eclipse IDE. Another editor, called OML Luxor, is an extension to the VSCode IDE that can be used in a VSCode desktop client or browser-based client (like Gitpod). In this tutorial, we will demonstrate using the OML Rosetta editor, but the reader can follow using any other editor.

Note: that OML also has a UML-like graphical notation. Although we will not demonstrate how to create them, we will sometimes show OML diagrams to help visualize the models.

Furthermore, OML projects created by openCAESAR are Gradle projects that have OML analysis tools configured as Gradle tasks (in a build.gradle script). A user can choose to invoke such tools from a console/terminal session using the Gradle Wrapper CLI (e.g., ./gradlew <task>). Alternatively, the supported OML editors mentioned above allow invoking those Gradle tasks using a UI. We will demonstrate invoking Gradle tasks from the UI in Eclipse, but the reader can choose to invoke them using the CLI (or another UI) instead.

Install OML Rosetta

  1. Download the latest release of OML Rosetta archive that matches your OS from oml-rosetta.

  2. Unzip the archive to some folder on your local drive to get the Rosetta app.

    Note: If you’re on a Mac, run this command in the terminal to get around the issue that the app is not yet signed (a temporary issue).

    $xattr -cr <path/to/Rosetta.app>
    
  3. Navigate to the Rosetta app icon and double click to run it.

  4. When prompted to choose a workspace, create a new one in your local file system.

Run OML Rosetta

  1. Once Rosetta opens with the workspace, switch to the Modeling Perspective.

  1. Once the Modeling Perspective opens, make some extra views visible.

  1. This is how the Modeling Perspective should look like now.

Notice the following components of the Modeling Perspective above:

  1. Model Explorer view (top-left): shows your project files hierarchy.

  2. Properties view (bottom-right): shows detailed property sheet of the selection.

  3. Problems view (bottom-right): shows problems (errors, warnings) with your projects.

  4. Gradle Tasks view (bottom-right): shows the list of Gradle tasks in your projects.

  5. Gradle Executions view (bottom-right): shows the details of execution of Gradle tasks.

  6. Console view (bottom-right): shows messages printed by Gradle tasks.

  7. Editors (top-right): this area shows the open editors (if any).

  8. Outline view (bottom-left): shows the outline of content in the active editor.

  9. Turn on the Show line number preference by navigating to the Preferences dialog in the main menu bar (on a Mac, you will find it under the Rosetta menu; on Windows, you will find it under the Windows menu).

1. Tutorial 1: OML Basics

Note: If you have not already done so, please follow the Getting Ready instructions first before proceeding.

1.1. Learning Objectives

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.

1.2. Create OML Project

  1. Right click in the Model Explorer view and select New -> OML Project.

  2. Enter the project name as tutorial1. Press Next.

  3. Enter the project details as shown below. Press Finish.

  4. The tutorial1 project 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.

  1. Expand the tutorial1 project node in the Model Explorer view as shown in the image below.

1.3. Create OML Vocabulary

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.

  1. Right click on the tutorial1 subfolder (highlighted in the picture above) in the Model Explorer view and select New -> OML Model.

  2. Enter the details of the pizza vocabulary as shown below. Press Finish.

  3. The pizza vocabulary will be created and its OML editor opens as shown below.

  4. 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

    // Top Level

    @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
    ]   

    // Identified Things

    @rdfs:comment "The class of food items"
    concept Food < IdentifiedThing

    @rdfs:comment "A relation from a food to another used as an ingredient"
    relation entity HasIngredient [
        from Food
        to Food
        forward hasIngredient
        reverse isIngredientOf
        transitive
    ]

    @rdfs:comment "An enumeration of spiciness levels"
    scalar Spiciness [
        oneOf "Hot", "Medium", "Mild"
    ]

    @rdfs:comment "The spiciness property of a food item"
    scalar property hasSpiceness [
        domain Food
        range Spiciness
        functional
    ]
    
    // Foods
    
    @rdfs:comment "The class of pizzas"
    concept Pizza < Food [
        restricts some hasBase to 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 entity HasBase [
        from Pizza
        to PizzaBase
        forward hasBase
        reverse isBaseOf
        functional
        inverse functional
    ] < HasIngredient 

    @rdfs:comment "A relation from a pizza to a topping"
    relation entity HasTopping [
        from Pizza
        to PizzaTopping
        forward hasTopping
        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 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, MeatyPizza [
        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

    @rdfs:comment "The class of hot spiciness toppings"
    concept HotTopping < PizzaTopping [
        restricts hasSpiceness to "Hot"
    ]

    @rdfs:comment "The class of medium spiciness toppings"
    concept MediumTopping < PizzaTopping [
        restricts hasSpiceness to "Medium"
    ]

    @rdfs:comment "The class of mild spiciness toppings"
    concept MildTopping < PizzaTopping [
        restricts hasSpiceness to "Mild"
    ]

    // Meat Topping

    @rdfs:comment "The class sausage toppings"
    concept SausageTopping < MeatTopping, MildTopping
    @rdfs:comment "The class spiced beef toppings"
    concept SpicedBeefTopping < MeatTopping, HotTopping
    
    // Vegetarion 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 TobascoTopping < SauceTopping, HotTopping

    // Cheese Toppings

    @rdfs:comment "The class of parmesan toppings"
    concept ParmesanTopping < CheeseTopping, MildTopping
    @rdfs:comment "The class of mozzarella toppings"
    concept MozzarellaTopping < CheeseTopping, MildTopping

    // Fruit Toppings

    @rdfs:comment "The class of sultana toppings"
    concept SultanaTopping < FruitTopping, MediumTopping
    
    // Vegetable Toppings

    @rdfs:comment "The class of pepper toppings"
    concept PepperTopping < VegetableTopping
    @rdfs:comment "The class of tomatoe toppings"
    concept TomatoTopping < VegetableTopping, MildTopping

    // Pepper Toppings

    @rdfs:comment "The class of jalapeno pepper toppings"
    concept JalapenoPepperTopping < PepperTopping, HotTopping
    @rdfs:comment "The class of sweet pepper toppings"
    concept SweetPepperTopping < PepperTopping, MildTopping
}

1.4. Create OML Vocabulary Bundle

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).

  1. Right click on the vocabulary subfolder in the Model Explorer view and select New -> OML Model.

  2. Enter the details of the pizza-bundle vocabulary bundle as shown below. Press Finish.

  3. The pizza-bundle vocabulary bundle will be created and its OML editor opens as shown below.

  4. 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#>

}

1.5. Create OML Description

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.

  1. Right click on the description subfolder in the Model Explorer view and select New -> OML Model.

  2. Enter the details of the restaurant description as shown below. Press Finish.

  3. The restaurant description will be created and its OML editor opens as shown below.

  4. 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 base1 : pizza:DeepPanBase
    instance topping1-1 : pizza:TomatoTopping
    instance topping1-2 : pizza:MozzarellaTopping
    instance topping1-3 : pizza:SausageTopping
    instance pizza1 : pizza:American [
        pizza:hasId "1"
        pizza:hasBase base1
        pizza:hasTopping topping1-1
        pizza:hasTopping topping1-2
        pizza:hasTopping topping1-3
    ]
    
    // Pizza 2

    instance base2 : pizza:ThinAndCrispyBase
    instance topping2-1 : pizza:TomatoTopping
    instance topping2-2 : pizza:MozzarellaTopping
    instance topping2-3 : pizza:SausageTopping
    instance pizza2 : pizza:American [
        pizza:hasId "2"
        pizza:hasBase base2
        pizza:hasTopping topping2-1
        pizza:hasTopping topping2-2
        pizza:hasTopping topping2-3
    ]
        
    // Pizza 3

    instance base3 : pizza:ThinAndCrispyBase
    instance topping3-1 : pizza:TomatoTopping
    instance topping3-2 : pizza:MozzarellaTopping
    instance pizza3 : pizza:Margherita [
        pizza:hasId "3"
        pizza:hasBase base3
        pizza:hasTopping topping3-1
        pizza:hasTopping topping3-2
    ]

    // Pizza 4

    instance base4 : pizza:ThinAndCrispyBase
    instance topping4-1 : pizza:TomatoTopping
    instance topping4-2 : pizza:MozzarellaTopping
    instance topping4-3 : pizza:SultanaTopping
    instance topping4-4 : pizza:SpicedBeefTopping
    instance pizza4 : pizza:Veneziana [
        pizza:hasId "4"
        pizza:hasBase base4
        pizza:hasTopping topping4-1
        pizza:hasTopping topping4-2
        pizza:hasTopping topping4-3
        pizza:hasTopping topping4-4
    ]

    // Pizza 5

    instance base5 : pizza:DeepPanBase
    instance topping5-1 : pizza:TobascoTopping
    instance topping5-2 : pizza:ParmesanTopping
    instance topping5-3 : pizza:JalapenoPepperTopping
    instance pizza5 : pizza:VegetarianPizza [
        pizza:hasId "5"
        pizza:hasBase base5
        pizza:hasTopping topping5-1
        pizza:hasTopping topping5-2
        pizza:hasTopping topping5-3
    ]

    // Pizza 6

    instance base6 : pizza:DeepPanBase
    instance topping6-1 : pizza:TomatoTopping
    instance topping6-2 : pizza:JalapenoPepperTopping
    instance pizza6 : pizza:VegetarianPizza [
        pizza:hasId "6"
        pizza:hasBase base6
        pizza:hasTopping topping6-1
        pizza:hasTopping topping6-1
    ]

}

1.6. Edit OML Description Bundle

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.

  1. Double-click on the description/bundle.oml file in the Model Explorer view to open the editor (if not already open).

  2. 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#>
    
}

1.7. Run Build Task

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.

  1. Click on the Gradle Tasks view and wait until the tutorial1 project shows up there (keep an eye on the loading message in the status bar bottom-right).

  2. Expand the tutorial1 node followed by expanding the oml node.

  3. Double-click on the build task and wait for it to finish running in the Gradle Executions view.

  1. Inspect the build status in the Gradle Executions view and notice that it is all green icons.

1.8. Fix Logical Inconsistency

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

  1. Click on the restaurant.oml editor to bring it in focus.

  2. In line 30, change the hasId property value of instance pizza2 to "1" (from "2"), to become like the value of hasId of instance pizza1 (in line 16). Save the editor.

  3. In the Gradle Tasks view double-click to rerun task tutorial1/oml/build again, and wait for it to finish running in the Gradle Executions view.

  4. Inspect the build status in the Gradle Executions view and notice that it now shows a failure (red icons) on task owlReason.

  5. Right click on the Execute run for :owlReason red icon and select Show Failures from 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.

  6. In the Model Explorer view, right click on the tutorial1 project and choose Refresh. Then, navigate to file orial1/build/reports/reasoning.xml and double click on it. The file opens in the Junit view 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 entity HasBase [
    from Pizza
    to PizzaBase
    forward hasBase
    reverse isBaseOf
    functional
    inverse functional
] < HasIngredient

Moreover, it says that instance base1 is a value of property hasBase on instance pizza1, and instance base2 is a value of property hasBase on instance pizza2. Looking at the relevant snippet of the restaurant description confirms that.

instance base1 : pizza:DeepPanBase
instance pizza1 : pizza:American [
    pizza:hasId "1"
    pizza:hasBase base1
]
instance base2 : pizza:ThinAndCrispyBase
instance pizza2 : pizza:American [
    pizza:hasId "1"
    pizza:hasBase base2
]

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 base1 and base2 must be two names of the same base individual. 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 known by the two names base1 and base2 and it is inferred to belong to type DeepPanBase and its complement type ThinAndCrispyBase, which is a logical inconsistency.

Fixing the problem

  1. Let’s now fix the problem by reverting the change we just did. Click on the restaurant editor again and navigate to line 30 and restore the original hasId property value of pizza2 to "2". Save the editor.

  2. Click on the Gradle Tasks view and double-click to rerun the tutorial1/oml/build task again and wait for it to finish running in the Gradle Executions view.

  3. Inspect the build status in the Gradle Executions view and notice that it is back to showing green icons.

1.9. Run Query Task

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.

  1. Navigate in the Model Explorer view to the src folder, and right click on it and choose New -> Folder.

  2. Enter the name of the folder as sparql and press Finish. This creates a new folder src/sparql in the Model Explorer view.

  3. Right click on the src/sparql folder in the Model Explorer view and select New -> File.

  4. Enter the name of the file as NumberOfToppings.sparql and press Finish. This creates a new file under the sparql folder in the Model Explorer view.

  5. 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
  1. Repeat the previous steps to create a second query file called WhichPizzaIsSpicy.sparql and 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"]
    
}
  1. Repeat the previous steps to create a third query file called WhichPizzaIsVegan.sparql and 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 
       }
    } 
}
  1. By now, you should see the following in the Model Explorer view.

  2. 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.

  1. In the Gradle Tasks view, navigate to the task tutorial1/oml/owlQuery and 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 the sparql folder.

  2. In the Model Explorer view, right click on the tutorial1 project and choose Refresh. Then, navigate to folder build/results to see the JSON files resulting from running the queries. Each one is named after one query.

  1. Double click on file NumberOfToppings.json in the Model Explorer view to open its editor.

This query returned a table of two columns. The first column named toppingKind represents the unique topping kinds (identified by their IRIs) that were used by the restaurant in making pizzas. The second column named toppingCount represents the total count of each topping kind that were used to make those pizzas.

  1. Double click on file WhichPizzaIsSpicy.json in the Model Explorer view to open its editor.

This query returned a table of one column named pizza which represents the pizzas (identified by their IRIs) that were considered spicy because the spiciness of one of their toppings was Hot.

  1. Double click on file WhichPizzaIsVegan.json in the Model Explorer view to open its editor.

This query returned a table of one column named pizza which represents the pizzas (identified by their IRIs) that were considered vegan because none their toppings were non-vegetable.

  1. Now that we are done running queries, we can stop the Fuseki server by navigating to task tutorial1/oml/stopFuseki in 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.

1.10. Summary

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.

2. Tutorial 2: OML Patterns

Note: This tutorial builds on Tutorial 1. Please do that first before proceeding.

2.1. Learning Objectives

This tutorial demonstrates the process of developing a methodology for capturing knowledge in a given business domain with OML. The methodology will be developed as a series of patterns, each of which represents a small step in the methodology and is encoded by some new vocabulary. As an example, we will develop a simple systems engineering methodology. The tutorial also demonstrates describing knowledge using instances of those patterns and organizing them into modules that capture related concerns. As an example, we will describe a fanciful space mission called Kepler16b, which is an exoplanet orbiting a binary star system called Kepler16—approximately 245 light-years from Earth.

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.

2.2. Create OML Project

We will start by creating an OML project that has a vocabulary bundle and a description bundle that uses it.

  1. In the [=Model Explorer view], right click and choose New -> OML Project.

  2. Name the project tutorial2. Click Next.

  3. Fill the OML project details as seen below. Click Finish.

  4. In the Model Explorer view, double click on the build.gradle file, and modify the declared dependency on core-vocabulary to metrology-vocabulary instead. Save the editor.

Note: specifying + as a dynamic version for metrology-vocabularies will result in downloding the latest version. However, a safer approach would be to pin the dependency to the current major revision (e.g., 7.+) to protect the project against future incompatible major revisions of metrology-vocabularies. At the time of this writing, the major revision for that library was 7.+ but this could be different when you take the tutorial. Click here to check what the latest version at this time is.

  1. In the Model Explorer view, expand the tutorial2 project, right-click on the src/oml/example.com/tutorial2 folder, choose New -> OML Model and fill the OML model details as shown below. Click Finish.

  2. In the Model Explorer view, double click on the file src/oml/example.com/tutorial2/description/bundle.oml to open its editor. Paste the following OML code as the contents of the file.

description bundle <http://example.com/tutorial2/description/bundle#> as ^bundle {
	
	uses <http://example.com/tutorial2/vocabulary/bundle#>
}

Note: since we be running SPARQL queries for every pattern, we will run a Fuseki server once now, and keep it running till the end of the tutorial when we will stop it.

  1. From the Gradle Tasks view, run the task tutorial2/oml/startFuseki and wait until it finishes execution in the Gradle Executions view. It should run successfully with no errors.

Note: you should now be ready to create the patterns below. For each pattern, we give its synopsis, the new vocabulary required to support it, the new descriptions to use it, and finally the queries that we can analyze it.

2.3. P1: Objective aggregates Objective

Pattern Synopsis

A systems engineering endeavor begins with objectives to be achieved. Objectives are not requirements; they are desires. They may be in conflict. They may not be achievable in principle. They may not be feasible. They may be related such that achieving one objective helps to achieve another. We call this relationship aggregates, which could be important for planning a campaign of pursuit. Aggregates is a general relationship, broader than objectives, but is homomeric, meaning that parts and whole are of the same type. We say an Objective is an AggregatedThing, meaning it can aggregate or be aggregated. We further say an Objective aggregates only Objectives and is aggregated in only Objectives (this is called a restriction in OML).

New Vocabulary

We will create two vocabularies and add them to the vocabulary bundle. The first vocabulary is called base, which we will use to define basic patterns, and the second is called mission, which we will use to describe patterns related to missions in systems engineering. We will then add to them the details of pattern P1.

  1. Create a vocabulary with the IRI <http://example.com/tutorial2/vocabulary/base#> and prefix base. Copy the following OML code as its contents. Save the editor.

vocabulary <http://example.com/tutorial2/vocabulary/base#> as base {

	extends <http://www.w3.org/2000/01/rdf-schema#> as rdfs
	extends <http://www.w3.org/2001/XMLSchema#> as xsd
}
  1. Create a vocabulary with the IRI <http://example.com/tutorial2/vocabulary/mission#> and prefix mission. Copy the following OML code as its contents. Save the editor.

vocabulary <http://example.com/tutorial2/vocabulary/mission#> as mission {
	
	extends <http://www.w3.org/2000/01/rdf-schema#> as rdfs
	extends <http://example.com/tutorial2/vocabulary/base#> as base
}
  1. Open the vocabulary/bundle editor, Copy the follow OML code as its contents. Save the editor.

vocabulary bundle <http://example.com/tutorial2/vocabulary/bundle#> as ^bundle {
	
	includes <http://example.com/tutorial2/vocabulary/mission#>
}

Note: how we only added the mission vocabulary, not the base vocabulary, to the bundle. This is because in OML, import statements (like includes, extends, and uses) are transitive. Since mission already imports (extends) base, the bundle would transitively include base as well. But It would not be wrong to explicitly include base in the bundle too.

  1. if you did all the previous steps correctly, the following should be the contents of all files so far.

  2. In the vocabulary/base ontology, append the following OML code to its body (i.e., insert it before the closing } bracket):

@rdfs:comment "The class of things having an id and a canonical name"aspect IdentifiedThing [  key hasIdentifier]@rdfs:comment "The has canonical name property"scalar property hasCanonicalName [  domain IdentifiedThing  range xsd:string]@rdfs:comment "The has identifier property"scalar property hasIdentifier [  domain IdentifiedThing  range xsd:string  functional]@rdfs:comment "The has description property"scalar property hasDescription [  domain IdentifiedThing  range xsd:string] @rdfs:comment "The class of things that can be aggregated"aspect AggregatedThing@rdfs:comment "The aggregates relation between aggregated things"relation entity Aggregates [  from AggregatedThing  to AggregatedThing  forward aggregates  reverse isAggregatedIn  asymmetric  irreflexive]

Note: the syntax used for annotations on ontology members above (e.g., @rdfs:comment "value" used to put a comment on a vocabulary member). What comes after the @ is the IRI of an annotation property (declared in some vocabulary) followed by a (literal or IRI) value.

Note: the key axiom in the IdentifiedThing aspect. It says that instances of this type must have unique values for their hasIdentifier property (which is similar to the concept of primary key in relational schemas). For this to work as expected, properties that are part of a key needs to be defined as functional, meaning that may have a maximum of one value. Otherwise, two instances with different values for hasIdentifier may still be inferred as aliases to the same instance with twos values for the key property.

  1. In the vocabulary/mission ontology, append the following OML code to its body:

@rdfs:comment "An Objective represents a specific interest 
that one or more stakeholders have in the successful execution of a mission."
concept Objective < base:IdentifiedThing, base:AggregatedThing [
	restricts all base:aggregates to Objective
	restricts all base:isAggregatedIn to Objective
]
  1. This is a visualization of the vocabularies you created so far.

    Base Vocabulary Mission Vocabulary
  2. Let us check that our ontologies are good so far, by running the task tutorial2/oml/build from the Gradle Tasks view, and waiting for it to finish running in the Gradle Executions view. This should run with no errors.

New Description

We will now create a new description model for the objectives of the Kepler16 mission, then add it to the description bundle. Each description is identified with an id and a canonical name and may specify which other objective it aggregates.

  1. Create a description with the IRI <http://example.com/tutorial2/description/objectives#> and prefix objectives. Copy the following OML code as its contents. Save the editor.

description <http://example.com/tutorial2/description/objectives#> as objectives {

	uses <http://example.com/tutorial2/vocabulary/base#> as base
	uses <http://example.com/tutorial2/vocabulary/mission#> as mission

	instance characterize-atmosphere : mission:Objective [
		base:hasIdentifier "O.01"
		base:hasCanonicalName "Characterize the atmosphere of Kepler 16b"
		base:aggregates characterize-liquid-ocean
	]
	instance characterize-liquid-ocean : mission:Objective [
		base:hasIdentifier "O.02"
		base:hasCanonicalName "Characterize the liquid ocean of Kepler 16b"
	]
	instance characterize-gravitational-field : mission:Objective [
		base:hasIdentifier "O.03"
		base:hasCanonicalName "Characterize the gravitational field of Kepler 16b"
		base:aggregates characterize-liquid-ocean
		base:aggregates characterize-rocky-core
	]
	instance characterize-rocky-core : mission:Objective [
		base:hasIdentifier "O.04"
		base:hasCanonicalName "Characterize the rocky core of Kepler 16b"
		base:aggregates characterize-rocky-core-density
		base:aggregates characterize-rocky-core-shape
	]
	instance characterize-rocky-core-density : mission:Objective [
		base:hasIdentifier "O.05"
		base:hasCanonicalName "Characterize the core density of Kepler 16b"
	]
	instance characterize-rocky-core-shape : mission:Objective [
		base:hasIdentifier "O.06"
		base:hasCanonicalName "Characterize the core shape of Kepler 16b"
	]
	instance characterize-environment : mission:Objective [
		base:hasIdentifier "O.07"
		base:hasCanonicalName "Characterize the energetic particule environment of the Kepler 16b binary star system"
		base:aggregates characterize-liquid-ocean
	]
}
  1. This is a visualization of the descriptions you created so far.

    Objectives Desrciption
  2. Open the description/bundle editor, Append the follow OML code to the body. Save the editor.

includes <http://example.com/tutorial2/description/objectives#>
  1. Let us check that our ontologies are still good, by running the task tutorial2/oml/build from the Gradle Tasks view, and waiting for it to finish running in the Gradle Executions view. This should run with no errors.

New Queries

Now that we have defined the vocabulary of the first pattern, and used it in the mission description, we will create a SPARQL query to extract the pattern instances from the description.

  1. Create the file src/sparql/objectives.sparql and copy the following SPARQL code as its content. It looks for objectives in the model and selects their ids and names.

PREFIX base:        <http://example.com/tutorial2/vocabulary/base#>
PREFIX mission:     <http://example.com/tutorial2/vocabulary/mission#>

SELECT DISTINCT ?o1_id ?o1_name ?o2_id ?o2_name
WHERE {
    ?o1 a mission:Objective ;
		base:hasIdentifier ?o1_id ;
		base:hasCanonicalName ?o1_name ;
		base:aggregates [
			base:hasIdentifier ?o2_id ;
			base:hasCanonicalName ?o2_name
		]
}
ORDER BY ?o1_id ?o2_id
  1. Let’s now run this query by running the task tutorial2/oml/owlQuery from the Gradle Tasks view and waiting for it to finish execution in the Gradle Executions view. It should run with no errors.

  2. Right click on the project in the Model Explorer view and select Refresh. Navigate to the file build/results/objectives.json and double click it to open its editor. You should see the following results in JSON.

  3. With this JSON results, one could develop a visualization like the following:

Note: the visualization code is not part of this tutorial

o1_id o1_name o2_id o2_name
O.01 Characterize the atmosphere of Kepler 16b O.02 Characterize the liquid ocean of Kepler 16b
O.03 Characterize the gravitational field of Kepler 16b O.02 Characterize the liquid ocean of Kepler 16b
O.03 Characterize the gravitational field of Kepler 16b O.04 Characterize the rocky core of Kepler 16b
O.04 Characterize the rocky core of Kepler 16b O.05 Characterize the core density of Kepler 16b
O.04 Characterize the rocky core of Kepler 16b O.06 Characterize the core shape of Kepler 16b
O.07 Characterize the energetic particule environment of the Kepler 16b binary star system O.02 Characterize the liquid ocean of Kepler 16b

2.4. P2: Mission pursues Objective

Pattern Synopsis

We undertake missions to pursue objectives. Again, objectives are not requirements. Part of the job of a mission is to negotiate an achievable set of objectives. Every mission pursues zero or more objectives. The lower bound is zero in the vocabulary because one-or-more is really a life-cycle completeness constraint. The notion of mission makes sense even if we don’t know what its objectives are. Every objective may be pursued by zero or more missions.

New Vocabulary

  1. In the body of ontology vocabulary/mission, append the following OML code, which adds the concept of a Mission with relation entity Purses, after the existing concept of Objective. Save the editor.

@rdfs:comment "A Mission pursues Objectives."
concept Mission < base:IdentifiedThing

@rdfs:comment "A Mission pursues zero or more Objectives."
relation entity Pursues [
	from Mission
	to Objective
	forward pursues
	reverse isPursuedBy
	asymmetric
	irreflexive
]

Note: that relation entity Purses is a reified relation (i.e., a class of relation instances), where its forward purses and its reverse isPursedBy are unreified relations (i.e., simple references). In a description model, either a reified or an unreified version of the relation can be used. The former is useful when it is desired to give the link a name and other characterizations.

Note: the semantic (logical) flags specified on the relation entity Pursues. The first flag reverse functional means that an Objective (the target of the relation) can be pursued by a max of one Mission (the source of the relation). The second flag asymmetric means that if a mission pursues an objective, then it would be illogical to infer that the objective pursues the mission (remember with open world assumptions, anything can be inferred unless you state otherwise). The third flag irreflexive means that an instance cannot be related to itself by this relation.

  1. The following is a visualization of the mission vocabulary so far:

  2. Let us check that our ontologies are good so far, by running the task tutorial2/oml/build from the Gradle Tasks view, and waiting for it to finish running in the Gradle Executions view. This should run with no errors.

New Description

Let us now use the pattern in describing a couple of missions in kepler6b.

  1. Create a description with the IRI <http://example.com/tutorial2/description/missions#> and prefix missions. Copy the following OML code as its contents. Save the editor.

description <http://example.com/tutorial2/description/missions#> as missions {

	uses <http://example.com/tutorial2/vocabulary/base#> as base
	uses <http://example.com/tutorial2/vocabulary/mission#> as mission
	extends <http://example.com/tutorial2/description/objectives#> as objectives

	instance orbiter : mission:Mission [
		base:hasIdentifier "M.01"
		base:hasCanonicalName "Orbiter Mission"
		mission:pursues objectives:characterize-atmosphere
		mission:pursues objectives:characterize-environment
		mission:pursues objectives:characterize-gravitational-field
	]
	instance lander : mission:Mission [
		base:hasIdentifier "M.02"
		base:hasCanonicalName "Lander Mission"
		mission:pursues objectives:characterize-atmosphere
		mission:pursues objectives:characterize-environment
	]
}
  1. The following is a visualization of the missions description we just created.

  2. Append the following OML code to the body of description/bundle to include the new missions ontology. Save the editor.

includes <http://example.com/tutorial2/description/missions#>
  1. Let us check that our ontologies are good so far, by running the task tutorial2/oml/build from the Gradle Tasks view, and waiting for it to finish running in the Gradle Executions view. This should run with no errors.

New Queries

Now we will create a SPARQL query to extract the pattern instances from the description.

  1. Creat the file src/sparql/missions.sparql and copy the following SPARQL code as its content. It looks for missions that purses objectives.

PREFIX base:        <http://example.com/tutorial2/vocabulary/base#>
PREFIX mission:     <http://example.com/tutorial2/vocabulary/mission#>

SELECT DISTINCT ?m_id ?m_name ?o_id ?o_name
WHERE {
	?m a mission:Mission ;
		base:hasIdentifier ?m_id ;
		base:hasCanonicalName ?m_name ;
		mission:pursues [
        a mission:Objective ;
        base:hasIdentifier ?o_id ;
    	  base:hasCanonicalName ?o_name
    ]
}
ORDER BY ?m_id ?o_id
  1. Let’s now run this query by running the task tutorial2/oml/owlQuery from the Gradle Tasks view and waiting for it to finish execution in the Gradle Executions view. It should run with no errors.

  2. Right click on the project in the Model Explorer view and select Refresh. Navigate to the file build/results/missions.json and double click it to open its editor. You should see the following results in JSON.

  3. With this JSON results, one could develop visualizations like the following:

Note: the visualization code is not part of this tutorial

2.5. P3: Mission deploys Component

Pattern Synopsis

We say a mission deploys components, which are typically the major systems of the mission. In our case, these are Launch System, Spacecraft, etc. Deploys is a whole-part relationship, but allows more than one mission to deploy the same component, as in for example, a shared mission operates a system for coordinated ops.

New Vocabularies

  1. Append the following OML code, which adds the concept of a Component with the relation Deploys to the body of the mission vocabulary. Save the editor.

@rdfs:comment "A Component is something that can be deployed in a mission."
concept Component < base:IdentifiedThing

@rdfs:comment "A Mission deploys zero or more Components."
relation entity Deploys [
	from Mission
	to Component
	forward deploys
	reverse isDeployedBy
	asymmetric
	irreflexive
]
  1. The following is a visualization of the mission vocabulary so far:

  2. Let us check that our ontologies are good so far, by running the task tutorial2/oml/build from the Gradle Tasks view, and waiting for it to finish running in the Gradle Executions view. This should run with no errors.

New Descriptions

We will now add more details to the missions description using the pattern above. Specifically, we will add components and specify that the mission deploys some of them (the roots of component containment as shown in pattern P4 later).

  1. Create a description with the IRI <http://example.com/tutorial2/description/components#> and prefix components. Copy the following OML code as its contents. Save the editor.

description <http://example.com/tutorial2/description/components#> as components {

	uses <http://example.com/tutorial2/vocabulary/base#> as base
	uses <http://example.com/tutorial2/vocabulary/mission#> as mission

	instance orbiter-launch-system : mission:Component [
		base:hasIdentifier "C.01"
		base:hasCanonicalName "Orbiter Launch System"
	]
	instance orbiter-spacecraft : mission:Component [
		base:hasIdentifier "C.02"
		base:hasCanonicalName "Orbiter Spacecraft"
	]
	instance orbiter-power-subsystem : mission:Component [
		base:hasIdentifier "C.02.01"
		base:hasCanonicalName "Orbiter Power Subsystem"
	]
	instance orbiter-harness : mission:Component [
		base:hasIdentifier "C.02.02"
		base:hasCanonicalName "Orbiter Harness"
	]
	instance orbiter-thermal-subsystem : mission:Component [
		base:hasIdentifier "C.02.03"
		base:hasCanonicalName "Orbiter Thermal Subsystem"
	]
	instance orbiter-command-and-data-handling-subsystem : mission:Component [
		base:hasIdentifier "C.02.04"
		base:hasCanonicalName "Orbiter C&DH Subsystem"
	]
	instance orbiter-telecom-subsystem : mission:Component [
		base:hasIdentifier "C.02.05"
		base:hasCanonicalName "Orbiter Telecom Subsystem"
	]
	instance orbiter-guidance-navigation-control-subsystem : mission:Component [
		base:hasIdentifier "C.02.06"
		base:hasCanonicalName "Orbiter GN&C Subsystem"
	]
	instance orbiter-mechanical-subsystem : mission:Component [
		base:hasIdentifier "C.02.07"
		base:hasCanonicalName "Orbiter Mechanical Subsystem"
	]
	instance orbiter-spacraft-flight-software : mission:Component [
		base:hasIdentifier "C.02.08"
		base:hasCanonicalName "Orbiter Flight Software"
	]
	instance orbiter-propulsion-subsystem : mission:Component [
		base:hasIdentifier "C.02.09"
		base:hasCanonicalName "Orbiter Propulsion Subsystem"
	]
	instance orbiter-ground-data-system : mission:Component [
		base:hasIdentifier "C.03"
		base:hasCanonicalName "Orbiter Ground Data System"
	]
	instance mission-operations-system : mission:Component [
		base:hasIdentifier "C.04"
		base:hasCanonicalName "Mission Operations System"
	]
	instance lander-launch-system : mission:Component [
		base:hasIdentifier "C.05"
		base:hasCanonicalName "Lander Launch System"
	]
	instance lander-spacecraft : mission:Component [
		base:hasIdentifier "C.06"
		base:hasCanonicalName "Lander Spacecraft"
	]
	instance lander-ground-data-system : mission:Component [
		base:hasIdentifier "C.07"
		base:hasCanonicalName "Lander Ground Data System"
	]
}
  1. In description/missions, append the following OML code to the body of the description.

ref instance orbiter [
	mission:deploys components:orbiter-launch-system
	mission:deploys components:orbiter-spacecraft
	mission:deploys components:orbiter-ground-data-system
	mission:deploys components:mission-operations-system
]
ref instance lander [
	mission:deploys components:lander-launch-system
	mission:deploys components:lander-spacecraft
	mission:deploys components:lander-ground-data-system
	mission:deploys components:mission-operations-system
]

Note: The usage of the OML keyword ref before the instance keyword in the OML code above. It is used to reference an instance defined elsewhere (in this case in the same description). Alternatively, we could have added the mission:deploys statements directly to the instance definitions above. However, the chosen style allows us to decouple the pattern instances for the sake of this tutorial.

Note: The OML code above will show error markers because the components ontology is not imported yet. The next step has the fix.

  1. In description/missions, add the following OML statement right after the existing extends statement (at the top). Save the editor.

extends <http://example.com/tutorial2/description/components#> as components

Note: This should clear the errors from the previous step.

  1. The following is a visualization of the components and missions descriptions so far:

    Components

    Missions

  2. Append the following OML code to the body of description/bundle to include the new components ontology. Save the editor.

includes <http://example.com/tutorial2/description/components#>
  1. Let us check that our ontologies are good so far, by running the task tutorial2/oml/build from the Gradle Tasks view, and waiting for it to finish running in the Gradle Executions view. This should run with no errors.

New Queries

Note: No new queries for this pattern. We will incorporate it with another pattern below.

2.6. P4: Component contains Component

Pattern Synopsis

Contains is another whole-part relationship, but unlike Deploys, a part can be contained in at most one whole. Like Aggregates, Contains is homomeric, meaning parts and whole are of the same type. We say a Component is a ContainedThing, meaning it can contain or be contained. We further say a Component contains only Components and is contained in only a Component.

New Vocabularies

  1. Since Contains is a fundamental relation, like Aggregates, let us add it to the vocabulary/base ontology. Append the following OML code to the vocabulary/base ontology. Save the editor.

@rdfs:comment "A ContainedElement is a thing that can participate in homomeric containment relationships."
aspect ContainedElement

@rdfs:comment "Contains is a many-to-many relation used to represent homomeric relations that form directed rooted trees."
relation entity Contains [
	from ContainedElement
	to ContainedElement
	forward contains
	reverse isContainedIn
	inverse functional
	asymmetric
	irreflexive
]

Note: You have seen so far that some entities are modeled as concept while others are modeled as aspect in OML vocabularies. While the former is used to define a concrete entity, the latter is used to define an abstract one meant for specialization.

  1. In the vocabulary/mission ontology, append the following OML code to the body. Save the editor.

@rdfs:comment "A Component can be organized hierarchically by containing other Components. "
ref concept Component < base:ContainedElement [
	restricts all base:contains to Component
	restricts all base:isContainedIn to Component
]

Note: that we used the ref keyword here to add more statements to the concept Component defined earlier. Again, we could have added these statements to the original definition but chose this style to separate concerns.

  1. The following is a visualization of the (modified) base and mission vocabularies so far:

    Base Vocabulary Mission Vocabulary
  2. Let us check that our ontologies are good so far, by running the task tutorial2/oml/build from the Gradle Tasks view, and waiting for it to finish running in the Gradle Executions view. This should run with no errors.

New Descriptions

Now, we will use the pattern to describe the physical composition (containment) of the components in kepler16b.

  1. Append the following OML code to the description/components ontology. Save the editor.

ref instance orbiter-spacecraft [
	base:contains orbiter-power-subsystem
	base:contains orbiter-harness
	base:contains orbiter-thermal-subsystem
	base:contains orbiter-command-and-data-handling-subsystem
	base:contains orbiter-telecom-subsystem
	base:contains orbiter-guidance-navigation-control-subsystem
	base:contains orbiter-mechanical-subsystem
	base:contains orbiter-spacraft-flight-software
	base:contains orbiter-propulsion-subsystem
]
  1. The following is a visualization of the components description so far:

  2. Let us check that our ontologies are good so far, by running the task tutorial2/oml/build from the Gradle Tasks view, and waiting for it to finish running in the Gradle Executions view. This should run with no errors.

New Queries

Note: No new queries for this pattern. We will incorporate it with another pattern below.

2.7. P5: Mass characterizes Component

Pattern Synopsis

This pattern adds a mass quantity to a mechanical component and a magnitude for that quantity for leaf components. The metrology vocabulary used is based on the VIM4 Draft 11 January 2021. The quantity library used is based on the ISO/IEC 80000.

New Vocabularies

  1. Create a vocabulary with the IRI <http://example.com/tutorial2/vocabulary/mechanical#> and prefix mechanical. Copy the following OML code as its contents. Save the editor.

vocabulary <http://example.com/tutorial2/vocabulary/mechanical#> as mechanical {

	extends <http://www.w3.org/2000/01/rdf-schema#> as rdfs
	extends <http://example.com/tutorial2/vocabulary/mission#> as mission
	extends <http://bipm.org/jcgm/vim4#> as vim4
	uses <http://iso.org/iso-80000-4.1#> as iso-80000-4.1

	@rdfs:comment "The class of mechanical components as physical objects"
	concept MechanicalComponent < mission:Component, vim4:Object

	@rdfs:comment "The class of magnitudes in kilograms for mass quantities on mechanical components"
	concept MassMagnitude < vim4:InherentUnitaryQuantityValue [
		restricts all vim4:characterizes to MechanicalComponent
		restricts vim4:instantiates to iso-80000-4.1:mass
		restricts vim4:unit to iso-80000-4.1:kilogram
	]
}

Note: how concept MechanicalComponent specializes both mission:Component and vim4:Object (multiple-inheritance).

Note: how concept MassMagnitude specializes vim4:InherentUnitaryQuantityValue and restricts some of its properties like saying that it characterizes MechanicalComponent, instantiates the iso-80000-4.1:mass quantity, and has a unit of iso-80000-4.1:kilogram. This makes all instances of MassMagnitude have those restrictions.

  1. The following is a visualization of the mechanical vocabulary:

  2. Append the following OML code to the body of vocabulary/bundle to include the new mechanical ontology. Save the editor.

includes <http://example.com/tutorial2/vocabulary/mechanical#>
  1. Let us check that our ontologies are good so far, by running the task tutorial2/oml/build from the Gradle Tasks view, and waiting for it to finish running in the Gradle Executions view. This should run with no errors.

New Descriptions

Now we can use this pattern to define the mechanical components and add their mass characterizations.

  1. Create a description with the IRI <http://example.com/tutorial2/description/masses#> and prefix masses. Copy the following OML code as its contents. Save the editor.

description <http://example.com/tutorial2/description/masses#> as masses {

	uses <http://www.w3.org/2001/XMLSchema#> as xsd
	uses <http://bipm.org/jcgm/vim4#> as vim4
	uses <http://example.com/tutorial2/vocabulary/mechanical#> as mechanical
	extends <http://example.com/tutorial2/description/components#> as components

	ref instance components:orbiter-launch-system : mechanical:MechanicalComponent
	instance orbiter-launch-system.mass.magnitude : mechanical:MassMagnitude [
		vim4:hasDoubleNumber "2000"^^xsd:double
		vim4:characterizes components:orbiter-launch-system
	]

	ref instance components:lander-launch-system : mechanical:MechanicalComponent
	instance lander-launch-system.mass.magnitude : mechanical:MassMagnitude [
		vim4:hasDoubleNumber "3500"^^xsd:double
		vim4:characterizes components:lander-launch-system
	]

	ref instance components:lander-spacecraft : mechanical:MechanicalComponent
	instance lander-spacecraft.mass.magnitude : mechanical:MassMagnitude [
		vim4:hasDoubleNumber "1200"^^xsd:double
		vim4:characterizes components:lander-spacecraft
	]

	ref instance components:orbiter-power-subsystem : mechanical:MechanicalComponent
	instance orbiter-power-subsystem.mass.magnitude : mechanical:MassMagnitude [
		vim4:hasDoubleNumber "297"^^xsd:double
		vim4:characterizes components:orbiter-power-subsystem
	]

	ref instance components:orbiter-harness : mechanical:MechanicalComponent
	instance orbiter-harness.mass.magnitude : mechanical:MassMagnitude [
		vim4:hasDoubleNumber "138"^^xsd:double
		vim4:characterizes components:orbiter-harness
	]

	ref instance components:orbiter-thermal-subsystem : mechanical:MechanicalComponent
	instance orbiter-thermal-subsystem.mass.magnitude : mechanical:MassMagnitude [
		vim4:hasDoubleNumber "307"^^xsd:double
		vim4:characterizes components:orbiter-thermal-subsystem
	]

	ref instance components:orbiter-command-and-data-handling-subsystem : mechanical:MechanicalComponent
	instance orbiter-command-and-data-handling-subsystem.mass.magnitude : mechanical:MassMagnitude [
		vim4:hasDoubleNumber "147"^^xsd:double
		vim4:characterizes components:orbiter-command-and-data-handling-subsystem
	]

	ref instance components:orbiter-telecom-subsystem : mechanical:MechanicalComponent
	instance orbiter-telecom-subsystem.mass.magnitude : mechanical:MassMagnitude [
		vim4:hasDoubleNumber "316"^^xsd:double
		vim4:characterizes components:orbiter-telecom-subsystem
	]

	ref instance components:orbiter-guidance-navigation-control-subsystem : mechanical:MechanicalComponent
	instance orbiter-guidance-navigation-control-subsystem.mass.magnitude : mechanical:MassMagnitude [
		vim4:hasDoubleNumber "156"^^xsd:double
		vim4:characterizes components:orbiter-guidance-navigation-control-subsystem
	]

	ref instance components:orbiter-mechanical-subsystem : mechanical:MechanicalComponent
	instance orbiter-mechanical-subsystem.mass.magnitude : mechanical:MassMagnitude [
		vim4:hasDoubleNumber "325"^^xsd:double
		vim4:characterizes components:orbiter-mechanical-subsystem
	]

	ref instance components:orbiter-spacraft-flight-software : mechanical:MechanicalComponent
	instance orbiter-spacraft-flight-software.mass.magnitude : mechanical:MassMagnitude [
		vim4:hasDoubleNumber "165"^^xsd:double
		vim4:characterizes components:orbiter-spacraft-flight-software
	]

	ref instance components:orbiter-propulsion-subsystem : mechanical:MechanicalComponent
	instance orbiter-propulsion-subsystem.mass.magnitude : mechanical:MassMagnitude [
		vim4:hasDoubleNumber "6"^^xsd:double
		vim4:characterizes components:orbiter-propulsion-subsystem
	]
}

Note: how in OML code above, instances already typed by mission:Components in P3 are referenced (with ref) in this description and declared with another type mechanical:MechanicalComponent. This ability to add multiple types (whether related to each other by specialization or not) to an instance is a powerful feature of OML called multi-classification. Being able to add those other types from a different description is yet another nice feature, since it allows separation of concerns. (Alternatively, we could have changed the original type of those instances from mission:Component to mechanical:MechanicalComponent).

Note: how the magnitude of each component is specified with a double literal. The literal type here specifies the precision of the value. Recall how the unit of the value has been restricted to iso-80000-4.1:kilogram in type mechanical:MassMagnitude. This means all those magnitudes values above have this unit. If the restriction was omitted, each instance could have specified its own unit. But, restricting units makes it easier to unify them in the same system, and manage this in one place, without losing precision (by ignoring to specify a unit) or inviting inconsistencies (by specifying them with every value).

  1. The following is a visualization of the masses description:

  2. Append the following OML code to the body of description/bundle to include the new masses ontology. Save the editor.

includes <http://example.com/tutorial2/description/masses#>
  1. Let us check that our ontologies are good so far, by running the task tutorial2/oml/build from the Gradle Tasks view, and waiting for it to finish running in the Gradle Executions view. This should run with no errors.

New Queries

Let us now create a query that extracts component, their compositions (if any), and their mass characterizations (if any).

  1. Create the file src/sparql/components.sparql and copy the following SPARQL code as its content.

PREFIX base:        <http://example.com/tutorial2/vocabulary/base#>
PREFIX mission:     <http://example.com/tutorial2/vocabulary/mission#>
PREFIX vim4:		<http://bipm.org/jcgm/vim4#>

SELECT DISTINCT ?c1_id ?c1_name ?c1_mass ?c2_id ?c2_name
WHERE {
	?c1 a mission:Component ;
		base:hasIdentifier ?c1_id ;
		base:hasCanonicalName ?c1_name .
	OPTIONAL {
		?c1 base:isContainedIn ?c2 .
		?c2 base:hasIdentifier ?c2_id ;
			base:hasCanonicalName ?c2_name .
	}
	OPTIONAL {
		?c1_mass_mag vim4:characterizes ?c1 ;
			vim4:hasDoubleNumber ?c1_mass .
	}
}
ORDER BY ?c1_id ?c2_id
  1. Let’s now run this query by running the task tutorial2/oml/owlQuery from the Gradle Tasks view and waiting for it to finish execution in the Gradle Executions view. It should run with no errors.

  2. Right click on the project in the Model Explorer view and select Refresh. Navigate to the file build/results/components.json and double click it to open its editor. You should see the following results in JSON.

  3. With this JSON results, one could develop visualizations like the following:

Note: the visualization code is not part of this tutorial

c1_name c1_id c1_mass c2_name c2_id
Orbiter Launch System C.01 2000.0
Orbiter Spacecraft C.02
Orbiter Power Subsystem C.02.01 297.0 Orbiter Spacecraft C.02
Orbiter Harness C.02.02 138.0 Orbiter Spacecraft C.02
Orbiter Thermal Subsystem C.02.03 307.0 Orbiter Spacecraft C.02
Orbiter C&DH subsystem C.02.04 147.0 Orbiter Spacecraft C.02
Orbiter Telecom Subsystem C.02.05 316.0 Orbiter Spacecraft C.02
Orbiter GN&C subsystem C.02.06 156.0 Orbiter Spacecraft C.02
Orbiter Mechanical Subsystem C.02.07 325.0 Orbiter Spacecraft C.02
Orbiter Spacraft Flight Software C.02.08 165.0 Orbiter Spacecraft C.02
Orbiter Propulsion Subsystem C.02.09 6.0 Orbiter Spacecraft C.02
Orbiter Ground Data System C.03
Mission Operations System C.04
Lander Launch System C.05 3500.0
Lander Spacecraft C.06 1200.0
Lander Ground Data System C.07

Orbiter Propulsion Subsystem(6) Orbiter Harness(138) Orbiter C&DH Subsystem(147) Orbiter GN&C Subsystem(156) Orbiter Spacraft Flight Software(165) Orbiter Power Subsystem(297) Orbiter Thermal Subsystem(307) Orbiter Telecom Subsystem(316) orbiter-mechanical-subsystem(325) Lander Ground Data System(0) Mission Operations System(0) Orbiter Ground Data System(0) Lander Spacecraft(1200) Orbiter Spacecraft(1857) Orbiter Launch System(2000) Lander Launch System(3500) Root

Note: that in the second visualization above, each node in the tree rolls up the mass from the levels below (if any). Such computation would be part of an analysis that runs on the query results before producing the visualization.

2.8. P6: Component presents Interface

Pattern Synopsis

Components contain other components. These subcomponents interact in ways that lead to emergent behavior. The interactions are sometimes the result of purposeful interconnection. Components may be designed with specific features to allow or enact interconnection. These features we call Interfaces. We say Components present Interfaces. Note that an interface is on one side or the other; it’s not the connection itself.

New Vocabularies

  1. In the vocabulary/mission ontology, append the following OML code to the body. Save the editor.

@rdfs:comment "An Interface represents a set of features that describe some Component’s interaction with another Component."
concept Interface < base:IdentifiedThing

@rdfs:comment "A Component presents zero or more Interfaces."
relation entity Presents [
	from Component
	to Interface
	forward presents
	reverse isPresentedBy
	inverse functional
	asymmetric
	irreflexive
]
  1. The following is a visualization of the mission vocabulary so far:

  2. Let us check that our ontologies are good so far, by running the task tutorial2/oml/build from the Gradle Tasks view, and waiting for it to finish running in the Gradle Executions view. This should run with no errors.

New Description

With this pattern, we can model the interfaces of some of the components in kepler16b.

  1. Create a description with the IRI <http://example.com/tutorial2/description/interfaces#> and prefix interfaces. Copy the following OML code as its contents. Save the editor.

description <http://example.com/tutorial2/description/interfaces#> as interfaces {

	uses <http://example.com/tutorial2/vocabulary/base#> as base
	uses <http://example.com/tutorial2/vocabulary/mission#> as mission
	extends <http://example.com/tutorial2/description/components#> as components

	instance orbiter-ground-data-system.telemetryIn : mission:Interface [
		base:hasIdentifier "I.04"
		base:hasCanonicalName "Telemetry In"
	]
	relation instance orbiter-ground-data-system.presents.telemetryIn : mission:Presents [
		from components:orbiter-ground-data-system
		to orbiter-ground-data-system.telemetryIn
	]
	instance orbiter-ground-data-system.commandOut : mission:Interface [
		base:hasIdentifier "I.03"
		base:hasCanonicalName "Command Out"
	]
	relation instance orbiter-ground-data-system.presents.commandOut : mission:Presents [
		from components:orbiter-ground-data-system
		to orbiter-ground-data-system.commandOut
	]

	instance orbiter-spacecraft.commandIn : mission:Interface [
		base:hasIdentifier "I.01"
		base:hasCanonicalName "Command In"
	]
	relation instance orbiter-spacecraft.presents.commandIn : mission:Presents [
		from components:orbiter-spacecraft
		to orbiter-spacecraft.commandIn
	]
	instance orbiter-spacecraft.telemetryOut : mission:Interface [
		base:hasIdentifier "I.02"
		base:hasCanonicalName "Telemetry Out"
	]
	relation instance orbiter-spacecraft.presents.telemetryOut : mission:Presents [
		from components:orbiter-spacecraft
		to orbiter-spacecraft.telemetryOut
	]
}

Note: in the OML code above a new element defined with the keywords relation instance. This is a named instance that represents a reified link between two instances. A relation instance is typed by one or more relation entity (comparable to a concept instance being typed by one or more concepts). For example, the first relation instance above named orbiter-ground-data-system.presents.telemetryIn is typed by the mission:Presents relation entity and is from concept instance components:orbiter-ground-data-system (a component) to concept instance orbiter-ground-data-system.telemetryIn (an interface).

Note: Creating a relation instance is an alternative to creating an unreified (simple) link as we have doing so far. It is done when the link needs to be referenced by other statements (as we will see in P7), or when it needs to be characterized by values to its properties (defined in the domain of relation entity types).

  1. The following is a visualization of the interfaces description:

  2. Append the following OML code to the body of description/bundle to include the new interfaces ontology. Save the editor.

includes <http://example.com/tutorial2/description/interfaces#>
  1. Let us check that our ontologies are good so far, by running the task tutorial2/oml/build from the Gradle Tasks view, and waiting for it to finish running in the Gradle Executions view. This should run with no errors.

New Queries

Note: No new queries for this pattern. We will incorporate it with another pattern below.

2.9. P7: Requirement specifies Presents

Pattern Synopsis

Requirements specify conditions that must be true of the system. One thing a requirement may specify is that some component presents an interface of a certain type or with certain properties. We say this with the reification pattern: (Requirement specifies (Component presents Interface)), in which (Component presents Interface) is reified with a member of class Presents (as seen above in P6).

New Vocabulary

  1. In the vocabulary/mission ontology, append the following OML code to the body. Save the editor.

@rdfs:comment "SpecifiedThing is something that isSpecifiedBy a Requirement"
aspect SpecifiedThing

@rdfs:comment "Presents is a relation that can be specified by a requirement."
ref relation entity Presents < SpecifiedThing

@rdfs:comment "A Requirement specifies that something must be true about something."
concept Requirement < base:IdentifiedThing

@rdfs:comment "A Requirement specifies zero or more SpecifiedThings."
relation entity Specifies [
	from Requirement
	to SpecifiedThing
	forward specifies
	reverse isSpecifiedBy
	functional
	asymmetric
	irreflexive
]

Note: how we added aspect SpecifiedThing as another supertype to relation entity Presents, which was previous defined. This says that its relation instances can be ranges of Specifies links.

  1. The following is a visualization of the mission vocabulary so far:

  2. Let us check that our ontologies are good so far, by running the task tutorial2/oml/build from the Gradle Tasks view, and waiting for it to finish running in the Gradle Executions view. This should run with no errors.

New Description

With this pattern in the vocabulary, and with (Component Presents Interface) relation instances specified in P6 before, we can now create requirements that specify those instances.

  1. Create a description with the IRI <http://example.com/tutorial2/description/requirements#> and prefix requirements. Copy the following OML code as its contents. Save the editor.

description <http://example.com/tutorial2/description/requirements#> as requirements {

	uses <http://example.com/tutorial2/vocabulary/base#> as base
	uses <http://example.com/tutorial2/vocabulary/mission#> as mission
	extends <http://example.com/tutorial2/description/interfaces#> as interfaces

	instance orbiter-ground-data-system-command-to-spacecraft : mission:Requirement [
		base:hasIdentifier "R.04"
		mission:specifies interfaces:orbiter-ground-data-system.presents.commandOut
	]

	instance orbiter-spacecraft-command-from-ground-data-system : mission:Requirement [
		base:hasIdentifier "R.05"
		mission:specifies interfaces:orbiter-spacecraft.presents.commandIn
	]

	instance orbiter-ground-data-system-telemetry-from-spacecraft : mission:Requirement [
		base:hasIdentifier "R.06"
		mission:specifies interfaces:orbiter-ground-data-system.presents.telemetryIn
	]

	instance orbiter-spacecraft-telemetry-to-ground-data-system : mission:Requirement [
		base:hasIdentifier "R.07"
		mission:specifies interfaces:orbiter-spacecraft.presents.telemetryOut
	]
}
  1. The following is a visualization of the requirements description:

  2. Append the following OML code to the body of description/bundle to include the new requirements ontology. Save the editor.

includes <http://example.com/tutorial2/description/requirements#>
  1. Let us check that our ontologies are good so far, by running the task tutorial2/oml/build from the Gradle Tasks view, and waiting for it to finish running in the Gradle Executions view. This should run with no errors.

New Queries

Let us now develop a query that extracts the requirements on components presenting interfaces.

  1. Create the file src/sparql/requirements.sparql and copy the following SPARQL code as its content.

PREFIX base:        <http://example.com/tutorial2/vocabulary/base#>
PREFIX mission:     <http://example.com/tutorial2/vocabulary/mission#>
PREFIX oml:     	<http://opencaesar.io/oml#>
PREFIX rdfs:     	<http://www.w3.org/2000/01/rdf-schema#>

SELECT DISTINCT ?r_id ?c_name ?i_name
WHERE {
	?r a mission:Requirement ;
		base:hasIdentifier ?r_id ;
		mission:specifies [
			a mission:Presents ;
			oml:hasSource [
				base:hasCanonicalName ?c_name
			] ;
			oml:hasTarget [
				base:hasCanonicalName ?i_name
			]
		]
}
ORDER BY ?r_id ?c_name ?i_name
  1. Let’s now run this query by running the task tutorial2/oml/owlQuery from the Gradle Tasks view and waiting for it to finish execution in the Gradle Executions view. It should run with no errors.

  2. Right click on the project in the Model Explorer view and select Refresh. Navigate to the file build/results/requirements.json and double click it to open its editor. You should see the following results in JSON.

  3. With this JSON results, one could develop a requirement doc generator that would generate the following:

Note: the doc generator code is not part of the tutorial.

Requirement 'R.04' specifies that component 'Orbiter Ground Data System' shall present interface 'Command Out'.
Requirement 'R.05' specifies that component 'Orbiter Spacecraft' shall present interface 'Command In'.
Requirement 'R.06' specifies that component 'Orbiter Ground Data System' shall present interface 'Telemetry In'.
Requirement 'R.07' specifies that component 'Orbiter Spacecraft' shall present interface 'Telemetry Out'.

2.10. P8: Interface joins Interface

Pattern Synopsis

Junctions represent actual connections between Interfaces presented by Components. When a component has an interface that joins an interface of another component, we infer that there is a Connection between these components.

New Vocabulary

  1. In the vocabulary/mission ontology, append the following OML code to the body. Save the editor.

@rdfs:comment "A Junction joins two or more Interfaces."
relation entity Junction [
	from Interface
	to Interface
	forward joins
	symmetric
	irreflexive
] < base:IdentifiedThing

@rdfs:comment "A Component connects to zero or more components."
relation entity Connection [
	from Component
	to Component
	forward connectsTo
	symmetric
]

@rdfs:comment "When interfaces presented by components are joined, we infer that the components are connected."
rule Junction-infers-Connection [
	presents(c1, i1) & joins(i1, i2) & isPresentedBy(i2, c2) -> connectsTo(c1, c2)
]

Note: how the rule Junction-infers-Connection says that when a component presents an interface that joins another interface, which is presented by another component, then the former component is said to connect to the latter component. Since both relation entities Junction and Connection are flagged as symmetric, a single assertion that a junction joins one interface to another is sufficient to make a DL reasoner infer that both their components connect to one another.

  1. The following is a visualization of the mission vocabulary so far:

  2. Let us check that our ontologies are good so far, by running the task tutorial2/oml/build from the Gradle Tasks view, and waiting for it to finish running in the Gradle Executions view. This should run with no errors.

New Description

With this pattern in the vocabulary, we can specify Junction relation instances between some interfaces.

  1. Create a description with the IRI <http://example.com/tutorial2/description/junctions#> and prefix junctions. Copy the following OML code as its contents. Save the editor.

description <http://example.com/tutorial2/description/junctions#> as junctions {

	uses <http://example.com/tutorial2/vocabulary/base#> as base
	uses <http://example.com/tutorial2/vocabulary/mission#> as mission
	extends <http://example.com/tutorial2/description/interfaces#> as interfaces

	relation instance orbiter-ground-data-system.orbiter-spacecraft.command.uplink : mission:Junction [
		from interfaces:orbiter-ground-data-system.commandOut
		to interfaces:orbiter-spacecraft.commandIn
		base:hasIdentifier "J.01"
		base:hasCanonicalName "Orbiter Command Uplink"
	]

	relation instance orbiter-ground-data-system.orbiter-spacecraft.telemetry.downlink : mission:Junction [
		from interfaces:orbiter-spacecraft.telemetryOut
		to interfaces:orbiter-ground-data-system.telemetryIn
		base:hasIdentifier "J.02"
		base:hasCanonicalName "Orbiter Telemetry Downlink"
	]
}
  1. The following is a visualization of the junctions description:

  2. Append the following OML code to the body of description/bundle to include the new junctions ontology. Save the editor.

includes <http://example.com/tutorial2/description/junctions#>
  1. Let us check that our ontologies are good so far, by running the task tutorial2/oml/build from the Gradle Tasks view, and waiting for it to finish running in the Gradle Executions view. This should run with no errors.

New Queries

Let us now develop a query that extracts the requirements on components presenting interfaces.

  1. Creat the file src/sparql/connections.sparql and copy the following SPARQL code as its content.

PREFIX base:        <http://example.com/tutorial2/vocabulary/base#>
PREFIX mission:     <http://example.com/tutorial2/vocabulary/mission#>

SELECT DISTINCT ?c1_name ?c2_name
WHERE {
	?c1 a mission:Component ;
		base:hasCanonicalName ?c1_name ;
		mission:connectsTo [
			base:hasCanonicalName ?c2_name
		]
}
ORDER BY ?c1_name ?c2_name
  1. Let’s now run this query by running the task tutorial2/oml/owlQuery from the Gradle Tasks view and waiting for it to finish execution in the Gradle Executions view. It should run with no errors.

  2. Right click on the project in the Model Explorer view and select Refresh. Navigate to the file build/results/connections.json and double click it to open its editor. You should see the following results in JSON.

Note: how the entailments generated by the DL reasoner allowed us to write concise queries that would otherwise have had to encode all the logical semantics.

2.11. Summary

This tutorial introduced the pattern-based approach of modeling with OML. It demonstrated how vocabulary can be designed in terms of patterns with well-defined syntax and semantics that allow useful analysis to be carried out on a model in support of a particular methodology of systems modeling. It also demonstrated how descriptions can be organized into loosely coupled fragments that declare minimum dependencies on each other. This can help scalability when specifying large models (by loading the minimum information only). It can also improve reusability of models (by reusing the minimum information only). Finally, the tutorial also covered some more syntax and semantics of OML that makes modeling and reasoning flexible and powerful. We saw the use of aspects as mix-in types to add capabilities to concepts and relation entities, relation entities and how they can be used to create both reified and unreified links, scalar properties and how they are typed by datatypes, restrictions and how they can be used to tighten the range or values of properties, and rules and semantic flags of relation entities and how they can influence logical entailments and reasoning.

3. Tutorial 3: OML CI

Note: This tutorial builds on the project developed in Tutorial 2. Please do that first before proceeding.

3.1. Learning Objectives

Managing an OML project in a git repository makes a lot of sense since OML models are textual artifacts. One of the biggest advantages of managing source files in a git repo is that it is easy to setup a continuous integration (CI) pipeline that builds the source on every commit. This mechanism to centralize and automate the build process is especially important to help foster collaboration on a project without sacrificing its quality as it detects problems early.

By the end of this tutorial, readers will be able to:

  1. Setup a Git repository for OML projects:

  2. Setup CI pipelines for OML repositories

  3. Use CI pipeline to detect OML syntactic errors

  4. Use CI pipeline to detect OML semantic errors

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.

3.2. Setup Git Repository

In this step, we will create a new Github repo and push the OML project from Tutorial 2 to it using the Git CLI.

  1. Open a web browser. navigate to your favorite Github organization and select the New Repository button. Set the name of the repo to kepler16b-example and the other settings as shown below. Finally, click the Create Repository button.

  2. In your OML Rosetta workspace, right-click on the tutorial2 project, select Proprties action, and note the Location path.

  3. Open the Terminal application on your machine, navigate to the project’s path, and initialize the repo using the following commands:

$ cd path/to/tutorial2
$ git init
$ git remote add origin git@github.com:OWNER/kepler16b-example.git
$ git pull
Replace OWNER by your new Github repo’s owner.
  1. Stage, commit, and push the project to the Github remote repository using the following commands:

$ git add .
$ git commit -m "initial commit"
$ git push --set-upstream origin main -f
  1. In your web browser, refresh the repo’s page. You should now see the repository looking like this:

  2. In the OML Rosetta workspace, right click on the project and choose Refresh.

Your project should now show as being managed in the github repo.

3.3. Setup CI Pipeline

In this step, we will create a CI pipeline for the repository using Github Actions. The pipeline (called a workflow in Github Actions) will build the project on every commit.

  1. In a web browser, navigate to your repo’s web page, and click on the Actions tab.

  2. In the Actions page, click on the Configure button of the Simple workflow.

  3. In the path, rename the file to ci.yml.

  4. Replace the file contents by the following code:

name: CI/CD

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@v3
    - name: Setup JDK
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'
    - name: Setup Gradle
      uses: gradle/gradle-build-action@v2
    - name: Build
      run: ./gradlew build
    - name: Upload
      if: ${{ always() }}
      uses: actions/upload-artifact@v3
      with:
        name: build
        path: build/

The CI workflow above has a single job called build with 5 named: Check, Setup JDK, Setup Gradle, Build (runs the DL reasoner) and Upload (uploads the build folder as an artifact for later inspection in case of error).

  1. Push the cy.yml file to the repo and watch the first workflow run complete successfully.

  2. Back in OML Rosetta, Right click on the project and select Team -> Pull. A pull results dialog will open. Press the button to close it.

Note: this last step is needed to refresh the local clone of the repo with the changes we committed on the remote on Github.

3.4. Detect Syntactic Error

In OML Rosetta, create a new branch, add a syntactic error (an invalid cross-reference) to the OML files, and push it. Watch how the CI workflow detects the error. Then, undo the change and push to watch how the workflow succeeds again.

3.5. Detect Semantic Error

In OML Rosetta, in the same branch (created in the previous step), add a semantic error (an assembly contained by two assemblies which is a violation of the base:contains relation being inverse functional) to the OML files to see how the CI workflow detects the error. Then, undo the change and push to watch how the workflow succeeds again.

3.6. Summary

In the realm of OML projects, efficient collaboration and project integrity are paramount. This tutorial serves as a comprehensive guide, detailing the strategic management of OML projects within Git repositories, fortified by Continuous Integration (CI) pipelines. Through a step-by-step approach, the tutorial expertly navigates learners through the intricacies of project management on GitHub repositories, followed by the establishment of robust CI pipelines using GitHub Actions. A focal point of the tutorial lies in the demonstration of how these pipelines can adeptly identify both syntactic and semantic errors in commits pushed to the repository. By embracing these methodologies, professionals can elevate project collaboration, fortify quality control, and ensure the consistent enhancement of OML projects within a collaborative environment.

4. Tutorial 4: OML Reports

Note: This tutorial builds on the project developed in Tutorial 3. Please do that first before proceeding.

4.1. Learning Objectives

Modeling a system with OML would not be useful without the ability to analyze the model and share the analysis results with the project’s stakeholders. In Tutorial 2, we learned how an OML model can be analyzed with SPARQL queries, and the results visualized, but we did not see how those visualizations were created. Moreover, in Tutorial 3, we learned how a (CI or Continuous Integration) pipeline can run the build task automatically, but we did not see how it can also automate the analysis of the project and the deployment of the results to the project’s stakeholder (CD or Continuous Deployment).

By the end of this tutorial, readers will be able to:

  1. Utilize Jupyter Notebook to create a rich report based on SPARQL query results (final result).

  2. Generate canonical OML documentation (OmlDoc) that can be cross referenced from the report.

  3. Evolve a CI pipeline to be a CI/CD pipeline that runs analysis and deploys its results.

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.

4.2. Install Jupyter Notebook

Jupyter is an open-source project that lets you easily combine Markdown text and executable Python source code on one canvas called a notebook. Many editors support working with Jupyter Notebooks; however, in this tutorial we will use VS Code.

  1. If you don’t already have Python with the Jupyter package, follow these instructions.

  2. If you don’t already have VS Code with Jupyter Notebook extension, follow these instructions.

  3. Run VS Code client and if the Terminal window is not already visible, open it by selecting View -> Terminal from the main menu.

Note: for best experience in this tutorial, switch VS Code to the Light Modern color theme.

  1. Select File -> Open Folder... and select the folder of the project you created in Tutorial 2.

Note: Assuming you had also done Tutorial 3, this folder should be a clone of a git repository called kepler16b-example.

4.3. Run SPARQL Queries

Recall from Tutorial 2 that an OML project can create SPARQL query files in some path and store results in some other path. The code below is an excerpt from the project’s ./build.gradle file showing a task called owlQuery that runs the SPARQL queries.

task owlQuery(type:io.opencaesar.owl.query.OwlQueryTask, group:"oml", dependsOn: owlLoad) {
    inputs.files(owlLoad.inputs.files) // rerun when the dataset changes
    endpointURL = "http://localhost:3030/$dataset".toString()
    queryPath = file('src/sparql')
    resultPath = file('build/results')
    format = 'json'
}
  1. In the Terminal view, run the following command, then inspect the json files in the build/results folder.

$ ./gradlew owlQuery

> Task :startFuseki
Fuseki server is already running with pid=56106

BUILD SUCCESSFUL in 6s
6 actionable tasks: 3 executed, 3 up-to-date

  1. Open the .github/workflows/ci.yml file (created in Tutorial 3) and insert the following step right before the Upload one:

- name: Query
  run: ./gradlew owlQuery

  1. Click on the Source Control tab on the left; you should find the ci.yml file listed. Type a commit message and select Commit & Push button. Answer Yes in the next dialog box. Wait for the operation to finish.

  2. In a web browser, navigate to the kepler16b-example Github repo you created in Tutorial 3. Click on the Actions tab. Wait until the CI workflow succeeds.

4.4. Generate OML Doc

In this step, we will add a new task of type OwlDoc to the build.gradle file. This tool generates default documentation for an Oml dataset. We will also add it to the CI script.

Not: An OwlDoc task requires the Oml dataset to be converted first to Owl, which we get when we chain it to an Oml2Owl task.

  1. In VS Code, switch to the Explorer tab on the left. Open the build.gradle file. Find the buildscript.dependencies clause. Add a new dependency on the owl-doc-gradle tool on the top:

classpath 'io.opencaesar.owl:owl-doc-gradle:2.+'

  1. In the build.gradle file, insert the following generateDocs task right before the startFuseki task. Save.

/*
 * A task to generate documentation for the OWL catalog
 * @seeAlso https://github.com/opencaesar/owl-tools/blob/master/owl-doc/README.md
 */
task generateDocs(type: io.opencaesar.owl.doc.OwlDocTask, dependsOn: owlReason) {
    // OWL catalog
    inputCatalogPath = file('build/owl/catalog.xml')
    // OWL catalog title
    inputCatalogTitle = 'Kepler16b'
    // OWL catalog version
    inputCatalogVersion = project.version
    // OWL Ontology Iris
    inputOntologyIris = [ "$rootIri/classes", "$rootIri/properties",  "$rootIri/individuals" ]
    // Output folder
    outputFolderPath = file('build/web/doc')
    // Output case sensitivie path
    outputCaseSensitive = org.gradle.internal.os.OperatingSystem.current().isLinux()
}
  1. In Terminal, run ./gradlew generateDocs. Inspect the build/web/doc folder. You will find the generated HTML documentation.

  2. Open the file build/web/doc/index.html in a web browser and browse through the generated documentation.

  3. Open the .github/workflows/ci.yml file and add the following step right after the Upload step.

- name: Generate Docs
  run: ./gradlew generateDocs

  1. Using the Source Control tab, commit and push both build.gradle and ci.yml files. Check that the CI workflow succeeded on the repository’s Actions tab.

4.5. Setup Github Pages

In the next few sections, we will create a Jupyter Notebook, convert it to HTML, and publish (deploy) it. Before we can do that, we need to setup a web server to deploy it to.

A convenient choice here is to deploy to the repository’s Github Pages. This makes the page accessible at the address http://OWNER.github.io/kepler16b-example/ (where OWNER is the repository’s owner on github).

Note: This choice means that the content of Pages will be published by a Github Actions workflow. As we will see below, this will allow the repository’s CI script to publish it,

4.6. Create Jupyter Notebook

In this step, we will create a simple Jupyter Notebook in the project and setup the CI workflow to publish it to Github Pages.

  1. In VS Code Explorer, right click on the src folder, select New Folder, and call it ipynb.

  2. Right click on the ipynb folder, select New File, and call it index.ipynb. This opens a Jupiter Notebook editor with a single empty cell.

  3. In the top right corner of the editor, click on the Select Kernel button, select Python Environments, and select one of the available Python 3 environments. Notice that now the name of the selected kernel shows up in the top right corner.

  4. Hover over the first empty cell in the editor. A toolbar appears with a trash can. Click on the trash can to delete the cell.

  5. In place of the cell, you should now see two buttons, one says Code and the other says Markdown. Click on the latter. This adds a Markdown cell as the first cell.

  6. In the cell, type the following text, then click on the (tick) icon in the cell’s toolbar to apply. The editor should now look like the picture below.

# Kepler16b
The Kepler16b project is developing a hypothetical mission to an exoplanet that is millions of light years away.

Before we go further, let us setup the CI script to publish this simple Notebook. This involves setting up a Python environment, installing some dependencies, running a couple of python tools to execute the notebook and convert it to HTML, then finally deploying that HTML to Github Pages.

  1. In VS Code Explorer, right click on the ipynb folder, choose New File, and name it requirements.txt. Insert the following text as its contents and save.

ipykernel
nbconvert
pandas
plantuml
igraph
cairocffi

Note: A requirements.txt file is a typical Python mechanism to declare dependencies. In this case, the first two dependencies are the minimum required to enable executing and converting the notebook to HTML. The rest of the dependencies are libraries that will be used in the steps below to visualize the analysis results.

  1. In VS Code Terminal, activate the python environment used by the notebook’s kernel, then run the following command to install the dependencies locally:

pip install -r src/ipynb/requirements.txt
  1. Open the .github/workflows/ci.yml file and add the following step right after the Generate Docs step.

  - name: Set up Python 3
    uses: actions/setup-python@v4
    with: 
      python-version: '3.10'
  - name: Install Requirements
    run: pip install -r src/ipynb/requirements.txt
  - name: Run Notebook
    run: python -m nbconvert --execute --to notebook --no-input src/ipynb/index.ipynb --output-dir='build/web' 
  - name: Convert Notebook to HTML
    run: python -m nbconvert --to html --no-input build/web/index.ipynb
  - name: Publish
    uses: actions/upload-pages-artifact@v1
    with:
      path: build/web
      
deploy:
  needs: build
  permissions:
    pages: write
    id-token: write
  environment:
    name: github-pages
    url: ${{ steps.deployment.outputs.page_url }}
  runs-on: ubuntu-latest
  steps:
    - name: Deploy
      id: deployment
      uses: actions/deploy-pages@v1

Note: The first set of steps are added to the build job. They setup a Python 3 environment, install the dependencies, run the notebook, convert it to HTML in the build/web folder, then upload the folder as a Pages artifact. Then, a new job named deploy is added. This job runs after build is done and deploys the Pages artifact to the web server.

  1. Using the Source Control tab, commit and push all the three files index.ipynb, requirements.txt and ci.yml. Check that the CI workflow succeeded on the repository’s Actions tab. You should see the following page in your browser:

  2. Click on the hyperlink in the deploy box to navigate the deployed Github Pages of the repository. This should open up a new page that looks like this:

4.7. P1: Reference Documentation

In this step, we will add to the notebook some cross references to the OML documentation that we generated previously for the project.

  1. In the src/ipynb/index.ipynb editor, add a new Markdown cell, and type the following text in it. Click the icon to apply.

## Missions
The Kepler16b project delivers two missions: [a Lander Mission](doc/example.com/tutorial2/description/missions/lander.html) and an [Orbiter Mission](doc/example.com/tutorial2/description/missions/orbiter.html), each of which pursues a number of objectives. For details, check the [full documentation](doc).
  1. Using the Source Control tab, commit index.ipynb. In the repository’s website, wait until the CI workflow succeeds, navigate to the Pages link and refresh the page.

  2. Click on the three hyperlinks and verify that you can navigate to them fine.

4.8. P2: Visualize Missions

In this step, we will add the first visualization of SPARQL query results to the notebook. In this case, we will visualize data from the build/results/missions.json file, which holds the result of running the missions.sparql query, which matched missions and the objectives they pursue. We will use d3.js to code this visualization, but to make this easier to present here, we will copy and paste a python file called utilities.py (that has helpful utility functions) to the project.

  1. Navigate to the file utilities.py and copy its contents.

  2. In VS Code Explorer, right click on the ipynb folder, select New File, name it utilities.py, and paste the contents to it. Save.

  3. In the src/ipynb/index.ipynb editor, add a new Code cell, and type the following code in it. Click on the execute button on the cell’s left side to run.

from utilities import *
df = dataframe("missions.json")
data = df.to_json(orient = "records")
HTML(tree.safe_substitute(data=data))

Note: Click on the circles to collapse/expand them

  1. Using the Source Control tab, commit index.ipynb and utilities.py. In the repository’s website, wait until the CI workflow succeeds, navigate to the Pages link and refresh the page.

4.9. P3: Visualize Objectives

In this step, we will add a visualization for the data in the build/results/objectives.json file, which holds the result of running the objectives.sparql query, which matched the objective aggregation hierarchy. We will use the PlantUml tool to code this visualization.

  1. In the src/ipynb/index.ipynb editor, add a new Markdown cell, and type the following text in it. Click the icon to apply.

## Objectives
The Kepler16b missions' objectives aggregate other lower-level objectives as depicted by the following diagram:
  1. In the src/ipynb/index.ipynb editor, add a new Code cell, and type the following code in it. Click on the execute button on the cell’s left side to run.

from utilities import *
df = dataframe("objectives.json")
objectives1 = todict(df, 'o1_id', 'o1_name')
objectives2 = todict(df, 'o2_id', 'o2_name')
aggregations = tolists(df, 'o1_id', 'o2_id')
diagram(objects(union(objectives1, objectives2), aggregations, 'o--', 'objective'))

  1. Using the Source Control tab, commit index.ipynb. In the repository’s website, wait until the CI workflow succeeds, navigate to the Pages link and refresh the page.

4.10. P4: Visualize Components

In this step, we will add a visualization for the data in the build/results/components.json file, which holds the result of running the components.sparql query, which matched the component physical decomposition hierarchy. We will use the PlantUml tool to code this visualization.

  1. In the src/ipynb/index.ipynb editor, add a new Markdown cell, and type the following text in it. Click the icon to apply.

## Components
The Kelper16 missions' components are organized in a physical decomposition hierarchy as shown below.
  1. In the src/ipynb/index.ipynb editor, add a new Code cell, and type the following code in it. Click on the execute button on the cell’s left side to run.

from utilities import *
df = dataframe("components.json")
components1 = todict(df, 'c1_id', 'c1_name')
components2 = todict(df, 'c2_id', 'c2_name')
compositions = tolists(df, 'c2_id', 'c1_id')
diagram('left to right direction\nskinparam nodesep 10\n'+objects(union(components1, components2), compositions, '*--', 'component'))

  1. Using the Source Control tab, commit index.ipynb. In the repository’s website, wait until the CI workflow succeeds, navigate to the Pages link and refresh the page.

4.11. P5: Visualize Mass Rollup

In this step, we will add another visualization for the data in the build/results/components.json file. This time, we will use the mass matched for each leaf component and roll them up the composition hierarchy. This means the masses of composed components are summed up and set as the mass of their composing component. The final mass of each component is then shown in a Pandas table.

  1. In the src/ipynb/index.ipynb editor, add a new Markdown cell, and type the following text in it. Click the icon to apply.

## Mass Rollup
The Kelper16 missions' components are characterized by their masses. Those masses are rolled up the physical decomposition hierarchy as shown below."
  1. In the src/ipynb/index.ipynb editor, add a new Code cell, and type the following code in it. Click on the execute button on the cell’s left side to run.

from utilities import *
df = dataframe("components.json")
components = tolist(df, 'c1_id')
compositions = tolists(df, 'c2_id', 'c1_id')
masses = [float(x) if not pd.isna(x) else 0 for x in tolist(df, 'c1_mass')]
graph = rollup(components, compositions, "mass", masses)
df = df[['c1_id', 'c1_name', 'c1_mass']]
df.loc[:, 'c1_mass'] = graph.vs["mass"]
df = df.rename(columns={"c1_id": "Id", "c1_name": "Name", "c1_mass": "Mass"})
style = df.style.hide(axis="index").set_properties(**{'text-align': 'left', 'font-size': '12pt',})
style.format(precision=2).set_table_styles([dict(selector='th', props=[('text-align', 'left')])])

  1. Using the Source Control tab, commit index.ipynb. In the repository’s website, wait until the CI workflow succeeds, navigate to the Pages link and refresh the page.

4.12. Change the Model

Now that we have developed an interesting notebook as a report to share with stakeholders, we can now change the OML model and see the notebook getting updated automatically.

  1. In VS Code Exploer, edit the file src/oml/example.com/tutorial2/description/missions.oml by adding a new objective to the lander mission. Save.

  2. Using the Source Control tab, commit mission.oml. In the repository’s website, wait until the CI workflow succeeds, navigate to the Pages link and refresh the page.

  3. In VS Code Exploer, edit the file src/oml/example.com/tutorial2/description/masses.oml by changing the mass of components:orbiter-propulsion-subsystem from 6 to 106.

  4. Using the Source Control tab, commit mission.oml. In the repository’s website, wait until the CI workflow succeeds, navigate to the Pages link and refresh the page. Notice the new mass for C.02.09 and changed mass of the composing component C.02 now 100 higher than before.

  5. In VS Code Explorer, revert the changes you made to the two OML files above. Using the Source Control tab, commit mission.oml and masses.oml. In the repository’s website, wait until the CI workflow succeeds, navigate to the Pages link and refresh the page. Verify that the page returns to the original contents before the changes.

4.13. Summary

This tutorial serves as a comprehensive guide, outlining the step-by-step process of utilizing Jupyter Notebook and GitHub Pages to visualize the results derived from querying OML datasets. Through clear and concise instructions, learners will gain practical insights into the seamless integration of these tools for efficient data exploration, analysis, and collaborative review. By employing automation techniques, the tutorial demonstrates how the generated notebook content and OML documentation can be effortlessly deployed onto GitHub Pages, facilitating real-time impact assessment, and fostering a collaborative environment for informed decision-making. This tutorial proves particularly valuable for professionals seeking enhanced methods of data visualization and streamlined peer review processes within the context of OML datasets.

5. Tutorial 5: OML Sirius

Note: This tutorial builds on the skills learned in Tutorial 1. Please do that first before proceeding.

5.1. Learning Objectives

Welcome to this enlightening tutorial where we will explore the versatile capabilities of the Sirius framework and how it simplifies the process of authoring OML models within the Rosetta workbench. Throughout this tutorial, we will embark on a journey to create an OML project, where we harness the power of built-in Sirius-based editors and viewers to not only author but also visually depict OML models. But that’s not all—our expedition continues as we venture into the creation of a Sirius viewpoint project, where we will craft user-friendly, domain-specific editors tailored for the seamless authoring of description models. This approach is a game-changer, significantly reducing the learning curve for OML users who may find working with a graphical user interface more intuitive than a traditional textual editor. Finally, we will get another glimpse of the powerful analysis capabilities of OML.

Upon completing this tutorial, you will gain the following essential skills:

Prepare to embark on a transformative journey, as this tutorial equips you with the tools and knowledge to harness the full potential of OML with the assistance of the Sirius framework. By the end, you’ll be well-equipped to navigate the world of OML modeling and analysis with confidence and finesse. Let’s get started!

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.

5.2. Create OML Model Project

In this step, we will create a new OML project called basicfamily-model in the Rosetta workbench. This process should already be familiar from Tutorial 1.

  1. Right click in the Model Explorer view and select New -> OML Project.

  2. Enter the project name as basicfamily-model. Press Next.

  3. Enter the project details as shown below. Press Finish.

  4. The basicfamily-model project should now be visible in the Model Explorer view.

  5. Expand the basicfamily-model project node in the Model Explorer view as shown in the image below.

5.3. Create OML Vocabulary

In the step, we will create a vocabulary called basicfamily, which is the OML equivalent of an Ecore metamodel that is published as an example with the Sirius framework. Unlike in previous tutorials, we will use the built-in Sirius-based OML vocabulary diagram editor.

  1. Right click on the src/oml/example.com subfolder in the Model Explorer view and select New -> OML Model.

  2. Enter the details of the basicfamily vocabulary as shown below. Press Finish.

  3. The basicfamily vocabulary will be created and its OML editor opens.

Before we can use any Sirius-based viewpoints, we need to convert the project to what Sirius calls a Modeling Project.

  1. Right-click on the basicfamily-model project in the Model Explorer view and select "Configure -> Convert to Modeling Project".

Before we can use the built-in Vocabulary viewpoint, which provides the vocabulary diagram editor, we need to enable the viewpoint on the project.

  1. Right-click on the on the basicfamily-model project in the Model Explorer view and select "Viewpoints Selection".

  2. Check the "Vocabularies" box and click OK. This activates the ability to create vocabulary diagrams in the project.

Now, we are ready to create a vocabulary diagram for basicfamily vocabulary to use it as an alternative editor to the OML text editor.

  1. Navigate to the basicfamily.oml file in the Model Explorer view and expand it to show the root vocabulary element. Right-click on the element and select "New Representation -> Basicfamily Editor".

  2. The New Vocabulary Editor dialog opens up to allow you to customize the diagram name. In this case, we will keep the default name and click OK.

  3. In order to observe how the graphical syntax is synchronized with the textual syntax, drag the diagram editor using its title bar and dock it to the right of the text editor.

  1. We will now use the Vocabulary diagram editor to author the vocabulary. Follow the steps in the video below and save the editor regularly to see the corresponding textual syntax.

  1. If you have done the visual authoring all correct, you should end up with the following OML text in basicfamily.oml.

Note: that the order of the statements below may be slightly different for you if you created the diagram elements in different order from the video.

vocabulary <http://example.com/vocabulary/basicfamily#> as basicfamily {

	extends <http://www.w3.org/2001/XMLSchema#> as xsd

	aspect Named

	scalar property name [
		domain Named
		range xsd:string
		functional
	]

	concept Family < Named

	concept Person < Named [
		restricts parents to max 2
	]

	relation members [
		from Family
		to Person
	]

	concept Man < Person

	concept Woman < Person

	relation parents [
		from Person
		to Person
		reverse children
	]

	relation mother [
		from Person
		to Woman
		functional
	]

	relation father [
		from Person
		to Man
		functional
	]
}

Notice that the vocabulary diagram editor does not yet have feature parity with the textual editor in terms of its ability to author the full OML syntax. However, we intend to make incremental progress towards this goal with every release (if you want us to prioritize this, let us know). On the other hand, the editor’s visualization capabilities are more complete. Since the textual and diagram editors are always in sync, a modeler could easily switch back and forth between them to edit the vocabulary.

  1. In the OML textual editor for basicfamily.oml, add specialization from relations mother and father to relation parents.

relation mother [
	from Person
	to Woman
	functional
] < parents

relation father [
	from Person
	to Man
	functional
] < parents

Finally, we want to create an OML vocabulary bundle that includes the basicfamily vocabulary.

  1. The basicfamily vocabulary diagram now looks like this (notice the {subsets parents} on relation mother and father).

  2. Right-click on the src/oml/example.com/vocabulary folder in Model Explorer view and select New -> OML Model. Fill in the diagram like in the picture. Click Finish.

  3. In the open editor for bundle.oml, add an include statement for basicfamily like this:

vocabulary bundle <http://example.com/vocabulary/bundle#> as ^bundle {
	
	includes <http://example.com/vocabulary/basicfamily#>
}

Note: Recall that a vocabulary bundle automatically asserts disjointness between included concepts with no common subtypes.

5.4. Create OML Description

In this step, we will create an OML description model using the OML textual editor, then visualize it with the built-on OML description diagram.

Compared with the built-in vocabulary diagram which has authoring capabilities, th built-in OML description diagram only has visualization abilities (so far).

  1. Right click on the src/oml/example.com/description subfolder in the Model Explorer view and select New -> OML Model.

  2. Enter the details of the family1 description as shown below. Press Finish.

  3. The family1 description will be created and its OML editor opens.

We will now use the text editor to create an OML description model defining family1, its men and women members, and their interrelationships.

  1. Copy the following OML text and paste it as the new content of the open editor.

description <http://example.com/description/family1#> as family1 {

	uses <http://example.com/vocabulary/basicfamily#> as basicfamily

	instance family1 : basicfamily:Family [
		basicfamily:members Paul
		basicfamily:members Isa
		basicfamily:members Elias
		basicfamily:members Lea
		basicfamily:members Dave
		basicfamily:members Alain
		basicfamily:members Bryan
		basicfamily:members Fiona
		basicfamily:members Katell
		basicfamily:members Clara
		basicfamily:members Albert
		basicfamily:members Jane
		basicfamily:members Peter
	]

	instance Paul : basicfamily:Man

	instance Isa : basicfamily:Woman

	instance Elias : basicfamily:Man [
		basicfamily:father Paul
		basicfamily:mother Isa
	]

	instance Lea : basicfamily:Woman [
		basicfamily:father Paul
		basicfamily:mother Isa
	]

	instance Dave : basicfamily:Man [
		basicfamily:father Elias
	]

	instance Alain : basicfamily:Man [
		basicfamily:father Dave
		basicfamily:mother Katell
	]

	instance Bryan : basicfamily:Man [
		basicfamily:father Elias
	]

	instance Fiona : basicfamily:Woman [
		basicfamily:father Elias
	]

	instance Katell : basicfamily:Woman

	instance Clara : basicfamily:Woman [
		basicfamily:father Elias
	]

	instance Albert : basicfamily:Man

	instance Jane : basicfamily:Woman [
		basicfamily:mother Fiona
	]

	instance Peter : basicfamily:Man [
		basicfamily:mother Clara
	]
}

We will now visualize the family1.oml model using the built-in Sirius-based description diagram.

  1. Right-click on the basicfamily-model project in the Model Explorer view and select "Viewpoints Selection". In the dialog, check the "Descriptions" box and click OK.

Now, we are ready to create a description diagram for family1.oml.

  1. Navigate to family1.oml in the Model Explorer view and expand it to reveal the root description. Right-click on the description and select "New Representation -> Family1 Viewer". In the opened dialog, leave the default name and click OK.

The Family1 Viewer diagram opens with visualization of the model content. You will notice that the diagram is a bit busy since the family1 instance, which is related to all family members using the members relation is visualized. Deleting this node from the diagram and rearranging the remaining nodes improves the layout.

  1. Follow the few steps in the video below to improve the layout of the diagram.

  1. The final diagram should look like this now.

Notice that although the diagram looks reasonable is still hard to read. Also, while you can make notational/stylistic changes to the diagram, you cannot use it to edit the description model. In the next section, we will develop a custom diagram to improve both aspects.

Finally, we want to add the family1 description to the description bundle and make the latter use the basicfamily vocabulary.

  1. Navigate in Model Explorer view to the src/oml/example.com/description/bundle.oml file and double click it to open its OML textual editor.

  2. In the open editor for bundle.oml, replace the contents with the following. Save the editor.

description bundle <http://example.com/description/bundle#> as ^bundle {
	
	uses <http://example.com/vocabulary/basicfamily#>
	
	includes <http://example.com/description/family1#>
}
Note: Recall that a description bundle represents a closed dataset that we want to reason on.

5.5. Create Sirius Viewpoint Project

In this step, we will use Sirius to develop a custom diagram for the basicfamily vocabulary that a) improves the notation/style, and b) allows for editing the description model. In order to do this in Sirius, we need to define what Sirius calls a Viewpoint Specification Project.

  1. In the Model Explorer view, right click on an empty area and choose New -> Project -> Sirius -> Viewpoint Specification Project.

  2. Give the project the name basicfamily-viewpoint and click Next.

  3. In the Viewpoint Specification Model page, rename the model to simply basicfamily.odesign then click Finish.

The project shows up in the Model Explorer view and its editor opens up.

  1. Drag and dock it to the right of the working area. Your screen should now look like this.

  2. In the odesign editor, expand the tree to reveal a viewpoint node called MyViewpoint. Select it and then in the Property Sheet view, rename it to persons. Also set the "Model File Extension" field to oml.

  3. Under the persons node, you see another node for a Java service. Select it and edit it its text to defaultpackage.Services.

This step is only needed since we chose the name of the project to be basicfamily-viewpoint, which contains an invalid Java character -. Eclipse could not use that name for the root package and instead named it defaultpackage.

  1. Double click the defaultpackage.Services node in the tree. It should open a Java editor for the Services class. This is how you know you have configured this correctly. Close the Java editor.

We will add a few more Java services from the openCAESAR public API.

  1. Navigate in the Model Explorer view to the file basicfamily-viewpoint/META-INF/MANIFEST.MF and double click on it. In the open editor, switch to the Dependencies tab. Click the Add button. In the Plug-in Selection dialog, search for oml and select the io.opencaesar.oml item and click Add.

  2. Click the Add button again in the Dependencies tab and in the dialog search for rosetta and select the io.opencaesar.rosetta.sirius.viewpoint item and click Add. Save the MANIFEST.MF editor and close it.

Note: The versions of the new dependencies you added will correspond to the version of Rosetta you installed (i.e., they do not have to match the picture).

  1. Back in the odesign editor, right click on the defaultpackage.Services node and select Copy. Then, right click on the persons node, and select Paste. Repeat the paste a total of 4 times. Now, click on each of the pasted services and edit them in the Property Sheet view to the following class names. Save the editor after.

We need to copy some icons to the basicfamily-viewpoint project, so we use them in the definition of the viewpoint.

  1. Download icons.zip and unzip it. Move the icons folder to the root of the basicfamily-viewpoint project.

Finally, we need to activate the new persons viewpoint on the basicfamily-model project.

  1. Right click on the basicfamily-model project in the Model Explorer view and select Viewpoint Selection. Check the persons box in the dialog and click OK.

Now, all the editors we will define in the persons viewpoint can be used in the basicfamily-model project.

5.6. Create Diagram Editor

In this step, we will define a custom diagram editor for OML description models that use the basicfamily OML vocabulary.

  1. In the basicfamily.odesign editor, right click on the persons viewpoint and select New Representation -> Diagram Description.

  2. Select the newly created diagram node in the editor then click on the Property Sheet view to edit its properties. Switch first on the Metamodels tab, click on Add from registry button (on the right), type oml in the edit box, and select the http://opencaesar.io/oml item and click OK.

Now, we will specify that this diagram can be created for a oml.ConceptInstance that is typed by the basicfamily:Family concept from the vocabulary.

  1. In the Property Sheet view, switch to the General tab and make the following modifications:

    General:
    - Id: Persons diagram
    - Domain Class: oml.ConceptInstance
    - Precondition Expression: aql:self.findIsKindOf('basicfamily:Family')
    

We will now create two kinds of nodes within the diagram, one for a Man and one for a Woman (the two kind of Person from the vocabulary).

  1. In the basicfamily.odesign editor, expand the Persons diagram node to reveal the Default node. Right click on that node and select New Diagram Element -> Node. In the Property Sheet, enter the following information for the node:

    General:
    - Id: ManNode
    - Domain Class: oml.ConceptInstance
    - Semantic Candidates Expression: aql:self.findTargetInstances('basicfamily:members')
    Advanced:
    - Precondition Expression: aql:self.findIsKindOf('basicfamily:Man')
    
  2. In the basicfamily.odesign editor, right click on the ManNode node and select New Style -> Workspace Image. In the Property Sheet, change the following:

    General:
    - Image Path: /basicfamily-viewpoint/icons/man.svg
    Label:
    - Show Icon: false
    - Label Position: border
    Corner:
    - Arc Height: 1
    - Arc Width: 1
    Advanced:
    - Size Computation Expression: 4
    
  3. In the basicfamily.odesign editor, right click on the ManNode node and select Copy. Select the Default node, right click and select Paste. In the Property Sheet, change the following information only:

    General:
    - Id: WomanNode
    Advanced
    - Precondition Expression: aql:self.findIsKindOf('basicfamily:Woman')
    

We will now create two kinds of edges within the diagram, one for the basicfamily:father relation and one for a basicfamily:mother relation (from the vocabulary).

  1. In the basicfamily.odesign editor, right click on the Default node and select New Diagram Element -> Relation Based Edge. In the Property Sheet, change the following:

    General:
    - Id: FatherEdge
    - Source Mapping: ManNode, WomanNode
    - Target Mapping: ManNode
    - Target Finder Expression: aql:self.findTargetInstances('basicfamily:father')
    
  2. In the basicfamily.odesign editor, expand the fatherEdge node and select the nested Edge Style node. In the Property Sheet, change the following:

    Color:
    - Stroke Color: blue
    
  3. In the basicfamily.odesign editor, right click on the fatherEdge node and select Copy. Select the Default node, right click and select Paste. In the Property Sheet, change the following information only:

    General:
    - Id: MotherEdge
    - Target Mapping: WomanNode
    - Target Finder Expression: aql:self.findTargetInstances('basicfamily:mother')
    
  4. In the basicfamily.odesign editor, expand the motherEdge node and select the nested Edge Style node. In the Property Sheet, change the following:

    Color:
    - Stroke Color: purple
    

Save the basicfamily.odesign editor. Now, we will create an instance of this diagram editor in the description model.

  1. In the Model Explorer view, navigate to the file basicfamily-model/src/oml/example.com/description/family1.oml. Expand the file’s node to reveal the root description node. Right click the nested family1 node and select New Representation -> new Persons diagram. Rename the diagram to Persons diagram. Click OK.

  2. The new diagram editor opens. Drag it and dock it to the right editor stack. It should look like this:

When compared to the built-in description diagram, the new custom diagram is much more concise and readable. So far, this is a viewer only, now we will turn it into an editor. We will start by adding tools to create Man and Woman nodes.

  1. In the basicfamily.odesign editor, right click on the Default node and select New Tool -> Section. In the property sheet, change the following:

    General:
    - Id: Tools
    
  2. Right click on the Tools node and select New Element Creation -> Node Creation. In the property sheet, change the following:

    General:
    - Id: createMan
    - Label: Man
    - Node Mapping: ManNode
    Advanced:
    - Icon Path: /basicfamily-viewpoint/icons/man.gif
    
  3. Expand the Node Creation Man node and right click on the nested container node and select New Variable -> Expression Variable. In the property sheet, change the following:

    General:
    - Name: context
    - Label: aql:container.getDescription()
    
  4. Copy the context variable and paste it under the same container variable. In the property sheet, change the following:

    General:
    - Name: containerInstance
    - Label: aql:container.oclAsType(oml::ConceptInstance)
    
  5. Right click on the nested Begin node, select New Operation -> Change Context. In the property sheet, change the following:

    General:
    - Browse Expression: aql:context.createConceptInstance('basicfamily:Man', containerInstance, 'basicfamily:members')
    
  6. Right click on the new Change Context... node, select New Operation -> Set. In the property sheet, change the following:

    General:
    - Feature Name: name
    - Value Expression: aql:context.getNewMemberName('man')
    
  7. Copy the Node Creation Man node and paste it in the Section Tools node. In the property sheet, change the following:

    General:
    - Id: createWoman
    - Label: Woman
    - Node Mapping: WomanNode
    Advanced:
    - Icon Path: /basicfamily-viewpoint/icons/woman.gif
    
  8. Expand the Node Creation Woman node and select the nested Change Context... node. In the property sheet, change the following:

    General:
    - Browse Expression: aql:context.createConceptInstance('basicfamily:Woman', containerInstance, 'basicfamily:members')
    
  9. Select the nested Set node. In the property sheet, change the following:

    General:
    - Value Expression: aql:context.getNewMemberName('woman')
    

Now, we will add tools to create Father and Mother edges.

  1. Right click on the Tools node and select New Element Creation -> Edge Creation. In the property sheet, change the following:

    General:
    - Id: createFather
    - Label: Father
    - Edge Mapping: FatherEdge
    Advanced:
    - Icon Path: /basicfamily-viewpoint/icons/father.png
    
  2. Expand the Edge Creation Father node and right click on the nested source node and select New Variable -> Expression Variable. In the property sheet, change the following:

    General:
    - Name: context
    - Label: aql:source.getDescription()
    
  3. Copy the context variable and paste it under the source variable. In the property sheet, change the following:

    General:
    - Name: sourceInstance
    - Label: aql:source.oclAsType(oml::ConceptInstance)
    
  4. Copy the sourceInstance variable and paste it under the target variable. In the property sheet, change the following:

    General:
    - Name: targetInstance
    - Label: aql:target.oclAsType(oml::ConceptInstance)
    
  5. Right click on the Begin node of the tool and select New Operation -> Change Context. In the property sheet, change the following:

    General:
    - Browse Expression: aql:context.addPropertyValue(sourceInstance, 'basicfamily:father', targetInstance)
    
  6. Copy the Edge Creation Fatehr node and paste it under the Tools node. In the property sheet, change the following:

    General:
    - Id: createMother
    - Label: Mother
    - Edge Mapping: MotherEdge
    Advanced:
    - Icon Path: /basicfamily-viewpoint/icons/mother.png
    
  7. Navigate to the nested Begin -> Change Context... node. In the property sheet, change the following:

    General:
    - Browse Expression: aql:context.addPropertyValue(sourceInstance, 'basicfamily:mother', targetInstance)
    

Now that we created four create tools, we will create a couple of delete (from model) tools.

The first is Recursive delete, which when run on an instance, it deletes the instance and recursively its links (e.g., deleting a Man instance deletes its incoming and outgoing father and mother links).

  1. Right click on the Section Tools node and select New Element Edition -> Delete Element. In the property sheet, change the following:

    General:
    - Id: Recursive
    - Mappings: ManNode, WomanNode, FatherEdge, MotherEdge
    
  2. Expand the Delete Element Recursive node, right click on the Begin node and select New Operation -> Change Context. In the property sheet, change the following:

    General:
    - Browse Expression: aql:self.recursiveDelete()
    

The second is Cascade delete, which when run on an instance, it deletes the instance, its links, and its related instances based on cascade delete rules. For example, when deleting a Man or Woman instance, we can configure it to cascade the delete to their children recursively.

  1. Right click on the Section Tools node and select New Menu -> Operation Action. In the property sheet, change the following:

    General:
    - Id: Cascade Delete
    - Precondition: ManNode, WomanNode, FatherEdge, MotherEdge
    
  2. Expand the Operation Action Cascade Delete node, right click on the Begin node and select New Operation -> Change Context. In the property sheet, change the following:

    General:
    - Browse Expression: aql:self.cascadeDelete()
    

The recursiveDelete function used in the expression is not provided by the openCAEASR API. Instead, we need to implement it as a service in the viewpoint project itself.

  1. In the basicfamily.odesign editor, double click on the defaultpackage.Services node. This opens a Java editor for the Services class. Replace the content by the following. Save the editor after.

package defaultpackage;

import java.util.ArrayList;
import java.util.HashSet;

import io.opencaesar.oml.NamedInstance;
import io.opencaesar.oml.Relation;
import io.opencaesar.oml.util.OmlDelete;
import io.opencaesar.oml.util.OmlDelete.CascadeDirection;
import io.opencaesar.oml.util.OmlDelete.CascadeRule;
import io.opencaesar.oml.util.OmlRead;
import io.opencaesar.rosetta.sirius.viewpoint.OmlServices;

public class Services {
    
    public void cascadeDelete(NamedInstance instance) {
    	var cascadeRules = new ArrayList<CascadeRule>();
    	
    	Relation parents = (Relation) OmlRead.getMemberByAbbreviatedIri(instance.getOntology(), "basicfamily:parents");
    	cascadeRules.add(new CascadeRule(CascadeDirection.TARGET_TO_SOURCE, null, parents, null, "delete children"));
    	
    	var result = OmlDelete.cascadeDelete(instance, cascadeRules);
    	OmlDelete.recursiveDelete(result);
    }
}

Now that we created the tools, we can use them on the family1 diagram.

  1. In the family1 diagram, click the palette arrow on the right-hand-side to reveal the node/edge creation tools. Then use the tools to create a man and a woman that are father and mother of another man.

  1. Save the diagram and inspect the family1.oml file with the OML text editor. It should have the following at the end:

instance man : basicfamily:Man

instance woman : basicfamily:Woman

instance man1 : basicfamily:Man [
	basicfamily:father man
	basicfamily:mother woman
]
  1. Select the family1 diagram editor again, and from the main menu run Undo a few times until the new changes are all undone. Save the editor. Look at the family1.oml text editor and observe how the new statements got deleted.

Now, we will try the delete tools.

  1. In the family1 diagram, select Paul and from the diagram’s toolbar select the Delete from Model button. This invokes the Recursive delete tool. After this, undo the delete.

  1. Select Paul again and this time right click on it and select Cascade Delete action in the menu. This invokes the Cascade delete tool where it deletes Paul and his offspring. Aftr this, undo the delete. Save the editor.

5.7. Create Table Editor

In this step, we will define a custom table editor for OML description models that use the basicfamily OML vocabulary.

  1. In the basicfamily.odesign editor, right click on the persons viewpoint and select New Representation -> Edition Table Description. In the Property Sheet, change the following information:

    General:
    - Id: Persons table
    - Domain Class: oml.ConceptInstance
    Metamodels:
    - Click Add from registry and select http://opencaesar.io/oml.
    Advanced:
    - Precondition Expression: aql:self.findIsKindOf('basicfamily:Family')
    

Similar to the custom diagram, this enables the custom table to be createable on concept instances of type basicfamily:Family. Next, we creat a Line (Row) in the table.

  1. Right click on the Persons table node and select New Table Element -> Line. In the Property Sheet, change the following:

    General:
    - Id: PersonLine
    - Domain Class: oml.ConceptInstance
    - Semantic Candidates Expression: aql:self.findTargetInstances('basicfamily:members')
    Label:
    - Header Label Expression: feature:name
    
  2. Right click on the PersonLine node and select New Style -> Foreground. In the property sheet, change the following:

    Label:
    - Label Size: 11
    
  3. Right click on the Persons table node and select New Table Element -> Feature Column. In the property sheet, change the following:

    General:
    - Id: FatherColumn
    - Feature Name: name
    Label:
    - Header Label Expression: Father
    Advanced:
    - Feature Parent Expression: aql:self.findTargetInstance('basicfamily:father')
    
  4. Right click on the Persons table node and select New Table Element -> Feature Column. In the property sheet, change the following:

    General:
    - Id: MotherColumn
    - Feature Name: name
    Label:
    - Header Label Expression: Mother
    Advanced:
    - Feature Parent Expression: aql:self.findTargetInstance('basicfamily:mother')
    
  5. Right click on the Persons table node and select New Table Element -> Feature Column. In the property sheet, change the following:

    General:
    - Id: ChildrenColumn
    - Feature Name: *
    Label:
    - Header Label Expression: Children
    - Label Expression: aql:self.findTargetInstances('basicfamily:children')->size()
    
  6. Right click on the Persons table node and select New Table Element -> Feature Column. In the property sheet, change the following:

    General:
    - Id: SiblingsColumn
    - Feature Name: *
    Label:
    - Header Label Expression: Siblings
    - Label Expression: aql:self.calculateSiblingsCount()
    

This last label expression is not a standard API from openCAESAR, but rather a custom Java function in basicfamily-viewpoint project.

  1. In basicfamily.odesign editor, double click on defaultpackage.Services node. This opens a Java editor for Services class. Add the following function to the class:

public int calculateSiblingsCount(NamedInstance self) {
	var siblings = new HashSet<>();

	var parents = OmlServices.findTargetInstances(self, "basicfamily:parents");
	for (var parent : parents) {
		siblings.addAll(OmlServices.findTargetInstances(parent, "basicfamily:children"));
	}
	
	siblings.remove(self);
	
	return siblings.size();
}

Now, we are not ready to create an instance of the table for the family1 instance.

  1. In the Model Explorer view, navigate to the file basicfamily-model/src/oml/example.com/description/family1.oml. Expand the file’s node to reveal the root description node. Right click the nested family1 node and select New Representation -> new Persons table. Rename the diagram to Persons table. Click OK.

  2. The new table editor opens. Drag it and dock it to the right editor stack while docking the diagram to the left stack. Compare the info presented in both the table and the diagram to verify they are in sync.

So far, this table is a viewer only, now we will turn it into an editor. We will start by adding tools to create Man and Woman nodes.

  1. In the basicfamily.odesign editor, right click on the Persons table node and select New Tool -> Create Line Tool. In the property sheet, change the following:

    General:
    - Id: createManLine
    - Label: New Man
    - Mapping: PersonLine
    

Expand the New Man node to reveal the variable nodes.

  1. Right click on the root variable and select New Variable -> Expression Variable. In the property sheet, change the following:

    General:
    - Name: context
    - Computation Expression: aql:root.getDescription()
    
  2. Right click on the root variable and select New Variable -> Expression Variable. In the property sheet, change the following:

    General:
    - Name: containerInstance
    - Computation Expression: aql:root.oclAsType(oml::ConceptInstance)
    
  3. Right click on the New Man node and select New Operation -> Change Context. In the property sheet, change the following:

    General:
    - Browse Expression: aql:context.createConceptInstance('basicfamily:Man', containerInstance, 'basicfamily:members')
    
  4. Right click on the last Change Context node and select New Operation -> Set. In the property sheet, change the following:

    General:
    - Feature Name: name
    - Value Expression: aql:context.getNewMemberName('man')
    
  5. Copy the New Man node and paste it under the Persons table node. In the property sheet, change the following:

    General:
    - Id: createWomanLine
    - Label: New Woman
    - Mapping: PersonLine
    
  6. Expand the New Woman node and navigate to the nested Change Context node. In the property sheet, change the following:

    General:
    - Value Expression: aql:context.createConceptInstance('basicfamily:Woman', containerInstance, 'basicfamily:members')
    
  7. Navigate to the nested Set node. In the property sheet, change the following:

    General:
    - Value Expression: aql:context.getNewMemberName('woman')
    

We will now add a Delete tool

  1. Right click on the PersonsLine node and select New Tool -> Delete Line Tool. In the property sheet, change the following:

    General:
    - Id: Delete
    
  2. Right click on the Delete node and select New Operation -> Change Context. In the property sheet, change the following:

    General:
    - Browse Expression: aql:self.recursiveDelete()
    

Now, let us use these tools to add and delete persons from the table and observe the diagram in the same time.

  1. In the Persons table, right click on any row and select from the popup menu New Man. Notice that a new row gets added to the end of the table and on the diagram in the same time. Repeat the step creating a New Woman this time. Finally select both new rows, right click and choose Delete Line.

  1. Similarly, in the Persons diagram, use the pallette tools to create a man and a woman. Observe how the table stays in sync and shows new rows. Then, use the delete tool on the diagram to delete them and observe them being deleted from the table. Save the editor after.

5.8. Create SPARQL Queries

In this step, we will add a couple of SPARQL queries to the basicfamily-model project to explore how OML has powerful query and reasoning capabilities. One of the queries is meant to replicate the sibling relation in the table above. The other queries for a cousin relation in a couple of ways.

  1. In the Model Explorer view, navigate to basicfamily-model/src folder, right click and choose New -> Folder, and name it sparql.

  2. Right click on the sparql folder and choose New -> File, and name it siblings.sparql. Click Finish. A text editor opens up.

  3. In the siblings.sparql editor, copy/paste the following SPARQL code. Save the editor.

PREFIX basicfamily:        <http://example.com/vocabulary/basicfamily#>

SELECT DISTINCT ?person (COUNT(?sibling) as ?siblings)
WHERE {
	?person a basicfamily:Person .
	OPTIONAL {
		?person basicfamily:parents ?parent .
		?parent basicfamily:children ?sibling .
		FILTER (?person != ?sibling)
	}
}
GROUP BY ?person ?parent
ORDER BY ?person
  1. Click in the Gradle Tasks view and double click on the task basicfamily-model/oml/owlQuery. Execution runs in the Gradle Executions view.

  2. Inspect the results by navigating in the Model Explorer view to the basicfamily-model/build folder. Right click on it and select Refresh. Navigate to the basicfamily-model/build/results/silblings.json file. Double-click to open it. You should see the following result:

{ "head": {
    "vars": [ "person" , "siblings" ]
  } ,
  "results": {
    "bindings": [
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Alain" } ,
        "siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Albert" } ,
        "siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Bryan" } ,
        "siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "3" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Clara" } ,
        "siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "3" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Dave" } ,
        "siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "3" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Elias" } ,
        "siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "1" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Fiona" } ,
        "siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "3" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Isa" } ,
        "siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Jane" } ,
        "siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Katell" } ,
        "siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Lea" } ,
        "siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "1" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Paul" } ,
        "siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Peter" } ,
        "siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      }
    ]
  }
}

Compare this result to the one we got previously in the Persons table. It should be the same.

  1. In the Model Explorer view, right click on the sparql folder and choose New -> File, and name it cousins.sparql. Click Finish. A text editor opens up.

  2. In the cousins.sparql editor, copy/paste the following SPARQL code. Save the editor.

PREFIX basicfamily:        <http://example.com/vocabulary/basicfamily#>

SELECT DISTINCT ?person (COUNT(?cousin) as ?cousins)
WHERE {
	?person a basicfamily:Person .
	OPTIONAL {
		?person basicfamily:parents ?parent .
		?parent basicfamily:parents ?grandparent .
		?grandparent basicfamily:children ?uncleOrAunt .
		?uncleOrAunt basicfamily:children ?cousin .
		FILTER(?uncleOrAunt != ?parent)
	}
}
GROUP BY ?person
ORDER BY ?person
  1. Click in the Gradle Tasks view and double click on the task basicfamily-model/oml/owlQuery. Execution runs in the Gradle Executions view.

  2. Inspect the results by navigating in the Model Explorer view to the basicfamily-model/build folder. Right click on it and select Refresh. Navigate to the basicfamily-model/build/results/cousins.json file. Double-click to open it. You should see the following result:

{ "head": {
    "vars": [ "person" , "cousins" ]
  } ,
  "results": {
    "bindings": [
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Alain" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "2" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Albert" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Bryan" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Clara" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Dave" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Elias" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Fiona" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Isa" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Jane" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "2" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Katell" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Lea" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Paul" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Peter" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "2" }
      }
    ]
  }
}
  1. Verify this result against the info in the Persons diagram.

Notice that the cousin relation is not asserted in the model but is derived by query. This suggests that every query that needs to match cousins would need to incorporate the same derivation logic, which could potentially make them both complex to write and costly to run. Besides, if this relation matters for logical reasoning (e.g., if you want to consider cousins marrying to be inconsistent), then you may want to add it to the vocabulary explicitly but still derive it by a rule.

  1. In the Model Explorer view, navigate to basicfamily-model/src/oml/example.com/vocabulary/basicfamily.oml and double click to open it.

  2. Add the following OML fragment to the end of the basicfamily vocabulary (before the closing braces):

relation cousin [
	from Person
	to Person
]

rule infer_couson [
	parents(x, y) & 
	parents(y, z) & 
	children(z, c) & 
	differentFrom(c, y) &
	children(c, m) ->
	cousin(x, m)
]

This basically adds the relation cousin to the vocabulary and provides a rule (equivalent to the query we saw previously) to infer it when the DL reasoner generate entailments. This makes the cousin axioms available directly as part of the dataset, which makes querying for them easier (as we will see) and usable by the reasoner when checking consistency. Of course, this comes at the expense of inflating the size of the dataset, which may or may not be a concern.

  1. In the Model Explorer view, navigate to basicfamily-model/src/sparql/cousins.sparql file and double click to open it. Replace the contents by:

PREFIX basicfamily:        <http://example.com/vocabulary/basicfamily#>

SELECT DISTINCT ?person (COUNT(?cousin) as ?cousins)
WHERE {
	?person a basicfamily:Person .
	OPTIONAL {
		?person basicfamily:cousin ?cousin
	}
}
GROUP BY ?person
ORDER BY ?person

Notice, how the pattern for matching cousin is now direct (one hop).

  1. Click in the Gradle Tasks view and double click on the task basicfamily-model/oml/owlQuery. Execution runs in the Gradle Executions view.

  2. Inspect the results in the basicfamily-model/build/results/cousins.json file. You should see the following result:

{ "head": {
    "vars": [ "person" , "cousins" ]
  } ,
  "results": {
    "bindings": [
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Alain" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Albert" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Bryan" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Clara" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Dave" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Elias" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Fiona" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Isa" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Jane" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Katell" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Lea" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Paul" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Peter" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      }
    ]
  }
}

Opps, this result is unexpected. Why were no cousins found? If you look more closely at the infer_cousin rule (above) again, you will see that it uses the standard differentFrom predicate to ensure that a parent is not matched again as an uncle or an aunt (children of a grandparent). So where is the problem? The problem can be explained by the fact that the vocabulary, as defined, lacks a way to make a person different from another person. Having different names is not by itself sufficient, since a DL reasoner may infer that they are just aliases to the same individual. So how do we fix this? We need to either add a key axiom to the Person aspect to say that one (or more) of its properties should be considered as a unique key. Alternatively, we can configure the DL reason to use the Unique Name Assumption by turning on the uniqueNames option on the owlReason task in the build.gradle script. Since we demonstrated the use of keys in Tutorial 1 already, we will use the second method here.

  1. In the Model Explorer view, navigate to basicfamily-model/build.gradle file and double click to open it. Find the owlReason task and set the uniqueNames flag to true as follows:

task owlReason(type:io.opencaesar.owl.reason.OwlReasonTask, group:"oml", dependsOn: omlToOwl) {
   ...
    // use unique name assumption
    uniqueNames = true
}
  1. Rerun the task basicfamily-model/oml/owlQuery and inspect the results in the basicfamily-model/build/results/cousins.json file. You should see the following correct result now:

{ "head": {
    "vars": [ "person" , "cousins" ]
  } ,
  "results": {
    "bindings": [
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Alain" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "2" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Albert" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Bryan" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Clara" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Dave" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Elias" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Fiona" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Isa" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Jane" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "2" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Katell" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Lea" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Paul" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
      } ,
      { 
        "person": { "type": "uri" , "value": "http://example.com/description/family1#Peter" } ,
        "cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "2" }
      }
    ]
  }
}

5.9. Summary

In this comprehensive tutorial, we’ve delved into some captivating facets of OML. Firstly, while OML’s textual syntax offers a highly convenient means of working with the language, it’s important to note that it’s not the sole method available to OML modelers. OML Rosetta takes the experience a step further by furnishing ready-made graphical views (diagrams) for both authoring and viewing OML models. What’s truly remarkable is that these graphical and textual views remain in perfect harmony, allowing modelers to seamlessly switch between them without missing a beat.

Furthermore, OML Rosetta empowers users with the ability to craft custom views, leveraging the robust Sirius framework. These custom views can encompass diagrams, tables, and more, all designed to enhance the user experience and make it more accessible. Imagine the potential here – not only can this feature be used to create specialized, methodology-specific views that guide users through systematically describing their systems, step by step (akin to the approach we outlined in Tutorial 2), but these views also stay impeccably synchronized since they stem from the same semantic models.

Lastly, we revisited the remarkable analytical capabilities that OML brings to the table. While certain analyses can be effortlessly integrated into the authoring views – as exemplified by our demonstration of computing sibling counts within the table view using the OML Java API – OML’s true power lies in its capacity to infer axioms using OML rules and subsequently query these axioms with SPARQL queries. This not only grants methodologists unparalleled control over vocabulary expressiveness, dataset size, and analysis performance but also opens the door to more intricate and potent analyses. With OML, the potential for robust, insightful modeling and analysis knows no bounds.

6. Tutorial 6: OML Federation

Note: This tutorial builds on the skills learned in Tutorial 2. Please do that first before proceeding.

6.1. Learning Objectives

This tutorial illustrates the utilization of OML in a federated approach for modeling and analyzing systems. In this context, federation entails the distribution of a model across distinct projects, which may reside in different repositories, encompass various concerns, and be overseen by different authorities. Within this federated framework, these projects establish dependencies on one another to import their artifacts, typically in the form of archives or models and analyses. This strategic approach not only facilitates the clear demarcation of concerns by defining well-defined boundaries but also enhances collaborative efforts. It enables concurrent work processes and allows for synchronized planning, fostering efficient teamwork.

Furthermore, we will employ a compelling real-world scenario to highlight the concept of federation support. Our illustrative scenario revolves around a home security system. Initially, an acquiring organization designs the system at an abstract level using a specific methodology. Subsequently, distinct components of this architectural design are entrusted to various suppliers for implementation with tangible, concrete components. Finally, the acquiring organization orchestrates the integration of these concrete components, utilizing the established architecture, and conducts a comprehensive analysis of this integrated system.

Upon completing this tutorial, you will gain the following essential skills:

Although the tutorial provides insights into leveraging federation for efficient modeling, collaboration, and system analysis in a representative project, it is not the intention of this tutorial to suggest a particular federation strategy. This is a matter of methodology, which is an orthogonal concern.

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.

6.2. Introduction to Federation

OML federation can be implemented in one of the following (incrementally-federated) styles:

  1. OML sub projects of a common parent, depend on each other directly, managed in a single repo.

  2. OML sub projects of a common parent, depend on each other through Maven, managed in a single repo.

  3. OML projects are all root, depend on each other through Maven, managed in a single repo.

  4. OML projects are all root, depend on each other through Maven, managed in different repos.

We will primarily follow style 1 in our demonstration of the home security example. We will then note the differences needed for other styles at the end.

The following specific OML projects will be created for the example:

  1. Parent: used to nest the sub projects and facilitate their direct inter-dependencies.

  2. Methodology: used to define the vocabulary for the systems modeling methodology.

  3. Home Security: used to define an abstract security system architecture (depends on 2).

  4. Smart Sensors: used to realize the sensor components by the Smart supplier (depends on 3).

  5. Safe Alarms: used to realize the alarm system by the Safe supplier (depends on 3).

  6. Supreme Monitors: used to realize the monitoring system by the Supreme supplier (depends on 3).

  7. Secure Systems: used to integrate realized components by the Secure acquirer (depends on 4, 5, 6).

The above dependencies reflect our federation strategy. First, we define the system modeling methodology we will use. After that, we use the methodology to define an architecture for a home security system at an abstract level identifying the main components and their characteristics, interrelations, and constraints. Then, we give each component to a supplier to realize it in a way that conforms to the abstract architecture. Finally, a system integrator/acquirer (e.g., an OEM, a solution provider) integrates the realized components into a realization architecture and analyzes its conformance to the abstract architecture.

6.3. Create Federated Projects

In this step, we will create all the projects of the example and configure their inter-dependencies as specified above.

We will use the Project Explorer view as it supports project nesting better than the Model Explorer view. If not already visible, from the menu, select Window -> Show View -> Other ..., search for 'Project Explorer', select it and click Open.

Parent Project

  1. In Project Explorer view, right click to choose New -> Other ... -> Gradle -> Gradle Project.

Recall that an OML project is itself a Gradle project, hence can be nested in a parent Gradle project.

  1. Click Next a couple of times to get to the New Gradle Project page. Specify the project name as homesecurity-models. Click Finish.

  2. Expand the project in the Project Explorer view, right click on the nested libs folder and choose Delete. Check the box "Delete project contents on disk" and click OK.

  3. Navigate to the settings.gradle file in Project Explorer, double click to open it. Remove the include('lib') line. Instead, insert the following lines. Save and close the editor.

    include 'Methodology'
    include 'HomeSecurity'
    include 'SmartSensors'
    include 'SafeAlarms'
    include 'SupremeMonitors'
    include 'SecureSystems'
    

Methodology Project

  1. In Project Explorer view, right click to choose New -> OML Project. Specify the project name as Methodology.

  2. Uncheck the Use default location box, browse to the folder of the parent project and click Open. Append /Methodology to the path. Click Next.

  3. Specify the project properties as follows, then click Finish.

    Base IRI: http://example.com/methodology
    Bundle Kind: Vocabulary
    Bundle Namespace: http://example.com/methodology/bundle#
    Title: Methodology
    Description: This is the methodology example
    

Note: If you get a Problem Occurred message box, it’s a known issue. Click on OK to simply dismiss it.

Although we define the methodology in a single project here, it could itself be federated into multiple projects. It is also common for a methodology to extend from other established (public or private) methodologies, in which case it would declare dependencies on them.

HomeSecurity Project

  1. In Project Explorer view, right click to choose New -> OML Project. Specify the project name as HomeSecurity.

  2. Uncheck the Use default location box, browse to the folder of the parent project and click Open. Append /HomeSecurity to the path. Click Next.

  3. Specify the project properties as follows, then click Finish.

    Base IRI: http://example.com/homesecurity
    Bundle Kind: Description
    Bundle Namespace: http://example.com/homesecurity/bundle#
    Title: Home Security
    Description: This is the home security example
    
  4. Expand the new HomeSecurity project and double click to open build.gradle file.

  5. Navigate to the dependencies section (right above downloadDependencies task), replace the default dependency with oml project(':Methodology').

SmartSensors Project

  1. In Project Explorer view, right click to choose New -> OML Project. Specify the project name as SmartSensors.

  2. Uncheck the Use default location box, browse to the folder of the parent project and click Open. Append /SmartSensors to the path. Click Next.

  3. Specify the project properties as follows, then click Finish.

    Base IRI: http://example.com/smart
    Bundle Kind: Vocabulary
    Bundle Namespace: http://example.com/smart/bundle#
    Title: Smart Sensors
    Description: This is the Smart sensors example
    
  4. Expand the new SmartSensors project and double click to open build.gradle file.

  5. Navigate to the dependencies section (right above downloadDependencies task), replace the default dependency with oml project(':HomeSecurity').

SafeAlarms Project

  1. In Project Explorer view, right click to choose New -> OML Project. Specify the project name as SafeAlarms.

  2. Uncheck the Use default location box, browse to the folder of the parent project and click Open. Append /SafeAlarms to the path. Click Next.

  3. Specify the project properties as follows, then click Finish.

    Base IRI: http://example.com/safe
    Bundle Kind: Vocabulary
    Bundle Namespace: http://example.com/safe/bundle#
    Title: Safe Alarms
    Description: This is the Safe alarms example
    
  4. Expand the new SafeAlarms project and double click to open build.gradle file.

  5. Navigate to the dependencies section (right above downloadDependencies task), replace the default dependency with oml project(':HomeSecurity').

SupremeMonitors Project

  1. In Project Explorer view, right click to choose New -> OML Project. Specify the project name as SupremeMonitors.

  2. Uncheck the Use default location box, browse to the folder of the parent project and click Open. Append /SupremeMonitors to the path. Click Next.

  3. Specify the project properties as follows, then click Finish.

    Base IRI: http://example.com/supreme
    Bundle Kind: Vocabulary
    Bundle Namespace: http://example.com/supreme/bundle#
    Title: Supreme Monitors
    Description: This is the Supreme monitors example
    
  4. Expand the new SupremeMonitors project and double click to open build.gradle file.

  5. Navigate to the dependencies section (right above downloadDependencies task), replace the default dependency with oml project(':HomeSecurity').

This is how the Project Explorer should look like now:

SecureSystems Project

  1. In Project Explorer view, right click to choose New -> OML Project. Specify the project name as SecureSystems.

  2. Uncheck the Use default location box, browse to the folder of the parent project and click Open. Append /SecureSystems to the path. Click Next.

  3. Specify the project properties as follows, then click Finish.

    Base IRI: http://example.com/secure
    Bundle Kind: Description
    Bundle Namespace: http://example.com/secure/bundle#
    Title: Secure Systems
    Description: This is the Secure systems example
    
  4. Expand the new SecureSystems project and double click to open build.gradle file.

  5. Navigate to the dependencies section (right above downloadDependencies task), replace the default dependency with:

    oml project(':SmartSensors')
    oml project(':SafeAlarms')
    oml project(':SupremeMonitors')
    

We will now do one last step, which is a workaround for a temporary issue with the Gradle plugin in Eclipse.

  1. In Project Explorer, navigate to homesecurity-models/.settings/org.eclipse.buildship.core.prefs and open it. Clear the value for the property connection.project.dir attribute. It should now read:

    connection.project.dir=
    

6.4. Define Modeling Methodology

In this tep, we define a very simple system modeling vocabulary that is a subset of the IMCE Vocabularies.

  1. In Project Explorer, navigate to the folder Methodology/src/oml/example.com/methodology. Right click and choose New -> OML Model.

  2. Specify the properties of the model as follows:

    Ontology Kind: Vocabulary
    Namespace: http://example.com/methodology/system#
    Prefix: system
    
  3. In the open OML editor, replace the contents by the following code then save the editor.

    vocabulary <http://example.com/methodology/system#> as system {
    
      extends <http://www.w3.org/2001/XMLSchema#> as xsd
    
      aspect IdentifiedElement [ 
        key hasIdentifier
      ]
    
      concept Component < IdentifiedElement
    
      concept Interface < IdentifiedElement
    
      concept Message < IdentifiedElement
    
      concept Junction < IdentifiedElement
    
      scalar property hasIdentifier [
        domain IdentifiedElement
        range xsd:string
        functional
      ]
    
      relation contains [
        from Component
        to Component
        reverse isContainedIn
        inverse functional
        asymmetric
        irreflexive
      ]
    
      relation presents [
        from Component
        to Interface
        reverse isPresentedBy
        inverse functional
        asymmetric
        irreflexive
      ]
    
      relation traverses [
        from Message
        to Junction
        reverse isTraversedBy
        asymmetric
        irreflexive
      ]
    
      relation joins [
        from Junction
        to Interface
        reverse isJoinedIn
        asymmetric
        irreflexive
      ]
    
      relation transfers [
        from Interface
        to Message
        asymmetric
        irreflexive
      ]
    
      relation transfersIn [
        from Interface
        to Message
        asymmetric
        irreflexive
      ] < transfers
    
      relation transfersOut [
        from Interface
        to Message
        asymmetric
        irreflexive
      ] < transfers
    }
    

The above vocabulary can be used to describe simple system architectures in terms of Components that can recursively contain other components. These components can present Interfaces that can be joined by Junctions, which can traverse Messages that are transferred in and out of those interfaces.

Note: that vocabulary was created using the built-in vocabulary editor seen in Tutorial 5.

We will now add the vocabulary to the project’s vocabulary bundle.

  1. In Project Explorer, navigate to the file Methodology/src/oml/example.com/methodology/bundle.oml and double click to open it. Replace content by the following then save the editor.

    vocabulary bundle <http://example.com/methodology/bundle#> as ^bundle {
    	
      includes <http://example.com/methodology/system#>
    }
    

Let us now verify the bundle is good by building it.

  1. In the Gradle Tasks view, run the task homesecurity-models/Methodology/build/build. The task should run successfully.

6.5. Define System Architecture

Now that we have defined a vocabulary in the Methodology project, we can use it define a Home Security abstract architecture. This is the first glimpse of the power of OML federation. It will give the HomeSecurity project the ability to reuse the system vocabulary defined by the Methodology project. It will do so by leveraging the project’s dependency we have previously established to download a read-only copy of the system vocabulary to the HomeSecurity project’s build/oml folder (where all the project’s read-only OML dependencies reside).

Let us first verify that the dependency is not already downloaded.

  1. Navigate to the folder HomeSecurity/build/oml and verify that there is no nested folder named example.com.

Now refresh the dependencies of the project using a simple menu action in the Project Explorer view.

  1. Right click on the HomeSecurity project and choose Gradle -> Refresh Gradle Project. This action downloads the latest version of the dependencies.

Note: An alternative is to run the oml/downloadDependencies task from the Gradle Tasks view.

  1. Right click on the folder HomeSecurity/build/oml and choose Refresh. Now, you can see a nested example.com folder. Expand it to see read-only copies of the OML files defined by the Methodology project including the system vocabulary.

Now that we have the dependency updated, we can now define the architecture vocabulary.

  1. In Project Explorer, navigate to the folder HomeSecurity/src/oml/example.com/homesecurity and right click. Choose New -> OML Model. Specify the following:

    Ontology Kind: Vocabulary
    Namespace: http://example.com/homesecurity/architecture#
    Prefix: hsa
    
  2. In the open OML editor, replace the contents by the following code then save the editor.

      vocabulary <http://example.com/homesecurity/architecture#> as hsa {
    
      extends <http://example.com/methodology/system#> as system
    
      concept SecuritySystem < system:Component [
        restricts system:presents to exactly 1 I1
        restricts system:presents to exactly 1 I2
        restricts system:contains to min 1 Sensor
        restricts system:contains to exactly 1 AlarmSystem
        restricts system:contains to exactly 1 MonitoringSystem
      ]
    
      concept I1 < system:Interface [
        restricts all system:transfersOut to Panic
      ]
    
      concept I2 < system:Interface [
        restricts all system:transfersIn to Dispatch
      ]
    
      concept Sensor < system:Component [
        restricts system:presents to exactly 1 I3
      ]
    
      concept I3 < system:Interface [
        restricts all system:transfersOut to Event
      ]
    
      concept AlarmSystem < system:Component [
        restricts system:presents to exactly 1 I4
        restricts system:presents to exactly 1 I5
        restricts system:presents to exactly 1 I6
      ]
    
      concept I4 < system:Interface [
        restricts all system:transfersIn to Event
      ]
    
      concept I5 < system:Interface [
        restricts all system:transfersIn to Panic
      ]
    
      concept I6 < system:Interface [
        restricts all system:transfersOut to Alarm
      ]
    
      concept MonitoringSystem < system:Component [
        restricts system:presents to exactly 1 I7
        restricts system:presents to exactly 1 I8
      ]
    
      concept I7 < system:Interface [
        restricts all system:transfersIn to Alarm
      ]
    
      concept I8 < system:Interface [
        restricts all system:transfersOut to Dispatch
      ]
    
      concept Junction1 < system:Junction [
        restricts system:joins to exactly 1 I1
        restricts system:joins to exactly 1 I5
        restricts all system:isTraversedBy to Panic
      ]
    
      concept Junction2 < system:Junction [
        restricts system:joins to exactly 1 I8
        restricts system:joins to exactly 1 I2
        restricts all system:isTraversedBy to Dispatch
      ]
    
      concept Junction3 < system:Junction [
        restricts system:joins to exactly 1 I3
        restricts system:joins to exactly 1 I4
        restricts all system:isTraversedBy to Event
      ]
    
      concept Junction4 < system:Junction [
        restricts system:joins to exactly 1 I6
        restricts system:joins to exactly 1 I7
        restricts all system:isTraversedBy to Alarm
      ]
    
      concept Event < system:Message
    
      concept Panic < system:Message
    
      concept Alarm < system:Message
    
      concept Dispatch < system:Message
    }
    

The above vocabulary describes a simple architectures for a Security System that consists of Sensors that detect and send Event messages to an AlarmSystem, which can also receive a Panic message from a user (e.g., through a UI). The AlarmSystem then sends an Alarm message to a Monitoring System, which responds by sending a Dispatch message to the Police.

Note: that vocabulary was created using the built-in vocabulary editor seen in Tutorial 5.

We will now create a vocabulary bundle and add the vocabulary to it.

  1. In Project Explorer, right click on HomeSecurity/src/oml/example.com/homesecurity and choose New -> OML Model. Specify the following:

    Ontology Kind: Vocabulary Bundle
    Namespace: http://example.com/homesecurity/v-bundle#
    Prefix: bundle
    
  2. Replace content of v-bundle.oml by the following then save the editor.

    vocabulary bundle <http://example.com/homesecurity/v-bundle#> as ^bundle {
      
      extends <http://example.com/methodology/bundle#>
      
      includes <http://example.com/homesecurity/architecture#>
    }
    

Notice that we defined the Home Security abstract architecture with a vocabulary as opposed to a description. This is to say that we do not have an individual architecture yet, but a class of such architectures that can be further specialized and constrained. However, it would be helpful to define a candidate architecture individual (or more) as a prototype to analyze.

  1. In Project Explorer, right click on HomeSecurity/src/oml/example.com/homesecurity and choose New -> OML Model. Specify the following:

    Ontology Kind: Description
    Namespace: http://example.com/homesecurity/prototype#
    Prefix: prototype
    
  2. Replace content of prototype.oml by the following then save the editor.

    description <http://example.com/homesecurity/prototype#> as prototype {
    
      uses <http://example.com/homesecurity/architecture#> as hsa
    
      uses <http://example.com/methodology/system#> as system
    
      // Security System
      instance SecuritySystem : hsa:SecuritySystem [
        system:hasIdentifier "SecuritySystem"
        system:presents I1
        system:presents I2
        system:contains Sensor
        system:contains AlarmSystem
        system:contains MonitoringSystem
      ]
      instance I1 : hsa:I1 [ system:hasIdentifier "I1" ]
      instance I2 : hsa:I2 [ system:hasIdentifier "I2" ]
      
      //  Sensor
      instance Sensor : hsa:Sensor [
        system:hasIdentifier "Sensor"
        system:presents I3
      ]
      instance I3 : hsa:I3 [ system:hasIdentifier "I3" ]
    
      // Alarm System
      instance AlarmSystem : hsa:AlarmSystem [
        system:hasIdentifier "AlarmSystem"
        system:presents I4
        system:presents I5
        system:presents I6
      ]
      instance I4 : hsa:I4 [ system:hasIdentifier "I4" ]
      instance I5 : hsa:I5 [ system:hasIdentifier "I5" ]
      instance I6 : hsa:I6 [ system:hasIdentifier "I6" ]
    
      // Monitoring System
      instance MonitoringSystem : hsa:MonitoringSystem [
        system:hasIdentifier "MonitoringSystem"
        system:presents I7
        system:presents I8
      ]
      instance I7 : hsa:I7 [ system:hasIdentifier "I7" ]
      instance I8 : hsa:I8 [ system:hasIdentifier "I8" ]
    
      // Junction 1
      instance Junction1 : hsa:Junction1 [
        system:hasIdentifier "Junction1"
        system:joins I1
        system:joins I5
      ]
    
      // Junction 2
      instance Junction2 : hsa:Junction2 [
        system:hasIdentifier "Junction2"
        system:joins I8
        system:joins I2
      ]
    
      // Junction 3
      instance Junction3 : hsa:Junction3 [
        system:hasIdentifier "Junction3"
        system:joins I3
        system:joins I4
      ]
    
      // Junction 4
      instance Junction4 : hsa:Junction4 [
        system:hasIdentifier "Junction4"
        system:joins I6
        system:joins I7
      ]
    }
    

This prototype can be depicted as shown below. We used a custom viewpoint defined here.

We will now add the prototype to the project’s description bundle.

  1. In Project Explorer, navigate to the file HomeSecurity/src/oml/example.com/homesecurity/bundle.oml and double click to open it. Replace content by the following then save the editor.

    description bundle <http://example.com/homesecurity/bundle#> as ^bundle {
      
      uses <http://example.com/homesecurity/architecture#>
        
      includes <http://example.com/homesecurity/prototype#>
    }
    

Let us now verify the bundle is good by building it.

  1. In the Gradle Tasks view, run the task homesecurity-models/HomeSecurity/build/build. The task should run successfully.

6.6. Federate Component Realization

In this step, we will enable three different suppliers (Smart, Safe, and Supreme) to realize the subcomponents (Sensor, Alarm System, Monitoring System) of the home security architecture. This means, each of them, in their respective project, will refresh their dependencies to download a read-only copy of the architceture vocabulary so they can import it in their respective realization vocabulary.

Smart Sensors

This supplier builds two kinds of sensors, a smoke sensor and a motion sensor. They will provide a sensors vocabulary that describes how their sensors conform to the home security abstract architecture.

Let us refresh the dependencies to get the latest copy of the architecture (and transitively the methodology).

  1. In Project Explorer, right click on homesecurity-models/SmartSensors folder and choose Gradle -> Refresh Gradle Project.

  2. Navigate to the folder SmartSensors/build/oml/example.com and verify it has nested homesecurity and methodology folders.

  3. Right click on the folder SmartSensors/src/oml/example.com/smart and choose New -> OML Model. Specify the following:

    Ontology Kind: Vocabulary
    Namespace: http://example.com/smart/sensors#
    Prefix: smart
    
  4. In the open OML editor, replace the contents by the following code then save the editor.

    vocabulary <http://example.com/smart/sensors#> as smart {
    
      extends <http://example.com/homesecurity/architecture#> as hsa
    
      extends <http://example.com/methodology/system#> as system
    
      concept SmokeSensor < hsa:Sensor [
        restricts all system:presents to I3.S
      ]
    
      concept I3.S < hsa:I3 [
        restricts all system:transfersOut to SmokeEvent
      ]
    
      concept MotionSensor < hsa:Sensor [
        restricts all system:presents to I3.M
      ]
    
      concept I3.M < hsa:I3 [
        restricts all system:transfersOut to MotionEvent
      ]
    
      concept SmokeEvent < hsa:Event
    
      concept MotionEvent < hsa:Event
    }
    

The above vocabulary describes a SmokeSensor and a MotionSensor, both represent concrete specializations of the Sensor concept form the architecture. Notice also that each of them present concrete interfaces that specialize the corresponding interface of the Sensor concept. Moreover, each of the interfaces declare a special kind of event, SmokeEvent and MotionEvent, that they can transfer out.

Note: that vocabulary was created using the built-in vocabulary editor seen in Tutorial 5.

We will now add the prototype to the project’s vocabulary bundle.

  1. In Project Explorer, open the file SmartSensors/src/oml/example.com/smart/bundle.oml and replace content by the following then save the editor.

    vocabulary bundle <http://example.com/smart/bundle#> as ^bundle {
      
      extends <http://example.com/homesecurity/v-bundle#>
      
      includes <http://example.com/smart/sensors#>
    }
    

Let us now verify the bundle is good by building it.

  1. In the Gradle Tasks view, run the task homesecurity-models/SmartSensors/build/build. The task should run successfully.

Safe Alarms

This supplier builds an Alarm System for a home security architecture. They will provide an alarms vocabulary that describes how their system conforms to the architecture.

Let us refresh the dependencies to get the latest copy of the architecture (and transitively the methodology).

  1. In Project Explorer, right click on homesecurity-models/SafeAlarms folder and choose Gradle -> Refresh Gradle Project.

  2. Navigate to the folder SafeAlarms/build/oml/example.com and verify it has nested homesecurity and methodology folders.

  3. Right click on the folder SafeAlarms/src/oml/example.com/safe and choose New -> OML Model. Specify the following:

    Ontology Kind: Vocabulary
    Namespace: http://example.com/safe/alarms#
    Prefix: safe
    
  4. In the open OML editor, replace the contents by the following code then save the editor.

    vocabulary <http://example.com/safe/alarms#> as safe {
      
      extends <http://example.com/homesecurity/architecture#> as hsa
    
      extends <http://example.com/methodology/system#> as system
    
      concept AlarmSystem < hsa:AlarmSystem [
        restricts system:presents to exactly 1 I4
        restricts system:presents to exactly 1 I5
        restricts system:presents to exactly 1 I6
        restricts system:presents to max 1 I9
      ]
      
      concept I4 < hsa:I4
    
      concept I5 < hsa:I5
    
      concept I6 < hsa:I6
    
      concept I9 < system:Interface [
        restricts all system:transfersOut to Sound
      ]
      
      concept Speaker < system:Component [
        restricts system:presents to exactly 1 I10
      ]
      
      concept I10 < system:Interface [
        restricts all system:transfersIn to Sound
      ]
    
      concept Junction5 < system:Junction [
        restricts system:joins to exactly 1 I9
        restricts system:joins to exactly 1 I10
        restricts all system:isTraversedBy to Sound 
      ]
    
      concept Sound < system:Message
      
    }
    

The above vocabulary describes AlarmSystem as a concrete specialization of the abstract AlarmSystem concept. Notice how the concrete system present concrete interfaces that specialize the corresponding three interfaces of the abstract Sensor. In addition, it adds an additional interface I9 that transfers Sound out as an alarm. That interface can be joined to a Speaker component they provide too.

Note: that vocabulary was created using the built-in vocabulary editor seen in Tutorial 5.

We will now add the prototype to the project’s vocabulary bundle.

  1. In Project Explorer, open the file SafeAlarms/src/oml/example.com/safe/bundle.oml and replace content by the following then save the editor.

    vocabulary bundle <http://example.com/safe/bundle#> as ^bundle {
      
      extends <http://example.com/homesecurity/v-bundle#>
      
      includes <http://example.com/safe/alarms#>
    }
    

Let us now verify the bundle is good by building it.

  1. In the Gradle Tasks view, run the task homesecurity-models/SafeAlarms/build/build. The task should run successfully.

Supreme Monitors

This supplier builds a Monitoring System for a home security architecture. They provide an monitors vocabulary that describes how their system conforms to the architecture.

Let us refresh the dependencies to get the latest copy of the architecture (and transitively the methodology).

  1. In Project Explorer, right click on homesecurity-models/SupermeMonitors folder and choose Gradle -> Refresh Gradle Project.

  2. Navigate to the folder SupermeMonitors/build/oml/example.com and verify it has nested homesecurity and methodology folders.

  3. Right click on the folder SupermeMonitors/src/oml/example.com/supreme and choose New -> OML Model. Specify the following:

    Ontology Kind: Vocabulary
    Namespace: http://example.com/supreme/monitors#
    Prefix: supreme
    
  4. In the open OML editor, replace the contents by the following code then save the editor.

    vocabulary <http://example.com/supreme/monitors#> as ^supreme {
      
      extends <http://example.com/homesecurity/architecture#> as hsa
    
      extends <http://example.com/methodology/system#> as system
    
      concept MonitoringSystem < hsa:MonitoringSystem [
        restricts system:presents to exactly 1 I7
        restricts system:presents to exactly 1 I8
      ]
      
      concept I7 < hsa:I7
      
      concept I8 < hsa:I8
      
    }
    

The above vocabulary describes MonitoringSystem as a concrete specialization of the abstract MonitoringSystem concept. Notice how the concrete system present concrete interfaces that specialize the corresponding two interfaces of the abstract MonitoringSystem.

Note: that vocabulary was created using the built-in vocabulary editor seen in Tutorial 5.

We will now add the prototype to the project’s vocabulary bundle.

  1. In Project Explorer, open the file SupremeMonitors/src/oml/example.com/supreme/bundle.oml and replace content by the following then save the editor.

    vocabulary bundle <http://example.com/supreme/bundle#> as ^bundle {
      
      extends <http://example.com/homesecurity/v-bundle#>
      
      includes <http://example.com/supreme/monitors#>
    }
    

Let us now verify the bundle is good by building it.

  1. In the Gradle Tasks view, run the task homesecurity-models/SupremeMonitors/build/build. The task should run successfully.

6.7. Integrate Federated Components

In this step, we will enable a system integrator (SecureSystems), who is also an EOM for Security Systems, to describe how it realizes the abstract home security architecture by integrating the realized components from the three suppliers above with its own top level security system to get a conforming realization architecture.

Secure Systems

Let us refresh the dependencies to get the latest copy of the realized components (and transitively the architecture and the methodology).

  1. In Project Explorer, right click on homesecurity-models/SecureSystems folder and choose Gradle -> Refresh Gradle Project.

  2. Navigate to the folder SecureSystems/build/oml/example.com and verify it has nested folders: methodology, homesecurity, smart, safe, and supreme.

Now that the integrator has all the dependencies it needs, it can proceed to describe the realization architecture.

  1. In Project Explorer, navigate to the folder SecureSystems/src/oml/example.com/secure and right click. Choose New -> OML Model. Specify the following:

    Ontology Kind: Vocabulary
    Namespace: http://example.com/secure/architecture#
    Prefix: secure
    
  2. In the open OML editor, replace the contents by the following code then save the editor.

    vocabulary <http://example.com/secure/architecture#> as secure {
    
      extends <http://example.com/methodology/system#> as system
    
      extends <http://example.com/homesecurity/architecture#> as hsa
    
      extends <http://example.com/smart/sensors#> as smart
    
      extends <http://example.com/safe/alarms#> as safe
    
      extends <http://example.com/supreme/monitors#> as supreme
    
      concept SecuritySystem < hsa:SecuritySystem [
        restricts system:presents to exactly 1 I1
        restricts system:presents to exactly 1 I2
        restricts system:contains to min 2 smart:SmokeSensor
        restricts system:contains to min 1 smart:MotionSensor
        restricts system:contains to exactly 1 safe:AlarmSystem
        restricts system:contains to exactly 1 supreme:MonitoringSystem
      ]
    
      concept I1 < hsa:I1
    
      concept I2 < hsa:I2
    
      // Junction 1
      concept Junction1 < hsa:Junction1 [
        restricts system:joins to exactly 1 I1
        restricts system:joins to exactly 1 safe:I5
      ]
    
      // Junction 2
      concept Junction2 < hsa:Junction2 [
        restricts system:joins to exactly 1 supreme:I8
        restricts system:joins to exactly 1 I2
      ]
    
      // Junction 3
      concept Junction3.S < hsa:Junction3 [
        restricts system:joins to exactly 1 smart:I3.S
        restricts system:joins to exactly 1 safe:I4
      ]
    
      concept Junction3.M < hsa:Junction3 [
        restricts system:joins to exactly 1 smart:I3.M
        restricts system:joins to exactly 1 safe:I4
      ]
    
      // Junction 4
      concept Junction4 < hsa:Junction4 [
        restricts system:joins to exactly 1 safe:I6
        restricts system:joins to exactly 1 supreme:I7
      ]
    }
    

The above vocabulary describes a concrete realization architecture for the abstract SecuritySystem. That concrete system uses Smart sensors (a minimum of 2 smoke sensors and one motion sensor), a Safe alarm system, and a Supreme monitoring system. In addition, it provides its own realization of the two SecuritySystem interfaces and all the junctions that connect the various realized interfaces together based on the abstract architecture.

Note: that vocabulary was created using the built-in vocabulary editor seen in Tutorial 5.

We will now create a vocabulary bundle and add the vocabulary to it.

  1. In Project Explorer, right click on SecureSystems/src/oml/example.com/secure and choose New -> OML Model. Specify the following:

    Ontology Kind: Vocabulary Bundle
    Namespace: http://example.com/secure/v-bundle#
    Prefix: bundle
    
  2. Replace content of v-bundle.oml by the following then save the editor.

    vocabulary bundle <http://example.com/secure/v-bundle#> as ^bundle {
    
      extends <http://example.com/smart/bundle#>	
      extends <http://example.com/safe/bundle#>
      extends <http://example.com/supreme/bundle#>
      
      includes <http://example.com/secure/architecture#>
    }
    

Again, it would be helpful to define a prototype description (or more) to analyze this specific realization architecture.

  1. In Project Explorer, right click on SecureSystems/src/oml/example.com/secure and choose New -> OML Model. Specify the following:

    Ontology Kind: Description
    Namespace: http://example.com/secure/prototype#
    Prefix: prototype
    
  2. Replace content of prototype.oml by the following then save the editor.

    description <http://example.com/secure/prototype#> as prototype {
    
      uses <http://example.com/methodology/system#> as system
    
      uses <http://example.com/smart/sensors#> as smart
    
      uses <http://example.com/safe/alarms#> as safe
    
      uses <http://example.com/supreme/monitors#> as supreme
    
      uses <http://example.com/secure/architecture#> as secure
    
      // Security System
      instance SecuritySystem : secure:SecuritySystem [
        system:hasIdentifier "Secure-01"
        system:presents I1
        system:presents I2
        system:contains SmokeSensor1
        system:contains SmokeSensor2
        system:contains MotionSensor
        system:contains AlarmSystem
        system:contains MonitoringSystem
      ]
    
      instance I1 : secure:I1 [
        system:hasIdentifier "Secure-02"
      ]
    
      instance I2 : secure:I2 [
        system:hasIdentifier "Secure-03"
      ]
    
      // Smart Smoke Sensor
      instance SmokeSensor1 : smart:SmokeSensor [
        system:hasIdentifier "Smart-01"
        system:presents I3.S1
      ]
    
      instance I3.S1 : smart:I3.S [
        system:hasIdentifier "Smart-02"
      ]
    
      instance SmokeSensor2 : smart:SmokeSensor [
        system:hasIdentifier "Smart-03"
        system:presents I3.S2
      ]
    
      instance I3.S2 : smart:I3.S [
        system:hasIdentifier "Smart-4"
      ]
    
      // Smart Motion Sensor
      instance MotionSensor : smart:MotionSensor [
        system:hasIdentifier "Smart-05"
        system:presents I3.M
      ]
    
      instance I3.M : smart:I3.M [
        system:hasIdentifier "Smart-06"
      ]
    
      // Safe Alarm System
      instance AlarmSystem : safe:AlarmSystem [
        system:hasIdentifier "Safe-01"
        system:presents I4
        system:presents I5
        system:presents I6
      ]
    
      instance I4 : safe:I4 [
        system:hasIdentifier "Safe-02"
      ]
    
      instance I5 : safe:I5 [
        system:hasIdentifier "Safe-03"
      ]
    
      instance I6 : safe:I6 [
        system:hasIdentifier "Safe-04"
      ]
    
      // Secure Monitoring System
      instance MonitoringSystem : supreme:MonitoringSystem [
        system:hasIdentifier "Supreme-01"
        system:presents I7
        system:presents I8
      ]
    
      instance I7 : supreme:I7 [
        system:hasIdentifier "Supreme-02"
      ]
    
      instance I8 : supreme:I8 [
        system:hasIdentifier "Supreme-03"
      ]
    
      // Junction 1
      instance Junction1 : secure:Junction1 [
        system:hasIdentifier "Secure-04"
        system:joins I1
        system:joins I5
      ]
    
      // Junction 2
      instance Junction2 : secure:Junction2 [
        system:hasIdentifier "Secure-05"
        system:joins I8
        system:joins I2
      ]
    
      // Junction 3
      instance Junction3.S1 : secure:Junction3.S [
        system:hasIdentifier "Secure-06"
        system:joins I3.S1
        system:joins I4
      ]
    
      instance Junction3.S2 : secure:Junction3.S [
        system:hasIdentifier "Secure-07"
        system:joins I3.S2
        system:joins I4
      ]
    
      instance Junction3.M : secure:Junction3.M [
        system:hasIdentifier "Secure-08"
        system:joins I3.M
        system:joins I4
      ]
    
      // Junction 4
      instance Junction4 : secure:Junction4 [
        system:hasIdentifier "Secure-09"
        system:joins I6
        system:joins I7
      ]
    }
    

This prototype can be depicted as shown below. We used a custom viewpoint defined here.

We will now add the prototype to the project’s description bundle.

  1. In Project Explorer, navigate to the file SecureSystems/src/oml/example.com/secure/bundle.oml and double click to open it. Replace content by the following then save the editor.

    description bundle <http://example.com/secure/bundle#> as ^bundle {
    
      uses <http://example.com/secure/v-bundle#>	
      
      includes <http://example.com/secure/prototype#>
    }
    

Let us now verify the bundle is good by building it.

  1. In the Gradle Tasks view, run the task homesecurity-models/SecureSystems/build/build. The task should run successfully.

Note: the reference repository contains the state of the models up to this point only.

6.8. Run Federation Scenarios

So far, we have seen the happy path for federation where upstream dependencies do not change and downstream dependencies always conform. Notice that change can always safely occur in any of the federated projects without impact as long as their downstream dependencies do not refresh their upstream dependencies. This is a great benefit of federation which allows projects to control the rate of update for their dependencies.

In this step, we will run other common federation scenarios to see the ability of OML to handle them. We will always undo the changes at the end of each scenario.

Scenario 1: Non-Breaking Methodology Change

In this scenario, we make a non-breaking change to the system methodology vocabulary.

  1. Open the file Methodology/src/oml/example.com/methodology/system.oml and change the contains relation by removing the irreflexive flag (a non-breaking change). Thew new relation looks like this:

    relation contains [
      from Component
      to Component
      reverse isContainedIn
      inverse functional
      asymmetric
    ]
    
  2. Right click on the project homesecurity-models and select Grade -> Refresh Grade Project. (Note the progress of this operation in the bottom-right corner of the IDE.)

Since we ran the action on a parent project, it runs on every nested project recursively resulting in all projects refreshing their dependencies. Since we changed the Methodology project, which is the most upstream dependency, all the other projects should get an updated version of that system vocabulary.

  1. Navigate to and open the read-only SecureSystems/build/oml/example.com/methodology/system.oml. Verify that the contains relation was modified.

  2. In Gradle Tasks view, run homesecurity-models/SecureSystems/build/build and verify that it still builds correctly.

  3. Undo the change in Methodology/src/oml/example.com/methodology/system.oml and right click on homesecurity-models and select Gradle -> Refresh Gradle Project.

Scenario 2: Breaking Methodology Change

In this scenario, we make a breaking syntax change to the system methodology vocabulary.

  1. Open the file Methodology/src/oml/example.com/methodology/system.oml and rename the contains relation to composes flag (a breaking change). Thew new relation looks like this:

    relation composes [
        from Component
        to Component
        reverse isContainedIn
        inverse functional
        asymmetric
        irreflexive
      ]
    
  2. Right click on the project homesecurity-models and select Grade -> Refresh Grade Project.

Verify that all the other projects show compile errors. Inspect the Problem view and should see many errors.

  1. Undo the change in Methodology/src/oml/example.com/methodology/system.oml and right click on homesecurity-models and select Gradle -> Refresh Gradle Project.

You should observe that the problems get resolved.

Scenario 3: Breaking Architectural Change

In this scenario, we make a breaking semantic change to the abstract architecture vocabulary.

  1. Open the file HomeSecurity/src/oml/example.com/homesecurity/architecture.oml and change the SecuritySystem concept to strenghten the restriction on the number of Sensors to be exactly 1 Sensor (a breaking change). Thew new concept looks like this:

    concept SecuritySystem < system:Component [
      restricts system:presents to exactly 1 I1
      restricts system:presents to exactly 1 I2
      restricts system:contains to exactly 1 Sensor
      restricts system:contains to exactly 1 AlarmSystem
      restricts system:contains to exactly 1 MonitoringSystem
    ]
    
  2. Right click on the project homesecurity-models and select Grade -> Refresh Grade Project.

  3. In Gradle Tasks view, run homesecurity-models/SecureSystems/build/build and verify that it gives an error.

  4. Double click to open the file SecureSystems/build/reports/reasoning.xml. You should see the following:

The error indicates that a max cardinality restriction is violated. This is nice because now the integrator SecureSystems gets alerted to the architectural change that calls for exactly 1 sensor, which is inconsistent with its realization architecture which calls for multiple sensors (see the code below).

// Realization SecuritySystem
concept SecuritySystem < hsa:SecuritySystem [
  restricts system:presents to exactly 1 I1
  restricts system:presents to exactly 1 I2
  restricts system:contains to min 2 smart:SmokeSensor
  restricts system:contains to min 1 smart:MotionSensor
  restricts system:contains to exactly 1 safe:AlarmSystem
  restricts system:contains to exactly 1 supreme:MonitoringSystem
]
  1. Undo the change in HomeSecurity/src/oml/example.com/homesecurity/architecture.oml and right click on homesecurity-models and select Gradle -> Refresh Gradle Project.

Scenario 4: Breaking Component Realization

In this scenario, we make a breaking semantic change in one of the realization components that gets detected at the integrated level.

  1. Open the file SafeAlarms/src/oml/example.com/safe/alarms.oml and change the AlarmSystem concept by changing the restriction on the I9 interface (for sound) to exactly 1. Thew new concept looks like this:

    concept AlarmSystem < hsa:AlarmSystem [
      restricts system:presents to exactly 1 I4
      restricts system:presents to exactly 1 I5
      restricts system:presents to exactly 1 I6
      restricts system:presents to exactly 1 I9
    ]
    
  2. Right click on the project homesecurity-models and select Grade -> Refresh Grade Project.

  3. In Gradle Tasks view, run homesecurity-models/SecureSystems/build/build and verify that it gives an error.

  4. Double click to open the file SecureSystems/build/reports/reasoning.xml. You should see the following:

The inconsistency in this case is a result of the prototype not incorporating the required sound interface. Note that such requirement is not called for in the abstract architecture but comes from the realization. This would be an opportunity for a conversation between all parties: the architect, the supplier, and the acquirer (the integrator).

  1. Undo the change in SafeAlarms/src/oml/example.com/safe/alarms.oml and right click on homesecurity-models and select Gradle -> Refresh Gradle Project.

6.9. Publish with Semantic Versions

We hope that the previous section convinced you that having proper support for federation is paramount. This is especially true in change scenarios. Controlling when to adopt a breaking change in your upstream dependencies gives you time to plan for the impact (the needed changes). But how can you tell if the change is breaking or not. The software industry has developed a very smart convention for this problem called semantic versions (for software libraries), where a major revision for indicates large breaking change, a minor revision indicates large non-breaking changes, and a patch revision indicates minor changes and/or bug fixes. In openCAESAR, we adopt the same approach of publishing OML models with semantic versions. This works really nice with the way OML projects declare their dependencies as Maven dependencies.

In this step, we will first publish all the projects to Maven Local (on the local machine). Then, we will convert one of the direct dependencies we declared before into a Maven-based one with a semantic version. This will be the dependency of the SecureSystems project on the SafeAlarm project.

Maven Local is a convenient playground for publishing Maven dependencies since it is only visible on that machine. Once a version is good and needs to be published more widely, it could be pushed to a remote accessible Maven repository (like Maven Central or an enterprise Maven repository).

  1. In Gradle Tasks view, run homesecurity-models/publishing/publishToMavenLocal and verify that it runs successfully.

Now, all the projects have published their OML models to Maven Local using the default 1.0.0 version.

  1. On your computer, navigate to your local Maven repository typically named .m2. Expand the nested path repository/com/example and you should see all the 1.0.0 project artifacts published there.

  2. Back in Rosetta, open the file SecureSystems/build.gradle and navigate to the dependencies section and change it as follows:

    /*
    * The OML dependencies
    */
    dependencies {
        oml project(':SmartSensors')
        //oml project(':SafeAlarms')
        oml "com.example:SafeAlarms:1.0.0"
        oml project(':SupremeMonitors')
    }
    

This pins the version of SafeAlarms that the integrator SecureSystems downloads to exactly 1.0.0. This means any future version of SafeAlarms will not be downloaded. Let’s test this.

  1. Open the file SafeAlarms/src/oml/example.com/safe/alarms.oml, find the concept AlarmSystem and change the I9 restriction to max 2 I9, which is a non-breaking change.

Since it created a non-breaking small change, SafeAlarm decided to change its version to 1.0.1 i.e, create a patch revision.

  1. Open the file SafeAlarms/build.gradle and change the version at the top from 1.0.0 to 1.0.1. Save the editor.

Now we need to publish the new revision to Maven Local again.

  1. In Gradle Tasks view, run homesecurity-models/publishing/publishToMavenLocal and verify that it runs successfully.

Inspect the new version in your Maven Local folder just to double check.

  1. Right click on the project homesecurity-models and select Grade -> Refresh Grade Project.

Since SecureSystems pinned the version of its dependency to 1.0.0 it will not get this revision.

  1. Open the file SecureSystems/build/oml/example.com/safe/alarms.oml and verify that the I9 restriction is still max 1 I9.

This is unfortunate since SecureSystems should really get those patch revisions regularly because they may contain important bug fixes from SafeAlarm. Let’s relax their semantic version dependency to 1.0.+ instead. Note that + is a dynamic version that means the latest. So this will still pin the major.minor versions to 1.0 but will get the latest patch version.

  1. Oen the file SecureSystems/build.gradle and change the version of SafeAlarms to 1.0.+.

  2. Right click on the project homesecurity-models and select Grade -> Refresh Grade Project.

  3. Open the file SecureSystems/build/oml/example.com/safe/alarms.oml again and verify that the I9 restriction is now max 2 I9. Yay!

In fact, SecureSystems may want to pin the version to 1.+ instead, i.e. fix the major revision but adopt the latest minor/patch revisions. This may be reasonable to protect against breaking changes but still allow non-breaking changes to be retrieved. Let’s test that.

  1. Open the file SecureSystems/build.gradle and change the version of SafeAlarms to 1.+.

  2. Open the file SafeAlarms/src/oml/example.com/safe/alarms.oml, find the concept AlarmSystem and change the I9 restriction to min 2 I9, which is a breaking change.

Since it created a breaking change, SafeAlarm changes its version to 2.0.0 i.e, create a major revision.

  1. Open the file SafeAlarms/build.gradle and change the version at the top from 1.0.0 to 2.0.0. Save the editor.

Now we need to publish the new revision to Maven Local again.

  1. In Gradle Tasks view, run homesecurity-models/publishing/publishToMavenLocal and verify that it runs successfully.

  2. Right click on the project homesecurity-models and select Grade -> Refresh Grade Project.

  3. Open the file SecureSystems/build/oml/example.com/safe/alarms.oml and verify that the I9 restriction is still max 2 I9.

It is great that SecureSystems did not get the new 2.0.0 revision of SafeAlarm as this would have broken their architecture possibly at an inconvenient time. Later, when they have more time in their schedule to deal with this major revision, they can explicitly specify that in their dependencies. Let’s test that.

  1. Open the file SecureSystems/build.gradle and change the version of SafeAlarms dependency to 2.+.

  2. Right click on the project homesecurity-models and select Grade -> Refresh Grade Project.

  3. Open the file SecureSystems/build/oml/example.com/safe/alarms.oml and verify that the I9 restriction is now min 2 I9 (the breaking change). Awesome!

In conclusion, the discussion above showed how semantic versions can be a powerful tool to enable more control over change propagation in a federation scenario. This is especially important when the two sub projects are not working closely together. When projects collaborate closely (federation style 1), they can simply use the version-less sub project dependencies, which will always retrieve the latest. If they decide go with any other federation style but still like to synchronize closely, they can specify a semantic version of + which literally means the latest.

6.10. Summary

This tutorial comprehensively introduced and showcased the robust federation support provided by openCAESAR for OML models. It not only introduced the concept but also delved into various federation styles that are supported. Furthermore, it provided a step-by-step walkthrough on configuring a federation of OML models across different projects.

To illustrate the power of this federation support, the tutorial utilized a motivating example, depicting an abstract home security system architecture. This abstraction allows multiple suppliers to contribute to its realization. Subsequently, an integrator (acquirer) assembles this realized architecture from various components and conducts a comprehensive compliance analysis against the original abstract architecture.

Additionally, the tutorial highlighted several common scenarios where federation proves invaluable, showcasing how the openCAESAR platform adeptly caters to these requirements. Notably, it demonstrated the control mechanism for change propagation within a federation using semantic versions. It’s important to note that this control relies on sub-projects within the federation correctly classifying their revisions. This level of precision empowers downstream dependencies to determine when to adopt various types of upstream changes.

With openCAESAR’s robust support for OML federation, modeling becomes a truly scalable enterprise-level endeavor, fostering collaboration and efficiency in complex projects.

Index

Terms defined by this specification