I'm doing a project in JENA using OWL rules.
In my ontology I have an entity named PEGI_RATING
. A PEGI_RATING
can have multiple descriptors.
Example:
<http://localhost:2020/PEGI_RATING/6>
a vocab:PEGI_RATING ;
rdfs:label "PEGI_RATING #6" ;
vocab:PEGI_RATING_age
16 ;
vocab:PEGI_RATING_has_PEGI_CONTENT_DESCRIPTOR
<http://localhost:2020/PEGI_CONTENT_DESCRIPTOR/Online> ,
<http://localhost:2020/PEGI_CONTENT_DESCRIPTOR/Violence> ,
<http://localhost:2020/PEGI_CONTENT_DESCRIPTOR/Bad_Language> ;
vocab:PEGI_RATING_ratingId
6 .
Now, I want to write a rule in OWL that attaches an age to each PEGI_RATING
. I know it is there already, but it is required to show that I know how to use the reasoner.
Now, a content descriptor has an age attached to it. I do this with the following rule:
[AgeLimit:
(?descr rdf:type vocab:PEGI_CONTENT_DESCRIPTOR)
(?descr vocab:PEGI_CONTENT_DESCRIPTOR_contentDescriptor "Sex")
->
(?descr vocab:PEGI_CONTENT_DESCRIPTOR_inf_age_limit "16"^^xsd:integer)
]
So finally I have a VIDEO_GAME
which has a PEGI_RATING
id.
Example:
<http://localhost:2020/VIDEOGAME/Grand_Theft_Auto_IV>
a vocab:VIDEOGAME ;
rdfs:label "VIDEOGAME #Grand Theft Auto IV" ;
vocab:VIDEOGAME_EsrbRatingCategoryCategory
<http://localhost:2020/ESRB_RATING_CATEGORY/M> ;
vocab:VIDEOGAME_gameplayRulesGameplayRulesId
<http://localhost:2020/GAMEPLAY_RULES/3> ;
vocab:VIDEOGAME_has_SIDE_GOAL
<http://localhost:2020/SIDE_GOAL/Complete_all_missions.> ;
vocab:VIDEOGAME_onlineMultiplayer
"false"^^xsd:boolean ;
vocab:VIDEOGAME_pegiRatingRatingId
<http://localhost:2020/PEGI_RATING/3> ;
vocab:VIDEOGAME_summary
"For Niko Bellic, fresh off the boat from Europe, it is the hope he can escape his past. For his cousin, Roman, it is the vision that together they can find fortune in Liberty City, gateway to the land of opportunity. As they slip into debt and are dragged into a criminal underworld by a series of shysters, thieves and sociopaths, they discover that the reality is very different from the dream in a city that worships money and status, and is heaven for those who have them and a living nightmare for those who don't." ;
vocab:VIDEOGAME_title
"Grand Theft Auto IV" .
I want to create a rule that determines the maximum age that PEGI_CONTENT_DESCRIPTOR
s that are attached to the PEGI_RATING
of the VIDEOGAME
.
In Prolog I would do something like this:
[Age:
(?gameRelease rdf:type vocab:GAME_RELEASE)
(?gameRelease vocab:GAME_RELEASE_videogameTitle ?game)
(?game vocab:VIDEOGAME_pegiRatingRatingId ?pegiID)
(?pegiID vocab:PEGI_RATING_has_PEGI_CONTENT_DESCRIPTOR ?descriptor)
(?descriptor vocab:PEGI_CONTENT_DESCRIPTOR_inf_age_limit ?age)
(?descriptor vocab:PEGI_CONTENT_DESCRIPTOR_inf_age_limit ?age2)
not(lessThan(?age, ?age2))
->
(?game vocab:VIDEOGAME_inf_minimumAge ?age)]
But since OWL does not appear to have negation by failure I'm stumped on how to solve it.
So far I have tried the following rule without succes:
[Age:
(?gameRelease rdf:type vocab:GAME_RELEASE)
(?gameRelease vocab:GAME_RELEASE_videogameTitle ?game)
(?game vocab:VIDEOGAME_pegiRatingRatingId ?pegiID)
(?pegiID vocab:PEGI_RATING_has_PEGI_CONTENT_DESCRIPTOR ?descriptor)
(?descriptor vocab:PEGI_CONTENT_DESCRIPTOR_inf_age_limit ?age)
(?descriptor vocab:PEGI_CONTENT_DESCRIPTOR_inf_age_limit ?age2)
greaterThan(?age, ?age2)
->
(?game vocab:VIDEOGAME_inf_minimumAge ?age)]
FINAL RESULT
So finally we have a working function. It does exactly what is supposed to do. However..
First of all, we have a number of rules in Jena format that add a specific age to each PEGI_CONTENT_DESCRIPTOR
. This is purely for didactic reasons (i.e., showing we can use a reasoner). This works. When I add these rules I can execute SPARQL queries against my model and I get the proper values. When i do a write of my model (as Rob Hall indicated in his example) the PEGI_CONTENT_DESCRIPTOR
s have indeed an age. They look like this:
<http://local.host.com:2020/PEGI_CONTENT_DESCRIPTOR/Violence>
a vocab:PEGI_CONTENT_DESCRIPTOR ;
rdfs:label "PEGI_CONTENT_DESCRIPTOR #Violence" ;
vocab:PEGI_CONTENT_DESCRIPTOR_contentDescriptor
"Violence" ;
vocab:PEGI_CONTENT_DESCRIPTOR_explanation
"May contain scenes of people getting injured or dying, often by use of weapons, whether realistically or in a fantastical or cartoonish manner. Also may contain gore and blood-letting." ;
vocab:PEGI_CONTENT_DESCRIPTOR_inf_age_limit
18 .
Remember that a video game has a PEGI rating Id: ?game vocab:VIDEOGAME_pegiRatingRatingId ?pegiId
.
We want to execute the Jena Builtin as follows:
minimumPegiAge(?pegiID, ?age)
To this end we have the function below. It actually works. However, for some strange reason, context.find(pegiID, has_descriptor.asNode(), Node.ANY);
does not seem to iterate over two particular PEGI_DESCRIPTOR
s. Namely Sex
and Violence
. As metioned, they are present in the inferred model and they are returned from SPARQL queries. Could we be dealing with a bug? Or are we missing something?
final Property has_age_limit =
ResourceFactory.createProperty("http://localhost:2020/vocab/PEGI_CONTENT_DESCRIPTOR_inf_age_limit");
final Property has_descriptor =
ResourceFactory.createProperty("http://localhost:2020/vocab/PEGI_RATING_has_PEGI_CONTENT_DESCRIPTOR");
// Create and Register a Builtin for Jena's rule system.
BuiltinRegistry.theRegistry.register(new BaseBuiltin() {
@Override
public String getName() {
return "minPegiAge";
}
@Override
public boolean bodyCall(final Node[] args, final int length, final com.hp.hpl.jena.reasoner.rulesys.RuleContext context) {
checkArgs(length, context);
final Node pegiID = getArg(0, args, context);
if( !getArg(1, args, context).isVariable() ){
return false;
}
// Should get all the descriptors for this PegiID.
ClosableIterator<Triple> x = context.find(pegiID, has_descriptor.asNode(), Node.ANY);
// Iterate over them.
final Iterator<Node> results =
new NiceIterator<Triple>()
.andThen(x) // Get all the descriptors
.mapWith(new Map1<Triple,Node>(){
@Override
public Node map1(Triple o) {
// o is a triple
// These triples are descriptors
// We need to get the age for these descriptors
System.out.println(o);
return o.getObject();
}});
if( !results.hasNext() ) {
return false;
}
Node min = null;
while(results.hasNext()) {
final Node pegiContentDescriptor = results.next();
System.out.println("DESCRIPTION: " + pegiContentDescriptor.toString());
ClosableIterator<Triple> y = context.find(pegiContentDescriptor, has_age_limit.asNode(), Node.ANY);
// Iterate over them.
final Iterator<Node> singleAge =
new NiceIterator<Triple>()
.andThen(y) // Get all the descriptors
.mapWith(new Map1<Triple,Node>(){
@Override
public Node map1(Triple o) {
// o is a triple
// These triples are descriptors
// We need to get the age for these descriptors
return o.getObject();
}});
if (singleAge.hasNext()) {
Node age = singleAge.next();
System.out.println("AGE: " + age.getLiteralValue());
if (min == null) {
min = age;
} else {
if (Util.compareTypedLiterals(min, age) < 0) {
min = age;
}
}
}
}
if (min == null) {
System.out.println("GEEN MINIMUM AGE GEVONDEN!");
} else {
System.out.println("MINIMUM: " + min.getLiteralValue());
}
context.getEnv().bind(getArg(1, args, context), min);
return true;
}
});
// Load TTL-file (full db dump!)
// Note: make sure the path containing the ttl file does not contain strange characters :D
// "-" and maybe spaces are not allowed
model = ModelFactory.createDefaultModel();
model.read(getClass().getResourceAsStream("/trivial-mapping-dump.ttl"), null, "TURTLE");
// Load the rules.
List<Rule> rules = Rule.rulesFromURL(getClass().getResource("/rules.txt").toString());
// Let the reasoner.. reason!
// Then add the triples existing due to rule firings to our base graph
GenericRuleReasoner r = new GenericRuleReasoner(rules);
r.setOWLTranslation(true); // not needed in RDFS case
r.setTransitiveClosureCaching(true);
r.setMode(GenericRuleReasoner.HYBRID);
InfModel infmodel = ModelFactory.createInfModel(r, model);
model.add(infmodel.getDeductionsModel());
}
This is very similar to an existing question: Giving array as parameter to jena builtin. Before I begin, it is useful to note that identifying this element using a SPARQL query is extremely easy.
In Jena, you can implement a rule similar to the following:
BEGIN EDIT
It is extremely important that your rule begins with some generic triple pattern and not with the custom builtin (
minPegiAge
in this case). I've run into a problem where theRuleContext
supplied to my builtin returns nothing fromRuleContext#find(...)
. Additionally, theInfGraph
(as well asGraph
) for my rule context are both empty graphs that are not associated with the my actualInfModel
. Once the rule is changed to include some generic triple pattern as the starting pattern, then theInfGraph
associated with theRuleContext
is the sameInfGraph
which yourInfModel
will return.END EDIT
This requires that you then implement a Jena Builtin to calculate the minimum. Within the Builtin, you would need to use the available
RuleContext
in order to explore your graph and get the things which you need to explore the minimum of. The following example creates a builtin that pulls the minimum value for a particular datatype property.