From 76f43ea68ef0330c2ad6c82d9e337591eaaec143 Mon Sep 17 00:00:00 2001 From: Cyril R <50868137+croooo@users.noreply.github.com> Date: Thu, 21 Sep 2023 16:50:40 +0200 Subject: [PATCH] Added: TimeWindows is now an extension point, notably allowing TimeWindow exclusions (#1) --- .../recreate/AbstractInsertionCalculator.java | 3 - .../recreate/ServiceInsertionCalculator.java | 12 +- .../recreate/ShipmentInsertionCalculator.java | 22 +-- .../jsprit/core/problem/job/Service.java | 22 ++- .../jsprit/core/problem/job/Shipment.java | 44 ++++- .../solution/route/activity/TimeWindows.java | 7 + .../route/activity/TimeWindowsImpl.java | 19 +++ .../activity/TimeWindowsOverlapImpl.java | 151 ++++++++++++++++++ .../IgnoreConditionalTimeWindowTest.java | 127 ++++++++++++++- 9 files changed, 374 insertions(+), 33 deletions(-) create mode 100644 jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/TimeWindowsOverlapImpl.java diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/AbstractInsertionCalculator.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/AbstractInsertionCalculator.java index 3bb26da60..5f169caf1 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/AbstractInsertionCalculator.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/AbstractInsertionCalculator.java @@ -24,7 +24,6 @@ import com.graphhopper.jsprit.core.problem.constraint.HardConstraint; import com.graphhopper.jsprit.core.problem.constraint.HardRouteConstraint; import com.graphhopper.jsprit.core.problem.misc.JobInsertionContext; -import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow; import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity; import java.util.ArrayList; @@ -35,8 +34,6 @@ * Created by schroeder on 06/02/17. */ abstract class AbstractInsertionCalculator implements JobInsertionCostsCalculator { - protected TimeWindow defaultTimeWindow = TimeWindow.newInstance(0.0, Double.MAX_VALUE); - InsertionData checkRouteConstraints(JobInsertionContext insertionContext, ConstraintManager constraintManager) { for (HardRouteConstraint hardRouteConstraint : constraintManager.getHardRouteConstraints()) { if (!hardRouteConstraint.fulfilled(insertionContext)) { diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/ServiceInsertionCalculator.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/ServiceInsertionCalculator.java index 52b2b7fc8..8d2166902 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/ServiceInsertionCalculator.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/ServiceInsertionCalculator.java @@ -135,14 +135,12 @@ public InsertionData getInsertionData(final VehicleRoute currentRoute, final Job nextAct = end; tourEnd = true; } + + ActivityContext activityContext = new ActivityContext(); + activityContext.setInsertionIndex(actIndex); + insertionContext.setActivityContext(activityContext); boolean not_fulfilled_break = true; - for(TimeWindow timeWindow : service.getTimeWindows()) { - ActivityContext activityContext = new ActivityContext(); - activityContext.setInsertionIndex(actIndex); - insertionContext.setActivityContext(activityContext); - if (!timeWindow.isApplicable(insertionContext)) { - timeWindow = defaultTimeWindow; - } + for(TimeWindow timeWindow : service.getTimeWindows(insertionContext)) { deliveryAct2Insert.setTheoreticalEarliestOperationStartTime(timeWindow.getStart()); deliveryAct2Insert.setTheoreticalLatestOperationStartTime(timeWindow.getEnd()); diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/ShipmentInsertionCalculator.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/ShipmentInsertionCalculator.java index 5c5641365..d32a8067a 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/ShipmentInsertionCalculator.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/ShipmentInsertionCalculator.java @@ -140,13 +140,10 @@ public InsertionData getInsertionData(final VehicleRoute currentRoute, final Job } boolean pickupInsertionNotFulfilledBreak = true; - for(TimeWindow pickupTimeWindow : shipment.getPickupTimeWindows()) { - ActivityContext activityContext = new ActivityContext(); - activityContext.setInsertionIndex(i); - insertionContext.setActivityContext(activityContext); - if (!pickupTimeWindow.isApplicable(insertionContext)) { - pickupTimeWindow = defaultTimeWindow; - } + ActivityContext activityContext = new ActivityContext(); + activityContext.setInsertionIndex(i); + insertionContext.setActivityContext(activityContext); + for(TimeWindow pickupTimeWindow : shipment.getPickupTimeWindows(insertionContext)) { pickupShipment.setTheoreticalLatestOperationStartTime(pickupTimeWindow.getEnd()); pickupShipment.setTheoreticalEarliestOperationStartTime(pickupTimeWindow.getStart()); @@ -194,13 +191,10 @@ else if (pickupShipmentConstraintStatus.equals(ConstraintsStatus.FULFILLED)) { } boolean deliveryInsertionNotFulfilledBreak = true; - for (TimeWindow deliveryTimeWindow : shipment.getDeliveryTimeWindows()) { - ActivityContext activityContext_ = new ActivityContext(); - activityContext_.setInsertionIndex(j); - insertionContext.setActivityContext(activityContext_); - if (!deliveryTimeWindow.isApplicable(insertionContext)) { - deliveryTimeWindow = defaultTimeWindow; - } + ActivityContext activityContext_ = new ActivityContext(); + activityContext_.setInsertionIndex(j); + insertionContext.setActivityContext(activityContext_); + for (TimeWindow deliveryTimeWindow : shipment.getDeliveryTimeWindows(insertionContext)) { deliverShipment.setTheoreticalEarliestOperationStartTime(deliveryTimeWindow.getStart()); deliverShipment.setTheoreticalLatestOperationStartTime(deliveryTimeWindow.getEnd()); diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Service.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Service.java index 0b24881c1..936d335a0 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Service.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Service.java @@ -21,9 +21,11 @@ import com.graphhopper.jsprit.core.problem.Capacity; import com.graphhopper.jsprit.core.problem.Location; import com.graphhopper.jsprit.core.problem.Skills; +import com.graphhopper.jsprit.core.problem.misc.JobInsertionContext; import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow; import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindows; import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindowsImpl; +import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindowsOverlapImpl; import com.graphhopper.jsprit.core.util.Coordinate; import java.util.ArrayList; @@ -87,7 +89,7 @@ public static Builder newInstance(String id) { protected Location location; - protected TimeWindowsImpl timeWindows; + protected TimeWindows timeWindows; private boolean twAdded = false; @@ -184,6 +186,20 @@ public Builder setTimeWindow(TimeWindow tw){ return this; } + public Builder setTimeWindows(TimeWindows timeWindows){ + if (timeWindows == null) throw new IllegalArgumentException("The time windows must not be null."); + if (twAdded) { + // Report already added TW for ascending compatibility and API clarity + // (otherwise previous calls to addTimeWindow would be silently ignored) + for (TimeWindow tw : this.timeWindows.getTimeWindows()) { + timeWindows.add(tw); + } + } + this.timeWindows = timeWindows; + twAdded = true; + return this; + } + public Builder addTimeWindow(TimeWindow timeWindow) { if (timeWindow == null) throw new IllegalArgumentException("The time window must not be null."); if(!twAdded){ @@ -310,6 +326,10 @@ public Collection getTimeWindows(){ return timeWindows.getTimeWindows(); } + public Collection getTimeWindows(JobInsertionContext insertionContext){ + return timeWindows.getTimeWindows(insertionContext); + } + @Override public String getId() { return id; diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Shipment.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Shipment.java index be95482bd..9d6fe8c3c 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Shipment.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Shipment.java @@ -21,7 +21,9 @@ import com.graphhopper.jsprit.core.problem.Capacity; import com.graphhopper.jsprit.core.problem.Location; import com.graphhopper.jsprit.core.problem.Skills; +import com.graphhopper.jsprit.core.problem.misc.JobInsertionContext; import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow; +import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindows; import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindowsImpl; import java.util.ArrayList; @@ -76,13 +78,13 @@ public static class Builder { private Location deliveryLocation_; - protected TimeWindowsImpl deliveryTimeWindows; + protected TimeWindows deliveryTimeWindows; private boolean deliveryTimeWindowAdded = false; private boolean pickupTimeWindowAdded = false; - private TimeWindowsImpl pickupTimeWindows; + private TimeWindows pickupTimeWindows; private int priority = 2; @@ -176,7 +178,18 @@ public Builder setPickupTimeWindow(TimeWindow timeWindow) { return this; } - + public Builder setPickupTimeWindows(TimeWindows timeWindows){ + if (timeWindows == null) throw new IllegalArgumentException("The time windows must not be null."); + if (pickupTimeWindows != null) { + // Report already added TW for ascending compatibility and API clarity + // (otherwise previous calls to addXXXTimeWindow would be silently ignored) + for (TimeWindow tw : this.pickupTimeWindows.getTimeWindows()) { + timeWindows.add(tw); + } + } + this.pickupTimeWindows = timeWindows; + return this; + } /** * Sets delivery location. @@ -222,6 +235,19 @@ public Builder setDeliveryTimeWindow(TimeWindow timeWindow) { return this; } + public Builder setDeliveryTimeWindows(TimeWindows timeWindows){ + if (timeWindows == null) throw new IllegalArgumentException("The time windows must not be null."); + if (deliveryTimeWindows != null) { + // Report already added TW for ascending compatibility and API clarity + // (otherwise previous calls to addXXXTimeWindow would be silently ignored) + for (TimeWindow tw : this.deliveryTimeWindows.getTimeWindows()) { + timeWindows.add(tw); + } + } + this.deliveryTimeWindows = timeWindows; + return this; + } + /** * Adds capacity dimension. * @@ -367,9 +393,9 @@ public Builder setMaxTimeInVehicle(double maxTimeInVehicle){ private final Location deliveryLocation_; - private final TimeWindowsImpl deliveryTimeWindows; + private final TimeWindows deliveryTimeWindows; - private final TimeWindowsImpl pickupTimeWindows; + private final TimeWindows pickupTimeWindows; private final int priority; @@ -442,6 +468,10 @@ public Collection getDeliveryTimeWindows() { return deliveryTimeWindows.getTimeWindows(); } + public Collection getDeliveryTimeWindows(JobInsertionContext insertionContext) { + return deliveryTimeWindows.getTimeWindows(insertionContext); + } + /** * Returns the time-window of pickup. * @@ -455,6 +485,10 @@ public Collection getPickupTimeWindows() { return pickupTimeWindows.getTimeWindows(); } + public Collection getPickupTimeWindows(JobInsertionContext insertionContext) { + return pickupTimeWindows.getTimeWindows(insertionContext); + } + /** * Returns a string with the shipment's attributes. diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/TimeWindows.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/TimeWindows.java index 615100a92..6ee730268 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/TimeWindows.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/TimeWindows.java @@ -20,11 +20,18 @@ import java.util.Collection; +import com.graphhopper.jsprit.core.problem.misc.JobInsertionContext; + /** * Created by schroeder on 20/05/15. */ public interface TimeWindows { + static TimeWindow defaultTimeWindow = TimeWindow.newInstance(0.0, Double.MAX_VALUE); + + public void add(TimeWindow timeWindow); public Collection getTimeWindows(); + public Collection getTimeWindows(JobInsertionContext insertionContext); + } diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/TimeWindowsImpl.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/TimeWindowsImpl.java index 84b8f37c9..7a781cdec 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/TimeWindowsImpl.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/TimeWindowsImpl.java @@ -21,6 +21,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.List; + +import com.graphhopper.jsprit.core.problem.misc.JobInsertionContext; /** * Created by schroeder on 26/05/15. @@ -29,6 +32,7 @@ public class TimeWindowsImpl implements TimeWindows { private Collection timeWindows = new ArrayList(); + @Override public void add(TimeWindow timeWindow){ for(TimeWindow tw : timeWindows){ if(timeWindow.getStart() > tw.getStart() && timeWindow.getStart() < tw.getEnd()){ @@ -44,10 +48,25 @@ public void add(TimeWindow timeWindow){ timeWindows.add(timeWindow); } + @Override public Collection getTimeWindows() { return Collections.unmodifiableCollection(timeWindows); } + @Override + public Collection getTimeWindows(JobInsertionContext insertionContext) { + List timeWindows = new ArrayList(this.timeWindows.size()); + for(TimeWindow tw : this.timeWindows){ + if (tw.isApplicable(insertionContext)) { + timeWindows.add(tw); + } + } + if (timeWindows.isEmpty()) { + timeWindows.add(TimeWindows.defaultTimeWindow); + } + return Collections.unmodifiableCollection(timeWindows); + } + @Override public String toString() { StringBuffer sb = new StringBuffer(timeWindows.size() * 60); diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/TimeWindowsOverlapImpl.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/TimeWindowsOverlapImpl.java new file mode 100644 index 000000000..76bc452c2 --- /dev/null +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/TimeWindowsOverlapImpl.java @@ -0,0 +1,151 @@ +package com.graphhopper.jsprit.core.problem.solution.route.activity; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import com.graphhopper.jsprit.core.problem.misc.JobInsertionContext; + +/** + * An alternative implementation of TimeWindows that allows for overlapping time windows as well as exclusions. + */ +public class TimeWindowsOverlapImpl implements TimeWindows { + + private List includedTimeWindows = new ArrayList(); + private List excludedTimeWindows = new ArrayList(); + + public static TimeWindowsOverlapImpl newInstance() { + return new TimeWindowsOverlapImpl(); + } + + public static TimeWindowsOverlapImpl newInstance(List includedTimeWindows) { + TimeWindowsOverlapImpl res = new TimeWindowsOverlapImpl(); + res.includedTimeWindows = includedTimeWindows; + return res; + } + + @Override + public void add(TimeWindow timeWindow) { + if (timeWindow == null) { + throw new IllegalArgumentException("The time window must not be null."); + } + + includedTimeWindows.add(timeWindow); + + // Keep collection sorted by start time - needed by getTimeWindows() + Collections.sort(includedTimeWindows, (a, b) -> (int)(a.getStart() - b.getStart())); + } + + public TimeWindowsOverlapImpl addExcludedTimeWindow(TimeWindow timeWindow) { + if (timeWindow == null) { + throw new IllegalArgumentException("The time window must not be null."); + } + excludedTimeWindows.add(timeWindow); + + // Keep collection sorted by start time - needed by getTimeWindows() + Collections.sort(excludedTimeWindows, (a, b) -> (int)(a.getStart() - b.getStart())); + return this; + } + + public TimeWindowsOverlapImpl addIncludedTimeWindow(TimeWindow timeWindow) { + // Synonym for the sake of symmetry + add(timeWindow); + return this; + } + + @Override + public Collection getTimeWindows() { + return Collections.unmodifiableCollection(includedTimeWindows); + } + + @Override + public Collection getTimeWindows(JobInsertionContext insertionContext) { + // First easy case: no exclusions, no performance loss. + if (excludedTimeWindows.isEmpty()) { + return getTimeWindows(); + } + + // Second easy case: no applicable exclusions. + List applicableExclusions = new ArrayList(excludedTimeWindows.size()); + for (TimeWindow excludedTw : excludedTimeWindows) { + if (excludedTw.isApplicable(insertionContext)) { + applicableExclusions.add(excludedTw); + } + } + if (applicableExclusions.isEmpty()) { + return getTimeWindows(); + } + + // First: filter included TW that are applicable + List result = new ArrayList(includedTimeWindows.size()); + for(TimeWindow includedTw : includedTimeWindows) { + if (!includedTw.isApplicable(insertionContext)) { + continue; + } + result.add(includedTw); + } + if (result.isEmpty()) { + // No applicable TW means the job has no time insertion constraints. So just use the "infinite" TW. + result.add(defaultTimeWindow); + } + + // Then remove the exclusions + for(TimeWindow excludedTw : applicableExclusions) { + result = cutTimeWindowsIfNeeded(excludedTw, result); + } + // Note that the result can be empty! This is normal: we can exclude everything and make the job impossible to insert. + + // And we are done! + return Collections.unmodifiableCollection(result); + } + + protected List cutTimeWindowsIfNeeded(TimeWindow excludedTw, List currentResult) { + List newResult = new ArrayList(); + for (TimeWindow includedTw : currentResult) { + if (excludedTw.getStart() >= includedTw.getStart() && excludedTw.getEnd() <= excludedTw.getEnd()) { + // Inclusion [ ] + // Exclusion [ ] + if (excludedTw.getStart() > includedTw.getStart()) { + newResult.add(TimeWindow.newInstance(includedTw.getStart(), excludedTw.getStart())); + } + if (excludedTw.getEnd() < includedTw.getEnd()) { + newResult.add(TimeWindow.newInstance(excludedTw.getEnd(), includedTw.getEnd())); + } + } + else if (excludedTw.getStart() < includedTw.getStart() && excludedTw.getEnd() < includedTw.getEnd() && excludedTw.getEnd() > includedTw.getStart()) { + // Inclusion [ ] + // Exclusion [ ] + newResult.add(TimeWindow.newInstance(excludedTw.getEnd(), includedTw.getEnd())); + } + else if (excludedTw.getStart() > includedTw.getStart() && excludedTw.getStart() < includedTw.getEnd() && excludedTw.getEnd() > includedTw.getEnd()) { + // Inclusion [ ] + // Exclusion [ ] + newResult.add(TimeWindow.newInstance(includedTw.getStart(), excludedTw.getStart())); + } + else if (excludedTw.getStart() < includedTw.getStart() && excludedTw.getEnd() > includedTw.getEnd()) { + // Inclusion [ ] + // Exclusion [ ] + // => nothing to add + } + else { + newResult.add(includedTw); + } + } + return newResult; + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(includedTimeWindows.size() * 60); + sb.append("Included:\n"); + for (TimeWindow tw : includedTimeWindows) { + sb.append("[timeWindow=").append(tw).append("]"); + } + sb.append("Excluded:\n"); + for (TimeWindow tw : excludedTimeWindows) { + sb.append("[timeWindow=").append(tw).append("]"); + } + return sb.toString(); + } +} diff --git a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/IgnoreConditionalTimeWindowTest.java b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/IgnoreConditionalTimeWindowTest.java index 411236eca..d51657f54 100644 --- a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/IgnoreConditionalTimeWindowTest.java +++ b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/IgnoreConditionalTimeWindowTest.java @@ -26,7 +26,9 @@ import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem; import com.graphhopper.jsprit.core.problem.job.Service; import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution; +import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow; import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindowConditionalOnVehicleType; +import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindowsOverlapImpl; import com.graphhopper.jsprit.core.problem.vehicle.VehicleImpl; import com.graphhopper.jsprit.core.problem.vehicle.VehicleType; import com.graphhopper.jsprit.core.problem.vehicle.VehicleTypeImpl; @@ -44,9 +46,6 @@ public void ignoreConditionalTimeWindow(){ VehicleType vehicleType1 = vehicleTypeBuilder1.build(); VehicleType vehicleType2 = vehicleTypeBuilder2.build(); - /* - * get a vehicle-builder and build a vehicle located at (10,10) with type "vehicleType" - */ VehicleImpl vehicle1,vehicle2; { @@ -113,4 +112,126 @@ public void ignoreConditionalTimeWindow(){ Assert.assertEquals(0, solution3.getUnassignedJobs().size()); } + + @Test + public void ignoreConditionalTimeWindowWithExclusion(){ + VehicleTypeImpl.Builder vehicleTypeBuilder1 = VehicleTypeImpl.Builder.newInstance("vehicleType1"); + VehicleTypeImpl.Builder vehicleTypeBuilder2 = VehicleTypeImpl.Builder.newInstance("vehicleType2"); + + VehicleType vehicleType1 = vehicleTypeBuilder1.build(); + VehicleType vehicleType2 = vehicleTypeBuilder2.build(); + + VehicleImpl.Builder vehicleBuilder = VehicleImpl.Builder.newInstance("v1"); + vehicleBuilder.setStartLocation(Location.newInstance(0, 0)); + vehicleBuilder.setType(vehicleType1); + vehicleBuilder.setEarliestStart(0).setLatestArrival(200); + + VehicleImpl vehicle1 = vehicleBuilder.build(); + + vehicleBuilder = VehicleImpl.Builder.newInstance("v2"); + vehicleBuilder.setStartLocation(Location.newInstance(0, 0)); + vehicleBuilder.setType(vehicleType2); + vehicleBuilder.setEarliestStart(0).setLatestArrival(200); + + VehicleImpl vehicle2 = vehicleBuilder.build(); + + + // First service is possible for vehicle v1 but only after a while due to exclusion. + TimeWindowsOverlapImpl tws = new TimeWindowsOverlapImpl(); + tws.addIncludedTimeWindow(TimeWindow.newInstance(0,100)); + tws.addExcludedTimeWindow(TimeWindowConditionalOnVehicleType.newInstance(0,40, "vehicleType1")); + Service service1 = Service.Builder.newInstance("s1").setLocation(Location.newInstance(0, 0)) + .setServiceTime(0).setTimeWindows(tws).build(); + + VehicleRoutingProblem vrp1 = VehicleRoutingProblem.Builder.newInstance() + .addVehicle(vehicle1) + .addJob(service1) + .setFleetSize(VehicleRoutingProblem.FleetSize.FINITE) + .build(); + + VehicleRoutingAlgorithm vra1 = Jsprit.createAlgorithm(vrp1); + vra1.setMaxIterations(50); + VehicleRoutingProblemSolution solution1 = Solutions.bestOf(vra1.searchSolutions()); + + Assert.assertEquals(0, solution1.getUnassignedJobs().size()); + Assert.assertEquals(1, (int)solution1.getRoutes().iterator().next().getActivities().size()); + Assert.assertEquals(40, (int)solution1.getRoutes().iterator().next().getActivities().get(0).getEndTime()); + + // For a vehicle not respecting the condition, the exclusion should not apply. + VehicleRoutingProblem vrp2 = VehicleRoutingProblem.Builder.newInstance() + .addVehicle(vehicle2) + .addJob(service1) + .setFleetSize(VehicleRoutingProblem.FleetSize.FINITE) + .build(); + + VehicleRoutingAlgorithm vra2 = Jsprit.createAlgorithm(vrp2); + vra2.setMaxIterations(50); + VehicleRoutingProblemSolution solution2 = Solutions.bestOf(vra2.searchSolutions()); + + Assert.assertEquals(0, solution2.getUnassignedJobs().size()); + Assert.assertEquals(0, (int)solution2.getRoutes().iterator().next().getActivities().get(0).getArrTime()); + + // Finaly, try the different kinds of exclusion + tws = new TimeWindowsOverlapImpl(); + tws.addIncludedTimeWindow(TimeWindow.newInstance(50,100)); + tws.addExcludedTimeWindow(TimeWindowConditionalOnVehicleType.newInstance(0,70, "vehicleType1")); + Service service3 = Service.Builder.newInstance("s1").setLocation(Location.newInstance(0, 0)) + .setServiceTime(0).setTimeWindows(tws).build(); + + VehicleRoutingProblem vrp3 = VehicleRoutingProblem.Builder.newInstance() + .addVehicle(vehicle1) + .addJob(service3) + .setFleetSize(VehicleRoutingProblem.FleetSize.FINITE) + .build(); + + VehicleRoutingAlgorithm vra3 = Jsprit.createAlgorithm(vrp3); + vra3.setMaxIterations(50); + VehicleRoutingProblemSolution solution3 = Solutions.bestOf(vra3.searchSolutions()); + + Assert.assertEquals(0, solution3.getUnassignedJobs().size()); + Assert.assertEquals(1, (int)solution3.getRoutes().iterator().next().getActivities().size()); + Assert.assertEquals(70, (int)solution3.getRoutes().iterator().next().getActivities().get(0).getEndTime()); + + // + tws = new TimeWindowsOverlapImpl(); + tws.addIncludedTimeWindow(TimeWindow.newInstance(50,100)); + tws.addExcludedTimeWindow(TimeWindowConditionalOnVehicleType.newInstance(70,150, "vehicleType1")); + Service service4 = Service.Builder.newInstance("s1").setLocation(Location.newInstance(0, 0)) + .setServiceTime(0).setTimeWindows(tws).build(); + + VehicleRoutingProblem vrp4 = VehicleRoutingProblem.Builder.newInstance() + .addVehicle(vehicle1) + .addJob(service4) + .setFleetSize(VehicleRoutingProblem.FleetSize.FINITE) + .build(); + + VehicleRoutingAlgorithm vra4 = Jsprit.createAlgorithm(vrp4); + vra4.setMaxIterations(50); + VehicleRoutingProblemSolution solution4 = Solutions.bestOf(vra4.searchSolutions()); + + Assert.assertEquals(0, solution4.getUnassignedJobs().size()); + Assert.assertEquals(1, (int)solution4.getRoutes().iterator().next().getActivities().size()); + Assert.assertEquals(50, (int)solution4.getRoutes().iterator().next().getActivities().get(0).getEndTime()); + + // + tws = new TimeWindowsOverlapImpl(); + tws.addIncludedTimeWindow(TimeWindow.newInstance(50,100)); + tws.addExcludedTimeWindow(TimeWindowConditionalOnVehicleType.newInstance(120,150, "vehicleType1")); + Service service5 = Service.Builder.newInstance("s1").setLocation(Location.newInstance(0, 0)) + .setServiceTime(0).setTimeWindows(tws).build(); + + VehicleRoutingProblem vrp5 = VehicleRoutingProblem.Builder.newInstance() + .addVehicle(vehicle1) + .addJob(service5) + .setFleetSize(VehicleRoutingProblem.FleetSize.FINITE) + .build(); + + VehicleRoutingAlgorithm vra5 = Jsprit.createAlgorithm(vrp5); + vra4.setMaxIterations(50); + VehicleRoutingProblemSolution solution5 = Solutions.bestOf(vra4.searchSolutions()); + + Assert.assertEquals(0, solution5.getUnassignedJobs().size()); + Assert.assertEquals(1, (int)solution5.getRoutes().iterator().next().getActivities().size()); + Assert.assertEquals(50, (int)solution5.getRoutes().iterator().next().getActivities().get(0).getEndTime()); + } }