How to correctly implement Flexible Time Windows?

I’m trying to implement flexible time windows (soft time windows) in jsprit and there were several questions, the answers to which I would like to receive.

  1. What should be the behavior when flexible time windows are enabled?

For example, if I use hard time windows (VehicleDependentTimeWindowConstraints) by adding constraintManager.addTimeWindowConstraint(), then points that do not fall within the time interval are thrown out.

  1. Should a different route with a new transport be built for points that do not fall within the specified time window, or, for example, should the application offer the user a different time?

After reading similar topics on the forum I found the following code:

public class SoftTimeWindowConstraint implements SoftActivityConstraint {

    // TODO: Choose good penalty factors
    private static final int ACTIVITY_EARLY_PENALTY_FACTOR = 1000;
    private static final int ACTIVITY_LATE_PENALTY_FACTOR = 1000;
    private static final int VEHICLE_EARLY_PENALTY_FACTOR = 1000;
    private static final int VEHICLE_LATE_PENALTY_FACTOR = 1000;

    public SoftTimeWindowConstraint() {

    public double getCosts(JobInsertionContext iFacts,
                           TourActivity prevAct, TourActivity newAct,
                           TourActivity nextAct, double prevActDepTime) {

        List<TourActivity> newActList = new ArrayList<TourActivity>();

        return softTimeWindowPenalties(newActList, iFacts.getNewVehicle(), iFacts.getNewDepTime());


    public static double softTimeWindowPenalties(List<TourActivity> activities, Vehicle vehicle, double vehicleDepartureTime) {

        double totalPenalty = 0.0;

        TourActivity lastActivity = null;
        double lastActivityEndTime = Double.NEGATIVE_INFINITY;

        for (TourActivity activity : activities) {

            double timeEarlyForActivity = Math.max(0, activity.getTheoreticalEarliestOperationStartTime() - activity.getArrTime());
            double timeLateForActivity = Math.max(0, activity.getEndTime() - (activity.getTheoreticalLatestOperationStartTime() - activity.getOperationTime()));
            totalPenalty += penaltyIfViolation(timeEarlyForActivity,ACTIVITY_EARLY_PENALTY_FACTOR);
            totalPenalty += penaltyIfViolation(timeLateForActivity,ACTIVITY_LATE_PENALTY_FACTOR);

            // Get the last activity in the tour to compare to the vehicle's end time
            if (activity.getEndTime() > lastActivityEndTime) {
                lastActivityEndTime = activity.getEndTime();
                lastActivity = activity;


        double timeEarlyForVehicle = Math.max(0, vehicle.getEarliestDeparture() - vehicleDepartureTime);
        double timeLateForVehicle = Math.max(0, (lastActivity.getEndTime() - lastActivity.getOperationTime()) - vehicle.getLatestArrival());

        totalPenalty += penaltyIfViolation(timeEarlyForVehicle,VEHICLE_EARLY_PENALTY_FACTOR);
        totalPenalty += penaltyIfViolation(timeLateForVehicle,VEHICLE_LATE_PENALTY_FACTOR);

        return totalPenalty;

    public static double penaltyIfViolation(double timeViolation, double penaltyFactor) {
        if (timeViolation > 0) {
            return penaltyFactor * Math.pow(timeViolation,2);
        } else {
            return 0.0;

  1. What is the basis for choosing penalty factors?
  1. Should the flexible window enable functionality be for the entire route (all points) or should it be possible to enable it for a separate point?

When I use hard time windows, if there is a point that does not hit the specified time, then no exception is thrown and the point is simply thrown from the system.
For this I have implemented the following:

 UnassignedJobReasonTracker reasonTracker = new UnassignedJobReasonTracker();


private void checkIfUnassignedJobsPresent(VehicleRoutingProblemSolution solution) {
  if (!solution.getUnassignedJobs().isEmpty()) {
    StringBuilder reason = new StringBuilder();
    for (Job unassignedJob : solution.getUnassignedJobs()) {
      String jobId = unassignedJob.getId();
      int reasonCode = reasonTracker.getMostLikelyReasonCode(jobId);
      String message = reasonTracker.getHumanReadableReason(reasonCode);

      reason.append("Reason code: ").append(reasonCode).append(" | ");
      for (Activity activity : unassignedJob.getActivities()) {
            .append(" ")
            .append("located in { ")
            .append("latitude: ")
            .append(" | ")
            .append("longitude: ")
            .append(" } ")
    throw new ImpossibleRoutingException(reason.toString());
  1. Is this the correct way to find points that have not been routed or are there other ways?

Thanks in advance for your help!

Powered by Discourse