Optaplanner: Iterate over list variable in drools rule file - drools

I am solving a problem similar to employee rostering. I have an additional constraint. The employees have a "type" value assigned to them. It's a hard constraint that atleast 1 employee of each "type" be there everyday. I have modelled it as follows:
rule "All employee types must be covered"
when
$type: Constants.EmployeeType() from Constants.EmployeeType.values()
not Shift(employeeId != null, $employee: getEmployee(), $employee.getType() == $type.getValue())
then
scoreHolder.addHardConstraintMatch(kcontext, -100);
end
This rule however, does not consider that the constraint be satisfied on each day. I have a list of date strings. How can I iterate over them in the drools file in the same manner that I am on the EmployeeType enum?
Edit: I figured out a way but it feels like a hack. When initialising the list of date strings, I also assign it to a static variable. Then I am able to use the static variable similar to the enum.
rule "All employee types must be covered"
when
$type: Constants.EmployeeType() from Constants.EmployeeType.values()
$date: String() from Constants.dateStringList;
not Shift(employeeId != null, $date == getDate(), $employee: getEmployee(), $employee.getType() == $type.getValue())
then
scoreHolder.addHardConstraintMatch(kcontext, -100);
end
Don't think this is the correct approach though.

Your approach works, but having to define dynamic configurations in a static property of a class doesn't sound right (like you pointed out).
One solution would be to either use a global in the session, or to have a fact class that specify this configuration.
Using a global
If you decide to take this approach, then you need to define a global of type List<String> in your DRL and then use it in your rules in combination with the memberOf operator:
global List<String> dates;
rule "All employee types must be covered"
when
$type: Constants.EmployeeType() from Constants.EmployeeType.values()
not Shift(
employeeId != null,
date memberOf dates,
$employee: getEmployee(),
$employee.getType() == $type.getValue()
)
then
scoreHolder.addHardConstraintMatch(kcontext, -100);
end
It is recommended to set the value for global before you insert any fact Shift into you session:
List<String> dates = //get the List from somewhere
ksession.setGlobal("dates", dates);
Using a Fact Class
Other than a global, you can model your configuration as a class. This makes things easier if you want for example to modify the configuration inside the rules themselves.
for this approach you will need to have a class containing the List<String> first. You could in theory insert the List<String> without wrapping it in any class, but this will make things hard to read and maintain.
public class DatesConfiguration {
private List<String> dates;
//... getters + setters
}
Then, you need to instantiate an object of this class and to insert it into your session:
DatesConfiguration dc = new DatesConfiguration();
dc.setDates(...);
ksession.insert(dc);
At this point, the object you have created is just another fact for Drools and can be used in your rules:
rule "All employee types must be covered"
when
$type: Constants.EmployeeType() from Constants.EmployeeType.values()
DatesConfiguration($dates: dates)
not Shift(
employeeId != null,
date memberOf $dates,
$employee: getEmployee(),
$employee.getType() == $type.getValue()
)
then
scoreHolder.addHardConstraintMatch(kcontext, -100);
end
Hope it helps,

Related

Drools - check if argument is null before calling query

Inside the drools rule file, I'm trying to match the request object against inserted facts using a query (backward chaining). How do I check for null for the request object attribute? If the attribute is not null, I want to pass it to the query. If the attribute is null, I want to keep it unbound so that it will match all results. Since there are many request attributes, I'm looking for a generic solution instead of different rules for each attribute.
To give an example, lets assume I have two attributes currency and country in the goal: Goal() object and I want to call the query isMatching(String country,String currency)
if goal.getCountry() and goal.getCurrency() is not null, I want to call isMatching with goal.getCountry()and goal.getCurrency().
isMatching(goal.getCountry(),goal.getCurrency())
if goal.getCountry() is null and goal.getCurrency() is not null, I want to call isMatching with unbound variable country and goal.getCurrency()
isMatching(country,goal.getCurrency())
if goal.getCountry() is not null and goal.getCurrency() is null, I want to call isMatching with goal.getCountry() and unbound variable currency
isMatching(goal.getCountry(),currency)
if both goal.getCountry() and goal.getCurrency() are null, I want to call isMatching with unbound variable country and currency
isMatching(country,currency)
Best practice is to have a separate rule for each combination.
rule "both country and currency"
when
Goal( $country: country != null, $currency: currency != null )
$isMatching: Boolean() from isMatching( $country, $currency )
then
//
end
Not sure what you're referring to as an "unbound" variable in your question for your other use cases.
If you insist on not following best practices and try to kludge all of this into a single rule, you could either do your null check on the right hand side, or possibly abuse conditional and named consequences to do this. Doing it in the rule consequences ("then") will cause you to lose all of the performance optimization done by the Drools engine, which is done on the left hand side only.
Alternatively you could just update the query to handle the null case.
query isMatching( String $country, String $currency) {
$country := String( this == null )
or
$currency := String( this == null )
or
( actual implementation )
}
rule "example"
when
Goal( $country: country, $currency: currency )
isMatching( $country, $currency )
then
// ...
end
Actual implementation may vary; I have no idea how you'd implement a currency <-> country check.

Drools compare if one list contains element from another list

I have got below structure in Java:
public class Request {
List<Product> product;
List<Account> accounts;
}
public class Product {
String productIdOne;
String productIdTwo;
String productTax;
}
public class Account {
List<ProductRelationship> productsRelationship;
}
public class ProductRelationship {
String productIdOne;
String productIdTwo;
}
And the request is the fact object send to drools. I am wondering how I can check if there is at least one product that productTax is set to 'true' and there is a relationship between one account and one product. In other words, if there is a product with tax set to true and at least one account contains a relationship with this product (by productIdOne and productIdTwo) then the rule result should pass;
The main issue is that the list of the product relationship is inside the account list.
Thanks for any advice
You have a rather straight-forward set of conditions, so it is possible to write a relatively simple rule to check them. I will consider each condition separately and then combine them into a final rule.
As you wrote:
there is at least one product that productTax is set to 'true'
Now, as you mentioned, your rule inputs are the Request instance which contains two lists (products, accounts.) We'll start by declaring that:
rule "Account exists with taxed product"
when
Request( $products: product != null,
$accounts: accounts != null )
Next, we want to find the taxed product. If we only wanted to prove the existence of the product, we could use an exists( ... ) condition, which is extremely fast. However since we want to do further comparisons, we'll want to actuall find the product with this condition and save a reference to it.
$taxedProduct: Product( productTax == "true" ) from $products
I've assumed here that any value other than exactly "true" is indicative of an untaxed product. You should adjust as needed (and possibly consider changing this type to a boolean.)
The next condition is to find the account:
there is a relationship between one account and [the taxed] product
First, we'll need to update our $taxedProduct declaration and get references to its ids:
$taxedProduct: Product( productTax == "true",
$id1: productIdOne,
$id2: productIdTwo ) from $products
Now we need to find an account with a matching relationship.
$account: Account( $relationships: productsRelationship != null ) from $accounts
exists( ProductRelationship( productIdOne == $id1,
productIdTwo == $id2 ) from $relationships )
Here, I used an exists condition for the relationship because we don't need to refer to the relationship itself ever again, just verify that the relationship exists. I did declare a variable $account to point to the account that has the product relationship.
Putting it all together, we have:
rule "Account exists with taxed product"
when
Request( $products: product != null,
$accounts: accounts != null )
$taxedProduct: Product( productTax == "true",
$id1: productIdOne,
$id2: productIdTwo ) from $products
$account: Account( $relationships: productsRelationship != null ) from $accounts
exists( ProductRelationship( productIdOne == $id1,
productIdTwo == $id2 ) from $relationships )
then
// We have a taxed product $taxedProduct
// and an associated account $account
end
When I first started with Drools I found it difficult to wrap my head around the way it treated objects in lists, which is why the ProductRelationship sub-list seems like a tricky issue on its face. What Drools is going to do is iterate through the $products list and find those Product instances that meet our criteria (namely, have productTax == "true".) Once it has found these taxed products, it then similarly goes through the $accounts list and finds all Accounts that meet the criteria (which have a productsRelationship list.) Then for each of those accounts, it is going to test that there exists a relationship as we've defined.
This is a simplified explanation, of course, but it helps to form a mental model of roughly what Drools is doing here. In reality Drools is much more efficient than then roughly O(n^3) workflow I've just described.
An interesting thing you should keep in mind is that this rule is not going to "stop" as soon as it finds a match. If you have two taxed products that have a relationship to a single account, this rule will fire twice -- once for each taxed product. Or, alternatively, if you have one taxed product and two accounts that have a relationship to it, the rule will fire twice (once for each account.) Basically, the rule will fire once for each "match" it finds in the given request.
can you try this following
rule "sample"
no-loop
when
request:Request(accountList: accounts)
request1:Request(productList: product)
Account(productsRelationshipList:ProductRelationship) from accountList
Product(productId contains productsRelationshipList, productTax = true ) from productList
then
System.out.println("Rule fired satisfied");
end

Using enum in drools

I am solving an employee rostering problem. One of the constraints there is that Employee from each "type" should be present on every day. Type is defined as an enum.
I have right now configured this rule as follows:
rule "All employee types must be covered"
when
not Shift(employeeId != null, $employee: getEmployee(), $employee.getType() == "Developer")
then
scoreHolder.addHardConstraintMatch(kcontext, -100);
end
This works fine. However I would have to configure a similar rule for all possible employee types.
To generalize it, I tried this:
rule "All employee types must be covered"
when
$type: Constants.EmployeeType()
not Shift(employeeId != null, $employee: getEmployee(), $employee.getType() == $type.getValue())
then
scoreHolder.addHardConstraintMatch(kcontext, -100);
end
However, this rule doesn't get executed. Below is my enum defined in Constants file
public enum EmployeeType {
Developer("Developer"),
Manager("Manager");
private String value;
Cuisine(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
What am I doing wrong?
I guess the problem is that you are never inserting the enums in your session (they are not facts).
One way to solve it is to manually insert them:
for(EmployeeType type : Constants.EmployeeType.values()){
ksession.insert(type);
}
Another way is to make your rule fetch all the possible values from the enum:
rule "All employee types must be covered"
when
$type: Constants.EmployeeType() from Constants.EmployeeType.values()
not Shift(employeeId != null, $employee: getEmployee(), $employee.getType() == $type.getValue())
then
scoreHolder.addHardConstraintMatch(kcontext, -100);
end
Hope it helps,

Compare pair of attributes using "IN"

The domain that I'm describing is not the same that I use (with this in mind, there is no need to worry with the fact the the sample class doesn't use Enum for the type, and other things), but describe well my problem. I want to make a query that filter transactions by username and type.
public class Transaction {
private String userName;
private String type;
private BigDecimal value;
...
}
I would like to search all the transactions that match a list of [{'username', 'type'}], where the couple {"username", "type"} is chosen by the user, and the user can choose as many as he wants
For instance: Find all :
"Debit" transactions made by "Rafael"
"Credit" transactions made by "Daniel"
"Debit" transactions made by "Monica"
"Credit" transactions made by "Monica"
I have two possible solutions for my problem, but I don't like any
POSSIBLE SOLUTION 1
I could dynamically add n "OR" clauses to my query, like:
"WHERE (t.userName = 'Rafael' AND t.type = 'Debit')
OR (t.userName = 'Daniel' AND t.type = 'Credit')
... "
POSSIBLE SOLUTION 2
I could concatenate the "username" to the "type" and check if the result is contained by a list of concatenated values of "username" with "type" which was generated dynamically.
CONCAT(t.userName, '-', t.type) IN (list)
for the example, the variable "list" would have the following values:
['Rafael-Debit', 'Daniel-Credit', 'Monica-Debit', 'Monica-Credit']
I'm using JPA, does anyone have a suggestion?

Morphia MongoDB check for null and non existing field

I am new to both Morphia and MongoDB. Is there a way to check using Morphia that a certain field in my database is not null and also exists. For example from the following record of a user from a collection of users in database:
{ "_id" : ObjectId("51398e6e30044a944cc23e2e"),
"age" : 21 ,
"createdDate" : ISODate("2013-03-08T07:08:30.168Z"),
"name" : "Some name" }
How would I use a Morphia query to check if field "createdDate" is not null and exists.
EDIT:
I am looking for a solution in Morphia. So far I have come up with this:
query.and(query.criteria("createdDate").exists(),
query.criteria("createdDate").notEqual(null));
From documentation, I learnt Morphia does not store empty or null fields. Hence the justification for notEqual(null).
EDIT 2: From the answers I can see the problem needs more explanation. I cannot modify the createdDate. To elaborate: the example above is less complex than my actual problem. My real problem has sensitive fields which I cannot modify. Also to complicate things a bit more, I do not have control over the model otherwise I could have used #PrePersist as proposed in one of the answers.
Is there a way to check for null and non existing field when I have no control over the model and I am not allowed to modify fields?
From the documentation, Morphia does not store Null/Empty values (by default) so the query
query.and(
query.criteria("createdDate").exists(),
query.criteria("createdDate").notEqual(null)
);
will not work since it seems you are not able to query on null, but can query for a specific value.
However, since you can only query for a specific value, you can devise a workaround where you can update the createdDate field with a date value that is never used in your model. For example, if you initialize a Date object with 0, it will be set to the beginning of the epoch, Jan 1st 1970 00:00:00 UTC. The hours you get is the localized time offset. It will be sufficient if your update only involves modifying the matching element(s) in mongo shell, hence it would look similarly to this:
db.users.update(
{"createdDate": null },
{ "$set": {"createdDate": new Date(0)} }
)
You can then use the Fluent Interface to query on that specific value:
Query<User> query = mongoDataStore
.find(User.class)
.field("createdDate").exists()
.field("createdDate").hasThisOne(new Date(0));
It would be much simpler when defining your model to include a prePersist method that updates the createdDate field. The method is tagged with the #PrePersist annotation so that the date is set on the order prior to it being saved. Equivalent annotations exist for #PostPersist, #PreLoad and #PostLoad.
#Entity(value="users", noClassNameStored = true)
public class User {
// Properties
private Date createdDate;
...
// Getters and setters
..
#PrePersist
public void prePersist() {
this.createdDate = (createdDate == null) ? new Date() : createdDate;
}
}
When you first create your Morphia instance, before calling morphia.mapPackage() do this:
morphia.getMapper().getOptions().setStoreNulls(true);
to have Morphia store null values.
Anyway, you should be able to query non-null values with:
query.field("createdDate").notEqual(null);
In mongo you can use this query:
db.MyCollection.find({"myField" : {$ne : null}})
This query will return objects that have the field 'myField' and has a value that is not null.