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..2302893aa84 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
@@ -1,8 +1,10 @@
package org.matsim.contrib.drt.prebooking;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
@@ -10,16 +12,25 @@
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.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;
@@ -29,6 +40,7 @@
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,16 +59,19 @@
*
* @author Sebastian Hörl (sebhoerl), IRT SystemX
*/
-public class PrebookingManager implements MobsimEngine, AdvanceRequestProvider {
+public class PrebookingManager implements MobsimEngine, AdvanceRequestProvider, PassengerRequestScheduledEventHandler,
+ PassengerRequestRejectedEventHandler, PersonStuckEventHandler {
private final String mode;
private final Network network;
private final EventsManager eventsManager;
private final VrpOptimizer optimizer;
+ private final RequestUnscheduler unscheduler;
public PrebookingManager(String mode, Network network, PassengerRequestCreator requestCreator,
- VrpOptimizer optimizer, PassengerRequestValidator requestValidator, EventsManager eventsManager) {
+ VrpOptimizer optimizer, PassengerRequestValidator requestValidator, EventsManager eventsManager,
+ RequestUnscheduler unscheduler) {
this.network = network;
this.mode = mode;
this.requestCreator = requestCreator;
@@ -64,6 +79,7 @@ public PrebookingManager(String mode, Network network, PassengerRequestCreator r
this.requestAttribute = PREBOOKED_REQUEST_PREFIX + ":" + mode;
this.requestValidator = requestValidator;
this.eventsManager = eventsManager;
+ this.unscheduler = unscheduler;
}
// Functionality for ID management
@@ -180,30 +196,204 @@ public PassengerRequest retrieveRequest(MobsimAgent agent, Leg leg) {
// Housekeeping of requests
private IdMap requests = new IdMap<>(Request.class);
+ private IdSet unscheduleUponVehicleAssignment = new IdSet<>(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);
+ }
+
+ @Override
+ public void handleEvent(PassengerRequestScheduledEvent event) {
+ 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(event.getTime(), event.getVehicleId(), event.getRequestId());
+ unscheduleUponVehicleAssignment.remove(event.getRequestId());
+ }
+ }
+
+ // Functionality for canceling requests
+
+ private static final String CANCEL_REASON = "canceled";
+ private final List cancelQueue = new LinkedList<>();
+
+ private void processCanceledRequests(double now) {
+ synchronized (cancelQueue) {
+ 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);
+
+ // 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;
+ }
+
+ eventsManager.processEvent(new PassengerRequestRejectedEvent(now, mode, requestId,
+ item.request.getPassengerId(), reason));
+ }
+ }
+
+ cancelQueue.clear();
+ }
+ }
+
+ public boolean cancel(Leg leg) {
+ return cancel(leg, null);
+ }
+
+ public boolean cancel(Leg leg, String reason) {
+ Id requestId = getRequestId(leg);
+
+ if (requestId != null) {
+ return cancel(requestId);
+ }
+
+ return false;
+ }
+
+ public boolean cancel(Id requestId) {
+ return cancel(requestId, null);
+ }
+
+ public boolean cancel(Id requestId, String reason) {
+ RequestItem item = requests.get(requestId);
+
+ if (item != null) {
+ if (!item.onboard) {
+ synchronized (cancelQueue) {
+ cancelQueue.add(new CancelItem(requestId, reason));
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private record CancelItem(Id requestId, String reason) {
+ }
+
+ // Functionality for abandoning requests
+
+ private static final String ABANDONED_REASON = "abandoned by vehicle";
+ private final List> abandonQueue = new LinkedList<>();
+
+ void abandon(Id requestId) {
+ synchronized (abandonQueue) {
+ RequestItem item = Objects.requireNonNull(requests.get(requestId));
+ Verify.verify(!item.onboard, "Cannot abandon request, person has already entered");
+ abandonQueue.add(requestId);
+ }
+ }
+
+ private void processAbandonedRequests(double now) {
+ synchronized (abandonQueue) {
+ for (Id requestId : abandonQueue) {
+ RequestItem item = Objects.requireNonNull(requests.remove(requestId));
+ Verify.verify(!item.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());
+ }
+
+ eventsManager.processEvent(new PassengerRequestRejectedEvent(now, mode, item.request.getId(),
+ item.request.getPassengerId(), ABANDONED_REASON));
+ }
+
+ abandonQueue.clear();
+ }
+ }
+
+ // React to external rejections or stuck agents
+
+ @Override
+ public void handleEvent(PassengerRequestRejectedEvent event) {
+ RequestItem item = requests.remove(event.getRequestId());
+
+ if (item != null) {
+ Verify.verify(!item.onboard);
+
+ // unschedule if already scheduled
+ if (item.vehicleId != null) {
+ unscheduler.unscheduleRequest(event.getTime(), item.vehicleId, event.getRequestId());
+ } else {
+ unscheduleUponVehicleAssignment.add(event.getRequestId());
+ }
+ }
+ }
+
+ @Override
+ public void handleEvent(PersonStuckEvent event) {
+ Iterator iterator = requests.values().iterator();
+
+ while (iterator.hasNext()) {
+ RequestItem item = iterator.next();
+
+ if (item.request.getPassengerId().equals(event.getPersonId())) {
+ cancel(item.request.getId());
+ }
+
+ queue.clear();
+ }
+ }
+
// Engine code
@Override
public void doSimStep(double now) {
+ processAbandonedRequests(now);
+ processCanceledRequests(now);
processQueue(now);
}
@Override
public void onPrepareSim() {
+ eventsManager.addHandler(this);
}
@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..0a24ed1f7fa 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.PrebookingParams.UnschedulingMode;
+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.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,10 @@ 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);
- return new PrebookingManager(getMode(), network, requestCreator, optimizer, requestValidator,
- eventsManager);
+ return new PrebookingManager(getMode(), network, requestCreator, optimizer, requestValidator, eventsManager,
+ requestUnscheduler);
})).in(Singleton.class);
addModalQSimComponentBinding().to(modalKey(PrebookingManager.class));
@@ -52,5 +71,36 @@ 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);
+
+ if (prebookingParams.unschedulingMode.equals(UnschedulingMode.StopBased)) {
+ bindModal(RequestUnscheduler.class).to(modalKey(SimpleRequestUnscheduler.class));
+ } else if (prebookingParams.unschedulingMode.equals(UnschedulingMode.Routing)) {
+ bindModal(RequestUnscheduler.class).to(modalKey(ComplexRequestUnscheduler.class));
+ } else {
+ throw new IllegalStateException();
+ }
}
}
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..3595128cb0b
--- /dev/null
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/ComplexRequestUnscheduler.java
@@ -0,0 +1,318 @@
+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(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 = pickupStop != null && 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..8683837694e
--- /dev/null
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/ComplexUnschedulerTest.java
@@ -0,0 +1,633 @@
+package org.matsim.contrib.drt.prebooking;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+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.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.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.addStay(1000.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());
+ assertTrue(schedule.getTasks().get(0) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(1) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(2) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(3) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(4) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(5) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(6) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(7) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(8) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(9) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(10) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(11) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(12) instanceof DrtStayTask);
+
+ 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.addStay(1000.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());
+ assertTrue(schedule.getTasks().get(0) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(1) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(2) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(3) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(4) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(5) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(6) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(7) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(8) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(9) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(10) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(11) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(12) instanceof DrtStayTask);
+
+ 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.addStay(1000.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());
+ assertTrue(schedule.getTasks().get(0) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(1) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(2) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(3) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(4) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(5) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(6) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(7) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(8) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(9) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(10) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(11) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(12) instanceof DrtStayTask);
+
+ 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.addStay(1000.0);
+
+ schedule.nextTask();
+ 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(15, schedule.getTaskCount());
+ assertTrue(schedule.getTasks().get(0) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(1) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(2) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(3) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(4) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(5) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(6) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(7) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(8) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(9) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(10) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(11) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(12) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(13) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(14) instanceof DrtStayTask);
+
+ 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.addStay(1000.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());
+ assertTrue(schedule.getTasks().get(0) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(1) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(2) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(3) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(4) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(5) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(6) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(7) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(8) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(9) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(10) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(11) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(12) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(13) instanceof DrtStayTask);
+
+ 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.addStay(1000.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());
+ assertTrue(schedule.getTasks().get(0) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(1) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(2) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(3) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(4) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(5) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(6) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(7) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(8) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(9) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(10) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(11) instanceof DrtStopTask);
+ assertTrue(schedule.getTasks().get(12) instanceof DrtStayTask);
+
+ 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.addStay(1000.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());
+ assertTrue(schedule.getTasks().get(0) instanceof DrtStayTask);
+ assertTrue(schedule.getTasks().get(1) instanceof DrtStayTask);
+
+ 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.addStay(1000.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());
+ assertTrue(schedule.getTasks().get(0) instanceof DrtDriveTask);
+ assertTrue(schedule.getTasks().get(1) instanceof DrtStayTask);
+
+ 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());
+ }
+
+ 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;
+ }
+
+ 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/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
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/StuckPrebookingTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/StuckPrebookingTest.java
new file mode 100644
index 00000000000..26ce3222f21
--- /dev/null
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/StuckPrebookingTest.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 StuckPrebookingTest {
+ @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;
+ }
+ }
+}