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