What happen “java.lang.IllegalStateException: The constraintMatchTotal could not remove constraintMatch ” - optaplanner

There are geography, math, history, art, physics 5 courses, and each course have three class. for example GeographyA, GeographyB, GeographyC ...
200 student each one need have 3 lectures for 3 courses which there selected in different timeslot, we don't need Day variable...
#PlanningEntity
class Lecture {
Student student // not null
Course course // not null
// there 3 timeslot, there is no same klass or same timeslot for student
#PlanningVariable
Klass Klass
#PlanningVariable
Integer timesolt
}
init lecture
new Lecture(id++, student_1, course_history)
new Lecture(id++, student_1, course_math)
new Lecture(id++, student_1, course_art)
At last, We need a rule, one student should not have 2 same timeslots.
rule "student should not have 2 same timeslot"
when
$student: Student()
$timeslotSet: Set() from accumulate (
Lecture(student == $student, $timeslot: timeslot, timeslot != null), collectSet($timeslot)
)
then
scoreHolder.addHardConstraintMatch(kcontext, -(3 - $timeslotSet.size()) * 17);
end
the exception will be thrown, but
rule "student should not have 2 same timeslot"
when
Lecture($id : id, $student: student, $timeslot: timeslot)
Lecture(id > $id, student == $student, timeslot == $timeslot)
then
scoreHolder.addHardConstraintMatch(kcontext, -17); // if
end
It works fine
WHY ??
I have googled and read the document, the content about "constraintMatch" is less.
I am sorry for my poor english

Related

Drools : stop a rule from firing after a number of executions

I'm trying to figured out how to stop a rule from firing after a number of executions.
Let's say you grant a discount if you buy an item X (current price $10), but this discount can be granted only 2 times per customer.
so my rule is as follow:
rule "Fixed Price Promotion Item X"
when
$o : Order( )
$ol1 : OrderLine( item.code == "X" )
then
FixedPrice fixedPrice = new FixedPrice(8.80, $ol1.getItem().getPrice().doubleValue(), $ol1.getItem().getBarcode(), 1, "e78fbca5ed014f49806ad667aea80965" , "Happy Mother's day!! This is a fixed price promotion" );
insertLogical (fixedPrice); //here I grant a promotion
Originally, I thought to have another rule to insert a fact when my condition was met so It would stop my promotion rule from firing.
I would have something like this:
declare LimitReachedOut
promotionId : String
end
rule "stop Promotion"
when
Number($numOfGrantedProm : intValue) from accumulate (
FixedPrice(promotionId == "123",
$count: quantity),
sum($count)
)
then
if( $numOfGrantedProm == 2 ){ //this cause an issue, even > 1 or >=2 will keep inserting new LimitReachedOut("123") recursively.
insertLogical (new LimitReachedOut("123"));
}
end
rule "Fixed Price Promotion Item X"
when
not( LimitReachedOut( promotionId == "123" ) )
$o : Order( )
$ol1 : OrderLine( item.code == "X" )
then
FixedPrice fixedPrice = new FixedPrice(8.80, $ol1.getItem().getPrice().doubleValue(), $ol1.getItem().getBarcode(), 1, "123" , "Happy Mother's day!! This is a fixed price promotion" );
insertLogical (fixedPrice);
end
Is there another way to do it?
I will really appreciate your comments.
I've found a way to accomplish what I wanted, and I'm posting it in here just if someone wants to use this as well.
I'm basically not executing the RHS of the rule putting a conditional clause there.
Firstly, I'm calculating the number of Facts that were inserted into the working memory ( FixedPrice with an id called ruleId to link the facts to this particular rule ) using the accumulate function.
Secondly, the condition in the RHS if( $numOfGrantedProm < 2 ) means that I will execute the block inside of the conditional if (create & insert fixedPrice fact) up to two times.
The rule will be fired anyway but the if condition will avoid executing the block of code.
rule "Fixed Price Promotion Item X"
when
$o : Order( )
$ol1 : OrderLine( item.code == "X" )
Number($numOfGrantedProm : intValue) from accumulate (
FixedPrice(ruleId ==
"e78fbca5ed014f49806ad667aea80965",$count:quantity),
sum($count)
)
then
if( $numOfGrantedProm < 2 ){
FixedPrice fixedPrice = new FixedPrice(8.80,
$ol1.getItem().getPrice().doubleValue(),
$ol1.getItem().getBarcode(),
1,
"e78fbca5ed014f49806ad667aea80965" ,
"Happy Mother's day!! This is a fixed price
promotion" );
insertLogical (fixedPrice); //here I grant a promotion enter code here
}
end

Working with consecutive events in drools

I need help with the following rule:
Accounts with multiple consecutive deposits and immediately after 4 hours, multiple extractions whose sum is equal to the same amount deposited using different ATM in the same bank.
(There aren't intermediate operations between the last deposit and the first extraction)
I have the class Operation: Account, Amount, Operation, ATM, Timestamp.
I have this rule:
declare Operation
#role(event)
#timestamp(Timestamp)
end
rule
when
$op1:Operation($acc:Account,$b:Bank,$amount:Amount,Operation‌​=="CREDIT",$atm:ATM)
not Operation(Account == $acc,ATM != $atm, Bank == $b, this before $op1) accumulate($op2:Operation(Account == $acc,ATM != $atm, Bank == $b,Operation=="CREDIT",$amount1:Amount);$c:count($op2),$d:su‌​m($amount1);$c>=4,$d‌​>=300)
then
System.out.println( "Account: " + $acc + " amount " + ($amount + $d) + " operations " + $c+ " in different banks");
end
The previous rule returns the sum of the deposits of the accounts that have made more than 4 operations in different ATMs of the same bank,my doubt is how to take into account that they are consecutive and that after 4 hours compare the previous sum with the sum of the extractions from that account.
If this rule fires,
rule "set up Monitor"
when
$op1: Operation( $a:account, $b:bank, $amt1:amount, operation‌​=="CREDIT", $atm1: atm )
$op2: Operation( account==$a, bank==$b, $amt2:amount, operation‌​=="CREDIT", atm!= $atm1 )
not Operation(account == $acc, atm != $atm1, bank == $b,
this before $op2, this after $op1 )
not Monitor( bank == $bank, account == $acc )
then
insert( new Monitor( $bank, $acc, $amt1 + $amt2, "CREDIT" );
end
you have a Monitor inserted. The monitor has the obvious fields and a state field, set to "CREDIT".
rule "add deposit"
when
$op1: Operation( $a:account, $b:bank, $amt1:amount, operation‌​=="CREDIT", $atm1: atm )
$mon: Monitor( account==$a, bank==$b, $bal: balance, state == "CREDIT" )
then
// another deposit
modify( $op1 ){ setAmount( $bal + $amt1 ) }
end
With this event, the state changes to withdrawal.
rule "state changes to withdrawal"
when
$op1: Operation( $a:account, $b:bank, $amt1: amount, operation‌​=="WITHDRAW", $atm1: atm )
$mon: Monitor( account==$a, bank==$b, $bal: balance, state == "CREDIT" )
then
// switch to withdraw
modify( $op1 ){ setAmount( $bal + $amt1 ),
setState( "WITHDRAW" ) }
end
And here it begins to get messy. We need to check whether this (first) withdrawal cashes in on all deposits (balance == $amt1) or just reduces the balance or withdraws more than the previous sequence of deposits have made.
I quit here, but I think I've shown that this isn't possible to do with a single rule.

forall always evaluates to be true [Drools 6.3 final]

I am a newbie for Drools. Would like to ask a question about the conditional element forall. Ihave some classes and the following UML diagram shows the relationships among these objects: 
UML diagram
One order has one customer and has a list of orderlines.
Each orderline is linked to one item. Frankly speaking, it is a sample code of one book about Drools.
Iwant to fire the below rule when the customer of order is SILVER category and all items in the order are in high range ( with high cost), in order to setup the discount of the order. Here is the rule:
rule "Silver Customers + High Range Order - 10% Discount -1"
when
$o: Order( $lines : orderLines.size >= 2, $customer: customer, discount == null )
$c: Customer( category == Category.SILVER, this == $customer )
forall( OrderLine( this memberOf $lines, $item : item)
Item(this == $item, category == Item.Category.HIGH_RANGE)
)
then
$o.setDiscount(new Discount(10.0));
update($o);
end
The problem is the rule always fires, even when I tried to insert one order with 5 items in low range.
In the debug info i found that the category of the first item was LOW_RANGE but the rule was still fired:
==>[BeforeActivationFiredEvent: getActivation()=[[ Silver Customers + High Range Order - 10% Discount active=false ] [ null
[fact 0:1:1561745898:141057847:1:DEFAULT:NON_TRAIT:org.drools.devguide.eshop.model.Customer:Customer [id = null, age=null, email=null, name=null, category = SILVER]]
[fact 0:12:1971991758:-345298280:12:DEFAULT:NON_TRAIT:org.drools.devguide.eshop.model.Order:Order [ id = null, customer=Customer [id = null, age=null, email=null, name=null, category = SILVER], date=Fri Oct 21 11:11:38 CST 2016, lines=[OrderLine [item=Item{id=1, name=A, cost=80.0, salePrice=800.0, category=LOW_RANGE}, quantity=1], OrderLine [item=Item{id=1, name=B, cost=800.0, salePrice=850.0, category=HIGH_RANGE}, quantity=2], OrderLine [item=Item{id=1, name=C, cost=800.0, salePrice=850.0, category=HIGH_RANGE}, quantity=3], OrderLine [item=Item{id=1, name=D, cost=800.0, salePrice=850.0, category=HIGH_RANGE}, quantity=4], OrderLine [item=Item{id=1, name=E, cost=800.0, salePrice=850.0, category=HIGH_RANGE}, quantity=5]], state=PENDING, discount=null]] ] ], getKnowledgeRuntime()=KieSession[0]]
-------------------
rule triggered:Silver Customers + High Range Order - 10% Discount
==>[AfterActivationFiredEvent: getActivation()=[[ Silver Customers + High Range Order - 10% Discount active=false ] [ null
[fact 0:1:1561745898:141057847:1:DEFAULT:NON_TRAIT:org.drools.devguide.eshop.model.Customer:Customer [id = null, age=null, email=null, name=null, category = SILVER]]
[fact 0:12:1971991758:-345298280:18:DEFAULT:NON_TRAIT:org.drools.devguide.eshop.model.Order:Order [ id = null, customer=Customer [id = null, age=null, email=null, name=null, category = SILVER], date=Fri Oct 21 11:11:38 CST 2016, lines=[OrderLine [item=Item{id=1, name=A, cost=80.0, salePrice=800.0, category=LOW_RANGE}, quantity=1], OrderLine [item=Item{id=1, name=B, cost=800.0, salePrice=850.0, category=HIGH_RANGE}, quantity=2], OrderLine [item=Item{id=1, name=C, cost=800.0, salePrice=850.0, category=HIGH_RANGE}, quantity=3], OrderLine [item=Item{id=1, name=D, cost=800.0, salePrice=850.0, category=HIGH_RANGE}, quantity=4], OrderLine [item=Item{id=1, name=E, cost=800.0, salePrice=850.0, category=HIGH_RANGE}, quantity=5]], state=PENDING, discount=10.0 % ]] ] ], getKnowledgeRuntime()=KieSession[0]]
I have read the answers of the question: forall always evaluates to be true [Drools] . I tried the solution, but i got another wrong result, the below rule never fires even i used an order with all high range items:
rule "Silver Customers + High Range Order - 10% Discount"
when
$o: Order( $lines : orderLines.size >= 2, discount == null )
$c: Customer( category == Category.SILVER ) from $o.customer
forall( OrderLine() from $o.orderLines
OrderLine( item.category == Item.Category.HIGH_RANGE)
)
then
$o.setDiscount(new Discount(10.0));
update($o);
end
The unittest code is below:
#Test
public void highRangeOrderDiscountTest() {
KieSession kSession = createDefaultSession();
Order o = ModelFactory.getOrderWithFiveHighRangeItems();
kSession.insert(o.getCustomer());
kSession.insert(o.getOrderLines().get(0));
kSession.insert(o.getOrderLines().get(1));
kSession.insert(o.getOrderLines().get(2));
kSession.insert(o.getOrderLines().get(3));
kSession.insert(o.getOrderLines().get(4));
kSession.insert(o.getOrderLines().get(0).getItem());
kSession.insert(o.getOrderLines().get(1).getItem());
kSession.insert(o.getOrderLines().get(2).getItem());
kSession.insert(o.getOrderLines().get(3).getItem());
kSession.insert(o.getOrderLines().get(4).getItem());
kSession.insert(o);
int fired = kSession.fireAllRules();
// We have 5 Items that are categorized -> 5 rules were fired
// We have 1 Customer that needs to be categorized -> 1 rule fired
// We have just one order with all HIGH RAnge items -> 1 rule fired
// One Coupon is created for the SILVER Customer -> 1 rule fired
assertThat(8, is(fired));
assertThat(o.getCustomer().getCategory(), is(Customer.Category.SILVER));
assertThat(o.getDiscount(), not(nullValue()));
assertThat(o.getDiscount().getPercentage(), is(10.0));
}
}
Could you tell me the cause or tell me the correct usage of forall ? Thanks a lot.
This establishes the set of OrderLine-s and then ascertains that all pertain to a high range item. (Note: untested)
rule "Silver Customers + High Range Order - 10% Discount"
when
$c: Customer( category == Category.SILVER )
$o: Order( $lines : orderLines.size >= 2, customer == $c,
discount == null )
forall( OrderLine( $item : item) from $lines
Item(this == $item, category == Item.Category.HIGH_RANGE))
then
modify( $o ){ setDiscount(new Discount(10.0)) }
end
All Customer and Item objects must be inserted as facts; it would be reasonable to do this for OrderLine-s as well.

Writing rule in Drools - conditional sum

I am stuck in writing a rule for a particular problem statement.
I have an excel file having columns id, specialty,salary. Specialty can take values such as "oncology","urology" etc. I have to calculate total salary corresponding to each specialty.
There are two ways of doing this.
First:
rule "Total salary of Oncology"
no-loop
lock-on-active
salience 100
when
$m : Masterclass( $id : phyid , $p : p,spec=="Oncology")
not Masterclass( spec=="Oncology", phyid < $id)
$total : Number() from accumulate ( Masterclass( $salary : salary ,spec=="Oncology") ,
init( double total =0;),
action(total+=$salary;),
result( new Double (total)))
then
System.out.println($m.getSpec());
System.out.println("Total target pay is : " + $total + " of specialty : "+ $m.getSpec());
retract($m);
end
And similarly for other specialties.
This rule works fine.
Second:
Write only a single rule which reads the value of specialty and then sums up the salaries corresponding to it.
I tried to implement this but didn't succeed.
Any help will be appreciated.
This is far from perfect. But note that you don't have to insert some specific string. Running the rule without a string to match will produce the accumulated sums for all specialities anyway.
And you should wrap the String into a Java class - I was just too lazy to invent a proper Java class with one field.
rule "trigger read"
when
not String()
then
String s = read_any_way_you_want();
insert( s );
end;
rule "Total salary of something"
when
$spec: String()
$m : Masterclass( $id: phyid , spec == $spec)
not Masterclass( spec == $spec, phyid < $id)
$total : Number() from
accumulate ( Masterclass($salary: salary, spec == $spec) ,
init( double total =0;),
action(total+=$salary;),
result( new Double (total)))
then
System.out.println($m.getSpec());
System.out.println("Total target pay is : " + $total +
" o specialty : "+ $m.getSpec());
retract($spec);
end

Drools Fusion De-duplication

I defined a class which acts as event to the rule engine
It has the following members
1. phone number
2. latitude
3. longitude
How do I formulate a rule wherein
1. The rule counts the number of distinct people in the same location
2. Same person if crossing the same location more than once during a 1 minute interval should be counted only as one and should not be duplicated
I made the below rule but it doesn't seem to be working
import locationbasedservices.LocationEvent;
declare LocationEvent
#role(event)
#expires(1m)
end
rule "footfallcount"
when
LocationEvent ( $msisdn : msisdn )
$footfallcnt : Number(intValue > 0)
from accumulate( LocationEvent(latitude=="77.77", longitude=="77.77",
age>31 && <40, arpu>40.00, gender=="MALE")
from entry-point LocationSvc,
not ArrayList( size >= 2 )
from collect( LocationEvent( msisdn == $msisdn )
from entry-point LocationSvc),
count(1))
then
System.out.println("Footfall: " + $footfallcnt);
end
Can someone help?
Regards
Subbu
This is what I had in mind:
$m2e: Map( $size: size )
from accumulate ( $le: LocationEvent ( latitude == "77.77", longitude == "77.77",
age > 31 && < 40, arpu > 40.00,
gender == "MALE", $msisdn : msisdn )
over window:time( 1m ),
init( Map m2e = new HashMap(); ),
action( m2e.put( $msisdn, $le ); ),
result( m2e ) )
Note carefully what the Drools manual says in the section on accumulate: It is better practice to develop an accumulate function in Java code, implementing org.drools.core.runtime.rule.TypedAccumulateFunction and running this using simple syntax.

Resources