2

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_DESCRIPTORs 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_DESCRIPTORs 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_DESCRIPTORs. 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());

}
Stanislav Kralin
  • 11,070
  • 4
  • 35
  • 58
Christophe De Troyer
  • 2,852
  • 3
  • 30
  • 47
  • 1
    A minor note: the rules that you providing are in Jena syntax. They aren't _in_ 'owl'-per se. – Rob Hall May 21 '14 at 15:24
  • Yeah I don't really know the difference. Afaik I can write owl rules but they just have to adhere to this specific syntax? Right? – Christophe De Troyer May 21 '14 at 15:25
  • 1
    These rules will be specific to Apache Jena. There are other rule syntaxes (like SWRL). Your selected syntax reflects the reasoner that will be interpreting them under-the-hood. OWL reasoning is implemented in Jena through the use of Jena Rules. This reasoning is over generic RDF data. If you write a new Jena Rule and add it to a rule set which includes the rules for OWL, then you will be reasoning with owl and your extra rules. The rules themselves won't be attached to your document, or expressed in owl. It is a minor vocabulary nitpic, and not one that affects the answer to your question. – Rob Hall May 21 '14 at 15:30
  • Thank you for elaborating! I appreciate it! – Christophe De Troyer May 21 '14 at 16:04
  • Regarding your edit: has your rule to execute the builtin changed? I've experienced what seems to be a bug in Jena where, if my builtin is the first clause in my rule body, then my context appears empty. I'll also take a look at your current implementation. – Rob Hall May 22 '14 at 15:38

1 Answers1

2

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:

[Age: 
  (?game urn:ex:hasRating ?pegiID) 
  minPegiAge(?pegiID ?age) 
  -> 
  (?game urn:ex:age ?age)]

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 the RuleContext supplied to my builtin returns nothing from RuleContext#find(...). Additionally, the InfGraph (as well as Graph) for my rule context are both empty graphs that are not associated with the my actual InfModel. Once the rule is changed to include some generic triple pattern as the starting pattern, then the InfGraph associated with the RuleContext is the same InfGraph which your InfModel 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.

// These properties will be used in the example, I define them for
// convenience here.
final Property hasRating = ResourceFactory.createProperty("urn:ex:hasRating");
final Property age = ResourceFactory.createProperty("urn:ex:age");

// 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 RuleContext context) {
        checkArgs(length, context);
        final Node rating = getArg(0, args, context);
        if( !getArg(1, args, context).isVariable() ){
            return false;
        }

        final Iterator<Node> results = 
                new NiceIterator<Triple>()
                .andThen(context.find(rating, age.asNode(), Node.ANY))
                .mapWith(new Map1<Triple,Node>(){
                    @Override
                    public Node map1(Triple o) {
                        return o.getObject();
                    }});
        if( !results.hasNext() ) {
            return false;
        }

        Node min = results.next();
        while(results.hasNext()) {
            final Node val = results.next();
            if( Util.compareTypedLiterals(val, min) < 0 ) {
                min = val;
            }
        }
        context.getEnv().bind(getArg(1, args, context), min);
        return true;
    }

});

// Construct some sample data for this simplified version of
// your example scenario.
final Model rawData = ModelFactory.createDefaultModel();
final Resource game = rawData.createResource("urn:ex:theGame");
final Resource rating = rawData.createResource("urn:ex:theRating");
game.addProperty(hasRating, rating);
rating.addLiteral(age, 15);
rating.addLiteral(age, 14);

// Construct a simplified version of the rule that you use
// in order to identify when the minimum age needs to be
// detected.
final String rules = 
        "[Age: \n"+
        "  (?game urn:ex:hasRating ?pegiID) \n"+
        "  minPegiAge(?pegiID ?age) \n"+
        "  -> \n"+
        "  (?game urn:ex:age ?age)]";


final Reasoner reasoner;
try( final BufferedReader src = new BufferedReader(new StringReader(rules)) ) {
  reasoner = new GenericRuleReasoner(Rule.parseRules(Rule.rulesParserFromReader(src)));
}
final InfModel inf = ModelFactory.createInfModel(reasoner, rawData);

// Write the model, now including a minimum age triple associated with
// the game rather than the various pe
inf.write(System.out, "TTL");
Community
  • 1
  • 1
Rob Hall
  • 2,693
  • 16
  • 22
  • 1
    This seems to be exactly what I need. However, I have a problem. Your example should work, if the age was immediatly accesible from the PEGIRATING. However, I need the age in the order: `VIDEOGAME` has 1 `PEGI_RATING_id` which has multiple `PEGI_CONTENT_DESCRIPTOR`s which in turn have an `AGE`. So I would need to get that value out of each descriptor too. I'm playing around with the code but it does not seem to work until now. I'll keep trying though :) – Christophe De Troyer May 21 '14 at 17:25
  • It looks like you are doing the right thing. The only thing that I can think of is a view behavior in Jena that I've encountered before while doing this same thing. – Rob Hall May 22 '14 at 15:47
  • 1
    Thanks for this code! It kickstarted us and after a day we finally figured it out. Thank you very much for your time and efforts! :) – Christophe De Troyer May 22 '14 at 19:53
  • Another note: if the triples for the age associated with a rating are inferred, then they may not be available at the time that this rule is run. This is because those rules that provide those triples may have yet to fire, and there is nothing in the preconditions for this builtin that forces them all to have been. One thing that may make sense is to implement this particular builtin in the context of a backwards chaining rule, and leave the forward chaining rules as the ones which add an age. Then the ages will be available when sought by the backwards chaining rule. – Rob Hall May 23 '14 at 16:50