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:
The rules must be homogeneous. Homogeneous rules mean that the conditions of those rules must be set on the same kind and number of objects. If you try to apply the sequential algorithm to heterogeneous rules, it creates more tuple objects and this can result in more rules being executed than in the RetePlus mode.
The rules must not be chained, that is, the modification done in the action part of a rule must not lead to execution of another rule.
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:
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.
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 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 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:
literal: The rules are kept in the order in which they are set into the task body (using up/down arrows).
sorted: The rules are sorted according to their static priorities.
Note that another value, dynamic, is available only for rule tasks using the RetePlus execution mode.
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:
allrules
This value means that all the rules that apply should be executed before skipping to the next tuple. This is the default value.
rule
This value means that only the first rule that applies should be executed on the first tuple.
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.
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:
|
Condition constructs | The following condition constructs are available in sequential processing:
|
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:
|
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:
The computed tuple structure will be:
The generated tuple matching code will be:
The main code remains the same:
The working memory iterator loop will be:
The execution trace will be:
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:
|
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
}
}