accumulate still returns objects, as we don't allow patterns against
primitives. maybe for special use cases we can allot $i : int() from
accumulate (......) in the future.
Btw did you try the sequential engine mode in your stateless approach?
Mark
Edson Tirelli wrote:
Geoffrey,
The "sum" function that is shipped with drools always uses double
internally, but all numeric accumulate functions must have return type
Number to avoid class cast exceptions... I must write that in the
docs... so, recommendation is to do:
Number( $total : intValue ) from accumulate(
Hop($distance : distance ), // distance is an int
sum($distance)
);
Now, having said that, the decision to use double is because we
needed a one size fits all to ship with drools. On the other hand I
tried to make as simple as possible to plug new or replace built-in
functions. So, if you want a sum function that uses only integers,
you can easily develop and plug it your own. Since I just wrote the
docs on how to do it, may I ask you please to read them and provide a
feedback if they are good, need more info, shall I write them in
another way, etc?
BTW, when I say it is really simple to create another sum
function, I mean, you can do it in about 20 minutes. Really!
Find attached the HTML doc page I'm talking about.
[]s
Edson
2007/7/22, Geoffrey De Smet < ge0ffrey.spam(a)gmail.com
<mailto:ge0ffrey.spam@gmail.com>>:
Hi guys,
I finally got around to experimenting with the accumulate support in
LocalSearchSolver. Thanks for implementing it :)
I use nothing but int's in my calculations,
but I get a ClassCastExceptions, as sum() always returns a Double and
setSoftConstraintsBroken(int) failes.
In a previous benchmarks I 've proven that summing integers in
double's
instead of int's hurts performance for 10% or more (which is
rather big).
Is there a sumInteger() available (or do I need to write it)? Or
can it
be overloaded over sum() in a much cleaner way?
rule "ScoreCalculator"
when
$scoreFact : ScoreFact(); // singleton
$total : Integer() from accumulate(
Hop($distance : distance ), // distance is an int
sum($distance)
);
then
$scoreFact.setSoftConstraintsBroken($total);
end
svn is still here, till it's ready for drools:
https://taseree.svn.sourceforge.net/svnroot/taseree/trunk
--
With kind regards,
Geoffrey De Smet
_______________________________________________
rules-dev mailing list
rules-dev(a)lists.jboss.org <mailto:rules-dev@lists.jboss.org>
https://lists.jboss.org/mailman/listinfo/rules-dev
--
Edson Tirelli
Software Engineer - JBoss Rules Core Developer
Office: +55 11 3529-6000
Mobile: +55 11 9287-5646
JBoss, a division of Red Hat @
www.jboss.com <
http://www.jboss.com>
------------------------------------------------------------------------
3.6. Advanced Conditional Elements
Prev <ch03s05.html> Chapter 3. The Rule Language Next <ch03s07.html>
------------------------------------------------------------------------
3.6. Advanced Conditional Elements
Note
/|(updated to Drools 4.0)|/
Drools 4.0 introduces a whole new set of conditional elements in order
to support full First Order Logic expressiveness, as well as some
facilities for handling collections of facts. This section will detail
the following new Conditional Elements:
*
from
*
collect
*
accumulate
*
forall
3.6.1. From
The *from* Conditional Element allows users to specify a source for
patterns to reason over. This allows the engine to reason over data
not in the Working Memory. This could be a sub-field on a bound
variable or the results of a method call. It is a powerful
construction that allows out of the box integration with other
application components and frameworks. One common example is the
integration with data retrieved on-demand from databases using
hibernate named queries.
The expression used to define the object source is any expression that
follows regular MVEL syntax. I.e., it allows you to easily use object
property navigation, execute method calls and access maps and
collections elements.
Here is a simple example of reasoning and binding on another pattern
sub-field:
rule "validate zipcode"
when
Person( $personAddress : address )
Address( zipcode == "23920W") from $personAddress
then
# zip code is ok
end
With all the flexibility from the new expressiveness in the Drools
engine you can slice and dice this problem many ways. This is the same
but shows how you can use a graph notation with the 'from':
rule "validate zipcode"
when
$p : Person( )
$a : Address( zipcode == "23920W") from $p.address
then
# zip code is ok
end
Previous examples were reasoning over a single pattern. The *from* CE
also support object sources that return a collection of objects. In
that case, *from* will iterate over all objects in the collection and
try to match each of them individually. For instance, if we want a
rule that applies 10% discount to each item in an order, we could do:
rule "apply 10% discount to all items over US$ 100,00 in an order"
when
$order : Order()
$item : OrderItem( value > 100 ) from $order.items
then
# apply discount to $item
end
The above example will cause the rule to fire once for each item whose
value is greater than 100 for each given order.
The next example shows how we can reason over the results of a
hibernate query. The Restaurant pattern will reason over and bind with
each result in turn:
3.6.2. Collect
The *collect* Conditional Element allows rules to reason over
collection of objects collected from the given source or from the
working memory. A simple example:
import java.util.ArrayList
rule "Raise priority if system has more than 3 pending alarms"
when
$system : System()
$alarms : ArrayList( size >= 3 )
from collect( Alarm( system == $system, status == 'pending' ) )
then
# Raise priority, because system $system has
# 3 or more alarms pending. The pending alarms
# are $alarms.
end
In the above example, the rule will look for all pending alarms in the
working memory for each given system and group them in ArrayLists. If
3 or more alarms are found for a given system, the rule will fire.
The *collect* CE result pattern can be any concrete class that
implements tha java.util.Collection interface and provides a default
no-arg public constructor. I.e., you can use default java collections
like ArrayList, LinkedList, HashSet, etc, or your own class, as long
as it implements the java.util.Collection interface and provide a
default no-arg public constructor.
Both source and result patterns can be constrained as any other pattern.
Variables bound before the *collect* CE are in the scope of both
source and result patterns and as so, you can use them to constrain
both your source and result patterns. Although, the /collect( ... )/
is a scope delimiter for bindings, meaning that any binding made
inside of it, is not available for use outside of it.
Collect accepts nested *from* elements, so the following example is a
valid use of *collect*:
import java.util.LinkedList;
rule "Send a message to all mothers"
when
$town : Town( name == 'Paris' )
$mothers : LinkedList()
from collect( Person( gender == 'F', children > 0 )
from $town.getPeople()
)
then
# send a message to all mothers
end
3.6.3. Accumulate
The *accumulate* Conditional Element is a more flexible and powerful
form of *collect* Conditional Element, in the sense that it can be
used to do what *collect* CE does and also do things that *collect* CE
is not capable to do. Basically what it does is it allows a rule to
iterate over a collection of objects, executing custom actions for
each of the elements, and at the end return a result object.
The general syntax of the *accumulate* CE is:
/|<result pattern>|/ from accumulate( /|<source pattern>|/,
init( /|<init code>|/ ),
action( /|<action code>|/ ),
reverse( /|<reverse code>|/ ),
result( /|<result expression>|/ ) )
The meaning of each of the elements is the following:
*
*<source pattern>*: the source pattern is a regular pattern that
the engine will try to match against each of the source objects.
*
*<init code>*: this is a semantic block of code in the selected
dialect that will be executed once for each tuple, before
iterating over the source objects.
*
*<action code>*: this is a semantic block of code in the
selected dialect that will be executed for each of the source
objects.
*
*<reverse code>*: this is an optional semantic block of code in
the selected dialect that if present will be executed for each
source object that no longer matches the source pattern. The
objective of this code block is to "undo" any calculation done
in the <action code> block, so that the engine can do
decremental calculation when a source object is modified or
retracted, hugely improving performance of these operations.
*
*<result expression>*: this is a semantic expression in the
selected dialect that is executed after all source objects are
iterated.
*
*<result pattern>*: this is a regular pattern that the engine
tries to match against the object returned from the <result
expression>. If it matches, the *accumulate* conditional element
evaluates to *true* and the engine proceeds with the evaluation
of the next CE in the rule. If it does not matches, the
*accumulate* CE evaluates to *false* and the engine stops
evaluating CEs for that rule.
It is easier to understand if we look at an example:
rule "Apply 10% discount to orders over US$ 100,00"
when
$order : Order()
$total : Number( doubleValue > 100 )
from accumulate( OrderItem( order == $order, $value : value ),
init( double total = 0; ),
action( total += $value; ),
reverse( total -= $value; ),
result( total ) )
then
# apply discount to $order
end
In the above example, for each Order() in the working memory, the
engine will execute the *init code* initializing the total variable to
zero. Then it will iterate over all OrderItem() objects for that
order, executing the *action* for each one (in the example, it will
sum the value of all items into the total variable). After iterating
over all OrderItem, it will return the value corresponding to the
*result expression* (in the above example, the value of the total
variable). Finally, the engine will try to match the result with the
Number() pattern and if the double value is greater than 100, the rule
will fire.
The example used java as the semantic dialect, and as such, note that
the usage of ';' is mandatory in the init, action and reverse code
blocks. The result is an expression and as such, it does not admit
';'. If the user uses any other dialect, he must comply to that
dialect specific syntax.
As mentioned before, the *reverse code* is optional, but it is
strongly recommended that the user writes it in order to benefit from
the /improved performance on update and retracts/.
The *accumulate* CE can be used to execute any action on source
objects. The following example instantiates and populates a custom object:
rule "Accumulate using custom objects"
when
$person : Person( $likes : likes )
$cheesery : Cheesery( totalAmount > 100 )
from accumulate( $cheese : Cheese( type == $likes ),
init( Cheesery cheesery = new Cheesery(); ),
action( cheesery.addCheese( $cheese ); ),
reverse( cheesery.removeCheese( $cheese ); ),
result( cheesery ) );
then
// do something
end
3.6.3.1. Accumulate Functions
The accumulate CE is a very powerful CE, but it gets real declarative
and easy to use when using predefined functions that are known as
Accumulate Functions. They work exactly like accumulate, but instead
of explicitly writing custom code in every accumulate CE, the user can
use predefined code for common operations.
For instance, the rule to apply discount on orders written in the
previous section, could be written in the following way, using
Accumulate Functions:
rule "Apply 10% discount to orders over US$ 100,00"
when
$order : Order()
$total : Number( doubleValue > 100 )
from accumulate( OrderItem( order == $order, $value : value ),
sum( $value ) )
then
# apply discount to $order
end
In the above example, sum is an AccumulateFunction and will sum the
$value of all OrderItems and return the result.
Drools 4.0 ships with the following built in accumulate functions:
*
average
*
min
*
max
*
count
*
sum
These common functions accept any expression as input. For instance,
if someone wants to calculate the average profit on all items of an
order, a rule could be written using the average function:
rule "Average profit"
when
$order : Order()
$profit : Number()
from accumulate( OrderItem( order == $order, $cost : cost, $price : price
)
average( 1 - $cost / $price ) )
then
# average profit for $order is $profit
end
Accumulate Functions are all pluggable. That means that if needed,
custom, domain specific functions can easily be added to the engine
and rules can start to use them without any restrictions. To implement
a new Accumulate Functions all one needs to do is to create a java
class that implements the
org.drools.base.acumulators.AccumulateFunction interface and add a
line to the configuration file or set a system property to let the
engine know about the new function. As an example of an Accumulate
Function implementation, the following is the implementation of the
"average" function:
/*
* Copyright 2007 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Created on Jun 21, 2007
*/
package org.drools.base.accumulators;
/**
* An implementation of an accumulator capable of calculating average values
*
* @author etirelli
*
*/
public class AverageAccumulateFunction implements AccumulateFunction {
protected static class AverageData {
public int count = 0;
public double total = 0;
}
/* (non-Javadoc)
* @see org.drools.base.accumulators.AccumulateFunction#createContext()
*/
public Object createContext() {
return new AverageData();
}
/* (non-Javadoc)
* @see org.drools.base.accumulators.AccumulateFunction#init(java.lang.Object)
*/
public void init(Object context) throws Exception {
AverageData data = (AverageData) context;
data.count = 0;
data.total = 0;
}
/* (non-Javadoc)
* @see org.drools.base.accumulators.AccumulateFunction#accumulate(java.lang.Object,
java.lang.Object)
*/
public void accumulate(Object context,
Object value) {
AverageData data = (AverageData) context;
data.count++;
data.total += ((Number) value).doubleValue();
}
/* (non-Javadoc)
* @see org.drools.base.accumulators.AccumulateFunction#reverse(java.lang.Object,
java.lang.Object)
*/
public void reverse(Object context,
Object value) throws Exception {
AverageData data = (AverageData) context;
data.count--;
data.total -= ((Number) value).doubleValue();
}
/* (non-Javadoc)
* @see org.drools.base.accumulators.AccumulateFunction#getResult(java.lang.Object)
*/
public Object getResult(Object context) throws Exception {
AverageData data = (AverageData) context;
return new Double( data.count == 0 ? 0 : data.total / data.count );
}
/* (non-Javadoc)
* @see org.drools.base.accumulators.AccumulateFunction#supportsReverse()
*/
public boolean supportsReverse() {
return true;
}
}
The code for the function is very simple, as we could expect, as all
the "dirty" integration work is done by the engine. Finally, to plug
the function into the engine, we added it to the configuration file:
drools.accumulate.function.average =
org.drools.base.accumulators.AverageAccumulateFunction
Where "drools.accumulate.function." is a prefix that must always be
used, "average" is how the function will be used in the rule file, and
"org.drools.base.accumulators.AverageAccumulateFunction" is the fully
qualified name of the class that implements the function behavior.
3.6.4. Forall
*Forall* is the Conditional Element that completes the First Order
Logic support in Drools. The syntax is very simple:
forall( /|<select pattern>|/ /|<constraint patterns>|/ )
The *forall* Conditional Element will evaluate to true when all facts
that match the /|<select pattern>|/ match all the /|<constraint
patterns>|/. Example:
rule "All english buses are red"
when
forall( $bus : Bus( type == 'english')
Bus( this == $bus, color = 'red' ) )
then
# all english buses are red
end
In the above rule, we "select" all Bus object whose type is
"english".
Then, for each fact that matchs this pattern we evaluate the following
patterns and if they match, the forall CE will evaluate to true.
Another example:
rule "all employees have health and dental care programs"
when
forall( $emp : Employee()
HealthCare( employee == $emp )
DentalCare( employee == $emp )
)
then
# all employees have health and dental care
end
Forall can be nested inside other CEs for complete expressiveness. For
instance, *forall* can be used inside a *not* CE:
rule "not all employees have health and dental care"
when
not forall( $emp : Employee()
HealthCare( employee == $emp )
DentalCare( employee == $emp )
)
then
# not all employees have health and dental care
end
As a side note, forall Conditional Element is equivalent to writing:
not( /|<select pattern>|/ and not ( and /|<constraint patterns>|/ ) )
Also, it is important to note that *forall is a scope delimiter*, so
it can use any previously bound variable, but no variable bound inside
it will be available to use outside of it.
------------------------------------------------------------------------
Prev <ch03s05.html> Up <ch03.html> Next <ch03s07.html>
3.5. Rule Home <title.html> | ToC <bk01-toc.html> 3.7. Query
------------------------------------------------------------------------
_______________________________________________
rules-dev mailing list
rules-dev(a)lists.jboss.org
https://lists.jboss.org/mailman/listinfo/rules-dev