diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/BreakForMultipleTimeWindowsInsertionCalculator.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/BreakForMultipleTimeWindowsInsertionCalculator.java new file mode 100644 index 00000000..778cde01 --- /dev/null +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/BreakForMultipleTimeWindowsInsertionCalculator.java @@ -0,0 +1,141 @@ +package com.graphhopper.jsprit.core.algorithm.recreate; + +import com.graphhopper.jsprit.core.problem.JobActivityFactory; +import com.graphhopper.jsprit.core.problem.Location; +import com.graphhopper.jsprit.core.problem.constraint.*; +import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingActivityCosts; +import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingTransportCosts; +import com.graphhopper.jsprit.core.problem.driver.Driver; +import com.graphhopper.jsprit.core.problem.job.BreakForMultipleTimeWindows; +import com.graphhopper.jsprit.core.problem.job.Job; +import com.graphhopper.jsprit.core.problem.misc.JobInsertionContext; +import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute; +import com.graphhopper.jsprit.core.problem.solution.route.activity.BreakForMultipleTimeWindowsActivity; +import com.graphhopper.jsprit.core.problem.solution.route.activity.End; +import com.graphhopper.jsprit.core.problem.solution.route.activity.Start; +import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity; +import com.graphhopper.jsprit.core.problem.vehicle.Vehicle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Iterator; + +final class BreakForMultipleTimeWindowsInsertionCalculator implements JobInsertionCostsCalculator { + private static final Logger logger = LoggerFactory.getLogger(BreakInsertionCalculator.class); + + private ConstraintManager constraintManager; + + private VehicleRoutingTransportCosts transportCosts; + + private final VehicleRoutingActivityCosts activityCosts; + + private ActivityInsertionCostsCalculator additionalTransportCostsCalculator; + + private JobActivityFactory activityFactory; + + private AdditionalAccessEgressCalculator additionalAccessEgressCalculator; + + public BreakForMultipleTimeWindowsInsertionCalculator(VehicleRoutingTransportCosts routingCosts, VehicleRoutingActivityCosts activityCosts, ActivityInsertionCostsCalculator additionalTransportCostsCalculator, ConstraintManager constraintManager) { + this.transportCosts = routingCosts; + this.activityCosts = activityCosts; + this.constraintManager = constraintManager; + this.additionalTransportCostsCalculator = additionalTransportCostsCalculator; + additionalAccessEgressCalculator = new AdditionalAccessEgressCalculator(routingCosts); + logger.debug("initialise " + this); + } + + public void setJobActivityFactory(JobActivityFactory jobActivityFactory) { + this.activityFactory = jobActivityFactory; + } + + @Override + public String toString() { + return "[name=calculatesServiceInsertion]"; + } + + /** + * 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) { + BreakForMultipleTimeWindows breakToInsert = (BreakForMultipleTimeWindows) jobToInsert; + JobInsertionContext insertionContext = new JobInsertionContext(currentRoute, jobToInsert, newVehicle, newDriver, newVehicleDepartureTime); + int insertionIndex = InsertionData.NO_INDEX; + + BreakForMultipleTimeWindowsActivity breakAct2Insert = (BreakForMultipleTimeWindowsActivity) activityFactory.createActivities(breakToInsert).get(0); + insertionContext.getAssociatedActivities().add(breakAct2Insert); + + /* + check hard constraints at route level + */ + if (!constraintManager.fulfilled(insertionContext)) { + return InsertionData.createEmptyInsertionData(); + } + + /* + check soft constraints at route level + */ + double additionalICostsAtRouteLevel = constraintManager.getCosts(insertionContext); + + double bestCost = bestKnownCosts; + additionalICostsAtRouteLevel += additionalAccessEgressCalculator.getCosts(insertionContext); + + /* + generate new start and end for new vehicle + */ + Start start = new Start(newVehicle.getStartLocation(), newVehicle.getEarliestDeparture(), Double.MAX_VALUE); + start.setEndTime(newVehicleDepartureTime); + End end = new End(newVehicle.getEndLocation(), 0.0, newVehicle.getLatestArrival()); + + Location bestLocation = null; + + TourActivity prevAct = start; + double prevActStartTime = newVehicleDepartureTime; + int actIndex = 0; + Iterator activityIterator = currentRoute.getActivities().iterator(); + boolean tourEnd = false; + while (!tourEnd) { + TourActivity nextAct; + if (activityIterator.hasNext()) nextAct = activityIterator.next(); + else { + nextAct = end; + tourEnd = true; + } + boolean breakThis = true; + final Location location = Location.Builder.newInstance().setId(breakAct2Insert.getJob().getLocation().getId()).setCoordinate(prevAct.getLocation().getCoordinate()).build(); + breakAct2Insert.setLocation(location); + breakAct2Insert.setTheoreticalEarliestOperationStartTime(breakToInsert.getTimeWindow().getStart()); + breakAct2Insert.setTheoreticalLatestOperationStartTime(breakToInsert.getTimeWindow().getEnd()); + HardActivityConstraint.ConstraintsStatus status = constraintManager.fulfilled(insertionContext, prevAct, breakAct2Insert, nextAct, prevActStartTime); + if (status.equals(HardActivityConstraint.ConstraintsStatus.FULFILLED)) { + //from job2insert induced costs at activity level + double additionalICostsAtActLevel = constraintManager.getCosts(insertionContext, prevAct, breakAct2Insert, nextAct, prevActStartTime); + double additionalTransportationCosts = additionalTransportCostsCalculator.getCosts(insertionContext, prevAct, nextAct, breakAct2Insert, prevActStartTime); + if (additionalICostsAtRouteLevel + additionalICostsAtActLevel + additionalTransportationCosts < bestCost) { + bestCost = additionalICostsAtRouteLevel + additionalICostsAtActLevel + additionalTransportationCosts; + insertionIndex = actIndex; + bestLocation = location; + } + breakThis = false; + } else if (status.equals(HardActivityConstraint.ConstraintsStatus.NOT_FULFILLED)) { + breakThis = false; + } + + double nextActArrTime = prevActStartTime + transportCosts.getTransportTime(prevAct.getLocation(), nextAct.getLocation(), prevActStartTime, newDriver, newVehicle); + prevActStartTime = Math.max(nextActArrTime, nextAct.getTheoreticalEarliestOperationStartTime()) + activityCosts.getActivityDuration(prevAct, nextAct,nextActArrTime,newDriver,newVehicle); + prevAct = nextAct; + actIndex++; + if (breakThis) break; + } + if (insertionIndex == InsertionData.NO_INDEX) { + return InsertionData.createEmptyInsertionData(); + } + InsertionData insertionData = new InsertionData(bestCost, InsertionData.NO_INDEX, insertionIndex, newVehicle, newDriver); + breakAct2Insert.setLocation(bestLocation); + insertionData.getEvents().add(new InsertBreak(currentRoute, newVehicle, breakAct2Insert, insertionIndex)); + insertionData.getEvents().add(new SwitchVehicle(currentRoute, newVehicle, newVehicleDepartureTime)); + insertionData.setVehicleDepartureTime(newVehicleDepartureTime); + return insertionData; + } +} diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/JobInsertionCostsCalculatorBuilder.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/JobInsertionCostsCalculatorBuilder.java index 712c0916..c3f842a9 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/JobInsertionCostsCalculatorBuilder.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/JobInsertionCostsCalculatorBuilder.java @@ -280,7 +280,6 @@ public class JobInsertionCostsCalculatorBuilder { } JobActivityFactory activityFactory = new JobActivityFactory() { - @Override public List createActivities(Job job) { return vrp.copyAndGetActivities(job); @@ -295,12 +294,16 @@ public class JobInsertionCostsCalculatorBuilder { BreakInsertionCalculator breakInsertionCalculator = new BreakInsertionCalculator(vrp.getTransportCosts(), vrp.getActivityCosts(), actInsertionCalc, constraintManager); breakInsertionCalculator.setJobActivityFactory(activityFactory); + BreakForMultipleTimeWindowsInsertionCalculator breakForMultipleTimeWindowsInsertionCalculator = new BreakForMultipleTimeWindowsInsertionCalculator(vrp.getTransportCosts(), vrp.getActivityCosts(), actInsertionCalc, constraintManager); + breakForMultipleTimeWindowsInsertionCalculator.setJobActivityFactory(activityFactory); + JobCalculatorSwitcher switcher = new JobCalculatorSwitcher(); switcher.put(Shipment.class, shipmentInsertion); switcher.put(Service.class, serviceInsertion); switcher.put(Pickup.class, serviceInsertion); switcher.put(Delivery.class, serviceInsertion); switcher.put(Break.class, breakInsertionCalculator); + switcher.put(BreakForMultipleTimeWindows.class, breakForMultipleTimeWindowsInsertionCalculator); switcher.put(ShipmentWithMutablePickupDeliverOptions.class, shipmentInsertion); CalculatorPlusListeners calculatorPlusListeners = new CalculatorPlusListeners(switcher); 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 43d4a9ba..37b512d7 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 @@ -18,6 +18,7 @@ package com.graphhopper.jsprit.core.algorithm.recreate; import com.graphhopper.jsprit.core.problem.JobActivityFactory; +import com.graphhopper.jsprit.core.problem.Location; import com.graphhopper.jsprit.core.problem.constraint.ConstraintManager; import com.graphhopper.jsprit.core.problem.constraint.HardActivityConstraint.ConstraintsStatus; import com.graphhopper.jsprit.core.problem.constraint.SoftActivityConstraint; @@ -30,10 +31,7 @@ import com.graphhopper.jsprit.core.problem.job.Shipment; import com.graphhopper.jsprit.core.problem.misc.ActivityContext; import com.graphhopper.jsprit.core.problem.misc.JobInsertionContext; import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute; -import com.graphhopper.jsprit.core.problem.solution.route.activity.End; -import com.graphhopper.jsprit.core.problem.solution.route.activity.Start; -import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow; -import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity; +import com.graphhopper.jsprit.core.problem.solution.route.activity.*; import com.graphhopper.jsprit.core.problem.vehicle.Vehicle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -175,7 +173,6 @@ final class ShipmentInsertionCalculator extends AbstractInsertionCalculator { insertionContext.setRelatedActivityContext(pickupContext); double prevActEndTime_deliveryLoop = shipmentPickupEndTime; - /* -------------------------------- */ @@ -220,6 +217,14 @@ final class ShipmentInsertionCalculator extends AbstractInsertionCalculator { //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(prevAct_deliveryLoop, nextAct_deliveryLoop,nextActArrTime,newDriver,newVehicle); + if (i == j && nextAct_deliveryLoop instanceof BreakForMultipleTimeWindowsActivity) { + final BreakForMultipleTimeWindowsActivity breakForMultipleTimeWindowsActivity = (BreakForMultipleTimeWindowsActivity) nextAct_deliveryLoop.duplicate(); + final Location location = Location.Builder.newInstance() + .setId(breakForMultipleTimeWindowsActivity.getJob().getLocation().getId()) + .setCoordinate(prevAct_deliveryLoop.getLocation().getCoordinate()).build(); + breakForMultipleTimeWindowsActivity.setLocation(location); + nextAct_deliveryLoop = breakForMultipleTimeWindowsActivity; + } prevAct_deliveryLoop = nextAct_deliveryLoop; j++; } diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/BreakForMultipleTimeWindows.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/BreakForMultipleTimeWindows.java new file mode 100644 index 00000000..62698432 --- /dev/null +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/BreakForMultipleTimeWindows.java @@ -0,0 +1,57 @@ +package com.graphhopper.jsprit.core.problem.job; + +import com.graphhopper.jsprit.core.problem.Capacity; + +public class BreakForMultipleTimeWindows extends Service { + + public static class Builder extends Service.Builder { + + /** + * Returns a new instance of builder that builds a pickup. + * + * @param id the id of the pickup + * @return the builder + */ + public static BreakForMultipleTimeWindows.Builder newInstance(String id) { + return new BreakForMultipleTimeWindows.Builder(id); + } + + private boolean variableLocation = true; + + Builder(String id) { + super(id); + } + + /** + * Builds Pickup. + *

+ *

Pickup type is "pickup" + * + * @return pickup + * @throws IllegalStateException if neither locationId nor coordinate has been set + */ + public BreakForMultipleTimeWindows build() { + if (location != null) { + variableLocation = false; + } + this.setType("break"); + super.capacity = Capacity.Builder.newInstance().build(); + super.skills = skillBuilder.build(); + return new BreakForMultipleTimeWindows(this); + } + + } + + private boolean variableLocation = true; + + BreakForMultipleTimeWindows(BreakForMultipleTimeWindows.Builder builder) { + super(builder); + this.variableLocation = builder.variableLocation; + } + + public boolean hasVariableLocation() { + return variableLocation; + } + + +} diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/BreakForMultipleTimeWindowsActivity.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/BreakForMultipleTimeWindowsActivity.java new file mode 100644 index 00000000..e715c600 --- /dev/null +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/BreakForMultipleTimeWindowsActivity.java @@ -0,0 +1,179 @@ +package com.graphhopper.jsprit.core.problem.solution.route.activity; + +import com.graphhopper.jsprit.core.problem.AbstractActivity; +import com.graphhopper.jsprit.core.problem.Capacity; +import com.graphhopper.jsprit.core.problem.Location; +import com.graphhopper.jsprit.core.problem.job.BreakForMultipleTimeWindows; +import com.graphhopper.jsprit.core.problem.job.Service; + +public class BreakForMultipleTimeWindowsActivity extends AbstractActivity implements TourActivity.JobActivity{ + public static int counter = 0; + + public double arrTime; + + public double endTime; + + private Location location; + + private double duration; + + /** + * @return the arrTime + */ + public double getArrTime() { + return arrTime; + } + + /** + * @param arrTime the arrTime to set + */ + public void setArrTime(double arrTime) { + this.arrTime = arrTime; + } + + /** + * @return the endTime + */ + public double getEndTime() { + return endTime; + } + + /** + * @param endTime the endTime to set + */ + public void setEndTime(double endTime) { + this.endTime = endTime; + } + + public static BreakForMultipleTimeWindowsActivity copyOf(BreakForMultipleTimeWindowsActivity breakActivity) { + return new BreakForMultipleTimeWindowsActivity(breakActivity); + } + + public static BreakForMultipleTimeWindowsActivity newInstance(BreakForMultipleTimeWindows aBreak) { + return new BreakForMultipleTimeWindowsActivity(aBreak); + } + + private final BreakForMultipleTimeWindows aBreak; + + private double earliest = 0; + + private double latest = Double.MAX_VALUE; + + protected BreakForMultipleTimeWindowsActivity(BreakForMultipleTimeWindows aBreak) { + counter++; + this.aBreak = aBreak; + this.duration = aBreak.getServiceDuration(); + this.location = aBreak.getLocation(); + } + + protected BreakForMultipleTimeWindowsActivity(BreakForMultipleTimeWindowsActivity breakActivity) { + counter++; + this.aBreak = (BreakForMultipleTimeWindows) breakActivity.getJob(); + this.arrTime = breakActivity.getArrTime(); + this.endTime = breakActivity.getEndTime(); + this.location = breakActivity.getLocation(); + setIndex(breakActivity.getIndex()); + this.earliest = breakActivity.getTheoreticalEarliestOperationStartTime(); + this.latest = breakActivity.getTheoreticalLatestOperationStartTime(); + this.duration = breakActivity.getOperationTime(); + } + + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((aBreak == null) ? 0 : aBreak.hashCode()); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BreakForMultipleTimeWindowsActivity other = (BreakForMultipleTimeWindowsActivity) obj; + if (aBreak == null) { + if (other.aBreak != null) + return false; + } else if (!aBreak.equals(other.aBreak)) + return false; + return true; + } + + public double getTheoreticalEarliestOperationStartTime() { + return earliest; + } + + public double getTheoreticalLatestOperationStartTime() { + return latest; + } + + @Override + public double getOperationTime() { + return duration; + } + + public void setOperationTime(double duration){ + this.duration = duration; + } + + @Override + public Location getLocation() { + return location; + } + + public void setLocation(Location breakLocation) { + this.location = breakLocation; + } + + @Override + public Service getJob() { + return aBreak; + } + + + @Override + public String toString() { + return "[type=" + getName() + "][location=" + getLocation() + + "][size=" + getSize().toString() + + "][twStart=" + Activities.round(getTheoreticalEarliestOperationStartTime()) + + "][twEnd=" + Activities.round(getTheoreticalLatestOperationStartTime()) + "]"; + } + + @Override + public void setTheoreticalEarliestOperationStartTime(double earliest) { + this.earliest = earliest; + } + + @Override + public void setTheoreticalLatestOperationStartTime(double latest) { + this.latest = latest; + } + + @Override + public String getName() { + return aBreak.getType(); + } + + @Override + public TourActivity duplicate() { + return new BreakForMultipleTimeWindowsActivity(this); + } + + @Override + public Capacity getSize() { + return aBreak.getSize(); + } + + +} diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/DefaultTourActivityFactory.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/DefaultTourActivityFactory.java index 0f1047dc..c573d01a 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/DefaultTourActivityFactory.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/solution/route/activity/DefaultTourActivityFactory.java @@ -18,6 +18,7 @@ package com.graphhopper.jsprit.core.problem.solution.route.activity; import com.graphhopper.jsprit.core.problem.AbstractActivity; +import com.graphhopper.jsprit.core.problem.job.BreakForMultipleTimeWindows; import com.graphhopper.jsprit.core.problem.job.Delivery; import com.graphhopper.jsprit.core.problem.job.Pickup; import com.graphhopper.jsprit.core.problem.job.Service; @@ -27,7 +28,9 @@ public class DefaultTourActivityFactory implements TourActivityFactory { @Override public AbstractActivity createActivity(Service service) { AbstractActivity act; - if (service instanceof Pickup) { + if (service instanceof BreakForMultipleTimeWindows) { + act = new BreakForMultipleTimeWindowsActivity((BreakForMultipleTimeWindows) service); + } else if (service instanceof Pickup) { act = new PickupService((Pickup) service); } else if (service instanceof Delivery) { act = new DeliverService((Delivery) service); diff --git a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/recreate/ShipmentInsertionCalculatorTest.java b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/recreate/ShipmentInsertionCalculatorTest.java index 1295ad7d..674a5c91 100644 --- a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/recreate/ShipmentInsertionCalculatorTest.java +++ b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/recreate/ShipmentInsertionCalculatorTest.java @@ -31,27 +31,27 @@ import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingActivityCosts; import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingTransportCosts; import com.graphhopper.jsprit.core.problem.driver.Driver; import com.graphhopper.jsprit.core.problem.driver.DriverImpl; +import com.graphhopper.jsprit.core.problem.job.BreakForMultipleTimeWindows; import com.graphhopper.jsprit.core.problem.job.Pickup; import com.graphhopper.jsprit.core.problem.job.Service; import com.graphhopper.jsprit.core.problem.job.Shipment; import com.graphhopper.jsprit.core.problem.misc.JobInsertionContext; import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute; -import com.graphhopper.jsprit.core.problem.solution.route.activity.DeliverShipment; -import com.graphhopper.jsprit.core.problem.solution.route.activity.PickupService; -import com.graphhopper.jsprit.core.problem.solution.route.activity.PickupShipment; -import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity; +import com.graphhopper.jsprit.core.problem.solution.route.activity.*; import com.graphhopper.jsprit.core.problem.solution.route.state.RouteAndActivityStateGetter; import com.graphhopper.jsprit.core.problem.vehicle.Vehicle; import com.graphhopper.jsprit.core.problem.vehicle.VehicleImpl; import com.graphhopper.jsprit.core.problem.vehicle.VehicleType; import com.graphhopper.jsprit.core.problem.vehicle.VehicleTypeImpl; import com.graphhopper.jsprit.core.util.CostFactory; +import com.graphhopper.jsprit.core.util.ManhattanCosts; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.UUID; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -316,5 +316,48 @@ public class ShipmentInsertionCalculatorTest { assertEquals(3, iData.getDeliveryInsertionIndex()); } + @Test + public void whenPickupInsertedBeforeBreak() { + final BreakForMultipleTimeWindows breakForMultipleTimeWindows = + BreakForMultipleTimeWindows.Builder.newInstance("break") + .setLocation(Location.newInstance(5, 10)) + .addTimeWindow(20, 20) + .setServiceTime(5) + .build(); + final VehicleImpl vehicle = VehicleImpl.Builder.newInstance(UUID.randomUUID().toString()) + .setStartLocation(Location.newInstance(0, 0)) + .build(); + final VehicleRoute route = VehicleRoute.Builder.newInstance(vehicle) + .addService(breakForMultipleTimeWindows) + .build(); + route.getActivities().get(0).setArrTime(5); + route.getActivities().get(0).setEndTime(10); + + Shipment shipment = Shipment.Builder.newInstance("shipment") + .setPickupLocation(Location.newInstance(0,10)) + .setPickupTimeWindow(new TimeWindow(0, 20)) + .setPickupServiceTime(10) + .setDeliveryLocation(Location.newInstance(10,10)) + .setDeliveryTimeWindow(new TimeWindow(60, 160)) + .setDeliveryServiceTime(10) + .build(); + + VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance().addJob(shipment).addJob(breakForMultipleTimeWindows).build(); + + StateManager stateManager = new StateManager(vrp); + final ConstraintManager constraintManager = new ConstraintManager(vrp, stateManager); + ShipmentInsertionCalculator insertionCalculator = new ShipmentInsertionCalculator(new ManhattanCosts() { + @Override + public double getTransportTime(Location from, Location to, double departureTime, Driver driver, Vehicle vehicle) { + return (to.getId() != null && to.getId().equals("break")) ? .0 : super.getTransportTime(from, to, departureTime, driver, vehicle); + } + }, activityCosts, + activityInsertionCostsCalculator, constraintManager); + insertionCalculator.setJobActivityFactory(vrp.getJobActivityFactory()); + + InsertionData iData = insertionCalculator.getInsertionData(route, shipment, vehicle, 0.0, null, Double.MAX_VALUE); + assertEquals(0, iData.getPickupInsertionIndex()); + assertEquals(0, iData.getDeliveryInsertionIndex()); + } } diff --git a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/problem/job/BreakForMultipleTimeWindowsTest.java b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/problem/job/BreakForMultipleTimeWindowsTest.java new file mode 100644 index 00000000..43efd377 --- /dev/null +++ b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/problem/job/BreakForMultipleTimeWindowsTest.java @@ -0,0 +1,31 @@ +package com.graphhopper.jsprit.core.problem.job; + +import com.graphhopper.jsprit.core.problem.Location; +import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow; +import org.junit.Test; + +import java.util.UUID; + +import static org.junit.Assert.*; + +public class BreakForMultipleTimeWindowsTest { + + @Test + public void breakCreatedWithDriverIdAndSkills() { + final String serviceId = UUID.randomUUID().toString(); + final String skill = UUID.randomUUID().toString(); + final BreakForMultipleTimeWindows breakForMultipleTimeWindows = + BreakForMultipleTimeWindows.Builder.newInstance(serviceId) + .setLocation(Location.newInstance(-0.25, 0.25)) + .addTimeWindow(0, 60) + .setServiceTime(20) + .addRequiredSkill(skill) + .setPriority(1) + .build(); + + assertTrue(breakForMultipleTimeWindows.getRequiredSkills().containsSkill(skill)); + assertEquals(Location.newInstance(-0.25, 0.25), breakForMultipleTimeWindows.getLocation()); + assertEquals(new TimeWindow(0, 60), breakForMultipleTimeWindows.getTimeWindow()); + assertEquals(20, breakForMultipleTimeWindows.getServiceDuration(), .0001); + } +}