diff --git a/contribs/drt-extensions/pom.xml b/contribs/drt-extensions/pom.xml index 2217f9ebdc7..3e2cbf8dcf7 100644 --- a/contribs/drt-extensions/pom.xml +++ b/contribs/drt-extensions/pom.xml @@ -48,6 +48,11 @@ 16.0-SNAPSHOT test + + + org.mockito + mockito-core + diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/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 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> personIds = new ArrayList<>(); + for (String person : personIdsAttribute) { + personIds.add(Id.create(person, Person.class)); + } Id vehicleId = Id.create(attributes.get(ATTRIBUTE_VEHICLE), DvrpVehicle.class); double pickupTime = Double.parseDouble(attributes.get(ATTRIBUTE_PICKUP_TIME)); double dropoffTime = Double.parseDouble(attributes.get(ATTRIBUTE_DROPOFF_TIME)); - return new PassengerRequestScheduledEvent(time, mode, requestId, personId, vehicleId, pickupTime, dropoffTime); + return new PassengerRequestScheduledEvent(time, mode, requestId, personIds, vehicleId, pickupTime, dropoffTime); } } diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequestSubmittedEvent.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequestSubmittedEvent.java index 73dd882740c..0b7981bd634 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequestSubmittedEvent.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerRequestSubmittedEvent.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.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 */ @@ -41,9 +42,9 @@ public class PassengerRequestSubmittedEvent extends AbstractPassengerRequestEven private final Id fromLinkId; private final Id toLinkId; - public PassengerRequestSubmittedEvent(double time, String mode, Id requestId, Id personId, + public PassengerRequestSubmittedEvent(double time, String mode, Id requestId, List> personIds, Id fromLinkId, Id toLinkId) { - super(time, mode, requestId, personId); + super(time, mode, requestId, personIds); this.fromLinkId = fromLinkId; this.toLinkId = toLinkId; } @@ -80,9 +81,14 @@ public static PassengerRequestSubmittedEvent 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)); - return new PassengerRequestSubmittedEvent(time, mode, requestId, personId, fromLinkId, toLinkId); + return new PassengerRequestSubmittedEvent(time, mode, requestId, personIds, fromLinkId, toLinkId); } } diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerWaitingEvent.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerWaitingEvent.java index 6c37f84a9be..db9a477954e 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerWaitingEvent.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/PassengerWaitingEvent.java @@ -19,6 +19,8 @@ package org.matsim.contrib.dvrp.passenger; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -33,8 +35,8 @@ public class PassengerWaitingEvent extends AbstractPassengerRequestEvent { public static final String EVENT_TYPE = "passenger waiting"; - public PassengerWaitingEvent(double time, String mode, Id requestId, Id personId) { - super(time, mode, requestId, personId); + public PassengerWaitingEvent(double time, String mode, Id requestId, List> personIds) { + super(time, mode, requestId, personIds); } @Override @@ -47,7 +49,11 @@ public static PassengerWaitingEvent 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)); - return new PassengerWaitingEvent(time, mode, requestId, personId); + String[] personIdsAttribute = attributes.get(ATTRIBUTE_PERSON).split(","); + List> personIds = new ArrayList<>(); + for (String person : personIdsAttribute) { + personIds.add(Id.create(person, Person.class)); + } + return new PassengerWaitingEvent(time, mode, requestId, personIds); } } diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/SinglePassengerDropoffActivity.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/SinglePassengerDropoffActivity.java index 5d74a5ed024..afab824bd96 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/SinglePassengerDropoffActivity.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/SinglePassengerDropoffActivity.java @@ -48,6 +48,6 @@ protected boolean isLastStep(double now) { @Override protected void afterLastStep(double now) { - 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/SinglePassengerPickupActivity.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/SinglePassengerPickupActivity.java index 33883acfcd6..3f0ee34bb62 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/SinglePassengerPickupActivity.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/SinglePassengerPickupActivity.java @@ -19,18 +19,23 @@ package org.matsim.contrib.dvrp.passenger; +import org.matsim.api.core.v01.Identifiable; 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; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + public class SinglePassengerPickupActivity extends FirstLastSimStepDynActivity implements PassengerPickupActivity { private final PassengerHandler passengerHandler; private final DynAgent driver; private final PassengerRequest request; private final double expectedEndTime; - private boolean passengerAboard = false; + private boolean passengersAboard = false; public SinglePassengerPickupActivity(PassengerHandler passengerHandler, DynAgent driver, StayTask pickupTask, PassengerRequest request, String activityType) { @@ -44,22 +49,22 @@ public SinglePassengerPickupActivity(PassengerHandler passengerHandler, DynAgent @Override protected boolean isLastStep(double now) { - return passengerAboard && now >= expectedEndTime; + return passengersAboard && now >= expectedEndTime; } @Override protected void beforeFirstStep(double now) { - passengerAboard = passengerHandler.tryPickUpPassenger(this, driver, request.getId(), now); + passengersAboard = passengerHandler.tryPickUpPassengers(this, driver, request.getId(), now); } @Override - public void notifyPassengerIsReadyForDeparture(MobsimPassengerAgent passenger, double now) { - if (passenger.getId().equals(request.getPassengerId())) { + public void notifyPassengersAreReadyForDeparture(List passengers, double now) { + if (request.getPassengerIds().containsAll(passengers.stream().map(Identifiable::getId).toList())) { throw new IllegalArgumentException("I am waiting for a different passenger!"); } - passengerAboard = passengerHandler.tryPickUpPassenger(this, driver, request.getId(), now); - if (!passengerAboard) { + passengersAboard = passengerHandler.tryPickUpPassengers(this, driver, request.getId(), now); + if (!passengersAboard) { throw new IllegalStateException("The passenger is not on the link or not available for departure!"); } } diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngine.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngine.java index 14374da7168..1f773a77043 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngine.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngine.java @@ -20,10 +20,7 @@ package org.matsim.contrib.dvrp.passenger; -import java.util.Collection; -import java.util.Comparator; -import java.util.PriorityQueue; -import java.util.Queue; +import java.util.*; import jakarta.inject.Inject; import jakarta.inject.Provider; @@ -35,6 +32,7 @@ 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.Route; import org.matsim.contrib.dvrp.optimizer.Request; import org.matsim.contrib.dvrp.run.DvrpModes; @@ -120,8 +118,10 @@ public void doSimStep(double time) { //first process passenger dropoff events while (!teleportedRequests.isEmpty() && teleportedRequests.peek().getLeft() <= time) { PassengerRequest request = teleportedRequests.poll().getRight(); - eventsManager.processEvent( - new PassengerDroppedOffEvent(time, mode, request.getId(), request.getPassengerId(), null)); + for (Id passenger : request.getPassengerIds()) { + eventsManager.processEvent( + new PassengerDroppedOffEvent(time, mode, request.getId(), passenger, null)); + } } //then end teleported rides @@ -143,12 +143,12 @@ public boolean handleDeparture(double now, MobsimAgent agent, Id fromLinkI Id toLinkId = passenger.getDestinationLinkId(); Route route = ((Leg)((PlanAgent)passenger).getCurrentPlanElement()).getRoute(); PassengerRequest request = requestCreator.createRequest(internalPassengerHandling.createRequestId(), - passenger.getId(), route, getLink(fromLinkId), getLink(toLinkId), now, now); - - eventsManager.processEvent(new PassengerWaitingEvent(now, mode, request.getId(), request.getPassengerId())); + List.of(passenger.getId()), route, getLink(fromLinkId), getLink(toLinkId), now, now); + + eventsManager.processEvent(new PassengerWaitingEvent(now, mode, request.getId(), request.getPassengerIds())); if (internalPassengerHandling.validateRequest(request, requestValidator, now)) { - Route teleportedRoute = adaptLegRouteForTeleportation(passenger, request, now); + Route teleportedRoute = adaptLegRouteForTeleportation(List.of(passenger), request, now); eventsManager.processEvent(new PassengerPickedUpEvent(now, mode, request.getId(), passenger.getId(), null)); teleportationEngine.handleDeparture(now, passenger, fromLinkId); teleportedRequests.add(ImmutablePair.of(now + teleportedRoute.getTravelTime().seconds(), request)); @@ -158,24 +158,26 @@ public boolean handleDeparture(double now, MobsimAgent agent, Id fromLinkI passenger.setStateToAbort(mobsimTimer.getTimeOfDay()); internalInterface.arrangeNextAgentState(passenger); } - + return true; } - private Route adaptLegRouteForTeleportation(MobsimPassengerAgent passenger, PassengerRequest request, double now) { + private Route adaptLegRouteForTeleportation(List passengers, PassengerRequest request, double now) { Route teleportedRoute = teleportedRouteCalculator.calculateRoute(request); - Leg leg = (Leg)WithinDayAgentUtils.getCurrentPlanElement(passenger);//side effect: makes the plan modifiable - Route originalRoute = leg.getRoute(); - Verify.verify(originalRoute.getStartLinkId().equals(teleportedRoute.getStartLinkId())); - Verify.verify(originalRoute.getEndLinkId().equals(teleportedRoute.getEndLinkId())); - Verify.verify(teleportedRoute.getTravelTime().isDefined()); + for (MobsimPassengerAgent passenger : passengers) { + Leg leg = (Leg)WithinDayAgentUtils.getCurrentPlanElement(passenger);//side effect: makes the plan modifiable + Route originalRoute = leg.getRoute(); + Verify.verify(originalRoute.getStartLinkId().equals(teleportedRoute.getStartLinkId())); + Verify.verify(originalRoute.getEndLinkId().equals(teleportedRoute.getEndLinkId())); + Verify.verify(teleportedRoute.getTravelTime().isDefined()); - leg.getAttributes().putAttribute(ORIGINAL_ROUTE_ATTRIBUTE, originalRoute); - leg.setRoute(teleportedRoute); + leg.getAttributes().putAttribute(ORIGINAL_ROUTE_ATTRIBUTE, originalRoute); + leg.setRoute(teleportedRoute); + } eventsManager.processEvent(new PassengerRequestScheduledEvent(mobsimTimer.getTimeOfDay(), mode, request.getId(), - request.getPassengerId(), null, now, now + teleportedRoute.getTravelTime().seconds())); + request.getPassengerIds(), null, now, now + teleportedRoute.getTravelTime().seconds())); return teleportedRoute; } @@ -186,19 +188,19 @@ private Link getLink(Id linkId) { } @Override - public boolean notifyWaitForPassenger(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, + public boolean notifyWaitForPassengers(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id requestId) { throw new UnsupportedOperationException("No notifying when teleporting"); } @Override - public boolean tryPickUpPassenger(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, + public boolean tryPickUpPassengers(PassengerPickupActivity pickupActivity, MobsimDriverAgent driver, Id requestId, double now) { throw new UnsupportedOperationException("No picking-up when teleporting"); } @Override - public void dropOffPassenger(MobsimDriverAgent driver, Id requestId, double now) { + public void dropOffPassengers(MobsimDriverAgent driver, Id requestId, double now) { throw new UnsupportedOperationException("No dropping-off when teleporting"); } diff --git a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/examples/onetaxi/RunOneTaxiWithPrebookingExampleIT.java b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/examples/onetaxi/RunOneTaxiWithPrebookingExampleIT.java index c2b6f6a5753..b6c7e2ed828 100644 --- a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/examples/onetaxi/RunOneTaxiWithPrebookingExampleIT.java +++ b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/examples/onetaxi/RunOneTaxiWithPrebookingExampleIT.java @@ -142,7 +142,7 @@ public void install() { if (event instanceof AgentWakeupEvent) { wakeupEvents.put(((AgentWakeupEvent)event).getPersonId(), (AgentWakeupEvent)event); } else if (event instanceof PassengerRequestScheduledEvent) { - requestScheduledEvents.put(((PassengerRequestScheduledEvent)event).getPersonId(), + requestScheduledEvents.put(((PassengerRequestScheduledEvent)event).getPersonIds().stream().findFirst().orElseThrow(), (PassengerRequestScheduledEvent)event); } else if (event instanceof ActivityEndEvent && ((ActivityEndEvent)event).getActType() .equals("dummy")) { @@ -239,7 +239,7 @@ private static void assertRequestScheduledEvent(Map, PassengerRequest PassengerRequestScheduledEvent event = events.get(Id.createPersonId(personId)); assertThat(event.getVehicleId().toString()).isEqualTo("taxi_one"); assertThat(event.getPickupTime()).isCloseTo(pickupTime, Offset.offset(0.01)); - assertThat(event.getPersonId().toString()).isEqualTo(personId); + assertThat(event.getPersonIds().get(0).toString()).isEqualTo(personId); assertThat(event.getRequestId().toString()).isEqualTo(requestId); } diff --git a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/DefaultPassengerEngineTest.java b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/DefaultPassengerEngineTest.java index 6636119ccd3..db4efe8d602 100644 --- a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/DefaultPassengerEngineTest.java +++ b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/DefaultPassengerEngineTest.java @@ -22,6 +22,8 @@ import static org.matsim.contrib.dvrp.passenger.PassengerEngineTestFixture.*; +import java.util.Collections; +import java.util.List; import java.util.Set; import org.junit.Test; @@ -60,7 +62,6 @@ import com.google.common.collect.ImmutableMap; import com.google.inject.Inject; -import com.google.inject.name.Names; /** * @author Michal Maciejewski (michalm) @@ -81,7 +82,7 @@ public class DefaultPassengerEngineTest { @Test public void test_valid_served() { double departureTime = 0; - fixture.addPersonWithLeg(fixture.linkAB, fixture.linkBA, departureTime); + fixture.addPersonWithLeg(fixture.linkAB, fixture.linkBA, departureTime, fixture.PERSON_ID); PassengerRequestValidator requestValidator = request -> Set.of();//valid createQSim(requestValidator, OneTaxiOptimizer.class).run(); @@ -98,10 +99,11 @@ public void test_valid_served() { var requestId = Id.create("taxi_0", Request.class); fixture.assertPassengerEvents( + Collections.singleton(fixture.PERSON_ID), new ActivityEndEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), null, START_ACTIVITY), new PersonDepartureEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), MODE, MODE), - new PassengerWaitingEvent(departureTime, MODE, requestId, fixture.PERSON_ID), - new PassengerRequestScheduledEvent(departureTime, MODE, requestId, fixture.PERSON_ID, VEHICLE_ID, 0, + new PassengerWaitingEvent(departureTime, MODE, requestId, List.of(fixture.PERSON_ID)), + new PassengerRequestScheduledEvent(departureTime, MODE, requestId, List.of(fixture.PERSON_ID), VEHICLE_ID, 0, scheduledDropoffTime), new PersonEntersVehicleEvent(pickupStartTime, fixture.PERSON_ID, Id.createVehicleId(VEHICLE_ID)), new PassengerPickedUpEvent(pickupStartTime, MODE, requestId, fixture.PERSON_ID, VEHICLE_ID), @@ -114,33 +116,37 @@ public void test_valid_served() { @Test public void test_invalid_rejected() { double departureTime = 0; - fixture.addPersonWithLeg(fixture.linkAB, fixture.linkBA, departureTime); + fixture.addPersonWithLeg(fixture.linkAB, fixture.linkBA, departureTime, fixture.PERSON_ID); PassengerRequestValidator requestValidator = request -> Set.of("invalid"); createQSim(requestValidator, OneTaxiOptimizer.class).run(); var requestId = Id.create("taxi_0", Request.class); - fixture.assertPassengerEvents(new ActivityEndEvent(0, fixture.PERSON_ID, fixture.linkAB.getId(), null, START_ACTIVITY), + fixture.assertPassengerEvents( + Collections.singleton(fixture.PERSON_ID), + new ActivityEndEvent(0, fixture.PERSON_ID, fixture.linkAB.getId(), null, START_ACTIVITY), new PersonDepartureEvent(0, fixture.PERSON_ID, fixture.linkAB.getId(), MODE, MODE), - new PassengerWaitingEvent(departureTime, MODE, requestId, fixture.PERSON_ID), - new PassengerRequestRejectedEvent(0, MODE, requestId, fixture.PERSON_ID, "invalid"), - new PersonStuckEvent(0, fixture.PERSON_ID, fixture.linkAB.getId(), MODE)); + new PassengerWaitingEvent(departureTime, MODE, requestId, List.of(fixture.PERSON_ID)), + new PassengerRequestRejectedEvent(0, MODE, requestId, List.of(fixture.PERSON_ID), "invalid"), + new PersonStuckEvent(1, fixture.PERSON_ID, fixture.linkAB.getId(), MODE)); } @Test public void test_valid_rejected() { double departureTime = 0; - fixture.addPersonWithLeg(fixture.linkAB, fixture.linkBA, departureTime); + fixture.addPersonWithLeg(fixture.linkAB, fixture.linkBA, departureTime, fixture.PERSON_ID); PassengerRequestValidator requestValidator = request -> Set.of(); createQSim(requestValidator, RejectingOneTaxiOptimizer.class).run(); var requestId = Id.create("taxi_0", Request.class); - fixture.assertPassengerEvents(new ActivityEndEvent(0, fixture.PERSON_ID, fixture.linkAB.getId(), null, START_ACTIVITY), + fixture.assertPassengerEvents( + Collections.singleton(fixture.PERSON_ID), + new ActivityEndEvent(0, fixture.PERSON_ID, fixture.linkAB.getId(), null, START_ACTIVITY), new PersonDepartureEvent(0, fixture.PERSON_ID, fixture.linkAB.getId(), MODE, MODE), - new PassengerWaitingEvent(departureTime, MODE, requestId, fixture.PERSON_ID), - new PassengerRequestRejectedEvent(0, MODE, requestId, fixture.PERSON_ID, "rejecting_all_requests"), - new PersonStuckEvent(0, fixture.PERSON_ID, fixture.linkAB.getId(), MODE)); + new PassengerWaitingEvent(departureTime, MODE, requestId, List.of(fixture.PERSON_ID)), + new PassengerRequestRejectedEvent(0, MODE, requestId, List.of(fixture.PERSON_ID), "rejecting_all_requests"), + new PersonStuckEvent(1, fixture.PERSON_ID, fixture.linkAB.getId(), MODE)); } private static class RejectingOneTaxiOptimizer implements VrpOptimizer { @@ -154,7 +160,7 @@ private static class RejectingOneTaxiOptimizer implements VrpOptimizer { public void requestSubmitted(Request request) { PassengerRequest passengerRequest = (PassengerRequest)request; eventsManager.processEvent(new PassengerRequestRejectedEvent(timer.getTimeOfDay(), MODE, request.getId(), - passengerRequest.getPassengerId(), "rejecting_all_requests")); + passengerRequest.getPassengerIds(), "rejecting_all_requests")); } @Override diff --git a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/GroupPassengerEngineTest.java b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/GroupPassengerEngineTest.java new file mode 100644 index 00000000000..2f34014ca2f --- /dev/null +++ b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/GroupPassengerEngineTest.java @@ -0,0 +1,133 @@ +package org.matsim.contrib.dvrp.passenger; + +import com.google.common.collect.ImmutableMap; +import org.apache.commons.compress.utils.Sets; +import org.junit.Test; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.*; +import org.matsim.api.core.v01.network.Network; +import org.matsim.api.core.v01.population.Person; +import org.matsim.contrib.dvrp.examples.onetaxi.OneTaxiActionCreator; +import org.matsim.contrib.dvrp.examples.onetaxi.OneTaxiOptimizer; +import org.matsim.contrib.dvrp.examples.onetaxi.OneTaxiRequest; +import org.matsim.contrib.dvrp.fleet.DvrpVehicle; +import org.matsim.contrib.dvrp.fleet.DvrpVehicleImpl; +import org.matsim.contrib.dvrp.fleet.Fleet; +import org.matsim.contrib.dvrp.fleet.ImmutableDvrpVehicleSpecification; +import org.matsim.contrib.dvrp.optimizer.Request; +import org.matsim.contrib.dvrp.optimizer.VrpOptimizer; +import org.matsim.contrib.dvrp.run.AbstractDvrpModeQSimModule; +import org.matsim.contrib.dvrp.run.DvrpModes; +import org.matsim.contrib.dvrp.run.MobsimTimerProvider; +import org.matsim.contrib.dvrp.vrpagent.VrpAgentLogic; +import org.matsim.contrib.dvrp.vrpagent.VrpAgentSourceQSimModule; +import org.matsim.contrib.dynagent.run.DynActivityEngine; +import org.matsim.core.events.MobsimScopeEventHandlingModule; +import org.matsim.core.mobsim.framework.MobsimTimer; +import org.matsim.core.mobsim.qsim.QSim; +import org.matsim.core.mobsim.qsim.QSimBuilder; +import org.matsim.vehicles.VehicleType; +import org.matsim.vehicles.VehicleUtils; + +import java.util.List; +import java.util.Set; + +import static org.matsim.contrib.dvrp.passenger.PassengerEngineTestFixture.*; + +public class GroupPassengerEngineTest { + + private final PassengerEngineTestFixture fixture = new PassengerEngineTestFixture(); + + private final Id VEHICLE_ID = Id.create("taxi1", DvrpVehicle.class); + private final DvrpVehicle oneTaxi = new DvrpVehicleImpl(ImmutableDvrpVehicleSpecification.newBuilder() + .id(VEHICLE_ID) + .serviceBeginTime(0) + .serviceEndTime(3600) + .startLinkId(fixture.linkAB.getId()) + .capacity(1) + .build(), fixture.linkAB); + private final Fleet fleet = () -> ImmutableMap.of(oneTaxi.getId(), oneTaxi); + + @Test + public void test_group() { + double departureTime = 0; + Id person1 = Id.createPersonId("1"); + Id person2 = Id.createPersonId("2"); + + fixture.addPersonWithLeg(fixture.linkAB, fixture.linkBA, departureTime, person1); + fixture.addPersonWithLeg(fixture.linkAB, fixture.linkBA, departureTime, person2); + + PassengerRequestValidator requestValidator = request -> Set.of();//valid + createQSim(requestValidator, OneTaxiOptimizer.class).run(); + + double pickupStartTime = 1; + double pickupEndTime = pickupStartTime + OneTaxiOptimizer.PICKUP_DURATION; + double taxiDepartureTime = pickupEndTime + 1; + double taxiEntersLinkBATime = taxiDepartureTime + 1; + double taxiArrivalTime = taxiEntersLinkBATime + (fixture.linkBA.getLength() / fixture.linkBA.getFreespeed()); + double dropoffEndTime = taxiArrivalTime + OneTaxiOptimizer.DROPOFF_DURATION; + + //1 second delay between pickupEndTime and taxiDepartureTime is not considered in schedules + double scheduledDropoffTime = dropoffEndTime - pickupStartTime - 1; + + var requestId = Id.create("taxi_0", Request.class); + fixture.assertPassengerEvents( + List.of(person1, person2), + new ActivityEndEvent(departureTime, person2, fixture.linkAB.getId(), null, START_ACTIVITY), + new PersonDepartureEvent(departureTime, person2, fixture.linkAB.getId(), MODE, MODE), + new ActivityEndEvent(departureTime, person1, fixture.linkAB.getId(), null, START_ACTIVITY), + new PersonDepartureEvent(departureTime, person1, fixture.linkAB.getId(), MODE, MODE), + new PassengerWaitingEvent(departureTime, MODE, requestId, List.of(person1, person2)), + new PassengerRequestScheduledEvent(departureTime, MODE, requestId, List.of(person1, person2), VEHICLE_ID, 0, + scheduledDropoffTime), + new PersonEntersVehicleEvent(pickupStartTime, person1, Id.createVehicleId(VEHICLE_ID)), + new PassengerPickedUpEvent(pickupStartTime, MODE, requestId, person1, VEHICLE_ID), + new PersonEntersVehicleEvent(pickupStartTime, person2, Id.createVehicleId(VEHICLE_ID)), + new PassengerPickedUpEvent(pickupStartTime, MODE, requestId, person2, VEHICLE_ID), + new PassengerDroppedOffEvent(dropoffEndTime, MODE, requestId, person1, VEHICLE_ID), + new PersonLeavesVehicleEvent(dropoffEndTime, person1, Id.createVehicleId(VEHICLE_ID)), + new PersonArrivalEvent(dropoffEndTime, person1, fixture.linkBA.getId(), MODE), + new ActivityStartEvent(dropoffEndTime, person1, fixture.linkBA.getId(), null, END_ACTIVITY), + new PassengerDroppedOffEvent(dropoffEndTime, MODE, requestId, person2, VEHICLE_ID), + new PersonLeavesVehicleEvent(dropoffEndTime, person2, Id.createVehicleId(VEHICLE_ID)), + new PersonArrivalEvent(dropoffEndTime, person2, fixture.linkBA.getId(), MODE), + new ActivityStartEvent(dropoffEndTime, person2, fixture.linkBA.getId(), null, END_ACTIVITY) + ); + } + + + private QSim createQSim(PassengerRequestValidator requestValidator, Class optimizerClass) { + return new QSimBuilder(fixture.config).useDefaults() + .addOverridingModule(new MobsimScopeEventHandlingModule()) + .addQSimModule(new PassengerEngineQSimModule(MODE, PassengerEngineQSimModule.PassengerEngineType.WITH_GROUPS)) + .addQSimModule(new VrpAgentSourceQSimModule(MODE)) + .addQSimModule(new AbstractDvrpModeQSimModule(MODE) { + @Override + protected void configureQSim() { + bindModal(Network.class).toInstance(fixture.network); + bind(MobsimTimer.class).toProvider(MobsimTimerProvider.class).asEagerSingleton(); + + //requests + bindModal(PassengerRequestCreator.class).to(OneTaxiRequest.OneTaxiRequestCreator.class) + .asEagerSingleton(); + bindModal(PassengerRequestValidator.class).toInstance(requestValidator); + + //supply + addQSimComponentBinding(DynActivityEngine.COMPONENT_NAME).to(DynActivityEngine.class); + bindModal(Fleet.class).toInstance(fleet); + bindModal(VehicleType.class).toInstance(VehicleUtils.getDefaultVehicleType()); + bindModal(VrpOptimizer.class).to(optimizerClass).asEagerSingleton(); + bindModal(VrpAgentLogic.DynActionCreator.class).to(OneTaxiActionCreator.class) + .asEagerSingleton(); + + //groups + bindModal(PassengerGroupIdentifier.class).toInstance(agent -> Id.create("group1", PassengerGroupIdentifier.PassengerGroup.class)); + } + }) + .configureQSimComponents(components -> { + components.addComponent(DvrpModes.mode(MODE)); + components.addNamedComponent(DynActivityEngine.COMPONENT_NAME); + }) + .build(fixture.scenario, fixture.eventsManager); + } +} diff --git a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/PassengerEngineTestFixture.java b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/PassengerEngineTestFixture.java index 84ec6202a63..1f83607037f 100644 --- a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/PassengerEngineTestFixture.java +++ b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/PassengerEngineTestFixture.java @@ -23,6 +23,7 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.matsim.api.core.v01.Coord; @@ -81,7 +82,7 @@ public PassengerEngineTestFixture() { eventsManager.initProcessing(); } - void addPersonWithLeg(Link fromLink, Link toLink, double departureTime) { + void addPersonWithLeg(Link fromLink, Link toLink, double departureTime, Id person_id) { PopulationFactory factory = scenario.getPopulation().getFactory(); Plan plan = factory.createPlan(); @@ -99,15 +100,18 @@ void addPersonWithLeg(Link fromLink, Link toLink, double departureTime) { plan.addActivity(factory.createActivityFromLinkId(END_ACTIVITY, toLink.getId())); - Person person = factory.createPerson(PERSON_ID); + Person person = factory.createPerson(person_id); person.addPlan(plan); scenario.getPopulation().addPerson(person); } - void assertPassengerEvents(Event... events) { + void assertPassengerEvents(Collection> personIds, Event... events) { assertThat(recordedEvents.size()).isGreaterThanOrEqualTo(events.length); var recordedPassengerEvents = recordedEvents.stream() - .filter(e -> e instanceof HasPersonId && ((HasPersonId)e).getPersonId().equals(PERSON_ID)); + .filter(e -> + e instanceof HasPersonId && personIds.contains(((HasPersonId)e).getPersonId()) || + e instanceof AbstractPassengerRequestEvent + ); assertThat(recordedPassengerEvents).usingRecursiveFieldByFieldElementComparator().containsExactly(events); } } diff --git a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngineTest.java b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngineTest.java index b8153c22ccd..6d1c3edd7d1 100644 --- a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngineTest.java +++ b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/passenger/TeleportingPassengerEngineTest.java @@ -20,17 +20,9 @@ package org.matsim.contrib.dvrp.passenger; -import static org.matsim.contrib.dvrp.passenger.PassengerEngineTestFixture.*; - -import java.util.Set; - import org.junit.Test; import org.matsim.api.core.v01.Id; -import org.matsim.api.core.v01.events.ActivityEndEvent; -import org.matsim.api.core.v01.events.ActivityStartEvent; -import org.matsim.api.core.v01.events.PersonArrivalEvent; -import org.matsim.api.core.v01.events.PersonDepartureEvent; -import org.matsim.api.core.v01.events.PersonStuckEvent; +import org.matsim.api.core.v01.events.*; import org.matsim.api.core.v01.network.Network; import org.matsim.api.core.v01.population.Route; import org.matsim.contrib.dvrp.optimizer.Request; @@ -45,6 +37,12 @@ import org.matsim.core.mobsim.qsim.QSimBuilder; import org.matsim.core.population.routes.GenericRouteImpl; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static org.matsim.contrib.dvrp.passenger.PassengerEngineTestFixture.*; + /** * @author Michal Maciejewski (michalm) */ @@ -54,7 +52,7 @@ public class TeleportingPassengerEngineTest { @Test public void test_valid_teleported() { double departureTime = 0; - fixture.addPersonWithLeg(fixture.linkAB, fixture.linkBA, departureTime); + fixture.addPersonWithLeg(fixture.linkAB, fixture.linkBA, departureTime, fixture.PERSON_ID); double travelTime = 999; double travelDistance = 555; @@ -70,10 +68,11 @@ public void test_valid_teleported() { double arrivalTime = departureTime + travelTime; var requestId = Id.create("taxi_0", Request.class); fixture.assertPassengerEvents( + Collections.singleton(fixture.PERSON_ID), new ActivityEndEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), null, START_ACTIVITY), new PersonDepartureEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), MODE, MODE), - new PassengerWaitingEvent(departureTime, MODE, requestId, fixture.PERSON_ID), - new PassengerRequestScheduledEvent(departureTime, MODE, requestId, fixture.PERSON_ID, null, departureTime, + new PassengerWaitingEvent(departureTime, MODE, requestId, List.of(fixture.PERSON_ID)), + new PassengerRequestScheduledEvent(departureTime, MODE, requestId, List.of(fixture.PERSON_ID), null, departureTime, arrivalTime), new PassengerPickedUpEvent(departureTime, MODE, requestId, fixture.PERSON_ID, null), new PassengerDroppedOffEvent(arrivalTime, MODE, requestId, fixture.PERSON_ID, null), new TeleportationArrivalEvent(arrivalTime, fixture.PERSON_ID, travelDistance, MODE), @@ -84,7 +83,7 @@ public void test_valid_teleported() { @Test public void test_invalid_rejected() { double departureTime = 0; - fixture.addPersonWithLeg(fixture.linkAB, fixture.linkBA, departureTime); + fixture.addPersonWithLeg(fixture.linkAB, fixture.linkBA, departureTime, fixture.PERSON_ID); TeleportedRouteCalculator teleportedRouteCalculator = request -> null; // unused PassengerRequestValidator requestValidator = request -> Set.of("invalid"); @@ -92,10 +91,11 @@ public void test_invalid_rejected() { var requestId = Id.create("taxi_0", Request.class); fixture.assertPassengerEvents( + Collections.singleton(fixture.PERSON_ID), new ActivityEndEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), null, START_ACTIVITY), new PersonDepartureEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), MODE, MODE), - new PassengerWaitingEvent(departureTime, MODE, requestId, fixture.PERSON_ID), - new PassengerRequestRejectedEvent(departureTime, MODE, requestId, fixture.PERSON_ID, "invalid"), + new PassengerWaitingEvent(departureTime, MODE, requestId, List.of(fixture.PERSON_ID)), + new PassengerRequestRejectedEvent(departureTime, MODE, requestId, List.of(fixture.PERSON_ID), "invalid"), new PersonStuckEvent(departureTime, fixture.PERSON_ID, fixture.linkAB.getId(), MODE)); } diff --git a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/util/DvrpEventsReadersTest.java b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/util/DvrpEventsReadersTest.java index 6f661026552..b9452d7c495 100644 --- a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/util/DvrpEventsReadersTest.java +++ b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/util/DvrpEventsReadersTest.java @@ -26,6 +26,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; @@ -71,9 +72,9 @@ private enum TestTaskType implements Task.TaskType { } private final List dvrpEvents = List.of( - new PassengerRequestSubmittedEvent(0, mode, request, person, fromLink, toLink), - new PassengerRequestScheduledEvent(1, mode, request, person, vehicle, 100, 200), - new PassengerRequestRejectedEvent(2, mode, request, person, "cause_1"), + new PassengerRequestSubmittedEvent(0, mode, request, List.of(person), fromLink, toLink), + new PassengerRequestScheduledEvent(1, mode, request, List.of(person), vehicle, 100, 200), + new PassengerRequestRejectedEvent(2, mode, request, List.of(person), "cause_1"), new PassengerPickedUpEvent(111, mode, request, person, vehicle), new PassengerDroppedOffEvent(222, mode, request, person, vehicle), new TaskStartedEvent(300, mode, vehicle, driver, TestTaskType.DRIVE_TASK, 0, fromLink), diff --git a/contribs/ev/src/main/java/org/matsim/contrib/ev/routing/EvNetworkRoutingModule.java b/contribs/ev/src/main/java/org/matsim/contrib/ev/routing/EvNetworkRoutingModule.java index 10123db4717..8f2eae8aa25 100644 --- a/contribs/ev/src/main/java/org/matsim/contrib/ev/routing/EvNetworkRoutingModule.java +++ b/contribs/ev/src/main/java/org/matsim/contrib/ev/routing/EvNetworkRoutingModule.java @@ -18,12 +18,6 @@ * *********************************************************************** */ package org.matsim.contrib.ev.routing; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; - import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.TransportMode; import org.matsim.api.core.v01.network.Link; @@ -32,14 +26,17 @@ import org.matsim.api.core.v01.population.Leg; import org.matsim.api.core.v01.population.Person; import org.matsim.api.core.v01.population.PlanElement; +import org.matsim.contrib.common.util.StraightLineKnnFinder; import org.matsim.contrib.ev.EvConfigGroup; import org.matsim.contrib.ev.charging.VehicleChargingHandler; import org.matsim.contrib.ev.discharging.AuxEnergyConsumption; import org.matsim.contrib.ev.discharging.DriveEnergyConsumption; -import org.matsim.contrib.ev.fleet.*; +import org.matsim.contrib.ev.fleet.ElectricFleetSpecification; +import org.matsim.contrib.ev.fleet.ElectricFleetUtils; +import org.matsim.contrib.ev.fleet.ElectricVehicle; +import org.matsim.contrib.ev.fleet.ElectricVehicleSpecification; import org.matsim.contrib.ev.infrastructure.ChargerSpecification; import org.matsim.contrib.ev.infrastructure.ChargingInfrastructureSpecification; -import org.matsim.contrib.common.util.StraightLineKnnFinder; import org.matsim.core.gbl.Gbl; import org.matsim.core.gbl.MatsimRandom; import org.matsim.core.network.NetworkUtils; @@ -54,6 +51,8 @@ import org.matsim.facilities.Facility; import org.matsim.vehicles.Vehicle; +import java.util.*; + /** * This network Routing module adds stages for re-charging into the Route. * This wraps a "computer science" {@link LeastCostPathCalculator}, which routes from a node to another node, into something that @@ -156,8 +155,10 @@ public List calcRoute(RoutingRequest request) { stagedRoute.add(lastLeg); Activity chargeAct = PopulationUtils.createStageActivityFromCoordLinkIdAndModePrefix(selectedChargerLink.getCoord(), selectedChargerLink.getId(), stageActivityModePrefix); - chargeAct = PopulationUtils.createActivity(chargeAct); // createStageActivity... creates a InteractionActivity where duration cannot be set. - double maxPowerEstimate = Math.min(selectedCharger.getPlugPower(), ev.getBatteryCapacity() / 3.6); + // createStageActivity... creates a InteractionActivity where duration cannot be set. + chargeAct = PopulationUtils.createActivity(chargeAct); + // assume that the battery is compatible with a power that allows for full charge within one hour (cf. FixedSpeedCharging) + double maxPowerEstimate = Math.min(selectedCharger.getPlugPower(), ev.getBatteryCapacity() / 3600); double estimatedChargingTime = (ev.getBatteryCapacity() * 1.5) / maxPowerEstimate; chargeAct.setMaximumDuration(Math.max(evConfigGroup.minimumChargeTime, estimatedChargingTime)); lastArrivaltime += chargeAct.getMaximumDuration().seconds(); diff --git a/contribs/ev/test/input/org/matsim/contrib/ev/example/RunEvExampleTest/runTest/output_events.xml.gz b/contribs/ev/test/input/org/matsim/contrib/ev/example/RunEvExampleTest/runTest/output_events.xml.gz index 199f077998c..4e509e420bb 100644 Binary files a/contribs/ev/test/input/org/matsim/contrib/ev/example/RunEvExampleTest/runTest/output_events.xml.gz and b/contribs/ev/test/input/org/matsim/contrib/ev/example/RunEvExampleTest/runTest/output_events.xml.gz differ diff --git a/contribs/ev/test/input/org/matsim/contrib/ev/example/RunEvExampleTest/runTest/output_plans.xml.gz b/contribs/ev/test/input/org/matsim/contrib/ev/example/RunEvExampleTest/runTest/output_plans.xml.gz index bab27d58b0d..795d36b6ad6 100644 Binary files a/contribs/ev/test/input/org/matsim/contrib/ev/example/RunEvExampleTest/runTest/output_plans.xml.gz and b/contribs/ev/test/input/org/matsim/contrib/ev/example/RunEvExampleTest/runTest/output_plans.xml.gz differ diff --git a/contribs/ev/test/input/org/matsim/contrib/ev/example/RunEvExampleWithLTHConsumptionModelTest/runTest/output_events.xml.gz b/contribs/ev/test/input/org/matsim/contrib/ev/example/RunEvExampleWithLTHConsumptionModelTest/runTest/output_events.xml.gz index 67efbfe39b3..a389b55a8e8 100644 Binary files a/contribs/ev/test/input/org/matsim/contrib/ev/example/RunEvExampleWithLTHConsumptionModelTest/runTest/output_events.xml.gz and b/contribs/ev/test/input/org/matsim/contrib/ev/example/RunEvExampleWithLTHConsumptionModelTest/runTest/output_events.xml.gz differ diff --git a/contribs/ev/test/input/org/matsim/contrib/ev/example/RunEvExampleWithLTHConsumptionModelTest/runTest/output_plans.xml.gz b/contribs/ev/test/input/org/matsim/contrib/ev/example/RunEvExampleWithLTHConsumptionModelTest/runTest/output_plans.xml.gz index bd2ab60d503..b03c4e3abc7 100644 Binary files a/contribs/ev/test/input/org/matsim/contrib/ev/example/RunEvExampleWithLTHConsumptionModelTest/runTest/output_plans.xml.gz and b/contribs/ev/test/input/org/matsim/contrib/ev/example/RunEvExampleWithLTHConsumptionModelTest/runTest/output_plans.xml.gz differ diff --git a/contribs/hybridsim/pom.xml b/contribs/hybridsim/pom.xml index 6755af32f20..52fb9013346 100644 --- a/contribs/hybridsim/pom.xml +++ b/contribs/hybridsim/pom.xml @@ -10,7 +10,7 @@ hybridsim - 3.25.0 + 3.25.1 1.59.0 diff --git a/contribs/otfvis/pom.xml b/contribs/otfvis/pom.xml index 46ee14b7dec..100be456b06 100644 --- a/contribs/otfvis/pom.xml +++ b/contribs/otfvis/pom.xml @@ -14,7 +14,10 @@ **/*$* org/matsim/vis/otfvis/checklists/**/*.java - once + + + + -Xmx700m -Djava.awt.headless=true false + + + + + + + + - + - + + + + + jogl + https://jogamp.org/deployment/maven/ + + + net.java.dev.timingframework timingframework 1.0 + + + org.jogamp.gluegen - gluegen-rt - ${jogl.version} - - - org.jogamp.gluegen - gluegen-rt - ${jogl.version} - natives-macosx-universal - - - org.jogamp.gluegen - gluegen-rt - ${jogl.version} - natives-linux-i586 - - - org.jogamp.gluegen - gluegen-rt - ${jogl.version} - natives-windows-i586 - - - org.jogamp.gluegen - gluegen-rt - ${jogl.version} - natives-windows-amd64 - - - org.jogamp.gluegen - gluegen-rt + gluegen-rt-main ${jogl.version} - natives-linux-amd64 - - - org.jogamp.jogl - jogl-all - ${jogl.version} - - - org.jogamp.jogl - jogl-all - ${jogl.version} - natives-macosx-universal - - - org.jogamp.jogl - jogl-all - ${jogl.version} - natives-linux-i586 - - - org.jogamp.jogl - jogl-all - ${jogl.version} - natives-windows-i586 - - - org.jogamp.jogl - jogl-all - ${jogl.version} - natives-windows-amd64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.jogamp.jogl - jogl-all + jogl-all-main ${jogl.version} - natives-linux-amd64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.jxmapviewer jxmapviewer2 2.6 - - org.matsim - matsim - 16.0-SNAPSHOT - diff --git a/contribs/otfvis/src/main/java/org/matsim/vis/otfvis/gui/OTFVisFrame.java b/contribs/otfvis/src/main/java/org/matsim/vis/otfvis/gui/OTFVisFrame.java index 4dd32899e7b..dee0b599d44 100644 --- a/contribs/otfvis/src/main/java/org/matsim/vis/otfvis/gui/OTFVisFrame.java +++ b/contribs/otfvis/src/main/java/org/matsim/vis/otfvis/gui/OTFVisFrame.java @@ -115,6 +115,15 @@ public OTFVisFrame(Component canvas, OTFServer server, OTFControlBar controlBar, buildMenu(saver); log.info("created HostControlBar"); log.info("created drawer"); + + log.warn("under win, the following tends to fail with com.jogamp.opengl.GLException: "); + log.warn("Unable to determine GraphicsConfiguration: WindowsWGLGraphicsConfiguration ..."); + log.warn("It then seems to help to add the following to the Java VM options: "); + log.warn("--add-exports java.base/java.lang=ALL-UNNAMED"); + log.warn("--add-exports java.desktop/sun.awt=ALL-UNNAMED"); + log.warn("--add-exports java.desktop/sun.java2d=ALL-UNNAMED"); + log.warn("See https://github.com/matsim-org/matsim-libs/pull/2940 and/or https://jogamp.org/bugzilla/show_bug.cgi?id=1317#c21 ."); + getContentPane().add(compositePanel, BorderLayout.CENTER); } diff --git a/contribs/otfvis/src/test/java/org/matsim/vis/otfvis/checklists/T0_RunWithQSim.java b/contribs/otfvis/src/test/java/org/matsim/vis/otfvis/checklists/T0_RunWithQSim.java index 87bcf20735e..661560a131e 100644 --- a/contribs/otfvis/src/test/java/org/matsim/vis/otfvis/checklists/T0_RunWithQSim.java +++ b/contribs/otfvis/src/test/java/org/matsim/vis/otfvis/checklists/T0_RunWithQSim.java @@ -28,7 +28,7 @@ public class T0_RunWithQSim { public static void main(String[] args) { - OTFVis.playConfig("test/scenarios/equil/config-qsim.xml"); + OTFVis.playConfig("contribs/otfvis/test/scenarios/equil/config-qsim.xml"); } } diff --git a/contribs/protobuf/pom.xml b/contribs/protobuf/pom.xml index bf18c765bd6..8256218bb80 100644 --- a/contribs/protobuf/pom.xml +++ b/contribs/protobuf/pom.xml @@ -11,7 +11,7 @@ protobuf - 3.25.0 + 3.25.1 diff --git a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/scheduler/TaxiScheduler.java b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/scheduler/TaxiScheduler.java index 320420d0c10..eb9df3f0bd1 100644 --- a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/scheduler/TaxiScheduler.java +++ b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/scheduler/TaxiScheduler.java @@ -142,7 +142,7 @@ public void scheduleRequest(DvrpVehicle vehicle, DrtRequest request, VrpPathWith eventsManager.processEvent( new PassengerRequestScheduledEvent(mobsimTimer.getTimeOfDay(), request.getMode(), request.getId(), - request.getPassengerId(), vehicle.getId(), pickupEndTime, dropoffStartTime)); + request.getPassengerIds(), vehicle.getId(), pickupEndTime, dropoffStartTime)); } protected void divertOrAppendDrive(Schedule schedule, VrpPathWithTravelData vrpPath, TaxiTaskType taskType) { diff --git a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/util/TaxiSimulationConsistencyChecker.java b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/util/TaxiSimulationConsistencyChecker.java index dfc34ad205b..d9bf48770c2 100644 --- a/contribs/taxi/src/main/java/org/matsim/contrib/taxi/util/TaxiSimulationConsistencyChecker.java +++ b/contribs/taxi/src/main/java/org/matsim/contrib/taxi/util/TaxiSimulationConsistencyChecker.java @@ -49,7 +49,7 @@ public void addCheckAllRequestsPerformed() { LogManager.getLogger(getClass()) .warn("Taxi request not performed. Request time:\t" + Time.writeTime( seq.getSubmitted().getTime()) + "\tPassenger:\t" + seq.getSubmitted() - .getPersonId()); + .getPersonIds()); } } } diff --git a/contribs/vsp/pom.xml b/contribs/vsp/pom.xml index 24b6b3e7b77..9165add2d65 100644 --- a/contribs/vsp/pom.xml +++ b/contribs/vsp/pom.xml @@ -11,6 +11,13 @@ vsp vsp + + + jogl + https://jogamp.org/deployment/maven/ + + + org.matsim.contrib @@ -74,16 +81,19 @@ - - org.jogamp.gluegen - gluegen-rt-main - 2.3.2 - - - org.jogamp.jogl - jogl-all-main - 2.3.2 - + + + + + + + + + + + + + org.matsim.contrib otfvis diff --git a/matsim/src/main/java/org/matsim/core/controler/corelisteners/PlansReplanningImpl.java b/matsim/src/main/java/org/matsim/core/controler/corelisteners/PlansReplanningImpl.java index 7dd5d99e824..733087d70df 100644 --- a/matsim/src/main/java/org/matsim/core/controler/corelisteners/PlansReplanningImpl.java +++ b/matsim/src/main/java/org/matsim/core/controler/corelisteners/PlansReplanningImpl.java @@ -24,7 +24,9 @@ import org.matsim.core.controler.events.ReplanningEvent; import org.matsim.core.controler.listener.ReplanningListener; import org.matsim.core.replanning.ReplanningContext; +import org.matsim.core.replanning.ReplanningUtils; import org.matsim.core.replanning.StrategyManager; +import org.matsim.core.replanning.conflicts.ConflictManager; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -41,21 +43,24 @@ */ @Singleton final class PlansReplanningImpl implements PlansReplanning, ReplanningListener { - private final Provider replanningContextProvider; private final Population population; private final StrategyManager strategyManager; + private final ConflictManager conflictManager; @Inject - PlansReplanningImpl(StrategyManager strategyManager, Population pop, Provider replanningContextProvider) { + PlansReplanningImpl(StrategyManager strategyManager, ConflictManager conflictManager, Population pop, + Provider replanningContextProvider) { this.population = pop; this.strategyManager = strategyManager; + this.conflictManager = conflictManager; this.replanningContextProvider = replanningContextProvider; } @Override public void notifyReplanning(final ReplanningEvent event) { + conflictManager.initializeReplanning(population); strategyManager.run(population, event.getIteration(), replanningContextProvider.get()); + conflictManager.run(population, event.getIteration()); } - } diff --git a/matsim/src/main/java/org/matsim/core/population/io/ParallelPopulationReaderMatsimV4.java b/matsim/src/main/java/org/matsim/core/population/io/ParallelPopulationReaderMatsimV4.java index 976d040df4a..1ffbe805141 100644 --- a/matsim/src/main/java/org/matsim/core/population/io/ParallelPopulationReaderMatsimV4.java +++ b/matsim/src/main/java/org/matsim/core/population/io/ParallelPopulationReaderMatsimV4.java @@ -51,13 +51,13 @@ * the file and creates empty person objects which are added to the population to ensure * that their order is not changed. Note that this approach is not compatible with * population streaming. When this feature is activated, the non-parallel reader is used. - * + * * The parallel threads interpret the xml data for each person. - * + * * @author cdobler */ /* deliberately package */ class ParallelPopulationReaderMatsimV4 extends PopulationReaderMatsimV4 { - + static final Logger log = LogManager.getLogger(ParallelPopulationReaderMatsimV4.class); private final boolean isPopulationStreaming; @@ -70,6 +70,7 @@ private List currentPersonXmlData; private final CoordinateTransformation coordinateTransformation; + private Throwable exception = null; public ParallelPopulationReaderMatsimV4( final Scenario scenario ) { @@ -81,14 +82,14 @@ public ParallelPopulationReaderMatsimV4( final Scenario scenario) { super( coordinateTransformation , scenario ); this.coordinateTransformation = coordinateTransformation; - + /* * Check whether population streaming is activated */ // if (scenario.getPopulation() instanceof Population && ((Population)scenario.getPopulation()).isStreaming()) { if ( scenario.getPopulation() instanceof StreamingPopulationReader.StreamingPopulation ) { log.warn("Population streaming is activated - cannot use " + ParallelPopulationReaderMatsimV4.class.getName() + "!"); - + this.isPopulationStreaming = true; this.numThreads = 1; this.queue = null; @@ -98,42 +99,70 @@ public ParallelPopulationReaderMatsimV4( isPopulationStreaming = false; if (scenario.getConfig().global().getNumberOfThreads() > 0) { - this.numThreads = scenario.getConfig().global().getNumberOfThreads(); + this.numThreads = scenario.getConfig().global().getNumberOfThreads(); } else this.numThreads = 1; - + this.queue = new LinkedBlockingQueue<>(); this.collectorPopulation = new CollectorPopulation(this.plans); this.collectorScenario = new CollectorScenario(scenario, collectorPopulation); } } - + private void initThreads() { threads = new Thread[numThreads]; for (int i = 0; i < numThreads; i++) { - + ParallelPopulationReaderMatsimV4Runner runner = new ParallelPopulationReaderMatsimV4Runner( this.coordinateTransformation, this.collectorScenario, this.queue); - + Thread thread = new Thread(runner); thread.setDaemon(true); thread.setName(ParallelPopulationReaderMatsimV4Runner.class.toString() + i); + thread.setUncaughtExceptionHandler(this::catchReaderException); threads[i] = thread; thread.start(); } } - + + private void stopThreads() { + // signal the threads that they should end parsing + for (int i = 0; i < this.numThreads; i++) { + List list = new ArrayList<>(); + list.add(new EndProcessingTag()); + this.queue.add(list); + } + + // wait for the threads to finish + try { + for (Thread thread : threads) { + thread.join(); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + if (this.exception != null) { + throw new RuntimeException(this.exception); + } + } + + private void catchReaderException(Thread thread, Throwable throwable) { + log.error("Error parsing XML", throwable); + this.exception = throwable; + } + @Override public void startTag(String name, Attributes atts, Stack context) { - + // if population streaming is activated, use non-parallel reader if (isPopulationStreaming) { super.startTag(name, atts, context); return; } - + if (PLANS.equals(name)) { log.info("Start parallel population reading..."); initThreads(); @@ -149,7 +178,7 @@ public void startTag(String name, Attributes atts, Stack context) { currentPersonXmlData.add(personTag); this.plans.addPerson(person); } - + // Create a new start tag and add it to the person data. StartTag tag = new StartTag(); tag.name = name; @@ -160,30 +189,15 @@ public void startTag(String name, Attributes atts, Stack context) { @Override public void endTag(String name, String content, Stack context) { - + // if population streaming is activated, use non-parallel reader if (isPopulationStreaming) { super.endTag(name, content, context); return; } - + if (PLANS.equals(name)) { - // signal the threads that they should end parsing - for (int i = 0; i < this.numThreads; i++) { - List list = new ArrayList<>(); - list.add(new EndProcessingTag()); - this.queue.add(list); - } - - // wait for the threads to finish - try { - for (Thread thread : threads) { - thread.join(); - } - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - + this.stopThreads(); super.endTag(name, content, context); log.info("Finished parallel population reading..."); } else { @@ -193,24 +207,24 @@ public void endTag(String name, String content, Stack context) { tag.content = content; tag.context = context; currentPersonXmlData.add(tag); - + // if its a person end tag, add the persons xml data to the queue. if (PERSON.equals(name)) queue.add(currentPersonXmlData); } } - + private static class CollectorScenario implements Scenario { - // yyyy Why is this necessary at all? Could you please explain your design decisions? The same instance is passed to all threads, so + // yyyy Why is this necessary at all? Could you please explain your design decisions? The same instance is passed to all threads, so // what is the difference to using the underlying population directly? - + private final Scenario delegate; private final CollectorPopulation population; - + public CollectorScenario(Scenario scenario, CollectorPopulation population) { this.delegate = scenario; this.population = population; } - + @Override public Network getNetwork() { return this.delegate.getNetwork(); @@ -220,7 +234,7 @@ public Network getNetwork() { public Population getPopulation() { return this.population; // return collector population } - + @Override public ActivityFacilities getActivityFacilities() { return this.delegate.getActivityFacilities(); @@ -266,15 +280,15 @@ public Vehicles getVehicles() { return this.delegate.getVehicles() ; } } - + private static class CollectorPopulation implements Population { private final Population population; - + public CollectorPopulation(Population population) { this.population = population; } - + @Override public PopulationFactory getFactory() { return population.getFactory(); @@ -310,24 +324,24 @@ public org.matsim.utils.objectattributes.attributable.Attributes getAttributes() throw new RuntimeException("Calls to this method are not expected to happen..."); } } - + public abstract static class Tag { String name; Stack context = null; // not used by the PopulationReader } - + public final class StartTag extends Tag { Attributes atts; } - + public final class PersonTag extends Tag { Person person; } - + public final class EndTag extends Tag { String content; } - + /* * Marker Tag to inform the threads that no further data has to be parsed. */ diff --git a/matsim/src/main/java/org/matsim/core/population/io/ParallelPopulationReaderMatsimV6.java b/matsim/src/main/java/org/matsim/core/population/io/ParallelPopulationReaderMatsimV6.java index fb0727493b8..648d6537d10 100644 --- a/matsim/src/main/java/org/matsim/core/population/io/ParallelPopulationReaderMatsimV6.java +++ b/matsim/src/main/java/org/matsim/core/population/io/ParallelPopulationReaderMatsimV6.java @@ -64,6 +64,7 @@ private final BlockingQueue> personInsertionQueue = new LinkedBlockingQueue<>(); private Thread personInsertionThread; + private Throwable exception = null; public ParallelPopulationReaderMatsimV6( final String inputCRS, @@ -108,6 +109,7 @@ private void initThreads() { Thread thread = new Thread(runner); thread.setDaemon(true); thread.setName(ParallelPopulationReaderMatsimV6Runner.class.toString() + i); + thread.setUncaughtExceptionHandler(this::catchReaderException); threads[i] = thread; thread.start(); } @@ -118,6 +120,44 @@ private void initThreads() { } } + private void stopThreads() { + // signal the threads that they should end parsing + for (int i = 0; i < this.numThreads; i++) { + this.tagQueue.add(List.of(new EndProcessingTag())); + } + + if (isPopulationStreaming) { + CompletableFuture finishPerson = new CompletableFuture<>(); + finishPerson.complete(null); + try { + this.personInsertionQueue.put(finishPerson); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + // wait for the threads to finish + try { + for (Thread thread : threads) { + thread.join(); + } + if(this.isPopulationStreaming) { + this.personInsertionThread.join(); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + if (this.exception != null) { + throw new RuntimeException(this.exception); + } + } + + private void catchReaderException(Thread thread, Throwable throwable) { + log.error("Error parsing XML", throwable); + this.exception = throwable; + } + @Override public void startTag(String name, Attributes atts, Stack context) { //Reached first time a person @@ -139,6 +179,11 @@ public void startTag(String name, Attributes atts, Stack context) { // If it is a new person, create a new person and a list for its attributes. if (PERSON.equals(name)) { + if (this.exception != null) { + this.stopThreads(); + throw new RuntimeException(this.exception); + } + // Just create a person, but do not add it here! Person person = this.plans.getFactory().createPerson(Id.create(atts.getValue(ATTR_PERSON_ID), Person.class)); currentPersonXmlData = new ArrayList<>(); @@ -187,34 +232,7 @@ public void endTag(String name, String content, Stack context) { // End of population reached if (POPULATION.equals(name)) { - // signal the threads that they should end parsing - for (int i = 0; i < this.numThreads; i++) { - this.tagQueue.add(List.of(new EndProcessingTag())); - } - if(isPopulationStreaming) - { - CompletableFuture finishPerson = new CompletableFuture<>(); - finishPerson.complete(null); - try { - this.personInsertionQueue.put(finishPerson); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - - // wait for the threads to finish - try { - for (Thread thread : threads) { - thread.join(); - } - if(this.isPopulationStreaming) - { - this.personInsertionThread.join(); - } - - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + this.stopThreads(); super.endTag(name, content, context); log.info("Finished parallel population reading..."); diff --git a/matsim/src/main/java/org/matsim/core/replanning/ReplanningUtils.java b/matsim/src/main/java/org/matsim/core/replanning/ReplanningUtils.java index 100345bded7..426c3c6309d 100644 --- a/matsim/src/main/java/org/matsim/core/replanning/ReplanningUtils.java +++ b/matsim/src/main/java/org/matsim/core/replanning/ReplanningUtils.java @@ -22,13 +22,40 @@ package org.matsim.core.replanning; +import javax.annotation.Nullable; + import org.matsim.api.core.v01.population.BasicPlan; +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.Plan; public final class ReplanningUtils { + static public final String INITIAl_PLAN_ATTRIBUTE = "isInitialPlan"; + + public static boolean isInitialPlan(Plan plan) { + Boolean isInitialPlan = (Boolean) plan.getAttributes().getAttribute(INITIAl_PLAN_ATTRIBUTE); + return isInitialPlan != null && isInitialPlan; + } + + @Nullable + public static Plan getInitialPlan(Person person) { + for (Plan plan : person.getPlans()) { + if (isInitialPlan(plan)) { + return plan; + } + } + + return null; + } + + public static void setInitialPlan(Person person) { + person.getPlans().forEach(plan -> plan.getAttributes().removeAttribute(INITIAl_PLAN_ATTRIBUTE)); + person.getSelectedPlan().getAttributes().putAttribute(INITIAl_PLAN_ATTRIBUTE, true); + } + /** * Return whether a strategy is innovative, i.e. is producing new plans. - * */ + */ public static

boolean isInnovativeStrategy(GenericPlanStrategy planStrategy) { return !isOnlySelector(planStrategy); } diff --git a/matsim/src/main/java/org/matsim/core/replanning/StrategyManagerModule.java b/matsim/src/main/java/org/matsim/core/replanning/StrategyManagerModule.java index d42fde9fabe..ef2daff09cc 100644 --- a/matsim/src/main/java/org/matsim/core/replanning/StrategyManagerModule.java +++ b/matsim/src/main/java/org/matsim/core/replanning/StrategyManagerModule.java @@ -35,6 +35,7 @@ import org.matsim.core.controler.OutputDirectoryHierarchy; import org.matsim.core.replanning.choosers.StrategyChooser; import org.matsim.core.replanning.choosers.WeightedStrategyChooser; +import org.matsim.core.replanning.conflicts.ConflictModule; import org.matsim.core.replanning.modules.ExternalModule; import org.matsim.core.replanning.selectors.RandomPlanSelector; import org.matsim.core.replanning.strategies.DefaultPlanStrategiesModule; @@ -92,6 +93,8 @@ public void install() { // (settings is the key ... ok. The Key.get(...) returns the PlanStrategy that was registered under its name at (*) above.) } } + + install(new ConflictModule()); } /** diff --git a/matsim/src/main/java/org/matsim/core/replanning/conflicts/ConflictManager.java b/matsim/src/main/java/org/matsim/core/replanning/conflicts/ConflictManager.java new file mode 100644 index 00000000000..16a3bcc02d2 --- /dev/null +++ b/matsim/src/main/java/org/matsim/core/replanning/conflicts/ConflictManager.java @@ -0,0 +1,117 @@ +package org.matsim.core.replanning.conflicts; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; + +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.IdSet; +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.core.replanning.ReplanningUtils; + +import com.google.common.base.Preconditions; + +/** + * This class handles conflicts during replanning. ConflictResolvers are used to + * identify agents whose plans are conflicting with others and need to be + * "rejected" in order to resolve these conflicts. "Rejecting" means to switch + * those agents back to a plan in their memory that does not cause any + * conflicts. Those are plans that are not "potentially conflicting", i.e., + * could interfere in any way with another agent. Those are usually plans that + * don't contain a certain restricted/limited/capacitated mode or resource. The + * logic of conflicts is defined using the ConflictResolver interface. + */ +public class ConflictManager { + private final static Logger logger = LogManager.getLogger(ConflictManager.class); + + private final Set resolvers; + private final ConflictWriter writer; + private final Random random; + + public ConflictManager(Set resolvers, ConflictWriter writer, Random random) { + this.resolvers = resolvers; + this.random = random; + this.writer = writer; + } + + public void initializeReplanning(Population population) { + if (resolvers.size() > 0) { // only require if active + population.getPersons().values().forEach(ReplanningUtils::setInitialPlan); + } + } + + public void run(Population population, int iteration) { + if (resolvers.size() == 0) { + return; + } + + logger.info("Resolving conflicts ..."); + + Map conflictCounts = new HashMap<>(); + IdSet conflictingIds = new IdSet<>(Person.class); + + for (ConflictResolver resolver : resolvers) { + IdSet resolverConflictingIds = resolver.resolve(population, iteration); + conflictCounts.put(resolver.getName(), resolverConflictingIds.size()); + conflictingIds.addAll(resolverConflictingIds); + } + + logger.info(" Conflicts: " + conflictCounts.entrySet().stream() + .map(entry -> String.format("%s=%d", entry.getKey(), entry.getValue())) + .collect(Collectors.joining(", "))); + + int switchedToInitialCount = 0; + int switchedToRandomCount = 0; + + for (Id personId : conflictingIds) { + Person person = population.getPersons().get(personId); + + // If the initial plan is non-conflicting, switch back to it + Plan initialPlan = ReplanningUtils.getInitialPlan(person); + + if (initialPlan != null && !isPotentiallyConflicting(initialPlan)) { + person.setSelectedPlan(initialPlan); + switchedToInitialCount++; + } else { + // Select a random non-conflicting plan + List candidates = person.getPlans().stream().filter(p -> !isPotentiallyConflicting(p)) + .collect(Collectors.toList()); + Preconditions.checkState(candidates.size() > 0, + String.format("Agent %s has no non-conflicting plan", personId)); + + // Shuffle, and select the first + Collections.shuffle(candidates, random); + person.setSelectedPlan(candidates.get(0)); + + switchedToRandomCount++; + } + } + + logger.info(String.format(" %d (%.2f%%) switched to initial", switchedToInitialCount, + (double) switchedToInitialCount / population.getPersons().size())); + logger.info(String.format(" %d (%.2f%%) switched to random", switchedToRandomCount, + (double) switchedToRandomCount / population.getPersons().size())); + + writer.write(iteration, switchedToInitialCount, switchedToRandomCount, conflictCounts); + + logger.info(" Done resolving conflicts!"); + } + + public boolean isPotentiallyConflicting(Plan plan) { + for (ConflictResolver resolver : resolvers) { + if (resolver.isPotentiallyConflicting(plan)) { + return true; + } + } + + return false; + } +} diff --git a/matsim/src/main/java/org/matsim/core/replanning/conflicts/ConflictModule.java b/matsim/src/main/java/org/matsim/core/replanning/conflicts/ConflictModule.java new file mode 100644 index 00000000000..4795c0568b5 --- /dev/null +++ b/matsim/src/main/java/org/matsim/core/replanning/conflicts/ConflictModule.java @@ -0,0 +1,72 @@ +package org.matsim.core.replanning.conflicts; + +import java.io.File; +import java.util.Random; +import java.util.Set; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.core.controler.AbstractModule; +import org.matsim.core.controler.OutputDirectoryHierarchy; +import org.matsim.core.gbl.MatsimRandom; + +import com.google.inject.Binder; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.binder.LinkedBindingBuilder; +import com.google.inject.multibindings.Multibinder; + +/** + * Prepares injection of the conflict resolution logic during replanning + * + * @author Sebastian Hörl (sebhoerl), IRT SystemX + */ +public class ConflictModule extends AbstractModule { + private final static Logger logger = LogManager.getLogger(ConflictModule.class); + + private final static String OUTPUT_FILE = "conflicts.csv"; + + @Override + public void install() { + // initialize the builder + getMultibinder(binder()); + } + + @Provides + @Singleton + ConflictWriter provideConflictWriter(OutputDirectoryHierarchy outputDirectoryHierarchy) { + File outputPath = new File(outputDirectoryHierarchy.getOutputFilename(OUTPUT_FILE)); + return new ConflictWriter(outputPath); + } + + @Provides + @Singleton + ConflictManager provideConflictManager(Set resolvers, ConflictWriter writer) { + if (!getConfig().replanning().getPlanSelectorForRemoval() + .equals(WorstPlanForRemovalSelectorWithConflicts.SELECTOR_NAME)) { + logger.warn("The replanning.planSelectorForRemoval is not set to " + + WorstPlanForRemovalSelectorWithConflicts.SELECTOR_NAME + + ". This will likely cause problems with the conflict logic if you are not sure what you are doing."); + } + + Random random = MatsimRandom.getRandom(); // no need for local instance, not parallel! + return new ConflictManager(resolvers, writer, random); + } + + static Multibinder getMultibinder(Binder binder) { + return Multibinder.newSetBinder(binder, ConflictResolver.class); + } + + /** + * Allows to bind a conflict resolver in an AbstractModule, for instance: + * + * + * ConflictModule.bindResolver(binder()).toInstance(new ConflictResolver() { + * // ... + * }); + * + */ + static public LinkedBindingBuilder bindResolver(Binder binder) { + return getMultibinder(binder).addBinding(); + } +} diff --git a/matsim/src/main/java/org/matsim/core/replanning/conflicts/ConflictResolver.java b/matsim/src/main/java/org/matsim/core/replanning/conflicts/ConflictResolver.java new file mode 100644 index 00000000000..9595d073e7b --- /dev/null +++ b/matsim/src/main/java/org/matsim/core/replanning/conflicts/ConflictResolver.java @@ -0,0 +1,23 @@ +package org.matsim.core.replanning.conflicts; + +import org.matsim.api.core.v01.IdSet; +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.Plan; +import org.matsim.api.core.v01.population.Population; + +/** + * This interface is called after standard replanning. Its purpose is to check + * the population and detect any conflicts between agents. The interface must + * then return a list of agents that should be reset to a non-conflicting plan + * in order to resolve all conflicts. Plans that are not conflicting are + * identified as such using the isPotentiallyConflicting method. + * + * @author Sebastian Hörl (sebhoerl), IRT SystemX + */ +public interface ConflictResolver { + IdSet resolve(Population population, int iteration); + + boolean isPotentiallyConflicting(Plan plan); + + String getName(); +} diff --git a/matsim/src/main/java/org/matsim/core/replanning/conflicts/ConflictWriter.java b/matsim/src/main/java/org/matsim/core/replanning/conflicts/ConflictWriter.java new file mode 100644 index 00000000000..77604d2b870 --- /dev/null +++ b/matsim/src/main/java/org/matsim/core/replanning/conflicts/ConflictWriter.java @@ -0,0 +1,61 @@ +package org.matsim.core.replanning.conflicts; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.matsim.core.utils.io.IOUtils; + +/** + * Writes high-level statistics on the conflict resolution process per iteration + * + * @author Sebastian Hörl (sebhoerl), IRT SystemX + */ +public class ConflictWriter { + private final File outputPath; + + public ConflictWriter(File outputPath) { + this.outputPath = outputPath; + } + + public void write(int iteration, int rejectedToInitial, int rejectedToRandom, Map conflictCounts) { + boolean writeHeader = !outputPath.exists(); + + List resolvers = new ArrayList<>(conflictCounts.keySet()); + Collections.sort(resolvers); + + try { + BufferedWriter writer = IOUtils.getAppendingBufferedWriter(outputPath.getPath()); + + if (writeHeader) { + List header = new ArrayList<>( + Arrays.asList("iteration", "rejected_total", "switched_to_initial", "switched_to_random")); + resolvers.stream().map(r -> "resolver:" + r).forEach(header::add); + + writer.write(String.join(";", header) + "\n"); + } + + List row = new ArrayList<>(Arrays.asList(String.valueOf(iteration), // + String.valueOf(rejectedToInitial + rejectedToRandom), // + String.valueOf(rejectedToInitial), // + String.valueOf(rejectedToRandom) // + )); + + for (String resolver : resolvers) { + row.add(String.valueOf(conflictCounts.get(resolver))); + } + + writer.write(String.join(";", row) + "\n"); + + writer.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/matsim/src/main/java/org/matsim/core/replanning/conflicts/WorstPlanForRemovalSelectorWithConflicts.java b/matsim/src/main/java/org/matsim/core/replanning/conflicts/WorstPlanForRemovalSelectorWithConflicts.java new file mode 100644 index 00000000000..22e4947e9d2 --- /dev/null +++ b/matsim/src/main/java/org/matsim/core/replanning/conflicts/WorstPlanForRemovalSelectorWithConflicts.java @@ -0,0 +1,90 @@ +/* *********************************************************************** * + * project: org.matsim.* + * WorstPlanSelector.java + * * + * *********************************************************************** * + * * + * copyright : (C) 2009 by the members listed in the COPYING, * + * LICENSE and WARRANTY file. * + * email : info at matsim dot org * + * * + * *********************************************************************** * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * See also COPYING, LICENSE and WARRANTY file * + * * + * *********************************************************************** */ + +package org.matsim.core.replanning.conflicts; + +import java.util.Collections; +import java.util.LinkedList; + +import org.apache.commons.lang3.tuple.Pair; +import org.jboss.logging.Logger; +import org.matsim.api.core.v01.population.HasPlansAndId; +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.Plan; +import org.matsim.core.replanning.selectors.PlanSelector; + +import com.google.inject.Inject; + +/** + * This selector is used like the standard WorstPlanForRemovalSelector to reduce + * plans from an agent's memory. However, adhering to the conflict resolution + * logic, the selector will make sure that a non-conflicting plan is never + * selected for removal if it is the last remaining non-conflicting one. This + * way, we make sure that every agent always keeps one plan that is not + * conflicting. + * + * @author Sebastian Hörl (sebhoerl), IRT SystemX + */ +public class WorstPlanForRemovalSelectorWithConflicts implements PlanSelector { + public static final String SELECTOR_NAME = "WorstPlanForRemovalSelectorWithConflicts"; + + private final Logger logger = Logger.getLogger(WorstPlanForRemovalSelectorWithConflicts.class); + + private final ConflictManager conflictManager; + + @Inject + public WorstPlanForRemovalSelectorWithConflicts(ConflictManager conflictManager) { + this.conflictManager = conflictManager; + } + + @Override + public Plan selectPlan(HasPlansAndId person) { + LinkedList> sorter = new LinkedList<>(); + int nonConflictingCount = 0; + + for (Plan plan : person.getPlans()) { + double score = plan.getScore() == null ? Double.NEGATIVE_INFINITY : plan.getScore(); + sorter.add(Pair.of(plan, score)); + + if (!conflictManager.isPotentiallyConflicting(plan)) { + nonConflictingCount++; + } + } + + if (nonConflictingCount == 0) { + logger.error(String.format("No non-conflicting plan found for agent %s", person.getId())); + } + + Collections.sort(sorter, (a, b) -> Double.compare(a.getRight(), b.getRight())); + + if (nonConflictingCount == 1 && !conflictManager.isPotentiallyConflicting(sorter.getFirst().getLeft())) { + // Remove the first from the removable candidates if it is the only + // non-conflicting one + sorter.removeFirst(); + } + + if (sorter.size() > 0) { + return sorter.getFirst().getLeft(); + } + + return null; + } + +} diff --git a/matsim/src/main/java/org/matsim/core/replanning/strategies/DefaultPlanStrategiesModule.java b/matsim/src/main/java/org/matsim/core/replanning/strategies/DefaultPlanStrategiesModule.java index 18ff8e31436..a0dd6d9c423 100644 --- a/matsim/src/main/java/org/matsim/core/replanning/strategies/DefaultPlanStrategiesModule.java +++ b/matsim/src/main/java/org/matsim/core/replanning/strategies/DefaultPlanStrategiesModule.java @@ -37,6 +37,7 @@ import org.matsim.core.controler.AbstractModule; import org.matsim.core.population.algorithms.PermissibleModesCalculator; import org.matsim.core.population.algorithms.PermissibleModesCalculatorImpl; +import org.matsim.core.replanning.conflicts.WorstPlanForRemovalSelectorWithConflicts; import org.matsim.core.replanning.selectors.ExpBetaPlanChanger; import org.matsim.core.replanning.selectors.ExpBetaPlanSelector; import org.matsim.core.replanning.selectors.PathSizeLogitSelector; @@ -54,6 +55,9 @@ public void install() { if (getConfig().replanning().getPlanSelectorForRemoval().equals(DefaultPlansRemover.WorstPlanSelector.toString())) { bindPlanSelectorForRemoval().to(WorstPlanForRemovalSelector.class); } + if (getConfig().replanning().getPlanSelectorForRemoval().equals(WorstPlanForRemovalSelectorWithConflicts.SELECTOR_NAME)) { + bindPlanSelectorForRemoval().to(WorstPlanForRemovalSelectorWithConflicts.class); + } if (getConfig().replanning().getPlanSelectorForRemoval().equals(DefaultPlansRemover.SelectRandom.toString())) { bindPlanSelectorForRemoval().to(new TypeLiteral>(){}); } diff --git a/matsim/src/main/java/org/matsim/run/gui/Gui.java b/matsim/src/main/java/org/matsim/run/gui/Gui.java index edbd65e199a..4b826b14842 100644 --- a/matsim/src/main/java/org/matsim/run/gui/Gui.java +++ b/matsim/src/main/java/org/matsim/run/gui/Gui.java @@ -449,8 +449,14 @@ private void startMATSim() { } absoluteClasspath.append(new File(cpPart).getAbsolutePath()); } - String[] cmdArgs = new String[] { txtJvmlocation.getText(), "-cp", absoluteClasspath.toString(), - "-Xmx" + txtRam.getText() + "m", mainClass, txtConfigfilename.getText() }; + String[] cmdArgs = new String[] { txtJvmlocation.getText(), + "-cp", absoluteClasspath.toString(), + "-Xmx" + txtRam.getText() + "m", + "--add-exports", "java.base/java.lang=ALL-UNNAMED", + "--add-exports", "java.desktop/sun.awt=ALL-UNNAMED", + "--add-exports", "java.desktop/sun.java2d=ALL-UNNAMED", + mainClass, txtConfigfilename.getText() }; + // see https://jogamp.org/bugzilla/show_bug.cgi?id=1317#c21 and/or https://github.com/matsim-org/matsim-libs/pull/2940 exeRunner = ExeRunner.run(cmdArgs, textStdOut, textErrOut, new File(txtConfigfilename.getText()).getParent()); int exitcode = exeRunner.waitForFinish(); diff --git a/matsim/src/test/java/org/matsim/core/population/io/ParallelPopulationReaderTest.java b/matsim/src/test/java/org/matsim/core/population/io/ParallelPopulationReaderTest.java new file mode 100644 index 00000000000..ba5efe96823 --- /dev/null +++ b/matsim/src/test/java/org/matsim/core/population/io/ParallelPopulationReaderTest.java @@ -0,0 +1,74 @@ +package org.matsim.core.population.io; + +import org.junit.Assert; +import org.junit.Test; +import org.matsim.api.core.v01.Scenario; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.scenario.ScenarioUtils; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +public class ParallelPopulationReaderTest { + + @Test + public void testParallelPopulationReaderV4_escalateException() { + String xml = """ + + + + + + + + + + + + + + """; + + InputStream stream = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)); + + Scenario scenario = ScenarioUtils.createScenario(ConfigUtils.createConfig()); + try { + new ParallelPopulationReaderMatsimV4(scenario).readStream(stream); + Assert.fail("Expected exception"); + } catch (Exception expected) { + expected.printStackTrace(); + } + } + + @Test + public void testParallelPopulationReaderV6_escalateException() { + String xml = """ + + + + + + + + + + + + + + + """; + + InputStream stream = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)); + + Scenario scenario = ScenarioUtils.createScenario(ConfigUtils.createConfig()); + try { + new ParallelPopulationReaderMatsimV6(null, null, scenario).readStream(stream); + Assert.fail("Expected exception"); + } catch (Exception expected) { + expected.printStackTrace(); + } + } + +} diff --git a/matsim/src/test/java/org/matsim/core/replanning/conflicts/ReplanningWithConflictsTest.java b/matsim/src/test/java/org/matsim/core/replanning/conflicts/ReplanningWithConflictsTest.java new file mode 100644 index 00000000000..d7dcef44001 --- /dev/null +++ b/matsim/src/test/java/org/matsim/core/replanning/conflicts/ReplanningWithConflictsTest.java @@ -0,0 +1,254 @@ +package org.matsim.core.replanning.conflicts; + +import static org.junit.Assert.assertEquals; + +import org.junit.Rule; +import org.junit.Test; +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdSet; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.events.PersonDepartureEvent; +import org.matsim.api.core.v01.events.handler.PersonDepartureEventHandler; +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.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.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.config.groups.ReplanningConfigGroup.StrategySettings; +import org.matsim.core.config.groups.RoutingConfigGroup.TeleportedModeParams; +import org.matsim.core.config.groups.ScoringConfigGroup.ActivityParams; +import org.matsim.core.config.groups.ScoringConfigGroup.ModeParams; +import org.matsim.core.controler.AbstractModule; +import org.matsim.core.controler.Controler; +import org.matsim.core.replanning.strategies.DefaultPlanStrategiesModule.DefaultSelector; +import org.matsim.core.replanning.strategies.DefaultPlanStrategiesModule.DefaultStrategy; +import org.matsim.core.router.TripStructureUtils; +import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.testcases.MatsimTestUtils; + +/** + * @author Sebastian Hörl (sebhoerl), IRT SystemX + */ +public class ReplanningWithConflictsTest { + @Rule + public MatsimTestUtils utils = new MatsimTestUtils(); + + @Test + public void testModeRestriction() { + /* + * This is the possibly simplest use of the the conflict resolution logic. We + * have 100 agents and two modes, one is "unrestricted", the other one is + * "restricted". Each agent has one leg with either mode, all start with an + * "unrestricted" leg. Having an "unrestrited" leg does not give any score, + * having a "restricted" leg gives a score of 1.0. The replanning logic is + * BestScore, so if selection is chosen, a plan with "restricted" will be chosen + * if it exists in the agent memory. For innovation, we use ChangeTripMode, so + * we choose a new random mode. + * + * Without conflicts, this will lead to all agents choosing the "restricted" + * mode eventually, only 10% will choose "unrestricted" because they innovate. + * + * However, we introduce a conflict logic that ensures that we never have more + * than 40 agents using the "restricted" mode. In our simple logic, we iterate + * through the agents and "accept" plans as long as we don't go over this + * threshold. As soon as we hit the limit, we note down the "conflicting" agents + * and let our ConflictResolver return their IDs. The conflict logic will then + * *reject* the plans generated by replanning and switch the agents back to a + * non-conflicting plan (in that case the ones with the "unrestricted" mode). + * + * To make sure that every agent always has a non-conflicting plan in memory, we + * use WorstPlanForRemovalSelectorWithConflicts as the planRemovalSelector. + * + * Running the simulation will make the agents replan and soon we hit the mark + * of 40 agents using the "restricted" mode. After that, the conflict logic will + * start rejecting plans. Note that in this particular logic as a side effect, + * we will also slowly sort out agents so that the 40 first in the population + * will eventually be the ones using the restricted mode. In a more elaborate + * logic we would make sure that, for instance, agents that didn't change their + * plan or were already using initially the restricted mode are treated with + * priority. + * + * This is the most simple set-up of a conflict resolution process. It can be + * adapted to all kind of use cases, for instance, managing parking space, + * matching agents for car-pooling or implementing peer-to-peer car-sharing. + */ + Config config = ConfigUtils.createConfig(); + + config.controller().setOutputDirectory(utils.getOutputDirectory()); + config.controller().setLastIteration(10); + + ActivityParams genericActivityParams = new ActivityParams("generic"); + genericActivityParams.setScoringThisActivityAtAll(false); + config.scoring().addActivityParams(genericActivityParams); + + ModeParams unrestrictedModeParams = new ModeParams("unrestricted_mode"); + unrestrictedModeParams.setConstant(0.0); + config.scoring().addModeParams(unrestrictedModeParams); + + ModeParams restrictedModeParams = new ModeParams("restricted_mode"); + restrictedModeParams.setConstant(1.0); + config.scoring().addModeParams(restrictedModeParams); + + TeleportedModeParams unrestrictedRoutingParams = new TeleportedModeParams("unrestricted_mode"); + unrestrictedRoutingParams.setTeleportedModeSpeed(1.0); + config.routing().addTeleportedModeParams(unrestrictedRoutingParams); + + TeleportedModeParams restrictedRoutingParams = new TeleportedModeParams("restricted_mode"); + restrictedRoutingParams.setTeleportedModeSpeed(1.0); + config.routing().addTeleportedModeParams(restrictedRoutingParams); + + TeleportedModeParams walkRoutingParams = new TeleportedModeParams("walk"); + walkRoutingParams.setTeleportedModeSpeed(1.0); + config.routing().addTeleportedModeParams(walkRoutingParams); + + config.replanning().clearStrategySettings(); + + StrategySettings selectionStrategy = new StrategySettings(); + selectionStrategy.setStrategyName(DefaultSelector.BestScore); + selectionStrategy.setWeight(0.9); + config.replanning().addStrategySettings(selectionStrategy); + + StrategySettings innovationStrategy = new StrategySettings(); + innovationStrategy.setStrategyName(DefaultStrategy.ChangeTripMode); + innovationStrategy.setWeight(0.1); + config.replanning().addStrategySettings(innovationStrategy); + + config.replanning().setPlanSelectorForRemoval(WorstPlanForRemovalSelectorWithConflicts.SELECTOR_NAME); + + config.changeMode().setModes(new String[] { "restricted_mode", "unrestricted_mode" }); + + Scenario scenario = ScenarioUtils.createScenario(config); + + Population population = scenario.getPopulation(); + PopulationFactory populationFactory = population.getFactory(); + + for (int i = 0; i < 100; i++) { + Person person = populationFactory.createPerson(Id.createPersonId("p" + i)); + population.addPerson(person); + + Plan plan = populationFactory.createPlan(); + person.addPlan(plan); + + Activity firstActivity = populationFactory.createActivityFromCoord("generic", new Coord(0.0, 0.0)); + firstActivity.setEndTime(0.0); + plan.addActivity(firstActivity); + + Leg leg = populationFactory.createLeg("unrestricted_mode"); + plan.addLeg(leg); + + Activity secondActivity = populationFactory.createActivityFromCoord("generic", new Coord(0.0, 0.0)); + plan.addActivity(secondActivity); + } + + Network network = scenario.getNetwork(); + NetworkFactory networkFactory = network.getFactory(); + + Node node = networkFactory.createNode(Id.createNodeId("node"), new Coord(0.0, 0.0)); + network.addNode(node); + + Link link = networkFactory.createLink(Id.createLinkId("link"), node, node); + network.addLink(link); + + Controler controller = new Controler(scenario); + + controller.addOverridingModule(new AbstractModule() { + @Override + public void install() { + ConflictModule.bindResolver(binder()).toInstance(new ConflictResolver() { + @Override + public IdSet resolve(Population population, int iteration) { + IdSet conflictingPersonIds = new IdSet<>(Person.class); + + int maximumRestricted = 40; + int restrictedCount = 0; + + for (Person person : population.getPersons().values()) { + Plan plan = person.getSelectedPlan(); + boolean usesRestrictedMode = false; + + for (Leg leg : TripStructureUtils.getLegs(plan)) { + if (leg.getMode().equals("restricted_mode")) { + usesRestrictedMode = true; + break; + } + } + + if (usesRestrictedMode) { + restrictedCount++; + + if (restrictedCount > maximumRestricted) { + conflictingPersonIds.add(person.getId()); + } + } + } + + return conflictingPersonIds; + } + + @Override + public boolean isPotentiallyConflicting(Plan plan) { + for (Leg leg : TripStructureUtils.getLegs(plan)) { + if (leg.getMode().equals("restricted_mode")) { + return true; + } + } + + return false; + } + + @Override + public String getName() { + return "restriction"; + } + }); + } + }); + + LegCounter counter = new LegCounter(); + counter.install(controller); + + controller.run(); + + assertEquals(40, counter.restricted); + assertEquals(60, counter.unrestricted); + } + + private class LegCounter implements PersonDepartureEventHandler { + int restricted; + int unrestricted; + + @Override + public void handleEvent(PersonDepartureEvent event) { + if (event.getLegMode().equals("unrestricted_mode")) { + unrestricted++; + } else if (event.getLegMode().equals("restricted_mode")) { + restricted++; + } + } + + @Override + public void reset(int iteration) { + restricted = 0; + unrestricted = 0; + } + + void install(Controler controller) { + LegCounter self = this; + + controller.addOverridingModule(new AbstractModule() { + @Override + public void install() { + addEventHandlerBinding().toInstance(self); + } + }); + } + } +} diff --git a/pom.xml b/pom.xml index 0e21cbfa639..50656e94ad9 100644 --- a/pom.xml +++ b/pom.xml @@ -30,12 +30,13 @@ 17 - 2.21.1 + 2.22.0 29.2 0.49.1 1.19.0 7.0.0 - 2.15.3 + 2.16.0 + 2.5.0 @@ -56,10 +57,17 @@ true + matsim https://repo.matsim.org/repository/matsim + + + jogl + https://jogamp.org/deployment/maven/ + + @@ -109,7 +117,7 @@ org.apache.commons commons-compress - 1.24.0 + 1.25.0 commons-logging @@ -250,16 +258,22 @@ 3.0.2 + + org.jogamp.gluegen - gluegen-rt - 2.4.0-matsim-1 + + gluegen-rt-main + + ${jogl.version} org.jogamp.jogl - jogl-all - 2.4.0-matsim-1 + + jogl-all-main + + ${jogl.version} @@ -309,7 +323,7 @@ net.bytebuddy byte-buddy - 1.14.9 + 1.14.10 test