Jess Information

Jess Home
Jess 7 Features
Download Now!
Online Demo

Documentation
FAQ
Manual
Mailing List
Jess Wiki

More information Related Web Sites
User Contributions
JSR94 Info
Developer's Log
About This Site

JESS ®, the Rule Engine for the JavaTM Platform

Jess Wiki: Property Set

Author

Wolfgang Laun
Wolfgang Laun a-t thalesgroup com

Name: Property Set

Intent: Representation of property sets

Motivation:

Occasionally you are confronted with the problem of representing facts that have an attribute which can be an arbitrary combination of elementary properties where just the presence or absence of that elementary property matters. In other words, the value of an attribute is a set of elements.

Thinking about facts in Jess, there are several ways for representing property sets, with the major design decision being whether to put the set into a single slot, or to use an approach where the presence of an attribute element for some entity is a fact of its own.

Using a single multislot for representing a set has the disadvantage that a multislot alone doesn't provide the mechanisms required for the elementary set operations. (Even the simple process of adding another element to the set requires some programming to avoid duplicates.)

The option of using some implementation of the java.util.Set interface provides you with all the basic methods. But then you'll have to rely exclusively on them while writing the pattern matching tests and the slot update operations.

Applicability:

You may use this pattern wherever sets (in the mathematical sense of the word) of some elements need to be represented in Jess' memory and where LHS rule terms contain set element tests.

Sample code:

Here we'll present the basic structure and method for representing attribute sets as facts. While the template structure requires a little extra work, the rules and update operations are quite simple.

(deftemplate Elem 
  "Represents some entity with a unique id."
  (slot id))

(deftemplate Prop
  "Represents the association of a property with an entity."
  (slot id)
  (slot prop))

This simple fact structure results in a straightforward rule to find entities that have some property in common:

(defrule match
  "Find some property p and any e != f from Elem, so that
  p is contained in both property sets, e.P and f.P"
  (Prop (id ?eid)         (prop ?p))
  (Prop (id ?fid & ~ ?eid)(prop ?p))
=>
  (printout t "e.id=" ?eid " f.id=" ?fid " share " ?p crlf)
)

In addition to the basic templates we'll use one for defining operations.

(deftemplate Op
  "Defines an operation combining two entities according to the
   operation code to create a new entity."
  (slot code)
  (slot id1)
  (slot id2)
  (slot id12))

The rule for a union of properties is pleasingly simple.

(defrule union
  "Creates Prop facts for an entity with the id from Op's id12
  one for each property id1 or id2 or both have."
  (Op (code or)(id1 ?id1)(id2 ?id2)(id12 ?id12))
  (Prop (id ?id & ?id1 | ?id2)(prop ?p))
=>
  (assert (Prop (id ?id12)(prop ?p)))
)
Given some Op fact containing two id values, id1 and id2, the rule matches Prop facts with one of the given ids. The value of the prop slot is one that must go into the newly created Prop fact. But what about those properties that both id1 and id2 have in common? Won't they appear twice? The answer is no, because Jess won't duplicate any fact - which is exactly right for set elements. (You might think of Jess' working memory as one big set.)

The rule determining common properties is quite simple, too:

(defrule intersection
  "Creates Prop facts for an entity with the id from Op's id12
  one for each property the entities id1 and id2 have in common."
  (Op (code and)(id1 ?id1)(id2 ?id2)(id12 ?id12))
  (Prop (id ?id1)(prop ?p))
  (Prop (id ?id2)(prop ?p))
=>
  (assert (Prop (id ?id12)(prop ?p)))
)

The rule matches a pair of Prop facts with the given ids and the additional constraint that the property is the same. So, the assert happens once for each property id1 and id2 have in common.

The rule for the set difference requires only a minor adjustment:

(defrule difference
  "Creates Prop facts for an entity with the id from Op's id12
  one for each property id1 has but id2 does not have."
  (Op (code diff)(id1 ?id1)(id2 ?id2)(id12 ?id12))
       (Prop (id ?id1)(prop ?p))
  (not (Prop (id ?id2)(prop ?p)))
=>
  (assert (Prop (id ?id12)(prop ?p)))
)

Now we find all Prop facts for id1, and the negated conditional element ensures that there is no Prop entry with the same prop slot value for entity id2.

Let's conclude with the symmetric difference. As the symmetric difference of A and B is the union of the differences A-B and B-A we can write the rule according to this identity.

(defrule symmetricDifference
  "Creates Prop facts for an entity with the id from Op's id12
  one for each property id1 or id2 (but not both) have."
  (Op (code xor)(id1 ?id1)(id2 ?id2)(id12 ?id12))
  (or (and      (Prop (id ?id1)(prop ?p))     ; OK, but see below
           (not (Prop (id ?id2)(prop ?p))))
      (and      (Prop (id ?id2)(prop ?p))
           (not (Prop (id ?id1)(prop ?p)))))
=>
  (assert (Prop (id ?id12)(prop ?p)))
)

It's fairly obvious that we have duplicated the conditional elements from the previous rule (exchanging id1 and id2), made the implied "and" operation explicit and joined the two conjunctions by the "or" operator into a top-level conditional element.

But this doesn't look as if it were the simplest possible solution. Shouldn't it be feasible to reduce the number of conditional elements by doing the "or" operation alongside with the basic match? Remembering the formula we had in the "union" rule, we can write this:

(defrule symmetricDifference
  "Creates Prop facts for an entity with the id from Op's id12
  one for each property id1 or id2 (but not both) have."
  (Op (code xor)(id1 ?id1)(id2 ?id2)(id12 ?id12))
       (Prop (id ?ida | ?id1          | ?id2          )(prop ?p))
  (not (Prop (id ?idb | ?id1 & ~ ?ida | ?id2 & ~ ?ida )(prop ?p)))
=>
  (assert (Prop (id ?id12)(prop ?p)))
)

The first conditional element achieves a match with some Prop fact with an id that is either id1 or id2. Notice that binding this fact's id slot value to variable ?ida keeps track of the id that's actually there. The negated conditional element needs this actual value so that it can be excluded from matching.


Front Page | Sandbox | Recent Changes | Powered by Friki | Last Edited: 13 January 2008