Sequential algorithm

Unlike the RetePlus algorithm, the sequential execution mode does not provide inference. The sequential algorithm works in a predictable way only when rules are homogeneous, that is when they are using the same bindings.

To guarantee the same execution with either algorithm, both the following conditions are required:

For the rulesets that can benefit from sequential processing mode, the sequential execution mode utilizes a bytecode generation that can significantly improve the speed of rule processing. If you specify the sequential execution mode for rules that are not compliant with it, the engine raises an error.

The use of sequential execution mode is described further in the sections:

Tuples

A tuple is a list of objects that complies with a particular structure. A tuple structure is simply a sequence of class descriptors. Each location in a tuple structure is called a slot. The same class descriptor can appear more than once at different slots.

Take a tuple structure:

(Customer,Product)

Here is a tuple that complies with this structure:

(new Customer("Henry"),new DVD("Mickey"))

Note that the subclassing is taken into account, since a DVD is a Product. However, some tuples that do not comply with the structure:

For example, the following tuple is too large:

(new Customer("Henry"),new CD("Madona"),new DVD("Mickey"))

This tuple is not correctly ordered:

(new DVD("Lord of the rings"),new Customer("Henry"))

And this tuple is too small:

(new Customer("Henry"))

In Java™, a tuple is easily implemented as an array of objects:

Java Tuple = Object[]

The Java code to create a tuple and its objects is:

new Object[] {new Customer("Henry"),new DVD("Mickey")}

At run time, the tuples are built either by hand or automatically from the content of the working memory and then passed to the rule engine. The tuples are passed one after the other to the tuple-matching method.

A tuple does not necessarily have to be complete. A partial tuple is a tuple with unused slots. Unused slots are simply set to null. For instance, the tuple(new Customer("Henry")) that was too small could be coded as (new Customer("Henry"),null) in order to fulfill the tuple structure.

Rule selection in sequential mode

The rules of a rule task that are set with the property algorithm equal to sequential are dynamically translated to Java bytecode. The bytecode is capable of performing sequential processing when the task is executed.

The body property of a rule task is mandatory. It selects the rules of the ruleset that will be taken into account by the rule task.

See also Runtime rule selection.

Control properties in sequential mode

Control properties affect the sequential mode execution and the tuple structure. See Control properties for rule tasks in execution modes for more general information.

The number of rules to be executed per tuple and the order in which they execute can be specified with control properties: ordering, firing, and firinglimit.

The ordering property

The ordering property is mandatory for sequential processing. It describes the order by which the rules of the rule task will be executed.

There are two values available:

Note that another value, dynamic, is available only for rule tasks using the RetePlus execution mode.

The firing property

The firing property specifies whether, for each tuple, all the rules or only the first rule that applies should be executed. This property is optional.

It has two possible values:

The firinglimit property

The firinglimit property gives another level of control when the firing property is set to allrules. It is used to specify a number that represents the maximum number of rules to be executed before skipping to the next tuple. This number should be greater than zero.

Known limitations of the sequential algorithm

In almost all cases there is no limitation in the number of rules handled by a single task which has an algorithm property equal to sequential. The only exception is with extremely large decision tables with an otherwise. The processing is as follows: All the conditions of all the lines are negated in a single expression. This single expression becomes the body of a single method once translated to bytecode. This is an issue since the Java verifier rejects methods with too big a body. The scalability algorithm used for the rules does not go down to the method body. The user will be notified of this limitation when the class loader rejects the rules. The limit in the number of rules is not clear-cut, but the number is likely to be several thousands. The limitation also affects a ruleset that uses dynamic rule compilation, see Activating dynamic rule compilation.

The following table outlines the limitations of sequential processing, in particular its use combined with certain ILOG® Rule Language (IRL) constructs.

Limitation Description
ILOG Rule Language (IRL) limitations

Not all the rule patterns that have been designed for the stateful Rete matching are available. Compile time errors occur when a rule is not compliant with the current tuple-matching implementation.

Therefore, when sequential processing is used, the IRL does not support the following features:

  • dynamic priorities

    Because there is no agenda in sequential processing, only the rules with static priorities are allowed.

  • not, exists, collect conditions without an enumerator

    The not, exists, and collect conditions with an enumerator (from or in) are translated to Java bytecode. When an enumerator is specified, the set of objects is available as a value tied to the condition. (Note that this is not a limitation for RetePlus or Fastpath.)

Condition constructs

The following condition constructs are available in sequential processing:

  • simple condition—bindings and tests are available

  • from

  • in

Exception handling

Exception handling is not supported in rule condition parts in sequential and Fastpath modes.

For example, if you specify an exception handler on a context, it is not taken into account in sequential and Fastpath modes:

IlrContext mycontext="....";
IlrExceptionHandler myhandler="....";
mycontext.setExceptionHandler(myhandler);
Refraction

The data unit for an engine cycle is the tuple. The engine does not record the tuples as they are matched, it rather forgets them between cycles. Therefore, there is no built-in support for refraction.

Using the sequential execution mode, the above rule task becomes:

ruletask main {
   algorithm = sequential;
   ordering = literal;
   body = {Person,PersonProduct}
}

The computed tuple structure will be:

(Person,Product)

The generated tuple matching code will be:

void matchObjects(Object[] tuple) {
   rule_Person((Person)tuple[0]);
   rule_PersonProduct((Person)tuple[0],(Product)tuple[1]);
}

The main code remains the same:

  1. context.insert(new Person("Henry"));

  2. context.insert(new CD("Madona"));

  3. context.insert(new DVD("Mickey"));

  4. context.execute();

The working memory iterator loop will be:

  1. matcher.matchObjects((Person("Henry"),CD("Madona")));

  2. matcher.matchObjects((Person("Henry"),DVD("Mickey")));

The execution trace will be:

  1. // matcher.matchObjects((Person("Henry"),CD("Madona")));

  2. Person(Henry)

  3. PersonProduct(Henry,Madona)

  4. // matcher.matchObjects((Person("Henry"),DVD("Mickey")));

  5. Person(Henry)

  6. PersonProduct(Henry,Mickey)

The execution trace shows that, using the sequential execution mode, the same rule will be executed twice for the same parts of two different tuples.

ILOG Rule Language (IRL) facilities

In contrast, practically all the script-level expressions and statements of the IRL are available in sequential processing. They serve principally to maintain the consistency of the other rule tasks that are not set to the sequential algorithm.

Here is a list of these features:

Functions

Function definitions and calls are fully available.

Script-level expressions

All the IRL expressions are available. (Note that ?instance works in any execution mode.)

Script-level statements

All the IRL statements are available.

Update

The update construct is available since spurious updates may exist in rule tasks set to the sequential algorithm. A spurious update is an update on an object that cannot be matched by any condition part of rule tasks set to the sequential algorithm, but may be significant for other rule tasks using the RetePlus algorithm. All rule tasks are specified in the ruleset and should be kept consistent with the current engine. However, it is important to note that the usual update is not handled at all by the stateless sequential processing algorithm.

Update refresh

The refresh mode is not handled at all by the sequential processing algorithm, but it might be relevant for other rule tasks using the RetePlus algorithm.

Insert

Insertion of objects that could match condition parts of rule tasks set to the sequential algorithm may cause inconsistencies when the tuples of objects are extracted from working memory. This is because the objects are inserted in the first position, and the iterator building the tuples will not be notified. The iterator will not, therefore, give the tuples involving the new object to the sequential processing engine. However, the insert will still be relevant for other rule tasks using the RetePlus algorithm.

The following examples show cases that cause Just-In-Time (JIT) compilation errors, that is, an error at execution time as opposed to a parsing error.

Example 1: IRL limitation causing compilation error

This example results in JIT compilation errors due to missing enumerators.

ruletask T {
  algorithm = sequential;
  body = { R1, R2, R3, R4, R5, R6, R7, R8, R9 }
}
rule R1 {
  when {
    not MyObject();  // No enumerator, an error will occur
  }
  then {
  }
}
rule R2 {
  when {
    exists MyObject();  // No enumerator, an error will occur
  }
  then {
  }
}
rule R3 {
  when {
    collect MyObject() where (size() == 1);  // No enumerator, an error
                                                will occur
  }
  then {
  }
}
function Object getObject() {
  return new MyObject();
}
function Object[] getObjects() {
  Object objs = new Object[1];
  objs[0] = getObject();
  return objs;
}
rule R4 {
  when {
    not MyObject() from getObject();  // An enumerator, no error
  }
  then {
  }
} 
rule R5 {
  when {
    not MyObject() in getObjects();  // An enumerator, no error
  }
  then {
  }
} 
rule R6 {
  when {
    exists MyObject() from getObject();  // An enumerator, no error
  }
  then {
  }
} 
rule R7 {
  when {
    exists MyObject() in getObjects();  // An enumerator, no error
  }
  then {
  }
} 
rule R8 {
  when {
    collect MyObject() from getObject() where (size() == 1); 
    // An enumerator, no error
  }
  then {
  }
} 
rule R9 {
  when {
    collect MyObject() in getObjects() where (size() == 1);  
    // An enumerator, no error
  }
  then {
  }
}

Example 2: IRL showing construct limitations

Here is an example with a collect construct to demonstrate the scope of the bindings. This holds for not and exists too. This example also results in JIT compilation errors due to the use of unsupported constructs.

rule R10 {
  when {
    r:MyRootObject();
    c:collect MyObject(n:name; n.startsWith("ilog")) in r.getObjects(); 
    where (s:size(); s > 1);
  }
  then {
    out.println(r);  // correct
    out.println(c);  // correct
    out.println(s);  // correct
    out.println(n);  // error
  }
}