diff --git a/contribs/drt-extensions/pom.xml b/contribs/drt-extensions/pom.xml
index 2217f9ebdc7..3e2cbf8dcf7 100644
--- a/contribs/drt-extensions/pom.xml
+++ b/contribs/drt-extensions/pom.xml
@@ -48,6 +48,11 @@
16.0-SNAPSHOT
test
+
+
+ org.mockito
+ mockito-core
+
diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/schedule/EDrtShiftChangeoverTaskImpl.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/schedule/EDrtShiftChangeoverTaskImpl.java
index 9cc818dace7..007a9f2fe3f 100644
--- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/schedule/EDrtShiftChangeoverTaskImpl.java
+++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/schedule/EDrtShiftChangeoverTaskImpl.java
@@ -82,4 +82,14 @@ public void addDropoffRequest(AcceptedDrtRequest request) {
public void addPickupRequest(AcceptedDrtRequest request) {
delegate.addPickupRequest(request);
}
+
+ @Override
+ public void removePickupRequest(Id requestId) {
+ delegate.removePickupRequest(requestId);
+ }
+
+ @Override
+ public void removeDropoffRequest(Id requestId) {
+ delegate.removeDropoffRequest(requestId);
+ }
}
diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/schedule/ShiftBreakTaskImpl.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/schedule/ShiftBreakTaskImpl.java
index e295a531ef5..b718954da1b 100644
--- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/schedule/ShiftBreakTaskImpl.java
+++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/schedule/ShiftBreakTaskImpl.java
@@ -63,4 +63,14 @@ public void addDropoffRequest(AcceptedDrtRequest request) {
public void addPickupRequest(AcceptedDrtRequest request) {
delegate.addPickupRequest(request);
}
+
+ @Override
+ public void removePickupRequest(Id requestId) {
+ delegate.removePickupRequest(requestId);
+ }
+
+ @Override
+ public void removeDropoffRequest(Id requestId) {
+ delegate.removeDropoffRequest(requestId);
+ }
}
diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/schedule/ShiftChangeoverTaskImpl.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/schedule/ShiftChangeoverTaskImpl.java
index 76818598dd6..0af9ed0b7bf 100644
--- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/schedule/ShiftChangeoverTaskImpl.java
+++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/schedule/ShiftChangeoverTaskImpl.java
@@ -1,5 +1,9 @@
package org.matsim.contrib.drt.extension.operations.shifts.schedule;
+import static org.matsim.contrib.drt.schedule.DrtTaskBaseType.STOP;
+
+import java.util.Map;
+
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.contrib.drt.extension.operations.operationFacilities.OperationFacility;
@@ -11,10 +15,6 @@
import org.matsim.contrib.dvrp.optimizer.Request;
import org.matsim.contrib.dvrp.schedule.DefaultStayTask;
-import java.util.Map;
-
-import static org.matsim.contrib.drt.schedule.DrtTaskBaseType.STOP;
-
/**
* A task representing stopping and waiting for a new shift.
* @author nkuehnel / MOIA
@@ -64,5 +64,15 @@ public void addDropoffRequest(AcceptedDrtRequest request) {
public void addPickupRequest(AcceptedDrtRequest request) {
delegate.addPickupRequest(request);
}
+
+ @Override
+ public void removePickupRequest(Id requestId) {
+ delegate.removePickupRequest(requestId);
+ }
+
+ @Override
+ public void removeDropoffRequest(Id requestId) {
+ delegate.removeDropoffRequest(requestId);
+ }
}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtAnalysisControlerListener.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtAnalysisControlerListener.java
index 62f9a26f467..cfb6d20ba50 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtAnalysisControlerListener.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtAnalysisControlerListener.java
@@ -176,10 +176,11 @@ public void notifyIterationEnds(IterationEndsEvent event) {
.collect(toList());
collection2Text(drtEventSequenceCollector.getRejectedRequestSequences().values(), filename(event, "drt_rejections", ".csv"),
- String.join(delimiter, "time", "personId", "requestId", "fromLinkId", "toLinkId", "fromX", "fromY", "toX", "toY"), seq -> {
+ String.join(delimiter, "time", "personId", "requestId", "fromLinkId", "toLinkId", "fromX", "fromY", "toX", "toY", "cause"), seq -> {
DrtRequestSubmittedEvent submission = seq.getSubmitted();
Coord fromCoord = network.getLinks().get(submission.getFromLinkId()).getToNode().getCoord();
Coord toCoord = network.getLinks().get(submission.getToLinkId()).getToNode().getCoord();
+ PassengerRequestRejectedEvent rejection = seq.getRejected().get();
return String.join(delimiter, submission.getTime() + "",//
submission.getPersonId() + "",//
submission.getRequestId() + "",//
@@ -188,7 +189,8 @@ public void notifyIterationEnds(IterationEndsEvent event) {
fromCoord.getX() + "",//
fromCoord.getY() + "",//
toCoord.getX() + "",//
- toCoord.getY() + "");
+ toCoord.getY() + "",//
+ rejection.getCause());
});
double rejectionRate = (double)drtEventSequenceCollector.getRejectedRequestSequences().size()
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingActionCreator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingActionCreator.java
index d13e62630ad..aa29bc7253f 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingActionCreator.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingActionCreator.java
@@ -2,6 +2,7 @@
import static org.matsim.contrib.drt.schedule.DrtTaskBaseType.getBaseTypeOrElseThrow;
+import org.matsim.contrib.drt.prebooking.abandon.AbandonVoter;
import org.matsim.contrib.drt.schedule.DrtStopTask;
import org.matsim.contrib.drt.schedule.DrtTaskBaseType;
import org.matsim.contrib.drt.stops.PassengerStopDurationProvider;
@@ -27,12 +28,17 @@ public class PrebookingActionCreator implements VrpAgentLogic.DynActionCreator {
private final VrpAgentLogic.DynActionCreator delegate;
private final PassengerHandler passengerHandler;
private final PassengerStopDurationProvider stopDurationProvider;
+ private final PrebookingManager prebookingManager;
+ private final AbandonVoter abandonVoter;
public PrebookingActionCreator(PassengerHandler passengerHandler, VrpAgentLogic.DynActionCreator delegate,
- PassengerStopDurationProvider stopDurationProvider) {
+ PassengerStopDurationProvider stopDurationProvider, PrebookingManager prebookingManager,
+ AbandonVoter abandonVoter) {
this.delegate = delegate;
this.passengerHandler = passengerHandler;
this.stopDurationProvider = stopDurationProvider;
+ this.prebookingManager = prebookingManager;
+ this.abandonVoter = abandonVoter;
}
@Override
@@ -42,7 +48,8 @@ public DynAction createAction(DynAgent dynAgent, DvrpVehicle vehicle, double now
if (getBaseTypeOrElseThrow(task).equals(DrtTaskBaseType.STOP)) {
DrtStopTask stopTask = (DrtStopTask) task;
return new PrebookingStopActivity(passengerHandler, dynAgent, stopTask, stopTask.getDropoffRequests(),
- stopTask.getPickupRequests(), DrtActionCreator.DRT_STOP_NAME, () -> stopTask.getEndTime(), stopDurationProvider, vehicle);
+ stopTask.getPickupRequests(), DrtActionCreator.DRT_STOP_NAME, () -> stopTask.getEndTime(),
+ stopDurationProvider, vehicle, prebookingManager, abandonVoter);
}
return delegate.createAction(dynAgent, vehicle, now);
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingManager.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingManager.java
index 3c25ff15430..f7cc5cedccb 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingManager.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingManager.java
@@ -3,32 +3,48 @@
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.IdMap;
+import org.matsim.api.core.v01.IdSet;
+import org.matsim.api.core.v01.events.PersonStuckEvent;
+import org.matsim.api.core.v01.events.handler.PersonStuckEventHandler;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
import org.matsim.api.core.v01.population.Leg;
+import org.matsim.api.core.v01.population.Person;
import org.matsim.api.core.v01.population.Plan;
+import org.matsim.contrib.drt.passenger.AcceptedDrtRequest;
+import org.matsim.contrib.drt.prebooking.unscheduler.RequestUnscheduler;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
import org.matsim.contrib.dvrp.optimizer.Request;
import org.matsim.contrib.dvrp.optimizer.VrpOptimizer;
import org.matsim.contrib.dvrp.passenger.AdvanceRequestProvider;
import org.matsim.contrib.dvrp.passenger.PassengerRequest;
import org.matsim.contrib.dvrp.passenger.PassengerRequestCreator;
import org.matsim.contrib.dvrp.passenger.PassengerRequestRejectedEvent;
+import org.matsim.contrib.dvrp.passenger.PassengerRequestRejectedEventHandler;
+import org.matsim.contrib.dvrp.passenger.PassengerRequestScheduledEvent;
+import org.matsim.contrib.dvrp.passenger.PassengerRequestScheduledEventHandler;
import org.matsim.contrib.dvrp.passenger.PassengerRequestValidator;
import org.matsim.core.api.experimental.events.EventsManager;
import org.matsim.core.mobsim.framework.MobsimAgent;
import org.matsim.core.mobsim.framework.MobsimAgent.State;
+import org.matsim.core.mobsim.framework.MobsimTimer;
+import org.matsim.core.mobsim.framework.events.MobsimAfterSimStepEvent;
+import org.matsim.core.mobsim.framework.listeners.MobsimAfterSimStepListener;
import org.matsim.core.mobsim.qsim.InternalInterface;
import org.matsim.core.mobsim.qsim.agents.WithinDayAgentUtils;
import org.matsim.core.mobsim.qsim.interfaces.MobsimEngine;
import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
/**
* This class manages prebooked requests. One instance of PrebookingManager
@@ -47,23 +63,30 @@
*
* @author Sebastian Hörl (sebhoerl), IRT SystemX
*/
-public class PrebookingManager implements MobsimEngine, AdvanceRequestProvider {
+public class PrebookingManager implements MobsimEngine, MobsimAfterSimStepListener, AdvanceRequestProvider,
+ PassengerRequestScheduledEventHandler, PassengerRequestRejectedEventHandler, PersonStuckEventHandler {
private final String mode;
private final Network network;
private final EventsManager eventsManager;
private final VrpOptimizer optimizer;
+ private final RequestUnscheduler unscheduler;
+
+ private final MobsimTimer mobsimTimer;
public PrebookingManager(String mode, Network network, PassengerRequestCreator requestCreator,
- VrpOptimizer optimizer, PassengerRequestValidator requestValidator, EventsManager eventsManager) {
+ VrpOptimizer optimizer, MobsimTimer mobsimTimer, PassengerRequestValidator requestValidator,
+ EventsManager eventsManager, RequestUnscheduler unscheduler) {
this.network = network;
this.mode = mode;
this.requestCreator = requestCreator;
this.optimizer = optimizer;
this.requestAttribute = PREBOOKED_REQUEST_PREFIX + ":" + mode;
this.requestValidator = requestValidator;
+ this.mobsimTimer = mobsimTimer;
this.eventsManager = eventsManager;
+ this.unscheduler = unscheduler;
}
// Functionality for ID management
@@ -90,60 +113,100 @@ public Id getRequestId(Leg leg) {
return Id.create(rawRequestId, Request.class);
}
+ // Event handling: We track events in parallel and process them later in
+ // notifyMobsimAfterSimStep
+
+ private final ConcurrentLinkedQueue scheduledEvents = new ConcurrentLinkedQueue<>();
+ private final ConcurrentLinkedQueue> rejectedEventIds = new ConcurrentLinkedQueue<>();
+ private final ConcurrentLinkedQueue> stuckPersonsIds = new ConcurrentLinkedQueue<>();
+
+ @Override
+ public void handleEvent(PassengerRequestScheduledEvent event) {
+ scheduledEvents.add(event);
+ }
+
+ @Override
+ public void handleEvent(PassengerRequestRejectedEvent event) {
+ rejectedEventIds.add(event.getRequestId());
+ }
+
+ @Override
+ public void handleEvent(PersonStuckEvent event) {
+ stuckPersonsIds.add(event.getPersonId());
+ }
+
+ // Event handling: We don't want to process events in notifyMobsimAfterSimStep,
+ // so we do it at the next time step
+ private record RejectionItem(Id requestId, Id personId, String cause) {
+ }
+
+ private final ConcurrentLinkedQueue rejections = new ConcurrentLinkedQueue<>();
+
+ private void processRejection(PassengerRequest request, String cause) {
+ rejections.add(new RejectionItem(request.getId(), request.getPassengerId(), cause));
+ }
+
+ private void flushRejections(double now) {
+ for (RejectionItem item : rejections) {
+ eventsManager.processEvent(
+ new PassengerRequestRejectedEvent(now, mode, item.requestId, item.personId, item.cause));
+ }
+
+ rejections.clear();
+ }
+
// Booking functionality
private final PassengerRequestCreator requestCreator;
private final PassengerRequestValidator requestValidator;
- private final List queue = new LinkedList<>();
+
+ // collects new bookings that need to be submitted
+ private final ConcurrentLinkedQueue bookingQueue = new ConcurrentLinkedQueue<>();
public void prebook(MobsimAgent person, Leg leg, double earliestDepartureTime) {
Preconditions.checkArgument(leg.getMode().equals(mode), "Invalid mode for this prebooking manager");
+ Preconditions.checkState(!person.getState().equals(State.ABORT), "Cannot prebook aborted agent");
- synchronized (queue) {
- queue.add(new QueueItem(person, leg, earliestDepartureTime));
- }
- }
-
- private void processQueue(double now) {
- synchronized (queue) {
- for (QueueItem item : queue) {
- Preconditions.checkState(!item.person.getState().equals(State.ABORT), "Cannot prebook aborted agent");
+ Id requestId = createRequestId();
+ double now = mobsimTimer.getTimeOfDay();
- Id requestId = createRequestId();
+ eventsManager.processEvent(new PassengerRequestBookedEvent(now, mode, requestId, person.getId()));
- eventsManager.processEvent(new PassengerRequestBookedEvent(now, mode, requestId, item.person.getId()));
+ PassengerRequest request = requestCreator.createRequest(requestId, person.getId(), leg.getRoute(),
+ getLink(leg.getRoute().getStartLinkId()), getLink(leg.getRoute().getEndLinkId()), earliestDepartureTime,
+ now);
- PassengerRequest request = requestCreator.createRequest(requestId, item.person.getId(),
- item.leg.getRoute(), getLink(item.leg.getRoute().getStartLinkId()),
- getLink(item.leg.getRoute().getEndLinkId()), item.earliestDepartureTime, now);
+ Set violations = requestValidator.validateRequest(request);
- Set violations = requestValidator.validateRequest(request);
+ Plan plan = WithinDayAgentUtils.getModifiablePlan(person);
+ int currentLegIndex = WithinDayAgentUtils.getCurrentPlanElementIndex(person);
+ int prebookingLegIndex = plan.getPlanElements().indexOf(leg);
- Plan plan = WithinDayAgentUtils.getModifiablePlan(item.person);
- int currentLegIndex = WithinDayAgentUtils.getCurrentPlanElementIndex(item.person);
- int prebookingLegIndex = plan.getPlanElements().indexOf(item.leg);
+ if (prebookingLegIndex <= currentLegIndex) {
+ violations = new HashSet<>(violations);
+ violations.add("past leg"); // the leg for which the booking was made has already happened
+ }
- if (prebookingLegIndex <= currentLegIndex) {
- violations = new HashSet<>(violations);
- violations.add("past leg"); // the leg for which the booking was made has already happened
- }
+ if (!violations.isEmpty()) {
+ String cause = String.join(", ", violations);
+ processRejection(request, cause);
+ } else {
+ leg.getAttributes().putAttribute(requestAttribute, request.getId().toString());
+ bookingQueue.add(request);
+ }
+ }
- if (!violations.isEmpty()) {
- String cause = String.join(", ", violations);
- eventsManager.processEvent(new PassengerRequestRejectedEvent(now, mode, request.getId(),
- request.getPassengerId(), cause));
- } else {
- synchronized (optimizer) {
- optimizer.requestSubmitted(request);
- }
+ private void processBookingQueue(double now) {
+ for (PassengerRequest request : bookingQueue) {
- item.leg.getAttributes().putAttribute(requestAttribute, request.getId().toString());
- requests.put(requestId, new RequestItem(request));
- }
+ synchronized (optimizer) { // needed?
+ optimizer.requestSubmitted(request);
}
- queue.clear();
+ requests.put(request.getId(), new RequestItem(request));
}
+
+ bookingQueue.clear();
}
private Link getLink(Id linkId) {
@@ -152,9 +215,6 @@ private Link getLink(Id linkId) {
linkId, mode);
}
- private record QueueItem(MobsimAgent person, Leg leg, double earliestDepartureTime) {
- }
-
// Interface with PassengerEngine
@Override
@@ -168,7 +228,7 @@ public PassengerRequest retrieveRequest(MobsimAgent agent, Leg leg) {
return null;
}
- RequestItem item = requests.remove(requestId);
+ RequestItem item = requests.get(requestId);
if (item == null) {
return null;
@@ -182,28 +242,213 @@ public PassengerRequest retrieveRequest(MobsimAgent agent, Leg leg) {
private IdMap requests = new IdMap<>(Request.class);
private class RequestItem {
- // this class looks minimal for now, but will be extended with canceling
- // functionality
final PassengerRequest request;
+ Id vehicleId = null;
+ boolean onboard = false;
+
RequestItem(PassengerRequest request) {
this.request = request;
}
}
+ void notifyPickup(double now, AcceptedDrtRequest request) {
+ RequestItem item = requests.get(request.getId());
+
+ if (item != null) {
+ // may be null, we treat all (also non-prebooked) requests here
+ item.onboard = true;
+ }
+ }
+
+ void notifyDropoff(Id requestId) {
+ requests.remove(requestId);
+ }
+
+ private IdSet unscheduleUponVehicleAssignment = new IdSet<>(Request.class);
+
+ private void processScheduledRequests(double now) {
+ for (PassengerRequestScheduledEvent event : scheduledEvents) {
+ RequestItem item = requests.get(event.getRequestId());
+
+ if (item != null) {
+ item.vehicleId = event.getVehicleId();
+ }
+
+ if (unscheduleUponVehicleAssignment.contains(event.getRequestId())) {
+ // this is the case if a request has been rejected / canceled after submission
+ // but before scheduling
+ unscheduler.unscheduleRequest(now, event.getVehicleId(), event.getRequestId());
+ unscheduleUponVehicleAssignment.remove(event.getRequestId());
+ }
+ }
+
+ scheduledEvents.clear();
+ }
+
+ // Functionality for canceling requests
+
+ private static final String CANCEL_REASON = "canceled";
+ private final List cancelQueue = new LinkedList<>();
+
+ private void processCanceledRequests(double now) {
+ for (CancelItem cancelItem : cancelQueue) {
+ Id requestId = cancelItem.requestId;
+ RequestItem item = requests.remove(requestId);
+
+ if (item != null) { // might be null if abandoned before canceling
+ Verify.verify(!item.onboard, "cannot cancel onboard request");
+
+ // unschedule if requests is scheduled already
+ if (item.vehicleId != null) {
+ unscheduler.unscheduleRequest(now, item.vehicleId, requestId);
+ } else {
+ unscheduleUponVehicleAssignment.add(requestId);
+ }
+
+ String reason = CANCEL_REASON;
+
+ if (cancelItem.reason != null) {
+ reason = CANCEL_REASON + ":" + cancelItem.reason;
+ }
+
+ processRejection(item.request, reason);
+ }
+ }
+
+ cancelQueue.clear();
+ }
+
+ public void cancel(Leg leg) {
+ cancel(leg, null);
+ }
+
+ public void cancel(Leg leg, String reason) {
+ Id requestId = getRequestId(leg);
+
+ if (requestId != null) {
+ cancel(requestId, reason);
+ }
+ }
+
+ public void cancel(Id requestId, String reason) {
+ cancelQueue.add(new CancelItem(requestId, reason));
+ }
+
+ public void cancel(Id requestId) {
+ cancel(requestId, null);
+ }
+
+ private record CancelItem(Id requestId, String reason) {
+ }
+
+ // Functionality for abandoning requests
+
+ private static final String ABANDONED_REASON = "abandoned by vehicle";
+ private final ConcurrentLinkedQueue> abandonQueue = new ConcurrentLinkedQueue<>();
+
+ void abandon(Id requestId) {
+ abandonQueue.add(requestId);
+ }
+
+ private void processAbandonedRequests(double now) {
+ for (Id requestId : abandonQueue) {
+ RequestItem item = Objects.requireNonNull(requests.remove(requestId));
+ Verify.verify(!item.onboard, "cannot abandon request that is already onboard");
+
+ // remove request from scheduled if already scheduled
+ if (item.vehicleId != null) {
+ unscheduler.unscheduleRequest(now, item.vehicleId, item.request.getId());
+ } else {
+ unscheduleUponVehicleAssignment.add(item.request.getId());
+ }
+
+ processRejection(item.request, ABANDONED_REASON);
+ }
+
+ abandonQueue.clear();
+ }
+
+ // Rejections
+
+ private void processRejections(double now) {
+ for (Id requestId : rejectedEventIds) {
+ RequestItem item = requests.remove(requestId);
+
+ if (item != null) {
+ // should this fail gracefully?
+ Verify.verify(!item.onboard, "cannot reject onboard request");
+
+ // unschedule if already scheduled
+ if (item.vehicleId != null) {
+ unscheduler.unscheduleRequest(now, item.vehicleId, requestId);
+ } else {
+ unscheduleUponVehicleAssignment.add(requestId);
+ }
+ }
+ }
+
+ rejectedEventIds.clear();
+ }
+
+ // Stuck
+
+ private void processStuckAgents(double now) {
+ bookingQueue.removeIf(request -> stuckPersonsIds.contains(request.getPassengerId()));
+
+ for (RequestItem item : requests.values()) {
+ if (stuckPersonsIds.contains(item.request.getPassengerId())) {
+ cancel(item.request.getId());
+ }
+ }
+
+ stuckPersonsIds.clear();
+ }
+
// Engine code
+ @Override
+ public void onPrepareSim() {
+ eventsManager.addHandler(this);
+ }
+
@Override
public void doSimStep(double now) {
- processQueue(now);
+ // avoid method as it runs in parallel with events, only process rejections
+ flushRejections(now);
}
@Override
- public void onPrepareSim() {
+ public void notifyMobsimAfterSimStep(@SuppressWarnings("rawtypes") MobsimAfterSimStepEvent e) {
+ // here we are back in the main thread and all events
+ // have been processed
+
+ double now = mobsimTimer.getTimeOfDay();
+
+ // first process scheduled events (this happened, we cannot change it)
+ processScheduledRequests(now);
+
+ // process rejected requests, potential problem if a person has entered the
+ // vehicle in just this simstep, but also a rejection has been sent
+ processRejections(now);
+
+ // process stuck agents, they are added to the cancel queue
+ processStuckAgents(now);
+
+ // process abandoned requests (by vehicle), here we are sure that the person
+ // cannot have entered in this iteration
+ processAbandonedRequests(now);
+
+ // process cancel requests, same situation as for rejections
+ processCanceledRequests(now);
+
+ // submit requests
+ processBookingQueue(now);
}
@Override
public void afterSim() {
+ eventsManager.removeHandler(this);
}
@Override
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingModeQSimModule.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingModeQSimModule.java
index 4c423d64ce4..b3f6faa4bc7 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingModeQSimModule.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingModeQSimModule.java
@@ -2,24 +2,39 @@
import org.matsim.api.core.v01.network.Network;
import org.matsim.api.core.v01.population.Population;
+import org.matsim.contrib.drt.optimizer.VehicleEntry;
+import org.matsim.contrib.drt.prebooking.abandon.AbandonVoter;
+import org.matsim.contrib.drt.prebooking.abandon.MaximumDelayAbandonVoter;
import org.matsim.contrib.drt.prebooking.logic.helpers.PopulationIterator.PopulationIteratorFactory;
import org.matsim.contrib.drt.prebooking.logic.helpers.PrebookingQueue;
+import org.matsim.contrib.drt.prebooking.unscheduler.ComplexRequestUnscheduler;
+import org.matsim.contrib.drt.prebooking.unscheduler.RequestUnscheduler;
+import org.matsim.contrib.drt.prebooking.unscheduler.SimpleRequestUnscheduler;
+import org.matsim.contrib.drt.schedule.DrtTaskFactory;
import org.matsim.contrib.drt.stops.PassengerStopDurationProvider;
import org.matsim.contrib.drt.vrpagent.DrtActionCreator;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicleLookup;
import org.matsim.contrib.dvrp.optimizer.VrpOptimizer;
import org.matsim.contrib.dvrp.passenger.PassengerEngine;
import org.matsim.contrib.dvrp.passenger.PassengerHandler;
import org.matsim.contrib.dvrp.passenger.PassengerRequestCreator;
import org.matsim.contrib.dvrp.passenger.PassengerRequestValidator;
import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule;
+import org.matsim.contrib.dvrp.schedule.ScheduleTimingUpdater;
import org.matsim.core.api.experimental.events.EventsManager;
+import org.matsim.core.mobsim.framework.MobsimTimer;
import org.matsim.core.mobsim.qsim.QSim;
+import org.matsim.core.router.util.LeastCostPathCalculator;
+import org.matsim.core.router.util.TravelTime;
import com.google.inject.Singleton;
public class PrebookingModeQSimModule extends AbstractDvrpModeQSimModule {
- public PrebookingModeQSimModule(String mode) {
+ private final PrebookingParams prebookingParams;
+
+ public PrebookingModeQSimModule(String mode, PrebookingParams prebookingParams) {
super(mode);
+ this.prebookingParams = prebookingParams;
}
@Override
@@ -28,8 +43,11 @@ protected void configureQSim() {
PassengerHandler passengerHandler = (PassengerEngine) getter.getModal(PassengerHandler.class);
DrtActionCreator delegate = getter.getModal(DrtActionCreator.class);
PassengerStopDurationProvider stopDurationProvider = getter.getModal(PassengerStopDurationProvider.class);
+ PrebookingManager prebookingManager = getter.getModal(PrebookingManager.class);
+ AbandonVoter abandonVoter = getter.getModal(AbandonVoter.class);
- return new PrebookingActionCreator(passengerHandler, delegate, stopDurationProvider);
+ return new PrebookingActionCreator(passengerHandler, delegate, stopDurationProvider, prebookingManager,
+ abandonVoter);
})).in(Singleton.class);
bindModal(PrebookingManager.class).toProvider(modalProvider(getter -> {
@@ -38,9 +56,11 @@ protected void configureQSim() {
VrpOptimizer optimizer = getter.getModal(VrpOptimizer.class);
PassengerRequestValidator requestValidator = getter.getModal(PassengerRequestValidator.class);
EventsManager eventsManager = getter.get(EventsManager.class);
+ RequestUnscheduler requestUnscheduler = getter.getModal(RequestUnscheduler.class);
+ MobsimTimer mobsimTimer = getter.get(MobsimTimer.class);
- return new PrebookingManager(getMode(), network, requestCreator, optimizer, requestValidator,
- eventsManager);
+ return new PrebookingManager(getMode(), network, requestCreator, optimizer, mobsimTimer, requestValidator,
+ eventsManager, requestUnscheduler);
})).in(Singleton.class);
addModalQSimComponentBinding().to(modalKey(PrebookingManager.class));
@@ -52,5 +72,39 @@ protected void configureQSim() {
bindModal(PopulationIteratorFactory.class).toProvider(modalProvider(getter -> {
return new PopulationIteratorFactory(getter.get(Population.class), getter.get(QSim.class));
}));
+
+ bindModal(MaximumDelayAbandonVoter.class).toProvider(modalProvider(getter -> {
+ double maximumDelay = prebookingParams.maximumPassengerDelay;
+ return new MaximumDelayAbandonVoter(maximumDelay);
+ })).in(Singleton.class);
+ bindModal(AbandonVoter.class).to(modalKey(MaximumDelayAbandonVoter.class));
+
+ bindModal(SimpleRequestUnscheduler.class).toProvider(modalProvider(getter -> {
+ DvrpVehicleLookup vehicleLookup = getter.get(DvrpVehicleLookup.class);
+ return new SimpleRequestUnscheduler(vehicleLookup);
+ })).in(Singleton.class);
+
+ bindModal(ComplexRequestUnscheduler.class).toProvider(modalProvider(getter -> {
+ DvrpVehicleLookup vehicleLookup = getter.get(DvrpVehicleLookup.class);
+ VehicleEntry.EntryFactory entryFactory = getter.getModal(VehicleEntry.EntryFactory.class);
+ DrtTaskFactory taskFactory = getter.getModal(DrtTaskFactory.class);
+ LeastCostPathCalculator router = getter.getModal(LeastCostPathCalculator.class);
+ TravelTime travelTime = getter.getModal(TravelTime.class);
+ ScheduleTimingUpdater timingUpdater = getter.getModal(ScheduleTimingUpdater.class);
+
+ return new ComplexRequestUnscheduler(vehicleLookup, entryFactory, taskFactory, router, travelTime,
+ timingUpdater, prebookingParams.scheduleWaitBeforeDrive);
+ })).in(Singleton.class);
+
+ switch (prebookingParams.unschedulingMode) {
+ case StopBased:
+ bindModal(RequestUnscheduler.class).to(modalKey(SimpleRequestUnscheduler.class));
+ break;
+ case Routing:
+ bindModal(RequestUnscheduler.class).to(modalKey(ComplexRequestUnscheduler.class));
+ break;
+ default:
+ throw new IllegalStateException("No binding for selected RequestUnscheduler");
+ }
}
}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingParams.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingParams.java
index 86376f51a67..fdf678bb272 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingParams.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingParams.java
@@ -2,6 +2,9 @@
import org.matsim.core.config.ReflectiveConfigGroup;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Positive;
+
public class PrebookingParams extends ReflectiveConfigGroup {
public static final String SET_NAME = "prebooking";
@@ -11,10 +14,28 @@ public PrebookingParams() {
@Parameter
@Comment("Defines whether vehicles drive immediately to the next"
- + "(prebooked) future task and wait for the planned stop to begin, or wait at the current"
- + "position and depart to arrive on time at the following stop. The latter behavior (not"
- + "the default) may lead to larger ucnertainty in highly congested scenarios.")
+ + " (prebooked) future task and wait for the planned stop to begin, or wait at the current"
+ + " position and depart to arrive on time at the following stop. The latter behavior (not"
+ + " the default) may lead to larger ucnertainty in highly congested scenarios.")
public boolean scheduleWaitBeforeDrive = false; // in the future, this could also become a double value indicating
// how many minutes before the next stop the vehicle should plan to
// be there
+
+ @Parameter
+ @Comment("Request gets rejected if a vehicle waits longer than the indicated duration at the stop")
+ @NotNull
+ @Positive
+ public double maximumPassengerDelay = Double.POSITIVE_INFINITY;
+
+ public enum UnschedulingMode {
+ StopBased, Routing
+ }
+
+ @Parameter
+ @Comment("When unscheduling requests because they have been canceled,"
+ + " we either simply remove the requests from the planned stops"
+ + " along the vehicle's schedule or we adaptively reconfigure and reroute the vehicle's schedule.")
+ @NotNull
+ public UnschedulingMode unschedulingMode = UnschedulingMode.StopBased;
+
}
\ No newline at end of file
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingStopActivity.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingStopActivity.java
index e83d03cf679..849ca5bde14 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingStopActivity.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingStopActivity.java
@@ -9,6 +9,7 @@
import org.matsim.api.core.v01.IdMap;
import org.matsim.api.core.v01.population.Person;
import org.matsim.contrib.drt.passenger.AcceptedDrtRequest;
+import org.matsim.contrib.drt.prebooking.abandon.AbandonVoter;
import org.matsim.contrib.drt.stops.PassengerStopDurationProvider;
import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
import org.matsim.contrib.dvrp.optimizer.Request;
@@ -38,16 +39,19 @@ public class PrebookingStopActivity extends FirstLastSimStepDynActivity implemen
private final IdMap leaveTimes = new IdMap<>(Request.class);
private final Set> enteredRequests = new HashSet<>();
+ private final PrebookingManager prebookingManager;
private final PassengerHandler passengerHandler;
private final PassengerStopDurationProvider stopDurationProvider;
+ private final AbandonVoter abandonVoter;
+
private final Supplier endTime;
public PrebookingStopActivity(PassengerHandler passengerHandler, DynAgent driver, StayTask task,
Map, ? extends AcceptedDrtRequest> dropoffRequests,
Map, ? extends AcceptedDrtRequest> pickupRequests, String activityType,
- Supplier endTime, PassengerStopDurationProvider stopDurationProvider,
- DvrpVehicle vehicle) {
+ Supplier endTime, PassengerStopDurationProvider stopDurationProvider, DvrpVehicle vehicle,
+ PrebookingManager prebookingManager, AbandonVoter abandonVoter) {
super(activityType);
this.passengerHandler = passengerHandler;
this.driver = driver;
@@ -55,6 +59,8 @@ public PrebookingStopActivity(PassengerHandler passengerHandler, DynAgent driver
this.pickupRequests = pickupRequests;
this.stopDurationProvider = stopDurationProvider;
this.vehicle = vehicle;
+ this.prebookingManager = prebookingManager;
+ this.abandonVoter = abandonVoter;
this.endTime = endTime;
}
@@ -86,13 +92,18 @@ private void processDropoffRequests(double now) {
if (entry.getValue() <= now) { // Request should leave now
passengerHandler.dropOffPassenger(driver, entry.getKey(), now);
+ prebookingManager.notifyDropoff(entry.getKey());
iterator.remove();
}
}
}
private boolean updatePickupRequests(double now) {
- for (var request : pickupRequests.values()) {
+ var pickupIterator = pickupRequests.values().iterator();
+
+ while (pickupIterator.hasNext()) {
+ var request = pickupIterator.next();
+
if (!enteredRequests.contains(request.getId()) && !enterTimes.containsKey(request.getId())) {
// this is a new request that has been added after the activity has been created
// or that had not arrived yet
@@ -100,27 +111,32 @@ private boolean updatePickupRequests(double now) {
if (passengerHandler.notifyWaitForPassenger(this, this.driver, request.getId())) {
// agent starts to enter
queuePickup(request, now);
+ } else if (now > request.getEarliestStartTime()) {
+ if (abandonVoter.abandonRequest(now, vehicle, request)) {
+ prebookingManager.abandon(request.getId());
+ }
}
}
}
- var iterator = enterTimes.entrySet().iterator();
+ var enterIterator = enterTimes.entrySet().iterator();
- while (iterator.hasNext()) {
- var entry = iterator.next();
+ while (enterIterator.hasNext()) {
+ var entry = enterIterator.next();
if (entry.getValue() <= now) {
// let agent enter now
Verify.verify(passengerHandler.tryPickUpPassenger(this, driver, entry.getKey(), now));
enteredRequests.add(entry.getKey());
- iterator.remove();
+ enterIterator.remove();
}
}
- return enterTimes.size() == 0 && enteredRequests.size() == pickupRequests.size();
+ return enterTimes.size() == 0 && pickupRequests.size() == enteredRequests.size();
}
private void queuePickup(AcceptedDrtRequest request, double now) {
+ prebookingManager.notifyPickup(now, request);
double enterTime = now + stopDurationProvider.calcPickupDuration(vehicle, request.getRequest());
enterTimes.put(request.getId(), enterTime);
}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/abandon/AbandonVoter.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/abandon/AbandonVoter.java
new file mode 100644
index 00000000000..20ba9a2baca
--- /dev/null
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/abandon/AbandonVoter.java
@@ -0,0 +1,8 @@
+package org.matsim.contrib.drt.prebooking.abandon;
+
+import org.matsim.contrib.drt.passenger.AcceptedDrtRequest;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
+
+public interface AbandonVoter {
+ boolean abandonRequest(double time, DvrpVehicle vehicle, AcceptedDrtRequest request);
+}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/abandon/MaximumDelayAbandonVoter.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/abandon/MaximumDelayAbandonVoter.java
new file mode 100644
index 00000000000..f23c6623e22
--- /dev/null
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/abandon/MaximumDelayAbandonVoter.java
@@ -0,0 +1,19 @@
+package org.matsim.contrib.drt.prebooking.abandon;
+
+import org.matsim.contrib.drt.passenger.AcceptedDrtRequest;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
+
+public class MaximumDelayAbandonVoter implements AbandonVoter {
+ private final double maximumDelay;
+
+ public MaximumDelayAbandonVoter(double maximumDelay) {
+ this.maximumDelay = maximumDelay;
+ }
+
+ @Override
+ public boolean abandonRequest(double time, DvrpVehicle vehicle, AcceptedDrtRequest request) {
+ double requestedDepartureTime = request.getEarliestStartTime();
+ double delay = time - requestedDepartureTime;
+ return delay > maximumDelay;
+ }
+}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/analysis/PrebookingAnalysisHandler.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/analysis/PrebookingAnalysisHandler.java
index 59c6f002974..e618476ceb0 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/analysis/PrebookingAnalysisHandler.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/analysis/PrebookingAnalysisHandler.java
@@ -54,7 +54,7 @@ public void handleEvent(PassengerRequestRejectedEvent event) {
Sequence sequence = sequences.get(event.getRequestId());
- if (sequence != null) {
+ if (sequence != null && sequence.rejected == null) { // only use first
sequence.rejected = event;
}
}
@@ -93,14 +93,15 @@ public List getRecords() {
sequence.submitted != null ? sequence.submitted.getTime() : null,
sequence.scheduled != null ? sequence.scheduled.getTime() : null,
sequence.rejected != null ? sequence.rejected.getTime() : null,
- sequence.waiting != null ? sequence.waiting.getTime() : null));
+ sequence.waiting != null ? sequence.waiting.getTime() : null,
+ sequence.rejected != null ? sequence.rejected.getCause() : null));
}
return records;
}
public record RequestRecord(Id requestId, Id personId, Double submissionTime, Double scheduledTime,
- Double rejectedTime, Double enteringTime) {
+ Double rejectedTime, Double departureTime, String rejectedReason) {
}
private class Sequence {
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/analysis/PrebookingAnalysisWriter.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/analysis/PrebookingAnalysisWriter.java
index f8fe4eb612f..a641eb6e281 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/analysis/PrebookingAnalysisWriter.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/analysis/PrebookingAnalysisWriter.java
@@ -23,7 +23,8 @@ public void write(List records) {
"submission_time", //
"scheduled_time", //
"rejected_time", //
- "entering_time" //
+ "departure_time", //
+ "rejected_reason" //
}) + "\n");
for (var record : records) {
@@ -33,7 +34,8 @@ public void write(List records) {
record.submissionTime() == null ? "" : String.valueOf(record.submissionTime()), //
record.scheduledTime() == null ? "" : String.valueOf(record.scheduledTime()), //
record.rejectedTime() == null ? "" : String.valueOf(record.rejectedTime()), //
- record.enteringTime() == null ? "" : String.valueOf(record.enteringTime()) //
+ record.departureTime() == null ? "" : String.valueOf(record.departureTime()), //
+ record.rejectedReason() //
}) + "\n");
}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/ComplexRequestUnscheduler.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/ComplexRequestUnscheduler.java
new file mode 100644
index 00000000000..4fb0cae5339
--- /dev/null
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/ComplexRequestUnscheduler.java
@@ -0,0 +1,319 @@
+package org.matsim.contrib.drt.prebooking.unscheduler;
+
+import org.matsim.api.core.v01.Id;
+import org.matsim.api.core.v01.network.Link;
+import org.matsim.contrib.drt.optimizer.VehicleEntry;
+import org.matsim.contrib.drt.optimizer.Waypoint;
+import org.matsim.contrib.drt.schedule.DrtDriveTask;
+import org.matsim.contrib.drt.schedule.DrtStayTask;
+import org.matsim.contrib.drt.schedule.DrtStopTask;
+import org.matsim.contrib.drt.schedule.DrtTaskBaseType;
+import org.matsim.contrib.drt.schedule.DrtTaskFactory;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicleLookup;
+import org.matsim.contrib.dvrp.optimizer.Request;
+import org.matsim.contrib.dvrp.path.VrpPathWithTravelData;
+import org.matsim.contrib.dvrp.path.VrpPaths;
+import org.matsim.contrib.dvrp.schedule.DriveTask;
+import org.matsim.contrib.dvrp.schedule.Schedule;
+import org.matsim.contrib.dvrp.schedule.ScheduleTimingUpdater;
+import org.matsim.contrib.dvrp.schedule.Schedules;
+import org.matsim.contrib.dvrp.schedule.StayTask;
+import org.matsim.contrib.dvrp.schedule.Task;
+import org.matsim.contrib.dvrp.schedule.Task.TaskStatus;
+import org.matsim.contrib.dvrp.tracker.OnlineDriveTaskTracker;
+import org.matsim.contrib.dvrp.util.LinkTimePair;
+import org.matsim.core.router.util.LeastCostPathCalculator;
+import org.matsim.core.router.util.TravelTime;
+
+import com.google.common.base.Verify;
+
+/**
+ * This RequestUnscheduler searches for a request in a vehicle's schedule and
+ * removes the request from the relevant stop tasks. Furthermore, the stops are
+ * removed if they don't carry any other pickups or dropoffs. Accordingly, the
+ * schedule will also be rerouted.
+ *
+ * @author Sebastian Hörl (sebhoerl), IRT SystemX
+ */
+public class ComplexRequestUnscheduler implements RequestUnscheduler {
+ private final DvrpVehicleLookup vehicleLookup;
+ private final VehicleEntry.EntryFactory vehicleEntryFactory;
+
+ private final DrtTaskFactory taskFactory;
+
+ private final LeastCostPathCalculator router;
+ private final TravelTime travelTime;
+ private final ScheduleTimingUpdater timingUpdater;
+
+ private final boolean scheduleWaitBeforeDrive;
+
+ public ComplexRequestUnscheduler(DvrpVehicleLookup vehicleLookup, VehicleEntry.EntryFactory vehicleEntryFactory,
+ DrtTaskFactory taskFactory, LeastCostPathCalculator router, TravelTime travelTime,
+ ScheduleTimingUpdater timingUpdater, boolean scheduleWaitBeforeDrive) {
+ this.vehicleLookup = vehicleLookup;
+ this.vehicleEntryFactory = vehicleEntryFactory;
+ this.taskFactory = taskFactory;
+ this.travelTime = travelTime;
+ this.router = router;
+ this.timingUpdater = timingUpdater;
+ this.scheduleWaitBeforeDrive = scheduleWaitBeforeDrive;
+ }
+
+ @Override
+ public void unscheduleRequest(double now, Id vehicleId, Id requestId) {
+ DvrpVehicle vehicle = vehicleLookup.lookupVehicle(vehicleId);
+ VehicleEntry vEntry = vehicleEntryFactory.create(vehicle, now);
+
+ Waypoint.Stop pickupStop = null;
+ Waypoint.Stop dropoffStop = null;
+
+ DrtStopTask pickupStopTask = null;
+ DrtStopTask dropoffStopTask = null;
+
+ for (Waypoint.Stop stop : vEntry.stops) {
+ if (stop.task.getPickupRequests().containsKey(requestId)) {
+ Verify.verify(pickupStop == null);
+ Verify.verify(pickupStopTask == null);
+
+ pickupStop = stop;
+ pickupStopTask = stop.task;
+ }
+
+ if (stop.task.getDropoffRequests().containsKey(requestId)) {
+ Verify.verify(dropoffStop == null);
+ Verify.verify(dropoffStopTask == null);
+
+ dropoffStop = stop;
+ dropoffStopTask = stop.task;
+ }
+ }
+
+ Verify.verifyNotNull(pickupStopTask, "Could not find request that I'm supposed to unschedule");
+ Verify.verifyNotNull(dropoffStopTask, "Could not find request that I'm supposed to unschedule");
+ Verify.verifyNotNull(pickupStop);
+ Verify.verifyNotNull(dropoffStop);
+
+ // remove request from stop, this we do in any case
+ pickupStopTask.removePickupRequest(requestId);
+ dropoffStopTask.removeDropoffRequest(requestId);
+
+ // remove pickup
+ // - either we didn't find a stop (because task is running), then we have
+ // removed the pickup and the StopAction will handle the situation
+ // - or we found a stop, then it is not started yet and we can remove it
+
+ boolean removePickup = pickupStopTask.getPickupRequests().size() == 0
+ && pickupStopTask.getDropoffRequests().size() == 0;
+ boolean removeDropoff = dropoffStopTask.getPickupRequests().size() == 0
+ && dropoffStopTask.getDropoffRequests().size() == 0;
+
+ Replacement pickupReplacement = removePickup ? findReplacement(vEntry, pickupStop) : null;
+ Replacement dropoffReplacement = removeDropoff ? findReplacement(vEntry, dropoffStop) : null;
+
+ if (pickupReplacement != null && dropoffReplacement != null) {
+ if (pickupReplacement.endTask.getTaskIdx() >= dropoffReplacement.startTask.getTaskIdx()) {
+ // we have an overlap
+ pickupReplacement = new Replacement(pickupReplacement.startTask, dropoffReplacement.endTask,
+ vehicle.getSchedule());
+ dropoffReplacement = null;
+ }
+ }
+
+ if (pickupReplacement != null) {
+ unschedule(now, vEntry, pickupReplacement);
+ }
+
+ if (dropoffReplacement != null) {
+ unschedule(now, vEntry, dropoffReplacement);
+ }
+ }
+
+ private Replacement findReplacement(VehicleEntry vEntry, Waypoint.Stop stop) {
+ int stopIndex = vEntry.stops.indexOf(stop);
+
+ final Task startTask;
+ if (stopIndex == 0) {
+ startTask = vEntry.vehicle.getSchedule().getCurrentTask();
+ } else {
+ startTask = vEntry.stops.get(stopIndex - 1).task;
+ }
+
+ final Task endTask;
+ if (stopIndex == vEntry.stops.size() - 1) {
+ endTask = Schedules.getLastTask(vEntry.vehicle.getSchedule());
+ } else {
+ endTask = vEntry.stops.get(stopIndex + 1).task;
+ }
+
+ return new Replacement(startTask, endTask, vEntry.vehicle.getSchedule());
+ }
+
+ private void unschedule(double now, VehicleEntry vEntry, Replacement replacement) {
+ Schedule schedule = vEntry.vehicle.getSchedule();
+
+ if (replacement.startTask instanceof DrtStayTask) {
+ replacement.startTask.setEndTime(now);
+ }
+
+ // special case: we remove everything until the end (and replace the stay task)
+ boolean removeUntilEnd = replacement.endTask == Schedules.getLastTask(schedule);
+ if (removeUntilEnd) {
+ Verify.verify(replacement.endTask instanceof DrtStayTask);
+ final Link stayLink;
+
+ if (replacement.startTask instanceof StayTask) {
+ stayLink = ((StayTask) replacement.startTask).getLink();
+ } else {
+ Verify.verify(replacement.startTask.getStatus().equals(TaskStatus.STARTED));
+ DriveTask driveTask = (DriveTask) replacement.startTask;
+
+ OnlineDriveTaskTracker tracker = (OnlineDriveTaskTracker) driveTask.getTaskTracker();
+ tracker.divertPath(VrpPaths.createZeroLengthPathForDiversion(tracker.getDiversionPoint()));
+
+ stayLink = driveTask.getPath().getToLink();
+ }
+
+ double initialEndTime = replacement.endTask.getEndTime();
+
+ while (!(replacement.startTask == Schedules.getLastTask(schedule))) {
+ schedule.removeLastTask();
+ }
+
+ schedule.addTask(taskFactory.createStayTask(vEntry.vehicle, replacement.startTask.getEndTime(),
+ Math.max(replacement.startTask.getEndTime(), initialEndTime), stayLink));
+
+ return; // done
+ }
+
+ // remove everything between the two indicated tasks
+ while (replacement.startTask.getTaskIdx() + 1 != replacement.endTask.getTaskIdx()) {
+ Task removeTask = schedule.getTasks().get(replacement.startTask.getTaskIdx() + 1);
+ schedule.removeTask(removeTask);
+ }
+
+ // if destination is not the schedule end, it must be another stop
+ Verify.verify(replacement.endTask instanceof DrtStopTask);
+ Link endLink = ((StayTask) replacement.endTask).getLink();
+ double endArrivalTime = replacement.endTask.getBeginTime();
+
+ final Task lastInsertedTask;
+ if (replacement.startTask instanceof DriveTask) { // special case: start task is driving
+ Verify.verify(replacement.startTask.getStatus().equals(TaskStatus.STARTED));
+
+ DriveTask driveTask = (DriveTask) replacement.startTask;
+ OnlineDriveTaskTracker tracker = (OnlineDriveTaskTracker) driveTask.getTaskTracker();
+ LinkTimePair diversion = tracker.getDiversionPoint();
+
+ VrpPathWithTravelData vrpPath = VrpPaths.calcAndCreatePathForDiversion(diversion, endLink, router,
+ travelTime);
+
+ if (vrpPath.getArrivalTime() < endArrivalTime && scheduleWaitBeforeDrive) {
+ tracker.divertPath(VrpPaths.createZeroLengthPathForDiversion(diversion));
+ lastInsertedTask = insertDriveWithWait(vEntry.vehicle, replacement.startTask, vrpPath, endArrivalTime);
+ } else {
+ tracker.divertPath(vrpPath);
+ lastInsertedTask = insertWait(vEntry.vehicle, replacement.startTask, endArrivalTime);
+ }
+ } else { // normal case
+ StayTask startStayTask = (StayTask) replacement.startTask;
+ Link startLink = startStayTask.getLink();
+
+ if (startLink == endLink) { // no need to move, maybe just wait
+ if (startStayTask.getEndTime() < endArrivalTime) {
+ lastInsertedTask = insertWait(vEntry.vehicle, startStayTask, endArrivalTime);
+ } else {
+ lastInsertedTask = startStayTask; // nothing inserted
+ }
+ } else {
+ VrpPathWithTravelData vrpPath = VrpPaths.calcAndCreatePath(startLink, endLink,
+ startStayTask.getEndTime(), router, travelTime);
+
+ lastInsertedTask = insertDriveWithWait(vEntry.vehicle, startStayTask, vrpPath, endArrivalTime);
+ }
+ }
+
+ timingUpdater.updateTimingsStartingFromTaskIdx(vEntry.vehicle, lastInsertedTask.getTaskIdx() + 1,
+ lastInsertedTask.getEndTime());
+ }
+
+ /*
+ * Copy & paste from DefaultRequestInsertionScheduler
+ */
+ private Task insertWait(DvrpVehicle vehicle, Task departureTask, double earliestNextStartTime) {
+ Schedule schedule = vehicle.getSchedule();
+
+ final Link waitLink;
+ if (departureTask instanceof StayTask) {
+ waitLink = ((StayTask) departureTask).getLink();
+ } else if (departureTask instanceof DriveTask) {
+ waitLink = ((DriveTask) departureTask).getPath().getToLink();
+ } else {
+ throw new IllegalStateException();
+ }
+
+ if (departureTask.getEndTime() < earliestNextStartTime) {
+ DrtStayTask waitTask = taskFactory.createStayTask(vehicle, departureTask.getEndTime(),
+ earliestNextStartTime, waitLink);
+ schedule.addTask(departureTask.getTaskIdx() + 1, waitTask);
+ return waitTask;
+ }
+
+ return departureTask;
+ }
+
+ /*
+ * Copy & paste from DefaultRequestInsertionScheduler
+ */
+ private Task insertDriveWithWait(DvrpVehicle vehicle, Task departureTask, VrpPathWithTravelData path,
+ double latestArrivalTime) {
+ Schedule schedule = vehicle.getSchedule();
+
+ Task leadingTask = departureTask;
+
+ if (scheduleWaitBeforeDrive) {
+ double driveDepartureTime = latestArrivalTime - path.getTravelTime();
+
+ if (driveDepartureTime > departureTask.getEndTime()) {
+ // makes sense to insert a wait task before departure
+ DrtStayTask waitTask = taskFactory.createStayTask(vehicle, departureTask.getEndTime(),
+ driveDepartureTime, path.getFromLink());
+ schedule.addTask(departureTask.getTaskIdx() + 1, waitTask);
+
+ path = path.withDepartureTime(driveDepartureTime);
+ leadingTask = waitTask;
+ }
+ }
+
+ Task driveTask = taskFactory.createDriveTask(vehicle, path, DrtDriveTask.TYPE);
+ schedule.addTask(leadingTask.getTaskIdx() + 1, driveTask);
+
+ if (driveTask.getEndTime() < latestArrivalTime) {
+ DrtStayTask waitTask = taskFactory.createStayTask(vehicle, driveTask.getEndTime(), latestArrivalTime,
+ path.getToLink());
+ schedule.addTask(driveTask.getTaskIdx() + 1, waitTask);
+ return waitTask;
+ } else {
+ return driveTask;
+ }
+ }
+
+ private class Replacement {
+ final Task startTask;
+ final Task endTask;
+
+ Replacement(Task startTask, Task endTask, Schedule schedule) {
+ boolean startIsOngoing = startTask.getStatus().equals(TaskStatus.STARTED);
+ boolean startIsStopTask = DrtTaskBaseType.STOP.isBaseTypeOf(startTask);
+
+ Verify.verify(startIsOngoing || startIsStopTask);
+ this.startTask = startTask;
+
+ boolean endIsLastStay = endTask instanceof DrtStayTask && Schedules.getLastTask(schedule) == endTask;
+ boolean endIsStopTask = DrtTaskBaseType.STOP.isBaseTypeOf(endTask);
+
+ Verify.verify(endIsLastStay || endIsStopTask);
+ this.endTask = endTask;
+ }
+ }
+}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/RequestUnscheduler.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/RequestUnscheduler.java
new file mode 100644
index 00000000000..fde9b138e92
--- /dev/null
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/RequestUnscheduler.java
@@ -0,0 +1,9 @@
+package org.matsim.contrib.drt.prebooking.unscheduler;
+
+import org.matsim.api.core.v01.Id;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
+import org.matsim.contrib.dvrp.optimizer.Request;
+
+public interface RequestUnscheduler {
+ void unscheduleRequest(double now, Id vehicleId, Id requestId);
+}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/SimpleRequestUnscheduler.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/SimpleRequestUnscheduler.java
new file mode 100644
index 00000000000..bc6f2f0fbcc
--- /dev/null
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/SimpleRequestUnscheduler.java
@@ -0,0 +1,60 @@
+package org.matsim.contrib.drt.prebooking.unscheduler;
+
+import org.matsim.api.core.v01.Id;
+import org.matsim.contrib.drt.schedule.DrtStopTask;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicleLookup;
+import org.matsim.contrib.dvrp.optimizer.Request;
+import org.matsim.contrib.dvrp.schedule.Schedule;
+import org.matsim.contrib.dvrp.schedule.Task;
+
+import com.google.common.base.Verify;
+
+/**
+ * This RequestUnscheduler searches for a request in a vehicle's schedule and
+ * removes the request from the relevant stop tasks. No other changes (wrt to
+ * rerouting the vehicle) are applied to the schedule.
+ *
+ * @author Sebastian Hörl (sebhoerl), IRT SystemX
+ */
+public class SimpleRequestUnscheduler implements RequestUnscheduler {
+ private final DvrpVehicleLookup vehicleLookup;
+
+ public SimpleRequestUnscheduler(DvrpVehicleLookup vehicleLookup) {
+ this.vehicleLookup = vehicleLookup;
+ }
+
+ @Override
+ public void unscheduleRequest(double now, Id vehicleId, Id requestId) {
+ DvrpVehicle vehicle = vehicleLookup.lookupVehicle(vehicleId);
+ Schedule schedule = vehicle.getSchedule();
+
+ DrtStopTask pickupTask = null;
+ DrtStopTask dropoffTask = null;
+
+ int currentIndex = schedule.getCurrentTask().getTaskIdx();
+ for (; currentIndex < schedule.getTaskCount() && dropoffTask == null; currentIndex++) {
+ Task currentTask = schedule.getTasks().get(currentIndex);
+
+ if (currentTask instanceof DrtStopTask) {
+ DrtStopTask stopTask = (DrtStopTask) currentTask;
+
+ if (stopTask.getPickupRequests().keySet().contains(requestId)) {
+ Verify.verify(pickupTask == null);
+ pickupTask = stopTask;
+ }
+
+ if (stopTask.getDropoffRequests().keySet().contains(requestId)) {
+ Verify.verify(dropoffTask == null);
+ dropoffTask = stopTask;
+ }
+ }
+ }
+
+ Verify.verifyNotNull(pickupTask);
+ Verify.verifyNotNull(dropoffTask);
+
+ pickupTask.removePickupRequest(requestId);
+ dropoffTask.removeDropoffRequest(requestId);
+ }
+}
\ No newline at end of file
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtModeQSimModule.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtModeQSimModule.java
index b0dea305ac0..526c75d8020 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtModeQSimModule.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtModeQSimModule.java
@@ -77,7 +77,7 @@ protected void configureQSim() {
}
if (drtCfg.getPrebookingParams().isPresent()) {
- install(new PrebookingModeQSimModule(getMode()));
+ install(new PrebookingModeQSimModule(getMode(), drtCfg.getPrebookingParams().get()));
bindModal(AdvanceRequestProvider.class).to(modalKey(PrebookingManager.class));
} else {
bindModal(AdvanceRequestProvider.class).toInstance(AdvanceRequestProvider.NONE);
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/schedule/DefaultDrtStopTask.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/schedule/DefaultDrtStopTask.java
index 21cb658a010..6efeb3fff96 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/schedule/DefaultDrtStopTask.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/schedule/DefaultDrtStopTask.java
@@ -84,4 +84,14 @@ public String toString() {
.add("super", super.toString())
.toString();
}
+
+ @Override
+ public void removePickupRequest(Id requestId) {
+ pickupRequests.remove(requestId);
+ }
+
+ @Override
+ public void removeDropoffRequest(Id requestId) {
+ dropoffRequests.remove(requestId);
+ }
}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/schedule/DrtStopTask.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/schedule/DrtStopTask.java
index f4e25dd79fd..3c2b5dd8351 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/schedule/DrtStopTask.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/schedule/DrtStopTask.java
@@ -42,4 +42,8 @@ public interface DrtStopTask extends StayTask {
void addDropoffRequest(AcceptedDrtRequest request);
void addPickupRequest(AcceptedDrtRequest request);
+
+ void removePickupRequest(Id requestId);
+
+ void removeDropoffRequest(Id requestId);
}
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/AbandonAndCancelTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/AbandonAndCancelTest.java
new file mode 100644
index 00000000000..c3dc7f546b0
--- /dev/null
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/AbandonAndCancelTest.java
@@ -0,0 +1,268 @@
+package org.matsim.contrib.drt.prebooking;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.matsim.api.core.v01.Id;
+import org.matsim.api.core.v01.population.Leg;
+import org.matsim.contrib.drt.prebooking.PrebookingTestEnvironment.RequestInfo;
+import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule;
+import org.matsim.core.controler.Controler;
+import org.matsim.core.mobsim.framework.PlanAgent;
+import org.matsim.core.mobsim.framework.events.MobsimBeforeSimStepEvent;
+import org.matsim.core.mobsim.framework.listeners.MobsimBeforeSimStepListener;
+import org.matsim.core.mobsim.qsim.QSim;
+import org.matsim.core.router.TripStructureUtils;
+import org.matsim.testcases.MatsimTestUtils;
+
+/**
+ * @author Sebastian Hörl (sebhoerl) / IRT SystemX
+ */
+public class AbandonAndCancelTest {
+ @Rule
+ public MatsimTestUtils utils = new MatsimTestUtils();
+
+ @Test
+ public void noAbandonTest() {
+ /*
+ * One person requests to depart at 2000 and also is there at 2000. Another
+ * person asks also to depart at 2000, but only arrives at 4000, i.e. the person
+ * has 1000s delay. The vehicle should wait accordingly.
+ */
+
+ PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) //
+ .addVehicle("vehicle", 1, 1) //
+ .addRequest("personOk", 0, 0, 5, 5, 2000.0, 0.0, 2000.0) //
+ .addRequest("personLate", 0, 0, 5, 5, 4000.0, 0.0, 2000.0) //
+ .configure(600.0, 1.3, 600.0, 60.0) //
+ .endTime(10.0 * 3600.0);
+
+ Controler controller = environment.build();
+ PrebookingTest.installPrebooking(controller);
+ controller.run();
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personOk");
+ assertEquals(0.0, requestInfo.submissionTime, 1e-3);
+ assertEquals(2061.0, requestInfo.pickupTime, 1e-3);
+ assertEquals(4271.0, requestInfo.dropoffTime, 1e-3);
+ }
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personLate");
+ assertEquals(0.0, requestInfo.submissionTime, 1e-3);
+ assertEquals(4060.0, requestInfo.pickupTime, 1e-3);
+ assertEquals(4271.0, requestInfo.dropoffTime, 1e-3);
+ }
+ }
+
+ @Test
+ public void abandonTest() {
+ /*
+ * One person requests to depart at 2000 and also is there at 2000. Another
+ * person asks also to depart at 2000, but only arrives at 4000, i.e. the person
+ * has 1000s delay.
+ *
+ * We configure that the vehicle should leave without the passenger if it waits
+ * longer than 500s. The late request will be rejected!
+ */
+
+ PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) //
+ .addVehicle("vehicle", 1, 1) //
+ .addRequest("personOk", 0, 0, 5, 5, 2000.0, 0.0, 2000.0) //
+ .addRequest("personLate", 0, 0, 5, 5, 4000.0, 0.0, 2000.0) //
+ .configure(600.0, 1.3, 600.0, 60.0) //
+ .endTime(10.0 * 3600.0);
+
+ Controler controller = environment.build();
+ PrebookingParams parameters = PrebookingTest.installPrebooking(controller);
+ parameters.maximumPassengerDelay = 500.0;
+ controller.run();
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personOk");
+ assertEquals(0.0, requestInfo.submissionTime, 1e-3);
+ assertEquals(2061.0, requestInfo.pickupTime, 1e-3);
+ assertEquals(2713.0, requestInfo.dropoffTime, 1e-3);
+ }
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personLate");
+ assertEquals(0.0, requestInfo.submissionTimes.get(0), 1e-3);
+ // agent tries a non-prebooked request upon arrival
+ assertEquals(4000.0, requestInfo.submissionTimes.get(1), 1e-3);
+ assertTrue(requestInfo.rejected);
+ }
+ }
+
+ @Test
+ public void abandonThenImmediateTest() {
+ /*
+ * One person requests to depart at 2000 and also is there at 2000. Another
+ * person asks also to depart at 2000, but only arrives at 4000, i.e. the person
+ * has 1000s delay.
+ *
+ * We configure that the vehicle should leave without the passenger if it waits
+ * longer than 500s. The person will, however, send a new request when arriving
+ * at the departure point and get an immediate vehicle.
+ */
+
+ PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) //
+ .addVehicle("vehicle", 1, 1) //
+ .addVehicle("vehicle2", 1, 1) //
+ .addRequest("personOk", 0, 0, 5, 5, 2000.0, 0.0, 2000.0) //
+ .addRequest("personLate", 0, 0, 5, 5, 4000.0, 0.0, 2000.0) //
+ .configure(600.0, 1.3, 600.0, 60.0) //
+ .endTime(10.0 * 3600.0);
+
+ Controler controller = environment.build();
+ PrebookingParams parameters = PrebookingTest.installPrebooking(controller);
+ parameters.maximumPassengerDelay = 500.0;
+ controller.run();
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personOk");
+ assertEquals(0.0, requestInfo.submissionTime, 1e-3);
+ assertEquals(2061.0, requestInfo.pickupTime, 1e-3);
+ assertEquals(2713.0, requestInfo.dropoffTime, 1e-3);
+ }
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personLate");
+ assertEquals(0.0, requestInfo.submissionTimes.get(0), 1e-3);
+ // agent tries a non-prebooked request upon arrival
+ assertEquals(4000.0, requestInfo.submissionTimes.get(1), 1e-3);
+ assertEquals(4146.0, requestInfo.pickupTime, 1e-3);
+ assertEquals(4357.0, requestInfo.dropoffTime, 1e-3);
+ assertTrue(requestInfo.rejected);
+ }
+ }
+
+ @Test
+ public void cancelEarlyTest() {
+ /*
+ * One person requests to depart at 2000 and also is there at 2000. Another
+ * person asks also to depart at 2000, but only arrives at 4000, i.e. the person
+ * has 1000s delay.
+ *
+ * In this test we manually cancel the second request at 500.0 (so before
+ * departure of any agent).
+ */
+
+ PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) //
+ .addVehicle("vehicle", 1, 1) //
+ .addRequest("personOk", 0, 0, 5, 5, 2000.0, 0.0, 2000.0) //
+ .addRequest("personLate", 0, 0, 5, 5, 4000.0, 0.0, 2000.0) //
+ .configure(600.0, 1.3, 600.0, 60.0) //
+ .endTime(10.0 * 3600.0);
+
+ Controler controller = environment.build();
+ PrebookingTest.installPrebooking(controller);
+
+ controller.addOverridingQSimModule(new AbstractDvrpModeQSimModule("drt") {
+ @Override
+ protected void configureQSim() {
+ addModalQSimComponentBinding().toProvider(modalProvider(getter -> {
+ PrebookingManager prebookingManager = getter.getModal(PrebookingManager.class);
+ QSim qsim = getter.get(QSim.class);
+
+ return new MobsimBeforeSimStepListener() {
+ @Override
+ public void notifyMobsimBeforeSimStep(MobsimBeforeSimStepEvent e) {
+ if (e.getSimulationTime() == 500.0) {
+ PlanAgent planAgent = (PlanAgent) qsim.getAgents()
+ .get(Id.createPersonId("personLate"));
+
+ Leg leg = TripStructureUtils.getLegs(planAgent.getCurrentPlan()).get(1);
+
+ prebookingManager.cancel(leg);
+ }
+ }
+ };
+ }));
+ }
+ });
+
+ controller.run();
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personOk");
+ assertEquals(0.0, requestInfo.submissionTime, 1e-3);
+ assertEquals(2061.0, requestInfo.pickupTime, 1e-3);
+ assertEquals(2272.0, requestInfo.dropoffTime, 1e-3);
+ }
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personLate");
+ assertEquals(0.0, requestInfo.submissionTimes.get(0), 1e-3);
+ // agent tries a non-prebooked request upon arrival
+ assertEquals(4000.0, requestInfo.submissionTimes.get(1), 1e-3);
+ assertTrue(requestInfo.rejected);
+ }
+ }
+
+ @Test
+ public void cancelLateTest() {
+ /*
+ * One person requests to depart at 2000 and also is there at 2000. Another
+ * person asks also to depart at 2000, but only arrives at 4000, i.e. the person
+ * has 1000s delay.
+ *
+ * In this test we manually cancel the second request at 3000.0 (so after
+ * departure of the first agent).
+ */
+
+ PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) //
+ .addVehicle("vehicle", 1, 1) //
+ .addRequest("personOk", 0, 0, 5, 5, 2000.0, 0.0, 2000.0) //
+ .addRequest("personLate", 0, 0, 5, 5, 4000.0, 0.0, 2000.0) //
+ .configure(600.0, 1.3, 600.0, 60.0) //
+ .endTime(10.0 * 3600.0);
+
+ Controler controller = environment.build();
+ PrebookingTest.installPrebooking(controller);
+
+ controller.addOverridingQSimModule(new AbstractDvrpModeQSimModule("drt") {
+ @Override
+ protected void configureQSim() {
+ addModalQSimComponentBinding().toProvider(modalProvider(getter -> {
+ PrebookingManager prebookingManager = getter.getModal(PrebookingManager.class);
+ QSim qsim = getter.get(QSim.class);
+
+ return new MobsimBeforeSimStepListener() {
+ @Override
+ public void notifyMobsimBeforeSimStep(MobsimBeforeSimStepEvent e) {
+ if (e.getSimulationTime() == 3000.0) {
+ PlanAgent planAgent = (PlanAgent) qsim.getAgents()
+ .get(Id.createPersonId("personLate"));
+
+ Leg leg = TripStructureUtils.getLegs(planAgent.getCurrentPlan()).get(1);
+
+ prebookingManager.cancel(leg);
+ }
+ }
+ };
+ }));
+ }
+ });
+
+ controller.run();
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personOk");
+ assertEquals(0.0, requestInfo.submissionTime, 1e-3);
+ assertEquals(2061.0, requestInfo.pickupTime, 1e-3);
+ assertEquals(3212.0, requestInfo.dropoffTime, 1e-3); // still waited quite a bit
+ }
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personLate");
+ assertEquals(0.0, requestInfo.submissionTimes.get(0), 1e-3);
+ // agent tries a non-prebooked request upon arrival
+ assertEquals(4000.0, requestInfo.submissionTimes.get(1), 1e-3);
+ assertTrue(requestInfo.rejected);
+ }
+ }
+}
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/ComplexUnschedulerTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/ComplexUnschedulerTest.java
new file mode 100644
index 00000000000..84c3ada7791
--- /dev/null
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/ComplexUnschedulerTest.java
@@ -0,0 +1,688 @@
+package org.matsim.contrib.drt.prebooking;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.junit.Test;
+import org.matsim.api.core.v01.Coord;
+import org.matsim.api.core.v01.Id;
+import org.matsim.api.core.v01.network.Link;
+import org.matsim.api.core.v01.network.Network;
+import org.matsim.api.core.v01.network.NetworkFactory;
+import org.matsim.api.core.v01.network.Node;
+import org.matsim.contrib.drt.optimizer.VehicleDataEntryFactoryImpl;
+import org.matsim.contrib.drt.optimizer.VehicleEntry;
+import org.matsim.contrib.drt.passenger.AcceptedDrtRequest;
+import org.matsim.contrib.drt.prebooking.unscheduler.ComplexRequestUnscheduler;
+import org.matsim.contrib.drt.run.DrtConfigGroup;
+import org.matsim.contrib.drt.schedule.DefaultDrtStopTask;
+import org.matsim.contrib.drt.schedule.DrtDriveTask;
+import org.matsim.contrib.drt.schedule.DrtStayTask;
+import org.matsim.contrib.drt.schedule.DrtStopTask;
+import org.matsim.contrib.drt.schedule.DrtTaskFactory;
+import org.matsim.contrib.drt.schedule.DrtTaskFactoryImpl;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicleImpl;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicleLookup;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicleSpecification;
+import org.matsim.contrib.dvrp.fleet.ImmutableDvrpVehicleSpecification;
+import org.matsim.contrib.dvrp.optimizer.Request;
+import org.matsim.contrib.dvrp.path.DivertedVrpPath;
+import org.matsim.contrib.dvrp.path.VrpPathWithTravelData;
+import org.matsim.contrib.dvrp.path.VrpPaths;
+import org.matsim.contrib.dvrp.schedule.DriveTask;
+import org.matsim.contrib.dvrp.schedule.Schedule;
+import org.matsim.contrib.dvrp.schedule.ScheduleTimingUpdater;
+import org.matsim.contrib.dvrp.schedule.StayTask;
+import org.matsim.contrib.dvrp.schedule.Task;
+import org.matsim.contrib.dvrp.tracker.OnlineDriveTaskTracker;
+import org.matsim.contrib.dvrp.util.LinkTimePair;
+import org.matsim.core.network.NetworkUtils;
+import org.matsim.core.router.DijkstraFactory;
+import org.matsim.core.router.costcalculators.OnlyTimeDependentTravelDisutility;
+import org.matsim.core.router.util.LeastCostPathCalculator;
+import org.matsim.core.router.util.TravelTime;
+import org.matsim.core.trafficmonitoring.FreeSpeedTravelTime;
+import org.mockito.Mockito;
+
+/**
+ * @author Sebastian Hörl (sebhoerl) / IRT SystemX
+ */
+public class ComplexUnschedulerTest {
+ @Test
+ public void testDirectDropoffAfterPickup() {
+ Fixture fixture = new Fixture();
+ Schedule schedule = fixture.schedule;
+
+ AcceptedDrtRequest otherRequest1 = fixture.createRequest();
+ AcceptedDrtRequest otherRequest2 = fixture.createRequest();
+ AcceptedDrtRequest unscheduleRequest = fixture.createRequest();
+
+ fixture.addDrive("f10");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(otherRequest1);
+ fixture.addDrive("f20");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(otherRequest2);
+ fixture.addDrive("f30"); // replace start
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(unscheduleRequest);
+ fixture.addDrive("f40");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(unscheduleRequest);
+ fixture.addDrive("f50");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addDropoffRequest(otherRequest2);
+ fixture.addDrive("f60");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(otherRequest1);
+ fixture.addFinalStay(30.0 * 3600.0);
+
+ schedule.nextTask();
+ schedule.nextTask();
+
+ ComplexRequestUnscheduler unscheduler = new ComplexRequestUnscheduler(fixture.lookup, fixture.entryFactory,
+ fixture.taskFactory, fixture.router, fixture.travelTime, fixture.timingUpdater, false);
+
+ unscheduler.unscheduleRequest(100.0, fixture.vehicle.getId(), unscheduleRequest.getId());
+
+ assertEquals(13, schedule.getTaskCount());
+
+ assertTasks(schedule, Arrays.asList( //
+ new ReferenceTask(DrtDriveTask.class, 0.0, 10001.0), // 0
+ new ReferenceTask(DrtStayTask.class, 10001.0, 10301.0), // 1
+ new ReferenceTask(DefaultDrtStopTask.class, 10301.0, 10361.0), // 2
+ new ReferenceTask(DrtDriveTask.class, 10361.0, 20362.0), // 3
+ new ReferenceTask(DrtStayTask.class, 20362.0, 20662.0), // 4
+ new ReferenceTask(DefaultDrtStopTask.class, 20662.0, 20722.0), // 5
+ new ReferenceTask(DrtDriveTask.class, 20722.0, 50723.0), // 6
+ new ReferenceTask(DrtStayTask.class, 50723.0, 51745.0), // 7
+ new ReferenceTask(DefaultDrtStopTask.class, 51745.0, 51805.0), // 8
+ new ReferenceTask(DrtDriveTask.class, 51805.0, 61806.0), // 9
+ new ReferenceTask(DrtStayTask.class, 61806.0, 62106.0), // 10
+ new ReferenceTask(DefaultDrtStopTask.class, 62106.0, 62166.0), // 11
+ new ReferenceTask(DrtStayTask.class, 62166.0, 30.0 * 3600.0) // 12
+ ));
+
+ DrtDriveTask insertedDriveTask = (DrtDriveTask) schedule.getTasks().get(6);
+ assertEquals("f20", insertedDriveTask.getPath().getFromLink().getId().toString());
+ assertEquals("f50", insertedDriveTask.getPath().getToLink().getId().toString());
+ }
+
+ @Test
+ public void testStandardSituation() {
+ Fixture fixture = new Fixture();
+ Schedule schedule = fixture.schedule;
+
+ AcceptedDrtRequest otherRequest1 = fixture.createRequest();
+ AcceptedDrtRequest otherRequest2 = fixture.createRequest();
+ AcceptedDrtRequest unscheduleRequest = fixture.createRequest();
+
+ fixture.addDrive("f10");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(otherRequest1); // f10
+ fixture.addDrive("f20"); // replace start
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(unscheduleRequest); // f20
+ fixture.addDrive("f30");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addPickupRequest(otherRequest2); // f30
+ fixture.addDrive("f40");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(otherRequest2); // f40
+ fixture.addDrive("f50"); // replace start
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(unscheduleRequest); // f50
+ fixture.addDrive("f60");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addDropoffRequest(otherRequest1); // f60
+ fixture.addFinalStay(30.0 * 3600.0);
+
+ schedule.nextTask();
+ schedule.nextTask();
+
+ ComplexRequestUnscheduler unscheduler = new ComplexRequestUnscheduler(fixture.lookup, fixture.entryFactory,
+ fixture.taskFactory, fixture.router, fixture.travelTime, fixture.timingUpdater, false);
+
+ unscheduler.unscheduleRequest(100.0, fixture.vehicle.getId(), unscheduleRequest.getId());
+
+ assertEquals(13, schedule.getTaskCount());
+
+ assertTasks(schedule, Arrays.asList( //
+ new ReferenceTask(DrtDriveTask.class, 0.0, 10001.0), // 0
+ new ReferenceTask(DrtStayTask.class, 10001.0, 10301.0), // 1
+ new ReferenceTask(DefaultDrtStopTask.class, 10301.0, 10361.0), // 2
+ new ReferenceTask(DrtDriveTask.class, 10361.0, 30362.0), // 3
+ new ReferenceTask(DrtStayTask.class, 30362.0, 31023.0), // 4
+ new ReferenceTask(DefaultDrtStopTask.class, 31023.0, 31083.0), // 5
+ new ReferenceTask(DrtDriveTask.class, 31083.0, 41084.0), // 6
+ new ReferenceTask(DrtStayTask.class, 41084.0, 41384.0), // 7
+ new ReferenceTask(DefaultDrtStopTask.class, 41384.0, 41444.0), // 8
+ new ReferenceTask(DrtDriveTask.class, 41444.0, 61445.0), // 9
+ new ReferenceTask(DrtStayTask.class, 61445.0, 62106.0), // 10
+ new ReferenceTask(DefaultDrtStopTask.class, 62106.0, 62166.0), // 11
+ new ReferenceTask(DrtStayTask.class, 62166.0, 30.0 * 3600.0) // 12
+ ));
+
+ DrtDriveTask insertedDriveTask = (DrtDriveTask) schedule.getTasks().get(3);
+ assertEquals("f10", insertedDriveTask.getPath().getFromLink().getId().toString());
+ assertEquals("f30", insertedDriveTask.getPath().getToLink().getId().toString());
+
+ DrtDriveTask insertedDriveTask2 = (DrtDriveTask) schedule.getTasks().get(9);
+ assertEquals("f40", insertedDriveTask2.getPath().getFromLink().getId().toString());
+ assertEquals("f60", insertedDriveTask2.getPath().getToLink().getId().toString());
+ }
+
+ @Test
+ public void testRemoveAtEnd() {
+ Fixture fixture = new Fixture();
+ Schedule schedule = fixture.schedule;
+
+ AcceptedDrtRequest otherRequest1 = fixture.createRequest();
+ AcceptedDrtRequest otherRequest2 = fixture.createRequest();
+ AcceptedDrtRequest unscheduleRequest = fixture.createRequest();
+
+ fixture.addDrive("f10");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(otherRequest1); // f10
+ fixture.addDrive("f20"); // replace start
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(unscheduleRequest); // f20
+ fixture.addDrive("f30");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addPickupRequest(otherRequest2); // f30
+ fixture.addDrive("f40");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(otherRequest2); // f40
+ fixture.addDrive("f50");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(otherRequest1); // f50
+ fixture.addDrive("f60"); // replace start
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(unscheduleRequest); // f60 // replace end
+ fixture.addFinalStay(30.0 * 3600.0);
+
+ schedule.nextTask();
+ schedule.nextTask();
+
+ ComplexRequestUnscheduler unscheduler = new ComplexRequestUnscheduler(fixture.lookup, fixture.entryFactory,
+ fixture.taskFactory, fixture.router, fixture.travelTime, fixture.timingUpdater, false);
+
+ unscheduler.unscheduleRequest(100.0, fixture.vehicle.getId(), unscheduleRequest.getId());
+
+ assertTasks(schedule, Arrays.asList( //
+ new ReferenceTask(DrtDriveTask.class, 0.0, 10001.0), // 0
+ new ReferenceTask(DrtStayTask.class, 10001.0, 10301.0), // 1
+ new ReferenceTask(DefaultDrtStopTask.class, 10301.0, 10361.0), // 2
+ new ReferenceTask(DrtDriveTask.class, 10361.0, 30362.0), // 3
+ new ReferenceTask(DrtStayTask.class, 30362.0, 31023.0), // 4
+ new ReferenceTask(DefaultDrtStopTask.class, 31023.0, 31083.0), // 5
+ new ReferenceTask(DrtDriveTask.class, 31083.0, 41084.0), // 6
+ new ReferenceTask(DrtStayTask.class, 41084.0, 41384.0), // 7
+ new ReferenceTask(DefaultDrtStopTask.class, 41384.0, 41444.0), // 8
+ new ReferenceTask(DrtDriveTask.class, 41444.0, 51445.0), // 9
+ new ReferenceTask(DrtStayTask.class, 51445.0, 51745.0), // 10
+ new ReferenceTask(DefaultDrtStopTask.class, 51745.0, 51805.0), // 11
+ new ReferenceTask(DrtStayTask.class, 51805.0, 30.0 * 3600.0) // 12
+ ));
+
+ DrtDriveTask insertedDriveTask = (DrtDriveTask) schedule.getTasks().get(3);
+ assertEquals("f10", insertedDriveTask.getPath().getFromLink().getId().toString());
+ assertEquals("f30", insertedDriveTask.getPath().getToLink().getId().toString());
+
+ DrtDriveTask insertedDriveTask2 = (DrtDriveTask) schedule.getTasks().get(9);
+ assertEquals("f40", insertedDriveTask2.getPath().getFromLink().getId().toString());
+ assertEquals("f50", insertedDriveTask2.getPath().getToLink().getId().toString());
+
+ DrtStayTask stayTask = (DrtStayTask) schedule.getTasks().get(12);
+ assertEquals("f50", stayTask.getLink().getId().toString());
+ }
+
+ @Test
+ public void testRemoveAtBeginningWithWaitSecond() {
+ Fixture fixture = new Fixture();
+ Schedule schedule = fixture.schedule;
+
+ AcceptedDrtRequest otherRequest1 = fixture.createRequest();
+ AcceptedDrtRequest otherRequest2 = fixture.createRequest();
+ AcceptedDrtRequest unscheduleRequest = fixture.createRequest();
+
+ fixture.addDrive("f10");
+ fixture.addWait(300.0); // replace start
+ fixture.addStop(60.0).addPickupRequest(unscheduleRequest); // f10
+ fixture.addDrive("f20");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addPickupRequest(otherRequest1); // f20
+ fixture.addDrive("f30");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(otherRequest2); // f30
+ fixture.addDrive("f40");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(otherRequest2); // f40
+ fixture.addDrive("f50"); // replace start
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(unscheduleRequest); // f50
+ fixture.addDrive("f60");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addDropoffRequest(otherRequest1); // f60
+ fixture.addFinalStay(30.0 * 3600.0);
+
+ schedule.nextTask();
+ schedule.nextTask();
+
+ ComplexRequestUnscheduler unscheduler = new ComplexRequestUnscheduler(fixture.lookup, fixture.entryFactory,
+ fixture.taskFactory, fixture.router, fixture.travelTime, fixture.timingUpdater, false);
+
+ unscheduler.unscheduleRequest(10100.0, fixture.vehicle.getId(), unscheduleRequest.getId());
+
+ assertEquals(15, schedule.getTaskCount());
+
+ assertTasks(schedule, Arrays.asList( //
+ new ReferenceTask(DrtDriveTask.class, 0.0, 10001.0), // 0
+ new ReferenceTask(DrtStayTask.class, 10001.0, 10100.0), // 1
+ new ReferenceTask(DrtDriveTask.class, 10100.0, 20101.0), // 2
+ new ReferenceTask(DrtStayTask.class, 20101.0, 20662.0), // 3
+ new ReferenceTask(DefaultDrtStopTask.class, 20662.0, 20722.0), // 4
+ new ReferenceTask(DrtDriveTask.class, 20722.0, 30723.0), // 5
+ new ReferenceTask(DrtStayTask.class, 30723.0, 31023.0), // 6
+ new ReferenceTask(DefaultDrtStopTask.class, 31023.0, 31083.0), // 7
+ new ReferenceTask(DrtDriveTask.class, 31083.0, 41084.0), // 8
+ new ReferenceTask(DrtStayTask.class, 41084.0, 41384.0), // 9
+ new ReferenceTask(DefaultDrtStopTask.class, 41384.0, 41444.0), // 10
+ new ReferenceTask(DrtDriveTask.class, 41444.0, 61445.0), // 11
+ new ReferenceTask(DrtStayTask.class, 61445.0, 62106.0), // 12
+ new ReferenceTask(DefaultDrtStopTask.class, 62106.0, 62166.0), // 13
+ new ReferenceTask(DrtStayTask.class, 62166.0, 30.0 * 3600.0) // 14
+ ));
+
+ DrtDriveTask insertedDriveTask = (DrtDriveTask) schedule.getTasks().get(2);
+ assertEquals("f10", insertedDriveTask.getPath().getFromLink().getId().toString());
+ assertEquals("f20", insertedDriveTask.getPath().getToLink().getId().toString());
+
+ DrtDriveTask insertedDriveTask2 = (DrtDriveTask) schedule.getTasks().get(11);
+ assertEquals("f40", insertedDriveTask2.getPath().getFromLink().getId().toString());
+ assertEquals("f60", insertedDriveTask2.getPath().getToLink().getId().toString());
+ }
+
+ @Test
+ public void testRemoveAtBeginningWithWaitFirst() {
+ Fixture fixture = new Fixture();
+ Schedule schedule = fixture.schedule;
+
+ AcceptedDrtRequest otherRequest1 = fixture.createRequest();
+ AcceptedDrtRequest otherRequest2 = fixture.createRequest();
+ AcceptedDrtRequest unscheduleRequest = fixture.createRequest();
+
+ fixture.addWait(300.0); // replace start
+ fixture.addDrive("f10");
+ fixture.addStop(60.0).addPickupRequest(unscheduleRequest); // f10
+ fixture.addDrive("f20");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addPickupRequest(otherRequest1); // f20
+ fixture.addDrive("f30");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(otherRequest2); // f30
+ fixture.addDrive("f40");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(otherRequest2); // f40
+ fixture.addDrive("f50"); // replace start
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(unscheduleRequest); // f50
+ fixture.addDrive("f60");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addDropoffRequest(otherRequest1); // f60
+ fixture.addFinalStay(30.0 * 3600.0);
+
+ schedule.nextTask();
+
+ ComplexRequestUnscheduler unscheduler = new ComplexRequestUnscheduler(fixture.lookup, fixture.entryFactory,
+ fixture.taskFactory, fixture.router, fixture.travelTime, fixture.timingUpdater, false);
+
+ unscheduler.unscheduleRequest(500.0, fixture.vehicle.getId(), unscheduleRequest.getId());
+
+ assertEquals(14, schedule.getTaskCount());
+
+ assertTasks(schedule, Arrays.asList( //
+ new ReferenceTask(DrtStayTask.class, 0.0, 500.0), // 0
+ new ReferenceTask(DrtDriveTask.class, 500.0, 20501.0), // 1
+ new ReferenceTask(DrtStayTask.class, 20501.0, 20662.0), // 2
+ new ReferenceTask(DefaultDrtStopTask.class, 20662.0, 20722.0), // 3
+ new ReferenceTask(DrtDriveTask.class, 20722.0, 30723.0), // 4
+ new ReferenceTask(DrtStayTask.class, 30723.0, 31023.0), // 5
+ new ReferenceTask(DefaultDrtStopTask.class, 31023.0, 31083.0), // 6
+ new ReferenceTask(DrtDriveTask.class, 31083.0, 41084.0), // 7
+ new ReferenceTask(DrtStayTask.class, 41084.0, 41384.0), // 8
+ new ReferenceTask(DefaultDrtStopTask.class, 41384.0, 41444.0), // 9
+ new ReferenceTask(DrtDriveTask.class, 41444.0, 61445.0), // 10
+ new ReferenceTask(DrtStayTask.class, 61445.0, 62106.0), // 11
+ new ReferenceTask(DefaultDrtStopTask.class, 62106.0, 62166.0), // 12
+ new ReferenceTask(DrtStayTask.class, 62166.0, 30.0 * 3600.0) // 13
+ ));
+
+ DrtDriveTask insertedDriveTask = (DrtDriveTask) schedule.getTasks().get(1);
+ assertEquals("f0", insertedDriveTask.getPath().getFromLink().getId().toString());
+ assertEquals("f20", insertedDriveTask.getPath().getToLink().getId().toString());
+
+ DrtDriveTask insertedDriveTask2 = (DrtDriveTask) schedule.getTasks().get(10);
+ assertEquals("f40", insertedDriveTask2.getPath().getFromLink().getId().toString());
+ assertEquals("f60", insertedDriveTask2.getPath().getToLink().getId().toString());
+ }
+
+ @Test
+ public void testRemoveAtBeginningWithDriveDiversion() {
+ Fixture fixture = new Fixture();
+ Schedule schedule = fixture.schedule;
+
+ AcceptedDrtRequest otherRequest1 = fixture.createRequest();
+ AcceptedDrtRequest otherRequest2 = fixture.createRequest();
+ AcceptedDrtRequest unscheduleRequest = fixture.createRequest();
+
+ fixture.addDrive("f10");
+ fixture.addWait(300.0); // replace start
+ fixture.addStop(60.0).addPickupRequest(unscheduleRequest); // f10
+ fixture.addDrive("f20");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addPickupRequest(otherRequest1); // f20
+ fixture.addDrive("f30");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(otherRequest2); // f30
+ fixture.addDrive("f40");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(otherRequest2); // f40
+ fixture.addDrive("f50"); // replace start
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(unscheduleRequest); // f50
+ fixture.addDrive("f60");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addDropoffRequest(otherRequest1); // f60
+ fixture.addFinalStay(30.0 * 3600.0);
+
+ schedule.nextTask();
+
+ OnlineDriveTaskTracker tracker = Mockito.mock(OnlineDriveTaskTracker.class);
+ schedule.getTasks().get(0).initTaskTracker(tracker);
+
+ LinkTimePair diversionPoint = new LinkTimePair(fixture.network.getLinks().get(Id.createLinkId("f5")), 20.0);
+ Mockito.when(tracker.getDiversionPoint()).thenReturn(diversionPoint);
+
+ Mockito.doAnswer(invocation -> {
+ VrpPathWithTravelData path = invocation.getArgument(0);
+ DriveTask task = (DriveTask) schedule.getTasks().get(0);
+ DivertedVrpPath divertedPath = new DivertedVrpPath(task.getPath(), path, 5);
+ task.pathDiverted(divertedPath, path.getArrivalTime());
+ return null;
+ }).when(tracker).divertPath(Mockito.any());
+
+ ComplexRequestUnscheduler unscheduler = new ComplexRequestUnscheduler(fixture.lookup, fixture.entryFactory,
+ fixture.taskFactory, fixture.router, fixture.travelTime, fixture.timingUpdater, false);
+
+ unscheduler.unscheduleRequest(500.0, fixture.vehicle.getId(), unscheduleRequest.getId());
+
+ assertEquals(13, schedule.getTaskCount());
+
+ assertTasks(schedule, Arrays.asList( //
+ new ReferenceTask(DrtDriveTask.class, 0.0, 15019.0), // 0
+ new ReferenceTask(DrtStayTask.class, 15019.0, 20662.0), // 1
+ new ReferenceTask(DefaultDrtStopTask.class, 20662.0, 20722.0), // 2
+ new ReferenceTask(DrtDriveTask.class, 20722.0, 30723.0), // 3
+ new ReferenceTask(DrtStayTask.class, 30723.0, 31023.0), // 4
+ new ReferenceTask(DefaultDrtStopTask.class, 31023.0, 31083.0), // 5
+ new ReferenceTask(DrtDriveTask.class, 31083.0, 41084.0), // 6
+ new ReferenceTask(DrtStayTask.class, 41084.0, 41384.0), // 7
+ new ReferenceTask(DefaultDrtStopTask.class, 41384.0, 41444.0), // 8
+ new ReferenceTask(DrtDriveTask.class, 41444.0, 61445.0), // 9
+ new ReferenceTask(DrtStayTask.class, 61445.0, 62106.0), // 10
+ new ReferenceTask(DefaultDrtStopTask.class, 62106.0, 62166.0), // 11
+ new ReferenceTask(DrtStayTask.class, 62166.0, 30.0 * 3600.0) // 12
+ ));
+
+ DrtDriveTask insertedDriveTask = (DrtDriveTask) schedule.getTasks().get(0);
+ assertEquals("f0", insertedDriveTask.getPath().getFromLink().getId().toString());
+ assertEquals("f20", insertedDriveTask.getPath().getToLink().getId().toString());
+
+ DrtDriveTask insertedDriveTask2 = (DrtDriveTask) schedule.getTasks().get(9);
+ assertEquals("f40", insertedDriveTask2.getPath().getFromLink().getId().toString());
+ assertEquals("f60", insertedDriveTask2.getPath().getToLink().getId().toString());
+ }
+
+ @Test
+ public void testRemoveAllStartWithWait() {
+ Fixture fixture = new Fixture();
+ Schedule schedule = fixture.schedule;
+
+ AcceptedDrtRequest unscheduleRequest = fixture.createRequest();
+
+ fixture.addWait(300.0);
+ fixture.addDrive("f10");
+ fixture.addStop(60.0).addPickupRequest(unscheduleRequest); // f10
+ fixture.addDrive("f20");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(unscheduleRequest); // f50
+ fixture.addFinalStay(30.0 * 3600.0);
+
+ schedule.nextTask();
+
+ ComplexRequestUnscheduler unscheduler = new ComplexRequestUnscheduler(fixture.lookup, fixture.entryFactory,
+ fixture.taskFactory, fixture.router, fixture.travelTime, fixture.timingUpdater, false);
+
+ unscheduler.unscheduleRequest(0.0, fixture.vehicle.getId(), unscheduleRequest.getId());
+
+ assertEquals(2, schedule.getTaskCount());
+
+ assertTasks(schedule, Arrays.asList( //
+ new ReferenceTask(DrtStayTask.class, 0.0, 0.0), // 0
+ new ReferenceTask(DrtStayTask.class, 0.0, 30.0 * 3600.0) // 1
+ ));
+
+ assertEquals("f0", ((StayTask) schedule.getTasks().get(0)).getLink().getId().toString());
+ assertEquals("f0", ((StayTask) schedule.getTasks().get(1)).getLink().getId().toString());
+ }
+
+ @Test
+ public void testRemoveAllStartWithDrive() {
+ Fixture fixture = new Fixture();
+ Schedule schedule = fixture.schedule;
+
+ AcceptedDrtRequest unscheduleRequest = fixture.createRequest();
+
+ fixture.addDrive("f10");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(unscheduleRequest); // f10
+ fixture.addDrive("f20");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(unscheduleRequest); // f50
+ fixture.addFinalStay(30.0 * 3600.0);
+
+ schedule.nextTask();
+
+ OnlineDriveTaskTracker tracker = Mockito.mock(OnlineDriveTaskTracker.class);
+ schedule.getTasks().get(0).initTaskTracker(tracker);
+
+ LinkTimePair diversionPoint = new LinkTimePair(fixture.network.getLinks().get(Id.createLinkId("f5")), 20.0);
+ Mockito.when(tracker.getDiversionPoint()).thenReturn(diversionPoint);
+
+ Mockito.doAnswer(invocation -> {
+ VrpPathWithTravelData path = invocation.getArgument(0);
+ DriveTask task = (DriveTask) schedule.getTasks().get(0);
+ DivertedVrpPath divertedPath = new DivertedVrpPath(task.getPath(), path, 5);
+ task.pathDiverted(divertedPath, path.getArrivalTime());
+ return null;
+ }).when(tracker).divertPath(Mockito.any());
+
+ ComplexRequestUnscheduler unscheduler = new ComplexRequestUnscheduler(fixture.lookup, fixture.entryFactory,
+ fixture.taskFactory, fixture.router, fixture.travelTime, fixture.timingUpdater, false);
+
+ unscheduler.unscheduleRequest(0.0, fixture.vehicle.getId(), unscheduleRequest.getId());
+
+ assertEquals(2, schedule.getTaskCount());
+
+ assertTasks(schedule, Arrays.asList( //
+ new ReferenceTask(DrtDriveTask.class, 0.0, 19.0), // 0
+ new ReferenceTask(DrtStayTask.class, 19.0, 30.0 * 3600.0) // 1
+ ));
+
+ DrtDriveTask driveTask = (DrtDriveTask) schedule.getTasks().get(0);
+ assertEquals("f0", driveTask.getPath().getFromLink().getId().toString());
+ assertEquals("f5", driveTask.getPath().getToLink().getId().toString());
+
+ assertEquals("f5", ((StayTask) schedule.getTasks().get(1)).getLink().getId().toString());
+ }
+
+ record ReferenceTask(Class extends Task> taskType, double startTime, double endTime) {
+ }
+
+ private static void assertTasks(Schedule schedule, List references) {
+ for (int i = 0; i < references.size(); i++) {
+ Task task = schedule.getTasks().get(i);
+ ReferenceTask reference = references.get(i);
+
+ assertEquals("wrong type in task " + i, reference.taskType, task.getClass());
+ assertEquals("wrong begin time in task " + i, reference.startTime, task.getBeginTime(), 1e-3);
+ assertEquals("wrong end time in task " + i, reference.endTime, task.getEndTime(), 1e-3);
+
+ if (i > 0) {
+ assertEquals("wrong transition from " + (i - 1) + " to " + i, schedule.getTasks().get(i).getBeginTime(),
+ schedule.getTasks().get(i - 1).getEndTime(), 1e-3);
+ }
+
+ assertTrue("invalid task " + i, task.getEndTime() >= task.getBeginTime());
+ }
+ }
+
+ private Network createNetwork() {
+ Network network = NetworkUtils.createNetwork();
+ NetworkFactory networkFactory = network.getFactory();
+
+ List nodes = new LinkedList<>();
+
+ for (int i = 0; i < 100; i++) {
+ Node node = networkFactory.createNode(Id.createNodeId("n" + i), new Coord(0.0, i * 1000.0));
+ network.addNode(node);
+ nodes.add(node);
+ }
+
+ for (int i = 0; i < 99; i++) {
+ Link forwardLink = networkFactory.createLink(Id.createLinkId("f" + i), nodes.get(i), nodes.get(i + 1));
+ network.addLink(forwardLink);
+
+ Link backwardLink = networkFactory.createLink(Id.createLinkId("b" + i), nodes.get(i + 1), nodes.get(i));
+ network.addLink(backwardLink);
+ }
+
+ for (Link link : network.getLinks().values()) {
+ link.setAllowedModes(Collections.singleton("car"));
+ link.setLength(1000.0);
+ link.setFreespeed(1.0);
+ }
+
+ return network;
+ }
+
+ private class Fixture {
+ private final DvrpVehicle vehicle;
+ private final Schedule schedule;
+ private final Network network;
+
+ private Link currentLink;
+ private double currentTime;
+
+ private final DrtTaskFactory taskFactory = new DrtTaskFactoryImpl();
+ private final LeastCostPathCalculator router;
+ private final TravelTime travelTime = new FreeSpeedTravelTime();
+
+ private final VehicleEntry.EntryFactory entryFactory;
+ private final ScheduleTimingUpdater timingUpdater;
+ private final DvrpVehicleLookup lookup;
+
+ private int requestIndex = 0;
+
+ Fixture() {
+ this.network = createNetwork();
+
+ Link depotLink = network.getLinks().get(Id.createLinkId("f0"));
+
+ DvrpVehicleSpecification vehicleSpecification = ImmutableDvrpVehicleSpecification.newBuilder() //
+ .id(Id.create("vehicle", DvrpVehicle.class)) //
+ .capacity(4) //
+ .serviceBeginTime(0.0) //
+ .serviceEndTime(30.0 * 3600.0) //
+ .startLinkId(depotLink.getId()) //
+ .build();
+
+ this.vehicle = new DvrpVehicleImpl(vehicleSpecification, depotLink);
+ this.schedule = vehicle.getSchedule();
+ this.currentLink = vehicle.getStartLink();
+ this.currentTime = 0.0;
+ this.router = new DijkstraFactory().createPathCalculator(network,
+ new OnlyTimeDependentTravelDisutility(travelTime), travelTime);
+
+ this.lookup = Mockito.mock(DvrpVehicleLookup.class);
+ Mockito.when(this.lookup.lookupVehicle(Mockito.any())).thenReturn(vehicle);
+
+ DrtConfigGroup drtConfig = new DrtConfigGroup();
+ drtConfig.stopDuration = 30.0;
+ drtConfig.maxWaitTime = 600.0;
+
+ this.entryFactory = new VehicleDataEntryFactoryImpl();
+
+ this.timingUpdater = Mockito.mock(ScheduleTimingUpdater.class);
+ }
+
+ AcceptedDrtRequest createRequest() {
+ AcceptedDrtRequest request = Mockito.mock(AcceptedDrtRequest.class);
+ Mockito.when(request.getId()).thenReturn(Id.create("req_" + requestIndex++, Request.class));
+ return request;
+ }
+
+ DrtStayTask addWait(double duration) {
+ DrtStayTask task = taskFactory.createStayTask(vehicle, currentTime, currentTime + duration, currentLink);
+ schedule.addTask(task);
+
+ currentTime += duration;
+ return task;
+ }
+
+ DrtStayTask addStay(double duration) {
+ DrtStayTask task = taskFactory.createStayTask(vehicle, currentTime, currentTime + duration, currentLink);
+ schedule.addTask(task);
+
+ currentTime += duration;
+ return task;
+ }
+
+ DrtStayTask addFinalStay(double until) {
+ DrtStayTask task = taskFactory.createStayTask(vehicle, currentTime, Math.max(currentTime, until),
+ currentLink);
+ schedule.addTask(task);
+
+ currentTime = Math.max(currentTime, until);
+ return task;
+ }
+
+ DrtStopTask addStop(double duration) {
+ DrtStopTask task = taskFactory.createStopTask(vehicle, currentTime, currentTime + duration, currentLink);
+ schedule.addTask(task);
+ currentTime += duration;
+ return task;
+ }
+
+ DrtDriveTask addDrive(String destinationLinkId) {
+ Link destinationLink = network.getLinks().get(Id.createLinkId(destinationLinkId));
+
+ VrpPathWithTravelData path = VrpPaths.calcAndCreatePath(currentLink, destinationLink, currentTime, router,
+ travelTime);
+ DrtDriveTask driveTask = taskFactory.createDriveTask(vehicle, path, DrtDriveTask.TYPE);
+ schedule.addTask(driveTask);
+
+ currentTime = driveTask.getEndTime();
+ currentLink = destinationLink;
+
+ return driveTask;
+ }
+ }
+}
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PersonStuckPrebookingTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PersonStuckPrebookingTest.java
new file mode 100644
index 00000000000..92ff2b4ebc0
--- /dev/null
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PersonStuckPrebookingTest.java
@@ -0,0 +1,227 @@
+package org.matsim.contrib.drt.prebooking;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.matsim.api.core.v01.Id;
+import org.matsim.api.core.v01.population.Activity;
+import org.matsim.api.core.v01.population.Leg;
+import org.matsim.api.core.v01.population.Person;
+import org.matsim.api.core.v01.population.Plan;
+import org.matsim.api.core.v01.population.Population;
+import org.matsim.api.core.v01.population.PopulationFactory;
+import org.matsim.contrib.drt.prebooking.logic.ProbabilityBasedPrebookingLogic;
+import org.matsim.contrib.drt.run.DrtConfigGroup;
+import org.matsim.contrib.dvrp.optimizer.Request;
+import org.matsim.contrib.dvrp.passenger.PassengerDroppedOffEvent;
+import org.matsim.contrib.dvrp.passenger.PassengerDroppedOffEventHandler;
+import org.matsim.contrib.dvrp.passenger.PassengerRequest;
+import org.matsim.contrib.dvrp.passenger.PassengerRequestRejectedEvent;
+import org.matsim.contrib.dvrp.passenger.PassengerRequestRejectedEventHandler;
+import org.matsim.contrib.dvrp.passenger.PassengerRequestSubmittedEvent;
+import org.matsim.contrib.dvrp.passenger.PassengerRequestSubmittedEventHandler;
+import org.matsim.contrib.dvrp.passenger.PassengerRequestValidator;
+import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule;
+import org.matsim.core.controler.AbstractModule;
+import org.matsim.core.controler.Controler;
+import org.matsim.core.mobsim.framework.events.MobsimBeforeSimStepEvent;
+import org.matsim.core.mobsim.framework.listeners.MobsimBeforeSimStepListener;
+import org.matsim.testcases.MatsimTestUtils;
+
+/**
+ * @author Sebastian Hörl (sebhoerl) / IRT SystemX
+ */
+public class PersonStuckPrebookingTest {
+ @Rule
+ public MatsimTestUtils utils = new MatsimTestUtils();
+
+ @Test
+ public void baselineTest() {
+ /*
+ * Agent personA is performing three drt legs during the day. Agent personB does
+ * exactly the same in parallel, both prebook their requests.
+ */
+
+ PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) //
+ .addVehicle("vehicle", 1, 1) //
+ .configure(600.0, 1.3, 600.0, 60.0) //
+ .endTime(20000.0);
+
+ Controler controller = environment.build();
+
+ implementPopulation(controller.getScenario().getPopulation());
+ PrebookingTest.installPrebooking(controller, false);
+ ProbabilityBasedPrebookingLogic.install(controller,
+ DrtConfigGroup.getSingleModeDrtConfig(controller.getConfig()), 1.0, 20000.0);
+
+ EventCounter eventCounterA = EventCounter.install(controller, Id.createPersonId("personA"));
+ EventCounter eventCounterB = EventCounter.install(controller, Id.createPersonId("personB"));
+
+ controller.run();
+
+ assertEquals(3, eventCounterA.submittedCount);
+ assertEquals(3, eventCounterA.dropoffCount);
+
+ assertEquals(3, eventCounterB.submittedCount);
+ assertEquals(3, eventCounterB.dropoffCount);
+ }
+
+ @Test
+ public void cancelTest() {
+ /*
+ * Agent personA is performing three drt legs during the day. Agent personB does
+ * exactly the same in parallel, both prebook there requests.
+ *
+ * We cancel the first request of personA. We check that the other reservations
+ * are automatically rejected as soon as the person is stuck.
+ */
+
+ PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) //
+ .addVehicle("vehicle", 1, 1) //
+ .configure(600.0, 1.3, 600.0, 60.0) //
+ .endTime(20000.0);
+
+ Controler controller = environment.build();
+
+ implementPopulation(controller.getScenario().getPopulation());
+ PrebookingTest.installPrebooking(controller, false);
+ ProbabilityBasedPrebookingLogic.install(controller,
+ DrtConfigGroup.getSingleModeDrtConfig(controller.getConfig()), 1.0, 20000.0);
+
+ EventCounter eventCounterA = EventCounter.install(controller, Id.createPersonId("personA"));
+ EventCounter eventCounterB = EventCounter.install(controller, Id.createPersonId("personB"));
+
+ controller.addOverridingQSimModule(new AbstractDvrpModeQSimModule("drt") {
+ @Override
+ protected void configureQSim() {
+ addModalQSimComponentBinding().toProvider(modalProvider(getter -> {
+ PrebookingManager prebookingManager = getter.getModal(PrebookingManager.class);
+
+ return new MobsimBeforeSimStepListener() {
+ @Override
+ public void notifyMobsimBeforeSimStep(MobsimBeforeSimStepEvent e) {
+ if (e.getSimulationTime() == 500.0) {
+ prebookingManager.cancel(Id.create("drt_prebooked_0", Request.class));
+ }
+ }
+ };
+ }));
+
+ bindModal(PassengerRequestValidator.class).toProvider(modalProvider(getter -> {
+ return new PassengerRequestValidator() {
+ @Override
+ public Set validateRequest(PassengerRequest request) {
+ if (!request.getId().toString().contains("prebooked")) {
+ return Collections.singleton("anything");
+ }
+
+ return Collections.emptySet();
+ }
+ };
+ }));
+ }
+ });
+
+ controller.run();
+
+ assertEquals(4, eventCounterA.submittedCount);
+ assertEquals(4, eventCounterA.rejectedCount);
+ assertEquals(0, eventCounterA.dropoffCount);
+
+ assertEquals(3, eventCounterB.submittedCount);
+ assertEquals(0, eventCounterB.rejectedCount);
+ assertEquals(3, eventCounterB.dropoffCount);
+ }
+
+ private void implementPopulation(Population population) {
+ PopulationFactory populationFactory = population.getFactory();
+
+ for (String personId : Arrays.asList("personA", "personB")) {
+ Person person = populationFactory.createPerson(Id.createPersonId(personId));
+ population.addPerson(person);
+
+ Plan plan = populationFactory.createPlan();
+ person.addPlan(plan);
+
+ Activity firstActivity = populationFactory.createActivityFromLinkId("generic", Id.createLinkId("1:1-2:1"));
+ firstActivity.setEndTime(2000.0);
+ plan.addActivity(firstActivity);
+
+ // departure at 2000
+ Leg firstLeg = populationFactory.createLeg("drt");
+ plan.addLeg(firstLeg);
+
+ Activity secondActivity = populationFactory.createActivityFromLinkId("generic", Id.createLinkId("5:5-6:5"));
+ secondActivity.setEndTime(6000.0);
+ plan.addActivity(secondActivity);
+
+ // departure at 6000
+ Leg secondLeg = populationFactory.createLeg("drt");
+ plan.addLeg(secondLeg);
+
+ Activity thirdActivity = populationFactory.createActivityFromLinkId("generic", Id.createLinkId("1:1-2:1"));
+ thirdActivity.setEndTime(10000.0);
+ plan.addActivity(thirdActivity);
+
+ // departure at 10000
+ Leg thirdLeg = populationFactory.createLeg("drt");
+ plan.addLeg(thirdLeg);
+
+ Activity finalActivity = populationFactory.createActivityFromLinkId("generic", Id.createLinkId("5:5-6:5"));
+ plan.addActivity(finalActivity);
+ }
+ }
+
+ static private class EventCounter implements PassengerDroppedOffEventHandler, PassengerRequestSubmittedEventHandler,
+ PassengerRequestRejectedEventHandler {
+ private final Id personId;
+
+ private EventCounter(Id personId) {
+ this.personId = personId;
+ }
+
+ int dropoffCount = 0;
+ int submittedCount = 0;
+ int rejectedCount = 0;
+
+ @Override
+ public void handleEvent(PassengerDroppedOffEvent event) {
+ if (event.getPersonId().equals(personId)) {
+ dropoffCount++;
+ }
+ }
+
+ @Override
+ public void handleEvent(PassengerRequestSubmittedEvent event) {
+ if (event.getPersonId().equals(personId)) {
+ submittedCount++;
+ }
+ }
+
+ @Override
+ public void handleEvent(PassengerRequestRejectedEvent event) {
+ if (event.getPersonId().equals(personId)) {
+ rejectedCount++;
+ }
+ }
+
+ static EventCounter install(Controler controller, Id personId) {
+ EventCounter instance = new EventCounter(personId);
+
+ controller.addOverridingModule(new AbstractModule() {
+
+ @Override
+ public void install() {
+ addEventHandlerBinding().toInstance(instance);
+ }
+ });
+
+ return instance;
+ }
+ }
+}
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTest.java
index 63e4e1bae45..35d9a0ef62b 100644
--- a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTest.java
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTest.java
@@ -50,10 +50,19 @@ public void withoutPrebookedRequests() {
assertEquals(2146.0, taskInfo.get(2).endTime, 1e-3);
}
- private void installPrebooking(Controler controller) {
+ static PrebookingParams installPrebooking(Controler controller) {
+ return installPrebooking(controller, true);
+ }
+
+ static PrebookingParams installPrebooking(Controler controller, boolean installLogic) {
DrtConfigGroup drtConfig = DrtConfigGroup.getSingleModeDrtConfig(controller.getConfig());
drtConfig.addParameterSet(new PrebookingParams());
- AttributeBasedPrebookingLogic.install(controller, drtConfig);
+
+ if (installLogic) {
+ AttributeBasedPrebookingLogic.install(controller, drtConfig);
+ }
+
+ return drtConfig.getPrebookingParams().get();
}
@Test
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTestEnvironment.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTestEnvironment.java
index 52c959cd384..d8e5f1c8624 100644
--- a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTestEnvironment.java
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTestEnvironment.java
@@ -358,6 +358,8 @@ public class RequestInfo {
public double submissionTime = Double.NaN;
public double pickupTime = Double.NaN;
public double dropoffTime = Double.NaN;
+
+ public List submissionTimes = new LinkedList<>();
}
private Map requestInfo = new HashMap<>();
@@ -381,6 +383,9 @@ private class RequestListener implements DrtRequestSubmittedEventHandler, Passen
public void handleEvent(DrtRequestSubmittedEvent event) {
requestInfo.computeIfAbsent(event.getPersonId().toString(), id -> new RequestInfo()).submissionTime = event
.getTime();
+ requestInfo.computeIfAbsent(event.getPersonId().toString(), id -> new RequestInfo()).submissionTimes
+ .add(event.getTime());
+
}
@Override