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-SNAPSHOTtest
+
+
+ org.mockito
+ mockito-core
+
diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/BasicDrtEstimator.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/BasicDrtEstimator.java
index 3d7555020e1..d446d5f34bc 100644
--- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/BasicDrtEstimator.java
+++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/BasicDrtEstimator.java
@@ -5,7 +5,9 @@
import org.apache.commons.math3.stat.regression.SimpleRegression;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.events.PersonMoneyEvent;
+import org.matsim.api.core.v01.population.Person;
import org.matsim.contrib.drt.analysis.DrtEventSequenceCollector;
import org.matsim.contrib.drt.extension.estimator.run.DrtEstimatorConfigGroup;
import org.matsim.contrib.drt.fare.DrtFareParams;
@@ -16,6 +18,7 @@
import org.matsim.core.controler.listener.IterationEndsListener;
import org.matsim.core.utils.misc.OptionalTime;
+import java.util.Map;
import java.util.SplittableRandom;
/**
@@ -64,19 +67,21 @@ public void notifyIterationEnds(IterationEndsEvent event) {
for (DrtEventSequenceCollector.EventSequence seq : collector.getPerformedRequestSequences().values()) {
- if (seq.getPickedUp().isPresent() && seq.getDroppedOff().isPresent()) {
+ Map, DrtEventSequenceCollector.EventSequence.PersonEvents> personEvents = seq.getPersonEvents();
+ for (Map.Entry, DrtEventSequenceCollector.EventSequence.PersonEvents> entry : personEvents.entrySet()) {
+ if (entry.getValue().getPickedUp().isPresent() && entry.getValue().getDroppedOff().isPresent()) {
+ double waitTime = entry.getValue().getPickedUp().get().getTime() - seq.getSubmitted().getTime();
+ est.waitTime.addValue(waitTime);
- double waitTime = seq.getPickedUp().get().getTime() - seq.getSubmitted().getTime();
- est.waitTime.addValue(waitTime);
+ double unsharedTime = seq.getSubmitted().getUnsharedRideTime();
+ double travelTime = entry.getValue().getDroppedOff().get().getTime() - entry.getValue().getPickedUp().get().getTime();
- double unsharedTime = seq.getSubmitted().getUnsharedRideTime();
- double travelTime = seq.getDroppedOff().get().getTime() - seq.getPickedUp().get().getTime();
+ est.detour.addValue(travelTime / unsharedTime);
- est.detour.addValue(travelTime / unsharedTime);
-
- double fare = seq.getDrtFares().stream().mapToDouble(PersonMoneyEvent::getAmount).sum();
- est.fare.addData(seq.getSubmitted().getUnsharedRideDistance(), fare);
- n++;
+ double fare = seq.getDrtFares().stream().mapToDouble(PersonMoneyEvent::getAmount).sum();
+ est.fare.addData(seq.getSubmitted().getUnsharedRideDistance(), fare);
+ n++;
+ }
}
}
diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/DrtEstimateAnalyzer.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/DrtEstimateAnalyzer.java
index 2d87f7ea7b8..d095a0037bd 100644
--- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/DrtEstimateAnalyzer.java
+++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/estimator/DrtEstimateAnalyzer.java
@@ -5,7 +5,9 @@
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.events.PersonMoneyEvent;
+import org.matsim.api.core.v01.population.Person;
import org.matsim.contrib.drt.analysis.DrtEventSequenceCollector;
import org.matsim.contrib.drt.extension.estimator.run.DrtEstimatorConfigGroup;
import org.matsim.contrib.drt.routing.DrtRoute;
@@ -23,6 +25,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
+import java.util.Map;
/**
* Analyzes and outputs drt estimates errors metrics based on daily requests.
@@ -96,22 +99,25 @@ private Iterable calcMetrics(int iteration) {
DescriptiveStatistics fare = new DescriptiveStatistics();
for (DrtEventSequenceCollector.EventSequence seq : collector.getPerformedRequestSequences().values()) {
- if (seq.getPickedUp().isPresent() && seq.getDroppedOff().isPresent()) {
+ Map, DrtEventSequenceCollector.EventSequence.PersonEvents> personEvents = seq.getPersonEvents();
+ for (Map.Entry, DrtEventSequenceCollector.EventSequence.PersonEvents> entry : personEvents.entrySet()) {
+ if (entry.getValue().getPickedUp().isPresent() && entry.getValue().getDroppedOff().isPresent()) {
- // many attributes are not filled, when using the constructor
- DrtRoute route = new DrtRoute(seq.getSubmitted().getFromLinkId(), seq.getSubmitted().getToLinkId());
- route.setDirectRideTime(seq.getSubmitted().getUnsharedRideTime());
- route.setDistance(seq.getSubmitted().getUnsharedRideDistance());
+ // many attributes are not filled, when using the constructor
+ DrtRoute route = new DrtRoute(seq.getSubmitted().getFromLinkId(), seq.getSubmitted().getToLinkId());
+ route.setDirectRideTime(seq.getSubmitted().getUnsharedRideTime());
+ route.setDistance(seq.getSubmitted().getUnsharedRideDistance());
- double valWaitTime = seq.getPickedUp().get().getTime() - seq.getSubmitted().getTime();
- double valTravelTime = seq.getDroppedOff().get().getTime() - seq.getPickedUp().get().getTime();
- double valFare = seq.getDrtFares().stream().mapToDouble(PersonMoneyEvent::getAmount).sum();
+ double valWaitTime = entry.getValue().getPickedUp().get().getTime() - seq.getSubmitted().getTime();
+ double valTravelTime = entry.getValue().getDroppedOff().get().getTime() - entry.getValue().getPickedUp().get().getTime();
+ double valFare = seq.getDrtFares().stream().mapToDouble(PersonMoneyEvent::getAmount).sum();
- DrtEstimator.Estimate estimate = estimator.estimate(route, OptionalTime.defined(seq.getSubmitted().getTime()));
+ DrtEstimator.Estimate estimate = estimator.estimate(route, OptionalTime.defined(seq.getSubmitted().getTime()));
- waitTime.addValue(Math.abs(estimate.waitingTime() - valWaitTime));
- travelTime.addValue(Math.abs(estimate.travelTime() - valTravelTime));
- fare.addValue(Math.abs(estimate.fare() - valFare));
+ waitTime.addValue(Math.abs(estimate.waitingTime() - valWaitTime));
+ travelTime.addValue(Math.abs(estimate.travelTime() - valTravelTime));
+ fare.addValue(Math.abs(estimate.fare() - valFare));
+ }
}
}
diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/charging/ChargingBreakActivity.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/charging/ChargingBreakActivity.java
index f535c15d5ca..1a008c63cdd 100644
--- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/charging/ChargingBreakActivity.java
+++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/eshifts/charging/ChargingBreakActivity.java
@@ -1,6 +1,7 @@
package org.matsim.contrib.drt.extension.operations.eshifts.charging;
import org.matsim.api.core.v01.Id;
+import org.matsim.api.core.v01.Identifiable;
import org.matsim.api.core.v01.population.Person;
import org.matsim.contrib.drt.passenger.AcceptedDrtRequest;
import org.matsim.contrib.dvrp.optimizer.Request;
@@ -15,7 +16,10 @@
import org.matsim.contrib.drt.extension.operations.shifts.schedule.ShiftBreakTask;
import org.matsim.core.mobsim.framework.MobsimPassengerAgent;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
/**
* based on {@link DrtStopActivity} and {@link ChargingActivity}
@@ -50,7 +54,7 @@ public ChargingBreakActivity(ChargingTask chargingTask, PassengerHandler passeng
protected boolean isLastStep(double now) {
if(chargingDelegate.getEndTime() < now && now >= endTime) {
for (var request : pickupRequests.values()) {
- if (passengerHandler.tryPickUpPassenger(this, driver, request.getId(), now)) {
+ if (passengerHandler.tryPickUpPassengers(this, driver, request.getId(), now)) {
passengersPickedUp++;
}
}
@@ -64,13 +68,13 @@ public void finalizeAction(double now) {
}
@Override
- public void notifyPassengerIsReadyForDeparture(MobsimPassengerAgent passenger, double now) {
+ public void notifyPassengersAreReadyForDeparture(List passengers, double now) {
if (!isLastStep(now)) {
return;// pick up only at the end of stop activity
}
- var request = getRequestForPassenger(passenger.getId());
- if (passengerHandler.tryPickUpPassenger(this, driver, request.getId(), now)) {
+ var request = getRequestForPassengers(passengers.stream().map(Identifiable::getId).toList());
+ if (passengerHandler.tryPickUpPassengers(this, driver, request.getId(), now)) {
passengersPickedUp++;
} else {
throw new IllegalStateException("The passenger is not on the link or not available for departure!");
@@ -81,7 +85,7 @@ public void notifyPassengerIsReadyForDeparture(MobsimPassengerAgent passenger, d
protected void beforeFirstStep(double now) {
// TODO probably we should simulate it more accurately (passenger by passenger, not all at once...)
for (var request : dropoffRequests.values()) {
- passengerHandler.dropOffPassenger(driver, request.getId(), now);
+ passengerHandler.dropOffPassengers(driver, request.getId(), now);
}
}
@@ -95,9 +99,9 @@ protected void simStep(double now) {
chargingDelegate.doSimStep(now);
}
- private AcceptedDrtRequest getRequestForPassenger(Id passengerId) {
+ private AcceptedDrtRequest getRequestForPassengers(List> passengerIds) {
return pickupRequests.values().stream()
- .filter(r -> passengerId.equals(r.getPassengerId()))
+ .filter(r -> r.getPassengerIds().size() == passengerIds.size() && r.getPassengerIds().containsAll(passengerIds))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("I am waiting for different passengers!"));
}
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-extensions/src/main/java/org/matsim/contrib/drt/extension/preplanned/optimizer/PreplannedDrtOptimizer.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/preplanned/optimizer/PreplannedDrtOptimizer.java
index 0103e5bb66a..5dd5f1c8bf7 100644
--- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/preplanned/optimizer/PreplannedDrtOptimizer.java
+++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/preplanned/optimizer/PreplannedDrtOptimizer.java
@@ -22,9 +22,7 @@
import static org.matsim.contrib.drt.schedule.DrtTaskBaseType.STAY;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Queue;
+import java.util.*;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
@@ -118,7 +116,7 @@ public void requestSubmitted(Request request) {
"Pre-planned request (%s) not assigned to any vehicle and not marked as unassigned.",
preplannedRequest);
eventsManager.processEvent(new PassengerRequestRejectedEvent(timer.getTimeOfDay(), mode, request.getId(),
- drtRequest.getPassengerId(), "Marked as unassigned"));
+ drtRequest.getPassengerIds(), "Marked as unassigned"));
return;
}
@@ -130,7 +128,7 @@ public void requestSubmitted(Request request) {
//TODO in the current implementation we do not know the scheduled pickup and dropoff times
eventsManager.processEvent(
new PassengerRequestScheduledEvent(timer.getTimeOfDay(), drtRequest.getMode(), drtRequest.getId(),
- drtRequest.getPassengerId(), vehicleId, Double.NaN, Double.NaN));
+ drtRequest.getPassengerIds(), vehicleId, Double.NaN, Double.NaN));
}
@Override
@@ -203,7 +201,7 @@ public record PreplannedSchedules(Map> pre
Map unassignedRequests) {
}
- public record PreplannedRequestKey(Id passengerId, Id fromLinkId, Id toLinkId) {
+ public record PreplannedRequestKey(Set> passengerIds, Id fromLinkId, Id toLinkId) {
}
// also input to the external optimiser
@@ -212,7 +210,7 @@ public record PreplannedRequest(PreplannedRequestKey key, double earliestStartTi
}
static PreplannedRequest createFromRequest(DrtRequest request) {
- return new PreplannedRequest(new PreplannedRequestKey(request.getPassengerId(), request.getFromLink().getId(),
+ return new PreplannedRequest(new PreplannedRequestKey(Set.copyOf(request.getPassengerIds()), request.getFromLink().getId(),
request.getToLink().getId()), request.getEarliestStartTime(), request.getLatestStartTime(),
request.getLatestArrivalTime());
}
diff --git a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/edrt/run/RunEDrtScenarioIT.java b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/edrt/run/RunEDrtScenarioIT.java
index 1fb77797343..3a3471007fd 100644
--- a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/edrt/run/RunEDrtScenarioIT.java
+++ b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/edrt/run/RunEDrtScenarioIT.java
@@ -18,6 +18,8 @@
package org.matsim.contrib.drt.extension.edrt.run;
+import static org.junit.Assert.assertEquals;
+
import java.net.URL;
import org.junit.Test;
@@ -25,10 +27,15 @@
import org.matsim.contrib.drt.prebooking.logic.ProbabilityBasedPrebookingLogic;
import org.matsim.contrib.drt.run.DrtConfigGroup;
import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup;
+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.run.DvrpConfigGroup;
import org.matsim.contrib.ev.EvConfigGroup;
import org.matsim.core.config.Config;
import org.matsim.core.config.ConfigUtils;
+import org.matsim.core.controler.AbstractModule;
import org.matsim.core.controler.Controler;
import org.matsim.core.utils.io.IOUtils;
import org.matsim.examples.ExamplesUtils;
@@ -57,6 +64,50 @@ public void testWithPrebooking() {
Controler controller = RunEDrtScenario.createControler(config, false);
ProbabilityBasedPrebookingLogic.install(controller, drtConfig, 0.5, 4.0 * 3600.0);
+ PrebookingTracker tracker = new PrebookingTracker();
+ tracker.install(controller);
+
controller.run();
+
+ assertEquals(74, tracker.immediateScheduled);
+ assertEquals(198, tracker.prebookedScheduled);
+ assertEquals(116, tracker.immediateRejected);
+ assertEquals(7, tracker.prebookedRejected);
+ }
+
+ static private class PrebookingTracker implements PassengerRequestRejectedEventHandler, PassengerRequestScheduledEventHandler {
+ int immediateScheduled = 0;
+ int prebookedScheduled = 0;
+ int immediateRejected = 0;
+ int prebookedRejected = 0;
+
+ @Override
+ public void handleEvent(PassengerRequestScheduledEvent event) {
+ if (event.getRequestId().toString().contains("prebooked")) {
+ prebookedScheduled++;
+ } else {
+ immediateScheduled++;
+ }
+ }
+
+ @Override
+ public void handleEvent(PassengerRequestRejectedEvent event) {
+ if (event.getRequestId().toString().contains("prebooked")) {
+ prebookedRejected++;
+ } else {
+ immediateRejected++;
+ }
+ }
+
+ void install(Controler controller) {
+ PrebookingTracker thisTracker = this;
+
+ controller.addOverridingModule(new AbstractModule() {
+ @Override
+ public void install() {
+ addEventHandlerBinding().toInstance(thisTracker);
+ }
+ });
+ }
}
}
diff --git a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/preplanned/optimizer/RunPreplannedDrtExampleIT.java b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/preplanned/optimizer/RunPreplannedDrtExampleIT.java
index 11bd8d5328b..cd0b743f9f2 100644
--- a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/preplanned/optimizer/RunPreplannedDrtExampleIT.java
+++ b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/preplanned/optimizer/RunPreplannedDrtExampleIT.java
@@ -25,11 +25,7 @@
import static org.matsim.contrib.drt.extension.preplanned.optimizer.PreplannedDrtOptimizer.PreplannedStop;
import java.net.URL;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Queue;
-import java.util.Set;
+import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -53,34 +49,34 @@ public void testRun() {
// create preplanned requests (they will be mapped to drt requests created during simulation)
var preplannedRequest_0 = new PreplannedRequest(
- new PreplannedRequestKey(Id.createPersonId("passenger_0"), Id.createLinkId("114"),
+ new PreplannedRequestKey(Set.of(Id.createPersonId("passenger_0")), Id.createLinkId("114"),
Id.createLinkId("349")), 0.0, 900.0, 844.4);
var preplannedRequest_1 = new PreplannedRequest(
- new PreplannedRequestKey(Id.createPersonId("passenger_1"), Id.createLinkId("144"),
+ new PreplannedRequestKey(Set.of(Id.createPersonId("passenger_1")), Id.createLinkId("144"),
Id.createLinkId("437")), 300.0, 1200.0, 1011.8);
var preplannedRequest_2 = new PreplannedRequest(
- new PreplannedRequestKey(Id.createPersonId("passenger_2"), Id.createLinkId("223"),
+ new PreplannedRequestKey(Set.of(Id.createPersonId("passenger_2")), Id.createLinkId("223"),
Id.createLinkId("347")), 600.0, 1500.0, 1393.7);
var preplannedRequest_3 = new PreplannedRequest(
- new PreplannedRequestKey(Id.createPersonId("passenger_3"), Id.createLinkId("234"),
+ new PreplannedRequestKey(Set.of(Id.createPersonId("passenger_3")), Id.createLinkId("234"),
Id.createLinkId("119")), 900.0, 1800.0, 1825.0);
var preplannedRequest_4 = new PreplannedRequest(
- new PreplannedRequestKey(Id.createPersonId("passenger_4"), Id.createLinkId("314"),
+ new PreplannedRequestKey(Set.of(Id.createPersonId("passenger_4")), Id.createLinkId("314"),
Id.createLinkId("260")), 1200.0, 2100.0, 1997.6);
var preplannedRequest_5 = new PreplannedRequest(
- new PreplannedRequestKey(Id.createPersonId("passenger_5"), Id.createLinkId("333"),
+ new PreplannedRequestKey(Set.of(Id.createPersonId("passenger_5")), Id.createLinkId("333"),
Id.createLinkId("438")), 1500.0, 2400.0, 2349.6);
var preplannedRequest_6 = new PreplannedRequest(
- new PreplannedRequestKey(Id.createPersonId("passenger_6"), Id.createLinkId("325"),
+ new PreplannedRequestKey(Set.of(Id.createPersonId("passenger_6")), Id.createLinkId("325"),
Id.createLinkId("111")), 1800.0, 2700.0, 2600.2);
var preplannedRequest_7 = new PreplannedRequest(
- new PreplannedRequestKey(Id.createPersonId("passenger_7"), Id.createLinkId("412"),
+ new PreplannedRequestKey(Set.of(Id.createPersonId("passenger_7")), Id.createLinkId("412"),
Id.createLinkId("318")), 2100.0, 3000.0, 2989.9);
var preplannedRequest_8 = new PreplannedRequest(
- new PreplannedRequestKey(Id.createPersonId("passenger_8"), Id.createLinkId("455"),
+ new PreplannedRequestKey(Set.of(Id.createPersonId("passenger_8")), Id.createLinkId("455"),
Id.createLinkId("236")), 2400.0, 3300.0, 3110.5);
var preplannedRequest_9 = new PreplannedRequest(
- new PreplannedRequestKey(Id.createPersonId("passenger_9"), Id.createLinkId("139"),
+ new PreplannedRequestKey(Set.of(Id.createPersonId("passenger_9")), Id.createLinkId("139"),
Id.createLinkId("330")), 2700.0, 3600.0, 3410.5);
var preplannedRequests = List.of(preplannedRequest_0, preplannedRequest_1, preplannedRequest_2,
preplannedRequest_3, preplannedRequest_4, preplannedRequest_5, preplannedRequest_6);
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..1f33b8cf008 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
@@ -35,7 +35,6 @@
import org.jfree.data.xy.XYSeries;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
-import org.matsim.api.core.v01.events.PersonDepartureEvent;
import org.matsim.api.core.v01.events.PersonMoneyEvent;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
@@ -126,31 +125,37 @@ private record DrtLeg(Id request, double submissionTime, double departu
double arrivalTime, double fare, double earliestDepartureTime, double latestDepartureTime, double latestArrivalTime) {
}
- private static DrtLeg newDrtLeg(EventSequence sequence, Function, ? extends Link> linkProvider) {
+ private static List newDrtLegs(EventSequence sequence, Function, ? extends Link> linkProvider) {
Preconditions.checkArgument(sequence.isCompleted());
+ List legs = new ArrayList<>();
DrtRequestSubmittedEvent submittedEvent = sequence.getSubmitted();
- PersonDepartureEvent departureEvent = sequence.getDeparture().get();
- PassengerPickedUpEvent pickedUpEvent = sequence.getPickedUp().get();
+
+ Map, EventSequence.PersonEvents> personEvents = sequence.getPersonEvents();
+
var request = submittedEvent.getRequestId();
var submissionTime = submittedEvent.getTime();
- var departureTime = departureEvent.getTime();
- var person = submittedEvent.getPersonId();
- var vehicle = pickedUpEvent.getVehicleId();
var fromLinkId = submittedEvent.getFromLinkId();
var fromCoord = linkProvider.apply(fromLinkId).getToNode().getCoord();
var toLinkId = submittedEvent.getToLinkId();
var toCoord = linkProvider.apply(toLinkId).getToNode().getCoord();
- var waitTime = pickedUpEvent.getTime() - departureEvent.getTime();
var unsharedDistanceEstimate_m = submittedEvent.getUnsharedRideDistance();
var unsharedTimeEstimate_m = submittedEvent.getUnsharedRideTime();
- var arrivalTime = sequence.getDroppedOff().get().getTime();
// PersonMoneyEvent has negative amount because the agent's money is reduced -> for the operator that is a positive amount
var fare = sequence.getDrtFares().stream().mapToDouble(PersonMoneyEvent::getAmount).sum();
var earliestDepartureTime = sequence.getSubmitted().getEarliestDepartureTime();
var latestDepartureTime = sequence.getSubmitted().getLatestPickupTime();
var latestArrivalTime = sequence.getSubmitted().getLatestDropoffTime();
- return new DrtLeg(request, submissionTime, departureTime, person, vehicle, fromLinkId, fromCoord, toLinkId, toCoord, waitTime, unsharedDistanceEstimate_m,
- unsharedTimeEstimate_m, arrivalTime, fare, earliestDepartureTime, latestDepartureTime, latestArrivalTime);
+
+ for (Id person : submittedEvent.getPersonIds()) {
+ var departureTime = personEvents.get(person).getDeparture().get().getTime();
+ PassengerPickedUpEvent pickedUp = personEvents.get(person).getPickedUp().get();
+ var vehicle = pickedUp.getVehicleId();
+ var waitTime = pickedUp.getTime() - personEvents.get(person).getDeparture().get().getTime();
+ var arrivalTime = personEvents.get(person).getDroppedOff().get().getTime();
+ legs.add(new DrtLeg(request, submissionTime, departureTime, person, vehicle, fromLinkId, fromCoord, toLinkId, toCoord, waitTime, unsharedDistanceEstimate_m,
+ unsharedTimeEstimate_m, arrivalTime, fare, earliestDepartureTime, latestDepartureTime, latestArrivalTime));
+ }
+ return legs;
}
@Override
@@ -164,34 +169,37 @@ public void notifyIterationEnds(IterationEndsEvent event) {
.values()
.stream()
.filter(EventSequence::isCompleted)
- .map(sequence -> newDrtLeg(sequence, network.getLinks()::get))
+ .map(sequence -> newDrtLegs(sequence, network.getLinks()::get))
+ .flatMap(Collection::stream)
.sorted(Comparator.comparing(leg -> leg.departureTime))
.collect(toList());
List rejectionEvents = drtEventSequenceCollector.getRejectedRequestSequences()
- .values()
- .stream()
- .map(eventSequence -> eventSequence.getRejected().get())
- .sorted(Comparator.comparing(rejectionEvent -> rejectionEvent.getTime()))
- .collect(toList());
+ .values()
+ .stream()
+ .map(eventSequence -> eventSequence.getRejected().get())
+ .sorted(Comparator.comparing(rejectionEvent -> rejectionEvent.getTime()))
+ .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", "personIds", "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.getPersonIds().stream().map(Object::toString).collect(Collectors.joining("-")) + "",//
submission.getRequestId() + "",//
submission.getFromLinkId() + "",//
submission.getToLinkId() + "",//
fromCoord.getX() + "",//
fromCoord.getY() + "",//
toCoord.getX() + "",//
- toCoord.getY() + "");
+ toCoord.getY() + "",//
+ rejection.getCause());
});
- double rejectionRate = (double)drtEventSequenceCollector.getRejectedRequestSequences().size()
+ double rejectionRate = (double) drtEventSequenceCollector.getRejectedRequestSequences().size()
/ drtEventSequenceCollector.getRequestSubmissions().size();
String legsSummarize = summarizeLegs(legs, drtVehicleStats.getTravelDistances(), drtEventSequenceCollector.getDrtFarePersonMoneyEvents(),
delimiter);
@@ -212,8 +220,8 @@ public void notifyIterationEnds(IterationEndsEvent event) {
String occStats = summarizeDetailedOccupancyStats(drtVehicleStats.getVehicleStates(), delimiter, maxcap);
writeIterationVehicleStats(vehStats, occStats, event.getIteration());
if (drtCfg.plotDetailedCustomerStats) {
- String header = String.join(delimiter, //
- "submissionTime", //
+ String header = String.join(delimiter, //
+ "submissionTime", //
"departureTime",//
"personId",//
"requestId",//
@@ -259,7 +267,7 @@ public void notifyIterationEnds(IterationEndsEvent event) {
writeVehicleDistances(drtVehicleStats.getVehicleStates(), filename(event, "vehicleDistanceStats", ".csv"), delimiter);
analyseDetours(network, legs, drtVehicleStats.getTravelDistances(), drtCfg, filename(event, "drt_detours"), createGraphs, delimiter);
analyseWaitTimes(filename(event, "waitStats"), legs, 1800, createGraphs, delimiter);
- analyseRejections(filename(event,"drt_rejections_perTimeBin"), rejectionEvents,1800, createGraphs, delimiter);
+ analyseRejections(filename(event, "drt_rejections_perTimeBin"), rejectionEvents, 1800, createGraphs, delimiter);
analyseConstraints(filename(event, "constraints"), legs, createGraphs);
double endTime = qSimCfg.getEndTime()
@@ -341,7 +349,7 @@ private void writeIterationPassengerStats(String summarizeLegs, int it) {
private void writeIterationVehicleStats(String summarizeVehicles, String vehOcc, int it) {
try (var bw = getAppendingBufferedWriter("drt_vehicle_stats", ".csv")) {
if (!vheaderWritten) {
- bw.write(line("runId", "iteration", "vehicles", "totalServiceDuration", "totalDistance", "totalEmptyDistance", "emptyRatio", "totalPassengerDistanceTraveled",
+ bw.write(line("runId", "iteration", "vehicles", "totalServiceDuration", "totalDistance", "totalEmptyDistance", "emptyRatio", "totalPassengerDistanceTraveled",
"averageDrivenDistance", "averageEmptyDistance", "averagePassengerDistanceTraveled", "d_p/d_t", "l_det",
"minShareIdleVehicles", "minCountIdleVehicles"));
}
@@ -373,11 +381,17 @@ private void writeAndPlotWaitTimeEstimateComparison(Collection pe
bw.append(line("RequestId", "actualWaitTime", "estimatedWaitTime", "deviate"));
for (EventSequence seq : performedRequestEventSequences) {
- if (seq.getPickedUp().isPresent()) {
- double actualWaitTime = seq.getPickedUp().get().getTime() - seq.getDeparture().get().getTime();
- double estimatedWaitTime = seq.getScheduled().get().getPickupTime() - seq.getSubmitted().getEarliestDepartureTime();
- bw.append(line(seq.getSubmitted().getRequestId(), actualWaitTime, estimatedWaitTime, actualWaitTime - estimatedWaitTime));
- times.add(actualWaitTime, estimatedWaitTime);
+ List> personIds = seq.getSubmitted().getPersonIds();
+ for (Id person : personIds) {
+ if(seq.getPersonEvents().containsKey(person)) {
+ EventSequence.PersonEvents personEvents = seq.getPersonEvents().get(person);
+ if(personEvents.getPickedUp().isPresent() && personEvents.getDeparture().isPresent()) {
+ double actualWaitTime = personEvents.getPickedUp().get().getTime() - personEvents.getDeparture().get().getTime();
+ double estimatedWaitTime = seq.getScheduled().get().getPickupTime() - seq.getSubmitted().getEarliestDepartureTime();
+ bw.append(line(seq.getSubmitted().getRequestId(), actualWaitTime, estimatedWaitTime, actualWaitTime - estimatedWaitTime));
+ times.add(actualWaitTime, estimatedWaitTime);
+ }
+ }
}
}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtEventSequenceCollector.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtEventSequenceCollector.java
index 04a89f30c36..b8b095cd1dc 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtEventSequenceCollector.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtEventSequenceCollector.java
@@ -29,6 +29,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
@@ -85,6 +86,28 @@ public class DrtEventSequenceCollector
PersonMoneyEventHandler, PersonDepartureEventHandler {
public static class EventSequence {
+
+ public static class PersonEvents {
+
+ @Nullable
+ private PersonDepartureEvent departure;
+ @Nullable
+ private PassengerPickedUpEvent pickedUp;
+ @Nullable
+ private PassengerDroppedOffEvent droppedOff;
+
+ public Optional getDeparture() {
+ return Optional.ofNullable(departure);
+ }
+
+ public Optional getPickedUp() {
+ return Optional.ofNullable(pickedUp);
+ }
+
+ public Optional getDroppedOff() {
+ return Optional.ofNullable(droppedOff);
+ }
+ }
private final DrtRequestSubmittedEvent submitted;
@Nullable
@@ -92,12 +115,8 @@ public static class EventSequence {
@Nullable
private PassengerRequestRejectedEvent rejected;
- @Nullable
- private PersonDepartureEvent departure;
- @Nullable
- private PassengerPickedUpEvent pickedUp;
- @Nullable
- private PassengerDroppedOffEvent droppedOff;
+ private final Map, PersonEvents> personEvents = new HashMap<>();
+
@Nullable
private List drtFares = new LinkedList<>();
@@ -105,15 +124,17 @@ public static class EventSequence {
this.submitted = Objects.requireNonNull(submitted);
}
- public EventSequence(PersonDepartureEvent departure, DrtRequestSubmittedEvent submitted,
+ public EventSequence(Id personId, PersonDepartureEvent departure, DrtRequestSubmittedEvent submitted,
PassengerRequestScheduledEvent scheduled, PassengerPickedUpEvent pickedUp,
PassengerDroppedOffEvent droppedOff, List drtFares) {
this.submitted = Objects.requireNonNull(submitted);
this.scheduled = scheduled;
- this.departure = departure;
- this.pickedUp = pickedUp;
- this.droppedOff = droppedOff;
this.drtFares = new ArrayList<>(drtFares);
+ PersonEvents personEvents = new PersonEvents();
+ personEvents.departure = departure;
+ personEvents.pickedUp = pickedUp;
+ personEvents.droppedOff = droppedOff;
+ this.personEvents.put(personId, personEvents);
}
public DrtRequestSubmittedEvent getSubmitted() {
@@ -128,24 +149,17 @@ public Optional getRejected() {
return Optional.ofNullable(rejected);
}
- public Optional getDeparture() {
- return Optional.ofNullable(departure);
+ public Map, PersonEvents> getPersonEvents() {
+ return personEvents;
}
- public Optional getPickedUp() {
- return Optional.ofNullable(pickedUp);
- }
-
- public Optional getDroppedOff() {
- return Optional.ofNullable(droppedOff);
- }
public List getDrtFares() {
return Collections.unmodifiableList(drtFares);
}
public boolean isCompleted() {
- return droppedOff != null;
+ return personEvents.values().stream().allMatch(pe -> pe.droppedOff != null);
}
}
@@ -155,7 +169,7 @@ public boolean isCompleted() {
private final List drtFarePersonMoneyEvents = new ArrayList<>();
private final Map, PersonDepartureEvent> latestDepartures = new HashMap<>();
- private final Map, PersonDepartureEvent> waitingForSubmission = new HashMap<>();
+ private final Map, List> waitingForSubmission = new HashMap<>();
public DrtEventSequenceCollector(String mode) {
this.mode = mode;
@@ -175,8 +189,8 @@ public Map, EventSequence> getRejectedRequestSequences() {
public Map, EventSequence> getPerformedRequestSequences() {
return sequences.entrySet().stream() //
.filter(e -> e.getValue().getRejected().isEmpty()) //
- .filter(e -> e.getValue().getDeparture().isPresent()) //
- .filter(e -> e.getValue().getPickedUp().isPresent()) //
+ .filter(e -> e.getValue().personEvents.values().stream().allMatch(pe -> pe.departure != null)) //
+ .filter(e -> e.getValue().personEvents.values().stream().allMatch(pe -> pe.pickedUp != null)) //
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
}
@@ -197,9 +211,16 @@ public void handleEvent(DrtRequestSubmittedEvent event) {
if (event.getMode().equals(mode)) {
EventSequence sequence = new EventSequence(event);
sequences.put(event.getRequestId(), sequence);
-
+
// if we already have a departure
- sequence.departure = waitingForSubmission.remove(event.getRequestId());
+ List waiting = waitingForSubmission.remove(event.getRequestId());
+ if(waiting != null) {
+ for (PersonDepartureEvent departure : waiting) {
+ sequence.getPersonEvents().computeIfAbsent(
+ departure.getPersonId(), personId -> new EventSequence.PersonEvents()
+ ).departure = departure;
+ }
+ }
}
}
@@ -218,21 +239,23 @@ public void handleEvent(PersonDepartureEvent event) {
@Override
public void handleEvent(PassengerWaitingEvent event) {
if (event.getMode().equals(mode)) {
- // must exist, otherwise something is wrong
- PersonDepartureEvent departureEvent = Objects.requireNonNull(latestDepartures.remove(event.getPersonId()));
-
EventSequence sequence = sequences.get(event.getRequestId());
- if (sequence != null) {
- // prebooked request, we already have the submission
- Verify.verifyNotNull(sequence.submitted);
- sequence.departure = departureEvent;
- } else {
- // immediate request, submission event should come soon
- waitingForSubmission.put(event.getRequestId(), departureEvent);
+ for (Id personId : event.getPersonIds()) {
+ // must exist, otherwise something is wrong
+ PersonDepartureEvent departureEvent = Objects.requireNonNull(latestDepartures.remove(personId));
+
+ if (sequence != null) {
+ // prebooked request, we already have the submission
+ Verify.verifyNotNull(sequence.submitted);
+ sequence.personEvents.computeIfAbsent(personId, p -> new EventSequence.PersonEvents()).departure = departureEvent;
+ } else {
+ // immediate request, submission event should come soon
+ waitingForSubmission.computeIfAbsent(event.getRequestId(), requestId -> new ArrayList<>()).add(departureEvent);
+ }
}
}
}
-
+
@Override
public void handleEvent(PassengerRequestScheduledEvent event) {
if (event.getMode().equals(mode)) {
@@ -250,14 +273,14 @@ public void handleEvent(PassengerRequestRejectedEvent event) {
@Override
public void handleEvent(PassengerPickedUpEvent event) {
if (event.getMode().equals(mode)) {
- sequences.get(event.getRequestId()).pickedUp = event;
+ sequences.get(event.getRequestId()).personEvents.get(event.getPersonId()).pickedUp = event;
}
}
@Override
public void handleEvent(PassengerDroppedOffEvent event) {
if (event.getMode().equals(mode)) {
- sequences.get(event.getRequestId()).droppedOff = event;
+ sequences.get(event.getRequestId()).personEvents.get(event.getPersonId()).droppedOff = event;
}
}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalWaitTimesAnalyzer.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalWaitTimesAnalyzer.java
index be03eda92f5..40c885b5531 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalWaitTimesAnalyzer.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalWaitTimesAnalyzer.java
@@ -39,6 +39,8 @@
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Polygon;
+import org.matsim.api.core.v01.Id;
+import org.matsim.api.core.v01.population.Person;
import org.matsim.contrib.drt.analysis.DrtEventSequenceCollector;
import org.matsim.contrib.drt.analysis.DrtEventSequenceCollector.EventSequence;
import org.matsim.contrib.drt.run.DrtConfigGroup;
@@ -138,11 +140,13 @@ private Map createZonalStats() {
zoneStats.put(zoneIdForOutsideOfZonalSystem, new DescriptiveStatistics());
for (EventSequence seq : requestAnalyzer.getPerformedRequestSequences().values()) {
- if (seq.getPickedUp().isPresent()) {
- DrtZone zone = zones.getZoneForLinkId(seq.getSubmitted().getFromLinkId());
- final String zoneStr = zone != null ? zone.getId() : zoneIdForOutsideOfZonalSystem;
- double waitTime = seq.getPickedUp().get().getTime() - seq.getSubmitted().getTime();
- zoneStats.get(zoneStr).addValue(waitTime);
+ for (Map.Entry, EventSequence.PersonEvents> entry : seq.getPersonEvents().entrySet()) {
+ if(entry.getValue().getPickedUp().isPresent()) {
+ DrtZone zone = zones.getZoneForLinkId(seq.getSubmitted().getFromLinkId());
+ final String zoneStr = zone != null ? zone.getId() : zoneIdForOutsideOfZonalSystem;
+ double waitTime = entry.getValue().getPickedUp().get() .getTime() - seq.getSubmitted().getTime();
+ zoneStats.get(zoneStr).addValue(waitTime);
+ }
}
}
return zoneStats;
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserter.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserter.java
index 34503207f82..0f8443f0141 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserter.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserter.java
@@ -64,7 +64,7 @@ public class DefaultUnplannedRequestInserter implements UnplannedRequestInserter
private final DrtInsertionSearch insertionSearch;
private final DrtRequestInsertionRetryQueue insertionRetryQueue;
private final DrtOfferAcceptor drtOfferAcceptor;
- private final ForkJoinPool forkJoinPool;
+ private final ForkJoinPool forkJoinPool;
private final PassengerStopDurationProvider stopDurationProvider;
public DefaultUnplannedRequestInserter(DrtConfigGroup drtCfg, Fleet fleet, MobsimTimer mobsimTimer,
@@ -127,12 +127,12 @@ private void scheduleUnplannedRequest(DrtRequest req, Map, Vehic
if (best.isEmpty()) {
if (!insertionRetryQueue.tryAddFailedRequest(req, now)) {
eventsManager.processEvent(
- new PassengerRequestRejectedEvent(now, mode, req.getId(), req.getPassengerId(),
+ new PassengerRequestRejectedEvent(now, mode, req.getId(), req.getPassengerIds(),
NO_INSERTION_FOUND_CAUSE));
log.debug("No insertion found for drt request "
+ req
- + " from passenger id="
- + req.getPassengerId()
+ + " with passenger ids="
+ + req.getPassengerIds().stream().map(Object::toString).collect(Collectors.joining(","))
+ " fromLinkId="
+ req.getFromLink().getId());
}
@@ -153,7 +153,7 @@ private void scheduleUnplannedRequest(DrtRequest req, Map, Vehic
} else {
vehicleEntries.remove(vehicle.getId());
}
-
+
double expectedPickupTime = pickupDropoffTaskPair.pickupTask.getBeginTime();
expectedPickupTime = Math.max(expectedPickupTime, acceptedRequest.get().getEarliestStartTime());
expectedPickupTime += stopDurationProvider.calcPickupDuration(vehicle, req);
@@ -162,7 +162,7 @@ private void scheduleUnplannedRequest(DrtRequest req, Map, Vehic
expectedDropoffTime += stopDurationProvider.calcDropoffDuration(vehicle, req);
eventsManager.processEvent(
- new PassengerRequestScheduledEvent(now, mode, req.getId(), req.getPassengerId(), vehicle.getId(),
+ new PassengerRequestScheduledEvent(now, mode, req.getId(), req.getPassengerIds(), vehicle.getId(),
expectedPickupTime, expectedDropoffTime));
}
}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGenerator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGenerator.java
index df990058487..60e27e4bc5a 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGenerator.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGenerator.java
@@ -20,6 +20,7 @@
package org.matsim.contrib.drt.optimizer.insertion;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import org.matsim.contrib.drt.optimizer.VehicleEntry;
@@ -152,14 +153,20 @@ public InsertionGenerator(StopTimeCalculator stopTimeCalculator, DetourTimeEstim
public List generateInsertions(DrtRequest drtRequest, VehicleEntry vEntry) {
int stopCount = vEntry.stops.size();
List insertions = new ArrayList<>();
+
+ if (drtRequest.getPassengerCount() > vEntry.vehicle.getCapacity()) {
+ //exit early
+ return Collections.EMPTY_LIST;
+ }
+
int occupancy = vEntry.start.occupancy;
-
+
for (int i = 0; i < stopCount; i++) {// insertions up to before last stop
Waypoint.Stop nextStop = nextStop(vEntry, i);
-
+
// (1) only not fully loaded arcs
- boolean allowed = occupancy < vEntry.vehicle.getCapacity();
-
+ boolean allowed = occupancy + drtRequest.getPassengerCount() <= vEntry.vehicle.getCapacity();
+
// (2) check if the request wants to depart after the departure time of the next
// stop. We can early on filter out the current insertion, because we will
// neither be able to insert our stop before the next stop nor merge the request
@@ -211,7 +218,7 @@ private void generateDropoffInsertions(DrtRequest request, VehicleEntry vEntry,
earliestPickupStartTime); //TODO stopDuration not included
var pickupDetourInfo = detourTimeCalculator.calcPickupDetourInfo(vEntry, pickupInsertion, toPickupTT,
fromPickupTT, true, request);
-
+
if (i == 0 && !checkStartSlack(vEntry, request, pickupDetourInfo)) {
// Inserting at schedule start and extending an ongoing stop task further than allowed
return;
@@ -251,7 +258,7 @@ private void generateDropoffInsertions(DrtRequest request, VehicleEntry vEntry,
// i -> pickup -> i+1 && j -> dropoff -> j+1
// check the capacity constraints if i < j (already validated for `i == j`)
Waypoint.Stop currentStop = currentStop(vEntry, j);
- if (currentStop.outgoingOccupancy == vEntry.vehicle.getCapacity()) {
+ if (currentStop.outgoingOccupancy + request.getPassengerCount() > vEntry.vehicle.getCapacity()) {
if (request.getToLink() == currentStop.task.getLink()) {
//special case -- we can insert dropoff exactly at node j
addInsertion(insertions,
@@ -288,24 +295,24 @@ private Waypoint.Stop currentStop(VehicleEntry entry, int insertionIdx) {
private Waypoint.Stop nextStop(VehicleEntry entry, int insertionIdx) {
return entry.stops.get(insertionIdx);
}
-
+
private boolean checkStartSlack(VehicleEntry vEntry, DrtRequest request, PickupDetourInfo pickupDetourInfo) {
if (vEntry.start.task.isEmpty()) {
return true;
}
-
+
Task startTask = vEntry.start.task.get();
-
+
if (!DrtTaskBaseType.STOP.isBaseTypeOf(startTask)) {
return true;
}
-
+
DrtStopTask stopTask = (DrtStopTask) startTask;
-
+
if (stopTask.getLink() != request.getFromLink()) {
return true;
}
-
+
return vEntry.getStartSlackTime() >= pickupDetourInfo.departureTime - stopTask.getEndTime();
}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/NetDepartureReplenishDemandEstimator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/NetDepartureReplenishDemandEstimator.java
index 04753cebeb3..2529a02b8d3 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/NetDepartureReplenishDemandEstimator.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/optimizer/rebalancing/demandestimator/NetDepartureReplenishDemandEstimator.java
@@ -43,7 +43,7 @@ public NetDepartureReplenishDemandEstimator(DrtZonalSystem zonalSystem, DrtConfi
public void handleEvent(PassengerRequestRejectedEvent event) {
if (event.getMode().equals(mode)) {
// Ignore rejected request.
- potentialDrtTripsMap.remove(event.getPersonId());
+ potentialDrtTripsMap.remove(event.getRequestId());
}
}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/AcceptedDrtRequest.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/AcceptedDrtRequest.java
index 4b3e594020b..81dd831859a 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/AcceptedDrtRequest.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/AcceptedDrtRequest.java
@@ -20,12 +20,13 @@
package org.matsim.contrib.drt.passenger;
+import com.google.common.base.MoreObjects;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.population.Person;
import org.matsim.contrib.dvrp.optimizer.Request;
-import com.google.common.base.MoreObjects;
+import java.util.List;
/**
* @author Michal Maciejewski (michalm)
@@ -98,8 +99,8 @@ public Link getToLink() {
return request.getToLink();
}
- public Id getPassengerId() {
- return request.getPassengerId();
+ public List> getPassengerIds() {
+ return request.getPassengerIds();
}
public String getMode() {
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtRequest.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtRequest.java
index 64a64ee58ff..e1c034f2a92 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtRequest.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtRequest.java
@@ -20,13 +20,15 @@
package org.matsim.contrib.drt.passenger;
+import com.google.common.base.MoreObjects;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.population.Person;
import org.matsim.contrib.dvrp.optimizer.Request;
import org.matsim.contrib.dvrp.passenger.PassengerRequest;
-import com.google.common.base.MoreObjects;
+import java.util.*;
+import java.util.stream.Collectors;
/**
* @author michalm
@@ -38,7 +40,7 @@ public class DrtRequest implements PassengerRequest {
private final double latestStartTime;
private final double latestArrivalTime;
- private final Id passengerId;
+ private final List> passengerIds = new ArrayList<>();
private final String mode;
private final Link fromLink;
@@ -50,7 +52,7 @@ private DrtRequest(Builder builder) {
earliestStartTime = builder.earliestStartTime;
latestStartTime = builder.latestStartTime;
latestArrivalTime = builder.latestArrivalTime;
- passengerId = builder.passengerId;
+ passengerIds.addAll(builder.passengerIds);
mode = builder.mode;
fromLink = builder.fromLink;
toLink = builder.toLink;
@@ -67,7 +69,7 @@ public static Builder newBuilder(DrtRequest copy) {
builder.earliestStartTime = copy.getEarliestStartTime();
builder.latestStartTime = copy.getLatestStartTime();
builder.latestArrivalTime = copy.getLatestArrivalTime();
- builder.passengerId = copy.getPassengerId();
+ builder.passengerIds = new ArrayList<>(copy.getPassengerIds());
builder.mode = copy.getMode();
builder.fromLink = copy.getFromLink();
builder.toLink = copy.getToLink();
@@ -109,8 +111,8 @@ public Link getToLink() {
}
@Override
- public Id getPassengerId() {
- return passengerId;
+ public List> getPassengerIds() {
+ return List.copyOf(passengerIds);
}
@Override
@@ -118,6 +120,11 @@ public String getMode() {
return mode;
}
+ @Override
+ public int getPassengerCount() {
+ return passengerIds.size();
+ }
+
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
@@ -126,7 +133,7 @@ public String toString() {
.add("earliestStartTime", earliestStartTime)
.add("latestStartTime", latestStartTime)
.add("latestArrivalTime", latestArrivalTime)
- .add("passengerId", passengerId)
+ .add("passengerIds", passengerIds.stream().map(Object::toString).collect(Collectors.joining(",")))
.add("mode", mode)
.add("fromLink", fromLink)
.add("toLink", toLink)
@@ -139,7 +146,7 @@ public static final class Builder {
private double earliestStartTime;
private double latestStartTime;
private double latestArrivalTime;
- private Id passengerId;
+ private List> passengerIds = new ArrayList<>();
private String mode;
private Link fromLink;
private Link toLink;
@@ -172,8 +179,8 @@ public Builder latestArrivalTime(double val) {
return this;
}
- public Builder passengerId(Id val) {
- passengerId = val;
+ public Builder passengerIds(List> val) {
+ passengerIds = new ArrayList<>(val);
return this;
}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtRequestCreator.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtRequestCreator.java
index 029bcf08239..8a2cff7227e 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtRequestCreator.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtRequestCreator.java
@@ -31,6 +31,8 @@
import org.matsim.contrib.dvrp.passenger.PassengerRequestCreator;
import org.matsim.core.api.experimental.events.EventsManager;
+import java.util.List;
+
/**
* @author michalm
*/
@@ -45,19 +47,19 @@ public DrtRequestCreator(String mode, EventsManager eventsManager) {
}
@Override
- public DrtRequest createRequest(Id id, Id passengerId, Route route, Link fromLink, Link toLink,
- double departureTime, double submissionTime) {
+ public DrtRequest createRequest(Id id, List> passengerIds, Route route, Link fromLink, Link toLink,
+ double departureTime, double submissionTime) {
DrtRoute drtRoute = (DrtRoute)route;
double latestDepartureTime = departureTime + drtRoute.getMaxWaitTime();
double latestArrivalTime = departureTime + drtRoute.getTravelTime().seconds();
eventsManager.processEvent(
- new DrtRequestSubmittedEvent(submissionTime, mode, id, passengerId, fromLink.getId(), toLink.getId(),
+ new DrtRequestSubmittedEvent(submissionTime, mode, id, passengerIds, fromLink.getId(), toLink.getId(),
drtRoute.getDirectRideTime(), drtRoute.getDistance(), departureTime, latestDepartureTime, latestArrivalTime));
DrtRequest request = DrtRequest.newBuilder()
.id(id)
- .passengerId(passengerId)
+ .passengerIds(passengerIds)
.mode(mode)
.fromLink(fromLink)
.toLink(toLink)
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtStopActivity.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtStopActivity.java
index e646c761df1..0f1e36e5706 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtStopActivity.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/DrtStopActivity.java
@@ -20,15 +20,17 @@
package org.matsim.contrib.drt.passenger;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.Supplier;
import org.matsim.api.core.v01.Id;
+import org.matsim.api.core.v01.Identifiable;
import org.matsim.api.core.v01.population.Person;
import org.matsim.contrib.dvrp.optimizer.Request;
import org.matsim.contrib.dvrp.passenger.PassengerHandler;
import org.matsim.contrib.dvrp.passenger.PassengerPickupActivity;
-import org.matsim.contrib.dvrp.schedule.StayTask;
import org.matsim.contrib.dynagent.DynAgent;
import org.matsim.contrib.dynagent.FirstLastSimStepDynActivity;
import org.matsim.core.mobsim.framework.MobsimPassengerAgent;
@@ -67,7 +69,7 @@ protected boolean isLastStep(double now) {
protected void beforeFirstStep(double now) {
// TODO probably we should simulate it more accurately (passenger by passenger, not all at once...)
for (var request : dropoffRequests.values()) {
- passengerHandler.dropOffPassenger(driver, request.getId(), now);
+ passengerHandler.dropOffPassengers(driver, request.getId(), now);
}
}
@@ -75,7 +77,7 @@ protected void beforeFirstStep(double now) {
protected void simStep(double now) {
if (now == endTime.get()) {
for (var request : pickupRequests.values()) {
- if (passengerHandler.tryPickUpPassenger(this, driver, request.getId(), now)) {
+ if (passengerHandler.tryPickUpPassengers(this, driver, request.getId(), now)) {
passengersPickedUp++;
}
}
@@ -83,23 +85,23 @@ protected void simStep(double now) {
}
@Override
- public void notifyPassengerIsReadyForDeparture(MobsimPassengerAgent passenger, double now) {
+ public void notifyPassengersAreReadyForDeparture(List passengers, double now) {
if (now < endTime.get()) {
return;// pick up only at the end of stop activity
}
- var request = getRequestForPassenger(passenger.getId());
- if (passengerHandler.tryPickUpPassenger(this, driver, request.getId(), now)) {
+ var request = getRequestForPassengers(passengers.stream().map(Identifiable::getId).toList());
+ if (passengerHandler.tryPickUpPassengers(this, driver, request.getId(), now)) {
passengersPickedUp++;
} else {
throw new IllegalStateException("The passenger is not on the link or not available for departure!");
}
}
- private AcceptedDrtRequest getRequestForPassenger(Id passengerId) {
+ private AcceptedDrtRequest getRequestForPassengers(List> passengerIds) {
return pickupRequests.values()
.stream()
- .filter(r -> passengerId.equals(r.getPassengerId()))
+ .filter(r -> r.getPassengerIds().size() == passengerIds.size() && r.getPassengerIds().containsAll(passengerIds))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("I am waiting for different passengers!"));
}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/events/DrtRequestSubmittedEvent.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/events/DrtRequestSubmittedEvent.java
index 7bf6f23b343..09f04e814d9 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/events/DrtRequestSubmittedEvent.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/passenger/events/DrtRequestSubmittedEvent.java
@@ -19,9 +19,6 @@
package org.matsim.contrib.drt.passenger.events;
-import java.util.Map;
-import java.util.Objects;
-
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.events.GenericEvent;
import org.matsim.api.core.v01.network.Link;
@@ -29,6 +26,8 @@
import org.matsim.contrib.dvrp.optimizer.Request;
import org.matsim.contrib.dvrp.passenger.PassengerRequestSubmittedEvent;
+import java.util.*;
+
/**
* @author michalm
*/
@@ -49,10 +48,10 @@ public class DrtRequestSubmittedEvent extends PassengerRequestSubmittedEvent {
private final double latestPickupTime;
private final double latestDropoffTime;
- public DrtRequestSubmittedEvent(double time, String mode, Id requestId, Id personId,
+ public DrtRequestSubmittedEvent(double time, String mode, Id requestId, List> personIds,
Id fromLinkId, Id toLinkId, double unsharedRideTime, double unsharedRideDistance,
double earliestDepartureTime, double latestPickupTime, double latestDropoffTime) {
- super(time, mode, requestId, personId, fromLinkId, toLinkId);
+ super(time, mode, requestId, personIds, fromLinkId, toLinkId);
this.unsharedRideTime = unsharedRideTime;
this.unsharedRideDistance = unsharedRideDistance;
this.earliestDepartureTime = earliestDepartureTime;
@@ -82,7 +81,7 @@ public final double getUnsharedRideDistance() {
public final double getEarliestDepartureTime() {
return earliestDepartureTime;
}
-
+
public final double getLatestPickupTime() {
return latestPickupTime;
}
@@ -107,7 +106,11 @@ public static DrtRequestSubmittedEvent convert(GenericEvent event) {
double time = Double.parseDouble(attributes.get(ATTRIBUTE_TIME));
String mode = Objects.requireNonNull(attributes.get(ATTRIBUTE_MODE));
Id requestId = Id.create(attributes.get(ATTRIBUTE_REQUEST), Request.class);
- Id personId = Id.createPersonId(attributes.get(ATTRIBUTE_PERSON));
+ String[] personIdsAttribute = attributes.get(ATTRIBUTE_PERSON).split(",");
+ List> personIds = new ArrayList<>();
+ for (String person : personIdsAttribute) {
+ personIds.add(Id.create(person, Person.class));
+ }
Id fromLinkId = Id.createLinkId(attributes.get(ATTRIBUTE_FROM_LINK));
Id toLinkId = Id.createLinkId(attributes.get(ATTRIBUTE_TO_LINK));
double unsharedRideTime = Double.parseDouble(attributes.get(ATTRIBUTE_UNSHARED_RIDE_TIME));
@@ -115,7 +118,7 @@ public static DrtRequestSubmittedEvent convert(GenericEvent event) {
double earliestDepartureTime = Double.parseDouble(attributes.getOrDefault(ATTRIBUTE_EARLIEST_DEPARTURE_TIME, "NaN"));
double latestPickupTime = Double.parseDouble(attributes.getOrDefault(ATTRIBUTE_LATEST_PICKUP_TIME, "NaN"));
double latestDropoffTime = Double.parseDouble(attributes.getOrDefault(ATTRIBUTE_LATEST_DROPOFF_TIME, "NaN"));
- return new DrtRequestSubmittedEvent(time, mode, requestId, personId, fromLinkId, toLinkId, unsharedRideTime,
+ return new DrtRequestSubmittedEvent(time, mode, requestId, personIds, fromLinkId, toLinkId, unsharedRideTime,
unsharedRideDistance, earliestDepartureTime, latestPickupTime, latestDropoffTime);
}
}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PassengerRequestBookedEvent.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PassengerRequestBookedEvent.java
index 9b8ce42dd25..e58f8835dfb 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PassengerRequestBookedEvent.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PassengerRequestBookedEvent.java
@@ -1,5 +1,6 @@
package org.matsim.contrib.drt.prebooking;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -16,7 +17,7 @@ public class PassengerRequestBookedEvent extends AbstractPassengerRequestEvent {
public static final String EVENT_TYPE = "PassengerRequest booked";
public PassengerRequestBookedEvent(double time, String mode, Id requestId, Id personId) {
- super(time, mode, requestId, personId);
+ super(time, mode, requestId, List.of(personId));
}
@Override
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..1fc580715f4 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingManager.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingManager.java
@@ -3,32 +3,48 @@
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.IdMap;
+import org.matsim.api.core.v01.IdSet;
+import org.matsim.api.core.v01.events.PersonStuckEvent;
+import org.matsim.api.core.v01.events.handler.PersonStuckEventHandler;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
import org.matsim.api.core.v01.population.Leg;
+import org.matsim.api.core.v01.population.Person;
import org.matsim.api.core.v01.population.Plan;
+import org.matsim.contrib.drt.passenger.AcceptedDrtRequest;
+import org.matsim.contrib.drt.prebooking.unscheduler.RequestUnscheduler;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
import org.matsim.contrib.dvrp.optimizer.Request;
import org.matsim.contrib.dvrp.optimizer.VrpOptimizer;
import org.matsim.contrib.dvrp.passenger.AdvanceRequestProvider;
import org.matsim.contrib.dvrp.passenger.PassengerRequest;
import org.matsim.contrib.dvrp.passenger.PassengerRequestCreator;
import org.matsim.contrib.dvrp.passenger.PassengerRequestRejectedEvent;
+import org.matsim.contrib.dvrp.passenger.PassengerRequestRejectedEventHandler;
+import org.matsim.contrib.dvrp.passenger.PassengerRequestScheduledEvent;
+import org.matsim.contrib.dvrp.passenger.PassengerRequestScheduledEventHandler;
import org.matsim.contrib.dvrp.passenger.PassengerRequestValidator;
import org.matsim.core.api.experimental.events.EventsManager;
import org.matsim.core.mobsim.framework.MobsimAgent;
import org.matsim.core.mobsim.framework.MobsimAgent.State;
+import org.matsim.core.mobsim.framework.MobsimTimer;
+import org.matsim.core.mobsim.framework.events.MobsimAfterSimStepEvent;
+import org.matsim.core.mobsim.framework.listeners.MobsimAfterSimStepListener;
import org.matsim.core.mobsim.qsim.InternalInterface;
import org.matsim.core.mobsim.qsim.agents.WithinDayAgentUtils;
import org.matsim.core.mobsim.qsim.interfaces.MobsimEngine;
import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
/**
* This class manages prebooked requests. One instance of PrebookingManager
@@ -36,34 +52,41 @@
* need to pass a person, a leg with the respective DRT mode, the
* requested/expected earliest departure time, and the time at which the request
* should be submitted / taken into account in the system.
- *
+ *
* Preplanned requests can be submitted any time before the planned
* departure/submission times.
- *
+ *
* Internally, the prebooking manager will create a request identifier and
* return the request once the agent actually wants to depart on the planned
* leg. The link between a leg and a request is managed by inserting a special
* attribute in the leg instance.
- *
+ *
* @author Sebastian Hörl (sebhoerl), IRT SystemX
*/
-public class PrebookingManager implements MobsimEngine, AdvanceRequestProvider {
+public class PrebookingManager implements MobsimEngine, MobsimAfterSimStepListener, AdvanceRequestProvider,
+ PassengerRequestScheduledEventHandler, PassengerRequestRejectedEventHandler, PersonStuckEventHandler {
private final String mode;
private final Network network;
private final EventsManager eventsManager;
private final VrpOptimizer optimizer;
+ private final RequestUnscheduler unscheduler;
+
+ private final MobsimTimer mobsimTimer;
public PrebookingManager(String mode, Network network, PassengerRequestCreator requestCreator,
- VrpOptimizer optimizer, PassengerRequestValidator requestValidator, EventsManager eventsManager) {
+ VrpOptimizer optimizer, MobsimTimer mobsimTimer, PassengerRequestValidator requestValidator,
+ EventsManager eventsManager, RequestUnscheduler unscheduler) {
this.network = network;
this.mode = mode;
this.requestCreator = requestCreator;
this.optimizer = optimizer;
this.requestAttribute = PREBOOKED_REQUEST_PREFIX + ":" + mode;
this.requestValidator = requestValidator;
+ this.mobsimTimer = mobsimTimer;
this.eventsManager = eventsManager;
+ this.unscheduler = unscheduler;
}
// Functionality for ID management
@@ -90,60 +113,100 @@ public Id getRequestId(Leg leg) {
return Id.create(rawRequestId, Request.class);
}
+ // Event handling: We track events in parallel and process them later in
+ // notifyMobsimAfterSimStep
+
+ private final ConcurrentLinkedQueue scheduledEvents = new ConcurrentLinkedQueue<>();
+ private final ConcurrentLinkedQueue> rejectedEventIds = new ConcurrentLinkedQueue<>();
+ private final ConcurrentLinkedQueue> stuckPersonsIds = new ConcurrentLinkedQueue<>();
+
+ @Override
+ public void handleEvent(PassengerRequestScheduledEvent event) {
+ scheduledEvents.add(event);
+ }
+
+ @Override
+ public void handleEvent(PassengerRequestRejectedEvent event) {
+ rejectedEventIds.add(event.getRequestId());
+ }
+
+ @Override
+ public void handleEvent(PersonStuckEvent event) {
+ stuckPersonsIds.add(event.getPersonId());
+ }
+
+ // Event handling: We don't want to process events in notifyMobsimAfterSimStep,
+ // so we do it at the next time step
+ private record RejectionItem(Id requestId, List> personIds, String cause) {
+ }
+
+ private final ConcurrentLinkedQueue rejections = new ConcurrentLinkedQueue<>();
+
+ private void processRejection(PassengerRequest request, String cause) {
+ rejections.add(new RejectionItem(request.getId(), request.getPassengerIds(), cause));
+ }
+
+ private void flushRejections(double now) {
+ for (RejectionItem item : rejections) {
+ eventsManager.processEvent(
+ new PassengerRequestRejectedEvent(now, mode, item.requestId, item.personIds, item.cause));
+ }
+
+ rejections.clear();
+ }
+
// Booking functionality
private final PassengerRequestCreator requestCreator;
private final PassengerRequestValidator requestValidator;
- private final List queue = new LinkedList<>();
+
+ // collects new bookings that need to be submitted
+ private final ConcurrentLinkedQueue bookingQueue = new ConcurrentLinkedQueue<>();
public void prebook(MobsimAgent person, Leg leg, double earliestDepartureTime) {
Preconditions.checkArgument(leg.getMode().equals(mode), "Invalid mode for this prebooking manager");
+ Preconditions.checkState(!person.getState().equals(State.ABORT), "Cannot prebook aborted agent");
- synchronized (queue) {
- queue.add(new QueueItem(person, leg, earliestDepartureTime));
- }
- }
-
- private void processQueue(double now) {
- synchronized (queue) {
- for (QueueItem item : queue) {
- Preconditions.checkState(!item.person.getState().equals(State.ABORT), "Cannot prebook aborted agent");
+ Id requestId = createRequestId();
+ double now = mobsimTimer.getTimeOfDay();
- Id requestId = createRequestId();
+ eventsManager.processEvent(new PassengerRequestBookedEvent(now, mode, requestId, person.getId()));
- eventsManager.processEvent(new PassengerRequestBookedEvent(now, mode, requestId, item.person.getId()));
+ PassengerRequest request = requestCreator.createRequest(requestId, List.of(person.getId()), leg.getRoute(),
+ getLink(leg.getRoute().getStartLinkId()), getLink(leg.getRoute().getEndLinkId()), earliestDepartureTime,
+ now);
- PassengerRequest request = requestCreator.createRequest(requestId, item.person.getId(),
- item.leg.getRoute(), getLink(item.leg.getRoute().getStartLinkId()),
- getLink(item.leg.getRoute().getEndLinkId()), item.earliestDepartureTime, now);
+ Set violations = requestValidator.validateRequest(request);
- Set violations = requestValidator.validateRequest(request);
+ Plan plan = WithinDayAgentUtils.getModifiablePlan(person);
+ int currentLegIndex = WithinDayAgentUtils.getCurrentPlanElementIndex(person);
+ int prebookingLegIndex = plan.getPlanElements().indexOf(leg);
- Plan plan = WithinDayAgentUtils.getModifiablePlan(item.person);
- int currentLegIndex = WithinDayAgentUtils.getCurrentPlanElementIndex(item.person);
- int prebookingLegIndex = plan.getPlanElements().indexOf(item.leg);
+ if (prebookingLegIndex <= currentLegIndex) {
+ violations = new HashSet<>(violations);
+ violations.add("past leg"); // the leg for which the booking was made has already happened
+ }
- if (prebookingLegIndex <= currentLegIndex) {
- violations = new HashSet<>(violations);
- violations.add("past leg"); // the leg for which the booking was made has already happened
- }
+ if (!violations.isEmpty()) {
+ String cause = String.join(", ", violations);
+ processRejection(request, cause);
+ } else {
+ leg.getAttributes().putAttribute(requestAttribute, request.getId().toString());
+ bookingQueue.add(request);
+ }
+ }
- if (!violations.isEmpty()) {
- String cause = String.join(", ", violations);
- eventsManager.processEvent(new PassengerRequestRejectedEvent(now, mode, request.getId(),
- request.getPassengerId(), cause));
- } else {
- synchronized (optimizer) {
- optimizer.requestSubmitted(request);
- }
+ private void processBookingQueue(double now) {
+ for (PassengerRequest request : bookingQueue) {
- item.leg.getAttributes().putAttribute(requestAttribute, request.getId().toString());
- requests.put(requestId, new RequestItem(request));
- }
+ synchronized (optimizer) { // needed?
+ optimizer.requestSubmitted(request);
}
- queue.clear();
+ requests.put(request.getId(), new RequestItem(request));
}
+
+ bookingQueue.clear();
}
private Link getLink(Id linkId) {
@@ -152,9 +215,6 @@ private Link getLink(Id linkId) {
linkId, mode);
}
- private record QueueItem(MobsimAgent person, Leg leg, double earliestDepartureTime) {
- }
-
// Interface with PassengerEngine
@Override
@@ -168,7 +228,7 @@ public PassengerRequest retrieveRequest(MobsimAgent agent, Leg leg) {
return null;
}
- RequestItem item = requests.remove(requestId);
+ RequestItem item = requests.get(requestId);
if (item == null) {
return null;
@@ -182,28 +242,213 @@ public PassengerRequest retrieveRequest(MobsimAgent agent, Leg leg) {
private IdMap requests = new IdMap<>(Request.class);
private class RequestItem {
- // this class looks minimal for now, but will be extended with canceling
- // functionality
final PassengerRequest request;
+ Id vehicleId = null;
+ boolean onboard = false;
+
RequestItem(PassengerRequest request) {
this.request = request;
}
}
+ void notifyPickup(double now, AcceptedDrtRequest request) {
+ RequestItem item = requests.get(request.getId());
+
+ if (item != null) {
+ // may be null, we treat all (also non-prebooked) requests here
+ item.onboard = true;
+ }
+ }
+
+ void notifyDropoff(Id requestId) {
+ requests.remove(requestId);
+ }
+
+ private IdSet unscheduleUponVehicleAssignment = new IdSet<>(Request.class);
+
+ private void processScheduledRequests(double now) {
+ for (PassengerRequestScheduledEvent event : scheduledEvents) {
+ RequestItem item = requests.get(event.getRequestId());
+
+ if (item != null) {
+ item.vehicleId = event.getVehicleId();
+ }
+
+ if (unscheduleUponVehicleAssignment.contains(event.getRequestId())) {
+ // this is the case if a request has been rejected / canceled after submission
+ // but before scheduling
+ unscheduler.unscheduleRequest(now, event.getVehicleId(), event.getRequestId());
+ unscheduleUponVehicleAssignment.remove(event.getRequestId());
+ }
+ }
+
+ scheduledEvents.clear();
+ }
+
+ // Functionality for canceling requests
+
+ private static final String CANCEL_REASON = "canceled";
+ private final List cancelQueue = new LinkedList<>();
+
+ private void processCanceledRequests(double now) {
+ for (CancelItem cancelItem : cancelQueue) {
+ Id requestId = cancelItem.requestId;
+ RequestItem item = requests.remove(requestId);
+
+ if (item != null) { // might be null if abandoned before canceling
+ Verify.verify(!item.onboard, "cannot cancel onboard request");
+
+ // unschedule if requests is scheduled already
+ if (item.vehicleId != null) {
+ unscheduler.unscheduleRequest(now, item.vehicleId, requestId);
+ } else {
+ unscheduleUponVehicleAssignment.add(requestId);
+ }
+
+ String reason = CANCEL_REASON;
+
+ if (cancelItem.reason != null) {
+ reason = CANCEL_REASON + ":" + cancelItem.reason;
+ }
+
+ processRejection(item.request, reason);
+ }
+ }
+
+ cancelQueue.clear();
+ }
+
+ public void cancel(Leg leg) {
+ cancel(leg, null);
+ }
+
+ public void cancel(Leg leg, String reason) {
+ Id requestId = getRequestId(leg);
+
+ if (requestId != null) {
+ cancel(requestId, reason);
+ }
+ }
+
+ public void cancel(Id requestId, String reason) {
+ cancelQueue.add(new CancelItem(requestId, reason));
+ }
+
+ public void cancel(Id requestId) {
+ cancel(requestId, null);
+ }
+
+ private record CancelItem(Id requestId, String reason) {
+ }
+
+ // Functionality for abandoning requests
+
+ private static final String ABANDONED_REASON = "abandoned by vehicle";
+ private final ConcurrentLinkedQueue> abandonQueue = new ConcurrentLinkedQueue<>();
+
+ void abandon(Id requestId) {
+ abandonQueue.add(requestId);
+ }
+
+ private void processAbandonedRequests(double now) {
+ for (Id requestId : abandonQueue) {
+ RequestItem item = Objects.requireNonNull(requests.remove(requestId));
+ Verify.verify(!item.onboard, "cannot abandon request that is already onboard");
+
+ // remove request from scheduled if already scheduled
+ if (item.vehicleId != null) {
+ unscheduler.unscheduleRequest(now, item.vehicleId, item.request.getId());
+ } else {
+ unscheduleUponVehicleAssignment.add(item.request.getId());
+ }
+
+ processRejection(item.request, ABANDONED_REASON);
+ }
+
+ abandonQueue.clear();
+ }
+
+ // Rejections
+
+ private void processRejections(double now) {
+ for (Id requestId : rejectedEventIds) {
+ RequestItem item = requests.remove(requestId);
+
+ if (item != null) {
+ // should this fail gracefully?
+ Verify.verify(!item.onboard, "cannot reject onboard request");
+
+ // unschedule if already scheduled
+ if (item.vehicleId != null) {
+ unscheduler.unscheduleRequest(now, item.vehicleId, requestId);
+ } else {
+ unscheduleUponVehicleAssignment.add(requestId);
+ }
+ }
+ }
+
+ rejectedEventIds.clear();
+ }
+
+ // Stuck
+
+ private void processStuckAgents(double now) {
+ bookingQueue.removeIf(request -> stuckPersonsIds.containsAll(request.getPassengerIds()));
+
+ for (RequestItem item : requests.values()) {
+ if (stuckPersonsIds.containsAll(item.request.getPassengerIds())) {
+ cancel(item.request.getId());
+ }
+ }
+
+ stuckPersonsIds.clear();
+ }
+
// Engine code
+ @Override
+ public void onPrepareSim() {
+ eventsManager.addHandler(this);
+ }
+
@Override
public void doSimStep(double now) {
- processQueue(now);
+ // avoid method as it runs in parallel with events, only process rejections
+ flushRejections(now);
}
@Override
- public void onPrepareSim() {
+ public void notifyMobsimAfterSimStep(@SuppressWarnings("rawtypes") MobsimAfterSimStepEvent e) {
+ // here we are back in the main thread and all events
+ // have been processed
+
+ double now = mobsimTimer.getTimeOfDay();
+
+ // first process scheduled events (this happened, we cannot change it)
+ processScheduledRequests(now);
+
+ // process rejected requests, potential problem if a person has entered the
+ // vehicle in just this simstep, but also a rejection has been sent
+ processRejections(now);
+
+ // process stuck agents, they are added to the cancel queue
+ processStuckAgents(now);
+
+ // process abandoned requests (by vehicle), here we are sure that the person
+ // cannot have entered in this iteration
+ processAbandonedRequests(now);
+
+ // process cancel requests, same situation as for rejections
+ processCanceledRequests(now);
+
+ // submit requests
+ processBookingQueue(now);
}
@Override
public void afterSim() {
+ eventsManager.removeHandler(this);
}
@Override
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingModeQSimModule.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingModeQSimModule.java
index 4c423d64ce4..b3f6faa4bc7 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingModeQSimModule.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingModeQSimModule.java
@@ -2,24 +2,39 @@
import org.matsim.api.core.v01.network.Network;
import org.matsim.api.core.v01.population.Population;
+import org.matsim.contrib.drt.optimizer.VehicleEntry;
+import org.matsim.contrib.drt.prebooking.abandon.AbandonVoter;
+import org.matsim.contrib.drt.prebooking.abandon.MaximumDelayAbandonVoter;
import org.matsim.contrib.drt.prebooking.logic.helpers.PopulationIterator.PopulationIteratorFactory;
import org.matsim.contrib.drt.prebooking.logic.helpers.PrebookingQueue;
+import org.matsim.contrib.drt.prebooking.unscheduler.ComplexRequestUnscheduler;
+import org.matsim.contrib.drt.prebooking.unscheduler.RequestUnscheduler;
+import org.matsim.contrib.drt.prebooking.unscheduler.SimpleRequestUnscheduler;
+import org.matsim.contrib.drt.schedule.DrtTaskFactory;
import org.matsim.contrib.drt.stops.PassengerStopDurationProvider;
import org.matsim.contrib.drt.vrpagent.DrtActionCreator;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicleLookup;
import org.matsim.contrib.dvrp.optimizer.VrpOptimizer;
import org.matsim.contrib.dvrp.passenger.PassengerEngine;
import org.matsim.contrib.dvrp.passenger.PassengerHandler;
import org.matsim.contrib.dvrp.passenger.PassengerRequestCreator;
import org.matsim.contrib.dvrp.passenger.PassengerRequestValidator;
import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule;
+import org.matsim.contrib.dvrp.schedule.ScheduleTimingUpdater;
import org.matsim.core.api.experimental.events.EventsManager;
+import org.matsim.core.mobsim.framework.MobsimTimer;
import org.matsim.core.mobsim.qsim.QSim;
+import org.matsim.core.router.util.LeastCostPathCalculator;
+import org.matsim.core.router.util.TravelTime;
import com.google.inject.Singleton;
public class PrebookingModeQSimModule extends AbstractDvrpModeQSimModule {
- public PrebookingModeQSimModule(String mode) {
+ private final PrebookingParams prebookingParams;
+
+ public PrebookingModeQSimModule(String mode, PrebookingParams prebookingParams) {
super(mode);
+ this.prebookingParams = prebookingParams;
}
@Override
@@ -28,8 +43,11 @@ protected void configureQSim() {
PassengerHandler passengerHandler = (PassengerEngine) getter.getModal(PassengerHandler.class);
DrtActionCreator delegate = getter.getModal(DrtActionCreator.class);
PassengerStopDurationProvider stopDurationProvider = getter.getModal(PassengerStopDurationProvider.class);
+ PrebookingManager prebookingManager = getter.getModal(PrebookingManager.class);
+ AbandonVoter abandonVoter = getter.getModal(AbandonVoter.class);
- return new PrebookingActionCreator(passengerHandler, delegate, stopDurationProvider);
+ return new PrebookingActionCreator(passengerHandler, delegate, stopDurationProvider, prebookingManager,
+ abandonVoter);
})).in(Singleton.class);
bindModal(PrebookingManager.class).toProvider(modalProvider(getter -> {
@@ -38,9 +56,11 @@ protected void configureQSim() {
VrpOptimizer optimizer = getter.getModal(VrpOptimizer.class);
PassengerRequestValidator requestValidator = getter.getModal(PassengerRequestValidator.class);
EventsManager eventsManager = getter.get(EventsManager.class);
+ RequestUnscheduler requestUnscheduler = getter.getModal(RequestUnscheduler.class);
+ MobsimTimer mobsimTimer = getter.get(MobsimTimer.class);
- return new PrebookingManager(getMode(), network, requestCreator, optimizer, requestValidator,
- eventsManager);
+ return new PrebookingManager(getMode(), network, requestCreator, optimizer, mobsimTimer, requestValidator,
+ eventsManager, requestUnscheduler);
})).in(Singleton.class);
addModalQSimComponentBinding().to(modalKey(PrebookingManager.class));
@@ -52,5 +72,39 @@ protected void configureQSim() {
bindModal(PopulationIteratorFactory.class).toProvider(modalProvider(getter -> {
return new PopulationIteratorFactory(getter.get(Population.class), getter.get(QSim.class));
}));
+
+ bindModal(MaximumDelayAbandonVoter.class).toProvider(modalProvider(getter -> {
+ double maximumDelay = prebookingParams.maximumPassengerDelay;
+ return new MaximumDelayAbandonVoter(maximumDelay);
+ })).in(Singleton.class);
+ bindModal(AbandonVoter.class).to(modalKey(MaximumDelayAbandonVoter.class));
+
+ bindModal(SimpleRequestUnscheduler.class).toProvider(modalProvider(getter -> {
+ DvrpVehicleLookup vehicleLookup = getter.get(DvrpVehicleLookup.class);
+ return new SimpleRequestUnscheduler(vehicleLookup);
+ })).in(Singleton.class);
+
+ bindModal(ComplexRequestUnscheduler.class).toProvider(modalProvider(getter -> {
+ DvrpVehicleLookup vehicleLookup = getter.get(DvrpVehicleLookup.class);
+ VehicleEntry.EntryFactory entryFactory = getter.getModal(VehicleEntry.EntryFactory.class);
+ DrtTaskFactory taskFactory = getter.getModal(DrtTaskFactory.class);
+ LeastCostPathCalculator router = getter.getModal(LeastCostPathCalculator.class);
+ TravelTime travelTime = getter.getModal(TravelTime.class);
+ ScheduleTimingUpdater timingUpdater = getter.getModal(ScheduleTimingUpdater.class);
+
+ return new ComplexRequestUnscheduler(vehicleLookup, entryFactory, taskFactory, router, travelTime,
+ timingUpdater, prebookingParams.scheduleWaitBeforeDrive);
+ })).in(Singleton.class);
+
+ switch (prebookingParams.unschedulingMode) {
+ case StopBased:
+ bindModal(RequestUnscheduler.class).to(modalKey(SimpleRequestUnscheduler.class));
+ break;
+ case Routing:
+ bindModal(RequestUnscheduler.class).to(modalKey(ComplexRequestUnscheduler.class));
+ break;
+ default:
+ throw new IllegalStateException("No binding for selected RequestUnscheduler");
+ }
}
}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingParams.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingParams.java
index 86376f51a67..fdf678bb272 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingParams.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingParams.java
@@ -2,6 +2,9 @@
import org.matsim.core.config.ReflectiveConfigGroup;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Positive;
+
public class PrebookingParams extends ReflectiveConfigGroup {
public static final String SET_NAME = "prebooking";
@@ -11,10 +14,28 @@ public PrebookingParams() {
@Parameter
@Comment("Defines whether vehicles drive immediately to the next"
- + "(prebooked) future task and wait for the planned stop to begin, or wait at the current"
- + "position and depart to arrive on time at the following stop. The latter behavior (not"
- + "the default) may lead to larger ucnertainty in highly congested scenarios.")
+ + " (prebooked) future task and wait for the planned stop to begin, or wait at the current"
+ + " position and depart to arrive on time at the following stop. The latter behavior (not"
+ + " the default) may lead to larger ucnertainty in highly congested scenarios.")
public boolean scheduleWaitBeforeDrive = false; // in the future, this could also become a double value indicating
// how many minutes before the next stop the vehicle should plan to
// be there
+
+ @Parameter
+ @Comment("Request gets rejected if a vehicle waits longer than the indicated duration at the stop")
+ @NotNull
+ @Positive
+ public double maximumPassengerDelay = Double.POSITIVE_INFINITY;
+
+ public enum UnschedulingMode {
+ StopBased, Routing
+ }
+
+ @Parameter
+ @Comment("When unscheduling requests because they have been canceled,"
+ + " we either simply remove the requests from the planned stops"
+ + " along the vehicle's schedule or we adaptively reconfigure and reroute the vehicle's schedule.")
+ @NotNull
+ public UnschedulingMode unschedulingMode = UnschedulingMode.StopBased;
+
}
\ No newline at end of file
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingStopActivity.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingStopActivity.java
index e83d03cf679..3fef9b0c932 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
@@ -1,14 +1,17 @@
package org.matsim.contrib.drt.prebooking;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.IdMap;
+import org.matsim.api.core.v01.Identifiable;
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 +41,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 +61,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;
}
@@ -85,42 +93,52 @@ private void processDropoffRequests(double now) {
var entry = iterator.next();
if (entry.getValue() <= now) { // Request should leave now
- passengerHandler.dropOffPassenger(driver, entry.getKey(), now);
+ passengerHandler.dropOffPassengers(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
- if (passengerHandler.notifyWaitForPassenger(this, this.driver, request.getId())) {
+ if (passengerHandler.notifyWaitForPassengers(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));
+ Verify.verify(passengerHandler.tryPickUpPassengers(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);
}
@@ -131,13 +149,17 @@ protected void simStep(double now) {
}
@Override
- public void notifyPassengerIsReadyForDeparture(MobsimPassengerAgent passenger, double now) {
- var request = getRequestForPassenger(passenger.getId());
+ public void notifyPassengersAreReadyForDeparture(List passengers, double now) {
+ var request = getRequestForPassengers(passengers.stream().map(Identifiable::getId).toList());
queuePickup(request, now);
}
- private AcceptedDrtRequest getRequestForPassenger(Id passengerId) {
- return pickupRequests.values().stream().filter(r -> passengerId.equals(r.getPassengerId())).findAny()
+
+ private AcceptedDrtRequest getRequestForPassengers(List> passengerIds) {
+ return pickupRequests.values()
+ .stream()
+ .filter(r -> r.getPassengerIds().size() == passengerIds.size() && r.getPassengerIds().containsAll(passengerIds))
+ .findAny()
.orElseThrow(() -> new IllegalArgumentException("I am waiting for different passengers!"));
}
}
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..a160ac5fce4 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;
}
}
@@ -89,18 +89,19 @@ public List getRecords() {
List records = new LinkedList<>();
for (Sequence sequence : sequences) {
- records.add(new RequestRecord(sequence.booked.getRequestId(), sequence.booked.getPersonId(),
+ records.add(new RequestRecord(sequence.booked.getRequestId(), sequence.booked.getPersonIds(),
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) {
+ public record RequestRecord(Id requestId, List> personIds, Double submissionTime, Double scheduledTime,
+ 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..5127841f292 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
@@ -3,6 +3,7 @@
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.List;
+import java.util.stream.Collectors;
import org.matsim.core.utils.io.IOUtils;
@@ -23,17 +24,19 @@ public void write(List records) {
"submission_time", //
"scheduled_time", //
"rejected_time", //
- "entering_time" //
+ "departure_time", //
+ "rejected_reason" //
}) + "\n");
for (var record : records) {
writer.write(String.join(",", new String[] { //
record.requestId().toString(), //
- record.personId().toString(), //
+ record.personIds().stream().map(Object::toString).collect(Collectors.joining("-")), //
record.submissionTime() == null ? "" : String.valueOf(record.submissionTime()), //
record.scheduledTime() == null ? "" : String.valueOf(record.scheduledTime()), //
record.rejectedTime() == null ? "" : String.valueOf(record.rejectedTime()), //
- record.enteringTime() == null ? "" : String.valueOf(record.enteringTime()) //
+ record.departureTime() == null ? "" : String.valueOf(record.departureTime()), //
+ record.rejectedReason() //
}) + "\n");
}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/ComplexRequestUnscheduler.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/ComplexRequestUnscheduler.java
new file mode 100644
index 00000000000..4fb0cae5339
--- /dev/null
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/ComplexRequestUnscheduler.java
@@ -0,0 +1,319 @@
+package org.matsim.contrib.drt.prebooking.unscheduler;
+
+import org.matsim.api.core.v01.Id;
+import org.matsim.api.core.v01.network.Link;
+import org.matsim.contrib.drt.optimizer.VehicleEntry;
+import org.matsim.contrib.drt.optimizer.Waypoint;
+import org.matsim.contrib.drt.schedule.DrtDriveTask;
+import org.matsim.contrib.drt.schedule.DrtStayTask;
+import org.matsim.contrib.drt.schedule.DrtStopTask;
+import org.matsim.contrib.drt.schedule.DrtTaskBaseType;
+import org.matsim.contrib.drt.schedule.DrtTaskFactory;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicleLookup;
+import org.matsim.contrib.dvrp.optimizer.Request;
+import org.matsim.contrib.dvrp.path.VrpPathWithTravelData;
+import org.matsim.contrib.dvrp.path.VrpPaths;
+import org.matsim.contrib.dvrp.schedule.DriveTask;
+import org.matsim.contrib.dvrp.schedule.Schedule;
+import org.matsim.contrib.dvrp.schedule.ScheduleTimingUpdater;
+import org.matsim.contrib.dvrp.schedule.Schedules;
+import org.matsim.contrib.dvrp.schedule.StayTask;
+import org.matsim.contrib.dvrp.schedule.Task;
+import org.matsim.contrib.dvrp.schedule.Task.TaskStatus;
+import org.matsim.contrib.dvrp.tracker.OnlineDriveTaskTracker;
+import org.matsim.contrib.dvrp.util.LinkTimePair;
+import org.matsim.core.router.util.LeastCostPathCalculator;
+import org.matsim.core.router.util.TravelTime;
+
+import com.google.common.base.Verify;
+
+/**
+ * This RequestUnscheduler searches for a request in a vehicle's schedule and
+ * removes the request from the relevant stop tasks. Furthermore, the stops are
+ * removed if they don't carry any other pickups or dropoffs. Accordingly, the
+ * schedule will also be rerouted.
+ *
+ * @author Sebastian Hörl (sebhoerl), IRT SystemX
+ */
+public class ComplexRequestUnscheduler implements RequestUnscheduler {
+ private final DvrpVehicleLookup vehicleLookup;
+ private final VehicleEntry.EntryFactory vehicleEntryFactory;
+
+ private final DrtTaskFactory taskFactory;
+
+ private final LeastCostPathCalculator router;
+ private final TravelTime travelTime;
+ private final ScheduleTimingUpdater timingUpdater;
+
+ private final boolean scheduleWaitBeforeDrive;
+
+ public ComplexRequestUnscheduler(DvrpVehicleLookup vehicleLookup, VehicleEntry.EntryFactory vehicleEntryFactory,
+ DrtTaskFactory taskFactory, LeastCostPathCalculator router, TravelTime travelTime,
+ ScheduleTimingUpdater timingUpdater, boolean scheduleWaitBeforeDrive) {
+ this.vehicleLookup = vehicleLookup;
+ this.vehicleEntryFactory = vehicleEntryFactory;
+ this.taskFactory = taskFactory;
+ this.travelTime = travelTime;
+ this.router = router;
+ this.timingUpdater = timingUpdater;
+ this.scheduleWaitBeforeDrive = scheduleWaitBeforeDrive;
+ }
+
+ @Override
+ public void unscheduleRequest(double now, Id vehicleId, Id requestId) {
+ DvrpVehicle vehicle = vehicleLookup.lookupVehicle(vehicleId);
+ VehicleEntry vEntry = vehicleEntryFactory.create(vehicle, now);
+
+ Waypoint.Stop pickupStop = null;
+ Waypoint.Stop dropoffStop = null;
+
+ DrtStopTask pickupStopTask = null;
+ DrtStopTask dropoffStopTask = null;
+
+ for (Waypoint.Stop stop : vEntry.stops) {
+ if (stop.task.getPickupRequests().containsKey(requestId)) {
+ Verify.verify(pickupStop == null);
+ Verify.verify(pickupStopTask == null);
+
+ pickupStop = stop;
+ pickupStopTask = stop.task;
+ }
+
+ if (stop.task.getDropoffRequests().containsKey(requestId)) {
+ Verify.verify(dropoffStop == null);
+ Verify.verify(dropoffStopTask == null);
+
+ dropoffStop = stop;
+ dropoffStopTask = stop.task;
+ }
+ }
+
+ Verify.verifyNotNull(pickupStopTask, "Could not find request that I'm supposed to unschedule");
+ Verify.verifyNotNull(dropoffStopTask, "Could not find request that I'm supposed to unschedule");
+ Verify.verifyNotNull(pickupStop);
+ Verify.verifyNotNull(dropoffStop);
+
+ // remove request from stop, this we do in any case
+ pickupStopTask.removePickupRequest(requestId);
+ dropoffStopTask.removeDropoffRequest(requestId);
+
+ // remove pickup
+ // - either we didn't find a stop (because task is running), then we have
+ // removed the pickup and the StopAction will handle the situation
+ // - or we found a stop, then it is not started yet and we can remove it
+
+ boolean removePickup = pickupStopTask.getPickupRequests().size() == 0
+ && pickupStopTask.getDropoffRequests().size() == 0;
+ boolean removeDropoff = dropoffStopTask.getPickupRequests().size() == 0
+ && dropoffStopTask.getDropoffRequests().size() == 0;
+
+ Replacement pickupReplacement = removePickup ? findReplacement(vEntry, pickupStop) : null;
+ Replacement dropoffReplacement = removeDropoff ? findReplacement(vEntry, dropoffStop) : null;
+
+ if (pickupReplacement != null && dropoffReplacement != null) {
+ if (pickupReplacement.endTask.getTaskIdx() >= dropoffReplacement.startTask.getTaskIdx()) {
+ // we have an overlap
+ pickupReplacement = new Replacement(pickupReplacement.startTask, dropoffReplacement.endTask,
+ vehicle.getSchedule());
+ dropoffReplacement = null;
+ }
+ }
+
+ if (pickupReplacement != null) {
+ unschedule(now, vEntry, pickupReplacement);
+ }
+
+ if (dropoffReplacement != null) {
+ unschedule(now, vEntry, dropoffReplacement);
+ }
+ }
+
+ private Replacement findReplacement(VehicleEntry vEntry, Waypoint.Stop stop) {
+ int stopIndex = vEntry.stops.indexOf(stop);
+
+ final Task startTask;
+ if (stopIndex == 0) {
+ startTask = vEntry.vehicle.getSchedule().getCurrentTask();
+ } else {
+ startTask = vEntry.stops.get(stopIndex - 1).task;
+ }
+
+ final Task endTask;
+ if (stopIndex == vEntry.stops.size() - 1) {
+ endTask = Schedules.getLastTask(vEntry.vehicle.getSchedule());
+ } else {
+ endTask = vEntry.stops.get(stopIndex + 1).task;
+ }
+
+ return new Replacement(startTask, endTask, vEntry.vehicle.getSchedule());
+ }
+
+ private void unschedule(double now, VehicleEntry vEntry, Replacement replacement) {
+ Schedule schedule = vEntry.vehicle.getSchedule();
+
+ if (replacement.startTask instanceof DrtStayTask) {
+ replacement.startTask.setEndTime(now);
+ }
+
+ // special case: we remove everything until the end (and replace the stay task)
+ boolean removeUntilEnd = replacement.endTask == Schedules.getLastTask(schedule);
+ if (removeUntilEnd) {
+ Verify.verify(replacement.endTask instanceof DrtStayTask);
+ final Link stayLink;
+
+ if (replacement.startTask instanceof StayTask) {
+ stayLink = ((StayTask) replacement.startTask).getLink();
+ } else {
+ Verify.verify(replacement.startTask.getStatus().equals(TaskStatus.STARTED));
+ DriveTask driveTask = (DriveTask) replacement.startTask;
+
+ OnlineDriveTaskTracker tracker = (OnlineDriveTaskTracker) driveTask.getTaskTracker();
+ tracker.divertPath(VrpPaths.createZeroLengthPathForDiversion(tracker.getDiversionPoint()));
+
+ stayLink = driveTask.getPath().getToLink();
+ }
+
+ double initialEndTime = replacement.endTask.getEndTime();
+
+ while (!(replacement.startTask == Schedules.getLastTask(schedule))) {
+ schedule.removeLastTask();
+ }
+
+ schedule.addTask(taskFactory.createStayTask(vEntry.vehicle, replacement.startTask.getEndTime(),
+ Math.max(replacement.startTask.getEndTime(), initialEndTime), stayLink));
+
+ return; // done
+ }
+
+ // remove everything between the two indicated tasks
+ while (replacement.startTask.getTaskIdx() + 1 != replacement.endTask.getTaskIdx()) {
+ Task removeTask = schedule.getTasks().get(replacement.startTask.getTaskIdx() + 1);
+ schedule.removeTask(removeTask);
+ }
+
+ // if destination is not the schedule end, it must be another stop
+ Verify.verify(replacement.endTask instanceof DrtStopTask);
+ Link endLink = ((StayTask) replacement.endTask).getLink();
+ double endArrivalTime = replacement.endTask.getBeginTime();
+
+ final Task lastInsertedTask;
+ if (replacement.startTask instanceof DriveTask) { // special case: start task is driving
+ Verify.verify(replacement.startTask.getStatus().equals(TaskStatus.STARTED));
+
+ DriveTask driveTask = (DriveTask) replacement.startTask;
+ OnlineDriveTaskTracker tracker = (OnlineDriveTaskTracker) driveTask.getTaskTracker();
+ LinkTimePair diversion = tracker.getDiversionPoint();
+
+ VrpPathWithTravelData vrpPath = VrpPaths.calcAndCreatePathForDiversion(diversion, endLink, router,
+ travelTime);
+
+ if (vrpPath.getArrivalTime() < endArrivalTime && scheduleWaitBeforeDrive) {
+ tracker.divertPath(VrpPaths.createZeroLengthPathForDiversion(diversion));
+ lastInsertedTask = insertDriveWithWait(vEntry.vehicle, replacement.startTask, vrpPath, endArrivalTime);
+ } else {
+ tracker.divertPath(vrpPath);
+ lastInsertedTask = insertWait(vEntry.vehicle, replacement.startTask, endArrivalTime);
+ }
+ } else { // normal case
+ StayTask startStayTask = (StayTask) replacement.startTask;
+ Link startLink = startStayTask.getLink();
+
+ if (startLink == endLink) { // no need to move, maybe just wait
+ if (startStayTask.getEndTime() < endArrivalTime) {
+ lastInsertedTask = insertWait(vEntry.vehicle, startStayTask, endArrivalTime);
+ } else {
+ lastInsertedTask = startStayTask; // nothing inserted
+ }
+ } else {
+ VrpPathWithTravelData vrpPath = VrpPaths.calcAndCreatePath(startLink, endLink,
+ startStayTask.getEndTime(), router, travelTime);
+
+ lastInsertedTask = insertDriveWithWait(vEntry.vehicle, startStayTask, vrpPath, endArrivalTime);
+ }
+ }
+
+ timingUpdater.updateTimingsStartingFromTaskIdx(vEntry.vehicle, lastInsertedTask.getTaskIdx() + 1,
+ lastInsertedTask.getEndTime());
+ }
+
+ /*
+ * Copy & paste from DefaultRequestInsertionScheduler
+ */
+ private Task insertWait(DvrpVehicle vehicle, Task departureTask, double earliestNextStartTime) {
+ Schedule schedule = vehicle.getSchedule();
+
+ final Link waitLink;
+ if (departureTask instanceof StayTask) {
+ waitLink = ((StayTask) departureTask).getLink();
+ } else if (departureTask instanceof DriveTask) {
+ waitLink = ((DriveTask) departureTask).getPath().getToLink();
+ } else {
+ throw new IllegalStateException();
+ }
+
+ if (departureTask.getEndTime() < earliestNextStartTime) {
+ DrtStayTask waitTask = taskFactory.createStayTask(vehicle, departureTask.getEndTime(),
+ earliestNextStartTime, waitLink);
+ schedule.addTask(departureTask.getTaskIdx() + 1, waitTask);
+ return waitTask;
+ }
+
+ return departureTask;
+ }
+
+ /*
+ * Copy & paste from DefaultRequestInsertionScheduler
+ */
+ private Task insertDriveWithWait(DvrpVehicle vehicle, Task departureTask, VrpPathWithTravelData path,
+ double latestArrivalTime) {
+ Schedule schedule = vehicle.getSchedule();
+
+ Task leadingTask = departureTask;
+
+ if (scheduleWaitBeforeDrive) {
+ double driveDepartureTime = latestArrivalTime - path.getTravelTime();
+
+ if (driveDepartureTime > departureTask.getEndTime()) {
+ // makes sense to insert a wait task before departure
+ DrtStayTask waitTask = taskFactory.createStayTask(vehicle, departureTask.getEndTime(),
+ driveDepartureTime, path.getFromLink());
+ schedule.addTask(departureTask.getTaskIdx() + 1, waitTask);
+
+ path = path.withDepartureTime(driveDepartureTime);
+ leadingTask = waitTask;
+ }
+ }
+
+ Task driveTask = taskFactory.createDriveTask(vehicle, path, DrtDriveTask.TYPE);
+ schedule.addTask(leadingTask.getTaskIdx() + 1, driveTask);
+
+ if (driveTask.getEndTime() < latestArrivalTime) {
+ DrtStayTask waitTask = taskFactory.createStayTask(vehicle, driveTask.getEndTime(), latestArrivalTime,
+ path.getToLink());
+ schedule.addTask(driveTask.getTaskIdx() + 1, waitTask);
+ return waitTask;
+ } else {
+ return driveTask;
+ }
+ }
+
+ private class Replacement {
+ final Task startTask;
+ final Task endTask;
+
+ Replacement(Task startTask, Task endTask, Schedule schedule) {
+ boolean startIsOngoing = startTask.getStatus().equals(TaskStatus.STARTED);
+ boolean startIsStopTask = DrtTaskBaseType.STOP.isBaseTypeOf(startTask);
+
+ Verify.verify(startIsOngoing || startIsStopTask);
+ this.startTask = startTask;
+
+ boolean endIsLastStay = endTask instanceof DrtStayTask && Schedules.getLastTask(schedule) == endTask;
+ boolean endIsStopTask = DrtTaskBaseType.STOP.isBaseTypeOf(endTask);
+
+ Verify.verify(endIsLastStay || endIsStopTask);
+ this.endTask = endTask;
+ }
+ }
+}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/RequestUnscheduler.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/RequestUnscheduler.java
new file mode 100644
index 00000000000..fde9b138e92
--- /dev/null
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/RequestUnscheduler.java
@@ -0,0 +1,9 @@
+package org.matsim.contrib.drt.prebooking.unscheduler;
+
+import org.matsim.api.core.v01.Id;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
+import org.matsim.contrib.dvrp.optimizer.Request;
+
+public interface RequestUnscheduler {
+ void unscheduleRequest(double now, Id vehicleId, Id requestId);
+}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/SimpleRequestUnscheduler.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/SimpleRequestUnscheduler.java
new file mode 100644
index 00000000000..bc6f2f0fbcc
--- /dev/null
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/unscheduler/SimpleRequestUnscheduler.java
@@ -0,0 +1,60 @@
+package org.matsim.contrib.drt.prebooking.unscheduler;
+
+import org.matsim.api.core.v01.Id;
+import org.matsim.contrib.drt.schedule.DrtStopTask;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicleLookup;
+import org.matsim.contrib.dvrp.optimizer.Request;
+import org.matsim.contrib.dvrp.schedule.Schedule;
+import org.matsim.contrib.dvrp.schedule.Task;
+
+import com.google.common.base.Verify;
+
+/**
+ * This RequestUnscheduler searches for a request in a vehicle's schedule and
+ * removes the request from the relevant stop tasks. No other changes (wrt to
+ * rerouting the vehicle) are applied to the schedule.
+ *
+ * @author Sebastian Hörl (sebhoerl), IRT SystemX
+ */
+public class SimpleRequestUnscheduler implements RequestUnscheduler {
+ private final DvrpVehicleLookup vehicleLookup;
+
+ public SimpleRequestUnscheduler(DvrpVehicleLookup vehicleLookup) {
+ this.vehicleLookup = vehicleLookup;
+ }
+
+ @Override
+ public void unscheduleRequest(double now, Id vehicleId, Id requestId) {
+ DvrpVehicle vehicle = vehicleLookup.lookupVehicle(vehicleId);
+ Schedule schedule = vehicle.getSchedule();
+
+ DrtStopTask pickupTask = null;
+ DrtStopTask dropoffTask = null;
+
+ int currentIndex = schedule.getCurrentTask().getTaskIdx();
+ for (; currentIndex < schedule.getTaskCount() && dropoffTask == null; currentIndex++) {
+ Task currentTask = schedule.getTasks().get(currentIndex);
+
+ if (currentTask instanceof DrtStopTask) {
+ DrtStopTask stopTask = (DrtStopTask) currentTask;
+
+ if (stopTask.getPickupRequests().keySet().contains(requestId)) {
+ Verify.verify(pickupTask == null);
+ pickupTask = stopTask;
+ }
+
+ if (stopTask.getDropoffRequests().keySet().contains(requestId)) {
+ Verify.verify(dropoffTask == null);
+ dropoffTask = stopTask;
+ }
+ }
+ }
+
+ Verify.verifyNotNull(pickupTask);
+ Verify.verifyNotNull(dropoffTask);
+
+ pickupTask.removePickupRequest(requestId);
+ dropoffTask.removeDropoffRequest(requestId);
+ }
+}
\ No newline at end of file
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtModeQSimModule.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtModeQSimModule.java
index b0dea305ac0..526c75d8020 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtModeQSimModule.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/run/DrtModeQSimModule.java
@@ -77,7 +77,7 @@ protected void configureQSim() {
}
if (drtCfg.getPrebookingParams().isPresent()) {
- install(new PrebookingModeQSimModule(getMode()));
+ install(new PrebookingModeQSimModule(getMode(), drtCfg.getPrebookingParams().get()));
bindModal(AdvanceRequestProvider.class).to(modalKey(PrebookingManager.class));
} else {
bindModal(AdvanceRequestProvider.class).toInstance(AdvanceRequestProvider.NONE);
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/schedule/DefaultDrtStopTask.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/schedule/DefaultDrtStopTask.java
index 21cb658a010..6efeb3fff96 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/schedule/DefaultDrtStopTask.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/schedule/DefaultDrtStopTask.java
@@ -84,4 +84,14 @@ public String toString() {
.add("super", super.toString())
.toString();
}
+
+ @Override
+ public void removePickupRequest(Id requestId) {
+ pickupRequests.remove(requestId);
+ }
+
+ @Override
+ public void removeDropoffRequest(Id requestId) {
+ dropoffRequests.remove(requestId);
+ }
}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/schedule/DrtStopTask.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/schedule/DrtStopTask.java
index f4e25dd79fd..3c2b5dd8351 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/schedule/DrtStopTask.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/schedule/DrtStopTask.java
@@ -42,4 +42,8 @@ public interface DrtStopTask extends StayTask {
void addDropoffRequest(AcceptedDrtRequest request);
void addPickupRequest(AcceptedDrtRequest request);
+
+ void removePickupRequest(Id requestId);
+
+ void removeDropoffRequest(Id requestId);
}
diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/speedup/DrtSpeedUp.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/speedup/DrtSpeedUp.java
index 978ddbc7417..5ad8dd776af 100644
--- a/contribs/drt/src/main/java/org/matsim/contrib/drt/speedup/DrtSpeedUp.java
+++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/speedup/DrtSpeedUp.java
@@ -27,8 +27,10 @@
import org.apache.commons.math3.stat.regression.SimpleRegression;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+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.population.Person;
import org.matsim.contrib.common.util.DistanceUtils;
import org.matsim.contrib.drt.analysis.DrtEventSequenceCollector;
import org.matsim.contrib.drt.passenger.events.DrtRequestSubmittedEvent;
@@ -192,18 +194,24 @@ private SimulatedTripStats computeSimulatedTripStats() {
continue;//skip incomplete sequences
}
DrtRequestSubmittedEvent submittedEvent = sequence.getSubmitted();
-
Link depLink = network.getLinks().get(submittedEvent.getFromLinkId());
Link arrLink = network.getLinks().get(submittedEvent.getToLinkId());
double beelineDistance = DistanceUtils.calculateDistance(depLink.getToNode(), arrLink.getToNode());
- double pickupTime = sequence.getPickedUp().get().getTime();
- double waitTime = pickupTime - sequence.getSubmitted().getTime();
- double rideTime = sequence.getDroppedOff().get().getTime() - pickupTime;
-
- //TODO I would map unshared_ride_time to rideTime -- should be more precise
- meanInVehicleBeelineSpeed.increment(beelineDistance / rideTime);
- meanWaitTime.increment(waitTime);
+ for (Id personId : submittedEvent.getPersonIds()) {
+ if(sequence.getPersonEvents().containsKey(personId)) {
+ DrtEventSequenceCollector.EventSequence.PersonEvents personEvents = sequence.getPersonEvents().get(personId);
+ if(personEvents.getPickedUp().isPresent() && personEvents.getDroppedOff().isPresent()) {
+ double pickupTime = personEvents.getPickedUp().get().getTime();
+ double waitTime = pickupTime - sequence.getSubmitted().getTime();
+ double rideTime = personEvents.getDroppedOff().get().getTime() - pickupTime;
+
+ //TODO I would map unshared_ride_time to rideTime -- should be more precise
+ meanInVehicleBeelineSpeed.increment(beelineDistance / rideTime);
+ meanWaitTime.increment(waitTime);
+ }
+ }
+ }
}
int count = (int)meanWaitTime.getN();
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/fare/DrtFareHandlerTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/fare/DrtFareHandlerTest.java
index 2751caf417e..e586e1a9dda 100644
--- a/contribs/drt/src/test/java/org/matsim/contrib/drt/fare/DrtFareHandlerTest.java
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/fare/DrtFareHandlerTest.java
@@ -31,6 +31,8 @@
import org.matsim.contrib.dvrp.passenger.PassengerDroppedOffEvent;
import org.matsim.core.events.ParallelEventsManager;
+import java.util.List;
+
/**
* @author jbischoff
*/
@@ -69,7 +71,7 @@ public void reset(int iteration) {
var personId = Id.createPersonId("p1");
{
var requestId = Id.create(0, Request.class);
- events.processEvent(new DrtRequestSubmittedEvent(0.0, mode, requestId, personId, Id.createLinkId("12"),
+ events.processEvent(new DrtRequestSubmittedEvent(0.0, mode, requestId, List.of(personId), Id.createLinkId("12"),
Id.createLinkId("23"), 240, 1000, 0.0, 0.0, 0.0));
events.processEvent(new PassengerDroppedOffEvent(300.0, mode, requestId, personId, null));
events.flush();
@@ -80,7 +82,7 @@ public void reset(int iteration) {
{
// test minFarePerTrip
var requestId = Id.create(1, Request.class);
- events.processEvent(new DrtRequestSubmittedEvent(0.0, mode, requestId, personId, Id.createLinkId("45"),
+ events.processEvent(new DrtRequestSubmittedEvent(0.0, mode, requestId, List.of(personId), Id.createLinkId("45"),
Id.createLinkId("56"), 24, 100, 0.0, 0.0, 0.0));
events.processEvent(new PassengerDroppedOffEvent(300.0, mode, requestId, personId, null));
events.finishProcessing();
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserterTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserterTest.java
index ee1e2efc29a..5730c59ea46 100644
--- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserterTest.java
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/DefaultUnplannedRequestInserterTest.java
@@ -29,10 +29,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Optional;
+import java.util.*;
import org.apache.commons.lang3.mutable.MutableInt;
import org.junit.Rule;
@@ -119,7 +116,7 @@ public void notScheduled_rejected() {
PassengerRequestRejectedEvent.class);
verify(eventsManager, times(1)).processEvent(captor.capture());
assertThat(captor.getValue()).isEqualToComparingFieldByField(
- new PassengerRequestRejectedEvent(now, mode, request1.getId(), request1.getPassengerId(),
+ new PassengerRequestRejectedEvent(now, mode, request1.getId(), request1.getPassengerIds(),
NO_INSERTION_FOUND_CAUSE));
}
@@ -248,7 +245,7 @@ public void acceptedRequest() {
PassengerRequestScheduledEvent.class);
verify(eventsManager, times(1)).processEvent(captor.capture());
assertThat(captor.getValue()).isEqualToComparingFieldByField(
- new PassengerRequestScheduledEvent(now, mode, request1.getId(), request1.getPassengerId(),
+ new PassengerRequestScheduledEvent(now, mode, request1.getId(), request1.getPassengerIds(),
vehicle1.getId(), pickupEndTime, dropoffBeginTime));
//vehicle entry was created twice:
@@ -276,7 +273,7 @@ private DvrpVehicle vehicle(String vehicleId) {
private DrtRequest request(String id, String fromLinkId, String toLinkId) {
return DrtRequest.newBuilder()
.id(Id.create(id, Request.class))
- .passengerId(Id.createPersonId(id))
+ .passengerIds(List.of(Id.createPersonId(id)))
.fromLink(link(fromLinkId))
.toLink(link(toLinkId))
.mode(mode)
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGeneratorTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGeneratorTest.java
index c433ced72db..18bb57661f2 100644
--- a/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGeneratorTest.java
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/optimizer/insertion/InsertionGeneratorTest.java
@@ -27,6 +27,7 @@
import java.util.Collections;
import java.util.List;
+import com.google.common.collect.Sets;
import org.junit.Test;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
@@ -64,7 +65,22 @@ public class InsertionGeneratorTest {
private final Link fromLink = link("from");
private final Link toLink = link("to");
- private final DrtRequest drtRequest = DrtRequest.newBuilder().fromLink(fromLink).toLink(toLink).build();
+ private final DrtRequest drtRequest = DrtRequest.newBuilder().fromLink(fromLink).toLink(toLink).passengerIds(List.of(Id.createPersonId("person"))).build();
+
+ private final DrtRequest drtRequest2Pax = DrtRequest.newBuilder().fromLink(fromLink).toLink(toLink).passengerIds(
+ List.of(
+ Id.createPersonId("person1"),
+ Id.createPersonId("person2")
+ )).build();
+
+ private final DrtRequest drtRequest5Pax = DrtRequest.newBuilder().fromLink(fromLink).toLink(toLink).passengerIds(
+ List.of(
+ Id.createPersonId("person1"),
+ Id.createPersonId("person2"),
+ Id.createPersonId("person3"),
+ Id.createPersonId("person4"),
+ Id.createPersonId("person5")
+ )).build();
private final DrtRequest prebookedRequest = DrtRequest.newBuilder().fromLink(fromLink).toLink(toLink).earliestStartTime(100).build();
private final Link depotLink = link("depot");
@@ -211,9 +227,9 @@ public void startEmpty_twoStops_notFullBetweenStops_tightSlackTimes() {
double[] slackTimes = { 0, 0, // impossible insertions: 00, 01, 02 (pickup at 0 is not possible)
500, // additional impossible insertions: 11 (too long total detour); however 12 is possible
1000 }; // 22 is possible
-
+
List precedingStayTimes = Arrays.asList(0.0, 0.0);
-
+
VehicleEntry entry = new VehicleEntry(vehicle, start, ImmutableList.of(stop0, stop1), slackTimes, precedingStayTimes, 0);
var insertions = new ArrayList();
@@ -348,7 +364,7 @@ public void noDetourForDropoff_vehicleOutgoingFullAfterDropoff_insertionPossible
//pickup after stop 0
new Insertion(drtRequest, entry, 2, 2));
}
-
+
@Test
public void startEmpty_prebookedRequest() {
Waypoint.Start start = new Waypoint.Start(null, link("start"), 0, 0);
@@ -395,6 +411,39 @@ public void startEmpty_prebookedRequest_afterAlreadyPrebookedOtherRequest() {
new Insertion(prebookedRequest, entry, 2, 2));
}
+
+ @Test
+ public void startEmpty_smallGroup() {
+ Waypoint.Start start = new Waypoint.Start(null, link("start"), 0, 0); //empty
+ VehicleEntry entry = entry(start);
+ assertInsertionsOnly(drtRequest2Pax, entry,
+ //pickup after start
+ new Insertion(drtRequest2Pax, entry, 0, 0));
+ }
+
+ @Test
+ public void startEmpty_groupExceedsCapacity() {
+ Waypoint.Start start = new Waypoint.Start(null, link("start"), 0, 0); //empty
+ VehicleEntry entry = entry(start);
+ assertInsertionsOnly(drtRequest5Pax, entry
+ //no insertion possible
+ );
+ }
+
+ @Test
+ public void startEmpty_twoStops_groupExceedsCapacityAtFirstStop() {
+ Waypoint.Start start = new Waypoint.Start(null, link("start"), 0, 0); //empty
+ Waypoint.Stop stop0 = stop(0, toLink, 3);//dropoff 1 pax
+ Waypoint.Stop stop1 = stop(0, link("stop1"), 0);//dropoff 1 pax
+ VehicleEntry entry = entry(start, stop0, stop1);
+ assertInsertionsOnly(drtRequest2Pax, entry,
+ //pickup after start:
+ new Insertion(drtRequest2Pax, entry, 0, 1),
+ //pickup after stop 1
+ new Insertion(drtRequest2Pax, entry, 2, 2)
+ );
+ }
+
private Link link(String id) {
return new FakeLink(Id.createLinkId(id));
}
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/AbandonAndCancelTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/AbandonAndCancelTest.java
new file mode 100644
index 00000000000..c3dc7f546b0
--- /dev/null
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/AbandonAndCancelTest.java
@@ -0,0 +1,268 @@
+package org.matsim.contrib.drt.prebooking;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.matsim.api.core.v01.Id;
+import org.matsim.api.core.v01.population.Leg;
+import org.matsim.contrib.drt.prebooking.PrebookingTestEnvironment.RequestInfo;
+import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule;
+import org.matsim.core.controler.Controler;
+import org.matsim.core.mobsim.framework.PlanAgent;
+import org.matsim.core.mobsim.framework.events.MobsimBeforeSimStepEvent;
+import org.matsim.core.mobsim.framework.listeners.MobsimBeforeSimStepListener;
+import org.matsim.core.mobsim.qsim.QSim;
+import org.matsim.core.router.TripStructureUtils;
+import org.matsim.testcases.MatsimTestUtils;
+
+/**
+ * @author Sebastian Hörl (sebhoerl) / IRT SystemX
+ */
+public class AbandonAndCancelTest {
+ @Rule
+ public MatsimTestUtils utils = new MatsimTestUtils();
+
+ @Test
+ public void noAbandonTest() {
+ /*
+ * One person requests to depart at 2000 and also is there at 2000. Another
+ * person asks also to depart at 2000, but only arrives at 4000, i.e. the person
+ * has 1000s delay. The vehicle should wait accordingly.
+ */
+
+ PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) //
+ .addVehicle("vehicle", 1, 1) //
+ .addRequest("personOk", 0, 0, 5, 5, 2000.0, 0.0, 2000.0) //
+ .addRequest("personLate", 0, 0, 5, 5, 4000.0, 0.0, 2000.0) //
+ .configure(600.0, 1.3, 600.0, 60.0) //
+ .endTime(10.0 * 3600.0);
+
+ Controler controller = environment.build();
+ PrebookingTest.installPrebooking(controller);
+ controller.run();
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personOk");
+ assertEquals(0.0, requestInfo.submissionTime, 1e-3);
+ assertEquals(2061.0, requestInfo.pickupTime, 1e-3);
+ assertEquals(4271.0, requestInfo.dropoffTime, 1e-3);
+ }
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personLate");
+ assertEquals(0.0, requestInfo.submissionTime, 1e-3);
+ assertEquals(4060.0, requestInfo.pickupTime, 1e-3);
+ assertEquals(4271.0, requestInfo.dropoffTime, 1e-3);
+ }
+ }
+
+ @Test
+ public void abandonTest() {
+ /*
+ * One person requests to depart at 2000 and also is there at 2000. Another
+ * person asks also to depart at 2000, but only arrives at 4000, i.e. the person
+ * has 1000s delay.
+ *
+ * We configure that the vehicle should leave without the passenger if it waits
+ * longer than 500s. The late request will be rejected!
+ */
+
+ PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) //
+ .addVehicle("vehicle", 1, 1) //
+ .addRequest("personOk", 0, 0, 5, 5, 2000.0, 0.0, 2000.0) //
+ .addRequest("personLate", 0, 0, 5, 5, 4000.0, 0.0, 2000.0) //
+ .configure(600.0, 1.3, 600.0, 60.0) //
+ .endTime(10.0 * 3600.0);
+
+ Controler controller = environment.build();
+ PrebookingParams parameters = PrebookingTest.installPrebooking(controller);
+ parameters.maximumPassengerDelay = 500.0;
+ controller.run();
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personOk");
+ assertEquals(0.0, requestInfo.submissionTime, 1e-3);
+ assertEquals(2061.0, requestInfo.pickupTime, 1e-3);
+ assertEquals(2713.0, requestInfo.dropoffTime, 1e-3);
+ }
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personLate");
+ assertEquals(0.0, requestInfo.submissionTimes.get(0), 1e-3);
+ // agent tries a non-prebooked request upon arrival
+ assertEquals(4000.0, requestInfo.submissionTimes.get(1), 1e-3);
+ assertTrue(requestInfo.rejected);
+ }
+ }
+
+ @Test
+ public void abandonThenImmediateTest() {
+ /*
+ * One person requests to depart at 2000 and also is there at 2000. Another
+ * person asks also to depart at 2000, but only arrives at 4000, i.e. the person
+ * has 1000s delay.
+ *
+ * We configure that the vehicle should leave without the passenger if it waits
+ * longer than 500s. The person will, however, send a new request when arriving
+ * at the departure point and get an immediate vehicle.
+ */
+
+ PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) //
+ .addVehicle("vehicle", 1, 1) //
+ .addVehicle("vehicle2", 1, 1) //
+ .addRequest("personOk", 0, 0, 5, 5, 2000.0, 0.0, 2000.0) //
+ .addRequest("personLate", 0, 0, 5, 5, 4000.0, 0.0, 2000.0) //
+ .configure(600.0, 1.3, 600.0, 60.0) //
+ .endTime(10.0 * 3600.0);
+
+ Controler controller = environment.build();
+ PrebookingParams parameters = PrebookingTest.installPrebooking(controller);
+ parameters.maximumPassengerDelay = 500.0;
+ controller.run();
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personOk");
+ assertEquals(0.0, requestInfo.submissionTime, 1e-3);
+ assertEquals(2061.0, requestInfo.pickupTime, 1e-3);
+ assertEquals(2713.0, requestInfo.dropoffTime, 1e-3);
+ }
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personLate");
+ assertEquals(0.0, requestInfo.submissionTimes.get(0), 1e-3);
+ // agent tries a non-prebooked request upon arrival
+ assertEquals(4000.0, requestInfo.submissionTimes.get(1), 1e-3);
+ assertEquals(4146.0, requestInfo.pickupTime, 1e-3);
+ assertEquals(4357.0, requestInfo.dropoffTime, 1e-3);
+ assertTrue(requestInfo.rejected);
+ }
+ }
+
+ @Test
+ public void cancelEarlyTest() {
+ /*
+ * One person requests to depart at 2000 and also is there at 2000. Another
+ * person asks also to depart at 2000, but only arrives at 4000, i.e. the person
+ * has 1000s delay.
+ *
+ * In this test we manually cancel the second request at 500.0 (so before
+ * departure of any agent).
+ */
+
+ PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) //
+ .addVehicle("vehicle", 1, 1) //
+ .addRequest("personOk", 0, 0, 5, 5, 2000.0, 0.0, 2000.0) //
+ .addRequest("personLate", 0, 0, 5, 5, 4000.0, 0.0, 2000.0) //
+ .configure(600.0, 1.3, 600.0, 60.0) //
+ .endTime(10.0 * 3600.0);
+
+ Controler controller = environment.build();
+ PrebookingTest.installPrebooking(controller);
+
+ controller.addOverridingQSimModule(new AbstractDvrpModeQSimModule("drt") {
+ @Override
+ protected void configureQSim() {
+ addModalQSimComponentBinding().toProvider(modalProvider(getter -> {
+ PrebookingManager prebookingManager = getter.getModal(PrebookingManager.class);
+ QSim qsim = getter.get(QSim.class);
+
+ return new MobsimBeforeSimStepListener() {
+ @Override
+ public void notifyMobsimBeforeSimStep(MobsimBeforeSimStepEvent e) {
+ if (e.getSimulationTime() == 500.0) {
+ PlanAgent planAgent = (PlanAgent) qsim.getAgents()
+ .get(Id.createPersonId("personLate"));
+
+ Leg leg = TripStructureUtils.getLegs(planAgent.getCurrentPlan()).get(1);
+
+ prebookingManager.cancel(leg);
+ }
+ }
+ };
+ }));
+ }
+ });
+
+ controller.run();
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personOk");
+ assertEquals(0.0, requestInfo.submissionTime, 1e-3);
+ assertEquals(2061.0, requestInfo.pickupTime, 1e-3);
+ assertEquals(2272.0, requestInfo.dropoffTime, 1e-3);
+ }
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personLate");
+ assertEquals(0.0, requestInfo.submissionTimes.get(0), 1e-3);
+ // agent tries a non-prebooked request upon arrival
+ assertEquals(4000.0, requestInfo.submissionTimes.get(1), 1e-3);
+ assertTrue(requestInfo.rejected);
+ }
+ }
+
+ @Test
+ public void cancelLateTest() {
+ /*
+ * One person requests to depart at 2000 and also is there at 2000. Another
+ * person asks also to depart at 2000, but only arrives at 4000, i.e. the person
+ * has 1000s delay.
+ *
+ * In this test we manually cancel the second request at 3000.0 (so after
+ * departure of the first agent).
+ */
+
+ PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) //
+ .addVehicle("vehicle", 1, 1) //
+ .addRequest("personOk", 0, 0, 5, 5, 2000.0, 0.0, 2000.0) //
+ .addRequest("personLate", 0, 0, 5, 5, 4000.0, 0.0, 2000.0) //
+ .configure(600.0, 1.3, 600.0, 60.0) //
+ .endTime(10.0 * 3600.0);
+
+ Controler controller = environment.build();
+ PrebookingTest.installPrebooking(controller);
+
+ controller.addOverridingQSimModule(new AbstractDvrpModeQSimModule("drt") {
+ @Override
+ protected void configureQSim() {
+ addModalQSimComponentBinding().toProvider(modalProvider(getter -> {
+ PrebookingManager prebookingManager = getter.getModal(PrebookingManager.class);
+ QSim qsim = getter.get(QSim.class);
+
+ return new MobsimBeforeSimStepListener() {
+ @Override
+ public void notifyMobsimBeforeSimStep(MobsimBeforeSimStepEvent e) {
+ if (e.getSimulationTime() == 3000.0) {
+ PlanAgent planAgent = (PlanAgent) qsim.getAgents()
+ .get(Id.createPersonId("personLate"));
+
+ Leg leg = TripStructureUtils.getLegs(planAgent.getCurrentPlan()).get(1);
+
+ prebookingManager.cancel(leg);
+ }
+ }
+ };
+ }));
+ }
+ });
+
+ controller.run();
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personOk");
+ assertEquals(0.0, requestInfo.submissionTime, 1e-3);
+ assertEquals(2061.0, requestInfo.pickupTime, 1e-3);
+ assertEquals(3212.0, requestInfo.dropoffTime, 1e-3); // still waited quite a bit
+ }
+
+ {
+ RequestInfo requestInfo = environment.getRequestInfo().get("personLate");
+ assertEquals(0.0, requestInfo.submissionTimes.get(0), 1e-3);
+ // agent tries a non-prebooked request upon arrival
+ assertEquals(4000.0, requestInfo.submissionTimes.get(1), 1e-3);
+ assertTrue(requestInfo.rejected);
+ }
+ }
+}
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/ComplexUnschedulerTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/ComplexUnschedulerTest.java
new file mode 100644
index 00000000000..84c3ada7791
--- /dev/null
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/ComplexUnschedulerTest.java
@@ -0,0 +1,688 @@
+package org.matsim.contrib.drt.prebooking;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.junit.Test;
+import org.matsim.api.core.v01.Coord;
+import org.matsim.api.core.v01.Id;
+import org.matsim.api.core.v01.network.Link;
+import org.matsim.api.core.v01.network.Network;
+import org.matsim.api.core.v01.network.NetworkFactory;
+import org.matsim.api.core.v01.network.Node;
+import org.matsim.contrib.drt.optimizer.VehicleDataEntryFactoryImpl;
+import org.matsim.contrib.drt.optimizer.VehicleEntry;
+import org.matsim.contrib.drt.passenger.AcceptedDrtRequest;
+import org.matsim.contrib.drt.prebooking.unscheduler.ComplexRequestUnscheduler;
+import org.matsim.contrib.drt.run.DrtConfigGroup;
+import org.matsim.contrib.drt.schedule.DefaultDrtStopTask;
+import org.matsim.contrib.drt.schedule.DrtDriveTask;
+import org.matsim.contrib.drt.schedule.DrtStayTask;
+import org.matsim.contrib.drt.schedule.DrtStopTask;
+import org.matsim.contrib.drt.schedule.DrtTaskFactory;
+import org.matsim.contrib.drt.schedule.DrtTaskFactoryImpl;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicleImpl;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicleLookup;
+import org.matsim.contrib.dvrp.fleet.DvrpVehicleSpecification;
+import org.matsim.contrib.dvrp.fleet.ImmutableDvrpVehicleSpecification;
+import org.matsim.contrib.dvrp.optimizer.Request;
+import org.matsim.contrib.dvrp.path.DivertedVrpPath;
+import org.matsim.contrib.dvrp.path.VrpPathWithTravelData;
+import org.matsim.contrib.dvrp.path.VrpPaths;
+import org.matsim.contrib.dvrp.schedule.DriveTask;
+import org.matsim.contrib.dvrp.schedule.Schedule;
+import org.matsim.contrib.dvrp.schedule.ScheduleTimingUpdater;
+import org.matsim.contrib.dvrp.schedule.StayTask;
+import org.matsim.contrib.dvrp.schedule.Task;
+import org.matsim.contrib.dvrp.tracker.OnlineDriveTaskTracker;
+import org.matsim.contrib.dvrp.util.LinkTimePair;
+import org.matsim.core.network.NetworkUtils;
+import org.matsim.core.router.DijkstraFactory;
+import org.matsim.core.router.costcalculators.OnlyTimeDependentTravelDisutility;
+import org.matsim.core.router.util.LeastCostPathCalculator;
+import org.matsim.core.router.util.TravelTime;
+import org.matsim.core.trafficmonitoring.FreeSpeedTravelTime;
+import org.mockito.Mockito;
+
+/**
+ * @author Sebastian Hörl (sebhoerl) / IRT SystemX
+ */
+public class ComplexUnschedulerTest {
+ @Test
+ public void testDirectDropoffAfterPickup() {
+ Fixture fixture = new Fixture();
+ Schedule schedule = fixture.schedule;
+
+ AcceptedDrtRequest otherRequest1 = fixture.createRequest();
+ AcceptedDrtRequest otherRequest2 = fixture.createRequest();
+ AcceptedDrtRequest unscheduleRequest = fixture.createRequest();
+
+ fixture.addDrive("f10");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(otherRequest1);
+ fixture.addDrive("f20");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(otherRequest2);
+ fixture.addDrive("f30"); // replace start
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(unscheduleRequest);
+ fixture.addDrive("f40");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(unscheduleRequest);
+ fixture.addDrive("f50");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addDropoffRequest(otherRequest2);
+ fixture.addDrive("f60");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(otherRequest1);
+ fixture.addFinalStay(30.0 * 3600.0);
+
+ schedule.nextTask();
+ schedule.nextTask();
+
+ ComplexRequestUnscheduler unscheduler = new ComplexRequestUnscheduler(fixture.lookup, fixture.entryFactory,
+ fixture.taskFactory, fixture.router, fixture.travelTime, fixture.timingUpdater, false);
+
+ unscheduler.unscheduleRequest(100.0, fixture.vehicle.getId(), unscheduleRequest.getId());
+
+ assertEquals(13, schedule.getTaskCount());
+
+ assertTasks(schedule, Arrays.asList( //
+ new ReferenceTask(DrtDriveTask.class, 0.0, 10001.0), // 0
+ new ReferenceTask(DrtStayTask.class, 10001.0, 10301.0), // 1
+ new ReferenceTask(DefaultDrtStopTask.class, 10301.0, 10361.0), // 2
+ new ReferenceTask(DrtDriveTask.class, 10361.0, 20362.0), // 3
+ new ReferenceTask(DrtStayTask.class, 20362.0, 20662.0), // 4
+ new ReferenceTask(DefaultDrtStopTask.class, 20662.0, 20722.0), // 5
+ new ReferenceTask(DrtDriveTask.class, 20722.0, 50723.0), // 6
+ new ReferenceTask(DrtStayTask.class, 50723.0, 51745.0), // 7
+ new ReferenceTask(DefaultDrtStopTask.class, 51745.0, 51805.0), // 8
+ new ReferenceTask(DrtDriveTask.class, 51805.0, 61806.0), // 9
+ new ReferenceTask(DrtStayTask.class, 61806.0, 62106.0), // 10
+ new ReferenceTask(DefaultDrtStopTask.class, 62106.0, 62166.0), // 11
+ new ReferenceTask(DrtStayTask.class, 62166.0, 30.0 * 3600.0) // 12
+ ));
+
+ DrtDriveTask insertedDriveTask = (DrtDriveTask) schedule.getTasks().get(6);
+ assertEquals("f20", insertedDriveTask.getPath().getFromLink().getId().toString());
+ assertEquals("f50", insertedDriveTask.getPath().getToLink().getId().toString());
+ }
+
+ @Test
+ public void testStandardSituation() {
+ Fixture fixture = new Fixture();
+ Schedule schedule = fixture.schedule;
+
+ AcceptedDrtRequest otherRequest1 = fixture.createRequest();
+ AcceptedDrtRequest otherRequest2 = fixture.createRequest();
+ AcceptedDrtRequest unscheduleRequest = fixture.createRequest();
+
+ fixture.addDrive("f10");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(otherRequest1); // f10
+ fixture.addDrive("f20"); // replace start
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(unscheduleRequest); // f20
+ fixture.addDrive("f30");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addPickupRequest(otherRequest2); // f30
+ fixture.addDrive("f40");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(otherRequest2); // f40
+ fixture.addDrive("f50"); // replace start
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(unscheduleRequest); // f50
+ fixture.addDrive("f60");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addDropoffRequest(otherRequest1); // f60
+ fixture.addFinalStay(30.0 * 3600.0);
+
+ schedule.nextTask();
+ schedule.nextTask();
+
+ ComplexRequestUnscheduler unscheduler = new ComplexRequestUnscheduler(fixture.lookup, fixture.entryFactory,
+ fixture.taskFactory, fixture.router, fixture.travelTime, fixture.timingUpdater, false);
+
+ unscheduler.unscheduleRequest(100.0, fixture.vehicle.getId(), unscheduleRequest.getId());
+
+ assertEquals(13, schedule.getTaskCount());
+
+ assertTasks(schedule, Arrays.asList( //
+ new ReferenceTask(DrtDriveTask.class, 0.0, 10001.0), // 0
+ new ReferenceTask(DrtStayTask.class, 10001.0, 10301.0), // 1
+ new ReferenceTask(DefaultDrtStopTask.class, 10301.0, 10361.0), // 2
+ new ReferenceTask(DrtDriveTask.class, 10361.0, 30362.0), // 3
+ new ReferenceTask(DrtStayTask.class, 30362.0, 31023.0), // 4
+ new ReferenceTask(DefaultDrtStopTask.class, 31023.0, 31083.0), // 5
+ new ReferenceTask(DrtDriveTask.class, 31083.0, 41084.0), // 6
+ new ReferenceTask(DrtStayTask.class, 41084.0, 41384.0), // 7
+ new ReferenceTask(DefaultDrtStopTask.class, 41384.0, 41444.0), // 8
+ new ReferenceTask(DrtDriveTask.class, 41444.0, 61445.0), // 9
+ new ReferenceTask(DrtStayTask.class, 61445.0, 62106.0), // 10
+ new ReferenceTask(DefaultDrtStopTask.class, 62106.0, 62166.0), // 11
+ new ReferenceTask(DrtStayTask.class, 62166.0, 30.0 * 3600.0) // 12
+ ));
+
+ DrtDriveTask insertedDriveTask = (DrtDriveTask) schedule.getTasks().get(3);
+ assertEquals("f10", insertedDriveTask.getPath().getFromLink().getId().toString());
+ assertEquals("f30", insertedDriveTask.getPath().getToLink().getId().toString());
+
+ DrtDriveTask insertedDriveTask2 = (DrtDriveTask) schedule.getTasks().get(9);
+ assertEquals("f40", insertedDriveTask2.getPath().getFromLink().getId().toString());
+ assertEquals("f60", insertedDriveTask2.getPath().getToLink().getId().toString());
+ }
+
+ @Test
+ public void testRemoveAtEnd() {
+ Fixture fixture = new Fixture();
+ Schedule schedule = fixture.schedule;
+
+ AcceptedDrtRequest otherRequest1 = fixture.createRequest();
+ AcceptedDrtRequest otherRequest2 = fixture.createRequest();
+ AcceptedDrtRequest unscheduleRequest = fixture.createRequest();
+
+ fixture.addDrive("f10");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(otherRequest1); // f10
+ fixture.addDrive("f20"); // replace start
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(unscheduleRequest); // f20
+ fixture.addDrive("f30");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addPickupRequest(otherRequest2); // f30
+ fixture.addDrive("f40");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(otherRequest2); // f40
+ fixture.addDrive("f50");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(otherRequest1); // f50
+ fixture.addDrive("f60"); // replace start
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(unscheduleRequest); // f60 // replace end
+ fixture.addFinalStay(30.0 * 3600.0);
+
+ schedule.nextTask();
+ schedule.nextTask();
+
+ ComplexRequestUnscheduler unscheduler = new ComplexRequestUnscheduler(fixture.lookup, fixture.entryFactory,
+ fixture.taskFactory, fixture.router, fixture.travelTime, fixture.timingUpdater, false);
+
+ unscheduler.unscheduleRequest(100.0, fixture.vehicle.getId(), unscheduleRequest.getId());
+
+ assertTasks(schedule, Arrays.asList( //
+ new ReferenceTask(DrtDriveTask.class, 0.0, 10001.0), // 0
+ new ReferenceTask(DrtStayTask.class, 10001.0, 10301.0), // 1
+ new ReferenceTask(DefaultDrtStopTask.class, 10301.0, 10361.0), // 2
+ new ReferenceTask(DrtDriveTask.class, 10361.0, 30362.0), // 3
+ new ReferenceTask(DrtStayTask.class, 30362.0, 31023.0), // 4
+ new ReferenceTask(DefaultDrtStopTask.class, 31023.0, 31083.0), // 5
+ new ReferenceTask(DrtDriveTask.class, 31083.0, 41084.0), // 6
+ new ReferenceTask(DrtStayTask.class, 41084.0, 41384.0), // 7
+ new ReferenceTask(DefaultDrtStopTask.class, 41384.0, 41444.0), // 8
+ new ReferenceTask(DrtDriveTask.class, 41444.0, 51445.0), // 9
+ new ReferenceTask(DrtStayTask.class, 51445.0, 51745.0), // 10
+ new ReferenceTask(DefaultDrtStopTask.class, 51745.0, 51805.0), // 11
+ new ReferenceTask(DrtStayTask.class, 51805.0, 30.0 * 3600.0) // 12
+ ));
+
+ DrtDriveTask insertedDriveTask = (DrtDriveTask) schedule.getTasks().get(3);
+ assertEquals("f10", insertedDriveTask.getPath().getFromLink().getId().toString());
+ assertEquals("f30", insertedDriveTask.getPath().getToLink().getId().toString());
+
+ DrtDriveTask insertedDriveTask2 = (DrtDriveTask) schedule.getTasks().get(9);
+ assertEquals("f40", insertedDriveTask2.getPath().getFromLink().getId().toString());
+ assertEquals("f50", insertedDriveTask2.getPath().getToLink().getId().toString());
+
+ DrtStayTask stayTask = (DrtStayTask) schedule.getTasks().get(12);
+ assertEquals("f50", stayTask.getLink().getId().toString());
+ }
+
+ @Test
+ public void testRemoveAtBeginningWithWaitSecond() {
+ Fixture fixture = new Fixture();
+ Schedule schedule = fixture.schedule;
+
+ AcceptedDrtRequest otherRequest1 = fixture.createRequest();
+ AcceptedDrtRequest otherRequest2 = fixture.createRequest();
+ AcceptedDrtRequest unscheduleRequest = fixture.createRequest();
+
+ fixture.addDrive("f10");
+ fixture.addWait(300.0); // replace start
+ fixture.addStop(60.0).addPickupRequest(unscheduleRequest); // f10
+ fixture.addDrive("f20");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addPickupRequest(otherRequest1); // f20
+ fixture.addDrive("f30");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(otherRequest2); // f30
+ fixture.addDrive("f40");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(otherRequest2); // f40
+ fixture.addDrive("f50"); // replace start
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(unscheduleRequest); // f50
+ fixture.addDrive("f60");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addDropoffRequest(otherRequest1); // f60
+ fixture.addFinalStay(30.0 * 3600.0);
+
+ schedule.nextTask();
+ schedule.nextTask();
+
+ ComplexRequestUnscheduler unscheduler = new ComplexRequestUnscheduler(fixture.lookup, fixture.entryFactory,
+ fixture.taskFactory, fixture.router, fixture.travelTime, fixture.timingUpdater, false);
+
+ unscheduler.unscheduleRequest(10100.0, fixture.vehicle.getId(), unscheduleRequest.getId());
+
+ assertEquals(15, schedule.getTaskCount());
+
+ assertTasks(schedule, Arrays.asList( //
+ new ReferenceTask(DrtDriveTask.class, 0.0, 10001.0), // 0
+ new ReferenceTask(DrtStayTask.class, 10001.0, 10100.0), // 1
+ new ReferenceTask(DrtDriveTask.class, 10100.0, 20101.0), // 2
+ new ReferenceTask(DrtStayTask.class, 20101.0, 20662.0), // 3
+ new ReferenceTask(DefaultDrtStopTask.class, 20662.0, 20722.0), // 4
+ new ReferenceTask(DrtDriveTask.class, 20722.0, 30723.0), // 5
+ new ReferenceTask(DrtStayTask.class, 30723.0, 31023.0), // 6
+ new ReferenceTask(DefaultDrtStopTask.class, 31023.0, 31083.0), // 7
+ new ReferenceTask(DrtDriveTask.class, 31083.0, 41084.0), // 8
+ new ReferenceTask(DrtStayTask.class, 41084.0, 41384.0), // 9
+ new ReferenceTask(DefaultDrtStopTask.class, 41384.0, 41444.0), // 10
+ new ReferenceTask(DrtDriveTask.class, 41444.0, 61445.0), // 11
+ new ReferenceTask(DrtStayTask.class, 61445.0, 62106.0), // 12
+ new ReferenceTask(DefaultDrtStopTask.class, 62106.0, 62166.0), // 13
+ new ReferenceTask(DrtStayTask.class, 62166.0, 30.0 * 3600.0) // 14
+ ));
+
+ DrtDriveTask insertedDriveTask = (DrtDriveTask) schedule.getTasks().get(2);
+ assertEquals("f10", insertedDriveTask.getPath().getFromLink().getId().toString());
+ assertEquals("f20", insertedDriveTask.getPath().getToLink().getId().toString());
+
+ DrtDriveTask insertedDriveTask2 = (DrtDriveTask) schedule.getTasks().get(11);
+ assertEquals("f40", insertedDriveTask2.getPath().getFromLink().getId().toString());
+ assertEquals("f60", insertedDriveTask2.getPath().getToLink().getId().toString());
+ }
+
+ @Test
+ public void testRemoveAtBeginningWithWaitFirst() {
+ Fixture fixture = new Fixture();
+ Schedule schedule = fixture.schedule;
+
+ AcceptedDrtRequest otherRequest1 = fixture.createRequest();
+ AcceptedDrtRequest otherRequest2 = fixture.createRequest();
+ AcceptedDrtRequest unscheduleRequest = fixture.createRequest();
+
+ fixture.addWait(300.0); // replace start
+ fixture.addDrive("f10");
+ fixture.addStop(60.0).addPickupRequest(unscheduleRequest); // f10
+ fixture.addDrive("f20");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addPickupRequest(otherRequest1); // f20
+ fixture.addDrive("f30");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(otherRequest2); // f30
+ fixture.addDrive("f40");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(otherRequest2); // f40
+ fixture.addDrive("f50"); // replace start
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(unscheduleRequest); // f50
+ fixture.addDrive("f60");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addDropoffRequest(otherRequest1); // f60
+ fixture.addFinalStay(30.0 * 3600.0);
+
+ schedule.nextTask();
+
+ ComplexRequestUnscheduler unscheduler = new ComplexRequestUnscheduler(fixture.lookup, fixture.entryFactory,
+ fixture.taskFactory, fixture.router, fixture.travelTime, fixture.timingUpdater, false);
+
+ unscheduler.unscheduleRequest(500.0, fixture.vehicle.getId(), unscheduleRequest.getId());
+
+ assertEquals(14, schedule.getTaskCount());
+
+ assertTasks(schedule, Arrays.asList( //
+ new ReferenceTask(DrtStayTask.class, 0.0, 500.0), // 0
+ new ReferenceTask(DrtDriveTask.class, 500.0, 20501.0), // 1
+ new ReferenceTask(DrtStayTask.class, 20501.0, 20662.0), // 2
+ new ReferenceTask(DefaultDrtStopTask.class, 20662.0, 20722.0), // 3
+ new ReferenceTask(DrtDriveTask.class, 20722.0, 30723.0), // 4
+ new ReferenceTask(DrtStayTask.class, 30723.0, 31023.0), // 5
+ new ReferenceTask(DefaultDrtStopTask.class, 31023.0, 31083.0), // 6
+ new ReferenceTask(DrtDriveTask.class, 31083.0, 41084.0), // 7
+ new ReferenceTask(DrtStayTask.class, 41084.0, 41384.0), // 8
+ new ReferenceTask(DefaultDrtStopTask.class, 41384.0, 41444.0), // 9
+ new ReferenceTask(DrtDriveTask.class, 41444.0, 61445.0), // 10
+ new ReferenceTask(DrtStayTask.class, 61445.0, 62106.0), // 11
+ new ReferenceTask(DefaultDrtStopTask.class, 62106.0, 62166.0), // 12
+ new ReferenceTask(DrtStayTask.class, 62166.0, 30.0 * 3600.0) // 13
+ ));
+
+ DrtDriveTask insertedDriveTask = (DrtDriveTask) schedule.getTasks().get(1);
+ assertEquals("f0", insertedDriveTask.getPath().getFromLink().getId().toString());
+ assertEquals("f20", insertedDriveTask.getPath().getToLink().getId().toString());
+
+ DrtDriveTask insertedDriveTask2 = (DrtDriveTask) schedule.getTasks().get(10);
+ assertEquals("f40", insertedDriveTask2.getPath().getFromLink().getId().toString());
+ assertEquals("f60", insertedDriveTask2.getPath().getToLink().getId().toString());
+ }
+
+ @Test
+ public void testRemoveAtBeginningWithDriveDiversion() {
+ Fixture fixture = new Fixture();
+ Schedule schedule = fixture.schedule;
+
+ AcceptedDrtRequest otherRequest1 = fixture.createRequest();
+ AcceptedDrtRequest otherRequest2 = fixture.createRequest();
+ AcceptedDrtRequest unscheduleRequest = fixture.createRequest();
+
+ fixture.addDrive("f10");
+ fixture.addWait(300.0); // replace start
+ fixture.addStop(60.0).addPickupRequest(unscheduleRequest); // f10
+ fixture.addDrive("f20");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addPickupRequest(otherRequest1); // f20
+ fixture.addDrive("f30");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(otherRequest2); // f30
+ fixture.addDrive("f40");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(otherRequest2); // f40
+ fixture.addDrive("f50"); // replace start
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(unscheduleRequest); // f50
+ fixture.addDrive("f60");
+ fixture.addWait(300.0); // replace end
+ fixture.addStop(60.0).addDropoffRequest(otherRequest1); // f60
+ fixture.addFinalStay(30.0 * 3600.0);
+
+ schedule.nextTask();
+
+ OnlineDriveTaskTracker tracker = Mockito.mock(OnlineDriveTaskTracker.class);
+ schedule.getTasks().get(0).initTaskTracker(tracker);
+
+ LinkTimePair diversionPoint = new LinkTimePair(fixture.network.getLinks().get(Id.createLinkId("f5")), 20.0);
+ Mockito.when(tracker.getDiversionPoint()).thenReturn(diversionPoint);
+
+ Mockito.doAnswer(invocation -> {
+ VrpPathWithTravelData path = invocation.getArgument(0);
+ DriveTask task = (DriveTask) schedule.getTasks().get(0);
+ DivertedVrpPath divertedPath = new DivertedVrpPath(task.getPath(), path, 5);
+ task.pathDiverted(divertedPath, path.getArrivalTime());
+ return null;
+ }).when(tracker).divertPath(Mockito.any());
+
+ ComplexRequestUnscheduler unscheduler = new ComplexRequestUnscheduler(fixture.lookup, fixture.entryFactory,
+ fixture.taskFactory, fixture.router, fixture.travelTime, fixture.timingUpdater, false);
+
+ unscheduler.unscheduleRequest(500.0, fixture.vehicle.getId(), unscheduleRequest.getId());
+
+ assertEquals(13, schedule.getTaskCount());
+
+ assertTasks(schedule, Arrays.asList( //
+ new ReferenceTask(DrtDriveTask.class, 0.0, 15019.0), // 0
+ new ReferenceTask(DrtStayTask.class, 15019.0, 20662.0), // 1
+ new ReferenceTask(DefaultDrtStopTask.class, 20662.0, 20722.0), // 2
+ new ReferenceTask(DrtDriveTask.class, 20722.0, 30723.0), // 3
+ new ReferenceTask(DrtStayTask.class, 30723.0, 31023.0), // 4
+ new ReferenceTask(DefaultDrtStopTask.class, 31023.0, 31083.0), // 5
+ new ReferenceTask(DrtDriveTask.class, 31083.0, 41084.0), // 6
+ new ReferenceTask(DrtStayTask.class, 41084.0, 41384.0), // 7
+ new ReferenceTask(DefaultDrtStopTask.class, 41384.0, 41444.0), // 8
+ new ReferenceTask(DrtDriveTask.class, 41444.0, 61445.0), // 9
+ new ReferenceTask(DrtStayTask.class, 61445.0, 62106.0), // 10
+ new ReferenceTask(DefaultDrtStopTask.class, 62106.0, 62166.0), // 11
+ new ReferenceTask(DrtStayTask.class, 62166.0, 30.0 * 3600.0) // 12
+ ));
+
+ DrtDriveTask insertedDriveTask = (DrtDriveTask) schedule.getTasks().get(0);
+ assertEquals("f0", insertedDriveTask.getPath().getFromLink().getId().toString());
+ assertEquals("f20", insertedDriveTask.getPath().getToLink().getId().toString());
+
+ DrtDriveTask insertedDriveTask2 = (DrtDriveTask) schedule.getTasks().get(9);
+ assertEquals("f40", insertedDriveTask2.getPath().getFromLink().getId().toString());
+ assertEquals("f60", insertedDriveTask2.getPath().getToLink().getId().toString());
+ }
+
+ @Test
+ public void testRemoveAllStartWithWait() {
+ Fixture fixture = new Fixture();
+ Schedule schedule = fixture.schedule;
+
+ AcceptedDrtRequest unscheduleRequest = fixture.createRequest();
+
+ fixture.addWait(300.0);
+ fixture.addDrive("f10");
+ fixture.addStop(60.0).addPickupRequest(unscheduleRequest); // f10
+ fixture.addDrive("f20");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(unscheduleRequest); // f50
+ fixture.addFinalStay(30.0 * 3600.0);
+
+ schedule.nextTask();
+
+ ComplexRequestUnscheduler unscheduler = new ComplexRequestUnscheduler(fixture.lookup, fixture.entryFactory,
+ fixture.taskFactory, fixture.router, fixture.travelTime, fixture.timingUpdater, false);
+
+ unscheduler.unscheduleRequest(0.0, fixture.vehicle.getId(), unscheduleRequest.getId());
+
+ assertEquals(2, schedule.getTaskCount());
+
+ assertTasks(schedule, Arrays.asList( //
+ new ReferenceTask(DrtStayTask.class, 0.0, 0.0), // 0
+ new ReferenceTask(DrtStayTask.class, 0.0, 30.0 * 3600.0) // 1
+ ));
+
+ assertEquals("f0", ((StayTask) schedule.getTasks().get(0)).getLink().getId().toString());
+ assertEquals("f0", ((StayTask) schedule.getTasks().get(1)).getLink().getId().toString());
+ }
+
+ @Test
+ public void testRemoveAllStartWithDrive() {
+ Fixture fixture = new Fixture();
+ Schedule schedule = fixture.schedule;
+
+ AcceptedDrtRequest unscheduleRequest = fixture.createRequest();
+
+ fixture.addDrive("f10");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addPickupRequest(unscheduleRequest); // f10
+ fixture.addDrive("f20");
+ fixture.addWait(300.0);
+ fixture.addStop(60.0).addDropoffRequest(unscheduleRequest); // f50
+ fixture.addFinalStay(30.0 * 3600.0);
+
+ schedule.nextTask();
+
+ OnlineDriveTaskTracker tracker = Mockito.mock(OnlineDriveTaskTracker.class);
+ schedule.getTasks().get(0).initTaskTracker(tracker);
+
+ LinkTimePair diversionPoint = new LinkTimePair(fixture.network.getLinks().get(Id.createLinkId("f5")), 20.0);
+ Mockito.when(tracker.getDiversionPoint()).thenReturn(diversionPoint);
+
+ Mockito.doAnswer(invocation -> {
+ VrpPathWithTravelData path = invocation.getArgument(0);
+ DriveTask task = (DriveTask) schedule.getTasks().get(0);
+ DivertedVrpPath divertedPath = new DivertedVrpPath(task.getPath(), path, 5);
+ task.pathDiverted(divertedPath, path.getArrivalTime());
+ return null;
+ }).when(tracker).divertPath(Mockito.any());
+
+ ComplexRequestUnscheduler unscheduler = new ComplexRequestUnscheduler(fixture.lookup, fixture.entryFactory,
+ fixture.taskFactory, fixture.router, fixture.travelTime, fixture.timingUpdater, false);
+
+ unscheduler.unscheduleRequest(0.0, fixture.vehicle.getId(), unscheduleRequest.getId());
+
+ assertEquals(2, schedule.getTaskCount());
+
+ assertTasks(schedule, Arrays.asList( //
+ new ReferenceTask(DrtDriveTask.class, 0.0, 19.0), // 0
+ new ReferenceTask(DrtStayTask.class, 19.0, 30.0 * 3600.0) // 1
+ ));
+
+ DrtDriveTask driveTask = (DrtDriveTask) schedule.getTasks().get(0);
+ assertEquals("f0", driveTask.getPath().getFromLink().getId().toString());
+ assertEquals("f5", driveTask.getPath().getToLink().getId().toString());
+
+ assertEquals("f5", ((StayTask) schedule.getTasks().get(1)).getLink().getId().toString());
+ }
+
+ record ReferenceTask(Class extends Task> taskType, double startTime, double endTime) {
+ }
+
+ private static void assertTasks(Schedule schedule, List references) {
+ for (int i = 0; i < references.size(); i++) {
+ Task task = schedule.getTasks().get(i);
+ ReferenceTask reference = references.get(i);
+
+ assertEquals("wrong type in task " + i, reference.taskType, task.getClass());
+ assertEquals("wrong begin time in task " + i, reference.startTime, task.getBeginTime(), 1e-3);
+ assertEquals("wrong end time in task " + i, reference.endTime, task.getEndTime(), 1e-3);
+
+ if (i > 0) {
+ assertEquals("wrong transition from " + (i - 1) + " to " + i, schedule.getTasks().get(i).getBeginTime(),
+ schedule.getTasks().get(i - 1).getEndTime(), 1e-3);
+ }
+
+ assertTrue("invalid task " + i, task.getEndTime() >= task.getBeginTime());
+ }
+ }
+
+ private Network createNetwork() {
+ Network network = NetworkUtils.createNetwork();
+ NetworkFactory networkFactory = network.getFactory();
+
+ List nodes = new LinkedList<>();
+
+ for (int i = 0; i < 100; i++) {
+ Node node = networkFactory.createNode(Id.createNodeId("n" + i), new Coord(0.0, i * 1000.0));
+ network.addNode(node);
+ nodes.add(node);
+ }
+
+ for (int i = 0; i < 99; i++) {
+ Link forwardLink = networkFactory.createLink(Id.createLinkId("f" + i), nodes.get(i), nodes.get(i + 1));
+ network.addLink(forwardLink);
+
+ Link backwardLink = networkFactory.createLink(Id.createLinkId("b" + i), nodes.get(i + 1), nodes.get(i));
+ network.addLink(backwardLink);
+ }
+
+ for (Link link : network.getLinks().values()) {
+ link.setAllowedModes(Collections.singleton("car"));
+ link.setLength(1000.0);
+ link.setFreespeed(1.0);
+ }
+
+ return network;
+ }
+
+ private class Fixture {
+ private final DvrpVehicle vehicle;
+ private final Schedule schedule;
+ private final Network network;
+
+ private Link currentLink;
+ private double currentTime;
+
+ private final DrtTaskFactory taskFactory = new DrtTaskFactoryImpl();
+ private final LeastCostPathCalculator router;
+ private final TravelTime travelTime = new FreeSpeedTravelTime();
+
+ private final VehicleEntry.EntryFactory entryFactory;
+ private final ScheduleTimingUpdater timingUpdater;
+ private final DvrpVehicleLookup lookup;
+
+ private int requestIndex = 0;
+
+ Fixture() {
+ this.network = createNetwork();
+
+ Link depotLink = network.getLinks().get(Id.createLinkId("f0"));
+
+ DvrpVehicleSpecification vehicleSpecification = ImmutableDvrpVehicleSpecification.newBuilder() //
+ .id(Id.create("vehicle", DvrpVehicle.class)) //
+ .capacity(4) //
+ .serviceBeginTime(0.0) //
+ .serviceEndTime(30.0 * 3600.0) //
+ .startLinkId(depotLink.getId()) //
+ .build();
+
+ this.vehicle = new DvrpVehicleImpl(vehicleSpecification, depotLink);
+ this.schedule = vehicle.getSchedule();
+ this.currentLink = vehicle.getStartLink();
+ this.currentTime = 0.0;
+ this.router = new DijkstraFactory().createPathCalculator(network,
+ new OnlyTimeDependentTravelDisutility(travelTime), travelTime);
+
+ this.lookup = Mockito.mock(DvrpVehicleLookup.class);
+ Mockito.when(this.lookup.lookupVehicle(Mockito.any())).thenReturn(vehicle);
+
+ DrtConfigGroup drtConfig = new DrtConfigGroup();
+ drtConfig.stopDuration = 30.0;
+ drtConfig.maxWaitTime = 600.0;
+
+ this.entryFactory = new VehicleDataEntryFactoryImpl();
+
+ this.timingUpdater = Mockito.mock(ScheduleTimingUpdater.class);
+ }
+
+ AcceptedDrtRequest createRequest() {
+ AcceptedDrtRequest request = Mockito.mock(AcceptedDrtRequest.class);
+ Mockito.when(request.getId()).thenReturn(Id.create("req_" + requestIndex++, Request.class));
+ return request;
+ }
+
+ DrtStayTask addWait(double duration) {
+ DrtStayTask task = taskFactory.createStayTask(vehicle, currentTime, currentTime + duration, currentLink);
+ schedule.addTask(task);
+
+ currentTime += duration;
+ return task;
+ }
+
+ DrtStayTask addStay(double duration) {
+ DrtStayTask task = taskFactory.createStayTask(vehicle, currentTime, currentTime + duration, currentLink);
+ schedule.addTask(task);
+
+ currentTime += duration;
+ return task;
+ }
+
+ DrtStayTask addFinalStay(double until) {
+ DrtStayTask task = taskFactory.createStayTask(vehicle, currentTime, Math.max(currentTime, until),
+ currentLink);
+ schedule.addTask(task);
+
+ currentTime = Math.max(currentTime, until);
+ return task;
+ }
+
+ DrtStopTask addStop(double duration) {
+ DrtStopTask task = taskFactory.createStopTask(vehicle, currentTime, currentTime + duration, currentLink);
+ schedule.addTask(task);
+ currentTime += duration;
+ return task;
+ }
+
+ DrtDriveTask addDrive(String destinationLinkId) {
+ Link destinationLink = network.getLinks().get(Id.createLinkId(destinationLinkId));
+
+ VrpPathWithTravelData path = VrpPaths.calcAndCreatePath(currentLink, destinationLink, currentTime, router,
+ travelTime);
+ DrtDriveTask driveTask = taskFactory.createDriveTask(vehicle, path, DrtDriveTask.TYPE);
+ schedule.addTask(driveTask);
+
+ currentTime = driveTask.getEndTime();
+ currentLink = destinationLink;
+
+ return driveTask;
+ }
+ }
+}
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PersonStuckPrebookingTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PersonStuckPrebookingTest.java
new file mode 100644
index 00000000000..11d3951b632
--- /dev/null
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PersonStuckPrebookingTest.java
@@ -0,0 +1,227 @@
+package org.matsim.contrib.drt.prebooking;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.matsim.api.core.v01.Id;
+import org.matsim.api.core.v01.population.Activity;
+import org.matsim.api.core.v01.population.Leg;
+import org.matsim.api.core.v01.population.Person;
+import org.matsim.api.core.v01.population.Plan;
+import org.matsim.api.core.v01.population.Population;
+import org.matsim.api.core.v01.population.PopulationFactory;
+import org.matsim.contrib.drt.prebooking.logic.ProbabilityBasedPrebookingLogic;
+import org.matsim.contrib.drt.run.DrtConfigGroup;
+import org.matsim.contrib.dvrp.optimizer.Request;
+import org.matsim.contrib.dvrp.passenger.PassengerDroppedOffEvent;
+import org.matsim.contrib.dvrp.passenger.PassengerDroppedOffEventHandler;
+import org.matsim.contrib.dvrp.passenger.PassengerRequest;
+import org.matsim.contrib.dvrp.passenger.PassengerRequestRejectedEvent;
+import org.matsim.contrib.dvrp.passenger.PassengerRequestRejectedEventHandler;
+import org.matsim.contrib.dvrp.passenger.PassengerRequestSubmittedEvent;
+import org.matsim.contrib.dvrp.passenger.PassengerRequestSubmittedEventHandler;
+import org.matsim.contrib.dvrp.passenger.PassengerRequestValidator;
+import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule;
+import org.matsim.core.controler.AbstractModule;
+import org.matsim.core.controler.Controler;
+import org.matsim.core.mobsim.framework.events.MobsimBeforeSimStepEvent;
+import org.matsim.core.mobsim.framework.listeners.MobsimBeforeSimStepListener;
+import org.matsim.testcases.MatsimTestUtils;
+
+/**
+ * @author Sebastian Hörl (sebhoerl) / IRT SystemX
+ */
+public class PersonStuckPrebookingTest {
+ @Rule
+ public MatsimTestUtils utils = new MatsimTestUtils();
+
+ @Test
+ public void baselineTest() {
+ /*
+ * Agent personA is performing three drt legs during the day. Agent personB does
+ * exactly the same in parallel, both prebook their requests.
+ */
+
+ PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) //
+ .addVehicle("vehicle", 1, 1) //
+ .configure(600.0, 1.3, 600.0, 60.0) //
+ .endTime(20000.0);
+
+ Controler controller = environment.build();
+
+ implementPopulation(controller.getScenario().getPopulation());
+ PrebookingTest.installPrebooking(controller, false);
+ ProbabilityBasedPrebookingLogic.install(controller,
+ DrtConfigGroup.getSingleModeDrtConfig(controller.getConfig()), 1.0, 20000.0);
+
+ EventCounter eventCounterA = EventCounter.install(controller, Id.createPersonId("personA"));
+ EventCounter eventCounterB = EventCounter.install(controller, Id.createPersonId("personB"));
+
+ controller.run();
+
+ assertEquals(3, eventCounterA.submittedCount);
+ assertEquals(3, eventCounterA.dropoffCount);
+
+ assertEquals(3, eventCounterB.submittedCount);
+ assertEquals(3, eventCounterB.dropoffCount);
+ }
+
+ @Test
+ public void cancelTest() {
+ /*
+ * Agent personA is performing three drt legs during the day. Agent personB does
+ * exactly the same in parallel, both prebook there requests.
+ *
+ * We cancel the first request of personA. We check that the other reservations
+ * are automatically rejected as soon as the person is stuck.
+ */
+
+ PrebookingTestEnvironment environment = new PrebookingTestEnvironment(utils) //
+ .addVehicle("vehicle", 1, 1) //
+ .configure(600.0, 1.3, 600.0, 60.0) //
+ .endTime(20000.0);
+
+ Controler controller = environment.build();
+
+ implementPopulation(controller.getScenario().getPopulation());
+ PrebookingTest.installPrebooking(controller, false);
+ ProbabilityBasedPrebookingLogic.install(controller,
+ DrtConfigGroup.getSingleModeDrtConfig(controller.getConfig()), 1.0, 20000.0);
+
+ EventCounter eventCounterA = EventCounter.install(controller, Id.createPersonId("personA"));
+ EventCounter eventCounterB = EventCounter.install(controller, Id.createPersonId("personB"));
+
+ controller.addOverridingQSimModule(new AbstractDvrpModeQSimModule("drt") {
+ @Override
+ protected void configureQSim() {
+ addModalQSimComponentBinding().toProvider(modalProvider(getter -> {
+ PrebookingManager prebookingManager = getter.getModal(PrebookingManager.class);
+
+ return new MobsimBeforeSimStepListener() {
+ @Override
+ public void notifyMobsimBeforeSimStep(MobsimBeforeSimStepEvent e) {
+ if (e.getSimulationTime() == 500.0) {
+ prebookingManager.cancel(Id.create("drt_prebooked_0", Request.class));
+ }
+ }
+ };
+ }));
+
+ bindModal(PassengerRequestValidator.class).toProvider(modalProvider(getter -> {
+ return new PassengerRequestValidator() {
+ @Override
+ public Set validateRequest(PassengerRequest request) {
+ if (!request.getId().toString().contains("prebooked")) {
+ return Collections.singleton("anything");
+ }
+
+ return Collections.emptySet();
+ }
+ };
+ }));
+ }
+ });
+
+ controller.run();
+
+ assertEquals(4, eventCounterA.submittedCount);
+ assertEquals(4, eventCounterA.rejectedCount);
+ assertEquals(0, eventCounterA.dropoffCount);
+
+ assertEquals(3, eventCounterB.submittedCount);
+ assertEquals(0, eventCounterB.rejectedCount);
+ assertEquals(3, eventCounterB.dropoffCount);
+ }
+
+ private void implementPopulation(Population population) {
+ PopulationFactory populationFactory = population.getFactory();
+
+ for (String personId : Arrays.asList("personA", "personB")) {
+ Person person = populationFactory.createPerson(Id.createPersonId(personId));
+ population.addPerson(person);
+
+ Plan plan = populationFactory.createPlan();
+ person.addPlan(plan);
+
+ Activity firstActivity = populationFactory.createActivityFromLinkId("generic", Id.createLinkId("1:1-2:1"));
+ firstActivity.setEndTime(2000.0);
+ plan.addActivity(firstActivity);
+
+ // departure at 2000
+ Leg firstLeg = populationFactory.createLeg("drt");
+ plan.addLeg(firstLeg);
+
+ Activity secondActivity = populationFactory.createActivityFromLinkId("generic", Id.createLinkId("5:5-6:5"));
+ secondActivity.setEndTime(6000.0);
+ plan.addActivity(secondActivity);
+
+ // departure at 6000
+ Leg secondLeg = populationFactory.createLeg("drt");
+ plan.addLeg(secondLeg);
+
+ Activity thirdActivity = populationFactory.createActivityFromLinkId("generic", Id.createLinkId("1:1-2:1"));
+ thirdActivity.setEndTime(10000.0);
+ plan.addActivity(thirdActivity);
+
+ // departure at 10000
+ Leg thirdLeg = populationFactory.createLeg("drt");
+ plan.addLeg(thirdLeg);
+
+ Activity finalActivity = populationFactory.createActivityFromLinkId("generic", Id.createLinkId("5:5-6:5"));
+ plan.addActivity(finalActivity);
+ }
+ }
+
+ static private class EventCounter implements PassengerDroppedOffEventHandler, PassengerRequestSubmittedEventHandler,
+ PassengerRequestRejectedEventHandler {
+ private final Id personId;
+
+ private EventCounter(Id personId) {
+ this.personId = personId;
+ }
+
+ int dropoffCount = 0;
+ int submittedCount = 0;
+ int rejectedCount = 0;
+
+ @Override
+ public void handleEvent(PassengerDroppedOffEvent event) {
+ if (event.getPersonId().equals(personId)) {
+ dropoffCount++;
+ }
+ }
+
+ @Override
+ public void handleEvent(PassengerRequestSubmittedEvent event) {
+ if (event.getPersonIds().contains(personId)) {
+ submittedCount++;
+ }
+ }
+
+ @Override
+ public void handleEvent(PassengerRequestRejectedEvent event) {
+ if (event.getPersonIds().contains(personId)) {
+ rejectedCount++;
+ }
+ }
+
+ static EventCounter install(Controler controller, Id personId) {
+ EventCounter instance = new EventCounter(personId);
+
+ controller.addOverridingModule(new AbstractModule() {
+
+ @Override
+ public void install() {
+ addEventHandlerBinding().toInstance(instance);
+ }
+ });
+
+ return instance;
+ }
+ }
+}
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTest.java
index 63e4e1bae45..35d9a0ef62b 100644
--- a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTest.java
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTest.java
@@ -50,10 +50,19 @@ public void withoutPrebookedRequests() {
assertEquals(2146.0, taskInfo.get(2).endTime, 1e-3);
}
- private void installPrebooking(Controler controller) {
+ static PrebookingParams installPrebooking(Controler controller) {
+ return installPrebooking(controller, true);
+ }
+
+ static PrebookingParams installPrebooking(Controler controller, boolean installLogic) {
DrtConfigGroup drtConfig = DrtConfigGroup.getSingleModeDrtConfig(controller.getConfig());
drtConfig.addParameterSet(new PrebookingParams());
- AttributeBasedPrebookingLogic.install(controller, drtConfig);
+
+ if (installLogic) {
+ AttributeBasedPrebookingLogic.install(controller, drtConfig);
+ }
+
+ return drtConfig.getPrebookingParams().get();
}
@Test
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTestEnvironment.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/prebooking/PrebookingTestEnvironment.java
index 52c959cd384..8e1f6394388 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
@@ -4,6 +4,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair;
import org.matsim.api.core.v01.Coord;
@@ -358,6 +359,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<>();
@@ -379,13 +382,17 @@ private class RequestListener implements DrtRequestSubmittedEventHandler, Passen
PassengerDroppedOffEventHandler, PassengerRequestRejectedEventHandler {
@Override
public void handleEvent(DrtRequestSubmittedEvent event) {
- requestInfo.computeIfAbsent(event.getPersonId().toString(), id -> new RequestInfo()).submissionTime = event
+ String ids = event.getPersonIds().stream().map(Object::toString).collect(Collectors.joining("-"));
+ requestInfo.computeIfAbsent(ids, id -> new RequestInfo()).submissionTime = event
.getTime();
+ requestInfo.computeIfAbsent(ids, id -> new RequestInfo()).submissionTimes
+ .add(event.getTime());
+
}
@Override
public void handleEvent(PassengerRequestRejectedEvent event) {
- requestInfo.computeIfAbsent(event.getPersonId().toString(), id -> new RequestInfo()).rejected = true;
+ requestInfo.computeIfAbsent(event.getPersonIds().stream().map(Object::toString).collect(Collectors.joining("-")), id -> new RequestInfo()).rejected = true;
}
@Override
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/speedup/DrtSpeedUpTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/speedup/DrtSpeedUpTest.java
index 0cc447f1237..af92306b69e 100644
--- a/contribs/drt/src/test/java/org/matsim/contrib/drt/speedup/DrtSpeedUpTest.java
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/speedup/DrtSpeedUpTest.java
@@ -27,7 +27,9 @@
import static org.mockito.Mockito.when;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
import org.junit.Test;
import org.matsim.api.core.v01.Coord;
@@ -38,6 +40,7 @@
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
import org.matsim.api.core.v01.network.Node;
+import org.matsim.api.core.v01.population.Person;
import org.matsim.contrib.common.util.DistanceUtils;
import org.matsim.contrib.drt.analysis.DrtEventSequenceCollector;
import org.matsim.contrib.drt.analysis.DrtEventSequenceCollector.EventSequence;
@@ -270,7 +273,9 @@ private void updateRequestAnalyser(EventSequence... eventSequences) {
private EventSequence eventSequence(String id, double submittedTime, double waitTime, double inVehicleSpeed) {
var requestId = Id.create(id, Request.class);
- var submittedEvent = new DrtRequestSubmittedEvent(submittedTime, MODE, requestId, null, linkAB.getId(),
+ var personId = Id.create(id, Person.class);
+
+ var submittedEvent = new DrtRequestSubmittedEvent(submittedTime, MODE, requestId, List.of(personId), linkAB.getId(),
linkBC.getId(), Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN);
var pickupEvent = new PassengerPickedUpEvent(submittedTime + waitTime, MODE, requestId, null, null);
double rideTime = DistanceUtils.calculateDistance(linkBC, linkAB) / inVehicleSpeed;
@@ -280,7 +285,8 @@ private EventSequence eventSequence(String id, double submittedTime, double wait
MODE, requestId.toString());
var departureEvent = mock(PersonDepartureEvent.class);
- return new EventSequence(departureEvent, submittedEvent, mock(PassengerRequestScheduledEvent.class),
+
+ return new EventSequence(Id.createPersonId("r1"), departureEvent, submittedEvent, mock(PassengerRequestScheduledEvent.class),
pickupEvent, dropoffEvent, List.of(drtFare));
}
diff --git a/contribs/drt/src/test/java/org/matsim/contrib/drt/util/DrtEventsReadersTest.java b/contribs/drt/src/test/java/org/matsim/contrib/drt/util/DrtEventsReadersTest.java
index afc39c2e6de..27fcefc3b52 100644
--- a/contribs/drt/src/test/java/org/matsim/contrib/drt/util/DrtEventsReadersTest.java
+++ b/contribs/drt/src/test/java/org/matsim/contrib/drt/util/DrtEventsReadersTest.java
@@ -25,6 +25,7 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import org.junit.Test;
@@ -64,7 +65,7 @@ public class DrtEventsReadersTest {
//standard dvrp events are tested in DvrpEventsReadersTest
private final List drtEvents = List.of(
- new DrtRequestSubmittedEvent(0, mode, request, person, link1, link2, 111, 222, 0.0, 412.0, 512.0),//
+ new DrtRequestSubmittedEvent(0, mode, request, List.of(person), link1, link2, 111, 222, 0.0, 412.0, 512.0),//
taskStarted(10, DrtDriveTask.TYPE, 0, link1),//
taskEnded(30, DefaultDrtStopTask.TYPE, 1, link2), //
taskStarted(50, DrtStayTask.TYPE, 2, link1),//
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/examples/onetaxi/OneTaxiOptimizer.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/examples/onetaxi/OneTaxiOptimizer.java
index fb23bac474d..ee5f38811fe 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/examples/onetaxi/OneTaxiOptimizer.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/examples/onetaxi/OneTaxiOptimizer.java
@@ -133,7 +133,7 @@ public void requestSubmitted(Request request) {
eventsManager.processEvent(
new PassengerRequestScheduledEvent(timer.getTimeOfDay(), TransportMode.taxi, request.getId(),
- req.getPassengerId(), vehicle.getId(), t1, t4));
+ req.getPassengerIds(), vehicle.getId(), t1, t4));
}
@Override
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/examples/onetaxi/OneTaxiRequest.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/examples/onetaxi/OneTaxiRequest.java
index a1d86022e2a..de9f9f0936b 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/examples/onetaxi/OneTaxiRequest.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/examples/onetaxi/OneTaxiRequest.java
@@ -28,6 +28,8 @@
import org.matsim.contrib.dvrp.passenger.PassengerRequest;
import org.matsim.contrib.dvrp.passenger.PassengerRequestCreator;
+import java.util.*;
+
/**
* @author michalm
*/
@@ -36,18 +38,18 @@ public final class OneTaxiRequest implements PassengerRequest {
private final double submissionTime;
private final double earliestStartTime;
- private final Id passengerId;
+ private final List> passengerIds = new ArrayList<>();
private final String mode;
private final Link fromLink;
private final Link toLink;
- public OneTaxiRequest(Id id, Id passengerId, String mode, Link fromLink, Link toLink,
- double departureTime, double submissionTime) {
+ public OneTaxiRequest(Id id, Collection> passengerIds, String mode, Link fromLink, Link toLink,
+ double departureTime, double submissionTime) {
this.id = id;
this.submissionTime = submissionTime;
this.earliestStartTime = departureTime;
- this.passengerId = passengerId;
+ this.passengerIds.addAll(passengerIds);
this.mode = mode;
this.fromLink = fromLink;
this.toLink = toLink;
@@ -79,8 +81,8 @@ public Link getToLink() {
}
@Override
- public Id getPassengerId() {
- return passengerId;
+ public List> getPassengerIds() {
+ return List.copyOf(passengerIds);
}
@Override
@@ -88,11 +90,16 @@ public String getMode() {
return mode;
}
+ @Override
+ public int getPassengerCount() {
+ return passengerIds.size();
+ }
+
public static final class OneTaxiRequestCreator implements PassengerRequestCreator {
@Override
- public OneTaxiRequest createRequest(Id id, Id passengerId, Route route, Link fromLink,
+ public OneTaxiRequest createRequest(Id id, List> passengerIds, Route route, Link fromLink,
Link toLink, double departureTime, double submissionTime) {
- return new OneTaxiRequest(id, passengerId, TransportMode.taxi, fromLink, toLink, departureTime,
+ return new OneTaxiRequest(id, passengerIds, TransportMode.taxi, fromLink, toLink, departureTime,
submissionTime);
}
}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/AbstractPassengerRequestEvent.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/AbstractPassengerRequestEvent.java
index ca269d5975a..529f2bfc5ba 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/AbstractPassengerRequestEvent.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/AbstractPassengerRequestEvent.java
@@ -20,13 +20,15 @@
package org.matsim.contrib.dvrp.passenger;
-import java.util.Map;
-
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.events.Event;
import org.matsim.api.core.v01.population.Person;
import org.matsim.contrib.dvrp.optimizer.Request;
-import org.matsim.api.core.v01.events.HasPersonId;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
/**
* This class is designed for inheritance without overriding.
@@ -35,19 +37,20 @@
*
* @author Michal Maciejewski (michalm)
*/
-public abstract class AbstractPassengerRequestEvent extends Event implements HasPersonId {
+public abstract class AbstractPassengerRequestEvent extends Event {
public static final String ATTRIBUTE_MODE = "mode";
public static final String ATTRIBUTE_REQUEST = "request";
+ public static final String ATTRIBUTE_PERSON = "person";
private final String mode;
private final Id requestId;
- private final Id personId;
+ private final List> personIds;
- protected AbstractPassengerRequestEvent(double time, String mode, Id requestId, Id personId) {
+ protected AbstractPassengerRequestEvent(double time, String mode, Id requestId, List> personIds) {
super(time);
this.mode = mode;
this.requestId = requestId;
- this.personId = personId;
+ this.personIds = personIds;
}
public final String getMode() {
@@ -62,11 +65,10 @@ public final Id getRequestId() {
}
/**
- * @return id of the passenger (person)
+ * @return ids of the passengers (persons)
*/
- @Override
- public final Id getPersonId() {
- return personId;
+ public final List> getPersonIds() {
+ return List.copyOf(personIds);
}
@Override
@@ -74,6 +76,7 @@ public Map getAttributes() {
Map attr = super.getAttributes();
attr.put(ATTRIBUTE_MODE, mode);
attr.put(ATTRIBUTE_REQUEST, requestId + "");
+ attr.put(ATTRIBUTE_PERSON, personIds.stream().map(Object::toString).collect(Collectors.joining(",")));
return attr;
}
}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/DefaultPassengerEngine.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/DefaultPassengerEngine.java
index 428d2cdd91f..6a19b93d470 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/DefaultPassengerEngine.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/DefaultPassengerEngine.java
@@ -19,12 +19,10 @@
package org.matsim.contrib.dvrp.passenger;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Queue;
+import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
+import com.google.common.base.Verify;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.network.Link;
import org.matsim.api.core.v01.network.Network;
@@ -67,17 +65,16 @@ public final class DefaultPassengerEngine implements PassengerEngine, PassengerR
private InternalInterface internalInterface;
//accessed in doSimStep() and handleDeparture() (no need to sync)
- private final Map, MobsimPassengerAgent> activePassengers = new HashMap<>();
-
+ private final Map, List> activePassengers = new HashMap<>();
+
// holds vehicle stop activities for requests that have not arrived at departure point yet
private final Map, PassengerPickupActivity> waitingForPassenger = new HashMap<>();
//accessed in doSimStep() and handleEvent() (potential data races)
private final Queue rejectedRequestsEvents = new ConcurrentLinkedQueue<>();
- DefaultPassengerEngine(String mode, EventsManager eventsManager, MobsimTimer mobsimTimer,
- PassengerRequestCreator requestCreator, VrpOptimizer optimizer, Network network,
- PassengerRequestValidator requestValidator, AdvanceRequestProvider advanceRequestProvider) {
+ DefaultPassengerEngine(String mode, EventsManager eventsManager, MobsimTimer mobsimTimer, PassengerRequestCreator requestCreator,
+ VrpOptimizer optimizer, Network network, PassengerRequestValidator requestValidator, AdvanceRequestProvider advanceRequestProvider) {
this.mode = mode;
this.mobsimTimer = mobsimTimer;
this.requestCreator = requestCreator;
@@ -106,18 +103,26 @@ public void doSimStep(double time) {
// event) after submission, but before departure, the PassengerEngine does not
// know this agent yet. Hence, we wait with setting the state to abort until the
// agent has arrived here (if ever).
-
+
Iterator iterator = rejectedRequestsEvents.iterator();
-
+
while (iterator.hasNext()) {
PassengerRequestRejectedEvent event = iterator.next();
- MobsimPassengerAgent passenger = activePassengers.remove(event.getRequestId());
+ if (event.getTime() == time) {
+ // There is a potential race condition wrt processing rejection events between doSimStep() and handleEvent().
+ // To ensure a deterministic behaviour, we only process events from the previous time step.
+ break;
+ }
+
+ List passengers = activePassengers.remove(event.getRequestId());
- if (passenger != null) {
+ if (passengers != null) {
// not much else can be done for immediate requests
// set the passenger agent to abort - the event will be thrown by the QSim
- passenger.setStateToAbort(mobsimTimer.getTimeOfDay());
- internalInterface.arrangeNextAgentState(passenger);
+ for (MobsimPassengerAgent passenger: passengers) {
+ passenger.setStateToAbort(mobsimTimer.getTimeOfDay());
+ internalInterface.arrangeNextAgentState(passenger);
+ }
iterator.remove();
}
}
@@ -137,36 +142,37 @@ public boolean handleDeparture(double now, MobsimAgent agent, Id fromLinkI
internalInterface.registerAdditionalAgentOnLink(passenger);
Id toLinkId = passenger.getDestinationLinkId();
-
+
// try to find a prebooked requests that is associated to this leg
- Leg leg = (Leg) ((PlanAgent) passenger).getCurrentPlanElement();
+ Leg leg = (Leg)((PlanAgent)passenger).getCurrentPlanElement();
PassengerRequest request = advanceRequestProvider.retrieveRequest(agent, leg);
-
+
if (request == null) { // immediate request
Route route = ((Leg)((PlanAgent)passenger).getCurrentPlanElement()).getRoute();
- request = requestCreator.createRequest(internalPassengerHandling.createRequestId(), passenger.getId(),
- route, getLink(fromLinkId), getLink(toLinkId), now, now);
+ request = requestCreator.createRequest(internalPassengerHandling.createRequestId(),
+ List.of(passenger.getId()), route, getLink(fromLinkId), getLink(toLinkId), now, now);
// must come before validateAndSubmitRequest (to come before rejection event)
- eventsManager.processEvent(new PassengerWaitingEvent(now, mode, request.getId(), request.getPassengerId()));
- activePassengers.put(request.getId(), passenger);
-
- validateAndSubmitRequest(passenger, request, now);
+ eventsManager.processEvent(new PassengerWaitingEvent(now, mode, request.getId(), List.of(passenger.getId())));
+ activePassengers.put(request.getId(), List.of(passenger));
+
+ validateAndSubmitRequest(List.of(passenger), request, now);
} else { // advance request
- eventsManager.processEvent(new PassengerWaitingEvent(now, mode, request.getId(), request.getPassengerId()));
- activePassengers.put(request.getId(), passenger);
+ eventsManager.processEvent(new PassengerWaitingEvent(now, mode, request.getId(), List.of(passenger.getId())));
+ activePassengers.put(request.getId(), List.of(passenger));
PassengerPickupActivity pickupActivity = waitingForPassenger.remove(request.getId());
if (pickupActivity != null) {
// the vehicle is already waiting for the request, notify it
- pickupActivity.notifyPassengerIsReadyForDeparture(passenger, now);
+ pickupActivity.notifyPassengersAreReadyForDeparture(List.of(passenger), now);
}
}
-
+
return true;
}
- private void validateAndSubmitRequest(MobsimPassengerAgent passenger, PassengerRequest request, double now) {
+ private void validateAndSubmitRequest(List passengers, PassengerRequest request, double now) {
+ activePassengers.put(request.getId(), passengers);
if (internalPassengerHandling.validateRequest(request, requestValidator, now)) {
//need to synchronise to address cases where requestSubmitted() may:
// - be called from outside DepartureHandlers
@@ -182,13 +188,12 @@ private void validateAndSubmitRequest(MobsimPassengerAgent passenger, PassengerR
private Link getLink(Id linkId) {
return Preconditions.checkNotNull(network.getLinks().get(linkId),
- "Link id=%s does not exist in network for mode %s. Agent departs from a link that does not belong to that network?",
- linkId, mode);
+ "Link id=%s does not exist in network for mode %s. Agent departs from a link that does not belong to that network?", linkId, mode);
}
/**
* There are two ways of interacting with the PassengerEngine:
- *
+ *
* - (1) The stop activity tries to pick up a passenger and receives whether the
* pickup succeeded or not (see tryPickUpPassenger). In the classic
* implementation, the vehicle only calls tryPickUpPassenger at the time when it
@@ -196,7 +201,7 @@ private Link getLink(Id linkId) {
* happen that the person is not present yet. In that case, the pickup request
* is saved and notifyPassengerReady is called on the stop activity upen
* departure of the agent.
- *
+ *
* - (2) If pickup and dropoff times are handled more flexibly by the stop
* activity, it might want to detect whether an agent is ready to be picked up,
* then start an "interaction time" and only after perform the actual pickup.
@@ -205,25 +210,27 @@ private Link getLink(Id linkId) {
* notified once the agent arrives for departure.
*/
@Override
- public boolean notifyWaitForPassenger(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id requestId) {
+ public boolean notifyWaitForPassengers(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id requestId) {
if (!activePassengers.containsKey(requestId)) {
waitingForPassenger.put(requestId, pickupActivity);
return false;
}
-
+
return true;
}
@Override
- public boolean tryPickUpPassenger(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver,
+ public boolean tryPickUpPassengers(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver,
Id requestId, double now) {
- return internalPassengerHandling.tryPickUpPassenger(driver, activePassengers.get(requestId),
+ boolean pickedUp = internalPassengerHandling.tryPickUpPassengers(driver, activePassengers.get(requestId),
requestId, now);
+ Verify.verify(pickedUp, "Not possible without prebooking");
+ return pickedUp;
}
@Override
- public void dropOffPassenger(MobsimDriverAgent driver, Id requestId, double now) {
- internalPassengerHandling.dropOffPassenger(driver, activePassengers.remove(requestId), requestId, now);
+ public void dropOffPassengers(MobsimDriverAgent driver, Id requestId, double now) {
+ internalPassengerHandling.dropOffPassengers(driver, activePassengers.remove(requestId), requestId, now);
}
@Override
@@ -243,10 +250,9 @@ public static Provider createProvider(String mode) {
@Override
public DefaultPassengerEngine get() {
- return new DefaultPassengerEngine(getMode(), eventsManager, mobsimTimer,
- getModalInstance(PassengerRequestCreator.class), getModalInstance(VrpOptimizer.class),
- getModalInstance(Network.class), getModalInstance(PassengerRequestValidator.class),
- getModalInstance(AdvanceRequestProvider.class));
+ return new DefaultPassengerEngine(getMode(), eventsManager, mobsimTimer, getModalInstance(PassengerRequestCreator.class),
+ getModalInstance(VrpOptimizer.class), getModalInstance(Network.class), getModalInstance(PassengerRequestValidator.class),
+ getModalInstance(AdvanceRequestProvider.class));
}
};
}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/GroupPassengerEngine.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/GroupPassengerEngine.java
new file mode 100644
index 00000000000..a5c2f9e9c84
--- /dev/null
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/GroupPassengerEngine.java
@@ -0,0 +1,231 @@
+package org.matsim.contrib.dvrp.passenger;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
+import jakarta.inject.Inject;
+import jakarta.inject.Provider;
+import org.matsim.api.core.v01.Id;
+import org.matsim.api.core.v01.Identifiable;
+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.Route;
+import org.matsim.contrib.dvrp.optimizer.Request;
+import org.matsim.contrib.dvrp.optimizer.VrpOptimizer;
+import org.matsim.contrib.dvrp.run.DvrpModes;
+import org.matsim.core.api.experimental.events.EventsManager;
+import org.matsim.core.gbl.Gbl;
+import org.matsim.core.mobsim.framework.*;
+import org.matsim.core.mobsim.qsim.InternalInterface;
+import org.matsim.core.modal.ModalProviders;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.stream.Collectors;
+
+public class GroupPassengerEngine implements PassengerEngine, PassengerRequestRejectedEventHandler {
+
+ private final String mode;
+ private final MobsimTimer mobsimTimer;
+
+ private final EventsManager eventsManager;
+
+ private final PassengerRequestCreator requestCreator;
+ private final VrpOptimizer optimizer;
+ private final Network network;
+ private final PassengerRequestValidator requestValidator;
+
+ private final InternalPassengerHandling internalPassengerHandling;
+
+ private InternalInterface internalInterface;
+
+
+ //accessed in doSimStep() and handleDeparture() (no need to sync)
+ private final Map, List> activePassengers = new HashMap<>();
+
+ // holds vehicle stop activities for requests that have not arrived at departure point yet
+ private final Map, PassengerPickupActivity> waitingForPassenger = new HashMap<>();
+
+ //accessed in doSimStep() and handleEvent() (potential data races)
+ private final Queue rejectedRequestsEvents = new ConcurrentLinkedQueue<>();
+
+ private final Set currentDepartures = new LinkedHashSet<>();
+
+ private final PassengerGroupIdentifier groupIdentifier;
+
+ GroupPassengerEngine(String mode, EventsManager eventsManager, MobsimTimer mobsimTimer, PassengerRequestCreator requestCreator, VrpOptimizer optimizer, Network network, PassengerRequestValidator requestValidator, PassengerGroupIdentifier groupIdentifier) {
+ this.mode = mode;
+ this.eventsManager = eventsManager;
+ this.mobsimTimer = mobsimTimer;
+ this.requestCreator = requestCreator;
+ this.optimizer = optimizer;
+ this.network = network;
+ this.requestValidator = requestValidator;
+ this.groupIdentifier = groupIdentifier;
+
+ internalPassengerHandling = new InternalPassengerHandling(mode, eventsManager);
+ }
+
+ @Override
+ public void setInternalInterface(InternalInterface internalInterface) {
+ this.internalInterface = internalInterface;
+ internalPassengerHandling.setInternalInterface(internalInterface);
+ }
+
+ @Override
+ public void onPrepareSim() {
+ }
+
+ @Override
+ public void doSimStep(double time) {
+ handleDepartures(time);
+ while (!rejectedRequestsEvents.isEmpty()) {
+ List passengers = activePassengers.remove(rejectedRequestsEvents.poll().getRequestId());
+ //not much else can be done for immediate requests
+ //set the passenger agent to abort - the event will be thrown by the QSim
+ for (MobsimPassengerAgent passenger : passengers) {
+ passenger.setStateToAbort(mobsimTimer.getTimeOfDay());
+ internalInterface.arrangeNextAgentState(passenger);
+ }
+ }
+ }
+
+ @Override
+ public void afterSim() {
+ }
+
+ @Override
+ public boolean handleDeparture(double now, MobsimAgent agent, Id fromLinkId) {
+
+ if (!agent.getMode().equals(mode)) {
+ return false;
+ }
+
+ MobsimPassengerAgent passenger = (MobsimPassengerAgent)agent;
+ internalInterface.registerAdditionalAgentOnLink(passenger);
+ currentDepartures.add(passenger);
+ return true;
+ }
+
+ private void handleDepartures(double now) {
+ Map, List> agentGroups =
+ currentDepartures.stream().collect(
+ Collectors.groupingBy(
+ groupIdentifier,
+ Collectors.collectingAndThen(Collectors.toList(), list -> {
+ list.sort(Comparator.comparing(MobsimPassengerAgent::getId));
+ return list;
+ })));
+
+ for (List group : agentGroups.values()) {
+
+ Iterator iterator = group.iterator();
+ MobsimAgent firstAgent = iterator.next();
+ MobsimPassengerAgent passenger = (MobsimPassengerAgent) firstAgent;
+
+ Id toLinkId = firstAgent.getDestinationLinkId();
+
+ while (iterator.hasNext()) {
+ MobsimAgent next = iterator.next();
+ Gbl.assertIf(firstAgent.getCurrentLinkId().equals(next.getCurrentLinkId()));
+ Gbl.assertIf(toLinkId.equals(next.getDestinationLinkId()));
+ }
+
+ Route route = ((Leg)((PlanAgent)passenger).getCurrentPlanElement()).getRoute();
+
+ PassengerRequest request = requestCreator.createRequest(internalPassengerHandling.createRequestId(),
+ group.stream().map(Identifiable::getId).toList(), route,
+ getLink(firstAgent.getCurrentLinkId()), getLink(toLinkId),
+ now, now);
+
+
+ // must come before validateAndSubmitRequest (to come before rejection event)
+ eventsManager.processEvent(new PassengerWaitingEvent(now, mode, request.getId(), group.stream().map(Identifiable::getId).toList()));
+
+ validateAndSubmitRequest(group, request, mobsimTimer.getTimeOfDay());
+ }
+ currentDepartures.clear();
+ }
+
+ private void validateAndSubmitRequest(List passengers, PassengerRequest request, double now) {
+ activePassengers.put(request.getId(), passengers);
+ if (internalPassengerHandling.validateRequest(request, requestValidator, now)) {
+ //need to synchronise to address cases where requestSubmitted() may:
+ // - be called from outside DepartureHandlers
+ // - interfere with VrpOptimizer.nextTask()
+ // - impact VrpAgentLogic.computeNextAction()
+ synchronized (optimizer) {
+ //optimizer can also reject request if cannot handle it
+ // (async operation, notification comes via the events channel)
+ optimizer.requestSubmitted(request);
+ }
+ }
+ }
+
+ private Link getLink(Id linkId) {
+ return Preconditions.checkNotNull(network.getLinks().get(linkId), "Link id=%s does not exist in network for mode %s. Agent departs from a link that does not belong to that network?", linkId, mode);
+ }
+
+ /**
+ * There are two ways of interacting with the PassengerEngine:
+ *
+ * - (1) The stop activity tries to pick up a passenger and receives whether the
+ * pickup succeeded or not (see tryPickUpPassenger). In the classic
+ * implementation, the vehicle only calls tryPickUpPassenger at the time when it
+ * actually wants to pick up the person (at the end of the activity). It may
+ * happen that the person is not present yet. In that case, the pickup request
+ * is saved and notifyPassengerReady is called on the stop activity upen
+ * departure of the agent.
+ *
+ * - (2) If pickup and dropoff times are handled more flexibly by the stop
+ * activity, it might want to detect whether an agent is ready to be picked up,
+ * then start an "interaction time" and only after perform the actual pickup.
+ * For that purpose, we have queryPickUpPassenger, which indicates whether the
+ * agent is already there, and, if not, makes sure that the stop activity is
+ * notified once the agent arrives for departure.
+ */
+ @Override
+ public boolean notifyWaitForPassengers(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id requestId) {
+
+ if (!activePassengers.containsKey(requestId)) {
+ waitingForPassenger.put(requestId, pickupActivity);
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean tryPickUpPassengers(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id requestId, double now) {
+ boolean pickedUp = internalPassengerHandling.tryPickUpPassengers(driver, activePassengers.get(requestId), requestId, now);
+ Verify.verify(pickedUp, "Not possible without prebooking");
+ return pickedUp;
+ }
+
+ @Override
+ public void dropOffPassengers(MobsimDriverAgent driver, Id requestId, double now) {
+ internalPassengerHandling.dropOffPassengers(driver, activePassengers.remove(requestId), requestId, now);
+ }
+
+ @Override
+ public void handleEvent(PassengerRequestRejectedEvent event) {
+ if (event.getMode().equals(mode)) {
+ rejectedRequestsEvents.add(event);
+ }
+ }
+
+ public static Provider createProvider(String mode) {
+ return new ModalProviders.AbstractProvider<>(mode, DvrpModes::mode) {
+ @Inject
+ private EventsManager eventsManager;
+
+ @Inject
+ private MobsimTimer mobsimTimer;
+
+ @Override
+ public GroupPassengerEngine get() {
+ return new GroupPassengerEngine(getMode(), eventsManager, mobsimTimer, getModalInstance(PassengerRequestCreator.class), getModalInstance(VrpOptimizer.class), getModalInstance(Network.class), getModalInstance(PassengerRequestValidator.class), getModalInstance(PassengerGroupIdentifier.class));
+ }
+ };
+ }
+}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/InternalPassengerHandling.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/InternalPassengerHandling.java
index 67da85c61a7..c36393d16f4 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/InternalPassengerHandling.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/InternalPassengerHandling.java
@@ -22,6 +22,7 @@
import static java.lang.String.format;
+import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
@@ -70,39 +71,50 @@ boolean validateRequest(PassengerRequest request, PassengerRequestValidator requ
LOGGER.warn(format("Request: %s of mode: %s will not be served. Agent will get stuck. Cause: %s",
request.getId(), mode, cause));
eventsManager.processEvent(
- new PassengerRequestRejectedEvent(now, mode, request.getId(), request.getPassengerId(), cause));
+ new PassengerRequestRejectedEvent(now, mode, request.getId(), request.getPassengerIds(), cause));
}
return violations.isEmpty();
}
- boolean tryPickUpPassenger(MobsimDriverAgent driver, MobsimPassengerAgent passenger, Id requestId,
+ boolean tryPickUpPassengers(MobsimDriverAgent driver, List passengers, Id requestId,
double now) {
- if (internalInterface.unregisterAdditionalAgentOnLink(passenger.getId(), driver.getCurrentLinkId()) == null) {
- //only possible with prebooking
- return false;
+
+ //ensure for every passenger first
+ for (MobsimPassengerAgent passenger : passengers) {
+ if (internalInterface.unregisterAdditionalAgentOnLink(passenger.getId(), driver.getCurrentLinkId()) == null) {
+ //only possible with prebooking
+ return false;
+ }
}
- MobsimVehicle mobVehicle = driver.getVehicle();
- mobVehicle.addPassenger(passenger);
- passenger.setVehicle(mobVehicle);
+ for (MobsimPassengerAgent passenger : passengers) {
+
+ MobsimVehicle mobVehicle = driver.getVehicle();
+ mobVehicle.addPassenger(passenger);
+ passenger.setVehicle(mobVehicle);
+
+ Id vehicleId = Id.create(mobVehicle.getId(), DvrpVehicle.class);
+ eventsManager.processEvent(new PersonEntersVehicleEvent(now, passenger.getId(), mobVehicle.getId()));
+ eventsManager.processEvent(new PassengerPickedUpEvent(now, mode, requestId, passenger.getId(), vehicleId));
+ }
- Id vehicleId = Id.create(mobVehicle.getId(), DvrpVehicle.class);
- eventsManager.processEvent(new PersonEntersVehicleEvent(now, passenger.getId(), mobVehicle.getId()));
- eventsManager.processEvent(new PassengerPickedUpEvent(now, mode, requestId, passenger.getId(), vehicleId));
return true;
}
- void dropOffPassenger(MobsimDriverAgent driver, MobsimPassengerAgent passenger, Id requestId, double now) {
+ void dropOffPassengers(MobsimDriverAgent driver, List passengers, Id requestId, double now) {
MobsimVehicle mobVehicle = driver.getVehicle();
- mobVehicle.removePassenger(passenger);
- passenger.setVehicle(null);
-
Id vehicleId = Id.create(mobVehicle.getId(), DvrpVehicle.class);
- eventsManager.processEvent(new PassengerDroppedOffEvent(now, mode, requestId, passenger.getId(), vehicleId));
- eventsManager.processEvent(new PersonLeavesVehicleEvent(now, passenger.getId(), mobVehicle.getId()));
- passenger.notifyArrivalOnLinkByNonNetworkMode(passenger.getDestinationLinkId());
- passenger.endLegAndComputeNextState(now);
- internalInterface.arrangeNextAgentState(passenger);
+ for (MobsimPassengerAgent passenger : passengers) {
+ mobVehicle.removePassenger(passenger);
+ passenger.setVehicle(null);
+
+ eventsManager.processEvent(new PassengerDroppedOffEvent(now, mode, requestId, passenger.getId(), vehicleId));
+ eventsManager.processEvent(new PersonLeavesVehicleEvent(now, passenger.getId(), mobVehicle.getId()));
+
+ passenger.notifyArrivalOnLinkByNonNetworkMode(passenger.getDestinationLinkId());
+ passenger.endLegAndComputeNextState(now);
+ internalInterface.arrangeNextAgentState(passenger);
+ }
}
}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/MultiPassengerDropoffActivity.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/MultiPassengerDropoffActivity.java
index e93a08f840b..7d157e4c798 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/MultiPassengerDropoffActivity.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/MultiPassengerDropoffActivity.java
@@ -54,7 +54,7 @@ protected boolean isLastStep(double now) {
protected void afterLastStep(double now) {
// dropoff at the end of stop activity
for (PassengerRequest request : requests.values()) {
- passengerHandler.dropOffPassenger(driver, request.getId(), now);
+ passengerHandler.dropOffPassengers(driver, request.getId(), now);
}
}
}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/MultiPassengerPickupActivity.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/MultiPassengerPickupActivity.java
index 8d7bcb2da96..badc060b37e 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/MultiPassengerPickupActivity.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/MultiPassengerPickupActivity.java
@@ -19,9 +19,13 @@
package org.matsim.contrib.dvrp.passenger;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
import org.matsim.api.core.v01.Id;
+import org.matsim.api.core.v01.Identifiable;
import org.matsim.api.core.v01.population.Person;
import org.matsim.contrib.dvrp.optimizer.Request;
import org.matsim.contrib.dvrp.schedule.StayTask;
@@ -35,7 +39,7 @@ public class MultiPassengerPickupActivity extends FirstLastSimStepDynActivity im
private final Map, ? extends PassengerRequest> requests;
private final double expectedEndTime;
- private int passengersPickedUp = 0;
+ private int requestsPickedUp = 0;
public MultiPassengerPickupActivity(PassengerHandler passengerHandler, DynAgent driver, StayTask pickupTask,
Map, ? extends PassengerRequest> requests, String activityType) {
@@ -49,32 +53,32 @@ public MultiPassengerPickupActivity(PassengerHandler passengerHandler, DynAgent
@Override
protected boolean isLastStep(double now) {
- return passengersPickedUp == requests.size() && now >= expectedEndTime;
+ return requestsPickedUp == requests.size() && now >= expectedEndTime;
}
@Override
protected void beforeFirstStep(double now) {
for (PassengerRequest request : requests.values()) {
- if (passengerHandler.tryPickUpPassenger(this, driver, request.getId(), now)) {
- passengersPickedUp++;
+ if (passengerHandler.tryPickUpPassengers(this, driver, request.getId(), now)) {
+ requestsPickedUp++;
}
}
}
@Override
- public void notifyPassengerIsReadyForDeparture(MobsimPassengerAgent passenger, double now) {
- PassengerRequest request = getRequestForPassenger(passenger.getId());
- if (passengerHandler.tryPickUpPassenger(this, driver, request.getId(), now)) {
- passengersPickedUp++;
+ public void notifyPassengersAreReadyForDeparture(List passengers, double now) {
+ PassengerRequest request = getRequestForPassenger(passengers.stream().map(Identifiable::getId).toList());
+ if (passengerHandler.tryPickUpPassengers(this, driver, request.getId(), now)) {
+ requestsPickedUp++;
} else {
throw new IllegalStateException("The passenger is not on the link or not available for departure!");
}
}
- private PassengerRequest getRequestForPassenger(Id passengerId) {
+ private PassengerRequest getRequestForPassenger(List> passengerIds) {
return requests.values()
.stream()
- .filter(r -> passengerId.equals(r.getPassengerId()))
+ .filter(r -> r.getPassengerIds().size() == passengerIds.size() && r.getPassengerIds().containsAll(passengerIds))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("I am waiting for different passengers!"));
}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerEngineQSimModule.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerEngineQSimModule.java
index 6f9517c5153..e9d806af179 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerEngineQSimModule.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerEngineQSimModule.java
@@ -10,7 +10,7 @@
*/
public class PassengerEngineQSimModule extends AbstractDvrpModeQSimModule {
public enum PassengerEngineType {
- DEFAULT, WITH_PREBOOKING, TELEPORTING
+ DEFAULT, WITH_PREBOOKING, TELEPORTING, WITH_GROUPS
}
private final PassengerEngineType type;
@@ -44,6 +44,10 @@ protected void configureQSim() {
addModalComponent( PassengerEngine.class, TeleportingPassengerEngine.createProvider( getMode() ) );
return;
}
+ case WITH_GROUPS -> {
+ addModalComponent( PassengerEngine.class, GroupPassengerEngine.createProvider( getMode() ) );
+ return;
+ }
default -> throw new IllegalStateException( "Type: " + type + " is not supported" );
}
}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerEngineWithPrebooking.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerEngineWithPrebooking.java
index 354bb788a6f..dc7939b34df 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerEngineWithPrebooking.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerEngineWithPrebooking.java
@@ -19,12 +19,7 @@
package org.matsim.contrib.dvrp.passenger;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Queue;
+import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.Collectors;
@@ -139,11 +134,12 @@ public void bookTrip(MobsimPassengerAgent passenger, TripInfoWithRequiredBooking
double now = mobsimTimer.getTimeOfDay();
//TODO have a separate request creator for prebooking (accept TripInfo instead of Route)
PassengerRequest request = requestCreator.createRequest(internalPassengerHandling.createRequestId(),
- passenger.getId(), tripInfo.getOriginalRequest().getPlannedRoute(),
+ List.of(passenger.getId()), tripInfo.getOriginalRequest().getPlannedRoute(),
getLink(tripInfo.getPickupLocation().getLinkId()), getLink(tripInfo.getDropoffLocation().getLinkId()),
tripInfo.getExpectedBoardingTime(), now);
validateAndSubmitRequest(passenger, request, tripInfo.getOriginalRequest(), now);
- advanceRequests.put(request.getPassengerId(), request);
+ // hard assumption that with this engine, passenger ids is always a singleton. nkuehnel oct '23
+ advanceRequests.put(request.getPassengerIds().stream().findFirst().orElseThrow(), request);
}
private Link getLink(Id linkId) {
return Preconditions.checkNotNull(network.getLinks().get(linkId),
@@ -166,14 +162,14 @@ private Link getLink(Id linkId) {
//TODO what if it was already rejected while prebooking??
PassengerRequest prebookedRequest = prebookedRequests.get(0);
-
- eventsManager.processEvent(new PassengerWaitingEvent(now, mode, prebookedRequest.getId(), prebookedRequest.getPassengerId()));
-
+
+ eventsManager.processEvent(new PassengerWaitingEvent(now, mode, prebookedRequest.getId(), prebookedRequest.getPassengerIds()));
+
PassengerPickupActivity awaitingPickup = awaitingPickups.remove(prebookedRequest.getId());
if (awaitingPickup != null) {
- awaitingPickup.notifyPassengerIsReadyForDeparture(passenger, now);
+ awaitingPickup.notifyPassengersAreReadyForDeparture(List.of(passenger), now);
}
-
+
return true;
}
@@ -203,7 +199,7 @@ private void validateAndSubmitRequest(MobsimPassengerAgent passenger, PassengerR
// ================ PICKUP / DROPOFF
@Override
- public boolean tryPickUpPassenger(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver,
+ public boolean tryPickUpPassengers(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver,
Id requestId, double now) {
Id linkId = driver.getCurrentLinkId();
RequestEntry requestEntry = activeRequests.get(requestId);
@@ -216,7 +212,7 @@ public boolean tryPickUpPassenger(PassengerPickupActivity pickupActivity, Mobsim
return false;// wait for the passenger
}
- if (!internalPassengerHandling.tryPickUpPassenger(driver, passenger, requestId, now)) {
+ if (!internalPassengerHandling.tryPickUpPassengers(driver, List.of(passenger), requestId, now)) {
// the passenger has already been picked up and is on another taxi trip
// seems there have been at least 2 requests made by this passenger for this location
awaitingPickups.put(requestId, pickupActivity);
@@ -227,8 +223,8 @@ public boolean tryPickUpPassenger(PassengerPickupActivity pickupActivity, Mobsim
}
@Override
- public void dropOffPassenger(MobsimDriverAgent driver, Id requestId, double now) {
- internalPassengerHandling.dropOffPassenger(driver, activeRequests.remove(requestId).passenger, requestId, now);
+ public void dropOffPassengers(MobsimDriverAgent driver, Id requestId, double now) {
+ internalPassengerHandling.dropOffPassengers(driver, List.of(activeRequests.remove(requestId).passenger), requestId, now);
}
// ================ REJECTED/SCHEDULED EVENTS
@@ -307,7 +303,7 @@ public static Provider createProvider(String mode) {
* to be tested anywhere. /sebhoerl
*/
@Override
- public boolean notifyWaitForPassenger(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id requestId) {
+ public boolean notifyWaitForPassengers(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id requestId) {
Id linkId = driver.getCurrentLinkId();
RequestEntry requestEntry = activeRequests.get(requestId);
MobsimPassengerAgent passenger = requestEntry.passenger;
@@ -318,7 +314,7 @@ public boolean notifyWaitForPassenger(PassengerPickupActivity pickupActivity, Mo
awaitingPickups.put(requestId, pickupActivity);
return false;// wait for the passenger
}
-
+
return true; // passenger present?
}
}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerGroupIdentifier.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerGroupIdentifier.java
new file mode 100644
index 00000000000..8dba2bdac52
--- /dev/null
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerGroupIdentifier.java
@@ -0,0 +1,21 @@
+package org.matsim.contrib.dvrp.passenger;
+
+import org.matsim.api.core.v01.Id;
+import org.matsim.core.mobsim.framework.MobsimPassengerAgent;
+
+import java.util.function.Function;
+
+/**
+ * Provides a method to identify the passenger group id of an agent.
+ * @author nkuehnel / MOIA
+ */
+public interface PassengerGroupIdentifier extends Function> {
+
+ class PassengerGroup {
+ private PassengerGroup(){}
+ }
+
+ @Override
+ Id apply(MobsimPassengerAgent agent);
+
+}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerHandler.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerHandler.java
index 408f187db77..06912f29ae2 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerHandler.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerHandler.java
@@ -30,10 +30,10 @@
* This looks quite general. But as of now is a dvrp thing. kai, apr'23
*/
public interface PassengerHandler {
- boolean notifyWaitForPassenger(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id requestId);
+ boolean notifyWaitForPassengers(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id requestId);
- boolean tryPickUpPassenger(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id requestId,
+ boolean tryPickUpPassengers(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id requestId,
double now);
- void dropOffPassenger(MobsimDriverAgent driver, Id requestId, double now);
+ void dropOffPassengers(MobsimDriverAgent driver, Id requestId, double now);
}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerPickupActivity.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerPickupActivity.java
index a083ee9ce18..b8aa5cd611e 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerPickupActivity.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerPickupActivity.java
@@ -22,6 +22,9 @@
import org.matsim.contrib.dynagent.DynActivity;
import org.matsim.core.mobsim.framework.MobsimPassengerAgent;
+import java.util.List;
+import java.util.Set;
+
public interface PassengerPickupActivity extends DynActivity {
- void notifyPassengerIsReadyForDeparture(MobsimPassengerAgent passenger, double now);
+ void notifyPassengersAreReadyForDeparture(List passengers, double now);
}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequest.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequest.java
index b3378b28f14..e6cbca39641 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequest.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequest.java
@@ -24,6 +24,8 @@
import org.matsim.api.core.v01.population.Person;
import org.matsim.contrib.dvrp.optimizer.Request;
+import java.util.List;
+
public interface PassengerRequest extends Request {
/**
* @return beginning of the time window (inclusive) - earliest time when the passenger can be picked up
@@ -41,7 +43,9 @@ default double getLatestStartTime() {
Link getToLink();
- Id getPassengerId();
+ List> getPassengerIds();
String getMode();
+
+ int getPassengerCount();
}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequestCreator.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequestCreator.java
index 9423cfd113b..15d4fe0ddde 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequestCreator.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequestCreator.java
@@ -25,6 +25,8 @@
import org.matsim.api.core.v01.population.Route;
import org.matsim.contrib.dvrp.optimizer.Request;
+import java.util.List;
+
/**
* @author michalm
*/
@@ -34,7 +36,7 @@ public interface PassengerRequestCreator {
* Prefer stateless implementation, otherwise provide other ways to achieve thread-safety.
*
* @param id request ID
- * @param passengerId passenger ID
+ * @param passengerIds list of unique passenger IDs
* @param route planned route (the required route type depends on the optimizer)
* @param fromLink start location
* @param toLink end location
@@ -42,6 +44,6 @@ public interface PassengerRequestCreator {
* @param submissionTime time at which request was submitted
* @return
*/
- PassengerRequest createRequest(Id id, Id passengerId, Route route, Link fromLink, Link toLink,
- double departureTime, double submissionTime);
+ PassengerRequest createRequest(Id id, List> passengerIds, Route route, Link fromLink, Link toLink,
+ double departureTime, double submissionTime);
}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequestRejectedEvent.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequestRejectedEvent.java
index fbe61390f34..0d17138700d 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequestRejectedEvent.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequestRejectedEvent.java
@@ -20,14 +20,15 @@
package org.matsim.contrib.dvrp.passenger;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.events.GenericEvent;
import org.matsim.api.core.v01.population.Person;
import org.matsim.contrib.dvrp.optimizer.Request;
+import static org.matsim.api.core.v01.events.HasPersonId.ATTRIBUTE_PERSON;
+
/**
* @author michalm
*/
@@ -38,9 +39,9 @@ public class PassengerRequestRejectedEvent extends AbstractPassengerRequestEvent
private final String cause;
- public PassengerRequestRejectedEvent(double time, String mode, Id requestId, Id personId,
+ public PassengerRequestRejectedEvent(double time, String mode, Id requestId, List> personIds,
String cause) {
- super(time, mode, requestId, personId);
+ super(time, mode, requestId, personIds);
this.cause = cause;
}
@@ -65,8 +66,11 @@ public static PassengerRequestRejectedEvent convert(GenericEvent event) {
double time = Double.parseDouble(attributes.get(ATTRIBUTE_TIME));
String mode = Objects.requireNonNull(attributes.get(ATTRIBUTE_MODE));
Id requestId = Id.create(attributes.get(ATTRIBUTE_REQUEST), Request.class);
- Id personId = Id.createPersonId(attributes.get(ATTRIBUTE_PERSON));
- String cause = Objects.requireNonNull(attributes.get(ATTRIBUTE_CAUSE));
- return new PassengerRequestRejectedEvent(time, mode, requestId, personId, cause);
+ String[] personIdsAttribute = attributes.get(ATTRIBUTE_PERSON).split(",");
+ List> personIds = new ArrayList<>();
+ for (String person : personIdsAttribute) {
+ personIds.add(Id.create(person, Person.class));
+ } String cause = Objects.requireNonNull(attributes.get(ATTRIBUTE_CAUSE));
+ return new PassengerRequestRejectedEvent(time, mode, requestId, personIds, cause);
}
}
diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequestScheduledEvent.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequestScheduledEvent.java
index 9de0b19b344..413b668db89 100644
--- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequestScheduledEvent.java
+++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequestScheduledEvent.java
@@ -20,8 +20,7 @@
package org.matsim.contrib.dvrp.passenger;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.events.GenericEvent;
@@ -29,6 +28,8 @@
import org.matsim.contrib.dvrp.fleet.DvrpVehicle;
import org.matsim.contrib.dvrp.optimizer.Request;
+import static org.matsim.api.core.v01.events.HasPersonId.ATTRIBUTE_PERSON;
+
/**
* @author michalm
*/
@@ -46,9 +47,9 @@ public class PassengerRequestScheduledEvent extends AbstractPassengerRequestEven
/**
* An event processed upon request submission.
*/
- public PassengerRequestScheduledEvent(double time, String mode, Id requestId, Id personId,
+ public PassengerRequestScheduledEvent(double time, String mode, Id requestId, List> personIds,
Id vehicleId, double pickupTime, double dropoffTime) {
- super(time, mode, requestId, personId);
+ super(time, mode, requestId, personIds);
this.vehicleId = vehicleId;
this.pickupTime = pickupTime;
this.dropoffTime = dropoffTime;
@@ -94,10 +95,14 @@ public static PassengerRequestScheduledEvent convert(GenericEvent event) {
double time = Double.parseDouble(attributes.get(ATTRIBUTE_TIME));
String mode = Objects.requireNonNull(attributes.get(ATTRIBUTE_MODE));
Id requestId = Id.create(attributes.get(ATTRIBUTE_REQUEST), Request.class);
- Id personId = Id.createPersonId(attributes.get(ATTRIBUTE_PERSON));
+ String[] personIdsAttribute = attributes.get(ATTRIBUTE_PERSON).split(",");
+ List