We are trying to solve a VRP with Optaplanner. The score calculation runs via constraint streams.
Now I have two vehicles (A and B) and want to schedule two jobs (J1 and J2). The construction heuristic (FIRST_FIT_DECREASING) schedules J1 to A and J2 to B, what is correct so far.
Now the two jobs also have an attribute "customer", and I want to assign a penalty if the customer of the two jobs is the same but the vehicles are different.
For this purpose, I have created a constraint in the ConstraintProvider that filters all jobs via groupBy that have the same customer but different vehicles.
If I now switch on the FULL_ASSERT_MODE, an IllegalStateException occurs after scheduling J2, because the score that is calculated incrementally is different from the score for the complete calculation. I suspect this is because the VariableListener, which recalculates the times of the jobs, only tells the ScoreDirector about a change to Job J2 for my shadowvariables and therefore only changes the score part that is related to it.
How can I tell Optaplanner that the score for J1 must also be recalculated? I can't get to job J1 via the VariableListener to tell the ScoreDirector that the score has to be changed here.
Or does this problem require a different approach?
This is a problem that is a bit hard to explain fully. TLDR version: constraint streams only react to changes to objects which are coming from either from()
, join()
or ifExists()
. Changes on objects not coming through these statements will not be caught, and therefore causing score corruptions. Longer explanation follows.
Consider a hypothetical Constraint Stream like this:
constraintFactory.from(Shift.class)
.join(Shift.class)
.filter((shift1, shift2) -> shift1.getEmployee() == shift2.getEmployee())
...
This constraint stream will work just fine, because if you change Shift
by setting a different employee, the Shift
s will be re-evaluated. They enter the stream via from()
and join()
, which is how CS knows to re-evaluate Shifts
when they change.
Now consider this constraint stream instead:
constraintFactory.from(Shift.class)
.filter(shift -> shift.getEmployee().getName() == "Lukas")
...
This constraint stream will be re-evaluated, if Shift
changes. But when the name
of Employee
changes, the constraint stream will not be re-evaluated; Employee
is neither in from()
nor in join()
, changes to Employee
will not trigger re-evaluation of the constraint stream.
In your particular situation, you need to ensure several things:
from()
or a join()
.