Optapy : Use toList function on group by from two joined classes - TypeError : No matching overloads - group-by

I am currently working on an employee rostering problem in which one constraint's goal is to avoid gaps in an employee schedule using a constraint stream. The idea is to get for each employee the timeslots they are assigned to along with their availability, retrieve those informations in a list, then perform the check on the returned list.
The constraint stream is as following :
def continuous_shifts(constraint_factory: ConstraintFactory, score = HardSoftScore.ONE_HARD):
return constraint_factory \
.for_each(timeslot_assignment_class) \
.join(availability_class, [
Joiners.equal(
lambda timeslot_assignment : timeslot_assignment.resource.resource_id,
lambda availability : availability.resource.resource_id
)
]) \
.group_by(lambda timeslot_assignment, availability : timeslot_assignment.resource.resource_id,
lambda timeslot_assignment, availability : availability.resource.resource_id,
ConstraintCollectors.to_list()) \
.penalize("holes in schedule",score,lambda timeslot_list : holes_in_list(timeslot_list))
What I want to do is to join the timeslot_assignment with the availabilities based on the resource_id attribute, group them by resource (i.e. employee) then return those groups in lists in which I can test gaps in schedule in the penalize part.
I have to use a join on the availability class because they are not contained as an attribute in resources and are stored separately for navigation purposes.
The main struggle I am having is in returning a list in the group by function. In the showed case, I got this error :
TypeError: No matching overloads found for org.optaplanner.constraint.streams.drools.bi.DroolsAbstractBiConstraintStream.groupBy(proxy.PythonBiFunction,proxy.PythonBiFunction,org.optaplanner.core.api.score.stream.DefaultUniConstraintCollector),
Followed by a list of suitable options for ConstraintCollectors.
As I read in other posts, I understood that there can be overload issues with functions used with ConstraintCollectors and that the types might have to be manually specified. I tried other combinations like casting the lambda functions to java BiFunctions, or changing the group by function like this :
.group_by(lambda timeslot_assignment, availability : (timeslot_assignment.resource.resource_id,availability.resource.resource_id),
ConstraintCollectors.to_list())
which changed the error message according to the modified classes/stream cardinality but with no improvement. I also tried swapping the to_list function for toList which resulted in no change.
I can't figure out if the problem comes from the way I joined/grouped or if this is more of a type issue in which some types have to be specified.
Any help on the matter would be greatly appreciated.

The problem is to_list() is actually to_list(lambda x: x), which is a UniConstraintCollector, and thus can only be used with UniConstraintStream (that is, a constraint stream of only one element). You have a BiConstraintStream, so to_list() will not work. What will work is to_list(lambda x, y: (x, y)), which is a BiConstraintCollector (and thus will work with BiConstraintStream). Thus, the constraint stream should look like this:
def continuous_shifts(constraint_factory: ConstraintFactory, score = HardSoftScore.ONE_HARD):
return constraint_factory \
.for_each(TimeslotAssignment) \
.join(Availability,
Joiners.equal(
lambda timeslot_assignment : timeslot_assignment.resource.resource_id,
lambda availability : availability.resource.resource_id
)
) \
.group_by(ConstraintCollectors.to_list(lambda timeslot_assignment, availability : (timeslot_assignment.resource.resource_id,availability.resource.resource_id))) \
.penalize("holes in schedule",score,lambda timeslot_list : holes_in_list(timeslot_list))
Note: optapy no longer require usage of get_class; you can directly use the relevant Python classes. Additionally, casting is no longer required for group by's, and you don't need to pass Joiners as a list. (although the old code will still work).
def continuous_shifts(constraint_factory: ConstraintFactory, score = HardSoftScore.ONE_HARD):
return constraint_factory \
.for_each(TimeslotAssignment) \
.join(Availability,
Joiners.equal(
lambda timeslot_assignment : timeslot_assignment.resource.resource_id,
lambda availability : availability.resource.resource_id
)
) \
.group_by(ConstraintCollectors.to_list(lambda timeslot_assignment, availability : (timeslot_assignment.resource.resource_id,availability.resource.resource_id))) \
.penalize("holes in schedule",score,lambda timeslot_list : holes_in_list(timeslot_list))
Like Lukas said, to_list is a performance killer. If you are looking for singular gaps, I suggest looking at the OptaPy employee scheduling quickstart, in particular the "at least two hours between two shifts" constraint.
In regards to finding (global) consecutive shifts and breaks in shifts (i.e. need to penalize/reward based on a non-linear function on the number of consecutive shifts/breaks); that is best done using a ConstraintCollector; there is currently an experimental one in optaplanner-examples, but that is not accessible to optapy. Once that collector is available in optaplanner-core, it will also be available in optapy. You cannot create your own custom ConstraintCollector currently, but it will be available in a future version of optapy.

Related

How to join 2 sets of Prometheus metrics?

AKS = 1.17.9
Prometheus = 2.16.0
kube-state-metrics = 1.8.0
My use case: I want to alert when 1 of my persistent volumes are not in a "Bound" phase and only when this falls within a predefined set of namespaces.
This got me to my first attempt at joining Prometheus metrics - so, please bear with me : )
I opted to use the following to obtain the pv phase:
kube_persistentvolume_status_phase{phase="Bound",job="kube-state-metrics"}
Renders:
kube_persistentvolume_status_phase{instance="10.147.5.110:8080",job="kube-state-metrics",persistentvolume="pvc-33197ae6-d42a-777e-b8ca-efbd66a8750d",phase="Bound"} 1
kube_persistentvolume_status_phase{instance="10.147.5.110:8080",job="kube-state-metrics",persistentvolume="pvc-165d5006-erd4-481e-8acc-eed4a04a3bce",phase="Bound"} 1
This worked well, except for the fact that it does not include the namespace.
So I managed to determine the persistentvolumeclaim namespaces with this:
kube_persistentvolumeclaim_info{namespace=~"monitoring|vault"}
Renders:
kube_persistentvolumeclaim_info{instance="10.147.5.110:8080",job="kube-state-metrics",namespace="vault",persistentvolumeclaim="vault-file",storageclass="default",volumename="pvc-33197ae6-d42a-777e-b8ca-efbd66a8750d"} 1
kube_persistentvolumeclaim_info{instance="10.147.5.110:8080",job="kube-state-metrics",namespace="monitoring",persistentvolumeclaim="prometheus-prometheus-db-prometheus-prometheus-0",storageclass="default",volumename="pvc-165d5006-erd4-481e-8acc-eed4a04a3bce"} 1
So my idea was to join these sets with the matching values in the following fields:
(kube_persistentvolume_status_phase)persistentvolume
on
(kube_persistentvolumeclaim_info)volumename  
BUT, if I understood it correctly you are only able to join two metrics sets on labels that match exactly (text and their values). I hence opted for the "instance" and "job" labels as these were common on both sides and matching. 
kube_persistentvolume_status_phase{phase!="Bound",job="kube-state-metrics"}  * on(instance,job) group_left(namespace) kube_persistentvolumeclaim_info{namespace=~"monitoring|vault"}
Renders:
Error executing query: found duplicate series for the match group {instance="10.147.5.110:8080" , job="kube-state-metrics"} on the right hand-side of the operation: [{__name__="kube_persistentvolumeclaim_info", instance="10.147.5.110:8080", job="kube-state-metrics", namespace="monitoring", persistentvolumeclaim="alertmanager-prometheusam-db-alertmanager-prometheusam-0", storageclass="default", volumename="pvc-b8406fb8-3262-7777-8da8-151815e05d75"}, {__name__="kube_persistentvolumeclaim_info", instance="10.147.5.110:8080", job="kube-state-metrics", namespace="vault", persistentvolumeclaim="vault-file", storageclass="default", volumename="pvc-33197ae6-d42a-777e-b8ca-efbd66a8750d"}];many-to-many matching not allowed: matching labels must be unique on one side
So in all fairness, the query does communicate well on what the problem is - so I attempted to solve this with the "ignoring" option - attempting to keep only the matching labels and values (instance and job) and "excluding/ignoring" the non-matching ones on both sides. This did not work either - resulting in a parsing error. Which in turn nudged me to take a step back and reassess what I am doing.
I am just a bit concerned that I am perhaps barking up the wrong tree here.
My question is: Is this at all possible and if so how? or is there perhaps another, more prudent way to achieve this?
Thanks in advance!

newbie - Drools filter from same fact

I am newbie to drools. Need understanding how to cascade filtered data from one condition to next one as shown below.
rule "rulename"
when
$var1 : MainObj(filter1)
$var2 : SubObj() from $var1.getSubObj // need all SubObj without any filter here for then clause.
$var3 : SubObj(conditions) from $var2 // Need a subset of SubObj for another then clause
then
$var2.doSomething()
$var3.doVar3Stuff()
end
How can I achieve that?
Continuation of my original post..
i have below rule computing statistics of a rule execution as below.
rule "myrule"
when
$a:wrapper(isActive("ruleName") && dataTypeCode.equals("typeCodeinDB"))
$total : Number() from accumulate($entity: MyObj(ObjAttribute=='testValue') from $a.getMyObj() , count( $entity ))
$filter: MyObj(ObjAttribute=="testValue" && ObjAttribute2.equals("ValidValue")) from $a.getMyObj()
then
$filter.addToResults($filter.getRuleConstituents(),1,drools.getRule().getName(),null); // Add data for insertion in DB at later stage
$filter.addPayloadExecution($a.dataTypeCode(),$a.getMyObj().toString(),$a.getMyObj().size()); //Total Count of MyObj
$filter.addRuleExecutions(drools.getRule().getName(), $total); // anotehr slice of data
end
Here.. i am computing two stats.. Total count of MYObj(), apply some filter for data validation and compute count of valid list ($filter is valid data)
When I run that code I am getting error as below
Exception executing consequence for rule "MyRule" in moneta.rules: [Error: $filter.addRuleExecutions(drools.getRule().getName(), $total): null]
[Near : {... $filter.addToResults($filter.get ....}]
^
[Line: 1, Column: 1]
Need advice on resolving this.
I can't tell if you're trying to change the data in one rule and make it visible in another, or if you're trying to do a single complex condition in a single rule. So I'll outline how both work.
Complex conditions
The pseudo-code in your rule indicates that you have a main object with a filter and a collection of sub-objects. You need to get a list of all sub-objects without a filter, and also to get a list of sub-objects that meet some sort of condition.
To collect these sublists, we have two available operations: accumulate and collect. Since you didn't give a real example, just some psuedo-code, I can't say for sure which is the correct one for you to use. The example I present below uses collect because it is appropriate for this use case. For more information about these operations, refer to the Drools documentation. I've linked to the section on operators; scroll down to see the details for collect and accumulate.
So imagine I have an app that is modeling a school, and my KindergartenClass object has a List of Student objects. In this case, KindergartenClass is the main object, and Student is the sub-object. My Students have names, ages, and an Allergies object that indicates food allergies. Students without Allergies have no allergy object. This is going to be the filter for the example rule -- finding the students without allergies.
The rule would then look like:
rule "EXAMPLE"
when
KindergartenClass( $students: students ) // $var1
// Find all students with no allergies, $var2
$var2: List() from collect( Student( allergies == null ) from $students )
// Find all students who are older than 5, $var3
$var3: List() from collect( Student( age > 5 ) from $students )
then
...
end
Obviously your right-hand-side would have to be adjusted because 'List' has no doSomething() and doVar3Stuff() methods. But without an actual example rule for what you're attempting I can't be more specific here.
You could get the individual students from the lists like this:
$studentWithoutAllergies: Student() from $var2
$studentOlderThan5: Student() from $var3
... but then your then-clause would trigger once for every single $studentWithoutAllergies and $studentOlderThan5. Most people don't want a cartesian product all elements in two lists and then having their consequences firing for each of those products.
Of course, you also talk about "cascading", though it's rather unclear what you mean. If you mean that you want $var3 to be a subset of what you found in $var2, then it's as simple as changing the from clause. So in the example I've been working with, if you actually want all students older than 5 who have no allergies, you can simply change the when clause to be:
// Find all students with no allergies, $var2
$var2: List() from collect( Student( allergies == null ) from $students )
// Find the subset of the non-allergy students who are older than 5, $var3
$var3: List() from collect( Student( age > 5 ) from $var2 )
Changing the data that triggers the rule
There are two keywords available for changing the data available to the left hand side (when clause): update and insert. Which one you use depends on what you're trying to do, and come with very different considerations in terms of performance.
When you ask Drools to fire a set of rules, it first goes through all of the rules and determines if the rule fires, given the data you've inputted. Basically it just goes through, in order, all of the when clauses and decides if the rule is activated or not. Then, once it has the sub-set of rules, in order, it goes through, one by one, executing the right hand side.
If you call update in your right hand side (then clause), then this process repeats: all rules are evaluated again, from the top, including the rule you just fired. If you call insert in your then clause, then the new data is put into working memory, and Drools re-evaluates all of the remaining rules in its execution list. (So, for example, if Drools had decided that rules A, B, C, and D met their when criteria, and B inserts a new fact into working memory, then C and D would be re-evaluated to make sure that they were still valid.)
This is how you'd use them:
rule "Example rule with Update"
when
$input: MyAwesomeInputObject(someValue == 0)
then
$input.setSomeValue(99);
$input.doSomeStuff();
update($input);
end
rule "Example rule with Insert"
when
Person(name == "Bob")
not( Age() )
then
Age age = new Age(18);
insert(age); // there is now an Age object in working memory
end
I'm not 100% familiar with your use case, but I'm going to assume you want to fire all of the filtering rules a second time after your initial rule fires, with the updated data. So I'd do something like this:
rule "rulename"
when
$var1 : MainObj(filter1)
$var2 : SubObj() from $var1.getSubObj // need all SubObj without any filter here for then clause.
$var3 : SubObj(conditions) from $var2 // Need a subset of SubObj for another then clause
then
$var2.doSomething();
$var3.doVar3Stuff();
update($var1); // update the var1 in working memory
end
I have no idea what your when clause is trying to do, since it doesn't appear to match the comments and the syntax is completely wonky. So only the right hand side (then clause) has been adjusted. I have added update($var1) so it will refire the rules with the new instance of $var1 that should have your changes in it.
Of course, once you start re-firing all rules, you run the risk of having rules loop. If this does happen, you will see spikes of CPU and apparent thread deadlocks (this is useful to alert on.) Note that there is a no-loop property, but it won't help when you're calling update.

How to generate code from custom scoping through instances?

I'm trying to write a code generator using xtext. There are instances of types declared in the corresponding DSL, attributes can be referenced through those instances by custom scoping (see code for example). The linking is performed directly from referencing element to attribute, so that there is no information about the surrounding instance - but for code generation, I exactly need the qualified name that is added in the DSL file. Is there any other possibility so that I can figure out through which instance the actual feature is referenced?
My first idea was to recall the ScopeProvider at code generation, which works but does not react on two instances of same type because the first matching Attribute is chosen - so if there are multiple instances, the generator cannot distinguish which one is meant.
Second idea was to include information from the corresponding DSL file, but I don't have any idea how to get this work. I already searched a lot if it is possible to get the corresponding DSL-file from the current model, but could not find any helpful answer.
Third idea was to include the instance as a hidden field in the referencing element - but I could not find any solution for this approach too.
Small extract of Grammar (simplified):
Screen:
(features += ScreenFeature)*
;
ScreenFeature:
name=ID ':' type=[ClientEntity]
;
ClientEntity:
(features += Feature)*
;
Feature:
name=ID ':' type=DefaultValue
;
DefaultValue:
'String'|'int'|'double'|'boolean'
;
ChangeViewParam:
param=[ScreenFeature|QualifiedName] ':' value=[ScreenFeature|QualifiedName]
;
DSL-Example:
ClientEntity Car {
id : int
name : String
}
Screen Details {
car : Car
car2 : Car
[...]
car2.id : car.id
}
Generation output of first approach (line: car2.id : car.id) :
car.id : car.id
Expected:
car2.id : car.id
I hope you can understand my problem and have an idea how to solve it. Thanks for your help!
You can use
org.eclipse.xtext.nodemodel.util.NodeModelUtils.findNodesForFeature(EObject, EStructuralFeature) to obtain the nodes for YourDslPackage.Literals.CHANGE_VIEW_PARAM__PARAM (should be only one) and ask that one for its text.
Alternatively you could split your param=[ScreenFeature|QualifiedName] into two references

Drools - Running a rule with an empty object

I'm trying to write a rule to calculate prices for an insurance product based on conditions. In the 'when' I'm using an object called AdditionalDriver, which contains the details for drivers other than the policy holder. From this, different prices can be calculated based on whether the additional driver is a parent, friend, spouse etc. See below:
when
AdditionalDriver($relToProp : relationToProposer)
then
String relToProp = $relToProp;
if(!relToProp.equals("P"))
{
//prices
}
end
"P" = parent.
This rule works when an additional driver has been added. However, if there is no additional driver, then the object is empty, and so the rule does not run. What do I need to do to get this rule to run, even when the object is empty?
Thanks in advance.
You should write one rule for each of the relative or acquaintance classes:
when
PolicyHolder( $phid: id )
AdditionalDriver( relationToProposer == "P", belongsTo == $phid )
then
//prices
end
For no additional driver being requested, write a rule
when
PolicyHolder( $phid: id )
not AdditionalDriver( belongsTo == $phid )
then
// cheaper prices
end
Don't use conditional statement in your consequences to further distinguish facts. This is a code smell.

Boolean flags handing in Drools conditions

We are working on a monitoring application in which we follow the processing of a task in a set of applications.
We have a set of drools rules matching our needs but we have some performance issues (we may have easily up to 50k objects in session).
We are looking for best pactices
This question is about bloolean flag usage.
We are working to remove most of org.drools.core.rule.constraint.MvelConstraint: Exception jitting: ... warns.
We have often such warn on boolean flags.
for example in:
rule "PropagateDeprecation"
when
$parent:BaseChainStep( $parent.Deprecated )
$child:BaseChainStep( $parent.Id == $child.Parent, !$child.Deprecated )
then
modify($child){
setDeprecated(true)
}
end
we have warn on both $parent.Deprecated and !$child.Deprecated.
We would like to understand why there is such warn on boolean flags.
We would like to know also the impacts of the warn on composed conditions.
For example in:
rule "App1_TriggerExpected"
when $chainStep:App1ChainStep(
HasChain
, HasParent
, !$chainStep.Deprecated
, Status in ("error", "closed")
, Places != null
, Analysis != null)
then
..
end
if we have the warn on the first condition HasChain, how is resolved the when clause ?
Does other conditions are evaluated too (with iteration on all App1ChainStep objects) or some "index" are still used to help ?
If its matter, we are using flags as boolean (and not Boolean) to ensure false value as default.
Edit:
The problem may be linked to extended classes. In our use case we have something like:
declare BaseChainStep
parent : GUID
deprecated : boolean
end
declare App1ChainStep extends BaseChainStep
// specific App1 fields
end
BaseChainStep fields may be manipulated in rules using App1ChainStep objects or BaseChainStep objects.
rule "deprecateApp1"
when $app1:App1ChainStep( BusinessLogicCondition )
then
modify($app1) {
setDeprecated(true)
}
end
Then the deprecated flag is propagated to App1 children using "PropagateDeprecation" rule.
Boolean flag causing warn are declared in BaseChainStep class.
Although you are deviating from the conventional way of accessing attributes, this should not trigger the warning you have reported. I can't reproduce this using 6.3.0. You should add (a) the Drools version (b) the Java code for a class BaseChainStep with which the problem can be reproduced with the rule as shown.
This is another (much simpler) way of writing rules combining boolean attributes:
rule bool1
when
X( big, fast )
then
System.out.println( "big fast X" );
end
You may even use boolean operators:
rule bool2
when
X( big && ! fast )
then
System.out.println( "big slow X" );
end
Note the simple use of field name, assuming conventional naming, e.g., big for the field, isBig and setBig for the accessors.