Modeling and Reasoning: OWL and Rel

This how-to guide demonstrates how we can represent Web Ontology Language (OWL) constructs and write SPARQL/RDF queries in Rel.

Download this guide as a RAI notebook by clicking here.

Goal

The goal of this how-to guide is to demonstrate how we can represent Web Ontology Language (OWL) constructs and write SPARQL/RDF queries in Rel. For the purpose of this guide, we will use the Leigh University Benchmark (LUBM) and address in detail OWL constructs like concepts/classes, relationships (properties) along with their hierarchies and equivalent classes.

Preliminaries

This how-to guide assumes that you are familiar with the Rel language. The following guides cover concepts useful to understand this how-to guide:

Introduction

In general, a Knowledge Graph (KG) consists of an ontology (schema) and data. The ontology describes the domain of interest including the different kinds of entities, and how they relate to each other. Reasoning is an integral part of working with an ontology. It is through reasoning the entities, new relationships and concepts are discovered.

LUBM is a KG that consists of a university domain ontology, customizable and repeatable synthetic data, and a set of test queries.

We will show how in Rel we can use relations, entities, and integrity constraints to model the LUBM ontology, data, and queries.

This guide is organized as follows:

  • Data Import: In this first section, we import the synthetic data and the actual answers for the 14 test queries.
  • Conceptual Modeling: This part explains how the LUBM ontology and data are represented in Rel using entities and reasoning.
  • LUBM Queries: This section illustrates the 14 queries in Rel and how they compare to SPARQL.
  • Query Validation: The final section validates the results of the 14 queries.

Before we get to work, let’s briefly go over the LUBM ontology. It has 43 concepts and 25 object properties. For this guide, the reference data pertains to a single university.

  • This university has approximately 15 to 25 departments.
  • Each department has full professors (7 to 10), associate professors (10 to 14) and assistant professors (8 to 11).
  • One of the full professors is the head of the department.
  • Every faculty member (full professor, associate professor, assistant professor) teaches courses (1 to 2).
  • Every department has graduate students (3 to 4 per Faculty member) and undergraduate students (8 to 14 per faculty member). A detailed profile of the data is presented here.

Data Import

The data is loaded as shown below using load_csv and insert. You can find more information on data import in the CSV Import how-to guide. First, we define the CSV data loading configurations, and then we load the data:

update
// data location
def path = "s3://relationalai-documentation-public/lubm/sf0.1/"

def configs = {

(:edges, :path, concat[path, "edges.csv"]);

(:university, :path, concat[path, "University.csv"]);
(:university, :syntax, :header,
{(1, :id); (2, :name); (3, :type); (4, :uri)}
);

(:department, :path, concat[path, "Department.csv"]);
(:department, :syntax, :header,
{(1, :id); (2, :name); (3, :type); (4, :uri)}
);

(:faculty, :path, concat[path, "Faculty.csv"]);
(:faculty, :syntax, :header,
{(1, :id); (2, :name); (3, :email); (4, :type);
(5, :telephone); (6, :uri); (7, :research)}
);

(:researchgroup, :path, concat[path, "ResearchGroup.csv"]);
(:researchgroup, :syntax, :header,
{(1, :id); (2, :type); (3, :uri)}
);

(:publication, :path, concat[path, "Publication.csv"]);
(:publication, :syntax, :header,
{(1, :id); (2, :name); (3, :type); (4, :uri)}
);

(:course, :path, concat[path, "Course.csv"]);
(:course, :syntax, :header,
{(1, :id); (2, :name); (3, :type); (4, :uri)}
);

(:graduate_student, :path, concat[path, "Graduatestudent.csv"]);
(:graduate_student, :syntax, :header,
{(1, :id); (2, :research_assistant); (3, :name);
(4, :email); (5, :type);(6, :telephone); (7, :uri)}
);

(:undergraduate_student, :path, concat[path, "Undergraduatestudent.csv"]);
(:undergraduate_student, :syntax, :header,
{(1, :id); (2, :research_assistant); (3, :name);
(4, :email); (5, :type); (6, :telephone); (7, :uri)}
)
}

def csv[i] = load_csv[configs[i]]
def insert[:lubm_csv] = csv

With the command load_csv[] we load the data. With insert[] we indicate that we want to store them as extensional data base (EDB) relations. The Updating Data: Working with EDB Relations concepts guide explains EDB relations in detail.

Similarly, we define config relations to load the 14 LUBM query answer files below. We also define a relation answer_ref to encapsulate the results of the 14 LUBM queries. Lastly, we use insert to add the load_csv results to this relation.

update
// location of the reference answer
def path_answer = "s3://relationalai-documentation-public/lubm/answers/"

def answer_configs = {

(:q1, :path, concat[path_answer, "answers_query1.txt"]);
(:q1, :syntax, :header, {(1, :student_id)});

(:q2, :path, concat[path_answer, "answers_query2.txt"]);
(:q2, :syntax, :header,
{(1, :student_id); (2, :university_id); (3, :department_id)}
);
(:q2, :syntax, :delim, '\t');

(:q3, :path, concat[path_answer, "answers_query3.txt"]);
(:q3, :syntax, :header, {(1, :publication_id)});

(:q4, :path, concat[path_answer, "answers_query4.txt"]);
(:q4, :syntax, :header,
{(1, :prof_id); (2, :prof_name); (3, :prof_email); (4, :prof_tele)}
);
(:q4, :syntax, :delim, '\t');

(:q5, :path, concat[path_answer, "answers_query5.txt"]);
(:q5, :syntax, :header, {(1, :person_id)});

(:q6, :path, concat[path_answer, "answers_query6.txt"]);
(:q6, :syntax, :header, {(1, :student_id)});

(:q7, :path, concat[path_answer, "answers_query7.txt"]);
(:q7, :syntax, :header, {(1, :student_id); (2, :course_id)});
(:q7, :syntax, :delim, '\t');

(:q8, :path, concat[path_answer, "answers_query8.txt"]);
(:q8, :syntax, :header,
{(1, :student_id); (2, :department_id); (3, :student_email)}
);
(:q8, :syntax, :delim, '\t');

(:q9, :path, concat[path_answer, "answers_query9.txt"]);
(:q9, :syntax, :header,
{(1, :student_id); (2, :faculty_id); (3, :course_id)}
);
(:q9, :syntax, :delim, '\t');

(:q10, :path, concat[path_answer, "answers_query10.txt"]);
(:q10, :syntax, :header, {(1, :student_id)});

(:q11, :path, concat[path_answer, "answers_query11.txt"]);
(:q11, :syntax, :header, {(1, :researchgroup_id)});

(:q12, :path, concat[path_answer, "answers_query12.txt"]);
(:q12, :syntax, :header, {(1, :chair_id);(2, :dept_id)});
(:q12, :syntax, :delim, '\t');

(:q13, :path, concat[path_answer, "answers_query13.txt"]);
(:q13, :syntax, :header, {(1, :person_id)});

(:q14, :path, concat[path_answer, "answers_query14.txt"]);
(:q14, :syntax, :header, {(1, :undergraduate_student_id)});
}

def answers_csv[i] = load_csv[answer_configs[i]]
def insert[:answer_ref] = answers_csv

We have now finished importing the data needed for this guide. Next, we are going to focus on how to properly model the data we have just imported.

Conceptual Modeling

Conceptual modeling is the process of capturing the ontological schema and the data. Let’s find out how we represent the LUBM ontology in Rel. In particular we are going to look into the following aspects:

Creating Entities

Based on the concepts defined in the LUBM ontology and the generated synthetic data, we now define 16 concepts using entities, along with the overarching Thing entity type:

install
entity GraduateStudent
graduate_student_from_id = lubm_csv:graduate_student[:id, _]

entity UndergraduateStudent
undergraduate_student_from_id = lubm_csv:undergraduate_student[:id, _]

entity Course
course_from_id = lubm_csv:course[:id, _]

entity University
university_from_id = lubm_csv:university[:id, _]

entity Department
department_from_id = lubm_csv:department[:id, _]

entity Publication
publication_from_id = lubm_csv:publication[:id, _]

entity Faculty
faculty_from_id = lubm_csv:faculty[:id, _]

entity ResearchGroup
researchgroup_from_id = lubm_csv:researchgroup[:id, _]

As shown above, we defined 8 of the 16 entity types. The constructors (e.g.: department_from_id) take the id from the CSV data (e.g.: lubm_csv:department) and generate entity keys for the corresponding entity type (e.g.: Department). The remaining entity types are defined in the Reasoning section. More details on entities and their construction can be found in the Entities concepts guide.

Below we see the constructor department_from_id mapping the department id to the Department entity keys.

query
department_from_id

Relation: output

"http://www.Department0.University0.edu"RelationalAITypes.HashValue(0x5ff943fe2275c6d5e5b7830cc1ed3d2c)
"http://www.Department1.University0.edu"RelationalAITypes.HashValue(0x457c7f78f5d940ee62c34cb384e79c35)
"http://www.Department10.University0.edu"RelationalAITypes.HashValue(0x3a1c09d6bbc08726290d1ee6c1c1b405)
"http://www.Department11.University0.edu"RelationalAITypes.HashValue(0x5235dccde9cb396c0eaae5434d57ed54)
"http://www.Department12.University0.edu"RelationalAITypes.HashValue(0xbf55af2f178e2fad340d9df04b862df5)
"http://www.Department13.University0.edu"RelationalAITypes.HashValue(0xb96fa173578290f90d3366383a0a296c)
"http://www.Department14.University0.edu"RelationalAITypes.HashValue(0xe73353f9a7ff0bc2cf5c41bf54baf2a8)
"http://www.Department2.University0.edu"RelationalAITypes.HashValue(0x1fb02706afdfd2ef9f1e61b664f83ce8)
"http://www.Department3.University0.edu"RelationalAITypes.HashValue(0x7b3c02ca5045e21f2341d93fe954021d)
"http://www.Department4.University0.edu"RelationalAITypes.HashValue(0x91e4293232e9bd1a3f3a3f6064832834)
"http://www.Department5.University0.edu"RelationalAITypes.HashValue(0xc490d54d87c22b03e755fe24002f5d23)
"http://www.Department6.University0.edu"RelationalAITypes.HashValue(0x3da2c7578fec56588f6d68ce0bd15748)
"http://www.Department7.University0.edu"RelationalAITypes.HashValue(0xbff7f73c33867e6394fc5a4eb361a227)
"http://www.Department8.University0.edu"RelationalAITypes.HashValue(0x08073d1a5bc0b95b6459cf4c9e57712d)
"http://www.Department9.University0.edu"RelationalAITypes.HashValue(0x6e91f83ebd086697a9ad68c63318910f)

The entity key is unique for each entity (instance). This is similar to having an Internationalized Resource Identifier (IRI) assigned to every individual of a concept in OWL.

Assigning Entity Attributes

After we have created the entities, we need to assign the attributes (i.e., Data properties in OWL) to the entities. To do so, we define a relation (e.g.: graduate_student) in the Graph Normal Form that holds all attributes for a given entity type (e.g.: GraduateStudent). In particular, after the assignments below, we will have a relation graduate_student that holds the triplet (e, :email, v) saying graduate student e has the email v.

install
// Helper function to assign entities to attributes
@inline
def assign_entity_attributes[CSV, MAP, column](e, col, v) =
CSV[:id, row].MAP(e) and
CSV(column, row, v) and
col = column
from row

def graduate_student = assign_entity_attributes[
lubm_csv:graduate_student,
graduate_student_from_id,
{:id; :research_assistant; :name; :email; :type; :telephone; :uri}
]

def undergraduate_student = assign_entity_attributes[
lubm_csv:undergraduate_student,
undergraduate_student_from_id,
{:id; :research_assistant; :name; :email; :type; :telephone; :uri}
]

def course = assign_entity_attributes[
lubm_csv:course,
course_from_id,
{:id; :name; :type; :uri}
]

def university = assign_entity_attributes[
lubm_csv:university,
university_from_id,
{:id; :name; :type; :uri}
]

def department = assign_entity_attributes[
lubm_csv:department,
department_from_id,
{:id; :name; :type; :uri}
]

def publication = assign_entity_attributes[
lubm_csv:publication,
publication_from_id,
{:id; :name; :type; :uri}
]

def faculty = assign_entity_attributes[
lubm_csv:faculty,
faculty_from_id,
{:id; :name; :email; :type; :telephone; :uri, :research}
]

def researchgroup = assign_entity_attributes[
lubm_csv:researchgroup,
researchgroup_from_id,
{:id; :type; :uri}
]

First, we defined a helper function assign_entity_attributes that maps each entity to its attribute values. We can read the definition of assign_entity_attributes as follows. The variables:

  • CSV: relation holding the imported CSV data,
  • MAP: entity constructor, and
  • attr: attribute name,

can be understood as “input” variables, and a collection of tuples (e, attr, v) will be returned, mapping the attribute attr of the entity e to its value v.

Again, let’s consider the relation graduate_student. It is defined by using the helper function assign_entity_attributes. The relation lubm_csv:graduate_student holds the imported CSV data, graduate_student_from_id is the constructor for the entity type GraduateStudent, and {:id; :research_assistant; :name; :email; :type; :telephone; :uri} are the attributes we want to assign to graduate students. Below, all attributes of a graduate student with id = "http://www.Department0.University0.edu/GraduateStudent10" are shown:

query
graduate_student[e]
from e
where graduate_student(e, :id, "http://www.Department0.University0.edu/GraduateStudent10")

Relation: output

:email"GraduateStudent10@Department0.University0.edu"
:id"http://www.Department0.University0.edu/GraduateStudent10"
:name"GraduateStudent10"
:research_assistant"true"
:telephone"xxx-xxx-xxxx"
:type"GraduateStudent"
:uri"http://www.Department0.University0.edu/GraduateStudent10"

In a similar fashion, all other entities are defined.

Connecting Entities

OWL uses object properties to link two individuals that belong to two different concepts. In Rel, this translate to defining binary relations that connect the corresponding entities with each other.

First let’s define the overarching entity type Thing. To do this before defining the connections between entities is very useful as we will be able to generalize over all entity types when mapping the :id information from the edges table to the corresponding entity.

Generally, in OWL every individual is of type Thing. Here, initially we define the relation thing followed by the definition of entity Thing. The relation thing is the union of the following relations undergraduate_student, graduate_student, course, university, department, publication, faculty, researchgroup. The ; acts as the union operator (see the Semicolon Section of the Language Reference for details).

Next, the entity Thing is defined using the function first which selects the first argument of the relation thing. In our case the first argument of the thing relation are the entity keys.

install
// In LUBM OWL ontology every concept is subclass of "Thing"
def thing =
course; department; faculty; graduate_student;
publication; researchgroup; undergraduate_student; university

def Thing = first[thing]

A detailed explanation about thing and how to reason about it is given below in Section Class/Entity Hierarchy.

Now we are ready to establish the connections between the entities.

install
/* One relation table for all OWL Object properties */
def edges(predicate, e_source, e_target) =
lubm_csv:edges(:TYPE, pos, predicate) and
lubm_csv:edges(:SOURCE, pos, a) and
lubm_csv:edges(:TARGET, pos, b) and
thing(e_source, :id, a) and
thing(e_target, :id, b)
from a, b, pos

/* Defining each individual relation from edges relation */
def takes_course = edges["takesCourse"]
def member_of = edges["memberOf"]
def sub_organization_of = edges["subOrganizationOf"]
def undergraduate_degree_from = edges["undergraduateDegreeFrom"]
def graduate_degree_from = edges["mastersDegreeFrom"]
def phd_degree_from = edges["doctoralDegreeFrom"]
def publication_author = edges["publicationAuthor"]
def works_for = edges["worksFor"]
def advisor = edges["advisor"]
def head_of = edges["headOf"]
def teacher_of = edges["teacherOf"]

The edges relation is an arity 3 relation that includes all binary edges that connect two entities as specified by edges. Each individual edge is keyed by its name (e.g.: “takesCourse”).

Here we see that using thing becomes quite handy as we can generalize over all entity types and don’t need to explicitly state each individual entity type.

Finally, each individual edge is then defined from the edges relation and brought into the global scope for easier use.

A sample subset is shown below:

query
top[5, takes_course]

Relation: output

1RelationalAITypes.HashValue(0x00090b9e112a88d6fd7f72690017fef5)RelationalAITypes.HashValue(0x0d78faa4466f13c73fd359a151c9bbcf)
2RelationalAITypes.HashValue(0x00090b9e112a88d6fd7f72690017fef5)RelationalAITypes.HashValue(0x240c0d4f71946a9e48e737ffea44665e)
3RelationalAITypes.HashValue(0x00090b9e112a88d6fd7f72690017fef5)RelationalAITypes.HashValue(0xa56481c583c58dec8b49ed6e6394ac71)
4RelationalAITypes.HashValue(0x00090b9e112a88d6fd7f72690017fef5)RelationalAITypes.HashValue(0xdfc59bb6d5ccac20defe928a839231ce)
5RelationalAITypes.HashValue(0x001655e52eb7ffb8cf177c39717fd639)RelationalAITypes.HashValue(0x1e82638e109f58f162c3d530e916ab4c)

Reasoning

In OWL, reasoning is the process of inferring new facts about an individual based on the ontology and the data (explicitly stated facts). One of the main goals of the LUBM benchmark is to test the reasoning capability of the underlying system.

In this section, we show how some of the main reasoning requirements of the LUBM ontology can be realized in Rel. In particular, we discuss:

Class/Entity Hierarchy

As shown earlier in Connecting Entities, Thing is a superclass/super-entity that encapsulates all the individual classes/entities.

In the LUBM ontology, there are many such hierarchies. For example, AssistantProfessor, AssociateProfessor, and FullProfessor are the sub-entities of Faculty, which was directly derived from the CSV data. This hierarchy can be modeled in the following way in Rel:

install
def assistant_professor[x] = faculty[x], faculty(x, :type, "AssistantProfessor")
def associate_professor[x] = faculty[x], faculty(x, :type, "AssociateProfessor")
def full_professor[x] = faculty[x], faculty(x, :type, "FullProfessor")

def AssistantProfessor = first[assistant_professor]
def AssociateProfessor = first[associate_professor]
def FullProfessor = first[full_professor]

The above code block first defines assistant_professor by extracting the faculty members based on the :type “AssistantProfessor”. The relationsassociate_professor and full_professor are similarly defined from the faculty relation. The unary relations AssistantProfessor, AssociateProfessor and FullProfessor are then defined by capturing the entity keys from the aforementioned relations using the function first.

Let’s look at some entity keys of AssistantProfessor

query
last[top[5, AssistantProfessor]]

Relation: output

RelationalAITypes.HashValue(0x0024bb6421c60d615d066e09c0bd99a9)
RelationalAITypes.HashValue(0x01935d77c9674dc12c30cd4b4a57f86a)
RelationalAITypes.HashValue(0x047b88e035f69f37f9a69277a5c7a672)
RelationalAITypes.HashValue(0x06cc47da5de365df3ad67a90111ceb85)
RelationalAITypes.HashValue(0x0a9651f93b05a7d45ab535a8da1156b5)

and their attributes.

query
table[
assistant_professor[e] for e in last[top[5, AssistantProfessor]]
]

Relation: output

RelationalAITypes.HashValue(0x0024bb6421c60d615d066e09c0bd99a9)RelationalAITypes.HashValue(0x01935d77c9674dc12c30cd4b4a57f86a)RelationalAITypes.HashValue(0x047b88e035f69f37f9a69277a5c7a672)RelationalAITypes.HashValue(0x06cc47da5de365df3ad67a90111ceb85)RelationalAITypes.HashValue(0x0a9651f93b05a7d45ab535a8da1156b5)
email"AssistantProfessor1@Department12.University0.edu""AssistantProfessor4@Department10.University0.edu""AssistantProfessor2@Department11.University0.edu""AssistantProfessor0@Department4.University0.edu""AssistantProfessor7@Department10.University0.edu"
id"http://www.Department12.University0.edu/AssistantProfessor1""http://www.Department10.University0.edu/AssistantProfessor4""http://www.Department11.University0.edu/AssistantProfessor2""http://www.Department4.University0.edu/AssistantProfessor0""http://www.Department10.University0.edu/AssistantProfessor7"
name"AssistantProfessor1""AssistantProfessor4""AssistantProfessor2""AssistantProfessor0""AssistantProfessor7"
telephone"xxx-xxx-xxxx""xxx-xxx-xxxx""xxx-xxx-xxxx""xxx-xxx-xxxx""xxx-xxx-xxxx"
type"AssistantProfessor""AssistantProfessor""AssistantProfessor""AssistantProfessor""AssistantProfessor"

Other hierarchies from the LUBM ontology that need to be modeled are:

  • University, Department and ResearchGroup are sub-entities of Organization relation
  • Student, and Faculty are sub-entities of Person,
  • AssistantProfessor, AssociateProfessor, and FullProfessor are sub-entities of Professor.

In Rel, we model these 3 hierarchies as follows:

install
def organization = university; department; researchgroup
def person = graduate_student; undergraduate_student; faculty
def professor = assistant_professor; associate_professor; full_professor

def Organization = first[organization]
def Person = first[person]
def Professor = first[professor]

As we can see, organization is defined as the union of university, department and researchgroup. (Here ; acts as the union operator. See the language reference for more information about ;.) Thereby, the relation organization contains all the universities, departments and research_groups defined earlier. Similarly,person and professor are defined.

Equivalent Class

In OWL, some concepts are defined with necessary and sufficient conditions. These concepts/classes are called equivalent classes. In the LUBM ontology, there are two equivalent classes that are needed for answering the LUBM queries:

  • Every UndergraduateStudent is a Student and also any Person who takes_course is a Student.
  • A Chair is any Person who is the head_of a Department.

In Rel, they can be modeled in the following way:

install
def student[x] = person[x], takes_course(x, _)
def student = undergraduate_student
def chair[x] = person[x], head_of(x, _)

def Student = first[student]
def Chair = first[chair]

Initially, we define a student relation as a person who takes_course (any course). Next, we define that any undergraduate_student is a student. Also, chair is defined as a person who is the head_of the department. Lastly, the corresponding unary entity relations Student and Chair are defined.

Note that the generated data has no explicitly defined Chair instance. However, we can use an integrity constraint to assert and check the equivalent assertion with respect to the Chair entity type, as follows:

install
ic chair_equivalence(e) {
Person(e) and head_of(e, _) iff Chair(e)
}

A sample output of the chair relation with id = http://www.Department13.University0.edu/FullProfessor7 is shown below.

query
chair[e] from e where chair(e, :id, "http://www.Department13.University0.edu/FullProfessor7")

Relation: output

:email"FullProfessor7@Department13.University0.edu"
:id"http://www.Department13.University0.edu/FullProfessor7"
:name"FullProfessor7"
:telephone"xxx-xxx-xxxx"
:type"FullProfessor"

Edge Hierarchy

Similarly to entities (classes in OWL), edge relations can also have a hierarchy. The LUBM ontology defines one of the super-properties as:

  • degree_from is a super property of undergraduate_degree_from, under_graduate_degree_from, phd_degree_from

In Rel this can be modeled as:

install
def degree_from = undergraduate_degree_from; graduate_degree_from; phd_degree_from

The degree_from relation captures all the entities that are linked with undergraduate_degree_from, graduate_degree_from, and phd_degree_from by performing an union over these individual edge relations.

Moreover, in the LUBM ontology some of the sub-properties are defined as follows:

  • head_of is a subproperty of works_for
  • works_for is a subproperty of member_of

In Rel these sub-properties can be modeled as:

install
def works_for = head_of
def member_of = works_for

The definition above states that the entities related with head_of are also related with works_for. Note, however, it doesn’t mean the reverse. For instance, a person can work for an institution and not be the head of it. Likewise, the member_of relation is defined.

Transitive and Inverse Properties

In the LUBM ontology, sub_organization_of is a transitive property; has_alumnus is a inverse property of degree_from, which is a superset of undergraduate_degree_from, graduate_degree_from, and phd_degree_from, as discussed above in the Edge Hierarchy section. In Rel these properties can be modeled as shown below.

install
def sub_organization_of = sub_organization_of.sub_organization_of
def has_alumnus = transpose[degree_from]

As demonstrated above, the relation sub_organization_of initially connected only Department with Universityentities and ResearchGroup with Department entities. After the recursive definition, the relation sub_organization_of will also connect directly Researchgroup with University entities.

The has_alumnus relation captures the entities in the inverse direction of the degree_from relation.

Data Consistency Verification

The following integrity constraints are used to verify the facts associated and derived from the data and the LUBM ontology.

install
ic degree_from_type(p, u) {
degree_from(p, u) implies Person(p) and University(u)
}

In the above code block degree_from_type(p, u) verifies that degree_from is a binary relation that connects Person with University entities.

install
ic degree_from_inverse(p, u) {
degree_from(p, u) iff has_alumnus(u, p)
}

In the second integrity constraint shown above degree_from_inverse(p, u) verifies that degree_from and has_alumnus relations are inversely related using the iff function.

The third integrity constraint head_works_for checks that head_of is a relation that holds two entities that are also present in the works_for relation.

install
ic head_works_for {
head_of works_for
}

Thus affirming that head_of is a proper subset of works_for relation.

LUBM Queries

In the following, we show how the 14 LUBM benchmark queries can be written and verified in Rel. At the beginning of each query we quote their original explanation/purpose. More information about the queries can be found here.

Additionally, we also show and compare how these queries are written in SPARQL and Rel. Here, we focus on demonstrating how similar the SPARQL queries can be written in Rel. In fact, we will see that SPARQL queries can basically be translated line-by-line into Rel.

As stated earlier in Assigning Entity Attributes, it is important to remember that the entity key for each entity (instance) is unique within the database. This is similar to having an Internationalized Resource Identifier (IRI) assigned to every individual in OWL.

Queries 1, 4, and 6 are discussed in more detail as these three queries cover all the key aspects. Moreover, the logic behind the remaining queries can be easily understood from these examples.

Query 1

This query bears large input and high selectivity. It queries about just one class and one property and does not assume any hierarchy information or inference.

Query 1 retrieves the list of GraduateStudents who takes the course (takesCourse) with the ID “http://www.Department0.University0.edu/GraduateCourse0”.

Query 1

SPARQL:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX ub: <http://www.lehigh.edu/~zhp2/2004/0401/univ-bench.owl#>
SELECT ?X
WHERE
{?X rdf:type ub:GraduateStudent .
?X ub:takesCourse
http://www.Department0.University0.edu/GraduateCourse0}

In Rel the above query is written as follows:

install
def answer[:q1] = x :
graduate_student(e_x, :id, x)
and course(e_y, :id, "http://www.Department0.University0.edu/GraduateCourse0")
and takes_course(e_x, e_y)
from e_x, e_y

Note that we return the identifying :id attribute of the graduate student and not its entity key e, which is used within Rel to identify the student. This allows us later to directly compare our results with the reference answers.

Another interesting point to mention is that we use the relation answer to collect the answers of all LUBM queries. To know which answers belong to which query, we key the answers by their query id.

query
answer[:q1]

Relation: output

"http://www.Department0.University0.edu/GraduateStudent101"
"http://www.Department0.University0.edu/GraduateStudent124"
"http://www.Department0.University0.edu/GraduateStudent142"
"http://www.Department0.University0.edu/GraduateStudent44"

Query 2

This query increases in complexity: 3 classes and 3 properties are involved. Additionally, there is a triangular pattern of relationships between the objects involved.

Query 2 retrieves the list of GraduateStudents, Universities and Departments, such that the GraduateStudent is a member of a Department, where this Department is a part of the University from which the GraduateStudent obtained his UnderGraduate degree.

Query 2

SPARQL:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX ub: <http://www.lehigh.edu/~zhp2/2004/0401/univ-bench.owl#>
SELECT ?X, ?Y, ?Z
WHERE
{?X rdf:type ub:GraduateStudent .
?Y rdf:type ub:University .
?Z rdf:type ub:Department .
?X ub:memberOf ?Z .
?Z ub:subOrganizationOf ?Y .
?X ub:undergraduateDegreeFrom ?Y}

In Rel the above query is written as follows:

install
def answer[:q2](x, y, z) {
graduate_student(e_x, :id, x)
and university(e_y, :id, y)
and department(e_z, :id, z)
and member_of(e_x, e_z)
and sub_organization_of(e_z, e_y)
and undergraduate_degree_from(e_x, e_y)
from e_x, e_y, e_z
}

The output of the query 2 is shown below:

query
answer[:q2]

Relation: output

Query 3

This query is similar to Query 1 but class Publication has a wide hierarchy.

Query 3 retrieves the list of Publications, where the PublicationAuthor is “http://www.Department0.University0.edu/AssistantProfessor0”.

Query 3

SPARQL:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX ub: <http://www.lehigh.edu/~zhp2/2004/0401/univ-bench.owl#>
SELECT ?X
WHERE
{?X rdf:type ub:Publication .
?X ub:publicationAuthor
    http://www.Department0.University0.edu/AssistantProfessor0}

In Rel the above query is written as follows:

install
def answer[:q3](x) {
publication(e_x, :id, x)
and faculty(e_y, :id, "http://www.Department0.University0.edu/AssistantProfessor0")
and publication_author(e_x, e_y)
from e_x, e_y
}

The output of the query 3 is shown below:

query
answer[:q3]

Relation: output

"http://www.Department0.University0.edu/AssistantProfessor0/Publication0"
"http://www.Department0.University0.edu/AssistantProfessor0/Publication1"
"http://www.Department0.University0.edu/AssistantProfessor0/Publication2"
"http://www.Department0.University0.edu/AssistantProfessor0/Publication3"
"http://www.Department0.University0.edu/AssistantProfessor0/Publication4"
"http://www.Department0.University0.edu/AssistantProfessor0/Publication5"

Query 4

This query has small input and high selectivity. It assumes subClassOf relationship between Professor and its subclasses. Class Professor has a wide hierarchy. Another feature is that it queries about multiple properties of a single class.

Query 4 retrieves the Professor id, name, emailaddress and telephone who works for department “http://www.Department0.University0.edu”.

Query 4

SPARQL:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX ub: <http://www.lehigh.edu/~zhp2/2004/0401/univ-bench.owl#>
SELECT ?X, ?Y1, ?Y2, ?Y3
WHERE
{?X rdf:type ub:Professor .
?X ub:worksFor <http://www.Department0.University0.edu> .
?X ub:name ?Y1 .
?X ub:emailAddress ?Y2 .
?X ub:telephone ?Y3}

In Rel the above query is written as follows:

install
def answer[:q4](x, y1, y2, y3) {
professor(e_x, :id, x)
and professor(e_x, :name, y1)
and professor(e_x, :email, y2)
and professor(e_x, :telephone, y3)
and department(e_z, :id, "http://www.Department0.University0.edu")
and works_for(e_x, e_z)
from e_x, e_z
}

All requested attributes associated with the Professor entity is accessed via the attribute relation professor, which contains all attributes that relate to any professor. The query result is again captured in the answer[:q4] relation. A sample output of query 4 is shown below:

query
top[5, answer[:q4]]

Relation: output

1"http://www.Department0.University0.edu/AssistantProfessor0""AssistantProfessor0""AssistantProfessor0@Department0.University0.edu""xxx-xxx-xxxx"
2"http://www.Department0.University0.edu/AssistantProfessor1""AssistantProfessor1""AssistantProfessor1@Department0.University0.edu""xxx-xxx-xxxx"
3"http://www.Department0.University0.edu/AssistantProfessor2""AssistantProfessor2""AssistantProfessor2@Department0.University0.edu""xxx-xxx-xxxx"
4"http://www.Department0.University0.edu/AssistantProfessor3""AssistantProfessor3""AssistantProfessor3@Department0.University0.edu""xxx-xxx-xxxx"
5"http://www.Department0.University0.edu/AssistantProfessor4""AssistantProfessor4""AssistantProfessor4@Department0.University0.edu""xxx-xxx-xxxx"

Query 5

This query assumes subClassOf relationship between Person and its subclasses and subPropertyOf relationship between memberOf and its subproperties. Moreover, class Person features a deep and wide hierarchy.

Query 5 retrieves the list of Persons who are members of the department “http://www.Department0.University0.edu”.

Query 5

SPARQL:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX ub: <http://www.lehigh.edu/~zhp2/2004/0401/univ-bench.owl#>
SELECT ?X
WHERE
{?X rdf:type ub:Person .
?X ub:memberOf <http://www.Department0.University0.edu>}

In Rel the above query is written as follows:

install
def answer[:q5](x) {
person(e_x, :id, x)
and department(e_y, :id, "http://www.Department0.University0.edu")
and member_of(e_x, e_y)
from e_x, e_y
}

A sample output of query 5 is shown below:

query
top[5, answer[:q5]]

Relation: output

1"http://www.Department0.University0.edu/AssistantProfessor0"
2"http://www.Department0.University0.edu/AssistantProfessor1"
3"http://www.Department0.University0.edu/AssistantProfessor2"
4"http://www.Department0.University0.edu/AssistantProfessor3"
5"http://www.Department0.University0.edu/AssistantProfessor4"

Query 6

This query queries about only one class. But it assumes both the explicit subClassOf relationship between UndergraduateStudent and Student and the implicit one between GraduateStudent and Student. In addition, it has large input and low selectivity.

Query 6 retrieves all the individuals (instances) that are of type Student.

Query 6

SPARQL:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX ub: <http://www.lehigh.edu/~zhp2/2004/0401/univ-bench.owl#>
SELECT ?X WHERE {?X rdf:type ub:Student}

In Rel the above query is written as follows:

install
def answer[:q6](x) {
student(_, :id, x)
}

This query uses _ (underscore) as an anonymous existential quantified variable, which avoids giving the variable a name and keeping the query compact. See the language reference for more information. A sample output of query 6 is shown below:

query
top[5,  answer[:q6]]

Relation: output

1"http://www.Department0.University0.edu/GraduateStudent0"
2"http://www.Department0.University0.edu/GraduateStudent1"
3"http://www.Department0.University0.edu/GraduateStudent10"
4"http://www.Department0.University0.edu/GraduateStudent100"
5"http://www.Department0.University0.edu/GraduateStudent101"

Query 7

This query is similar to Query 6 in terms of class Student but it increases in the number of classes and properties and its selectivity is high.

Query 7 retrieves the list of Students and Courses such that these Students take the courses and that these courses are taught by faculty “http://www.Department0.University0.edu/AssociateProfessor0”.

Query 7

SPARQL:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX ub: <http://www.lehigh.edu/~zhp2/2004/0401/univ-bench.owl#>
SELECT ?X, ?Y
WHERE
{?X rdf:type ub:Student .
?Y rdf:type ub:Course .
?X ub:takesCourse ?Y .
<http://www.Department0.University0.edu/AssociateProfessor0>,
    ub:teacherOf, ?Y}

In Rel the above query is written as follows:

install
def answer[:q7](x, y) {
student(e_x, :id, x)
and course(e_y, :id, y)
and takes_course(e_x, e_y)
and teacher_of(e_z, e_y)
and faculty(e_z, :id, "http://www.Department0.University0.edu/AssociateProfessor0")
from e_x, e_y, e_z
}

A sample output of query 7 is shown below:

query
top[5, answer[:q7]]

Relation: output

1"http://www.Department0.University0.edu/GraduateStudent106""http://www.Department0.University0.edu/GraduateCourse17"
2"http://www.Department0.University0.edu/GraduateStudent111""http://www.Department0.University0.edu/GraduateCourse18"
3"http://www.Department0.University0.edu/GraduateStudent134""http://www.Department0.University0.edu/GraduateCourse18"
4"http://www.Department0.University0.edu/GraduateStudent29""http://www.Department0.University0.edu/GraduateCourse17"
5"http://www.Department0.University0.edu/GraduateStudent30""http://www.Department0.University0.edu/GraduateCourse18"

Query 8

This query is further more complex than Query 7 by including one more property.

Query 8 retrieves the list of students, departments and email addresses of the students such that the student is a member of a department and the department is a sub-organization of university “http://www.University0.edu”.

Query 8

SPARQL:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX ub: <http://www.lehigh.edu/~zhp2/2004/0401/univ-bench.owl#>
SELECT ?X, ?Y, ?Z
WHERE
{?X rdf:type ub:Student .
?Y rdf:type ub:Department .
?X ub:memberOf ?Y .
?Y ub:subOrganizationOf <http://www.University0.edu> .
?X ub:emailAddress ?Z}

In Rel the above query is written as follows:

install
def answer[:q8](x, y, z) {
student(e_x, :id, x)
and department(e_y, :id, y)
and member_of(e_x, e_y)
and sub_organization_of(e_y, e_o)
and organization(e_o, :id, "http://www.University0.edu")
and student(e_x, :email, z)
from e_x, e_y, e_o
}

A sample output of query 8 is shown below:

query
top[5, answer[:q8]]

Relation: output

1"http://www.Department0.University0.edu/GraduateStudent0""http://www.Department0.University0.edu""GraduateStudent0@Department0.University0.edu"
2"http://www.Department0.University0.edu/GraduateStudent1""http://www.Department0.University0.edu""GraduateStudent1@Department0.University0.edu"
3"http://www.Department0.University0.edu/GraduateStudent10""http://www.Department0.University0.edu""GraduateStudent10@Department0.University0.edu"
4"http://www.Department0.University0.edu/GraduateStudent100""http://www.Department0.University0.edu""GraduateStudent100@Department0.University0.edu"
5"http://www.Department0.University0.edu/GraduateStudent101""http://www.Department0.University0.edu""GraduateStudent101@Department0.University0.edu"

Query 9

Besides the aforementioned features of class Student and the wide hierarchy of class Faculty, like Query 2, this query is characterized by the most classes and properties in the query set and there is a triangular pattern of relationships.

Query 9 retrieves the list of Students (?x), Faculty,’s (?y) and Courses (?z) such that these students (?x) takes the courses (?z) thought by their advisors (?y).

Query 9

SPARQL:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX ub: <http://www.lehigh.edu/~zhp2/2004/0401/univ-bench.owl#>
SELECT ?X, ?Y, ?Z
WHERE
{?X rdf:type ub:Student .
?Y rdf:type ub:Faculty .
?Z rdf:type ub:Course .
?X ub:advisor ?Y .
?Y ub:teacherOf ?Z .
?X ub:takesCourse ?Z}

In Rel the above query is written as follows:

install
def answer[:q9](x, y, z) {
student(e_x, :id, x)
and faculty(e_y, :id, y)
and course(e_z, :id, z)
and advisor(e_x, e_y)
and teacher_of(e_y, e_z)
and takes_course(e_x, e_z)
from e_x, e_y, e_z
}

A sample output of query 9 is shown below:

query
top[5, answer[:q9]]

Relation: output

1"http://www.Department0.University0.edu/GraduateStudent112""http://www.Department0.University0.edu/AssociateProfessor9""http://www.Department0.University0.edu/GraduateCourse31"
2"http://www.Department0.University0.edu/GraduateStudent122""http://www.Department0.University0.edu/FullProfessor2""http://www.Department0.University0.edu/GraduateCourse3"
3"http://www.Department0.University0.edu/GraduateStudent126""http://www.Department0.University0.edu/FullProfessor8""http://www.Department0.University0.edu/GraduateCourse14"
4"http://www.Department0.University0.edu/GraduateStudent143""http://www.Department0.University0.edu/AssistantProfessor8""http://www.Department0.University0.edu/GraduateCourse53"
5"http://www.Department0.University0.edu/GraduateStudent29""http://www.Department0.University0.edu/AssociateProfessor1""http://www.Department0.University0.edu/GraduateCourse19"

Query 10

This query differs from Query 6, 7, 8 and 9 in that it only requires the (implicit) subClassOf relationship between GraduateStudent and Student, i.e., subClassOf relationship between UndergraduateStudent and Student does not add to the results.

Query 10 retrieves the list of the students who takesCourse course “http://www.Department0.University0.edu/GraduateCourse0”.

Query 10

SPARQL:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX ub: <http://www.lehigh.edu/~zhp2/2004/0401/univ-bench.owl#>
SELECT ?X
WHERE
{?X rdf:type ub:Student .
?X ub:takesCourse
<http://www.Department0.University0.edu/GraduateCourse0>}
    ?X ub:takesCourse ?Z}

In Rel the above query is written as follows:

install
def answer[:q10](x) {
student(e_x, :id, x)
and course(e_y, :id, "http://www.Department0.University0.edu/GraduateCourse0")
and takes_course(e_x, e_y)
from e_x, e_y
}

A sample output of query 10 is shown below:

query
answer[:q10]

Relation: output

"http://www.Department0.University0.edu/GraduateStudent101"
"http://www.Department0.University0.edu/GraduateStudent124"
"http://www.Department0.University0.edu/GraduateStudent142"
"http://www.Department0.University0.edu/GraduateStudent44"

Query 11

Query 11, 12 and 13 are intended to verify the presence of certain OWL reasoning capabilities in the system. In this query, property subOrganizationOf is defined as transitive. Since in the benchmark data, instances of ResearchGroup are stated as a sub-organization of a Department individual and the later suborganization of a University individual, inference about the subOrgnizationOf relationship between instances of ResearchGroup and University is required to answer this query. Additionally, its input is small.

Query 11

Query 11 retrieves the list of ResearchGroups that are subOrganizationOf university “http://www.University0.edu”.

SPARQL:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX ub: <http://www.lehigh.edu/~zhp2/2004/0401/univ-bench.owl#>
SELECT ?X
WHERE
{?X rdf:type ub:ResearchGroup .
?X ub:subOrganizationOf <http://www.University0.edu>}

In Rel the above query is written as follows:

install
def answer[:q11](x) {
researchgroup(e_x, :id, x)
and university(e_y, :id, "http://www.University0.edu")
and sub_organization_of(e_x, e_y)
from e_x, e_y
}

A sample output of query 11 is shown below:

query
top[5, answer[:q11]]

Relation: output

1"http://www.Department0.University0.edu/ResearchGroup0"
2"http://www.Department0.University0.edu/ResearchGroup1"
3"http://www.Department0.University0.edu/ResearchGroup2"
4"http://www.Department0.University0.edu/ResearchGroup3"
5"http://www.Department0.University0.edu/ResearchGroup4"

Query 12

The benchmark data do not produce any instances of class Chair. Instead, each Department individual is linked to the chair professor of that department by property headOf. Hence this query requires realization, i.e., inference that that professor is an instance of class Chair because he or she is the head of a department. Input of this query is small as well.

Query 12 retrieves the list of Chairs(?X) and their Departments(?Y) such that the Chair worksfor the Department and the Department is a sub-organizationOf university “http://www.University0.edu”.

Query 12

SPARQL:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX ub: <http://www.lehigh.edu/~zhp2/2004/0401/univ-bench.owl#>
SELECT ?X, ?Y
WHERE
{?X rdf:type ub:Chair .
?Y rdf:type ub:Department .
?X ub:worksFor ?Y .
?Y ub:subOrganizationOf <http://www.University0.edu>}

In Rel the above query is written as follows:

install
def answer[:q12](x, y) {
chair(e_x, :id, x)
and department(e_y, :id, y)
and university(e_z, :id, "http://www.University0.edu")
and works_for(e_x, e_y)
and sub_organization_of(e_y, e_z)
from e_x, e_y, e_z
}

A sample output of query 12 is shown below:

query
top[5, answer[:q12]]

Relation: output

1"http://www.Department0.University0.edu/FullProfessor7""http://www.Department0.University0.edu"
2"http://www.Department1.University0.edu/FullProfessor4""http://www.Department1.University0.edu"
3"http://www.Department10.University0.edu/FullProfessor5""http://www.Department10.University0.edu"
4"http://www.Department11.University0.edu/FullProfessor1""http://www.Department11.University0.edu"
5"http://www.Department12.University0.edu/FullProfessor2""http://www.Department12.University0.edu"

Query 13

Property hasAlumnus is defined in the benchmark ontology as the inverse of property degreeFrom, which has three subproperties: undergraduateDegreeFrom, mastersDegreeFrom, and doctoralDegreeFrom. The benchmark data state a person as an alumnus of a university using one of these three subproperties instead of hasAlumnus. Therefore, this query assumes subPropertyOf relationships between degreeFrom and its subproperties, and also requires inference about inverseOf.

Query 13 retrieves the list of people (Person) who are alumni (hasAlumnus) of university “http://www.University0.edu”.

Query 13

SPARQL:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX ub: <http://www.lehigh.edu/~zhp2/2004/0401/univ-bench.owl#>
SELECT ?X
WHERE
{?X rdf:type ub:Person .
<http://www.University0.edu> ub:hasAlumnus ?X}

In Rel the above query is written as follows:

install
def answer[:q13](x) {
person(e_x, :id, x)
and university(e_y, :id, "http://www.University0.edu")
and has_alumnus(e_y, e_x)
from e_x, e_y
}

The output of query 13 is shown below:

query
answer[:q13]

Relation: output

"http://www.Department0.University0.edu/AssistantProfessor2"

Query 14

This query is the simplest in the test set. This query represents those with large input and low selectivity and does not assume any hierarchy information or inference.

Query 14 retrieves the list of all the UnderGraduateStudents.

Query 14

SPARQL:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX ub: <http://www.lehigh.edu/~zhp2/2004/0401/univ-bench.owl#>
SELECT ?X
WHERE {?X rdf:type ub:UndergraduateStudent}

In Rel the above query is written as follows:

install
def answer[:q14](x) {
undergraduate_student(_, :id, x)
}

A sample output of query 14 is shown below:

query
top[5, answer[:q14]]

Relation: output

1"http://www.Department0.University0.edu/UndergraduateStudent0"
2"http://www.Department0.University0.edu/UndergraduateStudent1"
3"http://www.Department0.University0.edu/UndergraduateStudent10"
4"http://www.Department0.University0.edu/UndergraduateStudent100"
5"http://www.Department0.University0.edu/UndergraduateStudent101"

Query Validation

Lastly, we validate the correctness of our LUBM queries using integrity constraints (see the Integrity Constraints concepts guide for details).

We perform the validation in 3 steps:

  1. Convert the answers in answer to Graph Normal Form (GNF),
  2. Confirm that all found answers are correct, and
  3. Confirm that all answers have been found.

Let’s start with the GNF conversion.

install
def answer_column_order[:q1] = {(:student_id, 1)}
def answer_column_order[:q2] = {(:student_id, 1); (:university_id, 2); (:department_id, 3)}
def answer_column_order[:q3] = {(:publication_id, 1)}
def answer_column_order[:q4] =
{(:prof_id, 1); (:prof_name, 2); (:prof_email, 3); (:prof_tele, 4)}
def answer_column_order[:q5] = {(:person_id, 1)}
def answer_column_order[:q6] = {(:student_id, 1)}
def answer_column_order[:q7] = {(:student_id, 1); (:course_id, 2)}
def answer_column_order[:q8] = {(:student_id, 1); (:department_id, 2); (:student_email, 3)}
def answer_column_order[:q9] = {(:student_id, 1); (:faculty_id, 2); (:course_id, 3)}
def answer_column_order[:q10] = {(:student_id, 1)}
def answer_column_order[:q11] = {(:researchgroup_id, 1)}
def answer_column_order[:q12] = {(:chair_id, 1); (:dept_id, 2)}
def answer_column_order[:q13] = {(:person_id, 1)}
def answer_column_order[:q14] = {(:undergraduate_student_id, 1)}

def answer_gnf[q](row, column, value) =
answer_column_order[q].(
pivot[x... : hash[answer[q]](x..., row)]
)(column, value)

To achieve this first we define a relation answer_column_order that captures the column names of all the queries. Next, answer_gnf is defined that converts all the query results into the Graph Normal Form.

The relation answer_gnf uses functions like hash and pivot to generate unique hash values for the answers and convert high arity relations to high cardinality relations. These functions are defined in the standard library reference.

Below the validation of the results of query 4 is shown. The other query results can also be validated in the similar manner.

The first integrity constraint, all_answers_correct, checks that all found answers are present in the reference answers. The second integrity constraint, all_answers_found, checks that all reference answers have been indeed found. The details of equal can be found in the standard library.

query
ic all_answers_correct(q, i, col) {
answer_gnf(q, i, col, _) and
q = :q4
implies
exists(j in first[answer_ref[q, col]]:
equal(answer_gnf[q, i, col], answer_ref[q, col, j])
)
}


ic all_answers_found(q, i, col) {
i = first[answer_ref[q, col]] and
q = :q4
implies
exists(j in first[answer_gnf[q]]:
equal(answer_gnf[q, j, col], answer_ref[q, col, i])
)
}

Note that if we removed the condition q = :q4 from both integrity constraints, the correctness of all queries (:q1 to :q14) would be checked together in one go.

Conclusion

We demonstrated that Rel is a powerful modeling language when it comes to modeling data and expressing complex ontologies and its data. In particular, we have shown how naturally the OWL ontology of the LUBM benchmark can be modeled in Rel. Also SPARQL queries can be translated with great ease into Rel making the transition from RDF/OWL/SPARQL to Rel quite easy.

Furthermore, Rel offers modeling capabilities that support much more complex modeling and reasoning concepts that go well beyond the scope of this how-to guide and what was needed to model the LUBM ontology.