How to debug N3 rules

© J.M. Vanel - 2009-03-25

© Jean-Marc Vanel - $Date: 2012-08-01$ - under Creative Commons License

Here are some tricks I used in the last months developing Déductions rules.

A companion document is N3 rules design good practices .

TOC

N3 prefixes used

@prefix e: <http://eulersharp.sourceforge.net/2003/03swap/log-rules#> .
@prefix eg: <http://eulergui.sourceforge.net/engine.owl#> .

(for Euler, use prefix eu: with prefix.cc)

Replace N3 sources with N3 test copies

Do not spoil your valuable sources with debugging lines.

With EulerGUI, it is very quick to:

  1. save a copy of the N3 file in the editor ( possibly on /tmp )
  2. deactivate the original N3 source in the EulerGUI project
  3. add the copy to the project

Detect rules that are actually fired

Add a debug predicate in the consequent of the rule. This will add verbose printings, but will not change the expected results . Using a list, you can print whatever you want in a single triple.

Example:

{
  ?UML_CLASS uml:ownedAttribute ?ATTRIBUTE .
  ?ATTRIBUTE uml:name ?NAME .
  (?SCOPE ?SPAN) e:findall
    ( ?TYPE { ?ATTRIBUTE uml:type ?TYPE } ?TYPES ).

} => {
  ?PROPERTY a owl:DatatypeProperty . 
  ?PROPERTY rdfs:domain ?OWL_CLASS .
  ?PROPERTY rdfs:range xsd:string .
  ?PROPERTY rdfs:label ?NAME .
  ?PROPERTY :translatedFromUML ?ATTRIBUTE .
  ?PROPERTY <urn:DEBUG> (
    :last_rule_UML_CLASS ?UML_CLASS "\n"
    :last_rule_ATTRIBUTE ?ATTRIBUTE "\n"
    :last_rule_TYPES     ?TYPES     "\n"
  ).
} .

You need an extra rule in the query (again, it will not disturb and can even stay in production code) :

{ ?X <urn:DEBUG> ?Y }
=>
{ ?X <urn:DEBUG> ?Y } .

Tracing

With Euler engine

What one also can use is the builtin e:trace , defined thus :

e:trace a rdf:Property;
  rdfs:comment "builtin that outputs the object";
  rdfs:domain rdfs:Resource;
  rdfs:range rdfs:Resource .

As its name implies, e:trace prints stuff at runtime. It can be put in the antecedent only, as it is a builtin in Euler engine .

Moreover, you can intersperse the antecedent with trace triples, so one can see which part of the rule is causing problems in which application (instantiation).

Example with Euler processor :

{
  ?OWL_CLASS :translatedFromUML ?UML_CLASS .
  _:d e:trace ( "step1" ?OWL_CLASS :translatedFromUML ?UML_CLASS ) .
  ?UML_CLASS uml:ownedAttribute ?ATTRIBUTE .
  _:d e:trace ( "step2" "ATTRIBUTE" ?ATTRIBUTE ) .
  ?ATTRIBUTE uml:name ?NAME .
  _:d e:trace ( "step3" "NAME" ?NAME ) .
} => {
  # some consequences ....
}.

Result:

One can see that for some instantiations, step2 is not reached :

#TRACE ("""step1""" _:sk2 umlowl:translatedFromUML def:id223205208)
#TRACE ("""step2""" """ATTRIBUTE""" def:id1563093566)
#TRACE ("""step3""" """NAME""" """name""")
#TRACE ("""step1""" _:sk3 umlowl:translatedFromUML def:id1563093566)
#TRACE ("""step1""" _:sk5 umlowl:translatedFromUML def:id1975260912)
#TRACE ("""step1""" _:sk7 umlowl:translatedFromUML def:id1563093566)
#TRACE ("""step1""" _:sk0 umlowl:translatedFromUML def:id223205208)

With Drools engine

In Drools N3 engine, contrary to above, tracing can only be used in the consequent side. Use a similar syntax, but a different builtin, e.g. :

_:d eg:trace ( ":x :p" ?listModel ?V ).

The e:trace builtin of Euler is taken in account : it generates no test in the antecedent, so that it can stay in the rule file while using Drools/N3 engine .

Check intermediary results

This is especially useful for "chains" of rules , with intermediary results firing other rules.

You can add ad-hoc queries to print these.

It is more convenient to again use the <urn:DEBUG> trick to print these, because you intervene in a single place: the rule you are working at.

Remove conditions until a given rule is fired

Using previous trick, when you see a rule is not fired, you can remove conditions until that rule is actually fired.

By comparing the conditions that are actually met with the extra conditions that cause the rule to fail, you can see what is happening.

Think about facultative predicates

Very often, some predicates are not present, like rdsf:label , or rdfs:range, but a rule is relying on it .

In this case , it is necessary to provide a fallback rule using e:findall or log:notIncludes .

There is also the builtin e:optional, defined thus :

e:optional a rdf:Property;
  rdfs:comment "builtin to call object formula and to succeed anyway";
  rdfs:domain rdfs:Resource;
  rdfs:range log:Formula .

Example:

??????????