Henriette's Notes

Creating Custom Rule Primitives for Jena

In this post I will show you

  1. how to add your own custom rule primitive,
  2. how to inform Jena of your custom rule primitive, and
  3. I will discuss things you have to keep in mind when writing a custom
    primitive.

Adding a Custom Rule Primitive

A powerful feature of Jena is that it allows you to create your own custom builtin rule primitives. Building on our student example of the previous post, assume we want to calculate the final mark for a student given their test result, exam result and project result. We assume we have the following data

:Peet :hasTestResult 77 .
:Peet :hasExamResult 86 .
:Peet :hasProjectResult 91 .

for which we add the following rule

[calcStudentFinalMarkRule: 
  (?student :hasTestResult ?testResult) 
  (?student :hasExamResult ?examResult) 
  (?student :hasProjectResult ?projectResult) 
  calcFinalMark(?testResult, ?examResult, 
   ?projectResult, ?finalMark)
      -> (?student :hasFinalMark ?finalMark)]

The meat of the implementation is the doUserRequiredAction(args, length,
context)
method, which consists of the following steps:

  1. check that we have the correct number of parameters,
  2. retrieve the input parameters,
  3. verify the typing of input parameters,
  4. doing the actual calculation,
  5. creating a node for the output parameter, and
  6. binding the node to the output parameter.
private boolean doUserRequiredAction(Node[] args, 
  int length, RuleContext context) {
  
  // Check we received the correct number of parameters
  checkArgs(length, context);

  boolean success = false;
  
  // Retrieve the input arguments
  Node studentTestResult = getArg(0, args, context);
  Node studentExamResult = getArg(1, args, context);
  Node studentProjectResult = getArg(2, args, context);

  // Verify the typing of the parameters
  if (studentTestResult.isLiteral() && 
    studentExamResult.isLiteral() && 
    studentProjectResult.isLiteral()) {
    Node finalMark = null;
    if (studentTestResult.getLiteralValue() 
        instanceof Number && 
      studentExamResult.getLiteralValue() 
        instanceof Number &&
      studentProjectResult.getIndexingValue() 
        instanceof Number) {
    
      Number nvStudentTestResult = 
        (Number)studentTestResult.getLiteralValue();
      Number nvStudentExamResult = 
        (Number)studentExamResult.getLiteralValue();
      Number nvStudentProjectResult = 
        (Number)studentProjectResult.getLiteralValue();
    
      // Doing the calculation
      int nFinalMark = 
        (nvStudentTestResult.intValue() * 20)/100 + 
        (nvStudentExamResult.intValue() * 50)/100 +
        (nvStudentProjectResult.intValue() * 30)/100;
      
      // Creating a node for the output parameter
      finalMark = Util.makeIntNode(nFinalMark);  
      
      // Binding the output parameter to the node
      BindingEnvironment env = context.getEnv();    
      success = env.bind(args[3], finalMark);
    } 
  }   
  return success;
}

Registering a Custom Primitive with Jena

Our code for load our rules and activating it is similar to my previous post, except that you have to make a call to register the custom primitive:

// Load RDF data
String data = path.toFile().getAbsolutePath() + 
  "/src/main/resources/data2.ttl";
Model model = ModelFactory.createDefaultModel();
model.read(data);
      
// Register custom primitive
BuiltinRegistry.theRegistry.register(new CalcFinalMark());
      
// Load rules
String rules = path.toFile().getAbsolutePath() + 
  "/src/main/resources/student2.rules";
Reasoner reasoner = new GenericRuleReasoner(Rule.rulesFromURL(rules));
     
InfModel infModel = ModelFactory.createInfModel(reasoner, model);  
infModel.rebind();   

Things to Keep in Mind

There are two main things I think one needs to keep in mind with Jena custom rule primitives:

  1. A primitive is suppose to be a elementary building block. Being able to create your own primitives may tempt you to add all sorts of interesting processing besides the manipulation of triples, but I strongly advice against that. Arbitrary processing in your builtin primitive can degrade performance of inferencing.
  2. Do not assume that you have control over when a rule will be triggered. Exactly when a rule will be triggered is dependent on when the Jena InfModel implementation decides to re-evaluate the rules, which is dependent on internal caching of the InfModel implementation and how it deals with modifications made to the model. Even though I believe InfModel implementations will avoid arbitrarily re-evaluating rules, I still think it is conceivable that under some circumstance the same rule may be triggered more than once for the same data. Furthermore, the Jena documentation of 3.6.0 states that the InfModel interface is still in flux, which could mean that even if a rule is only triggered once for given data currently, due to unforeseen changes it may be triggered more than once in future updates of Jena.

Conclusion

In this post I gave an example of how you can develop a custom rule primitive for Jena. The code for this example can be found at github.

Creating Custom Rules for Jena

In this post I will show you

  1. how to add your own custom rules to Jena,
  2. how to use rule primitives, and
  3. I will mention some things you may want to keep in mind when using rules.

Add and Activate Rules

We assume we start with the following simple triples:

:Peet :takesCourse :ComputerScience .
:Ruth :teachesCourse :ComputerScience .

for which we add the following rule to a student1.rules file:

[hasStudentRule: 
  (?student :takesCourse ?course) 
  (?lecturer :teachesCourse ?course) 
    -> (?lecturer :hasStudent ?student)] 

The hasStudentRule says that if a student takes a course and that course is presented by some lecturer, then the student is a student of that lecturer. The (?student :takesCourse ?course) (?lecturer :teachesCourse ?course) part of the rule is referred to as the premise or body of the rule and the (?lecturer :hasStudent ?student) part as the conclusion or head of the rule.

To activate the rules in Jena we have to create a model that is capable of doing inferences. In the context of Jena inferencing means that it will re-evaluate the rules against the data, which will cause additional statements to be potentially added to the model. This can be achieved by calling methods that will cause Jena to re-evaluate the model, i.e. calls like InfModel.rebind() and InfModel.validate().

Path path = Paths.get(".").toAbsolutePath().normalize();

// Load RDF data
String data = path.toFile().getAbsolutePath() + 
  "/src/main/resources/data.ttl";
Model model = ModelFactory.createDefaultModel();
model.read(data);
    
// Load rules
String rules = path.toFile().getAbsolutePath() + 
  "/src/main/resources/student.rules";
Reasoner reasoner = new GenericRuleReasoner(Rule.rulesFromURL(rules));

InfModel infModel = ModelFactory.createInfModel(reasoner, model);   
infModel.rebind();

When the hasStudentRule is activated a new statement will be added to the Jena model:

:Ruth :hasStudent :Peet .

Using Rule Primitives

Jena supports a number of builtin rule primitives that are intuitive to understand, i.e.

[pensionerRule: 
  (?person :hasAge ?age) greaterThan(?age, 60) 
    -> (?person a :Pensioner)]

which states that when ?person has an age greater than 60, ?person is considered to be a pensioner. Assuming we have the data

:Peet :hasAge 90 .

the following triple will be added to the Jena model:

:Peet a :Pensioner .

Things to Keep in Mind

There are two main things I think one needs to keep in mind with Jena rules:

  1. The purpose of rules are to manipulate the triples in the Jena model. For the most part it adds triples to the model, but it can also remove triples from the model if primitives like remove and drop are used.
  2. Adding and removing of triples can be achieved through SPARQL queries which can perform better or worse than rules. It is therefore best to check both approaches for your given use case.

Conclusion

In this post I gave a brief introduction in how to use custom rules and builtin rule primitives in Jena. This code is available at github.

Object Oriented Features that OWL lacks

At some level OWL seem to be very similar to UML, but there are important differences that you have to keep in mind. In this post I detail features you will have to do without coming from a coding or object orientation perspective when you start using OWL. These are:

  1. classes do not have attributes,
  2. classes do have class variables (but not in the way you think),
  3. you have to live without methods,
  4. there are no interfaces, and
  5. neither are there any abstract classes.

Classes do not have Attributes

Programmers are used to classes that have attributes or properties. This is also referred to as instance/member variables of the class. In OWL classes do not have properties. Rather, properties can be used to model relations that exist between classes and data types. You can however state that a class is a subclass of something that has some property. I.e.,

Class: Course
  SubClassOf: 
    hasSubject some Subject 

which states that a Course is a subclass of things that have some
Subject. I explain this here and here.

Classes do have Class Variables (but not in the way you think)

In programming class variables are values that describe the class rather than instances of the class. In OWL this is achieved trough annotations. There exist pre-defined annotation properties and you can define your own annotation properties. Interestingly annotation properties can be specified for any class, individual or axiom, as well as the ontology itself. Annotations are purely used to specify additional information and no reasoning is done over them.

There are no Methods nor Interfaces

This may seem like silly a comment, but is worth making it explicit: “OWL does not have methods”. Why not? Well, OWL is not a programming language nor a modelling language for designing software. It is a conceptual modelling language. Can you model methods in OWL? Yes, I have done it here to find software modelling heuristic violations, but it is not trivial. However, even if you can define the conceptual notion of a method, there is no way to execute methods in OWL. Again, because it is not a programming language.

What about interfaces? Well, since there are no methods in OWL, there are no interfaces either. In programming the idea is that an interface defines the signature of an interaction, usually in terms of method signatures, without specifying how the methods are actually implemented. This allows for the same interface to have different implementations.

Neither are there Abstract Classes

Abstract classes in programming are used to enable programmers to only implement a subset of the interfaces specified, thereby forcing subclasses to do the implementation. However, again since interfaces do not exist, and there are no methods, it does not make sense for OWL to support abstract classes.

What about the case where in programming an abstract class is defined that only consist of member/instance variables? In programming this has the effect that no instances of this class can be created directly. The only way an instance can be created is by creating an instance of a subclass of the abstract class. Importantly these instances created of the subclass are still instances of the abstract class. In OWL there is no way to force that individuals cannot be created of a class, but only for its subclasses. The closest to not being able to create individuals for a class is the class ‘owl:Nothing‘, but that really means the class has zero individuals, which is not what is meant by an abstract class in programming.

Conclusion

In this post I detailed some object oriented features programmers may feel OWL lacks. However, OWL is a conceptual modelling language with reasoning capability. Thinking about it causes one to realize it makes as little sense to say OWL lacks some object oriented feature as saying that Java or C# lacks reasoning capability.