I have two PlanningEntity classes like this:
@PlanningEntity
class MyPlanningEntity {
@PlanningVariable
MyPlanningVariable myPlanningVariable;
}
@PlanningEntity
class MyOtherPlanningEntity {
@ShadowVariable(...)
List<MyPlanningEntity> myPlanningEntities;
int weight;
}
I want to write constraints that compute penalties based on what's in MyOtherPlanningEntity.myPlanningEntities
.
At first I tried this:
Constraint myConstraint(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(MyOtherPlanningEntity.class)
.penalize(HardSoftScore.ONE_HARD, myOtherPlanningEntity -> myFunction(myOtherPlanningEntity.weight, myOtherPlanningEntity.myPlanningEntities)
.asConstraint("My constraint");
}
However, I think this wasn't always working because there wasn't an explicit dependency on the MyPlanningEntity
instances in MyOtherPlanningEntity.myPlanningEntities
(e.g., they weren't part of a join).
What is the right way to join on the variable number of MyPlanningEntity
s in myOtherPlanningEntity.myPlanningEntities
? I could build separate constraints for each possible length up to some max, but that seems verbose and inflexible. I thought about maybe using flattenLast
but for my constraints I want to keep MyOtherPlanningEntity
in the tuple with the MyPlanningEntity
s (since it has other information on it I want, like the weight). It's also not clear from the documentation if the elements resulting from flattenLast
will be tracked for changes, which is the real issue I'm solving here.
What's the right way to approach this?
The correct way would be to use a join
(to declare the dependency on MyPlanningEntity
) + filter
(to only include MyPlanningEntity
inside the myPlanningEntities list) + groupby
(So each MyOtherPlanningEntity
is only counted once):
Constraint myConstraint(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(MyOtherPlanningEntity.class)
.join(MyPlanningEntity.class)
.filter((container, child) -> container.myPlanningEntities.contains(child))
.groupby((container, child) -> container, ConstraintCollectors.toList((container, child) -> child))
.penalize(HardSoftScore.ONE_HARD, (myOtherPlanningEntity, myPlanningEntityList) -> myFunction(myOtherPlanningEntity.weight, myPlanningEntityList))
.asConstraint("My constraint");
}
That being said, if it is possible to refactor the penalize so it acts on items of the myPlanningEntityList instead of the entire myPlanningEntityList, please do so, since there will be numerous performance benefits. For instance, if your penalty function look like this:
int myFunction(int weight, List<MyPlanningEntity> myPlanningEntityList) {
int out = 0;
for (MyPlanningEntity entity : myPlanningEntityList) {
out += weight;
}
return out;
}
then it can be refactored to look like this:
int myFunction(int weight, MyPlanningEntity myPlanningEntity) {
return weight;
}
and the above stream changes to
Constraint myConstraint(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(MyOtherPlanningEntity.class)
.join(MyPlanningEntity.class)
.filter((container, child) -> container.myPlanningEntities.contains(child))
.penalize(HardSoftScore.ONE_HARD, (myOtherPlanningEntity, myPlanningEntity) -> myFunction(myOtherPlanningEntity.weight, myPlanningEntity))
.asConstraint("My constraint");
}