diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/run/EDrtModeOptimizerQSimModule.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/run/EDrtModeOptimizerQSimModule.java index 546f5b90cc2..c888ff86d8d 100644 --- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/run/EDrtModeOptimizerQSimModule.java +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/edrt/run/EDrtModeOptimizerQSimModule.java @@ -134,7 +134,8 @@ public EmptyVehicleChargingScheduler get() { getter.getModal(PassengerStopDurationProvider.class)))).asEagerSingleton(); bindModal(InsertionCostCalculator.class).toProvider(modalProvider( - getter -> new DefaultInsertionCostCalculator(getter.getModal(CostCalculationStrategy.class)))); + getter -> new DefaultInsertionCostCalculator(getter.getModal(CostCalculationStrategy.class), + drtCfg.addOrGetDrtOptimizationConstraintsParams().addOrGetDefaultDrtOptimizationConstraintsSet()))); install(DrtModeOptimizerQSimModule.getInsertionSearchQSimModule(drtCfg)); diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/run/ShiftDrtModeOptimizerQSimModule.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/run/ShiftDrtModeOptimizerQSimModule.java index e7f4f58b391..5fbae488eec 100644 --- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/run/ShiftDrtModeOptimizerQSimModule.java +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/run/ShiftDrtModeOptimizerQSimModule.java @@ -121,7 +121,8 @@ shiftsParams, new DefaultShiftStartLogic(), new DefaultAssignShiftToVehicleLogic bindModal(InsertionCostCalculator.class).toProvider(modalProvider( getter -> new ShiftInsertionCostCalculator(getter.get(MobsimTimer.class), - new DefaultInsertionCostCalculator(getter.getModal(CostCalculationStrategy.class))))); + new DefaultInsertionCostCalculator(getter.getModal(CostCalculationStrategy.class), + drtCfg.addOrGetDrtOptimizationConstraintsParams().addOrGetDefaultDrtOptimizationConstraintsSet())))); bindModal(VehicleEntry.EntryFactory.class).toInstance(new ShiftVehicleDataEntryFactory(new VehicleDataEntryFactoryImpl())); diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DrtModeOptimizerQSimModule.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DrtModeOptimizerQSimModule.java index 6110f4e8b87..9a930b9cdbf 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DrtModeOptimizerQSimModule.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DrtModeOptimizerQSimModule.java @@ -110,7 +110,8 @@ protected void configureQSim() { getter.getModal(PassengerStopDurationProvider.class)))).asEagerSingleton(); bindModal(InsertionCostCalculator.class).toProvider(modalProvider( - getter -> new DefaultInsertionCostCalculator(getter.getModal(CostCalculationStrategy.class)))); + getter -> new DefaultInsertionCostCalculator(getter.getModal(CostCalculationStrategy.class), + drtCfg.addOrGetDrtOptimizationConstraintsParams().addOrGetDefaultDrtOptimizationConstraintsSet()))); install(getInsertionSearchQSimModule(drtCfg)); diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DrtOptimizationConstraintsSet.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DrtOptimizationConstraintsSet.java index d80bc047b4e..63a03eaa014 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DrtOptimizationConstraintsSet.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/DrtOptimizationConstraintsSet.java @@ -88,6 +88,15 @@ public DrtOptimizationConstraintsSet() { @PositiveOrZero // used only for stopbased DRT scheme public double maxWalkDistance = Double.MAX_VALUE;// [m]; + @Parameter + @Comment( + "Time before reaching a planned dropoff from which it is not allowed to insert new detours for new requests. I.e.," + + " if set to 180, then a vehicle will not divert to pickup or dropoff a new passenger once a boarded passenger is only " + + "3 minutes away from her destination, even though her time window would allow it." + + " Delayed detours just before arrival are usually perceived very negatively.") + @PositiveOrZero + public double lateDiversionthreshold = 0; // [s]; + @Override protected void checkConsistency(Config config) { super.checkConsistency(config); diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/DefaultInsertionCostCalculator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/DefaultInsertionCostCalculator.java index 139d5262b12..f58acc13a27 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/DefaultInsertionCostCalculator.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/DefaultInsertionCostCalculator.java @@ -19,19 +19,25 @@ package org.matsim.contrib.drt.optimizer.insertion; -import static org.matsim.contrib.drt.optimizer.insertion.InsertionGenerator.Insertion; - +import org.matsim.contrib.drt.optimizer.DrtOptimizationConstraintsSet; +import org.matsim.contrib.drt.optimizer.VehicleEntry; +import org.matsim.contrib.drt.optimizer.Waypoint; import org.matsim.contrib.drt.optimizer.insertion.InsertionDetourTimeCalculator.DetourTimeInfo; import org.matsim.contrib.drt.passenger.DrtRequest; +import static org.matsim.contrib.drt.optimizer.insertion.InsertionGenerator.Insertion; + /** * @author michalm */ public class DefaultInsertionCostCalculator implements InsertionCostCalculator { private final CostCalculationStrategy costCalculationStrategy; + private final DrtOptimizationConstraintsSet constraintsSet; - public DefaultInsertionCostCalculator(CostCalculationStrategy costCalculationStrategy) { + public DefaultInsertionCostCalculator(CostCalculationStrategy costCalculationStrategy, + DrtOptimizationConstraintsSet constraintsSet) { this.costCalculationStrategy = costCalculationStrategy; + this.constraintsSet = constraintsSet; } /** @@ -67,6 +73,12 @@ public double calculate(DrtRequest drtRequest, Insertion insertion, DetourTimeIn return INFEASIBLE_SOLUTION_COST; } + if (vEntry.stops != null && !vEntry.stops.isEmpty() && constraintsSet.lateDiversionthreshold > 0) { + if(violatesLateDiversion(insertion, detourTimeInfo, vEntry, effectiveDropoffTimeLoss)) { + return INFEASIBLE_SOLUTION_COST; + } + } + return costCalculationStrategy.calcCost(drtRequest, insertion, detourTimeInfo); } @@ -75,4 +87,35 @@ private boolean violatesMaxRideDuration(DrtRequest drtRequest, InsertionDetourTi double rideDuration = detourTimeInfo.dropoffDetourInfo.arrivalTime - detourTimeInfo.pickupDetourInfo.departureTime; return drtRequest.getMaxRideDuration() < rideDuration; } -} + + private boolean violatesLateDiversion(Insertion insertion, DetourTimeInfo detourTimeInfo, + VehicleEntry vEntry, double effectiveDropoffTimeLoss) { + if (detourTimeInfo.pickupDetourInfo.pickupTimeLoss > 0) { + if (violatesLateDiversionBetweenStopIndices(vEntry, insertion.pickup.index, insertion.dropoff.index, + constraintsSet.lateDiversionthreshold)) { + return true; + } + } + if (effectiveDropoffTimeLoss > 0) { + if (violatesLateDiversionBetweenStopIndices(vEntry, insertion.dropoff.index, vEntry.stops.size(), + constraintsSet.lateDiversionthreshold)) { + return true; + } + } + return false; + } + + private boolean violatesLateDiversionBetweenStopIndices(VehicleEntry vehicleEntry, int start, int end, double lateDiversionThreshold) { + for (int s = start; s < end; s++) { + Waypoint.Stop stop = vehicleEntry.stops.get(s); + if (!stop.task.getDropoffRequests().isEmpty()) { + double remainingRideDuration = stop.getArrivalTime() - vehicleEntry.start.getDepartureTime(); + if (remainingRideDuration < lateDiversionThreshold) { + return true; + } + break; + } + } + return false; + } +} \ No newline at end of file diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionCostCalculatorTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionCostCalculatorTest.java index 0d617d9fc9f..8fab605b992 100644 --- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionCostCalculatorTest.java +++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionCostCalculatorTest.java @@ -20,18 +20,25 @@ package org.matsim.contrib.drt.optimizer.insertion; -import static org.assertj.core.api.Assertions.assertThat; -import static org.matsim.contrib.drt.optimizer.insertion.InsertionCostCalculator.INFEASIBLE_SOLUTION_COST; -import static org.matsim.contrib.drt.optimizer.insertion.InsertionDetourTimeCalculator.*; - +import com.google.common.collect.ImmutableList; import org.junit.jupiter.api.Test; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.network.Link; +import org.matsim.contrib.drt.optimizer.DrtOptimizationConstraintsSet; import org.matsim.contrib.drt.optimizer.VehicleEntry; +import org.matsim.contrib.drt.optimizer.Waypoint; import org.matsim.contrib.drt.optimizer.insertion.InsertionGenerator.Insertion; +import org.matsim.contrib.drt.passenger.AcceptedDrtRequest; import org.matsim.contrib.drt.passenger.DrtRequest; +import org.matsim.contrib.drt.run.DrtConfigGroup; +import org.matsim.contrib.drt.schedule.DefaultDrtStopTask; +import org.matsim.contrib.drt.schedule.DrtStopTask; import org.matsim.testcases.fakes.FakeLink; +import static org.assertj.core.api.Assertions.assertThat; +import static org.matsim.contrib.drt.optimizer.insertion.InsertionCostCalculator.INFEASIBLE_SOLUTION_COST; +import static org.matsim.contrib.drt.optimizer.insertion.InsertionDetourTimeCalculator.*; + /** * @author Michal Maciejewski (michalm) */ @@ -42,36 +49,141 @@ public class InsertionCostCalculatorTest { @Test void testCalculate() { - VehicleEntry entry = entry(new double[] { 20, 20, 50 }); + VehicleEntry entry = entry(new double[] { 20, 20, 50 }, ImmutableList.builder().build(), null); var insertion = insertion(entry, 0, 1); //feasible solution + final DrtConfigGroup drtConfigGroup = new DrtConfigGroup(); assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(0, 11), new DropoffDetourInfo(0, 22)), - 11 + 22); + 11 + 22, drtRequest, drtConfigGroup.addOrGetDrtOptimizationConstraintsParams().addOrGetDefaultDrtOptimizationConstraintsSet()); //feasible solution - longest possible pickup and dropoff time losses + final DrtConfigGroup drtConfigGroup1 = new DrtConfigGroup(); assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(0, 20), new DropoffDetourInfo(0, 30)), - 20 + 30); + 20 + 30, drtRequest, drtConfigGroup1.addOrGetDrtOptimizationConstraintsParams().addOrGetDefaultDrtOptimizationConstraintsSet()); //infeasible solution - time constraints at stop 0 + final DrtConfigGroup drtConfigGroup2 = new DrtConfigGroup(); assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(0, 21), new DropoffDetourInfo(0, 29)), - INFEASIBLE_SOLUTION_COST); + INFEASIBLE_SOLUTION_COST, drtRequest, drtConfigGroup2.addOrGetDrtOptimizationConstraintsParams().addOrGetDefaultDrtOptimizationConstraintsSet()); //infeasible solution - vehicle time constraints + final DrtConfigGroup drtConfigGroup3 = new DrtConfigGroup(); assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(0, 20), new DropoffDetourInfo(0, 31)), - INFEASIBLE_SOLUTION_COST); + INFEASIBLE_SOLUTION_COST, drtRequest, drtConfigGroup3.addOrGetDrtOptimizationConstraintsParams().addOrGetDefaultDrtOptimizationConstraintsSet()); + } + + + @Test + public void testAllowDetourBeforeArrivalThreshold() { + + + // start (0s) -----> new PU (60s) -----> existing DO (120s) -----> new DO (300s) + + Waypoint.Start start = new Waypoint.Start(null, link("start"), 0, 1); + + DrtStopTask existingDropoffTask = new DefaultDrtStopTask(120, 150, link("boardedDO")); + DrtRequest boardedRequest = DrtRequest.newBuilder().fromLink(link("boardedFrom")).toLink(link("boardedTo")).build(); + + AcceptedDrtRequest existingRequest = AcceptedDrtRequest.createFromOriginalRequest(boardedRequest); + existingDropoffTask.addDropoffRequest(existingRequest); + + Waypoint.Stop[] stops = new Waypoint.Stop[1]; + stops[0] = new Waypoint.Stop(existingDropoffTask, 0); + + VehicleEntry entry = entry(new double[] {60, 60, 300}, ImmutableList.copyOf(stops), start); + var insertion = insertion(entry, 0, 1); + + DrtRequest drtRequest = DrtRequest.newBuilder() + .fromLink(fromLink) + .toLink(toLink) + .latestStartTime(120) + .latestArrivalTime(300) + .maxRideDuration(Double.MAX_VALUE) + .build(); + + DrtConfigGroup drtConfigGroup = new DrtConfigGroup(); + DrtOptimizationConstraintsSet drtOptimizationConstraintsSet = drtConfigGroup.addOrGetDrtOptimizationConstraintsParams().addOrGetDefaultDrtOptimizationConstraintsSet(); + + // new insertion before dropoff of boarded passenger within threshold - infeasible solution + drtOptimizationConstraintsSet.lateDiversionthreshold = 180; + assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(60, 30), new DropoffDetourInfo(300, 30)), + INFEASIBLE_SOLUTION_COST, drtRequest, drtOptimizationConstraintsSet); + + // new insertion before dropoff of boarded passenger, inside of threshold but no additional delay - feasible solution + assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(120, 0), new DropoffDetourInfo(300, 30)), + 30, drtRequest, drtOptimizationConstraintsSet); + + // new insertion before dropoff of boarded passenger, but outside of threshold - feasible solution + drtOptimizationConstraintsSet.lateDiversionthreshold = 120; + assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(60, 30), new DropoffDetourInfo(300, 30)), + 60, drtRequest, drtOptimizationConstraintsSet); + + + // new insertion after dropoff of boarded passenger - feasible solution + insertion = insertion(entry, 1, 1); + assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(60, 30), new DropoffDetourInfo(300, 30)), + 60, drtRequest, drtOptimizationConstraintsSet); + } + + @Test + public void testAllowDetourBeforeArrivalThreshold2() { + + // start (0s) -----> new PU (60s) -----> existing PU (120s) -----> existing DO (200s) -----> new DO (300s) + + Waypoint.Start start = new Waypoint.Start(null, link("start"), 0, 0); + + DrtStopTask existingPickupTask = new DefaultDrtStopTask(120, 150, link("scheduledPU")); + DrtRequest scheduledRequest = DrtRequest.newBuilder().fromLink(link("scheduledFrom")).toLink(link("scheduledTo")).build(); + AcceptedDrtRequest acceptedScheduledRequest = AcceptedDrtRequest.createFromOriginalRequest(scheduledRequest); + existingPickupTask.addPickupRequest(acceptedScheduledRequest); + + DrtStopTask existingDropoffTask = new DefaultDrtStopTask(200, 230, link("boardedDO")); + DrtRequest boardedRequest = DrtRequest.newBuilder().fromLink(link("boardedFrom")).toLink(link("boardedTo")).build(); + AcceptedDrtRequest existingRequest = AcceptedDrtRequest.createFromOriginalRequest(boardedRequest); + existingDropoffTask.addDropoffRequest(existingRequest); + + Waypoint.Stop[] stops = new Waypoint.Stop[2]; + stops[0] = new Waypoint.Stop(existingPickupTask, 2); + stops[1] = new Waypoint.Stop(existingDropoffTask, 1); + + VehicleEntry entry = entry(new double[] {60, 60, 60, 300}, ImmutableList.copyOf(stops), start); + + + var insertion = insertion(entry, 0, 2); + + DrtRequest drtRequest = DrtRequest.newBuilder() + .fromLink(fromLink) + .toLink(toLink) + .latestStartTime(120) + .latestArrivalTime(300) + .maxRideDuration(Double.MAX_VALUE) + .build(); + + DrtConfigGroup drtConfigGroup = new DrtConfigGroup(); + + // new insertion before dropoff of boarded passenger within threshold - infeasible solution + DrtOptimizationConstraintsSet constraintsSet = drtConfigGroup.addOrGetDrtOptimizationConstraintsParams().addOrGetDefaultDrtOptimizationConstraintsSet(); + constraintsSet.lateDiversionthreshold = 300; + assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(60, 60), new DropoffDetourInfo(300, 60)), + INFEASIBLE_SOLUTION_COST, drtRequest, constraintsSet); + + // new insertion before dropoff of boarded passenger outside of threshold - feasible solution + constraintsSet.lateDiversionthreshold = 200; + assertCalculate(insertion, new DetourTimeInfo(new PickupDetourInfo(60, 60), new DropoffDetourInfo(300, 60)), + 120, drtRequest, constraintsSet); } - private void assertCalculate(Insertion insertion, DetourTimeInfo detourTimeInfo, double expectedCost) { + private void assertCalculate(Insertion insertion, DetourTimeInfo detourTimeInfo, double expectedCost, DrtRequest drtRequest, DrtOptimizationConstraintsSet constraintsSet) { var insertionCostCalculator = new DefaultInsertionCostCalculator( - new CostCalculationStrategy.RejectSoftConstraintViolations()); + new CostCalculationStrategy.RejectSoftConstraintViolations(), constraintsSet); var insertionWithDetourData = new InsertionWithDetourData(insertion, null, detourTimeInfo); assertThat(insertionCostCalculator.calculate(drtRequest, insertionWithDetourData.insertion, insertionWithDetourData.detourTimeInfo)).isEqualTo(expectedCost); } - private VehicleEntry entry(double[] slackTimes) { - return new VehicleEntry(null, null, null, slackTimes, null, 0); + private VehicleEntry entry(double[] slackTimes, ImmutableList stops, Waypoint.Start start) { + return new VehicleEntry(null, start, stops, slackTimes, stops.stream().map(s -> 0.).toList(), 0); } private Link link(String id) {