How to deal with Shipments with more possible PickupLocations

Thanks a lot @jie31best for you support.
I’m getting very close to a general solution.
I had to make changes to

Shipment

class and

PickupShipment

class and then implemented the

PickupLocations

interface and the

PickupLocationsImpl

class just like for TimeWindows.

The major changes were done to

ShipmentInsertionCalculator.getInsertionData

and it seems to work fine.
The only thing so far not working as intended is that the solutions aren’t considering the

vehicleBuilder.setLatestArrival

and i’m getting solutions that go beyond the latestArrival.

Any ideas why?

I’m pasting the code below.

> /**
>      * Calculates the marginal cost of inserting job i locally. This is based on the
>      * assumption that cost changes can entirely covered by only looking at the predecessor i-1 and its successor i+1.
>      */
>     @Override
>     public InsertionData getInsertionData(final VehicleRoute currentRoute, final Job jobToInsert, final Vehicle newVehicle, double newVehicleDepartureTime, final Driver newDriver, final double bestKnownCosts) {
>         JobInsertionContext insertionContext = new JobInsertionContext(currentRoute, jobToInsert, newVehicle, newDriver, newVehicleDepartureTime);
>         Shipment            shipment         = (Shipment) jobToInsert;
>         TourActivity        pickupShipment   = activityFactory.createActivities(shipment).get(0);
>         TourActivity        deliverShipment  = activityFactory.createActivities(shipment).get(1);
>         insertionContext.getAssociatedActivities().add(pickupShipment);
>         insertionContext.getAssociatedActivities().add(deliverShipment);
> 
>         /*
>         check hard route constraints
>          */
>         InsertionData noInsertion = checkRouteContraints(insertionContext, constraintManager);
>         if (noInsertion != null) {
>             return noInsertion;
>         }
>         /*
>         check soft route constraints
>          */
>         double additionalICostsAtRouteLevel = softRouteConstraint.getCosts(insertionContext);
> 
>         double bestCost = bestKnownCosts;
>         additionalICostsAtRouteLevel += additionalAccessEgressCalculator.getCosts(insertionContext);
> 
>         int pickupInsertionIndex   = InsertionData.NO_INDEX;
>         int deliveryInsertionIndex = InsertionData.NO_INDEX;
> 
>         TimeWindow bestPickupTimeWindow   = null;
>         TimeWindow bestDeliveryTimeWindow = null;
>         Location bestPickupLocation = null;
> 
>         Start start = new Start(newVehicle.getStartLocation(), newVehicle.getEarliestDeparture(), newVehicle.getLatestArrival());
>         start.setEndTime(newVehicleDepartureTime);
> 
>         End end = new End(newVehicle.getEndLocation(), 0.0, newVehicle.getLatestArrival());
> 
>         ActivityContext pickupContext = new ActivityContext();
> 
>         TourActivity prevAct        = start;
>         double       prevActEndTime = newVehicleDepartureTime;
> 
>         List<TourActivity> activities = currentRoute.getTourActivities().getActivities();
> 
>         List<String> failedActivityConstraints = new ArrayList<>();
>         //loops
>         int     i       = 0;
>         boolean tourEnd = false;
>         //pickupShipmentLoop
>         while (!tourEnd) {
>             TourActivity nextAct;
>             if (i < activities.size()) {
>                 nextAct = activities.get(i);
>             }
>             else {
>                 nextAct = end;
>                 tourEnd = true;
>             }
> 
>             boolean pickupInsertionNotFulfilledBreak = true;
>             for (Location pickupLocation : shipment.getPickupLocations()) {
>                 for (TimeWindow pickupTimeWindow : shipment.getPickupTimeWindows()) {
>                     pickupShipment.setTheoreticalEarliestOperationStartTime(pickupTimeWindow.getStart());
>                     pickupShipment.setTheoreticalLatestOperationStartTime(pickupTimeWindow.getEnd());
>                     ((PickupShipment) pickupShipment).setLocation(pickupLocation);
>                     ActivityContext activityContext = new ActivityContext();
>                     activityContext.setInsertionIndex(i);
>                     insertionContext.setActivityContext(activityContext);
>                     ConstraintsStatus pickupShipmentConstraintStatus = fulfilled(insertionContext, prevAct, pickupShipment, nextAct, prevActEndTime, failedActivityConstraints, constraintManager);
>                     if (pickupShipmentConstraintStatus.equals(ConstraintsStatus.NOT_FULFILLED)) {
>                         pickupInsertionNotFulfilledBreak = false;
>                         continue;
>                     }
>                     else if (pickupShipmentConstraintStatus.equals(ConstraintsStatus.NOT_FULFILLED_BREAK)) {
>                         continue;
>                     }
>                     else if (pickupShipmentConstraintStatus.equals(ConstraintsStatus.FULFILLED)) {
>                         pickupInsertionNotFulfilledBreak = false;
>                     }
>                     double additionalPickupICosts = softActivityConstraint.getCosts(insertionContext, prevAct, pickupShipment, nextAct, prevActEndTime);
>                     double pickupAIC              = calculate(insertionContext, prevAct, pickupShipment, nextAct, prevActEndTime);
> 
>                     TourActivity prevAct_deliveryLoop  = pickupShipment;
>                     double       shipmentPickupArrTime = prevActEndTime + transportCosts.getTransportTime(prevAct.getLocation(), pickupShipment.getLocation(), prevActEndTime, newDriver, newVehicle);
>                     double       shipmentPickupEndTime = Math.max(shipmentPickupArrTime, pickupShipment.getTheoreticalEarliestOperationStartTime()) + activityCosts.getActivityDuration(pickupShipment, shipmentPickupArrTime, newDriver, newVehicle);
> 
>                     pickupContext.setArrivalTime(shipmentPickupArrTime);
>                     pickupContext.setEndTime(shipmentPickupEndTime);
>                     pickupContext.setInsertionIndex(i);
>                     insertionContext.setRelatedActivityContext(pickupContext);
> 
>                     double prevActEndTime_deliveryLoop = shipmentPickupEndTime;
> 
> 			/*
>             --------------------------------
> 			 */
>                     //deliverShipmentLoop
>                     int     j                    = i;
>                     boolean tourEnd_deliveryLoop = false;
>                     while (!tourEnd_deliveryLoop) {
>                         TourActivity nextAct_deliveryLoop;
>                         if (j < activities.size()) {
>                             nextAct_deliveryLoop = activities.get(j);
>                         }
>                         else {
>                             nextAct_deliveryLoop = end;
>                             tourEnd_deliveryLoop = true;
>                         }
> 
>                         boolean deliveryInsertionNotFulfilledBreak = true;
>                         for (TimeWindow deliveryTimeWindow : shipment.getDeliveryTimeWindows()) {
>                             deliverShipment.setTheoreticalEarliestOperationStartTime(deliveryTimeWindow.getStart());
>                             deliverShipment.setTheoreticalLatestOperationStartTime(deliveryTimeWindow.getEnd());
>                             ActivityContext activityContext_ = new ActivityContext();
>                             activityContext_.setInsertionIndex(j);
>                             insertionContext.setActivityContext(activityContext_);
>                             ConstraintsStatus deliverShipmentConstraintStatus = fulfilled(insertionContext, prevAct_deliveryLoop, deliverShipment, nextAct_deliveryLoop, prevActEndTime_deliveryLoop, failedActivityConstraints, constraintManager);
>                             if (deliverShipmentConstraintStatus.equals(ConstraintsStatus.FULFILLED)) {
>                                 double additionalDeliveryICosts = softActivityConstraint.getCosts(insertionContext, prevAct_deliveryLoop, deliverShipment, nextAct_deliveryLoop, prevActEndTime_deliveryLoop);
>                                 double deliveryAIC              = calculate(insertionContext, prevAct_deliveryLoop, deliverShipment, nextAct_deliveryLoop, prevActEndTime_deliveryLoop);
>                                 double totalActivityInsertionCosts = pickupAIC + deliveryAIC
>                                         + additionalICostsAtRouteLevel + additionalPickupICosts + additionalDeliveryICosts;
>                                 if (totalActivityInsertionCosts < bestCost) {
>                                     bestCost = totalActivityInsertionCosts;
>                                     pickupInsertionIndex = i;
>                                     deliveryInsertionIndex = j;
>                                     bestPickupTimeWindow = pickupTimeWindow;
>                                     bestDeliveryTimeWindow = deliveryTimeWindow;
>                                     bestPickupLocation = pickupLocation;
>                                 }
>                                 deliveryInsertionNotFulfilledBreak = false;
>                             }
>                             else if (deliverShipmentConstraintStatus.equals(ConstraintsStatus.NOT_FULFILLED)) {
>                                 deliveryInsertionNotFulfilledBreak = false;
>                             }
>                         }
>                         if (deliveryInsertionNotFulfilledBreak) {
>                             break;
>                         }
>                         //update prevAct and endTime
>                         double nextActArrTime = prevActEndTime_deliveryLoop + transportCosts.getTransportTime(prevAct_deliveryLoop.getLocation(), nextAct_deliveryLoop.getLocation(), prevActEndTime_deliveryLoop, newDriver, newVehicle);
>                         prevActEndTime_deliveryLoop = Math.max(nextActArrTime, nextAct_deliveryLoop.getTheoreticalEarliestOperationStartTime()) + activityCosts.getActivityDuration(nextAct_deliveryLoop, nextActArrTime, newDriver, newVehicle);
>                         prevAct_deliveryLoop = nextAct_deliveryLoop;
>                         j++;
>                     }
>                 }
>             }
>             if (pickupInsertionNotFulfilledBreak) {
>                 break;
>             }
>             //update prevAct and endTime
>             double nextActArrTime = prevActEndTime + transportCosts.getTransportTime(prevAct.getLocation(), nextAct.getLocation(), prevActEndTime, newDriver, newVehicle);
>             prevActEndTime = Math.max(nextActArrTime, nextAct.getTheoreticalEarliestOperationStartTime()) + activityCosts.getActivityDuration(nextAct, nextActArrTime, newDriver, newVehicle);
>             prevAct = nextAct;
>             i++;
>         }
>         if (pickupInsertionIndex == InsertionData.NO_INDEX) {
>             InsertionData emptyInsertionData = new InsertionData.NoInsertionFound();
>             emptyInsertionData.getFailedConstraintNames().addAll(failedActivityConstraints);
>             return emptyInsertionData;
>         }
>         InsertionData insertionData = new InsertionData(bestCost, pickupInsertionIndex, deliveryInsertionIndex, newVehicle, newDriver);
>         pickupShipment.setTheoreticalEarliestOperationStartTime(bestPickupTimeWindow.getStart());
>         pickupShipment.setTheoreticalLatestOperationStartTime(bestPickupTimeWindow.getEnd());
>         ((PickupShipment) pickupShipment).setLocation(bestPickupLocation);
>         deliverShipment.setTheoreticalEarliestOperationStartTime(bestDeliveryTimeWindow.getStart());
>         deliverShipment.setTheoreticalLatestOperationStartTime(bestDeliveryTimeWindow.getEnd());
>         insertionData.setVehicleDepartureTime(newVehicleDepartureTime);
>         insertionData.getEvents().add(new InsertActivity(currentRoute, newVehicle, deliverShipment, deliveryInsertionIndex));
>         insertionData.getEvents().add(new InsertActivity(currentRoute, newVehicle, pickupShipment, pickupInsertionIndex));
>         insertionData.getEvents().add(new SwitchVehicle(currentRoute, newVehicle, newVehicleDepartureTime));
>         return insertionData;
>     }