To satisfy x out of y constraints - drools

I am making a timetabling program which does one to one matches from SubjectTeacherPeriod (planning entity) to Period. There comes a case when I need to: "for y periods, atleast x of the SubjectTeacherPeriod must match a match_condition"
For example, I want to constrain 3 particular periods, atleast two of them to be taught by teachers who match to asst prof.
Here is the data structure holding such a constraint:
Class XOfYPeriods
SomeType match_condition
int x
List<Period> Periods //problem
SubjectTeacherPeriod has a Period, of course
class SubjectTeacherPeriod
int id
SomeType attrib
Period period
How do I write a rule that evaluates individual Periods from a list to check if x number of SubjectTeacherPeriods that are allocated those Periods meet the match condition?
Do correct me if I am defining my classes in bad form.
For the sake of example, here is a statement to be evaluated to determine a match: eval(matches($stp_attrib,$match_condition))
Sorry for the use of Pseudocode if it confused more than clarified. The SomeType is actually List< String> and thus the match condition is checked with a Collections.disjoint

I will give it a try, but not sure I completely understand your problem statement:
rule "X of Y Periods"
when
$c : XOfYPeriods( )
$list : List( size > $c.x ) from
accumulate( $stp : SubjectTeacherPeriod( matches(attrib, $c.match_condition),
period memberOf $c.periods ),
collectList( $stp ) )
then
// $list of STP that match the condition and
// whose period matches one of the periods in the list
end
Hope it helps.

Related

Overriding `Comparison method violates its general contract` exception

I have a comparator like this:
lazy val seq = mapping.toSeq.sortWith { case ((_, set1), (_, set2)) =>
// Just propose all the most connected nodes first to the users
// But also allow less connected nodes to pop out sometimes
val popOutChance = random.nextDouble <= 0.1D && set2.size > 5
if (popOutChance) set1.size < set2.size else set1.size > set2.size
}
It is my intention to compare sets sizes such that smaller sets may appear higher in a sorted list with 10% chance.
But compiler does not let me do that and throws an Exception: java.lang.IllegalArgumentException: Comparison method violates its general contract! once I try to use it in runtime. How can I override it?
I think the problem here is that, every time two elements are compared, the outcome is random, thus violating the transitive property required of a comparator function in any sorting algorithm.
For example, let's say that some instance a compares as less than b, and then b compares as less than c. These results should imply that a compares as less than c. However, since your comparisons are stochastic, you can't guarantee that outcome. In fact, you can't even guarantee that a will be less than b next time they're compared.
So don't do that. No sort algorithm can handle it. (Such an approach also violates the referential transparency principle of functional programming and will make your program much harder to reason about.)
Instead, what you need to do is to decorate your map's members with a randomly assigned weighting - before attempting to sort them - so that they can be sorted consistently. However, since this happens at the start of a sort operation, the result of the sort will be different each time, which I think is what you're looking for.
It's not clear what type mapping has in your example, but it appears to be something like: Map[Any, Set[_]]. (You can replace the types as required - it's not that important to this approach. For example, say mapping actually has the type Map[String, Set[SomeClass]], then you would replace references below to Any with String and Set[_] to Set[SomeClass].)
First, we'll create a case class that we'll use to score and compare the map elements. Then we'll map the contents of mapping to a sequence of elements of this case class. Next, we sort those elements. Finally, we extract the tuple from the decorated class. The result should look something like this:
final case class Decorated(x: (Any, Set[_]), rand: Double = random.nextDouble)
extends Ordered[Decorated] {
// Calculate a rank for this element. You'll need to change this to suit your precise
// requirements. Here, if rand is less than 0.1 (a 10% chance), I'm adding 5 to the size;
// otherwise, I'll report the actual size. This allows transitive comparisons, since
// rand doesn't change once defined. Values are negated so bigger sets come to the fore
// when sorted.
private def rank: Int = {
if(rand < 0.1) -(x._2.size + 5)
else -x._2.size
}
// Compare this element with another, by their ranks.
override def compare(that: Decorated): Int = rank.compare(that.rank)
}
// Now sort your mapping elements as follows and convert back to tuples.
lazy val seq = mapping.map(x => Decorated(x)).toSeq.sorted.map(_.x)
This should put the elements with larger sets towards the front, but there's 10% chance that sets appear 5 bigger and so move up the list. The result will be different each time the last line is re-executed, since map will create new random values for each element. However, during sorting, the ranks will be fixed and will not change.
(Note that I'm setting the rank to a negative value. The Ordered[T] trait sorts elements in ascending order, so that - if we sorted purely by set size - smaller sets would come before larger sets. By negating the rank value, sorting will put larger sets before smaller sets. If you don't want this behavior, remove the negations.)

Optaplanner Curriculum example, Explanation for curriculumCourseScoreRules.drl

I'm currently reading the course curriculum example of optaplanner and I can't seem to understand this:
rule "conflictingLecturesSameCourseInSamePeriod"
when
// line 1
Lecture($leftId : id, $leftCourse : course, $period : period, period != null)
// line 2
Lecture(course == $leftCourse, period == $period, id > $leftId)
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
Questions are:
- What's the difference between the Lecture() in line 1 and the Lecture in line 2?
- I understand the variable assignements are happening in Line 1 but in Line 2, what's the difference between course and $leftCourse, period and $period and id and $leftId
So far, I can't seem to find any explanation in the documentation
A binding such as $leftCourse : course establishes $leftCourse as a variable that refers to the field course of a Lecture object.
The lecture in line 1 is identified by its id; the one in line 2 has an id that is greater. Assuming that id values are primary keys, this combination would basically match all possible unordered pairs of Lectures, but ...
... the constraint course == $leftCourse restricts the pairings to those of identical courses and ...
... the constraint period == $period furthermore restricts it to equal (non-null) periods.
In other words, the planning rules out an assignment for two different Lectures for the same course in the same period.

How do accumulate functions actually work?

Let's say we have the next example :
There are certain products that belong to certain product groups, and we want the total price summed up in an logical fact as either the products in the product group change or as their price changes.
private class ProductGroup {
private String name;
}
public class Product {
private ProductGroup productGroup;
private int price;
}
This is the class that will be intended for the logical facts that will get inserted by the summation rule in Drools.
private class ProductGroupTotalPrice {
private ProductGroup productGroup;
private int totalPrice;
}
There is a rule that sums up the total price for a given ProductGroup.
rule "total price for product group"
when
$productGroup : ProductGroup()
$totalPrice : Number() from accumulate(
Product(productGroup == $productGroup, $price : price),
sum($price)
)
then
insertLogical(new ProductGroupTotalPrice($productGroup, $totalPrice));
end
So my question is what will the logic be when Products from a given ProductGroup are added/deleted from the working memory, they change the ProductGroup or their price is being changed?
- Lets say that the summation is done at the beggining of the application based on the current state and the logical fact is inserted into the working memory with the total price. Then the price for one Product is changed at one point so the totalPrice needs to be updated.
Here are three cases how the process would possibly be done :
Incrementally with doing a constant time calculation. Only take into account the change that has happened and subtract the old price from the total and add the new one for the one Product that was changed. (Excelent)
The whole summation is done again but the Product instances that meet the criteria(that are from the given ProductGroup) are already known, they are not searched for. (Good)
Besides the summation a loop through all the Product instances in the working memory is done to see which ones meet the criteria(that are from the given ProductGroup). (Bad)
Is the logic that is implemented one of these three cases or it is something else?
You can look at the documentation of the other form of accumulate, i.e., the one where you can define the steps for initialization, processings (note the plural!) and returning an arbitrary function. Some functions permit the reverse operation so that removing a fact that has been used for computing the function result can be handled: e.g., 'sum'. (But compare 'max'.)
So I think that your accumulate pattern will be updated efficiently.
However, I think that this does not mean that your logically inserted ProductGroupTotalPrice will be updated. (Try it, I may be wrong.)
I would use a simple rule
rule "total price for product group"
when
$productGroup: ProductGroup()
Number( $totalPrice: intValue ) from accumulate(
Product(productGroup == $productGroup, $price : price),
sum($price)
)
$pgtp: ProductGroupTotalPrice( productGroup == $productGroup,
totalPrice != $totalPrice )
then
modify( $pgtp ){ setTotalPrice( $totalPrice ) }
end
and an addition rule to insert an initial ProductGroupTotalPrice for the product group with totalPrice 0.

Can't make accumulate to work properly

I have the following class structure (Class A contains Class B):
class A {
B object;
...
}
and I'm trying to do something whenever the average of the accumulation of a specific field in class B is above a given value.
so I'm trying to write the following :
when
A($var1 : object)
accumulate( B($num:num) from $var1;
$avg1 : avg ($num); $avg1 < 10000)
then ...
what happens is that instead of accumulating all entities in the session and calculating the average for all of them, the average is being calculated on each entity separately.
so if the session already contains 5 numeric values which bigger than 10000 and another one is inserted then the "then" part is invoked 6 times (each one with average value the equals to the numeric value itself) instead of only once.
Do you have some hint that might help me to solve that?
thanks.
You have to accumulate over all A facts accessing the field num of field object.
when
accumulate( A( $var1: object );
$avg1: avg($var1.getNum()); $avg1 < 10000)
then ...
Inserting all B objects as facts as well would permit you to write the straightforward
when
accumulate( B( $num: num );
$avg1: avg($num); $avg1 < 10000)
then ...

Why are products called minterms and sums called maxterms?

Do they have a reason for doing so? I mean, in the sum of minterms, you look for the terms with the output 1; I don't get why they call it "minterms." Why not maxterms because 1 is well bigger than 0?
Is there a reason behind this that I don't know? Or should I just accept it without asking why?
The convention for calling these terms "minterms" and "maxterms" does not correspond to 1 being greater than 0. I think the best way to answer is with an example:
Say that you have a circuit and it is described by X̄YZ̄ + XȲZ.
"This form is composed of two groups of three. Each group of three is a 'minterm'. What the expression minterm is intended to imply it that each of the groups of three in the expression takes on a value of 1 only for one of the eight possible combinations of X, Y and Z and their inverses." http://www.facstaff.bucknell.edu/mastascu/elessonshtml/Logic/Logic2.html
So what the "min" refers to is the fact that these terms are the "minimal" terms you need in order to build a certain function. If you would like more information, the example above is explained in more context in the link provided.
Edit: The "reason they used MIN for ANDs, and MAX for ORs" is that:
In Sum of Products (what you call ANDs) only one of the minterms must be true for the expression to be true.
In Product of Sums (what you call ORs) all the maxterms must be true for the expression to be true.
min(0,0) = 0
min(0,1) = 0
min(1,0) = 0
min(1,1) = 1
So minimum is pretty much like logical AND.
max(0,0) = 0
max(0,1) = 1
max(1,0) = 1
max(1,1) = 1
So maximum is pretty much like logical OR.
In Sum Of Products (SOP), each term of the SOP expression is called a "minterm" because,
say, an SOP expression is given as:
F(X,Y,Z) = X'.Y'.Z + X.Y'.Z' + X.Y'.Z + X.Y.Z
for this SOP expression to be "1" or true (being a positive logic),
ANY of the term of the expression should be 1.
thus the word "minterm".
i.e, any of the term (X'Y'Z) , (XY'Z') , (XY'Z) or (XYZ) being 1, results in F(X,Y,Z) to be 1!!
Thus they are called "minterms".
On the other hand,
In Product Of Sum (POS), each term of the POS expression is called a "maxterm" because,
say an POS expression is given as: F(X,Y,Z) = (X+Y+Z).(X+Y'+Z).(X+Y'+Z').(X'+Y'+Z)
for this POS expression to be "0" (because POS is considered as a negative logic and we consider 0 terms), ALL of the terms of the expression should be 0. thus the word "max term"!!
i.e for F(X,Y,Z) to be 0,
each of the terms (X+Y+Z), (X+Y'+Z), (X+Y'+Z') and (X'+Y'+Z) should be equal to "0", otherwise F won't be zero!!
Thus each of the terms in POS expression is called a MAXTERM (maximum all the terms!) because all terms should be zero for F to
be zero, whereas any of the terms in POS being one results in F to be
one. Thus it is known as MINTERM (minimum one term!)
I believe that AB is called a minterm is because it occupies the minimum area on a Venn diagram; while A+B is called a MAXTERM because it occupies a maximum area in a Venn diagram. Draw the two diagrams and the meanings will become obvious
Ed Brumgnach
Here is another way to think about it.
A product is called a minterm because it has minimum-satisfiability where as a sum is called a maxterm because it has maximum-satisfiability among all practically interesting boolean functions.
They are called terms because they are used as the building-blocks of various canonical representations of arbitrary boolean functions.
Details:
Note that '0' and '1' are the trivial boolean functions.
Assume a set of boolean variables x1,x2,...,xk and a non-trivial boolean function f(x1,x2,...,xk).
Conventionally, an input is said to satisfy the boolean function f, whenever f holds a value of 1 for that input.
Note that there are exactly 2^k inputs possible, and any non-trivial boolean-function can satisfy a minimum of 1 input to a maximum of 2^k -1 inputs.
Now consider the two simple boolean functions of interest: sum of all variables S, and product of all variables P (variables may/may-not appear as complements). S is one boolean function that has maximum-satisfiability hence called as maxterm, where as P is the one having minimum-satisfiability hence called a minterm.