optaplanner rule does not properly solve - drools

I made an optaplanner rule (see below).
//in expressway long tunnel(over 1km), equipment interval(400m)
rule "lcs_transport_tunnel_expway"
when
$road : RoadVO(roadCtgry=="EXPWAY")
$t1 : Transport(transportCode=="TUNNEL", $direction:direction,
Math.abs(mileageBegin-mileageEnd)>1000,
$mileageBegin:mileageBegin, $mileageEnd:mileageEnd )
$e0 : ItsEquipment(itsClass=="LCS", direction==$direction,
mileage >$mileageBegin && <$mileageEnd, $id:id, $mileage:mileage)
$e1 : ItsEquipment(itsClass== "LCS", direction==$direction, id==$id+1,
mileage==$mileage+400 )
then
scoreHolder.addSoftConstraintMatch(kcontext, 1000);
end
While optaplanner is solving, $e1 is not properly chosen.
What is the problem?
Please let me know. Thanks.

(without knowing what the desired result is, it's hard to answer this, but here goes... :)
The 2 selected ItsEquipment's don't constraint that they belong to the original selected Transport (or even the same Transport). They only need to belong to the same direction. Is that your intent?
The $e1 selection has id==$id+1 and mileage==$mileage+400. If your id's are unique, any other condition besides id==$id+1 is pointless (including mileage==$mileage+400).

Related

OptaPlanner: Drools rule on consecutive shift assignments

The context is Employee Shift Assignment with OptaPlanner using Drools rules for calculating scores.
My Employees cannot work for, say, for more than three consecutive days without a rest day.
I implement such a constraint very stupidly as:
rule "No more than three consecutive working days"
when
ShiftAssignment(
$id1 : id,
$empoloyee : empoloyee != null,
$shift1 : shift
)
ShiftAssignment(
id > $id1,
empoloyee == $empoloyee,
shift.isConsecutiveDay($shift1),
$id2 : id,
$shift2 : shift
)
ShiftAssignment(
id > $id2,
empoloyee == $empoloyee,
shift.isConsecutiveDay($shift2),
$id3 : id,
$shift3 : shift
)
ShiftAssignment(
id > $id3,
empoloyee == $empoloyee,
shift.isConsecutiveDay($shift10)
)
then
scoreHolder.penalize(kcontext);
end
I hope the name of the methods/variables clearly reveal what they do/mean.
Is there a more convenient and smart way to implement such a rule? Keep in mind that the three days above may need to change to a bigger number (I used three to avoid a more realistic ten and more lines of code in the rule). Thanks.
If we can assume an employee takes up to a single shift per day and the shift.isConsecutiveDay() may be replaced by something like shift.day == $shift1.day + 1, exists can be used:
when
ShiftAssignment($employee : empoloyee != null, $shift1 : shift)
exists ShiftAssignment(employee == $employee, shift.day == $shift1.day + 1)
exists ShiftAssignment(employee == $employee, shift.day == $shift1.day + 2)
exists ShiftAssignment(employee == $employee, shift.day == $shift1.day + 3)
then
If such an assumption cannot be made, your solution should work, with one potential corner case to think about:
The rule tries to filter out combinations of the same shifts by the condition id > $id1. This condition works, but the IDs must be generated ascendingly by the time of the shift, otherwise, it clashes with shift.isConsecutiveDay(...). In case this property cannot be guaranteed, checking for ID inequality could be preferable.
I used a combination of rules to achieve this. First rule sets up the start of a consecutive work sequence, second one sets up the end, 3rd rule creates a "Work Sequence" to fit between the start and end. Finally the "Max Consecutive Days" rule actually checks your "Work Sequence" against a limit on number of consecutive days.
This paradigm is actually in the nurse rostering example:
https://github.com/kiegroup/optaplanner/blob/master/optaplanner-examples/src/main/resources/org/optaplanner/examples/nurserostering/solver/nurseRosteringConstraints.drl

Drools rule using accumulate

Hello I it is my first time involved in drools project. I have created some simple rules that work fine, however I have trouble with more complex rules that use the accumulate function. Below I have this rule.
rule "1"
no-loop
when
$msg : Declaration(header.totalGrossMassMeasure != null,
header.totalGrossMassMeasure.compareTo(BigDecimal.ZERO) > 0 )
result : ValidationResult()
$netValue : Number() from accumulate (
GoodsItemsType($net : netNetWeightMeasure),
sum($net.doubleValue())
)
eval($netValue.doubleValue() > ($msg.getHeader().getTotalGrossMassMeasure().doubleValue() + (0.45 * $msg.getGoodsItems().size())))
then
RulesValidationError error = new RulesValidationError();
error.setErrorType(ErrorType.INCORECT_VALUE);
result.getErrorsList().add(error);
end
the concept is to sum the net value from a list of goodsItemType object and compare the sum to the total gross mass measure multiplied by one buffer number. The problem is I have been trying last couple of days not being able to fire the rule with anything. Could someone please help me?

Drools : Rule firing multiple times

I'm new to Drools and have hit a problem.
I've simplified the rule to exhibit the problem:
rule "test"
when
$ev : TestEvent()
$evList : ArrayList( size >= 3 ) from collect
(
TestEvent(linkId == $ev.getLinkId())
)
then
System.out.println("Rule fired!")
end
Basically, I want to count Events occurring on a particular Link (a Link is a section of road). When 3 events occur on the same Link I want the rule to fire.
The rule above is almost working, but when it fires, it fires 3 times, once for each event. I only want it to fire once.
What am I missing?
Many thanks in advance.
The first pattern picks any TestEvent irrespective of its linkId. If there are n TestEvent facts with a certain linkId, the acivation proceeds n times.
To restrict this rule to fire once you could select a single TestEvent out of any such group of n. Any attribute with a unique ordered value may be used, and if you have events also the event timestamp is available.
rule "test"
when
$ev: TestEvent( $lid: linkId )
not TestEvent( linkId == $lid, this before $ev )
$evList : ArrayList( size >= 3 ) from collect
(
TestEvent(linkId == $lid)
)
then
System.out.println("Rule fired!")
end
I got this working by changing my approach to the problem. I've created Link objects now and then tie the events back to the Link.
The rule ends up
rule "test"
when
$link : Link()
$events : ArrayList( size >= 3 ) from collect (TestEvent(link == $link))
then
System.out.println("Rule fired!")
end
This only fires once per link which is what I need.

Drools accumulate method that returns zero if no matching source fact

I am trying to implement a rule in Drools that calculates the sum of a some property of a fact. That works great using accumulate and sum. The problem is that when there are not fact that matches the criteria in the source part of the accumulate method the rule is not executed.
I would like the sum method to return zero if no fact is matching and that the rest of the when clauses is checked. Is that possible somehow?
Update:
I am using Drools 6.0.1
The problem seems to lie in the the and clause. Here is a code that is my problem.
rule "accu"
when
$n: Number()
from accumulate( $o: Order() and OrderLine( $v: quantity ),
sum($v))
then
System.out.println("*#*#*#*#*#*#*#*#*#* Accu has fired *#*#*#*#*#*#*#*#*#");
end
With only Order or OrderLine it works. I have a feeling I am attacking the problem the wrong way. In my real case the value I want to sum up is in the OrderLine but the criteria is in another class.
$ol : OrderLine($q : quantity)
and
$ac : ArticleClass(orderLine == $ol, crtiteria1=efg, criteria2=abc)
But accumulate does return 0 when there are no matching elements.
rule accu
when
$n: Number()
from accumulate( Fact( prop == "C", $v: value ),
sum($v))
then
//...
end
This fires in the absence of Fact facts with prop == "C" and it fires if there are no Fact facts at all. (Drools 5.5.0)
Please provide full code reproducing the error, Drools version, etc.

How to obtain the minimum of an updated value in Drools?

I want to use Drools to manage some questions in a form. Depending of the answer of the question, the question score be updated with a value. Then I want to obtain the minimum value of all questions answered in a category.
One example:
Category "Replicant or not?":
You’re in a desert walking along in the sand when all of the sudden you look down, and you see a tortoise, crawling toward you. You reach down, you flip the tortoise over on its back. Why is that?
1. it was an error. (5 points).
2. It is the funniest think to do at a dessert. (3 points).
3. You want to kill the tortoise in the most painful way (1 point).
//More other questions.
At the end of the test, the minimum value for each category will be the used one.
Then I have defined some drools rules to define the score for each one (in a spreadsheet, by I put the rule translations):
rule "Question_1"
when
$qs: Questions(checked == false);
$q: Question(category == 'cat1', question == "q1", answer == "a") from $qs.getQuestions();
then
$q.setValue(5);
$qs.setChecked(true);
update($qs);
end
checked value is used to avoid to reuse the rule when updating. category, question, answer are used for classifying the question.
And then, my rule for calculate the minimum is:
rule "Min value"
when
$qs: Questions(checked == true);
not q:Question($v: value; value < $v) from $qs.getQuestions();
then
$qs.setMinValue($v);
System.out.println("The smallest value is: "+ $v);
end
The error obtained is:
$v cannot be resolved to a variable
Then, the question is: How can I obtain the minimum value of a value setted by a previous rule?
The duplication of "from " can be avoided, using the accumulate CE which is about 25% faster.
rule "getmin"
when
Questions( $qs: questions )
accumulate( Question( $v: value ) from $qs; $min: min( $v ) )
then
System.out.println( "minimum is " + $min );
end
You are trying to get $v from a Fact that doesn't exist: not q:Question($v: value; value < $v) from $qs.getQuestions();
What is the expected value of $v is there is no Question that matches that pattern!?
What the seccond pattern is basically saying is: "there is no Question from Questions.getQuestions() that has a value ($v) and that that value is less than the same value"
What you have to do is to bind $v to the positive pattern. Something like this:
rule "Min value"
when
$qs: Questions(checked == true);
Question($v: value) from $qs.getQuestions()
not Question(value < $v) from $qs.getQuestions();
then
$qs.setMinValue($v);
System.out.println("The smallest value is: "+ $v);
end
Hope it helps,
---EDITED: added missing Question positive pattern