Skip to content

Commit

Permalink
fix(drt): bugs with variable stop duration and prebooking stop logic (#…
Browse files Browse the repository at this point in the history
…3205)

* add failing unit tests

* correct failing unit tests

* implement fix
  • Loading branch information
sebhoerl authored Apr 7, 2024
1 parent a898855 commit 93e317b
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import static org.matsim.contrib.drt.schedule.DrtTaskBaseType.getBaseTypeOrElseThrow;

import java.util.List;

import org.matsim.contrib.drt.passenger.AcceptedDrtRequest;
import org.matsim.contrib.drt.prebooking.abandon.AbandonVoter;
import org.matsim.contrib.drt.schedule.DrtStopTask;
import org.matsim.contrib.drt.schedule.DrtTaskBaseType;
Expand Down Expand Up @@ -47,11 +50,42 @@ public DynAction createAction(DynAgent dynAgent, DvrpVehicle vehicle, double now

if (getBaseTypeOrElseThrow(task).equals(DrtTaskBaseType.STOP)) {
DrtStopTask stopTask = (DrtStopTask) task;
int incomingCapacity = getIncomingOccupancy(vehicle, stopTask);

return new PrebookingStopActivity(passengerHandler, dynAgent, stopTask, stopTask.getDropoffRequests(),
stopTask.getPickupRequests(), DrtActionCreator.DRT_STOP_NAME, () -> stopTask.getEndTime(),
stopDurationProvider, vehicle, prebookingManager, abandonVoter);
stopDurationProvider, vehicle, prebookingManager, abandonVoter, incomingCapacity);
}

return delegate.createAction(dynAgent, vehicle, now);
}

private int getIncomingOccupancy(DvrpVehicle vehicle, DrtStopTask referenceTask) {
int incomingOccupancy = 0;

List<? extends Task> tasks = vehicle.getSchedule().getTasks();

int index = tasks.size() - 1;
while (index >= 0) {
Task task = tasks.get(index);

if (task instanceof DrtStopTask) {
DrtStopTask stopTask = (DrtStopTask) task;

incomingOccupancy += stopTask.getDropoffRequests().values().stream()
.mapToInt(AcceptedDrtRequest::getPassengerCount).sum();

incomingOccupancy -= stopTask.getPickupRequests().values().stream()
.mapToInt(AcceptedDrtRequest::getPassengerCount).sum();

if (task == referenceTask) {
return incomingOccupancy;
}
}

index--;
}

throw new IllegalStateException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,13 @@ public class PrebookingStopActivity extends FirstLastSimStepDynActivity implemen
private final AbandonVoter abandonVoter;

private final Supplier<Double> endTime;
private int onboard;

public PrebookingStopActivity(PassengerHandler passengerHandler, DynAgent driver, StayTask task,
Map<Id<Request>, ? extends AcceptedDrtRequest> dropoffRequests,
Map<Id<Request>, ? extends AcceptedDrtRequest> pickupRequests, String activityType,
Supplier<Double> endTime, PassengerStopDurationProvider stopDurationProvider, DvrpVehicle vehicle,
PrebookingManager prebookingManager, AbandonVoter abandonVoter) {
PrebookingManager prebookingManager, AbandonVoter abandonVoter, int initialOccupancy) {
super(activityType);
this.passengerHandler = passengerHandler;
this.driver = driver;
Expand All @@ -64,12 +65,13 @@ public PrebookingStopActivity(PassengerHandler passengerHandler, DynAgent driver
this.prebookingManager = prebookingManager;
this.abandonVoter = abandonVoter;
this.endTime = endTime;
this.onboard = initialOccupancy;
}

@Override
protected boolean isLastStep(double now) {
boolean pickupsReady = updatePickupRequests(now);
boolean dropoffsReady = updateDropoffRequests(now);
boolean pickupsReady = updatePickupRequests(now);
return pickupsReady && dropoffsReady && now >= endTime.get();
}

Expand Down Expand Up @@ -97,6 +99,7 @@ private boolean updateDropoffRequests(double now) {
if (entry.getValue() <= now) { // Request should leave now
passengerHandler.dropOffPassengers(driver, entry.getKey(), now);
prebookingManager.notifyDropoff(entry.getKey());
onboard -= dropoffRequests.get(entry.getKey()).getPassengerCount();
iterator.remove();
}
}
Expand Down Expand Up @@ -128,12 +131,18 @@ private boolean updatePickupRequests(double now) {
var enterIterator = enterTimes.entrySet().iterator();

while (enterIterator.hasNext()) {
if (onboard >= vehicle.getCapacity()) {
// only let people enter if there is currently free capacity
break;
}

var entry = enterIterator.next();

if (entry.getValue() <= now) {
// let agent enter now
Verify.verify(passengerHandler.tryPickUpPassengers(this, driver, entry.getKey(), now));
enteredRequests.add(entry.getKey());
onboard += pickupRequests.get(entry.getKey()).getPassengerCount();
enterIterator.remove();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.matsim.contrib.drt.passenger.DrtRequest;
import org.matsim.contrib.drt.prebooking.PrebookingTestEnvironment.RequestInfo;
import org.matsim.contrib.drt.prebooking.logic.AttributeBasedPrebookingLogic;
import org.matsim.contrib.drt.run.DrtConfigGroup;
import org.matsim.contrib.drt.stops.PassengerStopDurationProvider;
import org.matsim.contrib.drt.stops.StaticPassengerStopDurationProvider;
import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
import org.matsim.contrib.dvrp.run.AbstractDvrpModeModule;
import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule;
import org.matsim.core.controler.Controler;
import org.matsim.testcases.MatsimTestUtils;

Expand Down Expand Up @@ -566,7 +569,7 @@ void destinationEqualsPrebookedOrigin_twoRequests() {
assertEquals(8000.0 + 60.0 + 1.0, requestInfo.pickupTime, 1e-3);
assertEquals(8230.0, requestInfo.dropoffTime, 1e-3);
}

{
RequestInfo requestInfo = environment.getRequestInfo().get("requestC");
assertEquals(2.0, requestInfo.submissionTime, 1e-3);
Expand All @@ -576,7 +579,7 @@ void destinationEqualsPrebookedOrigin_twoRequests() {

assertEquals(4, environment.getTaskInfo().get("vehicleA").stream().filter(t -> t.type.equals("STOP")).count());
}

@Test
void destinationEqualsPrebookedOrigin_oneRequest() {
/*-
Expand Down Expand Up @@ -614,4 +617,147 @@ void destinationEqualsPrebookedOrigin_oneRequest() {

assertEquals(4, environment.getTaskInfo().get("vehicleA").stream().filter(t -> t.type.equals("STOP")).count());
}

@Test
void intraStopTiming_pickupTooEarly() {
/*-
* In this test, we cover the intra stop timing when we use customizable stop
* durations. Before the fix, there was a bug described by the following
* situation:
*
* - We look for an insertion for a pickup
* - We find an existing stop that already contains some dropoffs
* - Inserting the pickup overall will fit (assuming that the persons are dropped off, then picked up)
* - But because of variable stop durations, the pickup happens *before* the dropoffs
* - Leading to a few seconds in which the occupancy is higher than the vehicle capacity
*
* This test led to a VerifyException in VehicleOccupancyProfileCalculator.processOccupancyChange
*/

PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) //
.addVehicle("vehicleA", 1, 1) //
.setVehicleCapacity(2) //
.addRequest("requestA1", 1, 1, 8, 8, 2000.0, 1.0) // forward
.addRequest("requestA2", 1, 1, 8, 8, 2000.0, 2.0) // forward
.addRequest("requestB1", 8, 8, 1, 1, 2356.0, 3.0) // backward
.configure(300.0, 2.0, 1800.0, 60.0) //
.endTime(12.0 * 3600.0);

Controler controller = environment.build();
installPrebooking(controller);

controller.addOverridingModule(new AbstractDvrpModeModule("drt") {
@Override
public void install() {
bindModal(PassengerStopDurationProvider.class).toInstance(new PassengerStopDurationProvider() {
@Override
public double calcPickupDuration(DvrpVehicle vehicle, DrtRequest request) {
if (request.getPassengerIds().get(0).toString().startsWith("requestA")) {
return 60.0;
} else {
return 30.0; // shorter than the dropoff duration (see below)
}
}

@Override
public double calcDropoffDuration(DvrpVehicle vehicle, DrtRequest request) {
return 60.0;
}
});
}
});

controller.run();

{
RequestInfo requestInfo = environment.getRequestInfo().get("requestA1");
assertEquals(1.0, requestInfo.submissionTime, 1e-3);
assertEquals(2000.0 + 60.0 + 1.0, requestInfo.pickupTime, 1e-3);
assertEquals(2356.0 + 60.0, requestInfo.dropoffTime, 1e-3);
}

{
RequestInfo requestInfo = environment.getRequestInfo().get("requestA2");
assertEquals(2.0, requestInfo.submissionTime, 1e-3);
assertEquals(2000.0 + 60.0 + 1.0, requestInfo.pickupTime, 1e-3);
assertEquals(2356.0 + 60.0, requestInfo.dropoffTime, 1e-3);
}

{
RequestInfo requestInfo = environment.getRequestInfo().get("requestB1");
assertEquals(3.0, requestInfo.submissionTime, 1e-3);
assertEquals(2356.0 + 60.0, requestInfo.pickupTime, 1e-3); // NOT 30s because we need to wait for the dropoffs
assertEquals(2753.0 + 60.0, requestInfo.dropoffTime, 1e-3);
}

assertEquals(3, environment.getTaskInfo().get("vehicleA").stream().filter(t -> t.type.equals("STOP")).count());
}

@Test
void intraStopTiming_dropoffTooLate() {
/*-
* Inverse situation of the previous test: A new request is inserted, but the dropoff
* happens too late compared to the pickups in the following stop task.
*
* This test led to a VerifyException in VehicleOccupancyProfileCalculator.processOccupancyChange
*/

PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) //
.addVehicle("vehicleA", 1, 1) //
.setVehicleCapacity(2) //
.addRequest("requestA", 1, 1, 8, 8, 2000.0, 1.0) // forward
.addRequest("requestB1", 8, 8, 1, 1, 2356.0, 2.0) // backward
.addRequest("requestB2", 8, 8, 1, 1, 2356.0, 3.0) // backward
.configure(300.0, 2.0, 1800.0, 60.0) //
.endTime(12.0 * 3600.0);

Controler controller = environment.build();
installPrebooking(controller);

controller.addOverridingModule(new AbstractDvrpModeModule("drt") {
@Override
public void install() {
bindModal(PassengerStopDurationProvider.class).toInstance(new PassengerStopDurationProvider() {
@Override
public double calcPickupDuration(DvrpVehicle vehicle, DrtRequest request) {
return 60.0;
}

@Override
public double calcDropoffDuration(DvrpVehicle vehicle, DrtRequest request) {
if (request.getPassengerIds().get(0).toString().equals("requestA")) {
return 90.0; // longer than the pickups
} else {
return 60.0;
}
}
});
}
});

controller.run();

{
RequestInfo requestInfo = environment.getRequestInfo().get("requestA");
assertEquals(1.0, requestInfo.submissionTime, 1e-3);
assertEquals(2000.0 + 60.0 + 1.0, requestInfo.pickupTime, 1e-3);
assertEquals(2356.0 + 90.0, requestInfo.dropoffTime, 1e-3);
}

{
RequestInfo requestInfo = environment.getRequestInfo().get("requestB1");
assertEquals(2.0, requestInfo.submissionTime, 1e-3);
assertEquals(2356.0 + 60.0, requestInfo.pickupTime, 1e-3);
assertEquals(2753.0 + 60.0 + 30.0, requestInfo.dropoffTime, 1e-3); // +30 because we wait for dropoff of A for B2 to enter
}

{
RequestInfo requestInfo = environment.getRequestInfo().get("requestB2");
assertEquals(3.0, requestInfo.submissionTime, 1e-3);
assertEquals(2356.0 + 60.0 + 30.0, requestInfo.pickupTime, 1e-3); // +30 because we wait for dropoff of A
assertEquals(2753.0 + 60.0 + 30.0, requestInfo.dropoffTime, 1e-3); // +30 because we wait for dropoff of A
}

assertEquals(3, environment.getTaskInfo().get("vehicleA").stream().filter(t -> t.type.equals("STOP")).count());
}
}

0 comments on commit 93e317b

Please sign in to comment.