In our ongoing series on JRules Patterns, Adam Smolnik suggested covering a pattern used to determine the minimum or maximum value in a class present in working memory. We then extend the pattern to show custom collectors and touch on some inference topics.

The domain object is extremely simple: an Order class with a single field, amount:

package test; public class Order { double amount; public Order(double amount) { this.amount = amount; } public double getAmount() { return amount; } }

This class is simply imported into the BOM and verbalized. You can then write a rule that will be fired when the minimum order amount is modified:

definitions set minOrder to an order ; if there is no order where the amount of this order is less than the amount of minOrder , then print "Min order:" + the amount of minOrder ;

The corresponding rule to find the maximum order amount is:

definitions set maxOrder to an order ; if there is no order where the amount of this order is more than the amount of maxOrder , then print "Max order:" + the amount of maxOrder ;

So far, so simple! One point to note is that the min/max rule will fire for **each** minimum/maximum value in working memory. So if you have the order amounts [-10,-10,0,20,30,30,30] the min rule will fire twice (-10) and the max rule will fire three times (30). You can control this behavior by setting the rule task exit criteria to "Rule Instance" if necessary.

You can test your rules by writing a function called ilrman(Object obj) with the body:

for( int n=0; n < 100; n++ ) { Order order1 = new Order(n); insert(order1); } execute();

You launch configuration should be set to call ilrmain at startup. The function simply creates some test orders, adds each to working memory and then calls execute on the rule engine.

You should see the output:

Min order:0.0 Max order:99.0

If you need to perform more advanced statistics on the items in working memory you can 'collect' the instances into a collector and then implement your operations using Java methods. For example, here is a collector than computes the mean order amount. First we implement the IlrCollection interface and add our statistical methods. You could specialise the IlrDefaultCollector class but in this case we implement the IlrCollection interface directly as it allows us to update the total amount of all orders in the collections each time an order is added, yielding better performance for the getMean method.

package test; import ilog.rules.engine.IlrCollection; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; /** * A specialized IlrDefaultCollector that can compute * the mean amount for a collection of Orders. */ public class OrderCollector implements IlrCollection, Serializable, Iterable, Collection { public List backing = new ArrayList (); private double amount = 0; /** * */ private static final long serialVersionUID = -8643410752931284658L; /** * @return the mean of the orders */ public double getMean() { if( backing.size() > 0 ) { return amount / backing.size(); } else { return 0; } } @Override public boolean add(Order e) { amount += e.amount; return backing.add(e); } @Override public boolean addAll(Collection c) { boolean result = true; for (Order order : c) { add(order); } return result; } @Override public void clear() { backing.clear(); amount = 0; } @Override public boolean contains(Object o) { return backing.contains(o); } @Override public boolean containsAll(Collection c) { return backing.containsAll(c); } @Override public boolean isEmpty() { return backing.isEmpty(); } @Override public boolean remove(Object o) { amount -= ((Order) o).amount; return backing.remove(o); } @Override public boolean removeAll(Collection c) { boolean result = false; for (Object object : c) { result |= remove(object); } return result; } @SuppressWarnings("unchecked") @Override public boolean retainAll(Collection c) { clear(); return addAll((Collection) c); } @Override public int size() { return backing.size(); } @Override public Object[] toArray() { return backing.toArray(); } @Override public T[] toArray(T[] a) { return backing.toArray(a); } @Override public Iterator iterator() { return backing.iterator(); } @Override public void addElement(Object arg0) { add((Order) arg0); } @Override public void removeElement(Object arg0) { remove(arg0); } @Override public void updateElement(Object arg0) { double amount = 0d; Iterator it = backing.iterator(); while (it.hasNext()) { Order order = it.next(); amount += order.getAmount(); } } }

After creating the OrderCollector Java class update your BOM entry to import it into the BOM. You can then create a technical rule as follows:

when { orders: collect (new OrderCollector()) Order() where (size()>0 ); } then { ilog.rules.brl.System.printMessage( "Mean order: " + orders.mean); }

This rule is collecting all orders using the OrderCollector and then calling the getMean method if there are any orders.

If you run the 3 rules together you should see:

Min order:0.0 Max order:99.0 Mean order: 49.5

Now we can have some more fun! Let's create a rule that adds negative orders to working memory for as long as the mean order amount is greater than a threshold value:

when { orders: collect (new OrderCollector()) Order() where (size()>0 && orders.mean > 5 ); } then { insert new Order( -orders.mean * orders.size() / 2.0 ); }

Running all four rules together should print:

Min order:0.0 Max order:99.0 Mean order: 49.5 Min order:-2475.0 Mean order: 24.504950495049506 Mean order: 12.132352941176471 Mean order: 6.007281553398058 Mean order: 2.9747596153846154

Here we can see the min/max rules initially be fired as before for the values [0..99] and then the mean is calculated as 49.5 by our rule that prints the mean. Then the first instance of the rule to lower the mean fires and we insert an order of -2475, which causes the min rule to fire as -2475 < 0. We keep inserting negative orders and each insert causes the mean to be updated and printed. The cascade of updates to the min/max and mean rely on JRules' inference capabilities and the underlying RetePlus algorithm.

I suggest you play with the code to get a feel for some of the cool tricks your can play with inference. Download the projects for the article here.