Knowledge Base

ddossot edited this page Oct 25, 2014 · 14 revisions
Clone this wiki locally

General

First Steps

NxBRE does not expose in any way the rules to the development environment: the rules are interpreted by the engine itself. The responsibility of the application is to expose information to the rule engine so it can process the rules.

If you work with the Flow Engine, the information is exposed as pure objects via the engine Context ; if you work with the Inference Engine, the information is exposed as facts (which are basic pieces of data - predicates - related together by a named relationship : eg. "A is the father of B", A and B are predicates and "is the father of" is the fact type).

So the steps an application does are the following:

  • load a rule file in the engine,
    
  • expose information to the engine,
    
  • ask the engine to process,
    
  • analyze the deductions made by the engine. 
    

This only amounts to a few lines of code in NxBRE:

Flow Engine

IFlowEngine bre = new BREImpl();
bre.Init(new XBusinessRulesFileDriver(ruleFile))
bre.RuleContext.SetObject("TestObject", tobj);
bre.Process();
// check the modifications made on tobj

Inference Engine

IInferenceEngine ie = new IEImpl();
ie.LoadRuleBase(new RuleML09NafDatalogAdapter(ruleFile, FileAccess.Read));
ie.Assert(new Fact("is the father of", new Individual("John Smith"), new Individual("Kyle Smith"));
ie.Process();
// query the fact base for deducted facts or directly have the engine affect business objects via events

NB. these code fragments are just a samples, there are factories and other helpers that can be used.


Examples

Running the provided examples (Discount, Login, FraudControl and TelcoRating) requires to pass them a path to the place you have decompressed or SVN synchronized NxBRE.

You will find the command line syntax to use, with the list of parameters to pass, in NxBRE3/RuleFiles/readme.html. Here is a link to the most recent version of this file. Note that it might not match your local version.

The engine runs without any config file: is it useless?

The NxBRE.dll assemblies provided with NxBRE do not need any configuration information to be used in an application.

Configuration information is needed for these two cases:

  • when running the unit tests,
    
  • when using any other values than the default ones for the different configurable parameters (see PDF documentation for more information). 
    

Unit testing NxBRE

Whether you use the pre-compiled assembly provided with the official distribution or you build NxBRE yourself, it is highly recommended to execute the unit tests to certify the engine. Until you will get all green lights in NUnit , you should not use the engine in your project.

Since v3.1.0, the unit tests are located in a friend assembly that is compiled from a different project in the same solution. At run time, you need only to reference NxBRE.dll.

To successfully run the unit tests you must:

  • properly configure the engine under the "Unit Test Config" section: the paths should point to the correct location of test rule bases (file and http sources) and temporary folder,
    
  • have the XML schemas and DTDs copied in the output folder else validation of the rule bases generated during the unit test phase will fail. 
    

Flow Engine

Asserting Generic Types

Here is how you assert a System.Collections.Generic.Dictionary<string,string> from a FlowEngine rule:

<Assert id="dictionaryStringString"
        type="System.Collections.Generic.Dictionary`2[System.String,System.String]" />

An overview of binder methods to override or implement

To perform specific actions and comparisons, the engine can call methods developed by the user: these methods are stored in a component that is called a Binder, which is very similar to the code-behind concept of ASP.NET pages (in this case the binder code would be behind the rule base).

The engine supports two kinds of binders: CSharp Binders, which implement NxBRE.!InferenceEngine.IO.IBinder, and Flow Engine Binders, which uses the Flow Engine of NxBRE for performing the specific actions (stored in Sets that follow a naming convention).

Always consult the API doc of NxBRE to have the most up-to-date list of binder features (see class NxBRE.!InferenceEngine.IO.IBinder).

Process related methods

Depending on the kind of binder, control is passed to user code either before and after the engine process or fully delegated to user code that must start the inference process.

Flow Engine Binder

The Sets to define are either BeforeProcess and AfterProcess or ControlProcess. The engine façade is available in a context variable named NxBRE-IE and all the business objects are available in the context under the ID they were entered in the hashtable passed to ie.Process().

CSharp Binder

Implement the BeforeProcess and AfterProcess methods or the ControlProcess method, as defined in the IBinder interface. The engine façade is available as an instance property named IEF and the business objects are available in a property named BusinessObjects.

Comparison of individual predicates

Individual predicate are usually interpreted as values unless they follow certain patterns. In this case, they are evaluated against the individual value of a potentially matching fact via a binder (or a direct expression, which is not the subject of this page).

In the following atom, the string "min(150000,EUR)" will be interpreted as an expression to be evaluated in a binder because it matches the regular expression defined in NxBRE.!InferenceEngine.IO.AbstractBinder:

<Atom>
 <op>
  <Rel>Amount</Rel>
 </op>
 <Var>Transaction</Var>
 <Ind>min(150000,EUR)</Ind>
</Atom>

The method to implement to estimate this expression must return a boolean.

Flow Engine Binder

Define a Set whose id is "Evaluate_" + function_name + "_Argx" (where x is counted from 0 to the number of arguments of the function) and whose return value should be a boolean stored in the engine context under the ID "IE-RESULT". For the above example, it would be like this:

<Set id="Evaluate_min_Arg0_Arg1">
 <Logic>
  <If>
   <And>
    <GreaterThanEqualTo leftId="CurrentAmount" rightId="LimitAmount">
    <ObjectLookup id="CurrentAmount" objectId="MNYCNV" member="Convert">
      <Argument valueId="IE-PREDICATE"/>
      <Argument valueId="Arg1"/>
    </ObjectLookup>
    <ObjectLookup id="LimitAmount" type="NxBRE.Util.Reflection" member="CastValue">
      <Argument valueId="Arg0"/>
      <Argument value="System.Double"/>
    </ObjectLookup>
    </GreaterThanEqualTo>
   </And>
   <Do>
    <Boolean id="IE-RESULT" value="true"/>
   </Do>
  </If>
  <Else>
   <Boolean id="IE-RESULT" value="false"/>
  </Else>
 </Logic>
</Set>

In the above code, note how:

  • the predicate onto which the evaluation is performed is passed in the IE-PREDICATE context value,
    
  • the function arguments defined in the rule base (150000 and EUR in the above example) are received as strings in the Arg0 and Arg1 context values,
    
  • business objects (which are passed to the binder from the application code via `ie.Process(businessObjects)`) are accessed by their ID directly in the context ("MNYCNV" in this example). 
    

CSharp Binder

Implement the Evaluate method as defined in the IBinder interface, with an internal selection mechanism based on the method signature defined as function_name + "_Argx" (where x is counted from 0 to the number of arguments of the function). For the above example, it would be like this:

public override bool Evaluate(object predicate, string function, string[] arguments) {
 switch(function) {
  case "min_Arg0_Arg1":
   return ((MoneyConverter)BusinessObjects["MNYCNV"]).Convert((Money)predicate, arguments[1]) >= Double.Parse(arguments[0]);
  }
 throw new Exception("Binder can not evaluate " + function);
}

In the above code, note how:

  • the predicate onto which the evaluation is performed is passed in the predicate argument,
    
  • the function arguments defined in the rule base (150000 and EUR in the above example) are received as strings in the arguments array,
    
  • business objects (which are passed to the binder from the application code via `ie.Process(businessObjects)`) are available in the `BusinessObjects` property and accessed by their ID ("MNYCNV" in this example). 
    

Note: NxBRE's internal operators are automatically bound: the expression in the following example does not need to be resolved by the binder:

<Atom>
 <op>
  <Rel>probe</Rel>
 </op>
 <Var>object</Var>
 <Ind uri="nxbre://operator">Equals(100)</Ind>
</Atom>

Function-based atom relationships

Atoms are primarily matched on their relation types, i.e. the value that is in the tag. It is possible to use an function in the relation to represent atoms that will be considered positive if the function returns true. This kind of atom, similarly to NAF ones, do not contribute any predicate value to an implication or a query: they simply invalidate the evaluation branch in the inference process.

In the following atom, the string "WithinTolerance" will be interpreted as a function-based relation to be evaluated in the binder because it matches the simple pattern "*()":

<Atom>
  <op>
    <Rel>WithinTolerance()</Rel>
  </op>
  <Var>Value</Var>
  <Var>Threshold</Var>
  <Var>Percent Tolerance</Var>
</Atom>

Alternatively, as shown in the following atom, it is possible to use the "binder" URI to deterministically define the rest of the relationship as being to be resolved by the binder:

<Atom>
  <op>
    <Rel uri="nxbre://binder">WithinTolerance</Rel>
  </op>
  <Var>Value</Var>
  <Var>Threshold</Var>
  <Var>Percent Tolerance</Var>
</Atom>

The method to implement to estimate this expression must return a boolean.

Flow Engine Binder

Define a Set whose id is "Relate_" + relation_name + "_Argx" (where x is counted from 0 to the number of arguments of the function) and whose return value should be a boolean stored in the engine context under the ID "IE-RESULT". For the above example, it would be like this:

<Set id="Relate_WithinTolerance_Arg0_Arg1_Arg2">
  <ObjectLookup id="ActualScore" type="NxBRE.Util.Reflection" member="CastValue">
    <Argument valueId="Arg0"/>
    <Argument value="System.Double"/>
  </ObjectLookup>
  <ObjectLookup id="ScoreThreshold" type="NxBRE.Util.Reflection" member="CastValue">
    <Argument valueId="Arg1"/>
    <Argument value="System.Double"/>
  </ObjectLookup>
  <ObjectLookup id="PercentThreshold" type="NxBRE.Util.Reflection" member="CastValue">
    <Argument valueId="Arg2"/>
    <Argument value="System.Double"/>
  </ObjectLookup>
  <ObjectLookup id="HundredMinusPercentThreshold" type="NxBRE.Util.Maths" member="Subtract">
    <Argument value="100" type="Double"/>
    <Argument valueId="PercentThreshold"/>
  </ObjectLookup>
  <ObjectLookup id="ScoreThresholdWithTolerance" type="NxBRE.Util.Maths" member="Multiply">
    <Argument valueId="ScoreThreshold"/>
    <Argument valueId="HundredMinusPercentThreshold"/>
    <Argument value=".01" type="Double"/>
  </ObjectLookup>
  <Logic>
    <If>
      <And>
        <GreaterThanEqualTo leftId="ActualScore" rightId="ScoreThresholdWithTolerance"/>
      </And>
      <Do>
        <Boolean id="IE-RESULT" value="true"/>
      </Do>
    </If>
    <Else>
      <Boolean id="IE-RESULT" value="false"/>
    </Else>
  </Logic>
</Set>

Note that:

  • the function relation arguments defined in the rule base (Value, Threshold and Percent Tolerance in the above example) are received as objects in the Arg0, Arg1 and Arg2 context values,
    
  • business objects (which are passed to the binder from the application code via `ie.Process(businessObjects)`) are accessible by their ID directly in the context. 
    

CSharp Binder

Implement the Relate method as defined in the IBinder interface, with an internal selection mechanism based on the method signature defined as function_name + "_Argx" (where x is counted from 0 to the number of arguments of the function). For the above example, it would be like this:

public override bool Relate(string function, object[] predicates) {
  switch(function) {
    case "WithinTolerance_Arg0_Arg1_Arg2":
      return Convert.ToDouble(predicates[0]) >= (Convert.ToDouble(predicates[1]) * (100d - Convert.ToDouble(predicates[2])) * .01d);
  }
  throw new Exception("Binder can not evaluate " + function);
}

Note that:

  • the function arguments defined in the rule base (Value, Threshold and Percent Tolerance in the above example) are received as objects in the predicates array,
    
  • business objects (which are passed to the binder from the application code via `ie.Process(businessObjects)`) are available in the BusinessObjects property. 
    

Expression-based deduction formulas

It is possible to use formulas in a deduction atom of an implication: these formulas will be used for computing the value of the individual predicates of the asserted fact.

In the following atom, the string "CalculateTotalWeight" will be interpreted as a formula to be evaluated in the binder thanks to the URI attribute:

<Atom>
  <op>
    <Rel>Chocolate_Box_Weight</Rel>
  </op>
  <Var>Box</Var>
  <Ind uri="nxbre://binder">CalculateTotalWeight</Ind>
</Atom>

The method to implement to estimate this expression can return any type. All the resolved variables in the query part of the implication are passed to the function and are accessible by their name (i.e. as strings). If a formula is used in a modifying implication, the predicate values coming from the fact that will be modified are available by their position (i.e. as integers), starting from 0 for the first predicate. If a formula contains coma-separated values in parenthesis, these values will be available as an IList under the key typeof(NxBRE.Util.Parameter).

Flow Engine Binder

Define a Set whose id is "Compute_" + formula_name and whose return value should be stored in the engine context under the ID "IE-RESULT". For the above example, it would be like this:

<Set id="Compute_CalculateTotalWeight">
  <ObjectLookup id="TotalWeight" objectId="IE-ARGUMENTS" member="Item">
    <Argument value="1" type="Integer"/>
  </ObjectLookup>
  <ObjectLookup id="TotalWeight" type="NxBRE.Util.Reflection" member="CastValue">
    <Argument valueId="TotalWeight"/>
    <Argument value="System.Double"/>
  </ObjectLookup>
  <ObjectLookup id="Quantity" objectId="IE-ARGUMENTS" member="Item">
    <Argument value="Quantity" type="String"/>
  </ObjectLookup>
  <ObjectLookup id="Quantity" type="NxBRE.Util.Reflection" member="CastValue">
    <Argument valueId="Quantity"/>
    <Argument value="System.Double"/>
  </ObjectLookup>
  <ObjectLookup id="Weight" objectId="IE-ARGUMENTS" member="Item">
    <Argument value="Weight" type="String"/>
  </ObjectLookup>
  <ObjectLookup id="Weight" type="NxBRE.Util.Reflection" member="CastValue">
    <Argument valueId="Weight"/>
    <Argument value="System.Double"/>
  </ObjectLookup>
  <ObjectLookup id="BagWeight" type="NxBRE.Util.Maths" member="Multiply">
    <Argument valueId="Quantity"/>
    <Argument valueId="Weight"/>
  </ObjectLookup>
  <ObjectLookup id="IE-RESULT" type="NxBRE.Util.Maths" member="Add">
    <Argument valueId="TotalWeight"/>
    <Argument valueId="BagWeight"/>
  </ObjectLookup>
</Set>

Note that:

  • the arguments for the formula are received as objects in the IE-ARGUMENTS context value and mapped as explained before ; in the previous example TotalWeight is a predicate value coming from a modified fact, while Quantity and Weight are coming from the resolved variables of the body part of the implication,
    
  • business objects (which are passed to the binder from the application code via `ie.Process(businessObjects)`) are accessible by their ID directly in the context,
    
  • any extra string arguments passed between parenthesis to the formula are accessible as an IList under the ID TYPEOF_PARAMETER.
    

CSharp Binder

Implement the Compute method as defined in the IBinder interface, with an internal selection mechanism based on the function_name. For the above example, it would be like this:

public override object Compute(string operationName, IDictionary arguments) {
  if (operationName == "CalculateTotalWeight")
    return System.Convert.ToDouble(arguments[1])+System.Convert.ToDouble(arguments["Quantity"])*System.Convert.ToDouble(arguments["Weight"]);
  else
    return null;
}

Note that:

  • the arguments for the formula are received as objects in the arguments IDictionary and mapped as explained before ; in the previous example arguments[1] is a predicate value coming from a modified fact, while Quantity and Weight are coming from the resolved variables of the body part of the implication,
    
  • business objects (which are passed to the binder from the application code via `ie.Process(businessObjects)`) are available in the BusinessObjects property,
    
  • any extra string arguments passed between parethesis to the formula are accessible in the arguments IDictionary as an IList stored under the key `typeof(org.nxbre.util.Parameter)`. 
    

The binder file is not found while the path that points it is correct

This can happen with on-the-fly compiled binders, when using the LoadFromFile method of the BinderFactory used to generate the binder.

Behind the scene this boils down to this internal switch:

if (sourceIsString) {
  cr = compiler.CompileAssemblyFromSource(compilerParameters, source);
}
else {
  cr = compiler.CompileAssemblyFromFile(compilerParameters, source);
}

where compiler is a System.!CodeDom.Compiler.ICodeCompiler.

The mechanism used by .NET to locate the source file behind the CompileAssemblyFromFile method is more complex than it seems and can, sometimes, produce the unexpected result of not finding a binder file while the full path pointing to it is correct.

A work-around consists in loading the binder source code in a string and use the other compilation method to compile this string directly.

For a CSharp binder, the following code would do the trick:

using (StreamReader sr = File.OpenText("myruleBase.ruleml.ccb")) {
  ie = new IEImpl(CSharpBinderFactory.LoadFromString("MyBinder.FQN", sr.ReadToEnd()));
}

Diagnosing Rule Bases Designed With Visio

If you are having issues when opening a rule base created with Visio, here is the easiest way to track down the problem:

  1. Transform your VDX file to RuleML with Source/Resources/Visio2003toRuleML.xsl
    
  2. Validate the resulting RuleML rule base with the relevant schema
    
  3. Find out the issue and infer the possible cause in the original Visio file
    
  4. Apply the correction on the VDX file and retry from step one 
    

With a proper XML editing environment, steps 1 and 2 take only a few seconds. For step 3, the usual problems are:

  • badly connected atoms, operators or deductions
    
  • typos or extra characters in predicates (very often an extra CRLF) 
    

Reflection call to VB.NET class fails

If you have issues calling a VB.NET shared method from NxBRE, with a construct like this:

<ObjectLookup id="MyValue" type="{TargetClass}, {AssemblyName}" member="{MethodName}">
  <Argument value="2" type="String" />
</ObjectLookup>

Give errors like that:

Error when processing RuleFactory id: MyValue ---> System.TypeLoadException: Could not load type 'TargetClass' from assembly 'AssemblyName.'

It means that the VB.NET assembly is well found but the target class is not what you think it is. This can happen if your class is not in any namespace, as VB.NET will but them in a namespace based on the assembly name.

Anyway, to be sure of the correct target class name, run this little code:

Dim rt As Type = New clsUtil().GetType
Console.WriteLine(rt)

And use the printed class in your NxBRE config.


Inference Engine

Quick Tour

If you want to get a feeling on how the Inference Engine runs:

  • start the console
    
  • open "discount_lab.ruleml"
    
  • list the facts in the working memory with Ctrl+L
    
  • run the query with F6, see how it returns nothing,
    
  • make the engine infer with F5
    
  • see the facts it deducts
    
  • list the facts in the WM again
    
  • run the query again 
    

You can then start playing with the WM:

  • re-open the "discount_lab.ruleml" rule base (Ctrl+O),
    
  • create a new isolated memory with Ctrl+I
    
  • infer with F5
    
  • Ctrl+L
    
  • roll back to to global WM Ctrl+G
    
  • Ctrl+L 
    

Redo this but this time commit to the global memory and see how the asserted facts are "copied" to the global WM.

Another exercise consists in manually asserting/retracting facts the rule base can understand:

  • re-open the "discount_lab.ruleml" rule base,
    
  • process F5
    
  • process again : nothing is deducted
    
  • assert `luxury{Ferrari}` with Ctrl+A
    
  • process again and see how new deductions are made. 
    

This quick tour gives a first feeling on how the Inference Engine works & behaves.


Further learning with the console

This entry has been built as a reply to this question on NxBRE's forum:

I need an example of a complete RuleML file that asserts a simple fact e.g "Bob" is a "Man", that I can load into the NxBRE-IE-Console app. I'd then like to be able to retract the fact. And then I'd like to query for things like "Is Bob a Man?", "Who is Bob?", "How many men are there?", "How many people have the name Bob?".

Please note that all RuleML fragments are suggestions, there are many ways to reach your goals, which might be confusing. Find your preferred way to express facts and to query them.

Here is a complete RuleML file that asserts a simple fact e.g "Bob" is a "Man"

<?xml version="1.0" encoding="utf-8"?>
<RuleML xmlns="http://www.ruleml.org/0.9/xsd" xsi:schemaLocation="http://www.ruleml.org/0.9/xsd ruleml-0_9-nafdatalog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <Assert>
        <Atom>
            <Rel>Gender</Rel>
            <Ind>Bob</Ind>
            <Ind>Male</Ind>
        </Atom>
    </Assert>
</RuleML>

To retract the fact in the Console, do a Ctrl+R then enter "Gender{Bob,Male}" in the dialog box.

Now concerning the queries, it is not possible to create new queries from the Console (as of version 1.2.1), so they have to be pre-defined in the rule base. Some of the questions can be solved by using Queries and others can be solved by using Implications.

<?xml version="1.0" encoding="utf-8"?>
<RuleML xmlns="http://www.ruleml.org/0.9/xsd" xsi:schemaLocation="http://www.ruleml.org/0.9/xsd ruleml-0_9-nafdatalog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <Query>
        <oid>
            <Ind>Who is Bob</Ind>
        </oid>
        <Atom>
            <Rel>Gender</Rel>
            <Ind>Bob</Ind>
            <Var>The Gender</Var>
        </Atom>
    </Query>
    <Assert>
        <Atom>
            <Rel>Gender</Rel>
            <Ind>Bob</Ind>
            <Ind>Male</Ind>
        </Atom>
        <Implies>
            <oid>
                <Ind>Is Bob A Man</Ind>
            </oid>
            <Atom>
                <Rel>Gender</Rel>
                <Ind>Bob</Ind>
                <Var>TheGender</Var>
            </Atom>
            <Atom>
                <Rel>Is Bob A Man</Rel>
                <Ind uri="nxbre://expression">"Male".Equals({var:TheGender})</Ind>
            </Atom>
        </Implies>
        <Implies>
            <oid>
                <Ind>label:How many men are there?;action:count;</Ind>
            </oid>
            <Atom>
                <Rel>Gender</Rel>
                <Var>FirstName</Var>
                <Ind>Male</Ind>
            </Atom>
            <Atom>
                <Rel>Number of Men</Rel>
                <Var>value</Var>
            </Atom>
        </Implies>
        <Implies>
            <oid>
                <Ind>label:How many people have the name Bob?;action:count;</Ind>
            </oid>
            <Atom>
                <Rel>Gender</Rel>
                <Ind>Bob</Ind>
                <Var>TheGender</Var>
            </Atom>
            <Atom>
                <Rel>Number of Bobs</Rel>
                <Var>value</Var>
            </Atom>
        </Implies>
    </Assert>
</RuleML>

Of course, if you add more men and re-process the rule base, you will discover that assert static facts for counted values creates an interesting side effect: you can end-up with the following facts in the working memory:

Number of Men{1}
Number of Men{2}

To solve this you can either:

  • stop using Implications, stick to Queries and programmatically count the number of returned rows to have the count of matching facts (this can be done in a Binder, in the `AfterProcess` method),
    
  • assert new people in the Global memory and use isolated memories for processing: the counts will be rolled back every time you switch back to the Global memory to assert/retract facts about people. 
    

Typed Facts And Operators

Why is it that this fact in a RuleML rule base:

<Atom>
  <Rel>Hour</Rel>
  <Ind>CALL0001</Ind>
  <Ind>15</Ind>
</Atom>

is not selected by this atom:

<Atom>
  <Rel>Hour</Rel>
  <Var>Call</Var>
  <Ind uri="nxbre://operator">GreaterThanEqualTo(7)</Ind>
</Atom>

Is the GreaterThanEqualTo operator broken?

Not at all! The reason is that facts expressed with Ind elements are typeless, i.e. they are pure string objects.

So when the comparison is done in the previous example, it is done in text mode: the strings "15" is compared to "7" and is found... smaller!

If you assert the Hour fact from your code or your binder, like this:

IEF.AssertNewFactOrFail("Hour", new object[]{myCall, myCall.Time.Hour});

then the engine will automatically cast to the type of the individual predicate, hence the "7" will be cast to Int32 and the comparison will work.

This is the "cast to strong type" mechanism that you can find in the code of the engine: any non-string type is considered strong and all individual values found in the rule base will be casted to match this type.

NB. It is possible to directly assert strong typed facts from a RuleML rule base using Data elements, as shown in the provided sample: typed-facts-0_9.ruleml


How slots can contribute named values and simplify implications

Support for slots has been introduced in NxBRE v2.5. Since the release of patch for bug 1470721 , NxBRE has gain the capacity to have slots contribute named values in an implication body (as requested by RFE 1483072 ).

This works for fixed value individuals like:

<Ind>min(150000,EUR)</Ind>

or dynamic ones like:

<Ind uri="nxbre://expression">{ind}.StartsWith("hello")</Ind>
<Ind uri="nxbre://operator">GreaterThan(25)</Ind>
<Ind uri="nxbre://binder">isStartingWith(hello)</Ind>

How it was before

Let's take a simple rule as an example: "If a measure has a value greater than 25, a warning showing the value must be emitted".

Our immediate approach would be to filter with an operator on the individual, like this:

<Implies>
  <Atom>
     <Rel>measure</Rel>
     <Ind uri="nxbre://operator">GreaterThan(25)</Ind>
   </Atom>
   <Atom>
      <Rel>warning</Rel>
      <Var>what can we possibly use here?</Var>
    </Atom>
</Implies>

And the answer to the question above is: nothing! The atom in the body part correctly selects matching facts but do not produce any named value that the head part could reference in a variable.

So, the classical workaround consisted in bringing all the facts and compare them to the threshold with a function-based atom:

<Implies>
   <And>
     <Atom>
       <Rel>measure</Rel>
       <Var>value</Var>
     </Atom>
     <Atom>
       <Rel uri="nxbre://operator">GreaterThan</Rel>
       <Var>value</Var>
       <Data xsi:type="xs:int">25</Data>
     </Atom>
   <And>
   <Atom>
      <Rel>warning</Rel>
      <Var>value</Var>
    </Atom>
</Implies>

This was functional but complex and inefficient.

How it is now

Using a slot to name the individual used for selecting the facts allows the following syntax:

<Implies>
  <Atom>
     <Rel>measure</Rel>
     <slot>
       <Ind>value</Ind>
       <Ind uri="nxbre://operator">GreaterThan(25)</Ind>
     </slot>
   </Atom>
   <Atom>
      <Rel>warning</Rel>
      <Var>value</Var>
    </Atom>
</Implies>

Neat and efficient!

It also works in expression

Sloted values are also available in expressions like variables:

<Implies>
    <Atom>
      <Rel>flight</Rel>
      <Var>customer</Var>
      <slot>
        <Ind>distance</Ind>
        <Ind uri="nxbre://operator">GreaterThan(999)</Ind>
      </slot>
    </Atom>
    <Atom>
      <Rel>miles</Rel>
      <Var>customer</Var>
      <Ind uri="nxbre://expression">{var:distance}/10</Ind>
    </Atom>
</Implies>

Note that variables supersede slots, i.e. if in the body part of an atom both a variable and a slot have the same name, the variable will contribute values instead of the slot. Of course, having variables or slots with the same name is a pretty bad idea...


Why implications with Naf atoms sometimes show some attitude?

Per definition, Naf atoms do not "produce" any predicate value, they are either positive or negative. If they are positive, it means nothing has been found matching, hence no predicate value can be generated.

This explains why the following implication (in Human-Readable Format) will never deduct any new fact:

   !Gender{?user,Female}
&  !Gender{?user,Male}
->  UnknownGender{?user}

None of the Gender atoms will produce a predicate value for the user variable, hence the variables of the deduction atom will never been fully resolved so no new atom will ever be created.

To solve this, a predicate-value producing atom must be added, as shown here after:

    Alive{?user}
   !Gender{?user,Female}
&  !Gender{?user,Male}
->  UnknownGender{?user}

Parsing query results

The results returned by queries run on the fact base of the Inference engine are pretty hard to parse, mainly because they are not evenly structured like a regular RDBMS-backed row set. This is because each result (equivalent to a row) can contain a variable number of facts (equivalent to a variable number of columns).

The following code fragment hopefully demonstrates how to make sense of a query result set:

public static void DumpResult(IList<IList<Fact>> qrs) {
    int i = 0;
    Console.WriteLine("-(Query Results) -");

    foreach(IList<Fact> facts in qrs) {
        i++;
        Console.WriteLine(" (Result #{0})", i);
        foreach(Fact fact in facts) Console.WriteLine("  {0}", fact);
    }

    Console.WriteLine("-(End Results)-\n");
}

Common Inference Engine exceptions explained

An unhandled exception of type 'org.nxbre.BREException' occurred in nxbre.dll Additional information: Can not locate the rulebase root.

This happens either because the rule file is not in the format the adapter is expecting or if the schema file pointed by the rule file (schema location) can not be found.

Error in !RuleBase XYZ.ruleml Element {{{http://www.ruleml.org/0.9/xsd:RuleML}}} not declared. Error in XYZ.ruleml (3,2)

Same cause as above.

An unhandled exception has occured Additional information: Index was outside the bounds of the array.

This usually happens when the body part of the implication has not returned all the variable values required by the head in order to build the a new fact. This problem is common when using Naf or expression-based relation atoms, which do not produce values at all.

Error in !RuleBase XYZ.ruleml The element '{{{http://www.ruleml.org/0.9/xsd:Data}}}' has xsi:type derivation blocked.

This usually happens when using RuleML 0.9 typed facts (Data elements) and is due to a broken support of XML types in .NET 1.1. It is solved by applying .NET 1.1 SP1 .


NxBRE DSL

NxBRE DSL, aka NxDSL, main goal is to allow rules authors to use their own natural language to write executable rules. Technically this feature is based on:

  • a language-specific ANTLR grammar that strictly enforces the structure of a rule file: this file is not supposed to be edited by the implementer,
    
  • a custom definition file that translates statements into RuleML atoms: this file must be created by the implementer to capture, with regular expressions, the natural language fragments and how they translate in RuleML atoms. 
    

In the following example, the words in bold are parsed by ANTLR while the other ones are matched by the regular expression from the definition file.

rule "Discount for regular products"
if
The customer is rated premium
and
The product is a regular product
then deduct
The discount on this product for the customer is 5.0 %

The terms in italic are captured by ANTLR and the regular expressions to get values for labels, implication actions and atom values. As you can see, the ANTLR grammar defines and enforces the structure of the rulebase, i.e. the skeleton of the rules, logical blocks and statements.

NxDSL comes with a grammar that allows using French terms for defining the rule structure, thus opening the door to consistently write the body of the rules in the same language, as shown hereafter:

règle "Remise pour les produits standard"
si
Le client est en catégorie premium
et
Le produit est de type standard
alors déduis
La remise pour ce produit pour ce client est de 5.0 %