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