AN INTRODUCTION TO KNOWLEDGE REPRESENTATION

(This is a multi-part series on semantics and reasoning)

As explained in a previous article, Fuseki is an Apache Jena extension which allows you to play with triples and SPARQL before venturing into more enterprise-level offerings like AllegroGraph and Stardog. Doesn’t mean Jena is not a solid piece of software just that, like everything Apache, you have to deal with open-source type of issues.

Rather than explaining the obvious setup and usage in this article I want to show how you can enable inference in Jena.

External inference through Java

Writing rules is as simple as creating a little text file rules.txt and adding something like:

@prefix :  .
[ruleHasGender: (?s :uses :lipstick) (?s :wears :skirt) -> (?s :hasGender :female)]

Now, open your favorite Java or Scala IDE and create something like the following:

import com.hp.hpl.jena.rdf.model.InfModel;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.reasoner.Reasoner;
import com.hp.hpl.jena.reasoner.rulesys.GenericRuleReasoner;
import com.hp.hpl.jena.reasoner.rulesys.Rule;

  public class JenaReasoningWithRules
  {
    public static void main(String[] args) 
    {
        Model model = ModelFactory.createDefaultModel();
        model.read( "dataset.n3" );

        Reasoner reasoner = new GenericRuleReasoner( Rule.rulesFromURL( "rules.txt" ) );

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

        StmtIterator it = infModel.listStatements();

        while ( it.hasNext() )
        {
            Statement stmt = it.nextStatement();

            Resource subject = stmt.getSubject();
            Property predicate = stmt.getPredicate();
            RDFNode object = stmt.getObject();

            System.out.println( subject.toString() + " " + predicate.toString() + " " + object.toString() );
        }
    }
  }

This will loop of the triples and apply the predefined rule to your data to infer gender. Note the advantage of using an external text file: you edit and augement your (intelligent) rules on the fly.

The Jena inference documentation will give you all you need to continue in this direction.

Internal inference in Jena

Inference can also happen on the fly when you run a SPARQL query. To demonstrate this we’ll create a database in Fuseki via a configuration file rather than via the admin interface.

The easiest way to have Fuseki up and running on Linux and Mac is via the brew install fueski command. It install everything in one go and you end up with a Fuseki installation in /usr/local/Cellar/fuseki with data in /usr/local/var/fuseki/databases.

Add a folder Network in the databases and create three files in it: model.ttl, data.ttl and myrules.rules.

The content of the model.ttl is the ontology:

@prefix ns:  .
@prefix rdf:  .
@prefix rdfs:  .
@prefix owl:  .
@prefix xsd:    .

ns:Person       rdf:type        owl:Class .
ns:CEO          rdf:type        owl:Class .
ns:Company      rdf:type        owl:Class .
ns:Employee     rdf:type        owl:Class .
# ns:Person       owl:unionOf     (ns:Employee ns:CEO) .
ns:works_with   rdf:type        owl:TransitiveProperty .
ns:works_with   rdfs:domain     ns:Person .
ns:works_with   rdfs:range      ns:Person .

while the data.ttl is some data fitting the ontology:

@prefix ns:  .
@prefix rdf:  .
@prefix rdfs:  .
@prefix owl:  .
@prefix xsd:   .

ns:Liam       rdf:type        ns:CEO .
ns:Anna       rdf:type        ns:Employee .
ns:Indra      rdf:type        ns:Employee .
ns:Liam       ns:works_with   ns:Anna .
ns:Anna       ns:works_with   ns:Indra .

In the myrules.ttl you place the inference rules you like, say:

@prefix rdf:  .
@prefix rdfs:  .
@prefix owl:  .
@prefix ns:  .
@prefix xsd:   .

[ruleWorksWith: (ns:works_with rdf:type owl:TransitiveProperty) (?s ns:works_with ?o) -> (?o ns:works_with ?s)]
[isCEO: (?s rdf:type ns:CEO) -> (?s rdf:type ns:Person)]
[isEmployee: (?s rdf:type ns:Employee) -> (?s rdf:type ns:Person)]

All of which is rather obvious conceptually and fairly easy to write as well.

Inside the Fuseki configuration folder you add the database Network.ttl via the following definition:

@prefix :       .
@prefix tdb:    .
@prefix rdf:    .
@prefix ja:     .
@prefix rdfs:   .
@prefix fuseki:  .


:service1        a                        fuseki:Service ;
      fuseki:dataset                    :dataset ;
      fuseki:name                       "Network" ;
      fuseki:serviceQuery               "query" , "sparql" ;
      fuseki:serviceReadGraphStore      "get" ;
      fuseki:serviceReadWriteGraphStore "data" ;
      fuseki:serviceUpdate              "update" ;
      fuseki:serviceUpload              "upload" .

:dataset         rdf:type ja:RDFDataset ;
  rdfs:label "Network" ;
  ja:defaultGraph
    [ rdfs:label "Network" ;
      a ja:InfModel ;

      #Reference to model.ttl file
      ja:content [ja:externalContent  ] ;

      #Reference to data.ttl file
      ja:content [ja:externalContent  ] ;

      #Disable OWL based reasoner
      #ja:reasoner [ja:reasonerURL ] ;

      #Disable RDFS based reasoner
      #ja:reasoner [ja:reasonerURL ] ;

      #Enable Jena Rules based reasoner using the myrules.rules file
      ja:reasoner [
          ja:reasonerURL  ;
          ja:rulesFrom  ;                    
      ] ; 
    ] ;
   .

This defines the data, model and rules. Please alter the absolute paths above to reflect your situation.

Once all of this is in place, simply restart the server with something like

brew services restart fuseki

Using your favorite programming language or with YASGUI you can now query the Network database and you will see something like below:

Yasgui Query Editor

In this result set you clearly can see than there is more than our initial dataset data.ttl. The SPARQL engine has included the inferences on the fly. Go ahead and play with the rule in the myrules.ttl file. You need to restart the server when altering the rules althgouhg I’m sure there is somewhere a Jena settings which updates things dynamically.

This type of inference also allows you to solve some intricate things like the famous Einstein riddle (Zebra puzzle). You can find the OWL implementation here as well as elsewhere.