How to listen to an inverse Shadowvariable and the genuine planning variable in optaplanner?


1 weeks ago


4 time


Hello optaplanner community, im working on a capacitated vrp with pickup and delivery and timewindows without a central depot and a non homogeneous. A fleet of cargo Vessels pickup and deliver cargo from different ports saved in a distance matrix. The vessels have different speeds and different cargo space available. Each Vessel starts from a pre assigned port.

I followed the vrptw example in optaplanner and the steps some other developers tried and posted about here on stackoverflow.

When I use the FULL_ASSERT mode i run into the following problem:

Exception in thread "main" java.lang.IllegalStateException: VariableListener >corruption: the entity ([email protected])'s shadow variable (Cargo.arrivalTime)'s corrupted value (2179) changed to uncorrupted value (34) >after all VariableListeners were triggered without changes to the genuine> >variables. Maybe the VariableListener class (ShadowVarUpdater) for that shadow variable >(Cargo.arrivalTime) forgot to update it when one of its sources changed after >completedAction ([email protected] {[email protected] -> >[email protected]}).

The code corresponding to the values corrupted value (2179) and uncorrupted value (34) is:

public int getTravelTimeFromPreviousLocation(){
        if (vesselComesFromCargoLocation == null ){ 
            throw new IllegalStateException("This method must not be called when the vesselComesFromLocation ("
                    + vesselComesFromCargoLocation + ") is not initialized yet.");


        distanceToLastLocation = distanceMatrix.getDistanceBetweenPorts(vesselComesFromCargoLocation.getPortId(),portId);

        //if no Vessel is set -> cant use vessel speed to modify "distance" for softscore
        if(vesselComesFromCargoLocation.getVessel() == null){
            return distanceToLastLocation;

        return Math.round(distanceMatrix.calculateTimeForDistance(vesselComesFromCargoLocation.getVessel(),distanceToLastLocation));

vesselComesFromCargoLocation is the same Interface and planning variable as previousStandstill with the InverseShadowVariable "nextCustomer".

This is the listener to update arrivalTime:

public void updateArrivalTimeAndVesselContains(ScoreDirector scoreDirector, Cargo cargo){
        //get arrival time for cargo -> ATM no service duration 
        Location vesselComesFromCargoLocation = cargo.getVesselComesFromCargoLocation();

        Integer departureTime;
        if(vesselComesFromCargoLocation == null){
            departureTime = null;
        else if(vesselComesFromCargoLocation instanceof  Cargo){
            departureTime =((Cargo) vesselComesFromCargoLocation).getDepartureTime();
        else if(vesselComesFromCargoLocation instanceof Vessel) {
            departureTime = 0;
            departureTime = null;

        //update work through and update the chain
        Cargo chainCargo = cargo;

        Integer arrivalTime = findArrivalTime(chainCargo,departureTime);
        Integer amountOfCargo = 0;

        Set<Integer> vesselContains = (vesselComesFromCargoLocation instanceof  Cargo)? new HashSet<>(cargo.getVesselContains())
                                                    : new HashSet<>();


        if(cargo.getVessel() != null) {


        // (anchor)vessel<-cargo<-chainCargo<-chainCargo<-null    ->nextCargo  <-vesselComesFromLocation
        while(chainCargo != null &&!Objects.equals(chainCargo.getArrivalTime(),arrivalTime)){

                scoreDirector.beforeVariableChanged(chainCargo.getCounterpart(),"arrivalTime"); //update score for cargo counterpart to avoid false scores
                scoreDirector.afterVariableChanged(chainCargo.getCounterpart(),"arrivalTime"); //update score for cargo counterpart to avoid false scores

            //next cargo in chain ->repeat process
            departureTime = chainCargo.getDepartureTime();
            chainCargo = chainCargo.getNextCargo();
            arrivalTime = findArrivalTime(chainCargo, departureTime);

            if(chainCargo != null && chainCargo.getVessel() != null) {



    private Integer findArrivalTime(Cargo cargo, Integer prevDepartureTime){
        if(cargo == null || cargo.getVesselComesFromCargoLocation() == null ){
            return null;
        if(cargo.getVesselComesFromCargoLocation() instanceof Vessel){  // erste ankunft nach losfahren, da vessel commes from location ist
            //return 0;
            return cargo.getTravelTimeFromPreviousLocation(); 

       // System.out.println("prevDeptime: " + prevDepartureTime + " cargo.getTravelTimeFromPreviousLocation(): " + cargo.getTravelTimeFromPreviousLocation());
        return cargo.getTravelTimeFromPreviousLocation() + prevDepartureTime;


Since I employ a non homogeneous fleet all vessels have different speeds with wich i calculate the time to travel from port to port. So the corrupted value (2179) seems to be a distance without a the speed of the vessel and the unroruppted (34) is the correctly calculated value once a vessel is set for the chain.

It looks like this corruption occurs when there is no vehicle, or im my case Vessel, assigned for this chain yet. Meaning the inverse Shadowvariable "nextCargo" is not set.

How could I implement a custom listener that listens to the genuine planning variable "previousStandstill" and its inverse "nextCargo"? Or how should do I configure the sources correctly so my customlistener is triggert after nextCargo?

Or do you think my problem might be somewhere else?

Thank you a lot

0 answers