Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected behavior when switching to BAVET for entity with nullable planning variable

OptaPlanner 8.36.0.Final

When using ConstraintStreamImplType.DROOLS the following constraint behaves as expected:

return constraintFactory
    .forEach(Ewe.class)
    .groupBy(ConstraintCollectors.count())
    .penalize(level, 
    (count) -> {
        return activeRange.check(count));
    }
    )
    .asConstraint(name);

Ewe is a PlanningEntity that has a nullable PlanningVariable Ram. There are 100 Ewes of which between 50 and 75 should be assigned rams. This constraint penalizes under or over allocation. The constraint produces the expected result.

When I change to ConstraintStreamImplType.BAVET the solver ends with no ewes assigned and the score is zero, so the solution picked is one with none of the Ewes assigned and the constraint is not evaluated, therefore the zero score.

It seems like BAVET does not evaluate this constraint because there are zero entities that match. On the other hand DROOLS evaluates the constraint when there are zero matches.

Is this difference between DROOLS and BAVET expected?

I validated this behavior change by only switching between ConstraintStreamImplType.BAVET and ConstraintStreamImplType.DROOLS with no other change

Edit: Ultimately I want to count entries even including a zero count as a possible answer.

like image 960
Steven Avatar asked Dec 31 '25 19:12

Steven


1 Answers

Based off of the code you submitted in one of your comments, here's my analysis. BAVET behaves correctly, on account of the following:

  • forEach() will not forward any entities without variables assigned.
  • If groupBy has nothing on input, it does not evaluate anything. count() of nothing is not 0, it simply doesn't happen. (In other words: zero count is not possible in groupBy.) Therefore the resulting score for entities with null variables is 0.
  • Once you assign a variable to the first entitiy, the count is suddenly 1, making the total score -4. This is worse than 0, and therefore it is not picked.

In a way, this could be considered a score trap. In over-constrained problems (problems with nullable vars), we typically solve it by introducing another constraint which penalizes the number of unassigned entities. This gives the solver an incentive to actually assign entities. (And that, in turn, would trigger the groupBy(...).)

The question now becomes - why does DROOLS behave differently? This almost certainly looks like a bug. groupBy(count()) violates the general CS contract and produces something out of nothing. However, for backwards compatibility reasons, I do not think we will be fixing that; this behavior is far too exposed by now for us to change it, even if it is not correct.

As a side note, groupBy(groupKey, collector) does not show this behavior; only groupBy(collector) without a group key, as used in your code.

like image 106
Lukáš Petrovický Avatar answered Jan 06 '26 20:01

Lukáš Petrovický



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!