Optaplanner - Drools for int array constraint - drools

I'm trying to write a constraint to check if any element of a integer array (binUsedArray) is greater than a bound (dailyMaxNb).
rule "BinResourceConstraint"
when
BinResource($array : binUsedArray, $dailyMaxNb : dailyMaxNb)
$x : Integer() from $array
$x > $dailyMaxNb
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
I read drools doc 5.1.8 and tried to write a similar rule like this
rule "Iterate the numbers"
when
$xs : List()
$x : Integer() from $xs
then
$x matches and binds to each Integer in the collection
end
but got some error:
Caused by: java.lang.RuntimeException: [Message [id=1, kieBase=defaultKieBase, level=ERROR, path=com/cttq/aps/solver/taskScheduleConstraint.drl, line=78, column=0
text=[ERR 102] Line 78:11 mismatched input '>' in rule "BinResourceConstraint"], Message [id=2, kieBase=defaultKieBase, level=ERROR, path=com/cttq/aps/solver/taskScheduleConstraint.drl, line=0, column=0
text=Parser returned a null Package]]
===== updated with BinResource class, binUsedArray is a int array with size of 30, to keep number of bins used for next 30 days.
#PlanningEntity
public class BinResource {
private String index;
private String binType;
private String binArea;
private int dailyMaxNb;
#CustomShadowVariable(variableListenerRef = #PlanningVariableReference(entityClass = TaskAssignment.class, variableName = "modifiedProcessTimeInShift"))
private int[] binUsedArray;

The trivial solution would be to convert your array to a List and then it'll just work. Arrays.asList( array ) can be invoked on the LHS to convert the array ... something like:
BinResource($array : binUsedArray, $dailyMaxNb : dailyMaxNb)
$binUsed: List() from Arrays.asList($array)
Then you can do the same logic from the second rule to find the values which meet your criteria:
$x : Integer(this > $dailyMaxNb) from $binUsed
With that, your rule will trigger once for each value ($x) in the array / list which is greater than the dailyMaxNb value.
rule "BinResourceConstraint"
when
BinResource($array : binUsedArray, $dailyMaxNb : dailyMaxNb)
$binUsed: List() from Arrays.asList($array)
$x : Integer(this > $dailyMaxNb) from $binUsed
then
// this will trigger once PER MATCH ...
// eg if there are 3 values that > dailyMaxNb, this will trigger 3 times
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
If, however, your binUsedArray is actually an ArrayList, you can omit the conversion and just use $x: Integer(this > $dailyMaxNb) from $array. I mention this because sometimes when people ask about an "array" they're actually referring to an array list, and you've not provided the code of your BinResource class. That being said, you can use this pattern (MyType( <condition> ) from $collection) for any iterable collection to attempt to match against all values in that collection.

Related

How to use length and rlike using logical operator inside when clause

Want to check if the column has values that have certain length and contains only digits.
The problem is that the .rlike or .contains returns a Column type. Something like
.when(length(col("abc")) == 20 & col("abc").rlike(...), myValue)
won't work as col("abc").rlike(...) will return Column and unlike length(col("abc")) == 20 which returns Boolean (length() however also returns Column). How do I combine the two?
After doing a bit of searching in compiled code, found this
def when(condition : org.apache.spark.sql.Column, value : scala.Any) : org.apache.spark.sql.Column
Therefore the conditions in when must return Column type. length(col("abc")) == 20 was evaluating to Boolean.
Also, found this function with the following signature
def equalTo(other : scala.Any) : org.apache.spark.sql.Column
So, converted the whole expression to this
.when(length(col("abc")).equalTo(20) && col("abc").rlike(...), myValue)
Note that the logical operator is && and not &.
Edit/Update : #Histro's comment is correct.

Drools how to compare Integer

I have an Integer, and I want to compare it to some raw int like so:
$day : Day()
$itemsList : from collect(Item())
$number : Integer() from $day.getNumberOfItemsAvailable($itemsList)
$number > 4
but I'm getting
Message [id=1, kieBase=defaultKieBase, level=ERROR, path=ScoreRules.drl, line=42, column=0
text=[ERR 102] Line 42:21 mismatched input '<' in rule "rule"]
How do you compare an Integer to some other int value? Could you point me to some documentation that explains basics like this?
While you could use an eval statement as suggested in the previous answer, such constructs are not recommended because they can't be optimized.
Alternatively you could check in the same line where you assign your $number variable like this:
$number : Integer( this > 4 ) from $day.getNumberOfItemsAvailable($itemsList)
This will only assign number if the getNumberOfItems... call returns an integer greater than four. If the returned value is less, the condition won't be satisfied and the rule won't be evaluated.
You need to use the operator with int value.
eval($number.intValue() > 4)

unique with "with" operator in systemverilog

I am a new SystemVerilog user and I have faced a strange (from my point of view) behavior of combination of unique method called for fixed array with with operator.
module test();
int arr[12] = '{1,2,1,2,3,4,5,8,9,10,10,8};
int q[$]
initial begin
q = arr.unique() with (item > 5 ? item : 0);
$display("the result is %p",q);
end
I've expected to get queue {8,9,10} but instead I have got {1,8,9,10}.
Why there is a one at the index 0 ?
You are trying to combine the operation of the find method with unique. Unfortunately, it does not work the way you expect. unique returns the element, not the expression in the with clause, which is 0 for elements 1,2,3,4 and 5. The simulator could have chosen any of those elements to represent the unique value for 0(and different simulators do pick different values)
You need to write them separately:
module test();
int arr[$] = '{1,2,1,2,3,4,5,8,9,10,10,8};
int q[$]
initial begin
arr = arr.find() with (item > 5);
q = arr.unique();
$display("the result is %p",q);
end
Update explaining the original results
The with clause generates a list of values to check for uniqueness
'{0,0,0,0,0,0,0,8,9,10,10,8};
^. ^ ^ ^
Assuming the simulator chooses the first occurrence of a replicated value to remain, then it returns {arr[0], arr[7], arr[8], arr[9]} from the original array, which is {1,8,9,10}

Drools - Multiple matches in then clause

I am creating a rule which needs to fire when one or more conditions are met. The rule looks as follows:
rule "Demo Rule"
when
$data : Data (val == 1 || val == 2)
then
System.out.println($data);
end
I have a test for the rule, which has two matching Data objects (val 1 and val 2). The rule fires correctly, as it gets to the print statement. However, I can't find a way to parse $data and get access to the individual matching Data objects. The println results as follows:
custom.package.Data< val: 1, text:'Test1' >
custom.package.Data< val: 2, text:'Test2' >
So, I can see that multiple entries are present. But attempting to call as an array ($data[0]) or $data$1 or anything I could think of all result in parsing errors by Drools (complaining that $data is a Data object, not an array or list or other iterable).
The rule is fired once per each object matching your condition. So, you'll need to access the Data object directly as $data.val and data.text
If you need/want the objects in a list, you can use collect for that. Then your rule would be like
rule "Demo Rule"
when
$dataList : ArrayList() from collect( Data (val == 1 || val == 2) )
then
System.out.println($dataList);
end

Why does the 'from' keyword iterate backwards?

I have an ArrayList in working memory that I am iterating with a 'from' statement in my when clause. However, the rule is iterating backwards through the array. Is there a way to control this behavior?
List Creation:
List<Map<String,String>> list = new ArrayList<Map<String,String>>();
Map<String,String> m1 = ...
Map<String,String> m2 = ...
list.add(m1);
list.add(m2);
ImportItem item = new ImportItem(list); //model object. Constructor arg returned by getInput()
StatelessKnowledgeSession session = knowledgeBase.newStatelessKnowledgeSession();
session.insert(item);
session.fireAllRules();
Drools rules:
rule "My Rule"
dialect "java"
when
item : ImportItem(ignore == false) //model object that holds the input
map : Map() from item.getInput() // == the ArrayList from above
then
System.out.println(map);
end
Produces
<output of m2>
<output of m1>
The iteration is not backwards, as you can see if you attach an audit log to your session. The execution of the rules is what follows, by default, a LIFO order (in case you are not using any other conflict resolution strategy).
My suggestion for you is don't try to encapsulate the objects in data structures. If you are reading lines of a file and you need to process these lines in order, just use a class to represent the line and insert the lines directly into the session. To guarantee execution order, you can use the line number as a parameter to salience.
For instance:
rule "Process lines in order"
// salience executes from higher number to lowest number, so using the
// negative value you will executem them from the first to the last
salience (- $lineNumber )
when
$line : Line( $lineNumber : lineNumber )
then
// do something with $line
end
Obviously the same trick can be used with the "from" approach you took, but the engine is much more efficient matching facts in the working memory than it is iterating over collections of objects.
Hope it helps.
Edson
You should always try to write the rules so that the order doesn't count. If however, you really really need to have rules firing in certain order, or in this case, get the facts from a list in a certain order. Make sure that whatever changes in the rule engine internals doesn't break up your rules when you update to a newer version of the engine.
You can't trust the "from" even if it did the iteration the other way around.
There is one problem with this solution. If you update the ImportItem and need to iterate through the input again, you need to retract the MyIterator that is related to it.
declare MyIterator
index:int
lastIndex:int
importItem:ImportItem
end
rule "Insert ImportItem iterator"
when
item : ImportItem()
not MyIterator( importItem == item )
then
MyIterator myIterator = new MyIterator();
myIterator.setImportItem( item );
myIterator.setIndex( 0 );
myIterator.setLastIndex( item.getInput().size() );
insert( myIterator );
end
rule "My Rule"
when
myIterator :MyIterator( index < lastIndex )
item : ImportItem( this == myIterator.importItem,
ignore == false )
map : Map() from item.getInput().get( myIterator.getIndex() )
then
System.out.println(map);
myIterator.setIndex( myIterator.getIndex() + 1 );
update( myIterator );
end