String comparison algorithms are plentiful in computer science. You have Boyer-Moore, Knuth-Morris-Pratt,Rabin-Karp, and even the old-fashioned manual comparisons. Expert systems also require extensive pattern matching during their execution, but it is a special case of pattern matching. The Rete matching algorithm was invented to speed up that pattern-matching process.

One class of expert systems is known as production systems, which contain a database of production rules that govern behavior. Production systems such as OPS5 are straightforward; see Figure 1.

[Click image to view at full size]

Everything is orchestrated by the interpreter. A set of rules sits in production memory, and a set of facts, or assertions, sits in a database called data memory. First a pattern matcher looks at both memories to see which rules have their conditions satisfied by the facts. After the matcher generates a list of rules whose conditions have been satisfied, called the conflict set, it calls sort of conflict-resolution system.

This system looks at everything in the conflict set and, according to some algorithm, determines which particular rule will fire. The rule makes some changes in either data memory or production memory, and the cycle starts again.

Matching algorithms compare all the data elements in data memory with all the rules in production memory to determine which rules have their conditions satisfied and should be put in the conflict set and sent to the conflict resolver. For example, an expert system for a detective might have the following rules in production memory:

If a person x did something illegal, then person x is a criminal.

If fingerprints belonging to person x are on object y, then person x touched y at some point in the past.

If person x shot person y, then person x did something illegal.

If person x is dead, then person x should not be invited over for dinner.

And it might have the following data element in data memory:

Person Fred shot person Sam.

Person Sam is dead.

The Rete matching algorithm product is a conflict set containing two rules: If person x shot person y, then person x did something illegal, and If person x is dead, then person x should not be invited over for dinner.

All this takes a lot of pattern matching. Each time the inference engine cycles, the pattern matcher must compare the data stored in data memory with the rules stored in production memory to see which rules had all their conditions satisfied. This comparison process can be very lengthy if the production system has hundreds of data items and thousands of rules, as many real systems do.

Charles Forgy, inventor of the OPS5 rule-based language, devised the Rete algorithm in the late 1970s to speed up the comparisons. He found that old systems spent as much as 90% of their performing pattern matching. They would iterate through the process, taking each rule in turn and looking through data memory to see it the conditions for the rule were satisfied and then proceeding to the next rule. Some people found ways to index data elements and rule conditions. This speeds up program executing but still requires iterating through the series of rules and data elements. The Rete algorithms eliminates the iterative step and hence is a substantial improvement over competing algorithms.

The Rete matching algorithms avoids iterating through the data elements by storing the current contents of the conflict set in memory, only adding and deleting items form it as data elements are added and deleted from memory.

For example, assume that the expert system for a detective has the following rule in its conflict set: If someone is did charging a firearm at me, then duck. This means that somewhere in data memory is the fact that someone is discharging a firearm at me.

The conflict set might contain other rules: If someone is discharging a firearm, then he is committing an illegal act; if someone is wearing a symbol of an inverted badger, then he is a member of the Dreaded Legion of Inverted Badgers, and so on. When it comes time to update memory, either as a result of new input or of as a result of rule firing, the Rete algorithm would update the conflict set. Let's say the expert system decided to have our hero duck (a prudent move). That would delete the fact a someone is discharging a firearm at our hero; hence the rule, If someone is discharging a firearm, then he is committing an illegal act, would not be deleted from the conflict set.

In the other hand, the conflict resolver might decide to fire a different rule. As a result, this might add the fact that the person shooting him is a member of the Dreaded Legion of Inverted Badgers. Another rule might have its conditions satisfied, and it would be added to the conflict set. For example: If a member of the Dreaded Legion of Inverted Badgers discharges a firearm at me, then it is futile to duck because the firearm is equipped with special Inverted Badger Homing Bullets...and so on.

The point of all this is that the interpreter doesn't have to iterate through the entirety of data memory to see if a given pattern matches any of the data memory elements. A list of elements that match is stored with each pattern. When a new element is added to data memory, then the interpreter finds all the match it can and adds it to their lists. When an element is deleted from data memory, the interpreter finds all the patterns that matched it and deletes them from the lists. The pattern matching algorithm never has to look at data memory, since the algorithm keeps a record of how data memory affects the rules and the conflict set.

This alone would still force the pattern matching to iterate through production memory, looking for rules that must be updated because of changes in data memory. However, there is another half that iteration as well.

The Rete algorithm stores the conditions of all the production rules in a tree-structured sorting network. The Rete algorithm complies the network from the list of production rules and then keeps the network current as rules are added and deleted from the lists.

To see how that works, let's look at some rules in a candy company's expert system. Two production rules in that system are:

Rule Red__Round__Ones: If the goal is to identify a piece of candy, and if the candy sample is red, and if candy sample is round, then hypothesize it is a jellybean.

Rule Red__Cylindrical__Ones: If the goal is to identify a piece of candy, and if the candy sample is red, and if the candy sample is cylindrical, then hypothesize it is a licorice stick.

In OPS5 talk:

(P Red__Round__Ones (Goal | Type Identify | Object {N}) (Candy | Name {N} | Color Red | Shape Round | Company {X}) -> then clause, which is not important in this context) -> (P Red__Cylindrical__Ones (Goal | Type Identify? Object {N}) (Candy | Name{N} | Color Red | Shape Cylindrical | Company {X}) -> then clauses

OPS5 notation is easy to understand. P is the symbol for the production rule; it is followed by the name of the rule. The two lines after the name are the two elements that make up the statement that consists of class, of type, of element, and a list of attributes. The symbol | is OPS5's way of distinguishing attributes (Shape, for example) from values (Cylindrical, for example). Data memory might look as follows:

(Goal | Type identify | Object Sample7) (Candy | Name Sample7 | Color Red | Shape Round | Company FerraraPan)

To determine if the conditions for the first rule were satisfied, the pattern matcher would first check if there were a data element in data memory of class Goal, with the Type attribute of Identify and some Object variable. Then it would check of there were some data element of class Candy, with some Name variable, a Color attribute of Red, a Shape attribute of Round, and a Company attribute of some unknown variable. Finally, it would compare the value of the Object variable of the Goal with the value of the Name variable of the Candy. If all those subconditions were satisfied, the pattern matcher would add the rule Red__Round__Ones to the conflict set.

Then, if it were a conventional iterative pattern matcher, it would step down to the rule Red__Cylindrical__Ones and perform almost the exact same steps. Those two production rules are almost identical.

Rete's tree-structured sorting network takes advantages of these redundancies. The pattern complier builds a network of individual subconditions. It first looks at each element of a production rule individually, building a chain of nodes that tests for each attribute individually. Then it looks at comparisons between elements (comparing the Object variable of the Goal with the Name variable of the Candy, for example) and connects chains with new nodes. Finally, terminator nodes are added to signal that all the conditions for the production rule have been satisfied. The tree structure for Red__Round__Ones is shown in Figure 2.

[Click image to view at full size]

Additional production rules are grafted onto the same network. If they have no test in common, they don't interact at all.

How does all this work? The root node starts the process. Whenever a new element is added to data memory, it distributes it down the tree. Single-input nodes do their tests. If the element passes the test, then the node passes it downstream; if the element fails the test, then the node drops it into the bit bucket. Two-input nodes behave the same way, except that if they get only one input, they save it until the second input comes along. If, sometime later in the expert system's churning, the second element entered data memory, the pattern matcher would not have to process the first element all over again. It would just process the new pre-existing input would perform the test and either send the elements downstream or save them for next attempt.

Let's watch this in action. Assume data memory starts empty and then has the following data element added: (Goals | Type Identify | Object Sample7). The root distributes the element. It is not a Candy, so the right tree stops there. It is a Goal, so the left node passes it down. The value of the Type is Identify, so it passes down yet again to two different nodes. These nodes, both two input nodes, receive the element. They have only one of their two inputs satisfied, so they both save the fact that their Goal element was satisfied with Object equal to Sample7.

Sometime later in the program's execution, the following data element is added to data memory: ( Candy | Name Sample8 | Color Red | Shape Round | Company FerraraPan). Now it is processed through the network. It is not a Goal, but it is a Candy. The value of Color is Red. Now it is passed to two different nodes. the value of Shape is not cylindrical, but it is Round. Now one if the joins has both to its inputs satisfied (remember, it has saving the Goal element), so it looks for cases where the Name of the Candy equals the Object of the Goal. There are none, so it saves both elements. The other join doesn't do anything.

Even later, a third data element appears in data memory: (Goal | Type Identify | Object Sample8). It is processed like the previous Goal where the Object equals Sample8 as well as whose Name equals Sample8. That fact is sent downstream: the final node indicates that all the conditions for the rule Red__Round__Ones have been satisfied.

Deleting data elements from the network is similar. The element is processed the same way and deleted from any two-input node in which it is stored. Thus the network is kept current and constantly mirrors the contents of data memory.

The Rete algorithm has applications in all expert system applications that involve these production rules. It is especially suited for this many object/many pattern matching problem, in which many data memory elements are compared with many production memory rules. The only caveat is that the elements must be relatively consistent between iterations. Since the Rete algorithm maintains itself between iterations, applications in which most of the data changes between iterations will be slowed down with this algorithm.

What the Rete algorithm basically says is that production systems are best implemented as a series of sophisticated tree walks that everything else is nothing more than syntactic sugar. Considerable mental leverage can be gained in thinking in terms of rules and conditions, but when it comes time for implementation, that should all be thrown away. The Rete algorithm does not claim to make any conceptual changes to the production system model--it is simply a way to implement that model efficiently on a computer.

Suggested Reading

Brownston, L., R. Farrell, E. Kant, and N. Martin Programming Expert Systems in OPS5, Reading, Mass.: Addison Wesley, 1985.

Forgy, Charles L. On the Efficient Implementation of Production Systems, Ph.D. Thesis, Carnegie-Mellon University, 1979.

Forgy, Charles L. "Rete: A Fast Algorithm for the Many Pattern/Many Object Match Problem," Artificial Intelligence, (19)1, Sept. 1982, pp. 17-37.

— Bruce Schneier has a B.S. in physics and an M.S. in computer science. He is the president of Counterpane Systems, a computer consulting firm.

Source: AI Expert, December 1992

